@pentoshi/clai 3.12.1 → 3.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/agent/compaction-summary.d.ts +20 -12
- package/dist/agent/compaction-summary.js +72 -14
- package/dist/agent/compaction-summary.js.map +1 -1
- package/dist/agent/context-manager.d.ts +2 -0
- package/dist/agent/context-manager.js +64 -35
- package/dist/agent/context-manager.js.map +1 -1
- package/dist/agent/events.d.ts +1 -0
- package/dist/agent/message-slim.d.ts +6 -18
- package/dist/agent/message-slim.js +41 -23
- package/dist/agent/message-slim.js.map +1 -1
- package/dist/agent/runner.js +133 -97
- package/dist/agent/runner.js.map +1 -1
- package/dist/app/adapters/agent-event-adapter.js +1 -0
- package/dist/app/adapters/agent-event-adapter.js.map +1 -1
- package/dist/app/controllers/session-compact-helper.d.ts +3 -1
- package/dist/app/controllers/session-compact-helper.js +86 -42
- package/dist/app/controllers/session-compact-helper.js.map +1 -1
- package/dist/app/controllers/session-context-usage.js +0 -2
- package/dist/app/controllers/session-context-usage.js.map +1 -1
- package/dist/app/controllers/session-controller.js +3 -0
- package/dist/app/controllers/session-controller.js.map +1 -1
- package/dist/app/events/app-event.d.ts +1 -0
- package/dist/app/events/app-event.js.map +1 -1
- package/dist/prompts/embedded.js +1 -1
- package/dist/prompts/embedded.js.map +1 -1
- package/dist/prompts/index.js +3 -3
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/system.agent.md +2 -2
- package/dist/tools/elevated-shell.d.ts +4 -29
- package/dist/tools/elevated-shell.js +46 -71
- package/dist/tools/elevated-shell.js.map +1 -1
- package/dist/tools/registry.js +47 -17
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/shell.d.ts +2 -14
- package/dist/tools/shell.js +19 -43
- package/dist/tools/shell.js.map +1 -1
- package/dist/tui/state.js +5 -5
- package/dist/tui/state.js.map +1 -1
- package/dist/tui-v2/components/status/status-line.d.ts +1 -0
- package/dist/tui-v2/components/status/status-line.js +62 -3
- package/dist/tui-v2/components/status/status-line.js.map +1 -1
- package/dist/tui-v2/rendering/incremental-strip.js +1 -1
- package/dist/tui-v2/rendering/incremental-strip.js.map +1 -1
- package/dist/tui-v2/rendering/strip-tool-surfaces.d.ts +0 -8
- package/dist/tui-v2/rendering/strip-tool-surfaces.js +31 -17
- package/dist/tui-v2/rendering/strip-tool-surfaces.js.map +1 -1
- package/dist/tui-v2/state/transcript-compaction.js +1 -4
- package/dist/tui-v2/state/transcript-compaction.js.map +1 -1
- package/dist/tui-v2/state/transcript-reducer.js +3 -1
- package/dist/tui-v2/state/transcript-reducer.js.map +1 -1
- package/dist/version.generated.d.ts +2 -2
- package/dist/version.generated.js +2 -2
- package/package.json +1 -1
|
@@ -1,33 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cap in-memory / re-sent message bulk from native toolCalls.
|
|
3
|
-
*
|
|
4
|
-
* fs.write / writeMany put entire file bodies in tool-call args. Those were
|
|
5
|
-
* stored verbatim in history and LoopGuard signatures, so a scaffold turn
|
|
6
|
-
* could hold many multi-MB strings — memory climbed into the multi-GB range
|
|
7
|
-
* and Macs heated under GC. Full file bodies remain on disk; tool results
|
|
8
|
-
* still report paths/bytes.
|
|
9
|
-
*/
|
|
10
1
|
import { createHash } from "node:crypto";
|
|
11
|
-
/** Strings at or above this are replaced with a length+hash stub. */
|
|
12
2
|
export const SLIM_ARG_STRING_CHARS = 400;
|
|
13
|
-
/** Hard ceiling for a single string kept as-is in history (safety). */
|
|
14
3
|
export const SLIM_ARG_ABSOLUTE_MAX_CHARS = 8_000;
|
|
15
|
-
/** Max depth when walking args trees (writeMany files[]). */
|
|
16
4
|
const SLIM_MAX_DEPTH = 6;
|
|
5
|
+
const BULK_ARG_KEYS = new Set(["content", "body"]);
|
|
6
|
+
const ELIDED_STUB_PATTERN = /^«\d+ chars sha256=[0-9a-f]{12}(?:\s+—.*)?»$/s;
|
|
17
7
|
function shortHash(text) {
|
|
18
8
|
return createHash("sha256").update(text).digest("hex").slice(0, 12);
|
|
19
9
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export function slimValue(value, depth = 0) {
|
|
10
|
+
function slimLimitForKey(key) {
|
|
11
|
+
return key !== undefined && BULK_ARG_KEYS.has(key)
|
|
12
|
+
? SLIM_ARG_STRING_CHARS
|
|
13
|
+
: SLIM_ARG_ABSOLUTE_MAX_CHARS;
|
|
14
|
+
}
|
|
15
|
+
export function slimValue(value, depth = 0, key) {
|
|
26
16
|
if (typeof value === "string") {
|
|
27
|
-
if (value.length <
|
|
17
|
+
if (value.length < slimLimitForKey(key))
|
|
28
18
|
return value;
|
|
29
19
|
const hash = shortHash(value);
|
|
30
|
-
return `«${value.length} chars sha256=${hash}»`;
|
|
20
|
+
return `«${value.length} chars sha256=${hash} — elided from history; never reuse this stub, regenerate the full value»`;
|
|
31
21
|
}
|
|
32
22
|
if (value === null || value === undefined)
|
|
33
23
|
return value;
|
|
@@ -42,8 +32,8 @@ export function slimValue(value, depth = 0) {
|
|
|
42
32
|
return value.map((entry) => slimValue(entry, depth + 1));
|
|
43
33
|
}
|
|
44
34
|
const out = {};
|
|
45
|
-
for (const
|
|
46
|
-
out[
|
|
35
|
+
for (const childKey of Object.keys(value).sort()) {
|
|
36
|
+
out[childKey] = slimValue(value[childKey], depth + 1, childKey);
|
|
47
37
|
}
|
|
48
38
|
return out;
|
|
49
39
|
}
|
|
@@ -54,7 +44,35 @@ export function slimToolArgs(args) {
|
|
|
54
44
|
}
|
|
55
45
|
return {};
|
|
56
46
|
}
|
|
57
|
-
|
|
47
|
+
export function findElidedStubArg(value, path = "args", depth = 0) {
|
|
48
|
+
if (typeof value === "string") {
|
|
49
|
+
return ELIDED_STUB_PATTERN.test(value) ? { key: path, value } : undefined;
|
|
50
|
+
}
|
|
51
|
+
if (value === null || value === undefined || typeof value !== "object") {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
if (depth >= SLIM_MAX_DEPTH)
|
|
55
|
+
return undefined;
|
|
56
|
+
if (Array.isArray(value)) {
|
|
57
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
58
|
+
const found = findElidedStubArg(value[i], `${path}[${i}]`, depth + 1);
|
|
59
|
+
if (found)
|
|
60
|
+
return found;
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
65
|
+
const found = findElidedStubArg(childValue, path ? `${path}.${childKey}` : childKey, depth + 1);
|
|
66
|
+
if (found)
|
|
67
|
+
return found;
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
export function elidedStubReuseMessage(key) {
|
|
72
|
+
return (`Tool call rejected: argument "${key}" is an elided history placeholder («N chars sha256=…»), not a real value. ` +
|
|
73
|
+
"Compressed history replaces long arguments with those stubs and the original text cannot be recovered from them. " +
|
|
74
|
+
"Re-issue the tool call with the complete literal value — never copy «…» stubs from earlier context.");
|
|
75
|
+
}
|
|
58
76
|
export function measureToolCallsChars(toolCalls) {
|
|
59
77
|
if (!toolCalls?.length)
|
|
60
78
|
return 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-slim.js","sourceRoot":"","sources":["../../src/agent/message-slim.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"message-slim.js","sourceRoot":"","sources":["../../src/agent/message-slim.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/runner.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { streamWithProvider } from "../llm/router.js";
|
|
4
|
+
import { modelContextWindow } from "../llm/token-usage.js";
|
|
4
5
|
import { streamAlreadyEmitted } from "../llm/stream-progress.js";
|
|
5
6
|
import { classifyStreamFailure, planStreamRecovery, recordRecoveryAttempt, createStreamRecoveryState, resetStreamRecoveryState, } from "./stream-recovery.js";
|
|
6
7
|
import { modelSupportsVision, resolveToolDialect } from "../llm/capabilities.js";
|
|
@@ -38,6 +39,7 @@ function safeEngagementActionsForToolCall(call) {
|
|
|
38
39
|
}
|
|
39
40
|
import { availableToolNames, normalizeToolCall, runToolCall, BATCH_SAFE_TOOLS, } from "../tools/registry.js";
|
|
40
41
|
import { getToolDefinitions, getCompactToolDefinitions, RUNNER_META_TOOL_NAMES, } from "../tools/definitions.js";
|
|
42
|
+
import { elidedStubReuseMessage, findElidedStubArg, } from "./message-slim.js";
|
|
41
43
|
import { appendAssistantWithTools, ensureUniqueToolCallIds, toolCallIdsInHistory, appendToolResult, assertValidToolProtocol, fillMissingToolResults, repairToolProtocol, } from "./tool-history.js";
|
|
42
44
|
import { formatViewportHint, registerViewport } from "../ui/output-pane.js";
|
|
43
45
|
import { compactMessagesWithSummary, shouldApplyAutoCompact, COMPACTION_MEMORY_PREFIX, PLAN_IMPLEMENT_MEMORY_PREFIX, isCompactionMemoryMessage, } from "./context-manager.js";
|
|
@@ -55,7 +57,7 @@ import { analyzeTask, formatTaskAnalysisHint, isNarrowExplicitNmapOperation, } f
|
|
|
55
57
|
import { computeMaxIterations, computeStepBudget } from "./step-budget.js";
|
|
56
58
|
import { isScratchOnlyWrite } from "./scratch-write.js";
|
|
57
59
|
import { buildDurableEnvelope, WorkLedger, } from "./durable-envelope.js";
|
|
58
|
-
import { COMPACTION_SYSTEM_PROMPT, COMPACTION_MAX_COMPLETION_TOKENS, COMPACTION_MAP_MAX_COMPLETION_TOKENS, } from "./compaction-summary.js";
|
|
60
|
+
import { buildCompactionRetryPrompt, compactionSinglePassInputBudget, COMPACTION_SYSTEM_PROMPT, COMPACTION_MAX_COMPLETION_TOKENS, COMPACTION_MAP_MAX_COMPLETION_TOKENS, isCompactionCompletionTruncated, looksLikeIncompleteCompactionSummary, looksLikeTranscriptReplay, normalizeCompactionSummary, } from "./compaction-summary.js";
|
|
59
61
|
import { maybeAppendPlanModeReminder, PLAN_REMINDER_TOAST, } from "./plan-mode-reminders.js";
|
|
60
62
|
import { LoopGuard } from "./loop-guard.js";
|
|
61
63
|
import { appendInterruptedReasoning, interruptedReasoningBrief, isMeaningfulResumptionYield, } from "./interrupted-reasoning.js";
|
|
@@ -315,12 +317,21 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
315
317
|
process.stdout.write(chalk.dim(" ✦ Compacted Context · streaming Markdown\n\n"));
|
|
316
318
|
}
|
|
317
319
|
};
|
|
318
|
-
const writeCompactionDelta = (id, text) => {
|
|
319
|
-
if (!text)
|
|
320
|
+
const writeCompactionDelta = (id, text, replace = false) => {
|
|
321
|
+
if (!text && !replace)
|
|
320
322
|
return;
|
|
321
|
-
emit({
|
|
322
|
-
|
|
323
|
-
|
|
323
|
+
emit({
|
|
324
|
+
type: "compaction-delta",
|
|
325
|
+
id,
|
|
326
|
+
text,
|
|
327
|
+
...(replace ? { replace: true } : {}),
|
|
328
|
+
});
|
|
329
|
+
if (writesDirectly) {
|
|
330
|
+
if (replace)
|
|
331
|
+
process.stdout.write(chalk.dim("\n ↻ rewriting compacted context\n\n"));
|
|
332
|
+
if (text)
|
|
333
|
+
process.stdout.write(text);
|
|
334
|
+
}
|
|
324
335
|
};
|
|
325
336
|
const writeCompactionCompleted = (id, summary, beforeTokens, afterTokens) => {
|
|
326
337
|
emit({
|
|
@@ -1255,6 +1266,13 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1255
1266
|
emitToolResult(toolEventId, result, reason);
|
|
1256
1267
|
return { ok: false, call, result, contextOutput: reason };
|
|
1257
1268
|
}
|
|
1269
|
+
const elidedStub = findElidedStubArg(call.args);
|
|
1270
|
+
if (elidedStub) {
|
|
1271
|
+
const reason = elidedStubReuseMessage(elidedStub.key);
|
|
1272
|
+
const result = { ok: false, output: reason, exitCode: 1 };
|
|
1273
|
+
emitToolResult(toolEventId, result, reason);
|
|
1274
|
+
return { ok: false, call, result, contextOutput: reason };
|
|
1275
|
+
}
|
|
1258
1276
|
if (call.name === "image.ocr" && !imageOcrEnabled) {
|
|
1259
1277
|
writeNotice("info", "skipped OCR because the original image is attached to the vision model", chalk.dim(" ℹ skipped OCR — inspecting the attached image directly\n"));
|
|
1260
1278
|
const recoveryText = "The original image is attached to this message and you can inspect it directly. " +
|
|
@@ -2773,50 +2791,105 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
2773
2791
|
const summarizeForCompaction = async (summaryPrompt, stage) => {
|
|
2774
2792
|
const streamFinalSummary = stage?.phase !== "map";
|
|
2775
2793
|
const compactionId = streamFinalSummary ? activeCompactionId : undefined;
|
|
2776
|
-
const
|
|
2777
|
-
?
|
|
2794
|
+
const maxTokens = stage?.phase === "map"
|
|
2795
|
+
? COMPACTION_MAP_MAX_COMPLETION_TOKENS
|
|
2796
|
+
: COMPACTION_MAX_COMPLETION_TOKENS;
|
|
2797
|
+
const sourceMessages = stage?.sourceMessages;
|
|
2798
|
+
const compactionTools = sourceMessages
|
|
2799
|
+
? selectToolDefs(nativeToolsActive, useCompactSystemPrompt)
|
|
2778
2800
|
: undefined;
|
|
2779
2801
|
const request = {
|
|
2780
2802
|
provider,
|
|
2781
2803
|
model,
|
|
2782
|
-
messages:
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2804
|
+
messages: sourceMessages
|
|
2805
|
+
? [
|
|
2806
|
+
...sourceMessages,
|
|
2807
|
+
{ role: "user", content: summaryPrompt },
|
|
2808
|
+
]
|
|
2809
|
+
: [
|
|
2810
|
+
{ role: "system", content: COMPACTION_SYSTEM_PROMPT },
|
|
2811
|
+
{ role: "user", content: summaryPrompt },
|
|
2812
|
+
],
|
|
2786
2813
|
temperature: 0.1,
|
|
2787
|
-
maxTokens
|
|
2788
|
-
? COMPACTION_MAP_MAX_COMPLETION_TOKENS
|
|
2789
|
-
: COMPACTION_MAX_COMPLETION_TOKENS,
|
|
2814
|
+
maxTokens,
|
|
2790
2815
|
thinking: { enabled: false, effort: "none" },
|
|
2791
2816
|
signal: options.signal,
|
|
2792
2817
|
allowModelFallback: true,
|
|
2818
|
+
...(compactionTools?.length
|
|
2819
|
+
? {
|
|
2820
|
+
tools: compactionTools,
|
|
2821
|
+
toolChoice: "none",
|
|
2822
|
+
}
|
|
2823
|
+
: {}),
|
|
2793
2824
|
};
|
|
2794
|
-
const
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2825
|
+
const runAttempt = async (attemptRequest, replace = false) => {
|
|
2826
|
+
if (compactionId && replace) {
|
|
2827
|
+
writeCompactionDelta(compactionId, "", true);
|
|
2828
|
+
}
|
|
2829
|
+
const parser = createThinkingStreamParser((text) => {
|
|
2830
|
+
if (compactionId)
|
|
2831
|
+
writeCompactionDelta(compactionId, text);
|
|
2832
|
+
}, undefined, { remember: false });
|
|
2833
|
+
const result = await streamWithProvider(attemptRequest, (token) => parser.push(token), { onStatus: () => undefined, maxRetries: 0 });
|
|
2834
|
+
parser.finish();
|
|
2835
|
+
return result;
|
|
2836
|
+
};
|
|
2837
|
+
const first = await runAttempt(request);
|
|
2838
|
+
let visible = normalizeCompactionSummary(stripThinking(first.text).visible);
|
|
2839
|
+
let retryReason;
|
|
2840
|
+
if (isCompactionCompletionTruncated(first, maxTokens)) {
|
|
2841
|
+
retryReason = "truncated";
|
|
2842
|
+
}
|
|
2843
|
+
else if (!visible) {
|
|
2844
|
+
retryReason = "reasoning-only";
|
|
2845
|
+
}
|
|
2846
|
+
else if (looksLikeTranscriptReplay(visible)) {
|
|
2847
|
+
retryReason = "replayed";
|
|
2848
|
+
}
|
|
2849
|
+
else if (looksLikeIncompleteCompactionSummary(visible)) {
|
|
2850
|
+
retryReason = "incomplete";
|
|
2851
|
+
}
|
|
2852
|
+
if (retryReason) {
|
|
2853
|
+
const retry = await runAttempt({
|
|
2854
|
+
...request,
|
|
2855
|
+
messages: sourceMessages
|
|
2856
|
+
? [
|
|
2857
|
+
...sourceMessages,
|
|
2858
|
+
{
|
|
2859
|
+
role: "user",
|
|
2860
|
+
content: buildCompactionRetryPrompt(summaryPrompt, retryReason),
|
|
2861
|
+
},
|
|
2862
|
+
]
|
|
2863
|
+
: [
|
|
2864
|
+
{
|
|
2865
|
+
role: "system",
|
|
2866
|
+
content: `${COMPACTION_SYSTEM_PROMPT}\nReturn only a complete continuation-memory summary. Do not include analysis, reasoning, or <think> tags.`,
|
|
2867
|
+
},
|
|
2868
|
+
{
|
|
2869
|
+
role: "user",
|
|
2870
|
+
content: buildCompactionRetryPrompt(summaryPrompt, retryReason),
|
|
2871
|
+
},
|
|
2872
|
+
],
|
|
2873
|
+
temperature: 0,
|
|
2874
|
+
maxTokens,
|
|
2875
|
+
thinking: { enabled: false, effort: "none" },
|
|
2876
|
+
allowModelFallback: true,
|
|
2877
|
+
}, true);
|
|
2878
|
+
if (isCompactionCompletionTruncated(retry, maxTokens)) {
|
|
2879
|
+
throw new Error("compaction failed: model hit the summary output limit twice — original context retained");
|
|
2880
|
+
}
|
|
2881
|
+
visible = normalizeCompactionSummary(stripThinking(retry.text).visible);
|
|
2882
|
+
if (!visible) {
|
|
2883
|
+
throw new Error("compaction failed: model returned an empty summary");
|
|
2884
|
+
}
|
|
2885
|
+
if (looksLikeTranscriptReplay(visible)) {
|
|
2886
|
+
throw new Error("compaction failed: model replayed the transcript twice — original context retained");
|
|
2887
|
+
}
|
|
2888
|
+
if (looksLikeIncompleteCompactionSummary(visible)) {
|
|
2889
|
+
throw new Error("compaction failed: model returned an incomplete summary twice — original context retained");
|
|
2890
|
+
}
|
|
2818
2891
|
}
|
|
2819
|
-
return
|
|
2892
|
+
return visible;
|
|
2820
2893
|
};
|
|
2821
2894
|
/**
|
|
2822
2895
|
* Estimate the complete next model request, including attached native-tool
|
|
@@ -2909,9 +2982,12 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
2909
2982
|
activeCompactionId = compactionId;
|
|
2910
2983
|
writeCompactionStarted(compactionId, beforeTokens);
|
|
2911
2984
|
try {
|
|
2985
|
+
const compactionTools = selectToolDefs(nativeToolsActive, useCompactSystemPrompt);
|
|
2986
|
+
const compactionSchemaTokens = buildContextBreakdown([], compactionTools).estimatedTotalTokens;
|
|
2912
2987
|
const result = await compactMessagesWithSummary(messages, summarizeForCompaction, {
|
|
2913
2988
|
budgetTokens: 0,
|
|
2914
2989
|
keepRecent: AUTO_COMPACT_KEEP_RECENT,
|
|
2990
|
+
singlePassInputBudgetTokens: Math.max(0, compactionSinglePassInputBudget(contextLimitTokens ?? modelContextWindow(model, provider)) - compactionSchemaTokens),
|
|
2915
2991
|
...(durableEnvelope ? { durableEnvelope } : {}),
|
|
2916
2992
|
});
|
|
2917
2993
|
const summaryBody = result.messages.find((m) => isCompactionMemoryMessage(m))?.content ??
|
|
@@ -3098,6 +3174,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3098
3174
|
}, 10_000);
|
|
3099
3175
|
heartbeat.unref?.();
|
|
3100
3176
|
const deferredToolCalls = [];
|
|
3177
|
+
const streamedNativeCallNames = new Map();
|
|
3101
3178
|
const deltaParser = writesDirectly
|
|
3102
3179
|
? undefined
|
|
3103
3180
|
: createThinkingStreamParser((text) => emit({ type: "assistant-delta", text }), (text) => {
|
|
@@ -3208,64 +3285,22 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3208
3285
|
tools: turnTools,
|
|
3209
3286
|
toolChoice: "auto",
|
|
3210
3287
|
parallelToolCalls: true,
|
|
3211
|
-
// P2-3: emit tool cards as soon as the function name arrives.
|
|
3212
3288
|
onToolCallDelta: (delta) => {
|
|
3213
3289
|
if (!delta.name)
|
|
3214
3290
|
return;
|
|
3215
3291
|
const name = fromWireName(delta.name) ?? delta.name;
|
|
3216
|
-
|
|
3217
|
-
if (existing && existing.call.name !== "…") {
|
|
3218
|
-
if (delta.argumentsBytes &&
|
|
3219
|
-
delta.argumentsBytes >= 4096 &&
|
|
3220
|
-
!writesDirectly) {
|
|
3221
|
-
emit({
|
|
3222
|
-
type: "status",
|
|
3223
|
-
text: `${name} (${Math.round(delta.argumentsBytes / 1024)}KB args)`,
|
|
3224
|
-
});
|
|
3225
|
-
}
|
|
3226
|
-
return;
|
|
3227
|
-
}
|
|
3228
|
-
while (deferredToolCalls.length < delta.index) {
|
|
3229
|
-
const slot = deferredToolCalls.length;
|
|
3230
|
-
const placeholderId = `tool-${++nextToolEventId}`;
|
|
3231
|
-
callIds[slot] = placeholderId;
|
|
3232
|
-
deferredToolCalls.push({
|
|
3233
|
-
eventId: placeholderId,
|
|
3234
|
-
call: { name: "…", args: {} },
|
|
3235
|
-
rendered: "",
|
|
3236
|
-
shown: false,
|
|
3237
|
-
});
|
|
3238
|
-
}
|
|
3239
|
-
const call = normalizeToolCall({
|
|
3240
|
-
name,
|
|
3241
|
-
args: {},
|
|
3242
|
-
});
|
|
3243
|
-
const eventId = existing?.eventId ?? `tool-${++nextToolEventId}`;
|
|
3244
|
-
callIds[delta.index] = eventId;
|
|
3245
|
-
alreadyPrintedIds.add(eventId);
|
|
3246
|
-
const toolCallLine = chalk.cyan(` ▶ ${call.name}`) +
|
|
3247
|
-
chalk.gray(` ${formatToolArgs(call)}`);
|
|
3248
|
-
const entry = {
|
|
3249
|
-
eventId,
|
|
3250
|
-
call,
|
|
3251
|
-
rendered: styleToolChatter(call, toolCallLine) + "\n",
|
|
3252
|
-
shown: false,
|
|
3253
|
-
};
|
|
3254
|
-
if (deferredToolCalls.length === delta.index) {
|
|
3255
|
-
deferredToolCalls.push(entry);
|
|
3256
|
-
}
|
|
3257
|
-
else {
|
|
3258
|
-
deferredToolCalls[delta.index] = entry;
|
|
3259
|
-
}
|
|
3260
|
-
streamedCallsCount = Math.max(streamedCallsCount, deferredToolCalls.length);
|
|
3292
|
+
streamedNativeCallNames.set(delta.index, name);
|
|
3261
3293
|
if (!writesDirectly) {
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3294
|
+
emit({
|
|
3295
|
+
type: "status",
|
|
3296
|
+
text: delta.argumentsBytes && delta.argumentsBytes >= 4096
|
|
3297
|
+
? `${name} (${Math.round(delta.argumentsBytes / 1024)}KB args)`
|
|
3298
|
+
: name,
|
|
3299
|
+
});
|
|
3265
3300
|
}
|
|
3266
3301
|
else {
|
|
3267
3302
|
spinner.stop();
|
|
3268
|
-
spinner = startThinkingSpinner(`tool ${
|
|
3303
|
+
spinner = startThinkingSpinner(`tool ${name}…`, options.signal);
|
|
3269
3304
|
}
|
|
3270
3305
|
},
|
|
3271
3306
|
}
|
|
@@ -3283,7 +3318,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3283
3318
|
spinner.stop();
|
|
3284
3319
|
}
|
|
3285
3320
|
while (streamedCallsCount < parsedCalls.length) {
|
|
3286
|
-
const call = parsedCalls[streamedCallsCount];
|
|
3321
|
+
const call = normalizeToolCall(parsedCalls[streamedCallsCount]);
|
|
3287
3322
|
const eventId = `tool-${++nextToolEventId}`;
|
|
3288
3323
|
callIds[streamedCallsCount] = eventId;
|
|
3289
3324
|
alreadyPrintedIds.add(eventId);
|
|
@@ -3788,7 +3823,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3788
3823
|
continue;
|
|
3789
3824
|
}
|
|
3790
3825
|
const incompleteNativeStream = nativeToolCalls.length === 0 &&
|
|
3791
|
-
|
|
3826
|
+
streamedNativeCallNames.size > 0;
|
|
3792
3827
|
if (incompleteNativeStream) {
|
|
3793
3828
|
const reason = "The provider began this native tool call but never completed it. Nothing ran; reissue a complete call.";
|
|
3794
3829
|
for (const deferred of deferredToolCalls) {
|
|
@@ -4201,13 +4236,14 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4201
4236
|
let parsed = parseAllToolCalls(assistantText.visible || assistantText.thinkContent);
|
|
4202
4237
|
if (parsed.length === 0 && call)
|
|
4203
4238
|
parsed = [call];
|
|
4204
|
-
bound = parsed.map((
|
|
4239
|
+
bound = parsed.map((rawCall, index) => {
|
|
4240
|
+
const call = normalizeToolCall(rawCall);
|
|
4205
4241
|
const id = syntheticToolCallId(index);
|
|
4206
4242
|
return {
|
|
4207
4243
|
index,
|
|
4208
4244
|
id,
|
|
4209
|
-
call
|
|
4210
|
-
native: { id, name:
|
|
4245
|
+
call,
|
|
4246
|
+
native: { id, name: call.name, args: call.args },
|
|
4211
4247
|
};
|
|
4212
4248
|
});
|
|
4213
4249
|
}
|
|
@@ -4377,7 +4413,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4377
4413
|
for (const deferred of activeDeferredToolCalls.slice(0, allCalls.length)) {
|
|
4378
4414
|
if (!deferred.call.name || deferred.call.name === "…")
|
|
4379
4415
|
continue;
|
|
4380
|
-
if (!deferred.shown
|
|
4416
|
+
if (!deferred.shown) {
|
|
4381
4417
|
writeToolCall(deferred.eventId, deferred.call, deferred.rendered);
|
|
4382
4418
|
deferred.shown = true;
|
|
4383
4419
|
}
|