feat(cross-cutting): added multi-syntax in-band tool-call support for runtime tool conversion

- Added optional Agent and SDK tool-call syntax controls (`toolCallSyntax`, `PI_OWNED_TOOLS`) for owned calls.
- Added in-band grammar scanners and renderers for Anthropic, DeepSeek, GLM, Hermes, Kimi, PI, and Qwen3.
- Added supportsTools propagation and model schema updates to route unsupported models to fallback syntax.
- Replaced stream-markup parsing with syntax-specific in-band scanners and event conversion.
This commit is contained in:
can1357
2026-06-15 06:44:54 +02:00
parent 907bc9979e
commit fbba331f8a
66 changed files with 7528 additions and 543 deletions
@@ -639,6 +639,11 @@ class LiveProgress {
const metaParts = [op, target].filter((v): v is string => Boolean(v));
const meta = metaParts.length > 0 ? paint(ANSI.dim, metaParts.join(" ")) : "";
console.log(` ${tag}${meta ? ` ${meta}` : ""} ${clipped}`);
if (failure.rawBlock) {
const rawLine = failure.rawBlock.replace(/\s+/g, " ").trim();
const clippedRaw = rawLine.length > 240 ? `${rawLine.slice(0, 237)}...` : rawLine;
console.log(` ${paint(ANSI.dim, "raw")} ${clippedRaw}`);
}
}
}
@@ -780,10 +780,16 @@ export interface ToolCallStats {
totalInputChars: number;
}
interface PendingEditCall {
args: unknown;
rawBlock?: string;
}
export interface EditFailure {
toolCallId: string;
args: unknown;
error: string;
rawBlock?: string;
category?: EditFailureCategory;
}
@@ -1196,9 +1202,16 @@ async function runSingleTask(
});
break;
}
const pendingEdits = new Map<string, unknown>();
const pendingEdits = new Map<string, PendingEditCall>();
const rawToolBlocks = new Map<string, string>();
for (const event of events) {
if (event.type === "message_end") {
for (const raw of extractAssistantToolRawBlocks(event)) {
rawToolBlocks.set(raw.id, raw.rawBlock);
const pending = pendingEdits.get(raw.id);
if (pending) pending.rawBlock = raw.rawBlock;
}
}
if (event.type === "tool_execution_start") {
const e = event as { toolName?: string; toolCallId?: string; args?: unknown };
const toolName = e.toolName;
@@ -1206,7 +1219,8 @@ async function runSingleTask(
toolStats.read++;
} else if (isEditTool(toolName)) {
toolStats.edit++;
if (e.toolCallId) pendingEdits.set(e.toolCallId, e.args);
if (e.toolCallId)
pendingEdits.set(e.toolCallId, { args: e.args, rawBlock: rawToolBlocks.get(e.toolCallId) });
} else if (toolName === "write") {
toolStats.write++;
}
@@ -1218,7 +1232,8 @@ async function runSingleTask(
} else if (event.type === "tool_execution_end") {
const e = event as { toolName?: string; toolCallId?: string; isError?: boolean; result?: unknown };
if (isEditTool(e.toolName) && e.toolCallId && pendingEdits.has(e.toolCallId)) {
const args = pendingEdits.get(e.toolCallId) ?? null;
const pendingEdit = pendingEdits.get(e.toolCallId) ?? { args: null };
const args = pendingEdit.args;
pendingEdits.delete(e.toolCallId);
if (config.editVariant === "hashline" && args) {
const counts = countHashlineEditSubtypes(args);
@@ -1238,6 +1253,7 @@ async function runSingleTask(
toolCallId: e.toolCallId,
args,
error,
rawBlock: pendingEdit.rawBlock,
category: categorizeEditFailure(error, args),
});
} else {
@@ -1410,6 +1426,27 @@ function extractToolErrorMessage(result: unknown): string {
}
}
function extractAssistantToolRawBlocks(event: {
type: string;
[key: string]: unknown;
}): Array<{ id: string; rawBlock: string }> {
const message = event.message;
if (message === null || typeof message !== "object") return [];
const role = (message as { role?: unknown }).role;
if (role !== "assistant") return [];
const content = (message as { content?: unknown }).content;
if (!Array.isArray(content)) return [];
const rawBlocks: Array<{ id: string; rawBlock: string }> = [];
for (const block of content) {
if (block === null || typeof block !== "object") continue;
const typedBlock = block as { type?: unknown; id?: unknown; rawBlock?: unknown };
if (typedBlock.type !== "toolCall") continue;
if (typeof typedBlock.id !== "string" || typeof typedBlock.rawBlock !== "string") continue;
rawBlocks.push({ id: typedBlock.id, rawBlock: typedBlock.rawBlock });
}
return rawBlocks;
}
function shuffle<T>(items: T[]): T[] {
const copy = items.slice();
for (let i = copy.length - 1; i > 0; i--) {