tool verbose

This commit is contained in:
wassname
2026-04-19 15:41:15 +08:00
parent e7e3e86550
commit 5aa37b7a99
7 changed files with 515 additions and 76 deletions
+39 -3
View File
@@ -62,6 +62,8 @@ export interface TelegramMenuEffectPort {
setCurrentModel: (model: Model<any>) => void;
setThinkingLevel: (level: ThinkingLevel) => void;
getCurrentThinkingLevel: () => ThinkingLevel;
setTraceVisible: (traceVisible: boolean) => void;
getTraceVisible: () => boolean;
stagePendingModelSwitch: (selection: ScopedTelegramModel) => void;
restartInterruptedTelegramTurn: (
selection: ScopedTelegramModel,
@@ -70,7 +72,12 @@ export interface TelegramMenuEffectPort {
export type TelegramStatusMenuCallbackDeps = Pick<
TelegramMenuEffectPort,
"updateModelMenuMessage" | "updateThinkingMenuMessage" | "answerCallbackQuery"
| "updateModelMenuMessage"
| "updateThinkingMenuMessage"
| "updateStatusMessage"
| "setTraceVisible"
| "getTraceVisible"
| "answerCallbackQuery"
>;
export type TelegramThinkingMenuCallbackDeps = Pick<
@@ -121,7 +128,7 @@ export interface BuildTelegramModelMenuStateParams {
export type TelegramMenuCallbackAction =
| { kind: "ignore" }
| { kind: "status"; action: "model" | "thinking" }
| { kind: "status"; action: "model" | "thinking" | "trace" }
| { kind: "thinking:set"; level: string }
| {
kind: "model";
@@ -451,6 +458,9 @@ export function parseTelegramMenuCallbackAction(
if (data === "status:thinking") {
return { kind: "status", action: "thinking" };
}
if (data === "status:trace") {
return { kind: "status", action: "trace" };
}
if (data?.startsWith("thinking:set:")) {
return {
kind: "thinking:set",
@@ -665,6 +675,16 @@ export async function handleTelegramStatusMenuCallbackAction(
await deps.answerCallbackQuery(callbackQueryId);
return true;
}
if (action.kind === "status" && action.action === "trace") {
const nextTraceVisible = !deps.getTraceVisible();
deps.setTraceVisible(nextTraceVisible);
await deps.updateStatusMessage();
await deps.answerCallbackQuery(
callbackQueryId,
`Trace: ${nextTraceVisible ? "on" : "off"}`,
);
return true;
}
if (!(action.kind === "status" && action.action === "thinking")) {
return false;
}
@@ -793,6 +813,7 @@ export function buildThinkingMenuReplyMarkup(
export function buildStatusReplyMarkup(
activeModel: Model<any> | undefined,
currentThinkingLevel: ThinkingLevel,
traceVisible: boolean,
): TelegramReplyMarkup {
const rows: Array<Array<{ text: string; callback_data: string }>> = [];
rows.push([
@@ -804,6 +825,12 @@ export function buildStatusReplyMarkup(
callback_data: "status:model",
},
]);
rows.push([
{
text: formatStatusButtonLabel("Trace", traceVisible ? "on" : "off"),
callback_data: "status:trace",
},
]);
if (activeModel?.reasoning) {
rows.push([
{
@@ -847,12 +874,17 @@ export function buildTelegramStatusMenuRenderPayload(
statusText: string,
activeModel: Model<any> | undefined,
currentThinkingLevel: ThinkingLevel,
traceVisible: boolean,
): TelegramMenuRenderPayload {
return {
nextMode: "status",
text: statusText,
mode: "html",
replyMarkup: buildStatusReplyMarkup(activeModel, currentThinkingLevel),
replyMarkup: buildStatusReplyMarkup(
activeModel,
currentThinkingLevel,
traceVisible,
),
};
}
@@ -897,12 +929,14 @@ export async function updateTelegramStatusMessage(
statusText: string,
activeModel: Model<any> | undefined,
currentThinkingLevel: ThinkingLevel,
traceVisible: boolean,
deps: TelegramMenuMessageRuntimeDeps,
): Promise<void> {
const payload = buildTelegramStatusMenuRenderPayload(
statusText,
activeModel,
currentThinkingLevel,
traceVisible,
);
state.mode = payload.nextMode;
await deps.editInteractiveMessage(
@@ -919,12 +953,14 @@ export async function sendTelegramStatusMessage(
statusText: string,
activeModel: Model<any> | undefined,
currentThinkingLevel: ThinkingLevel,
traceVisible: boolean,
deps: TelegramMenuMessageRuntimeDeps,
): Promise<number | undefined> {
const payload = buildTelegramStatusMenuRenderPayload(
statusText,
activeModel,
currentThinkingLevel,
traceVisible,
);
state.mode = payload.nextMode;
return deps.sendInteractiveMessage(
+19 -7
View File
@@ -8,11 +8,28 @@ import type {
ExtensionCommandContext,
ExtensionContext,
} from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { queueTelegramAttachments } from "./attachments.ts";
import type { PendingTelegramTurn } from "./queue.ts";
function buildAttachmentParametersSchema(maxAttachmentsPerTurn: number) {
return {
type: "object",
properties: {
paths: {
type: "array",
items: {
type: "string",
description: "Local file path to attach",
},
minItems: 1,
maxItems: maxAttachmentsPerTurn,
},
},
required: ["paths"],
};
}
// --- Tool Registration ---
export interface TelegramAttachmentToolRegistrationDeps {
@@ -34,12 +51,7 @@ export function registerTelegramAttachmentTool(
promptGuidelines: [
"When handling a [telegram] message and the user asked for a file or generated artifact, call telegram_attach with the local path instead of only mentioning the path in text.",
],
parameters: Type.Object({
paths: Type.Array(
Type.String({ description: "Local file path to attach" }),
{ minItems: 1, maxItems: deps.maxAttachmentsPerTurn },
),
}),
parameters: buildAttachmentParametersSchema(deps.maxAttachmentsPerTurn),
async execute(_toolCallId, params) {
return queueTelegramAttachments({
activeTurn: deps.getActiveTurn(),
+94
View File
@@ -5,6 +5,100 @@
export const MAX_MESSAGE_LENGTH = 4096;
export type TelegramAssistantDisplayBlock =
| { type: "text"; text: string }
| { type: "thinking"; text: string }
| { type: "tool_call"; name: string; argsText?: string };
function truncateDisplayText(text: string, maxLength: number): string {
if (text.length <= maxLength) return text;
return `${text.slice(0, Math.max(0, maxLength - 1))}`;
}
function normalizePreviewInlineText(text: string): string {
return renderMarkdownPreviewText(text).replace(/\s+/g, " ").trim();
}
function renderTracePreviewLine(block: TelegramAssistantDisplayBlock): string | undefined {
if (block.type === "text") return undefined;
if (block.type === "thinking") {
const summary = normalizePreviewInlineText(block.text);
if (!summary) return undefined;
return `[thinking] ${truncateDisplayText(summary, 120)}`;
}
const parts = [`[tool] ${block.name}`];
if (block.argsText?.trim()) {
const summary = normalizePreviewInlineText(block.argsText);
if (summary) parts.push(summary);
}
return truncateDisplayText(parts.join(" "), 160);
}
function renderMarkdownQuote(text: string): string {
return text
.split(/\r?\n/)
.map((line) => `> ${line.length > 0 ? line : "\u00A0"}`)
.join("\n");
}
function renderToolArgsMarkdown(argsText: string): string {
const trimmed = argsText.trim();
if (trimmed.length === 0) return "";
if (trimmed.includes("\n") || trimmed.length > 120) {
return `\n\n\`\`\`json\n${trimmed}\n\`\`\``;
}
return ` ${"`"}${trimmed}${"`"}`;
}
export function buildTelegramAssistantPreviewText(
blocks: TelegramAssistantDisplayBlock[],
traceVisible: boolean,
): string {
const sections: string[] = [];
const text = blocks
.filter((block): block is Extract<TelegramAssistantDisplayBlock, { type: "text" }> => block.type === "text")
.map((block) => block.text)
.join("")
.trim();
if (text) {
sections.push(text);
}
if (traceVisible) {
const traceLines = blocks
.map(renderTracePreviewLine)
.filter((line): line is string => !!line);
if (traceLines.length > 0) {
sections.push(traceLines.join("\n"));
}
}
return sections.join("\n\n").trim();
}
export function buildTelegramAssistantTranscriptMarkdown(
blocks: TelegramAssistantDisplayBlock[],
traceVisible: boolean,
): string {
const sections: string[] = [];
for (const block of blocks) {
if (block.type === "text") {
const trimmed = block.text.trim();
if (trimmed) sections.push(trimmed);
continue;
}
if (!traceVisible) continue;
if (block.type === "thinking") {
const trimmed = block.text.trim();
if (!trimmed) continue;
sections.push(`**Thinking**\n${renderMarkdownQuote(trimmed)}`);
continue;
}
sections.push(
`**Tool call** ${"`"}${block.name}${"`"}${block.argsText ? renderToolArgsMarkdown(block.argsText) : ""}`,
);
}
return sections.join("\n\n").trim();
}
// --- Escaping ---
function escapeHtml(text: string): string {
+2
View File
@@ -87,6 +87,7 @@ function buildContextSummary(
export function buildStatusHtml(
ctx: ExtensionContext,
activeModel: Model<any> | undefined,
traceVisible: boolean,
): string {
const stats = collectUsageStats(ctx);
const usesSubscription = activeModel
@@ -102,6 +103,7 @@ export function buildStatusHtml(
lines.push(buildStatusRow("Cost", costSummary));
}
lines.push(buildStatusRow("Context", buildContextSummary(ctx, activeModel)));
lines.push(buildStatusRow("Trace", traceVisible ? "on" : "off"));
if (lines.length === 0) {
lines.push(buildStatusRow("Status", "No usage data yet."));
}