kfc-code-cli 0.0.1-alpha.15 → 0.0.1-alpha.16
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/dist/main.mjs +1334 -228
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -2312,6 +2312,9 @@ function createChatStreamingCallbacks(deps) {
|
|
|
2312
2312
|
});
|
|
2313
2313
|
},
|
|
2314
2314
|
onPrefetchedResult: (toolCallId, result) => {
|
|
2315
|
+
prefetchedResults.set(toolCallId, Promise.resolve(result));
|
|
2316
|
+
},
|
|
2317
|
+
onPrefetchedResultPromise: (toolCallId, result) => {
|
|
2315
2318
|
prefetchedResults.set(toolCallId, result);
|
|
2316
2319
|
},
|
|
2317
2320
|
onAtomicPart: async (atomic) => {
|
|
@@ -2355,25 +2358,29 @@ function createChatStreamingCallbacks(deps) {
|
|
|
2355
2358
|
* Pass 1 runs inside the step envelope and classifies every LLM-emitted
|
|
2356
2359
|
* tool call. Fallback branches write a durable tool_call + synthetic
|
|
2357
2360
|
* tool_result before step_end. Pass 2 runs after step_end and executes
|
|
2358
|
-
* allowed tools.
|
|
2359
|
-
*
|
|
2360
|
-
*
|
|
2361
|
-
*
|
|
2361
|
+
* allowed tools. Consecutive `isConcurrencySafe` tools execute as a
|
|
2362
|
+
* parallel batch; live tool.result events fire as soon as each sanitized
|
|
2363
|
+
* result is available, while durable context writes remain in provider
|
|
2364
|
+
* tool-call order for the next LLM request.
|
|
2362
2365
|
*/
|
|
2363
2366
|
const GRACE_TIMEOUT_MS = 2e3;
|
|
2364
|
-
function
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
if (result !== void 0) {
|
|
2368
|
-
emitToolResultEvent(emit, toolCall.id, result.output, result.isError);
|
|
2369
|
-
deferred.delete(toolCall.id);
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2367
|
+
function streamingPreparationKeepsPrefixOpen(preparation) {
|
|
2368
|
+
if (preparation.kind === "handled") return true;
|
|
2369
|
+
return preparation.kind === "pending" && preparation.pending.preparedResult !== void 0;
|
|
2372
2370
|
}
|
|
2373
|
-
async function classifyToolCalls(step, response
|
|
2371
|
+
async function classifyToolCalls(step, response) {
|
|
2374
2372
|
const { config, context, emit, signal, turnId, currentStep, stepUuid, toolCallByProviderId } = step;
|
|
2375
2373
|
const pending = [];
|
|
2376
2374
|
for (const toolCall of response.toolCalls) {
|
|
2375
|
+
const streamed = step.streamingPreparations.get(toolCall.id);
|
|
2376
|
+
if (streamed !== void 0) {
|
|
2377
|
+
const prepared = await streamed;
|
|
2378
|
+
if (prepared.kind === "pending") {
|
|
2379
|
+
pending.push(prepared.pending);
|
|
2380
|
+
continue;
|
|
2381
|
+
}
|
|
2382
|
+
if (prepared.kind === "handled") continue;
|
|
2383
|
+
}
|
|
2377
2384
|
emit({
|
|
2378
2385
|
type: "tool.call",
|
|
2379
2386
|
toolCallId: toolCall.id,
|
|
@@ -2384,29 +2391,20 @@ async function classifyToolCalls(step, response, deferred) {
|
|
|
2384
2391
|
if (tool === void 0) {
|
|
2385
2392
|
const output = `Tool "${toolCall.name}" not found`;
|
|
2386
2393
|
await writeFallbackToolCallAndResult(context, stepUuid, turnId, currentStep, toolCall, output);
|
|
2387
|
-
|
|
2388
|
-
output,
|
|
2389
|
-
isError: true
|
|
2390
|
-
});
|
|
2394
|
+
emitToolResultEvent(emit, toolCall.id, output, true);
|
|
2391
2395
|
continue;
|
|
2392
2396
|
}
|
|
2393
2397
|
if (toolCall.error !== void 0) {
|
|
2394
2398
|
const output = `Invalid input for tool "${toolCall.name}": ${toolCall.error}`;
|
|
2395
2399
|
await writeFallbackToolCallAndResult(context, stepUuid, turnId, currentStep, toolCall, output);
|
|
2396
|
-
|
|
2397
|
-
output,
|
|
2398
|
-
isError: true
|
|
2399
|
-
});
|
|
2400
|
+
emitToolResultEvent(emit, toolCall.id, output, true);
|
|
2400
2401
|
continue;
|
|
2401
2402
|
}
|
|
2402
2403
|
const parsed = tool.inputSchema.safeParse(toolCall.args);
|
|
2403
2404
|
if (!parsed.success) {
|
|
2404
2405
|
const output = `Invalid input for tool "${toolCall.name}": ${parsed.error.message}`;
|
|
2405
2406
|
await writeFallbackToolCallAndResult(context, stepUuid, turnId, currentStep, toolCall, output);
|
|
2406
|
-
|
|
2407
|
-
output,
|
|
2408
|
-
isError: true
|
|
2409
|
-
});
|
|
2407
|
+
emitToolResultEvent(emit, toolCall.id, output, true);
|
|
2410
2408
|
continue;
|
|
2411
2409
|
}
|
|
2412
2410
|
let hookResult;
|
|
@@ -2414,7 +2412,6 @@ async function classifyToolCalls(step, response, deferred) {
|
|
|
2414
2412
|
hookResult = await config.beforeToolCall({
|
|
2415
2413
|
toolCall,
|
|
2416
2414
|
args: parsed.data,
|
|
2417
|
-
assistantMessage: response.message,
|
|
2418
2415
|
context,
|
|
2419
2416
|
turnId,
|
|
2420
2417
|
stepNumber: currentStep,
|
|
@@ -2424,19 +2421,13 @@ async function classifyToolCalls(step, response, deferred) {
|
|
|
2424
2421
|
} catch (error) {
|
|
2425
2422
|
const output = `beforeToolCall hook failed for "${toolCall.name}": ${errorMessage$1(error)}`;
|
|
2426
2423
|
await writeFallbackToolCallAndResult(context, stepUuid, turnId, currentStep, toolCall, output);
|
|
2427
|
-
|
|
2428
|
-
output,
|
|
2429
|
-
isError: true
|
|
2430
|
-
});
|
|
2424
|
+
emitToolResultEvent(emit, toolCall.id, output, true);
|
|
2431
2425
|
continue;
|
|
2432
2426
|
}
|
|
2433
2427
|
if (hookResult?.block === true) {
|
|
2434
2428
|
const output = hookResult.reason ?? `Tool call "${toolCall.name}" was blocked`;
|
|
2435
2429
|
await writeFallbackToolCallAndResult(context, stepUuid, turnId, currentStep, toolCall, output);
|
|
2436
|
-
|
|
2437
|
-
output,
|
|
2438
|
-
isError: true
|
|
2439
|
-
});
|
|
2430
|
+
emitToolResultEvent(emit, toolCall.id, output, true);
|
|
2440
2431
|
continue;
|
|
2441
2432
|
}
|
|
2442
2433
|
pending.push({
|
|
@@ -2448,112 +2439,241 @@ async function classifyToolCalls(step, response, deferred) {
|
|
|
2448
2439
|
}
|
|
2449
2440
|
return pending;
|
|
2450
2441
|
}
|
|
2451
|
-
async function
|
|
2442
|
+
async function prepareStreamingToolCall(step, toolCall) {
|
|
2452
2443
|
const { config, context, emit, signal, turnId, currentStep, stepUuid, toolCallByProviderId } = step;
|
|
2444
|
+
const tool = findTool(config.tools, toolCall.name);
|
|
2445
|
+
if (tool === void 0 || toolCall.error !== void 0) return { kind: "declined" };
|
|
2446
|
+
const parsed = tool.inputSchema.safeParse(toolCall.args);
|
|
2447
|
+
if (!parsed.success || !isStreamingPrefetchSafe(tool, parsed.data)) return { kind: "declined" };
|
|
2448
|
+
emit({
|
|
2449
|
+
type: "tool.call",
|
|
2450
|
+
toolCallId: toolCall.id,
|
|
2451
|
+
name: toolCall.name,
|
|
2452
|
+
args: toToolCallArgs(toolCall.args)
|
|
2453
|
+
});
|
|
2454
|
+
let hookResult;
|
|
2455
|
+
if (config.beforeToolCall !== void 0) try {
|
|
2456
|
+
hookResult = await config.beforeToolCall({
|
|
2457
|
+
toolCall,
|
|
2458
|
+
args: parsed.data,
|
|
2459
|
+
context,
|
|
2460
|
+
turnId,
|
|
2461
|
+
stepNumber: currentStep,
|
|
2462
|
+
stepUuid,
|
|
2463
|
+
toolCallByProviderId
|
|
2464
|
+
}, signal);
|
|
2465
|
+
} catch (error) {
|
|
2466
|
+
const output = `beforeToolCall hook failed for "${toolCall.name}": ${errorMessage$1(error)}`;
|
|
2467
|
+
await writeFallbackToolCallAndResult(context, stepUuid, turnId, currentStep, toolCall, output);
|
|
2468
|
+
emitToolResultEvent(emit, toolCall.id, output, true);
|
|
2469
|
+
return { kind: "handled" };
|
|
2470
|
+
}
|
|
2471
|
+
if (hookResult?.block === true) {
|
|
2472
|
+
const output = hookResult.reason ?? `Tool call "${toolCall.name}" was blocked`;
|
|
2473
|
+
await writeFallbackToolCallAndResult(context, stepUuid, turnId, currentStep, toolCall, output);
|
|
2474
|
+
emitToolResultEvent(emit, toolCall.id, output, true);
|
|
2475
|
+
return { kind: "handled" };
|
|
2476
|
+
}
|
|
2477
|
+
const base = {
|
|
2478
|
+
toolCall,
|
|
2479
|
+
tool,
|
|
2480
|
+
parsedArgs: parsed.data,
|
|
2481
|
+
hookResult
|
|
2482
|
+
};
|
|
2483
|
+
if (!isStreamingPrefetchSafe(tool, getEffectiveInput(base))) return {
|
|
2484
|
+
kind: "pending",
|
|
2485
|
+
pending: base
|
|
2486
|
+
};
|
|
2487
|
+
const preparedResult = preparePendingToolResultCore(step, base).then((prepared) => {
|
|
2488
|
+
emitToolResultEvent(emit, prepared.toolCallId, prepared.output, prepared.isError);
|
|
2489
|
+
return prepared;
|
|
2490
|
+
});
|
|
2491
|
+
return {
|
|
2492
|
+
kind: "pending",
|
|
2493
|
+
pending: {
|
|
2494
|
+
...base,
|
|
2495
|
+
preparedResult,
|
|
2496
|
+
resultEventEmitted: true
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
async function executePendingCalls(step, pending) {
|
|
2501
|
+
const { emit, signal } = step;
|
|
2453
2502
|
const postContentCallbacks = [];
|
|
2454
|
-
for (
|
|
2503
|
+
for (let index = 0; index < pending.length;) {
|
|
2455
2504
|
if (index > 0) signal.throwIfAborted();
|
|
2456
|
-
const
|
|
2457
|
-
|
|
2458
|
-
if (
|
|
2459
|
-
const
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
}
|
|
2486
|
-
} catch (error) {
|
|
2487
|
-
const output = isAbortError$3(error) || signal.aborted ? `Tool "${toolCall.name}" was aborted` : `Tool "${toolCall.name}" failed: ${errorMessage$1(error)}`;
|
|
2488
|
-
const syntheticResult = {
|
|
2489
|
-
content: output,
|
|
2490
|
-
isError: true
|
|
2491
|
-
};
|
|
2492
|
-
deferred.set(toolCall.id, {
|
|
2493
|
-
output,
|
|
2494
|
-
isError: true
|
|
2495
|
-
});
|
|
2496
|
-
await context.appendToolResult(toolCallByProviderId.get(toolCall.id), toolCall.id, {
|
|
2497
|
-
output,
|
|
2498
|
-
isError: true
|
|
2499
|
-
});
|
|
2500
|
-
if (config.afterToolCall !== void 0) try {
|
|
2501
|
-
await config.afterToolCall({
|
|
2502
|
-
toolCall,
|
|
2503
|
-
args: effectiveInput,
|
|
2504
|
-
result: syntheticResult,
|
|
2505
|
-
context
|
|
2506
|
-
}, signal);
|
|
2507
|
-
} catch {}
|
|
2505
|
+
const item = pending[index];
|
|
2506
|
+
if (item === void 0) break;
|
|
2507
|
+
if (isConcurrencySafe(item) && !signal.aborted) {
|
|
2508
|
+
const batch = [];
|
|
2509
|
+
while (index < pending.length) {
|
|
2510
|
+
const next = pending[index];
|
|
2511
|
+
if (next === void 0 || !isConcurrencySafe(next)) break;
|
|
2512
|
+
batch.push(next);
|
|
2513
|
+
index += 1;
|
|
2514
|
+
}
|
|
2515
|
+
let batchStopped = false;
|
|
2516
|
+
const shouldEmit = () => !batchStopped;
|
|
2517
|
+
const tasks = batch.map((batchItem) => prepareAndEmitToolResult(step, batchItem, shouldEmit));
|
|
2518
|
+
for (const task of tasks) {
|
|
2519
|
+
const outcome = await task;
|
|
2520
|
+
if (!outcome.ok) {
|
|
2521
|
+
batchStopped = true;
|
|
2522
|
+
throw outcome.error instanceof Error ? outcome.error : new Error(errorMessage$1(outcome.error));
|
|
2523
|
+
}
|
|
2524
|
+
const prepared = outcome.prepared;
|
|
2525
|
+
await appendPreparedToolResult(step, prepared, postContentCallbacks);
|
|
2526
|
+
if (prepared.throwAfterCommit !== void 0) {
|
|
2527
|
+
batchStopped = true;
|
|
2528
|
+
throw prepared.throwAfterCommit;
|
|
2529
|
+
}
|
|
2530
|
+
if (signal.aborted) {
|
|
2531
|
+
batchStopped = true;
|
|
2532
|
+
signal.throwIfAborted();
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2508
2535
|
continue;
|
|
2509
2536
|
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2537
|
+
const prepared = await preparePendingToolResult(step, item);
|
|
2538
|
+
if (item.resultEventEmitted !== true) emitToolResultEvent(emit, prepared.toolCallId, prepared.output, prepared.isError);
|
|
2539
|
+
await appendPreparedToolResult(step, prepared, postContentCallbacks);
|
|
2540
|
+
if (prepared.throwAfterCommit !== void 0) throw prepared.throwAfterCommit;
|
|
2541
|
+
index += 1;
|
|
2542
|
+
}
|
|
2543
|
+
for (const callback of postContentCallbacks) await callback();
|
|
2544
|
+
}
|
|
2545
|
+
async function preparePendingToolResult(step, item) {
|
|
2546
|
+
if (item.preparedResult !== void 0) return item.preparedResult;
|
|
2547
|
+
return preparePendingToolResultCore(step, item);
|
|
2548
|
+
}
|
|
2549
|
+
async function preparePendingToolResultCore(step, item) {
|
|
2550
|
+
const { config, context, signal } = step;
|
|
2551
|
+
const { toolCall, hookResult } = item;
|
|
2552
|
+
const effectiveInput = getEffectiveInput(item);
|
|
2553
|
+
if (hookResult?.block === true) return makeSyntheticPrepared(step, toolCall, hookResult.reason ?? `Tool call "${toolCall.name}" was blocked`);
|
|
2554
|
+
let toolResult;
|
|
2555
|
+
try {
|
|
2556
|
+
toolResult = await executeOrAwaitPrefetch(step, item, effectiveInput);
|
|
2557
|
+
} catch (error) {
|
|
2558
|
+
const output = isAbortError$3(error) || signal.aborted ? `Tool "${toolCall.name}" was aborted` : `Tool "${toolCall.name}" failed: ${errorMessage$1(error)}`;
|
|
2559
|
+
const syntheticResult = {
|
|
2560
|
+
content: output,
|
|
2561
|
+
isError: true
|
|
2562
|
+
};
|
|
2512
2563
|
if (config.afterToolCall !== void 0) try {
|
|
2513
|
-
|
|
2564
|
+
await config.afterToolCall({
|
|
2514
2565
|
toolCall,
|
|
2515
2566
|
args: effectiveInput,
|
|
2516
|
-
result:
|
|
2567
|
+
result: syntheticResult,
|
|
2517
2568
|
context
|
|
2518
2569
|
}, signal);
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2570
|
+
} catch {}
|
|
2571
|
+
return makeSyntheticPrepared(step, toolCall, output);
|
|
2572
|
+
}
|
|
2573
|
+
let finalResult = toolResult;
|
|
2574
|
+
let afterError;
|
|
2575
|
+
if (config.afterToolCall !== void 0) try {
|
|
2576
|
+
const afterResult = await config.afterToolCall({
|
|
2577
|
+
toolCall,
|
|
2578
|
+
args: effectiveInput,
|
|
2579
|
+
result: toolResult,
|
|
2580
|
+
context
|
|
2581
|
+
}, signal);
|
|
2582
|
+
if (afterResult?.resultOverride !== void 0) finalResult = afterResult.resultOverride;
|
|
2583
|
+
} catch (error) {
|
|
2584
|
+
afterError = error;
|
|
2585
|
+
}
|
|
2586
|
+
if (afterError !== void 0) {
|
|
2587
|
+
if (isAbortError$3(afterError) || signal.aborted) return makeSyntheticPrepared(step, toolCall, `Tool "${toolCall.name}" aborted during afterToolCall hook.`, afterError instanceof Error ? afterError : new Error(errorMessage$1(afterError)));
|
|
2588
|
+
return makeSyntheticPrepared(step, toolCall, `afterToolCall hook failed for "${toolCall.name}": ${errorMessage$1(afterError)}`);
|
|
2589
|
+
}
|
|
2590
|
+
const adapted = adaptToolResult(finalResult);
|
|
2591
|
+
const output = typeof adapted.output === "string" ? adapted.output : JSON.stringify(adapted.output);
|
|
2592
|
+
return {
|
|
2593
|
+
toolCallId: toolCall.id,
|
|
2594
|
+
adapted,
|
|
2595
|
+
output,
|
|
2596
|
+
isError: finalResult.isError === true,
|
|
2597
|
+
postContent: !finalResult.isError ? finalResult.postContent : void 0,
|
|
2598
|
+
throwAfterCommit: void 0
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
async function prepareAndEmitToolResult(step, item, shouldEmit) {
|
|
2602
|
+
try {
|
|
2603
|
+
const prepared = await preparePendingToolResult(step, item);
|
|
2604
|
+
if (item.resultEventEmitted !== true && shouldEmit()) emitToolResultEvent(step.emit, prepared.toolCallId, prepared.output, prepared.isError);
|
|
2605
|
+
return {
|
|
2606
|
+
ok: true,
|
|
2607
|
+
prepared
|
|
2608
|
+
};
|
|
2609
|
+
} catch (error) {
|
|
2610
|
+
return {
|
|
2611
|
+
ok: false,
|
|
2612
|
+
error
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
async function executeOrAwaitPrefetch(step, item, effectiveInput) {
|
|
2617
|
+
const { emit, signal, turnId, toolCallByProviderId } = step;
|
|
2618
|
+
const { toolCall, tool } = item;
|
|
2619
|
+
const prefetched = step.prefetchedResults.get(toolCall.id);
|
|
2620
|
+
if (prefetched !== void 0) try {
|
|
2621
|
+
return await raceExecuteWithGraceTimeout(prefetched, signal, tool.name);
|
|
2622
|
+
} catch (error) {
|
|
2623
|
+
if (signal.aborted) throw error;
|
|
2624
|
+
}
|
|
2625
|
+
return raceExecuteWithGraceTimeout(tool.execute({
|
|
2626
|
+
turnId,
|
|
2627
|
+
toolCallId: toolCall.id,
|
|
2628
|
+
args: effectiveInput,
|
|
2629
|
+
signal,
|
|
2630
|
+
onUpdate: (update) => {
|
|
2631
|
+
emit({
|
|
2632
|
+
type: "tool.progress",
|
|
2633
|
+
toolCallId: toolCall.id,
|
|
2634
|
+
update
|
|
2544
2635
|
});
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2636
|
+
},
|
|
2637
|
+
wireUuid: toolCallByProviderId.get(toolCall.id)
|
|
2638
|
+
}), signal, tool.name);
|
|
2639
|
+
}
|
|
2640
|
+
function makeSyntheticPrepared(step, toolCall, output, throwAfterCommit) {
|
|
2641
|
+
return {
|
|
2642
|
+
toolCallId: toolCall.id,
|
|
2643
|
+
adapted: {
|
|
2644
|
+
output,
|
|
2645
|
+
isError: true
|
|
2646
|
+
},
|
|
2647
|
+
output,
|
|
2648
|
+
isError: true,
|
|
2649
|
+
postContent: void 0,
|
|
2650
|
+
throwAfterCommit
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
async function appendPreparedToolResult(step, prepared, postContentCallbacks) {
|
|
2654
|
+
const parentUuid = step.toolCallByProviderId.get(prepared.toolCallId);
|
|
2655
|
+
await step.context.appendToolResult(parentUuid, prepared.toolCallId, prepared.adapted);
|
|
2656
|
+
if (prepared.postContent !== void 0) postContentCallbacks.push(prepared.postContent);
|
|
2657
|
+
}
|
|
2658
|
+
function getEffectiveInput(item) {
|
|
2659
|
+
return item.hookResult?.updatedInput ?? item.parsedArgs;
|
|
2660
|
+
}
|
|
2661
|
+
function isConcurrencySafe(item) {
|
|
2662
|
+
if (item.hookResult?.block === true) return false;
|
|
2663
|
+
if (item.tool.isConcurrencySafe === void 0) return false;
|
|
2664
|
+
try {
|
|
2665
|
+
return item.tool.isConcurrencySafe(getEffectiveInput(item));
|
|
2666
|
+
} catch {
|
|
2667
|
+
return false;
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
function isStreamingPrefetchSafe(tool, input) {
|
|
2671
|
+
if (tool.isStreamingPrefetchSafe === void 0) return false;
|
|
2672
|
+
try {
|
|
2673
|
+
return tool.isStreamingPrefetchSafe(input);
|
|
2674
|
+
} catch {
|
|
2675
|
+
return false;
|
|
2555
2676
|
}
|
|
2556
|
-
for (const callback of postContentCallbacks) await callback();
|
|
2557
2677
|
}
|
|
2558
2678
|
function findTool(tools, name) {
|
|
2559
2679
|
return tools?.find((tool) => tool.name === name);
|
|
@@ -2643,33 +2763,7 @@ async function executeSoulStep(deps) {
|
|
|
2643
2763
|
const stepUuid = randomUUID();
|
|
2644
2764
|
const toolCallByProviderId = /* @__PURE__ */ new Map();
|
|
2645
2765
|
const prefetchedResults = /* @__PURE__ */ new Map();
|
|
2646
|
-
|
|
2647
|
-
uuid: stepUuid,
|
|
2648
|
-
turnId,
|
|
2649
|
-
step: currentStep
|
|
2650
|
-
});
|
|
2651
|
-
const response = await runtime.kosong.chat({
|
|
2652
|
-
messages,
|
|
2653
|
-
tools: visibleTools,
|
|
2654
|
-
model,
|
|
2655
|
-
systemPrompt: context.systemPrompt,
|
|
2656
|
-
effort: overrides?.effort,
|
|
2657
|
-
signal,
|
|
2658
|
-
...createChatStreamingCallbacks({
|
|
2659
|
-
emit,
|
|
2660
|
-
context,
|
|
2661
|
-
turnId,
|
|
2662
|
-
currentStep,
|
|
2663
|
-
stepUuid,
|
|
2664
|
-
prefetchedResults
|
|
2665
|
-
}),
|
|
2666
|
-
contextWindow: config.contextWindow
|
|
2667
|
-
});
|
|
2668
|
-
recordUsage(response.usage);
|
|
2669
|
-
const deferredResults = /* @__PURE__ */ new Map();
|
|
2670
|
-
const flushDeferredResults = () => {
|
|
2671
|
-
flushDeferredToolResults(response, deferredResults, emit);
|
|
2672
|
-
};
|
|
2766
|
+
const streamingPreparations = /* @__PURE__ */ new Map();
|
|
2673
2767
|
const step = {
|
|
2674
2768
|
config,
|
|
2675
2769
|
context,
|
|
@@ -2679,22 +2773,51 @@ async function executeSoulStep(deps) {
|
|
|
2679
2773
|
currentStep,
|
|
2680
2774
|
stepUuid,
|
|
2681
2775
|
toolCallByProviderId,
|
|
2682
|
-
prefetchedResults
|
|
2776
|
+
prefetchedResults,
|
|
2777
|
+
streamingPreparations
|
|
2683
2778
|
};
|
|
2779
|
+
const streamingScheduler = createStreamingToolCallScheduler(step, streamingPreparations);
|
|
2780
|
+
await context.appendStepBegin({
|
|
2781
|
+
uuid: stepUuid,
|
|
2782
|
+
turnId,
|
|
2783
|
+
step: currentStep
|
|
2784
|
+
});
|
|
2785
|
+
let response;
|
|
2684
2786
|
try {
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2787
|
+
response = await runtime.kosong.chat({
|
|
2788
|
+
messages,
|
|
2789
|
+
tools: visibleTools,
|
|
2790
|
+
model,
|
|
2791
|
+
systemPrompt: context.systemPrompt,
|
|
2792
|
+
effort: overrides?.effort,
|
|
2793
|
+
signal,
|
|
2794
|
+
...createChatStreamingCallbacks({
|
|
2795
|
+
emit,
|
|
2796
|
+
context,
|
|
2797
|
+
turnId,
|
|
2798
|
+
currentStep,
|
|
2799
|
+
stepUuid,
|
|
2800
|
+
prefetchedResults
|
|
2801
|
+
}),
|
|
2802
|
+
onToolCallReady: (toolCall, info) => {
|
|
2803
|
+
streamingScheduler.markReady(toolCall, info);
|
|
2804
|
+
},
|
|
2805
|
+
contextWindow: config.contextWindow
|
|
2692
2806
|
});
|
|
2693
|
-
await executePendingCalls(step, pending, deferredResults);
|
|
2694
|
-
signal.throwIfAborted();
|
|
2695
2807
|
} finally {
|
|
2696
|
-
|
|
2808
|
+
streamingScheduler.finish();
|
|
2697
2809
|
}
|
|
2810
|
+
recordUsage(response.usage);
|
|
2811
|
+
const pending = await classifyToolCalls(step, response);
|
|
2812
|
+
await context.appendStepEnd({
|
|
2813
|
+
uuid: stepUuid,
|
|
2814
|
+
turnId,
|
|
2815
|
+
step: currentStep,
|
|
2816
|
+
usage: toStepEndUsage(response.usage),
|
|
2817
|
+
finishReason: response.stopReason
|
|
2818
|
+
});
|
|
2819
|
+
await executePendingCalls(step, pending);
|
|
2820
|
+
signal.throwIfAborted();
|
|
2698
2821
|
emit({
|
|
2699
2822
|
type: "step.end",
|
|
2700
2823
|
step: currentStep
|
|
@@ -2710,6 +2833,86 @@ async function executeSoulStep(deps) {
|
|
|
2710
2833
|
} catch {}
|
|
2711
2834
|
return { stopReason };
|
|
2712
2835
|
}
|
|
2836
|
+
function createStreamingToolCallScheduler(step, streamingPreparations) {
|
|
2837
|
+
let nextOrdinal = 0;
|
|
2838
|
+
let nextSyntheticOrdinal = 0;
|
|
2839
|
+
let prefixOpen = true;
|
|
2840
|
+
let finished = false;
|
|
2841
|
+
let pumping = false;
|
|
2842
|
+
const queue = /* @__PURE__ */ new Map();
|
|
2843
|
+
const declined = () => ({ kind: "declined" });
|
|
2844
|
+
const drainDeclined = () => {
|
|
2845
|
+
for (const slot of queue.values()) slot.resolve(declined());
|
|
2846
|
+
queue.clear();
|
|
2847
|
+
};
|
|
2848
|
+
const assignOrdinal = (info) => {
|
|
2849
|
+
const ordinal = info?.ordinal;
|
|
2850
|
+
if (ordinal !== void 0 && Number.isSafeInteger(ordinal) && ordinal >= 0) {
|
|
2851
|
+
nextSyntheticOrdinal = Math.max(nextSyntheticOrdinal, ordinal + 1);
|
|
2852
|
+
return ordinal;
|
|
2853
|
+
}
|
|
2854
|
+
while (nextSyntheticOrdinal < nextOrdinal || queue.has(nextSyntheticOrdinal)) nextSyntheticOrdinal += 1;
|
|
2855
|
+
const synthetic = nextSyntheticOrdinal;
|
|
2856
|
+
nextSyntheticOrdinal += 1;
|
|
2857
|
+
return synthetic;
|
|
2858
|
+
};
|
|
2859
|
+
const pump = async () => {
|
|
2860
|
+
if (pumping) return;
|
|
2861
|
+
pumping = true;
|
|
2862
|
+
try {
|
|
2863
|
+
while (true) {
|
|
2864
|
+
if (finished || !prefixOpen) break;
|
|
2865
|
+
const slot = queue.get(nextOrdinal);
|
|
2866
|
+
if (slot === void 0) break;
|
|
2867
|
+
queue.delete(nextOrdinal);
|
|
2868
|
+
nextOrdinal += 1;
|
|
2869
|
+
let preparation;
|
|
2870
|
+
try {
|
|
2871
|
+
preparation = await prepareStreamingToolCall(step, slot.toolCall);
|
|
2872
|
+
} catch {
|
|
2873
|
+
preparation = declined();
|
|
2874
|
+
}
|
|
2875
|
+
slot.resolve(preparation);
|
|
2876
|
+
if (!streamingPreparationKeepsPrefixOpen(preparation)) {
|
|
2877
|
+
prefixOpen = false;
|
|
2878
|
+
drainDeclined();
|
|
2879
|
+
break;
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
} finally {
|
|
2883
|
+
pumping = false;
|
|
2884
|
+
if (!finished && prefixOpen && queue.has(nextOrdinal)) pump();
|
|
2885
|
+
}
|
|
2886
|
+
};
|
|
2887
|
+
return {
|
|
2888
|
+
markReady(toolCall, info) {
|
|
2889
|
+
if (streamingPreparations.has(toolCall.id)) return;
|
|
2890
|
+
const preparation = new Promise((resolve) => {
|
|
2891
|
+
if (finished || !prefixOpen) {
|
|
2892
|
+
resolve(declined());
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
const ordinal = assignOrdinal(info);
|
|
2896
|
+
if (ordinal < nextOrdinal || queue.has(ordinal)) {
|
|
2897
|
+
prefixOpen = false;
|
|
2898
|
+
resolve(declined());
|
|
2899
|
+
drainDeclined();
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
queue.set(ordinal, {
|
|
2903
|
+
toolCall,
|
|
2904
|
+
resolve
|
|
2905
|
+
});
|
|
2906
|
+
pump();
|
|
2907
|
+
});
|
|
2908
|
+
streamingPreparations.set(toolCall.id, preparation);
|
|
2909
|
+
},
|
|
2910
|
+
finish() {
|
|
2911
|
+
finished = true;
|
|
2912
|
+
drainDeclined();
|
|
2913
|
+
}
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2713
2916
|
function toStepEndUsage(usage) {
|
|
2714
2917
|
return {
|
|
2715
2918
|
input_tokens: usage.input,
|
|
@@ -3885,10 +4088,10 @@ var ToolStreamingPrefetchScope = class {
|
|
|
3885
4088
|
}
|
|
3886
4089
|
executeStreaming(toolCall, signal) {
|
|
3887
4090
|
const tool = this.currentTools.get(toolCall.name);
|
|
3888
|
-
if (tool === void 0 || tool.
|
|
4091
|
+
if (tool === void 0 || tool.isStreamingPrefetchSafe === void 0) return void 0;
|
|
3889
4092
|
let safe;
|
|
3890
4093
|
try {
|
|
3891
|
-
safe = tool.
|
|
4094
|
+
safe = tool.isStreamingPrefetchSafe(toolCall.args);
|
|
3892
4095
|
} catch {
|
|
3893
4096
|
return;
|
|
3894
4097
|
}
|
|
@@ -4600,7 +4803,9 @@ var StreamingKosongWrapper = class {
|
|
|
4600
4803
|
const sub = new AbortController();
|
|
4601
4804
|
const inFlight = /* @__PURE__ */ new Map();
|
|
4602
4805
|
const completed = /* @__PURE__ */ new Map();
|
|
4806
|
+
const upstreamOnToolCallReady = params.onToolCallReady;
|
|
4603
4807
|
const onPrefetchedResult = params.onPrefetchedResult;
|
|
4808
|
+
const onPrefetchedResultPromise = params.onPrefetchedResultPromise;
|
|
4604
4809
|
const onCallerAbort = () => {
|
|
4605
4810
|
try {
|
|
4606
4811
|
this.streaming.discardStreaming("aborted");
|
|
@@ -4609,19 +4814,28 @@ var StreamingKosongWrapper = class {
|
|
|
4609
4814
|
};
|
|
4610
4815
|
if (params.signal.aborted) sub.abort();
|
|
4611
4816
|
else params.signal.addEventListener("abort", onCallerAbort);
|
|
4612
|
-
const onToolCallReady = (toolCall) => {
|
|
4817
|
+
const onToolCallReady = (toolCall, info) => {
|
|
4818
|
+
if (upstreamOnToolCallReady !== void 0) {
|
|
4819
|
+
upstreamOnToolCallReady(toolCall, info);
|
|
4820
|
+
return;
|
|
4821
|
+
}
|
|
4613
4822
|
if (inFlight.has(toolCall.id) || completed.has(toolCall.id)) return;
|
|
4614
4823
|
const pending = this.streaming.executeStreaming(toolCall, sub.signal);
|
|
4615
4824
|
if (pending === void 0) return;
|
|
4616
4825
|
inFlight.set(toolCall.id, pending);
|
|
4826
|
+
try {
|
|
4827
|
+
onPrefetchedResultPromise?.(toolCall.id, pending);
|
|
4828
|
+
} catch {}
|
|
4617
4829
|
pending.then((result) => {
|
|
4618
4830
|
inFlight.delete(toolCall.id);
|
|
4619
4831
|
completed.set(toolCall.id, result);
|
|
4620
4832
|
try {
|
|
4621
4833
|
onPrefetchedResult?.(toolCall.id, result);
|
|
4622
4834
|
} catch {}
|
|
4835
|
+
return null;
|
|
4623
4836
|
}).catch(() => {
|
|
4624
4837
|
inFlight.delete(toolCall.id);
|
|
4838
|
+
return null;
|
|
4625
4839
|
});
|
|
4626
4840
|
};
|
|
4627
4841
|
const unbind = typeof this.streaming.bindStreaming === "function" ? this.streaming.bindStreaming({
|
|
@@ -4634,18 +4848,31 @@ var StreamingKosongWrapper = class {
|
|
|
4634
4848
|
if (!sub.signal.aborted) sub.abort();
|
|
4635
4849
|
}
|
|
4636
4850
|
}) : () => {};
|
|
4851
|
+
let deferredCleanup = false;
|
|
4852
|
+
const cleanup = (clearCompleted) => {
|
|
4853
|
+
if (clearCompleted) completed.clear();
|
|
4854
|
+
unbind();
|
|
4855
|
+
params.signal.removeEventListener("abort", onCallerAbort);
|
|
4856
|
+
};
|
|
4637
4857
|
try {
|
|
4638
4858
|
const wrappedParams = {
|
|
4639
4859
|
...params,
|
|
4640
4860
|
onToolCallReady
|
|
4641
4861
|
};
|
|
4642
4862
|
const response = await this.raw.chat(wrappedParams);
|
|
4643
|
-
if (inFlight.size > 0) await Promise.allSettled(inFlight.values());
|
|
4863
|
+
if (inFlight.size > 0 && onPrefetchedResultPromise === void 0) await Promise.allSettled(inFlight.values());
|
|
4864
|
+
if (inFlight.size > 0 && onPrefetchedResultPromise !== void 0) {
|
|
4865
|
+
deferredCleanup = true;
|
|
4866
|
+
const pendingAtReturn = [...inFlight.values()];
|
|
4867
|
+
Promise.allSettled(pendingAtReturn).finally(() => {
|
|
4868
|
+
cleanup(true);
|
|
4869
|
+
});
|
|
4870
|
+
return response;
|
|
4871
|
+
}
|
|
4644
4872
|
completed.clear();
|
|
4645
4873
|
return response;
|
|
4646
4874
|
} finally {
|
|
4647
|
-
|
|
4648
|
-
params.signal.removeEventListener("abort", onCallerAbort);
|
|
4875
|
+
if (!deferredCleanup) cleanup(false);
|
|
4649
4876
|
}
|
|
4650
4877
|
}
|
|
4651
4878
|
};
|
|
@@ -4703,6 +4930,7 @@ var DefaultToolExecutionScopeFactory = class {
|
|
|
4703
4930
|
if (inner.maxResultSizeChars !== void 0) wrapped.maxResultSizeChars = inner.maxResultSizeChars;
|
|
4704
4931
|
if (inner.display !== void 0) wrapped.display = inner.display;
|
|
4705
4932
|
if (inner.isConcurrencySafe !== void 0) wrapped.isConcurrencySafe = inner.isConcurrencySafe.bind(inner);
|
|
4933
|
+
if (inner.isStreamingPrefetchSafe !== void 0) wrapped.isStreamingPrefetchSafe = inner.isStreamingPrefetchSafe.bind(inner);
|
|
4706
4934
|
return wrapped;
|
|
4707
4935
|
};
|
|
4708
4936
|
return {
|
|
@@ -4817,6 +5045,7 @@ function copyToolMetadata(inner, wrapped) {
|
|
|
4817
5045
|
if (inner.maxResultSizeChars !== void 0) wrapped.maxResultSizeChars = inner.maxResultSizeChars;
|
|
4818
5046
|
if (inner.display !== void 0) wrapped.display = inner.display;
|
|
4819
5047
|
if (inner.isConcurrencySafe !== void 0) wrapped.isConcurrencySafe = inner.isConcurrencySafe.bind(inner);
|
|
5048
|
+
if (inner.isStreamingPrefetchSafe !== void 0) wrapped.isStreamingPrefetchSafe = inner.isStreamingPrefetchSafe.bind(inner);
|
|
4820
5049
|
}
|
|
4821
5050
|
async function maybeExecutePlanFileTool(toolName, args, planMode) {
|
|
4822
5051
|
if (!isRecord$10(args) || typeof args["path"] !== "string") return void 0;
|
|
@@ -5722,7 +5951,7 @@ function createSoulPlusToolRegistry(options) {
|
|
|
5722
5951
|
};
|
|
5723
5952
|
}
|
|
5724
5953
|
function registerSubagentTool(options) {
|
|
5725
|
-
if (options.hasSubagentInfra && options.isToolEnabled("Agent")) options.toolRegistry.push(new AgentTool(options.soulRegistry, "agent_main"));
|
|
5954
|
+
if (options.hasSubagentInfra && options.isToolEnabled("Agent")) options.toolRegistry.push(new AgentTool(options.soulRegistry, "agent_main", void 0, options.agentTypeRegistry));
|
|
5726
5955
|
}
|
|
5727
5956
|
function registerSkillTool(options) {
|
|
5728
5957
|
if (options.skillManager === void 0) return;
|
|
@@ -7443,6 +7672,10 @@ function isToolCall(part) {
|
|
|
7443
7672
|
function isToolCallPart(part) {
|
|
7444
7673
|
return part.type === "tool_call_part";
|
|
7445
7674
|
}
|
|
7675
|
+
/** Check if a streamed part marks a tool call as complete. */
|
|
7676
|
+
function isToolCallDone(part) {
|
|
7677
|
+
return part.type === "tool_call_done";
|
|
7678
|
+
}
|
|
7446
7679
|
/**
|
|
7447
7680
|
* Merge `source` into `target` in-place for streaming accumulation.
|
|
7448
7681
|
*
|
|
@@ -7568,13 +7801,13 @@ var APIEmptyResponseError = class extends ChatProviderError {
|
|
|
7568
7801
|
* (e.g. TextPart + TextPart, ToolCall + ToolCallPart) are merged in-place so
|
|
7569
7802
|
* the returned message always contains fully-assembled parts.
|
|
7570
7803
|
*
|
|
7571
|
-
* **
|
|
7572
|
-
*
|
|
7573
|
-
*
|
|
7574
|
-
*
|
|
7575
|
-
*
|
|
7576
|
-
*
|
|
7577
|
-
* stream
|
|
7804
|
+
* **tool-call callback firing**: {@link GenerateCallbacks.onToolCallReady}
|
|
7805
|
+
* may fire before stream end when a provider emits an explicit
|
|
7806
|
+
* `tool_call_done`, or when a linear provider opts into Python-style
|
|
7807
|
+
* merge-boundary readiness. Indexed streams without those signals can still
|
|
7808
|
+
* receive later routed argument deltas, so they are only marked ready after the
|
|
7809
|
+
* stream drains. {@link GenerateCallbacks.onToolCall} remains deferred until
|
|
7810
|
+
* after the stream completes, in final message order.
|
|
7578
7811
|
*
|
|
7579
7812
|
* @param provider - The chat provider to generate from.
|
|
7580
7813
|
* @param systemPrompt - System-level instruction prepended to the request.
|
|
@@ -7595,16 +7828,23 @@ async function generate$1(provider, systemPrompt, tools, history, callbacks, opt
|
|
|
7595
7828
|
toolCalls: []
|
|
7596
7829
|
};
|
|
7597
7830
|
let pendingPart = null;
|
|
7831
|
+
const readyToolCallIds = /* @__PURE__ */ new Set();
|
|
7832
|
+
const readyStreamIndexes = /* @__PURE__ */ new Set();
|
|
7598
7833
|
const toolCallIndexMap = /* @__PURE__ */ new Map();
|
|
7599
7834
|
if (options?.signal?.aborted) throwAbortError();
|
|
7600
7835
|
const stream = await provider.generate(systemPrompt, tools, history, options);
|
|
7601
7836
|
await throwIfAborted(options?.signal, stream);
|
|
7602
7837
|
for await (const part of stream) {
|
|
7603
7838
|
await throwIfAborted(options?.signal, stream);
|
|
7839
|
+
if (isLateArgumentDelta(part, readyStreamIndexes)) throw new Error(`Received tool-call arguments for stream index ${String(part.index)} after it was marked ready`);
|
|
7604
7840
|
if (callbacks?.onMessagePart !== void 0) {
|
|
7605
7841
|
await callbacks.onMessagePart(deepCopyPart(part));
|
|
7606
7842
|
await throwIfAborted(options?.signal, stream);
|
|
7607
7843
|
}
|
|
7844
|
+
if (isToolCallDone(part)) {
|
|
7845
|
+
pendingPart = await handleToolCallDone(message, pendingPart, part, toolCallIndexMap, callbacks, readyToolCallIds, readyStreamIndexes, options?.signal, stream);
|
|
7846
|
+
continue;
|
|
7847
|
+
}
|
|
7608
7848
|
if (isToolCallPart(part) && part.index !== void 0 && !isPendingToolCallAtIndex(pendingPart, part.index)) {
|
|
7609
7849
|
const arrayIdx = toolCallIndexMap.get(part.index);
|
|
7610
7850
|
if (arrayIdx !== void 0) {
|
|
@@ -7615,7 +7855,10 @@ async function generate$1(provider, systemPrompt, tools, history, callbacks, opt
|
|
|
7615
7855
|
}
|
|
7616
7856
|
if (pendingPart === null) pendingPart = part;
|
|
7617
7857
|
else if (!mergeInPlace(pendingPart, part)) {
|
|
7618
|
-
flushPart(message, pendingPart, toolCallIndexMap);
|
|
7858
|
+
const flushed = flushPart(message, pendingPart, toolCallIndexMap);
|
|
7859
|
+
if (flushed !== void 0 && canReadyFromMergeBoundary(provider, flushed) && hasCompleteJsonArguments(flushed.toolCall)) {
|
|
7860
|
+
if (await emitToolCallReady(callbacks, readyToolCallIds, flushed.toolCall, { ordinal: flushed.ordinal }, options?.signal, stream) && flushed.streamIndex !== void 0) readyStreamIndexes.add(flushed.streamIndex);
|
|
7861
|
+
}
|
|
7619
7862
|
pendingPart = part;
|
|
7620
7863
|
}
|
|
7621
7864
|
}
|
|
@@ -7626,6 +7869,7 @@ async function generate$1(provider, systemPrompt, tools, history, callbacks, opt
|
|
|
7626
7869
|
const hasText = message.content.some((p) => p.type === "text" && p.text.trim().length > 0);
|
|
7627
7870
|
const hasToolCalls = message.toolCalls.length > 0;
|
|
7628
7871
|
if (hasThink && !hasText && !hasToolCalls) throw new APIEmptyResponseError(`The API returned a response containing only thinking content without any text or tool calls. This usually indicates the stream was interrupted or the output token budget was exhausted during reasoning. Provider: ${provider.name}, model: ${provider.modelName}`);
|
|
7872
|
+
if (callbacks?.onToolCallReady !== void 0) for (const [ordinal, toolCall] of message.toolCalls.entries()) await emitToolCallReady(callbacks, readyToolCallIds, toolCall, { ordinal }, options?.signal, stream);
|
|
7629
7873
|
if (callbacks?.onToolCall !== void 0) for (const toolCall of message.toolCalls) {
|
|
7630
7874
|
await throwIfAborted(options?.signal, stream);
|
|
7631
7875
|
await callbacks.onToolCall(toolCall);
|
|
@@ -7659,13 +7903,29 @@ async function throwIfAborted(signal, stream) {
|
|
|
7659
7903
|
function isPendingToolCallAtIndex(pending, index) {
|
|
7660
7904
|
return pending !== null && isToolCall(pending) && pending._streamIndex === index;
|
|
7661
7905
|
}
|
|
7906
|
+
function isLateArgumentDelta(part, readyStreamIndexes) {
|
|
7907
|
+
return isToolCallPart(part) && part.index !== void 0 && readyStreamIndexes.has(part.index);
|
|
7908
|
+
}
|
|
7909
|
+
function hasCompleteJsonArguments(toolCall) {
|
|
7910
|
+
const raw = toolCall.function.arguments;
|
|
7911
|
+
if (raw === null || raw.length === 0) return false;
|
|
7912
|
+
try {
|
|
7913
|
+
JSON.parse(raw);
|
|
7914
|
+
return true;
|
|
7915
|
+
} catch {
|
|
7916
|
+
return false;
|
|
7917
|
+
}
|
|
7918
|
+
}
|
|
7919
|
+
function canReadyFromMergeBoundary(provider, flushed) {
|
|
7920
|
+
return flushed.streamIndex === void 0 || provider.toolCallReadyStrategy === "merge-boundary";
|
|
7921
|
+
}
|
|
7662
7922
|
/**
|
|
7663
7923
|
* Append a fully-merged part to the message.
|
|
7664
7924
|
*
|
|
7665
7925
|
* - ContentPart -> message.content
|
|
7666
7926
|
* - ToolCall -> message.toolCalls (the `_streamIndex` routing key is
|
|
7667
7927
|
* registered in the map and stripped before storage).
|
|
7668
|
-
* - ToolCallPart -> ignored (orphaned delta)
|
|
7928
|
+
* - ToolCallPart / ToolCallDone -> ignored (control or orphaned delta)
|
|
7669
7929
|
*/
|
|
7670
7930
|
function flushPart(message, part, toolCallIndexMap) {
|
|
7671
7931
|
if (isContentPart$1(part)) {
|
|
@@ -7680,10 +7940,48 @@ function flushPart(message, part, toolCallIndexMap) {
|
|
|
7680
7940
|
function: part.function,
|
|
7681
7941
|
extras: part.extras
|
|
7682
7942
|
};
|
|
7683
|
-
const
|
|
7943
|
+
const ordinal = message.toolCalls.length;
|
|
7684
7944
|
message.toolCalls.push(storedCall);
|
|
7685
|
-
if (streamIndex !== void 0) toolCallIndexMap.set(streamIndex,
|
|
7686
|
-
|
|
7945
|
+
if (streamIndex !== void 0) toolCallIndexMap.set(streamIndex, ordinal);
|
|
7946
|
+
return {
|
|
7947
|
+
toolCall: storedCall,
|
|
7948
|
+
streamIndex,
|
|
7949
|
+
ordinal
|
|
7950
|
+
};
|
|
7951
|
+
}
|
|
7952
|
+
}
|
|
7953
|
+
async function handleToolCallDone(message, pendingPart, part, toolCallIndexMap, callbacks, readyToolCallIds, readyStreamIndexes, signal, stream) {
|
|
7954
|
+
let target = getToolCallEntryByStreamIndex(message, toolCallIndexMap, part.index);
|
|
7955
|
+
let nextPending = pendingPart;
|
|
7956
|
+
if (target === void 0 && isPendingToolCallAtIndex(pendingPart, part.index)) {
|
|
7957
|
+
const flushed = flushPart(message, pendingPart, toolCallIndexMap);
|
|
7958
|
+
target = flushed !== void 0 ? {
|
|
7959
|
+
toolCall: flushed.toolCall,
|
|
7960
|
+
ordinal: flushed.ordinal
|
|
7961
|
+
} : void 0;
|
|
7962
|
+
nextPending = null;
|
|
7963
|
+
}
|
|
7964
|
+
if (target === void 0) return nextPending;
|
|
7965
|
+
if ("arguments" in part) target.toolCall.function.arguments = part.arguments ?? null;
|
|
7966
|
+
if (part.name !== void 0 && part.name.length > 0) target.toolCall.function.name = part.name;
|
|
7967
|
+
if (await emitToolCallReady(callbacks, readyToolCallIds, target.toolCall, { ordinal: target.ordinal }, signal, stream)) readyStreamIndexes.add(part.index);
|
|
7968
|
+
return nextPending;
|
|
7969
|
+
}
|
|
7970
|
+
function getToolCallEntryByStreamIndex(message, toolCallIndexMap, streamIndex) {
|
|
7971
|
+
const arrayIdx = toolCallIndexMap.get(streamIndex);
|
|
7972
|
+
if (arrayIdx === void 0) return void 0;
|
|
7973
|
+
const toolCall = message.toolCalls[arrayIdx];
|
|
7974
|
+
return toolCall === void 0 ? void 0 : {
|
|
7975
|
+
toolCall,
|
|
7976
|
+
ordinal: arrayIdx
|
|
7977
|
+
};
|
|
7978
|
+
}
|
|
7979
|
+
async function emitToolCallReady(callbacks, readyToolCallIds, toolCall, info, signal, stream) {
|
|
7980
|
+
if (callbacks?.onToolCallReady === void 0 || readyToolCallIds.has(toolCall.id)) return false;
|
|
7981
|
+
await throwIfAborted(signal, stream);
|
|
7982
|
+
readyToolCallIds.add(toolCall.id);
|
|
7983
|
+
await callbacks.onToolCallReady(toolCall, info);
|
|
7984
|
+
return true;
|
|
7687
7985
|
}
|
|
7688
7986
|
/**
|
|
7689
7987
|
* Produce a shallow-ish copy of a StreamedMessagePart.
|
|
@@ -21342,7 +21640,8 @@ var SoulPlus = class {
|
|
|
21342
21640
|
toolRegistry,
|
|
21343
21641
|
isToolEnabled,
|
|
21344
21642
|
hasSubagentInfra,
|
|
21345
|
-
soulRegistry
|
|
21643
|
+
soulRegistry,
|
|
21644
|
+
agentTypeRegistry: deps.agentTypeRegistry
|
|
21346
21645
|
});
|
|
21347
21646
|
registerSkillTool({
|
|
21348
21647
|
toolRegistry,
|
|
@@ -21811,13 +22110,13 @@ var KosongAdapter = class {
|
|
|
21811
22110
|
const onDelta = params.onDelta;
|
|
21812
22111
|
const onThinkDelta = params.onThinkDelta;
|
|
21813
22112
|
const onToolCallPart = params.onToolCallPart;
|
|
22113
|
+
const onToolCallReady = params.onToolCallReady;
|
|
21814
22114
|
const onAtomicPart = params.onAtomicPart;
|
|
21815
22115
|
const needMessagePart = onDelta !== void 0 || onThinkDelta !== void 0 || onToolCallPart !== void 0;
|
|
21816
22116
|
const toolCallsByStreamIndex = /* @__PURE__ */ new Map();
|
|
21817
22117
|
let lastToolCall;
|
|
21818
|
-
|
|
21819
|
-
|
|
21820
|
-
result = await generate$1(activeProvider, params.systemPrompt, kosongTools, params.messages, needMessagePart ? { onMessagePart: async (part) => {
|
|
22118
|
+
const callbacks = needMessagePart || onToolCallReady !== void 0 ? {
|
|
22119
|
+
...needMessagePart ? { onMessagePart: async (part) => {
|
|
21821
22120
|
if (part.type === "text" && onDelta !== void 0) onDelta(part.text);
|
|
21822
22121
|
else if (part.type === "think" && onThinkDelta !== void 0) onThinkDelta(part.think);
|
|
21823
22122
|
else if (part.type === "function") {
|
|
@@ -21826,16 +22125,30 @@ var KosongAdapter = class {
|
|
|
21826
22125
|
name: part.function.name
|
|
21827
22126
|
};
|
|
21828
22127
|
if (part._streamIndex !== void 0) toolCallsByStreamIndex.set(part._streamIndex, lastToolCall);
|
|
21829
|
-
} else if (isToolCallPart(part)
|
|
22128
|
+
} else if (isToolCallPart(part)) {
|
|
21830
22129
|
const target = part.index !== void 0 ? toolCallsByStreamIndex.get(part.index) : lastToolCall;
|
|
21831
|
-
if (target !== void 0) onToolCallPart({
|
|
22130
|
+
if (target !== void 0 && onToolCallPart !== void 0) onToolCallPart({
|
|
21832
22131
|
type: "tool_call_part",
|
|
21833
22132
|
tool_call_id: target.id,
|
|
21834
22133
|
name: target.name,
|
|
21835
22134
|
...part.argumentsPart !== null ? { arguments_chunk: part.argumentsPart } : {}
|
|
21836
22135
|
});
|
|
21837
22136
|
}
|
|
21838
|
-
} } :
|
|
22137
|
+
} } : {},
|
|
22138
|
+
...onToolCallReady !== void 0 ? { onToolCallReady: async (toolCall, info) => {
|
|
22139
|
+
const parsed = parseToolArgs(toolCall.function.arguments);
|
|
22140
|
+
if (parsed.error !== void 0) return;
|
|
22141
|
+
onToolCallReady({
|
|
22142
|
+
id: toolCall.id,
|
|
22143
|
+
name: toolCall.function.name,
|
|
22144
|
+
rawArgs: toolCall.function.arguments,
|
|
22145
|
+
args: parsed.args
|
|
22146
|
+
}, info);
|
|
22147
|
+
} } : {}
|
|
22148
|
+
} : void 0;
|
|
22149
|
+
let result;
|
|
22150
|
+
try {
|
|
22151
|
+
result = await generate$1(activeProvider, params.systemPrompt, kosongTools, params.messages, callbacks, { signal: params.signal });
|
|
21839
22152
|
} catch (error) {
|
|
21840
22153
|
if (isContextOverflowProviderError(error)) throw new ContextOverflowError(extractMessage(error));
|
|
21841
22154
|
throw error;
|
|
@@ -24261,6 +24574,7 @@ var ReadTool = class {
|
|
|
24261
24574
|
inputSchema = ReadInputSchema;
|
|
24262
24575
|
maxResultSizeChars = Number.POSITIVE_INFINITY;
|
|
24263
24576
|
isConcurrencySafe = (_input) => true;
|
|
24577
|
+
isStreamingPrefetchSafe = (_input) => true;
|
|
24264
24578
|
display = {
|
|
24265
24579
|
getUserFacingName: () => "Read",
|
|
24266
24580
|
getInputDisplay: (input) => ({
|
|
@@ -29183,6 +29497,7 @@ var GrepTool = class {
|
|
|
29183
29497
|
description = "Search file contents using regular expressions (powered by ripgrep).";
|
|
29184
29498
|
inputSchema = GrepInputSchema;
|
|
29185
29499
|
isConcurrencySafe = (_input) => true;
|
|
29500
|
+
isStreamingPrefetchSafe = (_input) => true;
|
|
29186
29501
|
constructor(kaos, workspace) {
|
|
29187
29502
|
this.kaos = kaos;
|
|
29188
29503
|
this.workspace = workspace;
|
|
@@ -29583,6 +29898,7 @@ var GlobTool = class {
|
|
|
29583
29898
|
description = GLOB_DESCRIPTION;
|
|
29584
29899
|
inputSchema = GlobInputSchema;
|
|
29585
29900
|
isConcurrencySafe = (_input) => true;
|
|
29901
|
+
isStreamingPrefetchSafe = (_input) => true;
|
|
29586
29902
|
constructor(kaos, workspace) {
|
|
29587
29903
|
this.kaos = kaos;
|
|
29588
29904
|
this.workspace = workspace;
|
|
@@ -32187,7 +32503,7 @@ function registerProcessHandlers(ctx) {
|
|
|
32187
32503
|
//#region ../../packages/kimi-core/src/wire-protocol/outbound/external-tool-proxy.ts
|
|
32188
32504
|
function buildExternalToolProxy(options) {
|
|
32189
32505
|
const inputSchema = z.unknown();
|
|
32190
|
-
|
|
32506
|
+
const tool = {
|
|
32191
32507
|
name: options.name,
|
|
32192
32508
|
description: options.description,
|
|
32193
32509
|
inputSchema,
|
|
@@ -32217,6 +32533,8 @@ function buildExternalToolProxy(options) {
|
|
|
32217
32533
|
}
|
|
32218
32534
|
}
|
|
32219
32535
|
};
|
|
32536
|
+
if (options.concurrencySafe === true) tool.isConcurrencySafe = () => true;
|
|
32537
|
+
return tool;
|
|
32220
32538
|
}
|
|
32221
32539
|
//#endregion
|
|
32222
32540
|
//#region ../../packages/kimi-core/src/wire-protocol/request/handlers/tools.ts
|
|
@@ -32229,12 +32547,14 @@ const TOOLS_HANDLER_DESCRIPTORS = [
|
|
|
32229
32547
|
if (reverse === void 0) throw new Error("session.registerTool requires a reverse-RPC channel (no `server` transport wired)");
|
|
32230
32548
|
ctx.sessionState.get(msg.session_id).externalTools.set(payload.name, {
|
|
32231
32549
|
description: payload.description ?? "",
|
|
32232
|
-
input_schema: payload.input_schema
|
|
32550
|
+
input_schema: payload.input_schema,
|
|
32551
|
+
concurrency_safe: payload.concurrency_safe === true
|
|
32233
32552
|
});
|
|
32234
32553
|
await ctx.sessionApplication.registerDynamicTool(msg.session_id, buildExternalToolProxy({
|
|
32235
32554
|
name: payload.name,
|
|
32236
32555
|
description: payload.description ?? "",
|
|
32237
32556
|
inputSchema: payload.input_schema,
|
|
32557
|
+
concurrencySafe: payload.concurrency_safe === true,
|
|
32238
32558
|
sendToolCall: async (call, signal) => {
|
|
32239
32559
|
const data = (await reverse.sendRequest("tool.call", msg.session_id, {
|
|
32240
32560
|
id: call.id,
|
|
@@ -32453,7 +32773,7 @@ function registerDefaultWireHandlers(deps) {
|
|
|
32453
32773
|
pathConfig,
|
|
32454
32774
|
runtimeProvider: () => deps.runtimeProvider?.() ?? runtime,
|
|
32455
32775
|
toolsProvider: (ctx) => deps.toolsProvider?.(ctx) ?? tools,
|
|
32456
|
-
...deps.enabledToolNames !== void 0 || deps.enabledToolNamesProvider !== void 0 ? { enabledToolNamesProvider: async () => deps.enabledToolNamesProvider !== void 0 ?
|
|
32776
|
+
...deps.enabledToolNames !== void 0 || deps.enabledToolNamesProvider !== void 0 ? { enabledToolNamesProvider: async () => deps.enabledToolNamesProvider !== void 0 ? deps.enabledToolNamesProvider() : deps.enabledToolNames } : {},
|
|
32457
32777
|
defaultModelProvider: () => deps.defaultModelProvider?.() ?? defaultModel,
|
|
32458
32778
|
...deps.defaultSystemPromptProvider !== void 0 ? { defaultSystemPromptProvider: deps.defaultSystemPromptProvider } : {},
|
|
32459
32779
|
compactionProviderProvider: () => deps.compactionProviderProvider?.() ?? compactionProvider,
|
|
@@ -33287,7 +33607,7 @@ function normalizeSessionTimestampSeconds(value) {
|
|
|
33287
33607
|
async function readSessionInfo(paths, sessionId) {
|
|
33288
33608
|
const statePath = paths.statePath(sessionId);
|
|
33289
33609
|
const cache = new StateCache(statePath);
|
|
33290
|
-
const [state, lastActivity] = await Promise.all([cache.read(), stat(statePath).then((st) => Math.floor(st.mtimeMs / 1e3), () =>
|
|
33610
|
+
const [state, lastActivity] = await Promise.all([cache.read(), stat(statePath).then((st) => Math.floor(st.mtimeMs / 1e3), () => {})]);
|
|
33291
33611
|
if (state !== null) return {
|
|
33292
33612
|
session_id: state.session_id,
|
|
33293
33613
|
created_at: state.created_at,
|
|
@@ -47647,6 +47967,7 @@ var AnthropicStreamedMessage = class {
|
|
|
47647
47967
|
}
|
|
47648
47968
|
}
|
|
47649
47969
|
async *_convertStreamResponse(response) {
|
|
47970
|
+
const toolUseBlockIndexes = /* @__PURE__ */ new Set();
|
|
47650
47971
|
try {
|
|
47651
47972
|
for await (const event of response) {
|
|
47652
47973
|
const evt = event;
|
|
@@ -47680,6 +48001,7 @@ var AnthropicStreamedMessage = class {
|
|
|
47680
48001
|
};
|
|
47681
48002
|
break;
|
|
47682
48003
|
case "tool_use":
|
|
48004
|
+
toolUseBlockIndexes.add(blockIndex);
|
|
47683
48005
|
yield {
|
|
47684
48006
|
type: "function",
|
|
47685
48007
|
id: block.id,
|
|
@@ -47723,6 +48045,15 @@ var AnthropicStreamedMessage = class {
|
|
|
47723
48045
|
};
|
|
47724
48046
|
break;
|
|
47725
48047
|
}
|
|
48048
|
+
} else if (eventType === "content_block_stop") {
|
|
48049
|
+
const blockIndex = evt.index;
|
|
48050
|
+
if (blockIndex !== void 0 && toolUseBlockIndexes.has(blockIndex)) {
|
|
48051
|
+
toolUseBlockIndexes.delete(blockIndex);
|
|
48052
|
+
yield {
|
|
48053
|
+
type: "tool_call_done",
|
|
48054
|
+
index: blockIndex
|
|
48055
|
+
};
|
|
48056
|
+
}
|
|
47726
48057
|
} else if (eventType === "message_delta") {
|
|
47727
48058
|
const deltaUsage = evt.usage;
|
|
47728
48059
|
if (deltaUsage !== void 0) {
|
|
@@ -76942,6 +77273,7 @@ var KimiStreamedMessage = class {
|
|
|
76942
77273
|
};
|
|
76943
77274
|
var KimiChatProvider = class {
|
|
76944
77275
|
name = "kimi";
|
|
77276
|
+
toolCallReadyStrategy = "merge-boundary";
|
|
76945
77277
|
_model;
|
|
76946
77278
|
_stream;
|
|
76947
77279
|
_apiKey;
|
|
@@ -77718,7 +78050,7 @@ var OpenAIResponsesStreamedMessage = class {
|
|
|
77718
78050
|
} else if (chunkType === "response.output_item.added") {
|
|
77719
78051
|
const item = chunk["item"];
|
|
77720
78052
|
if (item["type"] === "function_call") {
|
|
77721
|
-
const
|
|
78053
|
+
const streamIndex = item["id"] ?? chunk["output_index"];
|
|
77722
78054
|
const tc = {
|
|
77723
78055
|
type: "function",
|
|
77724
78056
|
id: item["call_id"] || crypto.randomUUID(),
|
|
@@ -77727,7 +78059,7 @@ var OpenAIResponsesStreamedMessage = class {
|
|
|
77727
78059
|
arguments: item["arguments"] ?? null
|
|
77728
78060
|
}
|
|
77729
78061
|
};
|
|
77730
|
-
if (
|
|
78062
|
+
if (streamIndex !== void 0) tc._streamIndex = streamIndex;
|
|
77731
78063
|
yield tc;
|
|
77732
78064
|
}
|
|
77733
78065
|
} else if (chunkType === "response.output_item.done") {
|
|
@@ -77740,15 +78072,41 @@ var OpenAIResponsesStreamedMessage = class {
|
|
|
77740
78072
|
};
|
|
77741
78073
|
if (encContent !== void 0) thinkPart.encrypted = encContent;
|
|
77742
78074
|
yield thinkPart;
|
|
78075
|
+
} else if (item["type"] === "function_call") {
|
|
78076
|
+
const streamIndex = item["id"] ?? chunk["output_index"];
|
|
78077
|
+
if (streamIndex !== void 0) {
|
|
78078
|
+
const donePart = {
|
|
78079
|
+
type: "tool_call_done",
|
|
78080
|
+
index: streamIndex
|
|
78081
|
+
};
|
|
78082
|
+
const argumentsValue = item["arguments"];
|
|
78083
|
+
if (typeof argumentsValue === "string") donePart.arguments = argumentsValue;
|
|
78084
|
+
const name = item["name"];
|
|
78085
|
+
if (typeof name === "string") donePart.name = name;
|
|
78086
|
+
yield donePart;
|
|
78087
|
+
}
|
|
77743
78088
|
}
|
|
77744
78089
|
} else if (chunkType === "response.function_call_arguments.delta") {
|
|
77745
|
-
const
|
|
78090
|
+
const streamIndex = chunk["item_id"] ?? chunk["output_index"];
|
|
77746
78091
|
const part = {
|
|
77747
78092
|
type: "tool_call_part",
|
|
77748
78093
|
argumentsPart: chunk["delta"]
|
|
77749
78094
|
};
|
|
77750
|
-
if (
|
|
78095
|
+
if (streamIndex !== void 0) part.index = streamIndex;
|
|
77751
78096
|
yield part;
|
|
78097
|
+
} else if (chunkType === "response.function_call_arguments.done") {
|
|
78098
|
+
const streamIndex = chunk["item_id"] ?? chunk["output_index"];
|
|
78099
|
+
if (streamIndex !== void 0) {
|
|
78100
|
+
const donePart = {
|
|
78101
|
+
type: "tool_call_done",
|
|
78102
|
+
index: streamIndex
|
|
78103
|
+
};
|
|
78104
|
+
const argumentsValue = chunk["arguments"];
|
|
78105
|
+
if (typeof argumentsValue === "string") donePart.arguments = argumentsValue;
|
|
78106
|
+
const name = chunk["name"];
|
|
78107
|
+
if (typeof name === "string") donePart.name = name;
|
|
78108
|
+
yield donePart;
|
|
78109
|
+
}
|
|
77752
78110
|
} else if (chunkType === "response.reasoning_summary_part.added") yield {
|
|
77753
78111
|
type: "think",
|
|
77754
78112
|
think: ""
|
|
@@ -78182,12 +78540,14 @@ var DeferredOAuthChatProvider = class DeferredOAuthChatProvider {
|
|
|
78182
78540
|
name;
|
|
78183
78541
|
modelName;
|
|
78184
78542
|
thinkingEffort;
|
|
78543
|
+
toolCallReadyStrategy;
|
|
78185
78544
|
options;
|
|
78186
78545
|
constructor(options) {
|
|
78187
78546
|
this.options = options;
|
|
78188
78547
|
this.name = options.providerConfig.type;
|
|
78189
78548
|
this.modelName = options.modelOverride ?? options.providerConfig.defaultModel ?? "";
|
|
78190
78549
|
this.thinkingEffort = options.thinkingEffort ?? null;
|
|
78550
|
+
this.toolCallReadyStrategy = this.createProviderWithApiKey("__oauth_token_placeholder__").toolCallReadyStrategy;
|
|
78191
78551
|
}
|
|
78192
78552
|
async generate(systemPrompt, tools, history, options) {
|
|
78193
78553
|
return (await this.materializeProvider()).generate(systemPrompt, tools, history, options);
|
|
@@ -78208,20 +78568,20 @@ var DeferredOAuthChatProvider = class DeferredOAuthChatProvider {
|
|
|
78208
78568
|
});
|
|
78209
78569
|
}
|
|
78210
78570
|
getCapability(model) {
|
|
78211
|
-
return
|
|
78212
|
-
...this.options.providerConfig,
|
|
78213
|
-
apiKey: "__oauth_token_placeholder__"
|
|
78214
|
-
}, this.options.modelOverride, this.options.defaultHeaders).getCapability?.(model) ?? UNKNOWN_CAPABILITY;
|
|
78571
|
+
return this.createProviderWithApiKey("__oauth_token_placeholder__").getCapability?.(model) ?? UNKNOWN_CAPABILITY;
|
|
78215
78572
|
}
|
|
78216
78573
|
async materializeProvider() {
|
|
78217
78574
|
const accessToken = await this.options.oauthResolver(this.options.providerName);
|
|
78218
|
-
let provider =
|
|
78219
|
-
...this.options.providerConfig,
|
|
78220
|
-
apiKey: accessToken
|
|
78221
|
-
}, this.options.modelOverride, this.options.defaultHeaders);
|
|
78575
|
+
let provider = this.createProviderWithApiKey(accessToken);
|
|
78222
78576
|
if (this.thinkingEffort !== null) provider = provider.withThinking(this.thinkingEffort);
|
|
78223
78577
|
return provider;
|
|
78224
78578
|
}
|
|
78579
|
+
createProviderWithApiKey(apiKey) {
|
|
78580
|
+
return createProvider(this.options.providerName, {
|
|
78581
|
+
...this.options.providerConfig,
|
|
78582
|
+
apiKey
|
|
78583
|
+
}, this.options.modelOverride, this.options.defaultHeaders);
|
|
78584
|
+
}
|
|
78225
78585
|
};
|
|
78226
78586
|
//#endregion
|
|
78227
78587
|
//#region ../../packages/kimi-core/src/soul-plus/subagent/agent-type-registry.ts
|
|
@@ -91723,8 +92083,8 @@ async function postForm(url, params, deviceHeaders, options) {
|
|
|
91723
92083
|
body,
|
|
91724
92084
|
signal
|
|
91725
92085
|
});
|
|
91726
|
-
} catch (
|
|
91727
|
-
throw new OAuthError(`OAuth request to ${url} failed: ${
|
|
92086
|
+
} catch (error) {
|
|
92087
|
+
throw new OAuthError(`OAuth request to ${url} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
91728
92088
|
}
|
|
91729
92089
|
const status = response.status;
|
|
91730
92090
|
let data = {};
|
|
@@ -91803,8 +92163,8 @@ async function refreshAccessToken(config, refreshToken, options = {}) {
|
|
|
91803
92163
|
grant_type: "refresh_token",
|
|
91804
92164
|
refresh_token: refreshToken
|
|
91805
92165
|
}, getDeviceHeaders()));
|
|
91806
|
-
} catch (
|
|
91807
|
-
lastError =
|
|
92166
|
+
} catch (error) {
|
|
92167
|
+
lastError = error instanceof Error ? error : new OAuthError(String(error));
|
|
91808
92168
|
if (attempt < maxRetries - 1) {
|
|
91809
92169
|
await sleep(backoff(attempt));
|
|
91810
92170
|
continue;
|
|
@@ -96466,6 +96826,15 @@ var ToolCallComponent = class extends Container {
|
|
|
96466
96826
|
subagentUsage;
|
|
96467
96827
|
subagentResultSummary;
|
|
96468
96828
|
subagentError;
|
|
96829
|
+
/**
|
|
96830
|
+
* 当 ToolCallComponent 被 group 容器(`AgentGroupComponent` /
|
|
96831
|
+
* `ReadGroupComponent`)借走做"隐身状态容器"时,由 group 注册回调;任何
|
|
96832
|
+
* 状态变化(subagent meta / phase / sub-tool / result 等)都会触发它,
|
|
96833
|
+
* 让 group 走节流重渲染。`undefined` 表示没人订阅,对独立单卡渲染路径
|
|
96834
|
+
* 无副作用。一个 ToolCallComponent 同时只可能在一个 group 里,所以
|
|
96835
|
+
* 单 listener slot 足够;后到的 listener 会覆盖前一个。
|
|
96836
|
+
*/
|
|
96837
|
+
onSnapshotChange;
|
|
96469
96838
|
constructor(toolCall, result, colors, ui, markdownTheme) {
|
|
96470
96839
|
super();
|
|
96471
96840
|
this.toolCall = toolCall;
|
|
@@ -96491,6 +96860,7 @@ var ToolCallComponent = class extends Container {
|
|
|
96491
96860
|
this.result = result;
|
|
96492
96861
|
this.headerText.setText(this.buildHeader());
|
|
96493
96862
|
this.rebuildBody();
|
|
96863
|
+
this.notifySnapshotChange();
|
|
96494
96864
|
}
|
|
96495
96865
|
updateToolCall(toolCall) {
|
|
96496
96866
|
this.toolCall = toolCall;
|
|
@@ -96548,9 +96918,72 @@ var ToolCallComponent = class extends Container {
|
|
|
96548
96918
|
this.subagentAgentId = agentId;
|
|
96549
96919
|
this.subagentAgentName = agentName;
|
|
96550
96920
|
this.rebuildContent();
|
|
96921
|
+
this.notifySnapshotChange();
|
|
96551
96922
|
this.ui?.requestRender();
|
|
96552
96923
|
}
|
|
96553
96924
|
/**
|
|
96925
|
+
* 让 group 容器(AgentGroup / ReadGroup)订阅本卡的状态变化。注册同时
|
|
96926
|
+
* 立即触发一次回调,使 group 拿到当前快照(无需再调一次 getSubagentSnapshot
|
|
96927
|
+
* / getReadSnapshot)。传入 `undefined` 解绑。
|
|
96928
|
+
*/
|
|
96929
|
+
setSnapshotListener(cb) {
|
|
96930
|
+
this.onSnapshotChange = cb;
|
|
96931
|
+
if (cb !== void 0) cb();
|
|
96932
|
+
}
|
|
96933
|
+
getSubagentSnapshot() {
|
|
96934
|
+
const finished = this.finishedSubCalls.length + this.hiddenSubCallCount;
|
|
96935
|
+
const tokens = this.subagentUsage ? this.subagentUsage.input + this.subagentUsage.output : 0;
|
|
96936
|
+
const latestActivity = computeLatestActivity(this.ongoingSubCalls, this.finishedSubCalls, this.subagentText);
|
|
96937
|
+
const derivedPhase = this.result !== void 0 ? this.result.is_error ? "failed" : "done" : this.subagentPhase;
|
|
96938
|
+
return {
|
|
96939
|
+
toolCallId: this.toolCall.id,
|
|
96940
|
+
toolName: this.toolCall.name,
|
|
96941
|
+
toolCallDescription: str(this.toolCall.args["description"]) || str(this.toolCall.description),
|
|
96942
|
+
agentName: this.subagentAgentName,
|
|
96943
|
+
phase: derivedPhase,
|
|
96944
|
+
toolCount: finished,
|
|
96945
|
+
tokens,
|
|
96946
|
+
isError: derivedPhase === "failed",
|
|
96947
|
+
errorText: this.subagentError ?? (derivedPhase === "failed" ? this.result?.output : void 0),
|
|
96948
|
+
latestActivity
|
|
96949
|
+
};
|
|
96950
|
+
}
|
|
96951
|
+
/**
|
|
96952
|
+
* 给 `ReadGroupComponent` 用:累加同 step 多张 Read 卡的 line 数。
|
|
96953
|
+
* `lines` 与单卡 chip(`pluralize(countNonEmptyLines(...), 'line')`)保持
|
|
96954
|
+
* 一致,避免 group 汇总和单卡读数不对齐。
|
|
96955
|
+
*/
|
|
96956
|
+
getReadSnapshot() {
|
|
96957
|
+
const args = this.toolCall.args;
|
|
96958
|
+
const filePathRaw = args["file_path"] ?? args["path"];
|
|
96959
|
+
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
96960
|
+
if (this.result === void 0) return {
|
|
96961
|
+
toolCallId: this.toolCall.id,
|
|
96962
|
+
filePath,
|
|
96963
|
+
phase: "pending",
|
|
96964
|
+
lines: 0
|
|
96965
|
+
};
|
|
96966
|
+
if (this.result.is_error === true) return {
|
|
96967
|
+
toolCallId: this.toolCall.id,
|
|
96968
|
+
filePath,
|
|
96969
|
+
phase: "failed",
|
|
96970
|
+
lines: 0
|
|
96971
|
+
};
|
|
96972
|
+
return {
|
|
96973
|
+
toolCallId: this.toolCall.id,
|
|
96974
|
+
filePath,
|
|
96975
|
+
phase: "done",
|
|
96976
|
+
lines: countNonEmptyLines(this.result.output)
|
|
96977
|
+
};
|
|
96978
|
+
}
|
|
96979
|
+
get toolCallView() {
|
|
96980
|
+
return this.toolCall;
|
|
96981
|
+
}
|
|
96982
|
+
/** 给本组件内部调用:state 任何变化都通知监听者(如果已绑定 group)。 */
|
|
96983
|
+
notifySnapshotChange() {
|
|
96984
|
+
this.onSnapshotChange?.();
|
|
96985
|
+
}
|
|
96986
|
+
/**
|
|
96554
96987
|
* 来自 wire 'subagent.spawned'。子 agent 已被注册,但内部活动事件
|
|
96555
96988
|
* (content.delta / tool.call) 可能尚未到达——把 UI 切到 'spawning'
|
|
96556
96989
|
* 占位状态(除非 runInBackground,这种情况直接进 'backgrounded')。
|
|
@@ -96561,6 +96994,7 @@ var ToolCallComponent = class extends Container {
|
|
|
96561
96994
|
this.subagentRunInBackground = meta.runInBackground;
|
|
96562
96995
|
this.subagentPhase = meta.runInBackground ? "backgrounded" : "spawning";
|
|
96563
96996
|
this.rebuildContent();
|
|
96997
|
+
this.notifySnapshotChange();
|
|
96564
96998
|
this.ui?.requestRender();
|
|
96565
96999
|
}
|
|
96566
97000
|
/**
|
|
@@ -96572,6 +97006,7 @@ var ToolCallComponent = class extends Container {
|
|
|
96572
97006
|
this.subagentUsage = payload.usage;
|
|
96573
97007
|
this.subagentResultSummary = payload.resultSummary.length > 0 ? payload.resultSummary : void 0;
|
|
96574
97008
|
this.rebuildContent();
|
|
97009
|
+
this.notifySnapshotChange();
|
|
96575
97010
|
this.ui?.requestRender();
|
|
96576
97011
|
}
|
|
96577
97012
|
/**
|
|
@@ -96581,12 +97016,14 @@ var ToolCallComponent = class extends Container {
|
|
|
96581
97016
|
this.subagentPhase = "failed";
|
|
96582
97017
|
this.subagentError = payload.error;
|
|
96583
97018
|
this.rebuildContent();
|
|
97019
|
+
this.notifySnapshotChange();
|
|
96584
97020
|
this.ui?.requestRender();
|
|
96585
97021
|
}
|
|
96586
97022
|
appendSubagentText(text) {
|
|
96587
97023
|
this.subagentText += text;
|
|
96588
97024
|
if (this.subagentPhase === void 0 || this.subagentPhase === "spawning") this.subagentPhase = "running";
|
|
96589
97025
|
this.rebuildContent();
|
|
97026
|
+
this.notifySnapshotChange();
|
|
96590
97027
|
this.ui?.requestRender();
|
|
96591
97028
|
}
|
|
96592
97029
|
appendSubToolCall(call) {
|
|
@@ -96598,6 +97035,7 @@ var ToolCallComponent = class extends Container {
|
|
|
96598
97035
|
});
|
|
96599
97036
|
if (this.subagentPhase === void 0 || this.subagentPhase === "spawning") this.subagentPhase = "running";
|
|
96600
97037
|
this.rebuildContent();
|
|
97038
|
+
this.notifySnapshotChange();
|
|
96601
97039
|
this.ui?.requestRender();
|
|
96602
97040
|
}
|
|
96603
97041
|
appendSubToolCallDelta(delta) {
|
|
@@ -96610,6 +97048,7 @@ var ToolCallComponent = class extends Container {
|
|
|
96610
97048
|
streamingArguments: nextArgsText
|
|
96611
97049
|
});
|
|
96612
97050
|
this.rebuildContent();
|
|
97051
|
+
this.notifySnapshotChange();
|
|
96613
97052
|
this.ui?.requestRender();
|
|
96614
97053
|
}
|
|
96615
97054
|
finishSubToolCall(result) {
|
|
@@ -96627,6 +97066,7 @@ var ToolCallComponent = class extends Container {
|
|
|
96627
97066
|
this.hiddenSubCallCount += 1;
|
|
96628
97067
|
}
|
|
96629
97068
|
this.rebuildContent();
|
|
97069
|
+
this.notifySnapshotChange();
|
|
96630
97070
|
this.ui?.requestRender();
|
|
96631
97071
|
}
|
|
96632
97072
|
buildHeader() {
|
|
@@ -96651,7 +97091,7 @@ var ToolCallComponent = class extends Container {
|
|
|
96651
97091
|
const tone = isError ? chalk.hex(colors.error) : chalk.hex(colors.primary);
|
|
96652
97092
|
return `${bullet}${tone.bold(label)}`;
|
|
96653
97093
|
}
|
|
96654
|
-
const verb = isFinished ? "Used" : "Using";
|
|
97094
|
+
const verb = isFinished ? "Used" : toolCall.streamingArguments !== void 0 ? "Preparing" : "Using";
|
|
96655
97095
|
const keyArg = extractKeyArgument(toolCall.name, toolCall.args);
|
|
96656
97096
|
const toolRef = chalk.hex(colors.primary).bold(toolCall.name);
|
|
96657
97097
|
const argStr = keyArg ? chalk.dim(` (${keyArg})`) : "";
|
|
@@ -96907,6 +97347,30 @@ var ToolCallComponent = class extends Container {
|
|
|
96907
97347
|
return true;
|
|
96908
97348
|
}
|
|
96909
97349
|
};
|
|
97350
|
+
/**
|
|
97351
|
+
* 推算 group line 第二级"最新活动"行:优先级
|
|
97352
|
+
* 1. 最近的 ongoing sub-tool(`Using {name} ({keyArg})`)
|
|
97353
|
+
* 2. 最近的 finished sub-tool(`Used {name} ({keyArg})`)
|
|
97354
|
+
* 3. subagent 累计文本的非空末行
|
|
97355
|
+
*/
|
|
97356
|
+
function computeLatestActivity(ongoing, finished, text) {
|
|
97357
|
+
if (ongoing.size > 0) {
|
|
97358
|
+
const lastOngoing = [...ongoing.values()].at(-1);
|
|
97359
|
+
if (lastOngoing !== void 0) return formatActivityLine("Using", lastOngoing.name, lastOngoing.args);
|
|
97360
|
+
}
|
|
97361
|
+
if (finished.length > 0) {
|
|
97362
|
+
const last = finished.at(-1);
|
|
97363
|
+
if (last !== void 0) return formatActivityLine("Used", last.name, last.args);
|
|
97364
|
+
}
|
|
97365
|
+
if (text.length > 0) {
|
|
97366
|
+
const tail = text.split("\n").toReversed().find((l) => l.trim().length > 0);
|
|
97367
|
+
if (tail !== void 0) return tail.trim();
|
|
97368
|
+
}
|
|
97369
|
+
}
|
|
97370
|
+
function formatActivityLine(verb, toolName, args) {
|
|
97371
|
+
const keyArg = extractKeyArgument(toolName, args);
|
|
97372
|
+
return keyArg ? `${verb} ${toolName} (${keyArg})` : `${verb} ${toolName}`;
|
|
97373
|
+
}
|
|
96910
97374
|
//#endregion
|
|
96911
97375
|
//#region src/tui/components/messages/user-message.ts
|
|
96912
97376
|
/**
|
|
@@ -97602,6 +98066,7 @@ function clearTranscriptAndRedraw(state) {
|
|
|
97602
98066
|
state.transcriptContainer.clear();
|
|
97603
98067
|
state.pendingToolComponents.clear();
|
|
97604
98068
|
state.streamingComponent = void 0;
|
|
98069
|
+
state.streamingTranscriptEntry = void 0;
|
|
97605
98070
|
state.todoPanel.clear();
|
|
97606
98071
|
state.todoPanelContainer.clear();
|
|
97607
98072
|
state.imageStore.clear();
|
|
@@ -97677,7 +98142,19 @@ function handleSubagentSourceEvent(ectx, payload) {
|
|
|
97677
98142
|
}
|
|
97678
98143
|
}
|
|
97679
98144
|
function handleSubagentSpawned(ectx, data) {
|
|
97680
|
-
|
|
98145
|
+
if (data.run_in_background) {
|
|
98146
|
+
ectx.state.backgroundAgents.add(data.agent_id);
|
|
98147
|
+
syncBackgroundAgentBadge(ectx);
|
|
98148
|
+
return;
|
|
98149
|
+
}
|
|
98150
|
+
let tc = ectx.state.pendingToolComponents.get(data.parent_tool_call_id);
|
|
98151
|
+
if (tc === void 0) {
|
|
98152
|
+
const toolCall = ectx.state.activeToolCalls.get(data.parent_tool_call_id);
|
|
98153
|
+
if (toolCall !== void 0) {
|
|
98154
|
+
ectx.onToolCallStart(toolCall);
|
|
98155
|
+
tc = ectx.state.pendingToolComponents.get(data.parent_tool_call_id);
|
|
98156
|
+
}
|
|
98157
|
+
}
|
|
97681
98158
|
if (tc === void 0) return;
|
|
97682
98159
|
tc.onSubagentSpawned({
|
|
97683
98160
|
agentId: data.agent_id,
|
|
@@ -97686,6 +98163,7 @@ function handleSubagentSpawned(ectx, data) {
|
|
|
97686
98163
|
});
|
|
97687
98164
|
}
|
|
97688
98165
|
function handleSubagentCompleted(ectx, data) {
|
|
98166
|
+
if (ectx.state.backgroundAgents.delete(data.agent_id)) syncBackgroundAgentBadge(ectx);
|
|
97689
98167
|
const tc = ectx.state.pendingToolComponents.get(data.parent_tool_call_id);
|
|
97690
98168
|
if (tc === void 0) return;
|
|
97691
98169
|
tc.onSubagentCompleted({
|
|
@@ -97694,10 +98172,15 @@ function handleSubagentCompleted(ectx, data) {
|
|
|
97694
98172
|
});
|
|
97695
98173
|
}
|
|
97696
98174
|
function handleSubagentFailed(ectx, data) {
|
|
98175
|
+
if (ectx.state.backgroundAgents.delete(data.agent_id)) syncBackgroundAgentBadge(ectx);
|
|
97697
98176
|
const tc = ectx.state.pendingToolComponents.get(data.parent_tool_call_id);
|
|
97698
98177
|
if (tc === void 0) return;
|
|
97699
98178
|
tc.onSubagentFailed({ error: data.error });
|
|
97700
98179
|
}
|
|
98180
|
+
function syncBackgroundAgentBadge(ectx) {
|
|
98181
|
+
ectx.state.footer.setBackgroundAgentCount(ectx.state.backgroundAgents.size);
|
|
98182
|
+
ectx.state.ui.requestRender();
|
|
98183
|
+
}
|
|
97701
98184
|
//#endregion
|
|
97702
98185
|
//#region src/utils/git/git-ls-files.ts
|
|
97703
98186
|
/**
|
|
@@ -98705,6 +99188,13 @@ var FooterComponent = class {
|
|
|
98705
99188
|
gitCache;
|
|
98706
99189
|
gitCacheWorkDir;
|
|
98707
99190
|
transientHint = null;
|
|
99191
|
+
/**
|
|
99192
|
+
* 在跑的 background subagent 数量。`subagent.spawned (run_in_background)`
|
|
99193
|
+
* 入集合 → 加 1;`subagent.completed/failed` → 减 1(详见
|
|
99194
|
+
* `handlers/subagent.ts`)。0 时整块隐藏;>=1 时在 line1 的 cwd 左侧
|
|
99195
|
+
* 插入 `[N agents running]` badge(位于 `agent(model ○)` 与 cwd 之间)。
|
|
99196
|
+
*/
|
|
99197
|
+
backgroundAgentCount = 0;
|
|
98708
99198
|
constructor(state, colors) {
|
|
98709
99199
|
this.state = state;
|
|
98710
99200
|
this.colors = colors;
|
|
@@ -98730,6 +99220,14 @@ var FooterComponent = class {
|
|
|
98730
99220
|
setTransientHint(hint) {
|
|
98731
99221
|
this.transientHint = hint;
|
|
98732
99222
|
}
|
|
99223
|
+
/**
|
|
99224
|
+
* Sync the background-agent badge with the live count. Called by
|
|
99225
|
+
* `handlers/subagent.ts` after `subagent.spawned/completed/failed` mutate
|
|
99226
|
+
* `state.backgroundAgents`. n=0 hides the badge entirely.
|
|
99227
|
+
*/
|
|
99228
|
+
setBackgroundAgentCount(n) {
|
|
99229
|
+
this.backgroundAgentCount = Math.max(0, n);
|
|
99230
|
+
}
|
|
98733
99231
|
invalidate() {}
|
|
98734
99232
|
attach(handlers, requestRender) {
|
|
98735
99233
|
const unsubStatus = handlers.onStatusUpdate((data) => {
|
|
@@ -98771,6 +99269,7 @@ var FooterComponent = class {
|
|
|
98771
99269
|
const dot = state.thinking ? "●" : "○";
|
|
98772
99270
|
left.push(chalk.hex(colors.text)(`agent(${model} ${dot})`));
|
|
98773
99271
|
}
|
|
99272
|
+
if (this.backgroundAgentCount > 0) left.push(chalk.hex(colors.primary)(`[${String(this.backgroundAgentCount)} agents running]`));
|
|
98774
99273
|
const cwd = shortenCwd(state.workDir);
|
|
98775
99274
|
if (cwd) left.push(chalk.hex(colors.status)(cwd));
|
|
98776
99275
|
const git = this.gitCache.getStatus();
|
|
@@ -99011,11 +99510,15 @@ function createTUIState(options) {
|
|
|
99011
99510
|
phaseSpinner: void 0,
|
|
99012
99511
|
activeThinkingComponent: void 0,
|
|
99013
99512
|
streamingComponent: void 0,
|
|
99513
|
+
streamingTranscriptEntry: void 0,
|
|
99014
99514
|
activeCompactionBlock: void 0,
|
|
99015
99515
|
toolOutputExpanded: false,
|
|
99016
99516
|
lastActivityMode: void 0,
|
|
99017
99517
|
lastHistoryContent: void 0,
|
|
99018
99518
|
pendingToolComponents: /* @__PURE__ */ new Map(),
|
|
99519
|
+
pendingAgentGroup: null,
|
|
99520
|
+
pendingReadGroup: null,
|
|
99521
|
+
backgroundAgents: /* @__PURE__ */ new Set(),
|
|
99019
99522
|
sessions: [],
|
|
99020
99523
|
loadingSessions: false,
|
|
99021
99524
|
showingSessionPicker: false,
|
|
@@ -99026,6 +99529,7 @@ function createTUIState(options) {
|
|
|
99026
99529
|
fdPath: detectFdPath(),
|
|
99027
99530
|
gitLsFilesCache: createGitLsFilesCache(initialAppState.workDir),
|
|
99028
99531
|
currentTurnId: void 0,
|
|
99532
|
+
currentStep: 0,
|
|
99029
99533
|
assistantDraft: "",
|
|
99030
99534
|
assistantStreamActive: false,
|
|
99031
99535
|
thinkingDraft: "",
|
|
@@ -99196,6 +99700,8 @@ function sendMessageInternal(state, addEntry, input, options) {
|
|
|
99196
99700
|
state.currentTurnId = void 0;
|
|
99197
99701
|
state.assistantDraft = "";
|
|
99198
99702
|
state.assistantStreamActive = false;
|
|
99703
|
+
state.streamingComponent = void 0;
|
|
99704
|
+
state.streamingTranscriptEntry = void 0;
|
|
99199
99705
|
state.thinkingDraft = "";
|
|
99200
99706
|
disposeActiveThinkingComponent(state);
|
|
99201
99707
|
state.activeToolCalls.clear();
|
|
@@ -99257,6 +99763,8 @@ function sendSkillActivation(state, addEntry, skillName, skillArgs, fullPrompt)
|
|
|
99257
99763
|
state.currentTurnId = void 0;
|
|
99258
99764
|
state.assistantDraft = "";
|
|
99259
99765
|
state.assistantStreamActive = false;
|
|
99766
|
+
state.streamingComponent = void 0;
|
|
99767
|
+
state.streamingTranscriptEntry = void 0;
|
|
99260
99768
|
state.thinkingDraft = "";
|
|
99261
99769
|
disposeActiveThinkingComponent(state);
|
|
99262
99770
|
state.activeToolCalls.clear();
|
|
@@ -99433,13 +99941,186 @@ async function performReload(state, action, hooks) {
|
|
|
99433
99941
|
state.ui.requestRender();
|
|
99434
99942
|
}
|
|
99435
99943
|
//#endregion
|
|
99944
|
+
//#region src/tui/components/messages/agent-group.ts
|
|
99945
|
+
const THROTTLE_MS$1 = 200;
|
|
99946
|
+
var AgentGroupComponent = class extends Container {
|
|
99947
|
+
entries = [];
|
|
99948
|
+
headerText;
|
|
99949
|
+
bodyContainer;
|
|
99950
|
+
throttleTimer = null;
|
|
99951
|
+
lastFlushPhases = /* @__PURE__ */ new Map();
|
|
99952
|
+
constructor(colors, ui) {
|
|
99953
|
+
super();
|
|
99954
|
+
this.colors = colors;
|
|
99955
|
+
this.ui = ui;
|
|
99956
|
+
this.addChild(new Spacer(1));
|
|
99957
|
+
this.headerText = new Text("", 0, 0);
|
|
99958
|
+
this.addChild(this.headerText);
|
|
99959
|
+
this.bodyContainer = new Container();
|
|
99960
|
+
this.addChild(this.bodyContainer);
|
|
99961
|
+
}
|
|
99962
|
+
size() {
|
|
99963
|
+
return this.entries.length;
|
|
99964
|
+
}
|
|
99965
|
+
/**
|
|
99966
|
+
* 把一个独立 `ToolCallComponent` 借进 group 做"隐身状态容器"。注册
|
|
99967
|
+
* snapshot 监听 → 状态有变即触发节流刷新。多次 attach 同一个 toolCallId
|
|
99968
|
+
* 是 noop。
|
|
99969
|
+
*/
|
|
99970
|
+
attach(toolCallId, tc) {
|
|
99971
|
+
if (this.entries.some((e) => e.toolCallId === toolCallId)) return;
|
|
99972
|
+
this.entries.push({
|
|
99973
|
+
toolCallId,
|
|
99974
|
+
tc
|
|
99975
|
+
});
|
|
99976
|
+
tc.setSnapshotListener(() => {
|
|
99977
|
+
this.scheduleRender();
|
|
99978
|
+
});
|
|
99979
|
+
this.flushRender();
|
|
99980
|
+
}
|
|
99981
|
+
/**
|
|
99982
|
+
* 立刻调度一次重绘。phase 真正跨阶段切换(spawning ↔ running ↔ done/failed)
|
|
99983
|
+
* 时强制立刷新;其他变更(latestActivity / tokens / toolCount)走节流。
|
|
99984
|
+
*/
|
|
99985
|
+
scheduleRender() {
|
|
99986
|
+
if (this.detectPhaseTransition()) {
|
|
99987
|
+
this.flushRender();
|
|
99988
|
+
return;
|
|
99989
|
+
}
|
|
99990
|
+
if (this.throttleTimer !== null) return;
|
|
99991
|
+
this.throttleTimer = setTimeout(() => {
|
|
99992
|
+
this.throttleTimer = null;
|
|
99993
|
+
this.flushRender();
|
|
99994
|
+
}, THROTTLE_MS$1);
|
|
99995
|
+
}
|
|
99996
|
+
/**
|
|
99997
|
+
* 比较当前各 child 的 phase 与上次刷新时的 phase 快照。任意一个发生
|
|
99998
|
+
* 变化即视为 phase transition,触发立刷新。
|
|
99999
|
+
*/
|
|
100000
|
+
detectPhaseTransition() {
|
|
100001
|
+
let changed = false;
|
|
100002
|
+
for (const e of this.entries) {
|
|
100003
|
+
const phase = e.tc.getSubagentSnapshot().phase;
|
|
100004
|
+
if (this.lastFlushPhases.get(e.toolCallId) !== phase) {
|
|
100005
|
+
changed = true;
|
|
100006
|
+
break;
|
|
100007
|
+
}
|
|
100008
|
+
}
|
|
100009
|
+
return changed;
|
|
100010
|
+
}
|
|
100011
|
+
flushRender() {
|
|
100012
|
+
if (this.throttleTimer !== null) {
|
|
100013
|
+
clearTimeout(this.throttleTimer);
|
|
100014
|
+
this.throttleTimer = null;
|
|
100015
|
+
}
|
|
100016
|
+
const snapshots = this.entries.map((e) => e.tc.getSubagentSnapshot());
|
|
100017
|
+
this.headerText.setText(this.buildHeader(snapshots));
|
|
100018
|
+
this.bodyContainer.clear();
|
|
100019
|
+
snapshots.forEach((snap, idx) => {
|
|
100020
|
+
const isLast = idx === snapshots.length - 1;
|
|
100021
|
+
this.appendLines(snap, isLast);
|
|
100022
|
+
});
|
|
100023
|
+
this.lastFlushPhases.clear();
|
|
100024
|
+
this.entries.forEach((entry, i) => {
|
|
100025
|
+
const snap = snapshots[i];
|
|
100026
|
+
if (snap !== void 0) this.lastFlushPhases.set(entry.toolCallId, snap.phase);
|
|
100027
|
+
});
|
|
100028
|
+
this.invalidate();
|
|
100029
|
+
this.ui?.requestRender();
|
|
100030
|
+
}
|
|
100031
|
+
buildHeader(snapshots) {
|
|
100032
|
+
const colors = this.colors;
|
|
100033
|
+
const total = snapshots.length;
|
|
100034
|
+
const done = snapshots.filter((s) => s.phase === "done").length;
|
|
100035
|
+
const failed = snapshots.filter((s) => s.phase === "failed").length;
|
|
100036
|
+
const finished = done + failed;
|
|
100037
|
+
const allDone = finished === total;
|
|
100038
|
+
const bullet = allDone ? chalk.hex(colors.success)("⏺ ") : chalk.hex(colors.roleAssistant)("⏺ ");
|
|
100039
|
+
if (allDone) {
|
|
100040
|
+
const types = new Set(snapshots.map((s) => s.agentName).filter((n) => n !== void 0));
|
|
100041
|
+
const headerLabel = types.size === 1 ? `${String(total)} ${[...types][0]} agents finished` : `${String(total)} agents finished`;
|
|
100042
|
+
const tail = formatHeaderTail(snapshots.reduce((acc, s) => acc + s.toolCount, 0), snapshots.reduce((acc, s) => acc + s.tokens, 0));
|
|
100043
|
+
return `${bullet}${chalk.hex(colors.primary).bold(headerLabel)}${tail}`;
|
|
100044
|
+
}
|
|
100045
|
+
let headerText = `Running ${String(total)} agents`;
|
|
100046
|
+
if (finished > 0) {
|
|
100047
|
+
const running = total - finished;
|
|
100048
|
+
const parts = [];
|
|
100049
|
+
if (done > 0) parts.push(`${String(done)} done`);
|
|
100050
|
+
if (failed > 0) parts.push(`${String(failed)} failed`);
|
|
100051
|
+
if (running > 0) parts.push(`${String(running)} running`);
|
|
100052
|
+
headerText = `Running ${String(total)} agents (${parts.join(", ")})`;
|
|
100053
|
+
}
|
|
100054
|
+
return `${bullet}${chalk.hex(colors.primary).bold(headerText)}`;
|
|
100055
|
+
}
|
|
100056
|
+
appendLines(snap, isLast) {
|
|
100057
|
+
const colors = this.colors;
|
|
100058
|
+
const dim = chalk.dim;
|
|
100059
|
+
const branch1 = isLast ? "└─" : "├─";
|
|
100060
|
+
const agentType = snap.agentName ?? "agent";
|
|
100061
|
+
const desc = snap.toolCallDescription || "(no description)";
|
|
100062
|
+
const tail = formatLineTail(snap, colors);
|
|
100063
|
+
const line1 = ` ${branch1} ${chalk.hex(colors.primary)(agentType)} ${dim(`· ${desc}`)}${formatStats(snap)}${tail}`;
|
|
100064
|
+
this.bodyContainer.addChild(new Text(line1, 0, 0));
|
|
100065
|
+
const branch2 = isLast ? " " : "│ ";
|
|
100066
|
+
if (snap.phase === "failed") {
|
|
100067
|
+
const errLine = (snap.errorText ?? "Failed").split("\n").at(0) ?? "Failed";
|
|
100068
|
+
const errStr = chalk.hex(colors.error)(`Error: ${errLine}`);
|
|
100069
|
+
this.bodyContainer.addChild(new Text(` ${branch2} ${errStr}`, 0, 0));
|
|
100070
|
+
return;
|
|
100071
|
+
}
|
|
100072
|
+
if (snap.phase === "done" || snap.phase === "backgrounded") return;
|
|
100073
|
+
const activity = snap.latestActivity ?? "Initializing…";
|
|
100074
|
+
this.bodyContainer.addChild(new Text(` ${branch2} ${dim(activity)}`, 0, 0));
|
|
100075
|
+
}
|
|
100076
|
+
/**
|
|
100077
|
+
* 给测试 / 开发期使用,立刻冲掉节流计时器并刷新。生产代码不需要主动调。
|
|
100078
|
+
*/
|
|
100079
|
+
flushNow() {
|
|
100080
|
+
this.flushRender();
|
|
100081
|
+
}
|
|
100082
|
+
/** 释放节流计时器,避免组件被销毁后还触发刷新。 */
|
|
100083
|
+
dispose() {
|
|
100084
|
+
if (this.throttleTimer !== null) {
|
|
100085
|
+
clearTimeout(this.throttleTimer);
|
|
100086
|
+
this.throttleTimer = null;
|
|
100087
|
+
}
|
|
100088
|
+
for (const e of this.entries) e.tc.setSnapshotListener(void 0);
|
|
100089
|
+
}
|
|
100090
|
+
};
|
|
100091
|
+
function formatStats(snap) {
|
|
100092
|
+
const dim = chalk.dim;
|
|
100093
|
+
return dim(`${` · ${String(snap.toolCount)} tool${snap.toolCount === 1 ? "" : "s"}`}${snap.tokens > 0 ? ` · ${formatTokens(snap.tokens)}` : ""}`);
|
|
100094
|
+
}
|
|
100095
|
+
function formatLineTail(snap, colors) {
|
|
100096
|
+
if (snap.phase === "done") return chalk.dim(" · ") + chalk.hex(colors.success)("✓ Completed");
|
|
100097
|
+
if (snap.phase === "failed") return chalk.dim(" · ") + chalk.hex(colors.error)("✗ Failed");
|
|
100098
|
+
if (snap.phase === "backgrounded") return chalk.dim(" · ◐ backgrounded");
|
|
100099
|
+
return "";
|
|
100100
|
+
}
|
|
100101
|
+
function formatHeaderTail(toolCount, tokens) {
|
|
100102
|
+
const dim = chalk.dim;
|
|
100103
|
+
const parts = [];
|
|
100104
|
+
if (toolCount > 0) parts.push(`${String(toolCount)} tool${toolCount === 1 ? "" : "s"}`);
|
|
100105
|
+
if (tokens > 0) parts.push(formatTokens(tokens));
|
|
100106
|
+
return parts.length > 0 ? dim(` · ${parts.join(" · ")}`) : "";
|
|
100107
|
+
}
|
|
100108
|
+
function formatTokens(n) {
|
|
100109
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M tok`;
|
|
100110
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k tok`;
|
|
100111
|
+
return `${String(n)} tok`;
|
|
100112
|
+
}
|
|
100113
|
+
//#endregion
|
|
99436
100114
|
//#region src/tui/actions/replay-ops.ts
|
|
99437
100115
|
async function hydrateTranscriptFromReplay(state, hooks, sessionId) {
|
|
99438
100116
|
setState(state, { isReplaying: true }, hooks);
|
|
99439
100117
|
try {
|
|
99440
100118
|
const replay = await state.client.replayRecords(sessionId);
|
|
99441
100119
|
const projection = projectReplayRecords(replay.records);
|
|
99442
|
-
|
|
100120
|
+
hydrateProjectedEntries(state, projection.entries);
|
|
100121
|
+
state.backgroundAgents = new Set(projection.backgroundAgents);
|
|
100122
|
+
state.footer.setBackgroundAgentCount(state.backgroundAgents.size);
|
|
100123
|
+
state.ui.requestRender();
|
|
99443
100124
|
if (replay.warnings !== void 0 && replay.warnings.length > 0) emitError(state, `Replay completed with ${String(replay.warnings.length)} warning(s).`);
|
|
99444
100125
|
return true;
|
|
99445
100126
|
} catch (error) {
|
|
@@ -99457,7 +100138,8 @@ function projectReplayRecords(records) {
|
|
|
99457
100138
|
assistant: {
|
|
99458
100139
|
thinking: [],
|
|
99459
100140
|
text: []
|
|
99460
|
-
}
|
|
100141
|
+
},
|
|
100142
|
+
backgroundAgents: /* @__PURE__ */ new Set()
|
|
99461
100143
|
};
|
|
99462
100144
|
for (const envelope of records) {
|
|
99463
100145
|
if (envelope.source?.kind === "subagent") {
|
|
@@ -99467,7 +100149,10 @@ function projectReplayRecords(records) {
|
|
|
99467
100149
|
projectMainRecord(state, envelope.record);
|
|
99468
100150
|
}
|
|
99469
100151
|
flushAssistant(state);
|
|
99470
|
-
return {
|
|
100152
|
+
return {
|
|
100153
|
+
entries: state.entries,
|
|
100154
|
+
backgroundAgents: state.backgroundAgents
|
|
100155
|
+
};
|
|
99471
100156
|
}
|
|
99472
100157
|
function projectMainRecord(state, rawRecord) {
|
|
99473
100158
|
const record = asRecord(rawRecord);
|
|
@@ -99509,6 +100194,22 @@ function projectMainRecord(state, rawRecord) {
|
|
|
99509
100194
|
text: []
|
|
99510
100195
|
};
|
|
99511
100196
|
return;
|
|
100197
|
+
case "subagent_spawned": {
|
|
100198
|
+
const data = record.data;
|
|
100199
|
+
if (!isObject(data)) return;
|
|
100200
|
+
if (data["run_in_background"] !== true) return;
|
|
100201
|
+
const agentId = stringValue(data["agent_id"]);
|
|
100202
|
+
if (agentId !== void 0) state.backgroundAgents.add(agentId);
|
|
100203
|
+
return;
|
|
100204
|
+
}
|
|
100205
|
+
case "subagent_completed":
|
|
100206
|
+
case "subagent_failed": {
|
|
100207
|
+
const data = record.data;
|
|
100208
|
+
if (!isObject(data)) return;
|
|
100209
|
+
const agentId = stringValue(data["agent_id"]);
|
|
100210
|
+
if (agentId !== void 0) state.backgroundAgents.delete(agentId);
|
|
100211
|
+
return;
|
|
100212
|
+
}
|
|
99512
100213
|
default: return;
|
|
99513
100214
|
}
|
|
99514
100215
|
}
|
|
@@ -99557,15 +100258,19 @@ function projectSubagentRecord(state, envelope) {
|
|
|
99557
100258
|
function projectToolCall(state, record) {
|
|
99558
100259
|
const tool = toolCallFromRecord(record);
|
|
99559
100260
|
if (tool === void 0) return;
|
|
100261
|
+
const turnId = stringValue(record.turn_id);
|
|
100262
|
+
const step = typeof record.step === "number" ? record.step : void 0;
|
|
99560
100263
|
const toolCall = {
|
|
99561
100264
|
id: tool.id,
|
|
99562
100265
|
name: tool.name,
|
|
99563
100266
|
args: tool.args,
|
|
99564
|
-
description: tool.description
|
|
100267
|
+
description: tool.description,
|
|
100268
|
+
...step !== void 0 ? { step } : {},
|
|
100269
|
+
...turnId !== void 0 ? { turnId } : {}
|
|
99565
100270
|
};
|
|
99566
100271
|
state.toolCalls.set(tool.id, toolCall);
|
|
99567
100272
|
state.entries.push(entry("tool_call", "", "plain", {
|
|
99568
|
-
turnId
|
|
100273
|
+
turnId,
|
|
99569
100274
|
toolCallData: toolCall
|
|
99570
100275
|
}));
|
|
99571
100276
|
}
|
|
@@ -99680,6 +100385,61 @@ function isObject(value) {
|
|
|
99680
100385
|
function stringValue(value) {
|
|
99681
100386
|
return typeof value === "string" ? value : void 0;
|
|
99682
100387
|
}
|
|
100388
|
+
/**
|
|
100389
|
+
* 把 projection 出的扁平 entries 注入 live state。其中相邻、同
|
|
100390
|
+
* `(turnId, step)` 的 ≥2 个 Agent tool_call 合并成一个 AgentGroupComponent,
|
|
100391
|
+
* 与 live 路径行为一致。其他 entry 走原本 `appendTranscriptEntry`。
|
|
100392
|
+
*
|
|
100393
|
+
* 注意:和 `tryAttachAgentToolCall` 不同,这里**不**写 `state.pendingAgentGroup`
|
|
100394
|
+
* —— replay 完成后该字段必须是 null(live 事件接管时从 zero 开始)。
|
|
100395
|
+
*/
|
|
100396
|
+
function hydrateProjectedEntries(state, entries) {
|
|
100397
|
+
let i = 0;
|
|
100398
|
+
while (i < entries.length) {
|
|
100399
|
+
const cur = entries[i];
|
|
100400
|
+
if (cur === void 0) {
|
|
100401
|
+
i += 1;
|
|
100402
|
+
continue;
|
|
100403
|
+
}
|
|
100404
|
+
const tc = cur.toolCallData;
|
|
100405
|
+
if (cur.kind === "tool_call" && tc !== void 0 && tc.name === "Agent" && tc.step !== void 0) {
|
|
100406
|
+
const batch = [cur];
|
|
100407
|
+
let j = i + 1;
|
|
100408
|
+
while (j < entries.length) {
|
|
100409
|
+
const next = entries[j];
|
|
100410
|
+
if (next === void 0) break;
|
|
100411
|
+
const nextTc = next.toolCallData;
|
|
100412
|
+
if (next.kind === "tool_call" && nextTc !== void 0 && nextTc.name === "Agent" && nextTc.step === tc.step && nextTc.turnId === tc.turnId) {
|
|
100413
|
+
batch.push(next);
|
|
100414
|
+
j++;
|
|
100415
|
+
continue;
|
|
100416
|
+
}
|
|
100417
|
+
break;
|
|
100418
|
+
}
|
|
100419
|
+
if (batch.length >= 2) {
|
|
100420
|
+
attachAgentBatchAsGroup(state, batch);
|
|
100421
|
+
i = j;
|
|
100422
|
+
continue;
|
|
100423
|
+
}
|
|
100424
|
+
}
|
|
100425
|
+
appendTranscriptEntry(state, cur);
|
|
100426
|
+
i++;
|
|
100427
|
+
}
|
|
100428
|
+
}
|
|
100429
|
+
function attachAgentBatchAsGroup(state, batch) {
|
|
100430
|
+
const group = new AgentGroupComponent(state.colors, state.ui);
|
|
100431
|
+
state.transcriptContainer.addChild(group);
|
|
100432
|
+
for (const item of batch) {
|
|
100433
|
+
const tc = item.toolCallData;
|
|
100434
|
+
if (tc === void 0) continue;
|
|
100435
|
+
state.transcriptEntries.push(item);
|
|
100436
|
+
const component = new ToolCallComponent(tc, tc.result, state.colors, state.ui, state.markdownTheme);
|
|
100437
|
+
if (state.toolOutputExpanded) component.setExpanded(true);
|
|
100438
|
+
state.pendingToolComponents.set(tc.id, component);
|
|
100439
|
+
group.attach(tc.id, component);
|
|
100440
|
+
}
|
|
100441
|
+
state.ui.requestRender();
|
|
100442
|
+
}
|
|
99683
100443
|
//#endregion
|
|
99684
100444
|
//#region src/tui/utils/proctitle.ts
|
|
99685
100445
|
/**
|
|
@@ -99842,6 +100602,8 @@ function releaseSessionSideEffects(state) {
|
|
|
99842
100602
|
state.currentTurnId = void 0;
|
|
99843
100603
|
state.assistantDraft = "";
|
|
99844
100604
|
state.assistantStreamActive = false;
|
|
100605
|
+
state.streamingComponent = void 0;
|
|
100606
|
+
state.streamingTranscriptEntry = void 0;
|
|
99845
100607
|
state.thinkingDraft = "";
|
|
99846
100608
|
disposeActiveThinkingComponent(state);
|
|
99847
100609
|
}
|
|
@@ -99909,6 +100671,100 @@ var CompactionComponent = class extends Container {
|
|
|
99909
100671
|
}
|
|
99910
100672
|
};
|
|
99911
100673
|
//#endregion
|
|
100674
|
+
//#region src/tui/actions/agent-group-ops.ts
|
|
100675
|
+
/**
|
|
100676
|
+
* Agent 合并组挂载策略 & 打断规则。
|
|
100677
|
+
*
|
|
100678
|
+
* `tryAttachAgentToolCall`:在 `onToolCallStart` 里给 Agent 工具走的拦截
|
|
100679
|
+
* 点。如果当前 step 已有 `solo` 但还没升级成 group,第二个同 step 的
|
|
100680
|
+
* Agent 到达时会就地把 solo 替换成 `AgentGroupComponent` 并把两者塞进去。
|
|
100681
|
+
*
|
|
100682
|
+
* `closePendingAgentGroup`:所有打断点(非 Agent 工具 / step.begin /
|
|
100683
|
+
* turn.end / step.interrupted / session.error / assistant text)调用,
|
|
100684
|
+
* 清空 pending 指针。已挂出去的 group 不被销毁——它在 transcript 里
|
|
100685
|
+
* 作为独立组件继续工作,只是不再有新的 Agent 合并进来。
|
|
100686
|
+
*/
|
|
100687
|
+
/**
|
|
100688
|
+
* 把一个新创建的 Agent ToolCallComponent 挂到合适位置(合并组或单卡)。
|
|
100689
|
+
* 调用方负责创建 `tc` 与登记 `pendingToolComponents`,本函数只决定
|
|
100690
|
+
* 挂载策略并把 tc 放到 transcript 里。
|
|
100691
|
+
*
|
|
100692
|
+
* @returns true 如果走了 group 路径(已经挂好),调用方不应再调
|
|
100693
|
+
* `transcriptContainer.addChild(tc)`;false 表示让调用方按
|
|
100694
|
+
* 原本的单卡路径自行 addChild。
|
|
100695
|
+
*/
|
|
100696
|
+
function tryAttachAgentToolCall(state, toolCall, tc) {
|
|
100697
|
+
if (toolCall.name !== "Agent") {
|
|
100698
|
+
closePendingAgentGroup(state);
|
|
100699
|
+
return false;
|
|
100700
|
+
}
|
|
100701
|
+
const step = toolCall.step ?? state.currentStep;
|
|
100702
|
+
const turnId = toolCall.turnId ?? state.currentTurnId;
|
|
100703
|
+
const pending = state.pendingAgentGroup;
|
|
100704
|
+
if (pending !== null && (pending.step !== step || pending.turnId !== turnId)) state.pendingAgentGroup = null;
|
|
100705
|
+
const cur = state.pendingAgentGroup;
|
|
100706
|
+
if (cur === null) {
|
|
100707
|
+
state.pendingAgentGroup = {
|
|
100708
|
+
step,
|
|
100709
|
+
turnId,
|
|
100710
|
+
solo: tc
|
|
100711
|
+
};
|
|
100712
|
+
state.transcriptContainer.addChild(tc);
|
|
100713
|
+
state.ui.requestRender();
|
|
100714
|
+
return true;
|
|
100715
|
+
}
|
|
100716
|
+
if (cur.group !== void 0) {
|
|
100717
|
+
cur.group.attach(toolCall.id, tc);
|
|
100718
|
+
return true;
|
|
100719
|
+
}
|
|
100720
|
+
const solo = cur.solo;
|
|
100721
|
+
if (solo === void 0) {
|
|
100722
|
+
state.pendingAgentGroup = {
|
|
100723
|
+
step,
|
|
100724
|
+
turnId,
|
|
100725
|
+
solo: tc
|
|
100726
|
+
};
|
|
100727
|
+
state.transcriptContainer.addChild(tc);
|
|
100728
|
+
state.ui.requestRender();
|
|
100729
|
+
return true;
|
|
100730
|
+
}
|
|
100731
|
+
const group = upgradeSoloToGroup$1(state, solo);
|
|
100732
|
+
group.attach(toolCall.id, tc);
|
|
100733
|
+
state.pendingAgentGroup = {
|
|
100734
|
+
step,
|
|
100735
|
+
turnId,
|
|
100736
|
+
group
|
|
100737
|
+
};
|
|
100738
|
+
state.ui.requestRender();
|
|
100739
|
+
return true;
|
|
100740
|
+
}
|
|
100741
|
+
/**
|
|
100742
|
+
* 任意打断信号触发本函数。仅清空 pending 指针——已挂出去的 solo 单卡
|
|
100743
|
+
* 或 group 都保持原状。
|
|
100744
|
+
*/
|
|
100745
|
+
function closePendingAgentGroup(state) {
|
|
100746
|
+
if (state.pendingAgentGroup === null) return;
|
|
100747
|
+
state.pendingAgentGroup = null;
|
|
100748
|
+
}
|
|
100749
|
+
/**
|
|
100750
|
+
* 把已挂在 transcriptContainer 的 solo `ToolCallComponent` 原地替换成新建
|
|
100751
|
+
* 的 `AgentGroupComponent`,并把 solo 塞进 group 里继续做"隐身状态容器"。
|
|
100752
|
+
*
|
|
100753
|
+
* pi-tui 没暴露 insertChildAt / replaceChildAt,但 `Container.children`
|
|
100754
|
+
* 是 public 数组。这里直接 splice,再调一次 `invalidate()` 触发差分渲染。
|
|
100755
|
+
*/
|
|
100756
|
+
function upgradeSoloToGroup$1(state, solo) {
|
|
100757
|
+
const group = new AgentGroupComponent(state.colors, state.ui);
|
|
100758
|
+
const children = state.transcriptContainer.children;
|
|
100759
|
+
const idx = children.indexOf(solo);
|
|
100760
|
+
if (idx >= 0) {
|
|
100761
|
+
children[idx] = group;
|
|
100762
|
+
state.transcriptContainer.invalidate();
|
|
100763
|
+
} else state.transcriptContainer.addChild(group);
|
|
100764
|
+
group.attach(solo.toolCallView.id, solo);
|
|
100765
|
+
return group;
|
|
100766
|
+
}
|
|
100767
|
+
//#endregion
|
|
99912
100768
|
//#region src/tui/actions/plan-ops.ts
|
|
99913
100769
|
async function refreshPlanFromCore(state) {
|
|
99914
100770
|
try {
|
|
@@ -99918,6 +100774,218 @@ async function refreshPlanFromCore(state) {
|
|
|
99918
100774
|
}
|
|
99919
100775
|
}
|
|
99920
100776
|
//#endregion
|
|
100777
|
+
//#region src/tui/components/messages/read-group.ts
|
|
100778
|
+
const THROTTLE_MS = 200;
|
|
100779
|
+
var ReadGroupComponent = class extends Container {
|
|
100780
|
+
entries = [];
|
|
100781
|
+
headerText;
|
|
100782
|
+
bodyContainer;
|
|
100783
|
+
throttleTimer = null;
|
|
100784
|
+
lastFlushPhases = /* @__PURE__ */ new Map();
|
|
100785
|
+
constructor(colors, ui) {
|
|
100786
|
+
super();
|
|
100787
|
+
this.colors = colors;
|
|
100788
|
+
this.ui = ui;
|
|
100789
|
+
this.addChild(new Spacer(1));
|
|
100790
|
+
this.headerText = new Text("", 0, 0);
|
|
100791
|
+
this.addChild(this.headerText);
|
|
100792
|
+
this.bodyContainer = new Container();
|
|
100793
|
+
this.addChild(this.bodyContainer);
|
|
100794
|
+
}
|
|
100795
|
+
size() {
|
|
100796
|
+
return this.entries.length;
|
|
100797
|
+
}
|
|
100798
|
+
/**
|
|
100799
|
+
* 把一个独立 `ToolCallComponent` 借进 group 做隐身状态容器。注册
|
|
100800
|
+
* snapshot 监听 → 状态有变即触发节流刷新。多次 attach 同一个 toolCallId
|
|
100801
|
+
* 是 noop。
|
|
100802
|
+
*/
|
|
100803
|
+
attach(toolCallId, tc) {
|
|
100804
|
+
if (this.entries.some((e) => e.toolCallId === toolCallId)) return;
|
|
100805
|
+
this.entries.push({
|
|
100806
|
+
toolCallId,
|
|
100807
|
+
tc
|
|
100808
|
+
});
|
|
100809
|
+
tc.setSnapshotListener(() => {
|
|
100810
|
+
this.scheduleRender();
|
|
100811
|
+
});
|
|
100812
|
+
this.flushRender();
|
|
100813
|
+
}
|
|
100814
|
+
/**
|
|
100815
|
+
* pending → done/failed 是用户最关心的转变,立刷新;其它情况节流。
|
|
100816
|
+
*/
|
|
100817
|
+
scheduleRender() {
|
|
100818
|
+
if (this.detectPhaseTransition()) {
|
|
100819
|
+
this.flushRender();
|
|
100820
|
+
return;
|
|
100821
|
+
}
|
|
100822
|
+
if (this.throttleTimer !== null) return;
|
|
100823
|
+
this.throttleTimer = setTimeout(() => {
|
|
100824
|
+
this.throttleTimer = null;
|
|
100825
|
+
this.flushRender();
|
|
100826
|
+
}, THROTTLE_MS);
|
|
100827
|
+
}
|
|
100828
|
+
detectPhaseTransition() {
|
|
100829
|
+
for (const e of this.entries) {
|
|
100830
|
+
const phase = e.tc.getReadSnapshot().phase;
|
|
100831
|
+
if (this.lastFlushPhases.get(e.toolCallId) !== phase) return true;
|
|
100832
|
+
}
|
|
100833
|
+
return false;
|
|
100834
|
+
}
|
|
100835
|
+
flushRender() {
|
|
100836
|
+
if (this.throttleTimer !== null) {
|
|
100837
|
+
clearTimeout(this.throttleTimer);
|
|
100838
|
+
this.throttleTimer = null;
|
|
100839
|
+
}
|
|
100840
|
+
const snapshots = this.entries.map((e) => e.tc.getReadSnapshot());
|
|
100841
|
+
let pending = 0;
|
|
100842
|
+
let failed = 0;
|
|
100843
|
+
let totalLines = 0;
|
|
100844
|
+
for (const snap of snapshots) if (snap.phase === "pending") pending += 1;
|
|
100845
|
+
else if (snap.phase === "failed") failed += 1;
|
|
100846
|
+
else totalLines += snap.lines;
|
|
100847
|
+
this.headerText.setText(this.buildHeader(snapshots.length, pending, failed, totalLines));
|
|
100848
|
+
this.bodyContainer.clear();
|
|
100849
|
+
snapshots.forEach((snap, idx) => {
|
|
100850
|
+
const isLast = idx === snapshots.length - 1;
|
|
100851
|
+
this.bodyContainer.addChild(new Text(this.buildBodyLine(snap, isLast), 0, 0));
|
|
100852
|
+
});
|
|
100853
|
+
this.lastFlushPhases.clear();
|
|
100854
|
+
this.entries.forEach((entry, i) => {
|
|
100855
|
+
const snap = snapshots[i];
|
|
100856
|
+
if (snap !== void 0) this.lastFlushPhases.set(entry.toolCallId, snap.phase);
|
|
100857
|
+
});
|
|
100858
|
+
this.invalidate();
|
|
100859
|
+
this.ui?.requestRender();
|
|
100860
|
+
}
|
|
100861
|
+
buildHeader(total, pending, failed, totalLines) {
|
|
100862
|
+
const colors = this.colors;
|
|
100863
|
+
const dim = chalk.dim;
|
|
100864
|
+
if (pending > 0) return `${chalk.hex(colors.roleAssistant)("⏺ ")}${chalk.hex(colors.primary).bold(`Reading ${String(total)} files…`)}`;
|
|
100865
|
+
if (failed === total) return `${chalk.hex(colors.error)("✗ ")}${chalk.hex(colors.error).bold(`Read ${String(total)} files`)}${chalk.hex(colors.error)(" · failed")}`;
|
|
100866
|
+
return `${chalk.hex(colors.success)("⏺ ")}${chalk.hex(colors.primary).bold(`Read ${String(total)} files`)}${dim(` · ${String(totalLines)} ${totalLines === 1 ? "line" : "lines"}`)}${failed > 0 ? chalk.hex(colors.error)(` · ${String(failed)} failed`) : ""}`;
|
|
100867
|
+
}
|
|
100868
|
+
buildBodyLine(snap, isLast) {
|
|
100869
|
+
const colors = this.colors;
|
|
100870
|
+
const dim = chalk.dim;
|
|
100871
|
+
const branch = isLast ? "└─" : "├─";
|
|
100872
|
+
const path = snap.filePath ?? "(unknown)";
|
|
100873
|
+
const pathPart = chalk.hex(colors.text)(path);
|
|
100874
|
+
let tail;
|
|
100875
|
+
if (snap.phase === "pending") tail = dim(" · reading…");
|
|
100876
|
+
else if (snap.phase === "failed") tail = chalk.hex(colors.error)(" · failed");
|
|
100877
|
+
else tail = dim(` · ${String(snap.lines)} ${snap.lines === 1 ? "line" : "lines"}`);
|
|
100878
|
+
return ` ${branch} ${pathPart}${tail}`;
|
|
100879
|
+
}
|
|
100880
|
+
/** 给测试 / 开发期使用,立刻冲掉节流计时器并刷新。生产代码不需要主动调。 */
|
|
100881
|
+
flushNow() {
|
|
100882
|
+
this.flushRender();
|
|
100883
|
+
}
|
|
100884
|
+
/** 释放节流计时器,避免组件被销毁后还触发刷新。 */
|
|
100885
|
+
dispose() {
|
|
100886
|
+
if (this.throttleTimer !== null) {
|
|
100887
|
+
clearTimeout(this.throttleTimer);
|
|
100888
|
+
this.throttleTimer = null;
|
|
100889
|
+
}
|
|
100890
|
+
for (const e of this.entries) e.tc.setSnapshotListener(void 0);
|
|
100891
|
+
}
|
|
100892
|
+
};
|
|
100893
|
+
//#endregion
|
|
100894
|
+
//#region src/tui/actions/read-group-ops.ts
|
|
100895
|
+
/**
|
|
100896
|
+
* Read 合并组挂载策略 & 打断规则。
|
|
100897
|
+
*
|
|
100898
|
+
* 与 `agent-group-ops.ts` 完全同构,只是 batch key 从「Agent 工具」换成
|
|
100899
|
+
* 「Read 工具」。
|
|
100900
|
+
*
|
|
100901
|
+
* `tryAttachReadToolCall`:在 `onToolCallStart` 里给 Read 工具走的拦截
|
|
100902
|
+
* 点。如果当前 step 已有 `solo` 但还没升级成 group,第二个同 step 的
|
|
100903
|
+
* Read 到达时会就地把 solo 替换成 `ReadGroupComponent` 并把两者塞进去。
|
|
100904
|
+
*
|
|
100905
|
+
* `closePendingReadGroup`:所有打断点(非 Read 工具 / step.begin /
|
|
100906
|
+
* turn.end / step.interrupted / session.error / assistant text /
|
|
100907
|
+
* thinking)调用,清空 pending 指针。已挂出去的 group 不被销毁——它在
|
|
100908
|
+
* transcript 里作为独立组件继续工作,只是不再有新的 Read 合并进来。
|
|
100909
|
+
*/
|
|
100910
|
+
/**
|
|
100911
|
+
* 把一个新创建的 Read ToolCallComponent 挂到合适位置(合并组或单卡)。
|
|
100912
|
+
* 调用方负责创建 `tc` 与登记 `pendingToolComponents`,本函数只决定
|
|
100913
|
+
* 挂载策略并把 tc 放到 transcript 里。
|
|
100914
|
+
*
|
|
100915
|
+
* @returns true 如果走了 group 路径(已经挂好),调用方不应再调
|
|
100916
|
+
* `transcriptContainer.addChild(tc)`;false 表示让调用方按
|
|
100917
|
+
* 原本的单卡路径自行 addChild。
|
|
100918
|
+
*/
|
|
100919
|
+
function tryAttachReadToolCall(state, toolCall, tc) {
|
|
100920
|
+
if (toolCall.name !== "Read") {
|
|
100921
|
+
closePendingReadGroup(state);
|
|
100922
|
+
return false;
|
|
100923
|
+
}
|
|
100924
|
+
const step = toolCall.step ?? state.currentStep;
|
|
100925
|
+
const turnId = toolCall.turnId ?? state.currentTurnId;
|
|
100926
|
+
const pending = state.pendingReadGroup;
|
|
100927
|
+
if (pending !== null && (pending.step !== step || pending.turnId !== turnId)) state.pendingReadGroup = null;
|
|
100928
|
+
const cur = state.pendingReadGroup;
|
|
100929
|
+
if (cur === null) {
|
|
100930
|
+
state.pendingReadGroup = {
|
|
100931
|
+
step,
|
|
100932
|
+
turnId,
|
|
100933
|
+
solo: tc
|
|
100934
|
+
};
|
|
100935
|
+
state.transcriptContainer.addChild(tc);
|
|
100936
|
+
state.ui.requestRender();
|
|
100937
|
+
return true;
|
|
100938
|
+
}
|
|
100939
|
+
if (cur.group !== void 0) {
|
|
100940
|
+
cur.group.attach(toolCall.id, tc);
|
|
100941
|
+
return true;
|
|
100942
|
+
}
|
|
100943
|
+
const solo = cur.solo;
|
|
100944
|
+
if (solo === void 0) {
|
|
100945
|
+
state.pendingReadGroup = {
|
|
100946
|
+
step,
|
|
100947
|
+
turnId,
|
|
100948
|
+
solo: tc
|
|
100949
|
+
};
|
|
100950
|
+
state.transcriptContainer.addChild(tc);
|
|
100951
|
+
state.ui.requestRender();
|
|
100952
|
+
return true;
|
|
100953
|
+
}
|
|
100954
|
+
const group = upgradeSoloToGroup(state, solo);
|
|
100955
|
+
group.attach(toolCall.id, tc);
|
|
100956
|
+
state.pendingReadGroup = {
|
|
100957
|
+
step,
|
|
100958
|
+
turnId,
|
|
100959
|
+
group
|
|
100960
|
+
};
|
|
100961
|
+
state.ui.requestRender();
|
|
100962
|
+
return true;
|
|
100963
|
+
}
|
|
100964
|
+
/**
|
|
100965
|
+
* 任意打断信号触发本函数。仅清空 pending 指针——已挂出去的 solo 单卡
|
|
100966
|
+
* 或 group 都保持原状。
|
|
100967
|
+
*/
|
|
100968
|
+
function closePendingReadGroup(state) {
|
|
100969
|
+
if (state.pendingReadGroup === null) return;
|
|
100970
|
+
state.pendingReadGroup = null;
|
|
100971
|
+
}
|
|
100972
|
+
/**
|
|
100973
|
+
* 把已挂在 transcriptContainer 的 solo `ToolCallComponent` 原地替换成新建
|
|
100974
|
+
* 的 `ReadGroupComponent`,并把 solo 塞进 group 里继续做隐身状态容器。
|
|
100975
|
+
* 与 agent-group-ops 同构:直接 splice transcriptContainer.children。
|
|
100976
|
+
*/
|
|
100977
|
+
function upgradeSoloToGroup(state, solo) {
|
|
100978
|
+
const group = new ReadGroupComponent(state.colors, state.ui);
|
|
100979
|
+
const children = state.transcriptContainer.children;
|
|
100980
|
+
const idx = children.indexOf(solo);
|
|
100981
|
+
if (idx >= 0) {
|
|
100982
|
+
children[idx] = group;
|
|
100983
|
+
state.transcriptContainer.invalidate();
|
|
100984
|
+
} else state.transcriptContainer.addChild(group);
|
|
100985
|
+
group.attach(solo.toolCallView.id, solo);
|
|
100986
|
+
return group;
|
|
100987
|
+
}
|
|
100988
|
+
//#endregion
|
|
99921
100989
|
//#region src/tui/actions/stream-ops.ts
|
|
99922
100990
|
/**
|
|
99923
100991
|
* Streaming / tool-call / compaction render hooks.
|
|
@@ -99927,11 +100995,23 @@ async function refreshPlanFromCore(state) {
|
|
|
99927
100995
|
* on `state` is touched — state-ops.ts owns AppState / LivePane.
|
|
99928
100996
|
*/
|
|
99929
100997
|
function onStreamingTextStart(state) {
|
|
100998
|
+
closePendingAgentGroup(state);
|
|
100999
|
+
closePendingReadGroup(state);
|
|
101000
|
+
const entry = {
|
|
101001
|
+
id: nextTranscriptId(),
|
|
101002
|
+
kind: "assistant",
|
|
101003
|
+
turnId: state.currentTurnId,
|
|
101004
|
+
renderMode: "markdown",
|
|
101005
|
+
content: ""
|
|
101006
|
+
};
|
|
99930
101007
|
state.streamingComponent = new AssistantMessageComponent(state.markdownTheme, state.colors);
|
|
101008
|
+
state.streamingTranscriptEntry = entry;
|
|
101009
|
+
state.transcriptEntries.push(entry);
|
|
99931
101010
|
state.transcriptContainer.addChild(state.streamingComponent);
|
|
99932
101011
|
state.ui.requestRender();
|
|
99933
101012
|
}
|
|
99934
101013
|
function onStreamingTextUpdate(state, fullText) {
|
|
101014
|
+
if (state.streamingTranscriptEntry !== void 0) state.streamingTranscriptEntry.content = fullText;
|
|
99935
101015
|
if (state.streamingComponent) {
|
|
99936
101016
|
state.streamingComponent.updateContent(fullText);
|
|
99937
101017
|
state.ui.requestRender();
|
|
@@ -99939,9 +101019,12 @@ function onStreamingTextUpdate(state, fullText) {
|
|
|
99939
101019
|
}
|
|
99940
101020
|
function onStreamingTextEnd(state) {
|
|
99941
101021
|
state.streamingComponent = void 0;
|
|
101022
|
+
state.streamingTranscriptEntry = void 0;
|
|
99942
101023
|
}
|
|
99943
101024
|
function onThinkingUpdate(state, fullText) {
|
|
99944
101025
|
if (state.activeThinkingComponent === void 0) {
|
|
101026
|
+
closePendingAgentGroup(state);
|
|
101027
|
+
closePendingReadGroup(state);
|
|
99945
101028
|
state.activeThinkingComponent = new ThinkingComponent(fullText, state.colors, true, "live", state.ui);
|
|
99946
101029
|
state.transcriptContainer.addChild(state.activeThinkingComponent);
|
|
99947
101030
|
} else state.activeThinkingComponent.setText(fullText);
|
|
@@ -99958,8 +101041,14 @@ function onToolCallStart(state, toolCall) {
|
|
|
99958
101041
|
const tc = new ToolCallComponent(toolCall, void 0, state.colors, state.ui, state.markdownTheme);
|
|
99959
101042
|
if (state.toolOutputExpanded) tc.setExpanded(true);
|
|
99960
101043
|
state.pendingToolComponents.set(toolCall.id, tc);
|
|
99961
|
-
|
|
99962
|
-
|
|
101044
|
+
if (toolCall.name !== "Agent") closePendingAgentGroup(state);
|
|
101045
|
+
if (toolCall.name !== "Read") closePendingReadGroup(state);
|
|
101046
|
+
let handled = tryAttachAgentToolCall(state, toolCall, tc);
|
|
101047
|
+
if (!handled) handled = tryAttachReadToolCall(state, toolCall, tc);
|
|
101048
|
+
if (!handled) {
|
|
101049
|
+
state.transcriptContainer.addChild(tc);
|
|
101050
|
+
state.ui.requestRender();
|
|
101051
|
+
}
|
|
99963
101052
|
if (toolCall.name === "ExitPlanMode" && typeof toolCall.args["plan"] !== "string") (async () => {
|
|
99964
101053
|
const snapshot = await refreshPlanFromCore(state);
|
|
99965
101054
|
tc.setPlanInfo(snapshot);
|
|
@@ -100342,6 +101431,9 @@ async function dispatchSlashCommand(input, state, buildCtx, addEntry) {
|
|
|
100342
101431
|
//#region src/tui/handlers/turn-lifecycle.ts
|
|
100343
101432
|
function handleTurnBegin(ectx, _data) {
|
|
100344
101433
|
ectx.state.streamingToolCallArguments.clear();
|
|
101434
|
+
ectx.state.currentStep = 0;
|
|
101435
|
+
closePendingAgentGroup(ectx.state);
|
|
101436
|
+
closePendingReadGroup(ectx.state);
|
|
100345
101437
|
ectx.patchLivePane({
|
|
100346
101438
|
mode: "waiting",
|
|
100347
101439
|
thinkingText: "",
|
|
@@ -100360,9 +101452,15 @@ function handleTurnEnd(ectx, _data, sendQueued) {
|
|
|
100360
101452
|
const todos = ectx.state.todoPanel.getTodos();
|
|
100361
101453
|
if (todos.length > 0 && todos.every((t) => t.status === "done")) ectx.setTodoList([]);
|
|
100362
101454
|
ectx.state.streamingToolCallArguments.clear();
|
|
101455
|
+
closePendingAgentGroup(ectx.state);
|
|
101456
|
+
closePendingReadGroup(ectx.state);
|
|
100363
101457
|
finalizeTurn(ectx, sendQueued);
|
|
100364
101458
|
}
|
|
100365
|
-
function handleStepBegin(ectx) {
|
|
101459
|
+
function handleStepBegin(ectx, data) {
|
|
101460
|
+
ectx.state.currentStep = data.step;
|
|
101461
|
+
closePendingAgentGroup(ectx.state);
|
|
101462
|
+
closePendingReadGroup(ectx.state);
|
|
101463
|
+
flushTurnBuffers(ectx, "waiting");
|
|
100366
101464
|
ectx.patchLivePane({
|
|
100367
101465
|
mode: "waiting",
|
|
100368
101466
|
pendingToolCall: null,
|
|
@@ -100375,6 +101473,8 @@ function handleStepBegin(ectx) {
|
|
|
100375
101473
|
});
|
|
100376
101474
|
}
|
|
100377
101475
|
function handleStepInterrupted(ectx, data) {
|
|
101476
|
+
closePendingAgentGroup(ectx.state);
|
|
101477
|
+
closePendingReadGroup(ectx.state);
|
|
100378
101478
|
flushTurnBuffers(ectx, "idle");
|
|
100379
101479
|
const reason = data.reason;
|
|
100380
101480
|
if (reason === "error") return;
|
|
@@ -100466,7 +101566,9 @@ function handleToolCall(ectx, data) {
|
|
|
100466
101566
|
id: data.id,
|
|
100467
101567
|
name: data.name,
|
|
100468
101568
|
args: data.args,
|
|
100469
|
-
description: data.description
|
|
101569
|
+
description: data.description,
|
|
101570
|
+
step: ectx.state.currentStep,
|
|
101571
|
+
turnId: ectx.state.currentTurnId
|
|
100470
101572
|
};
|
|
100471
101573
|
const existing = ectx.state.activeToolCalls.get(data.id);
|
|
100472
101574
|
ectx.state.activeToolCalls.set(data.id, toolCall);
|
|
@@ -100475,7 +101577,7 @@ function handleToolCall(ectx, data) {
|
|
|
100475
101577
|
if (existingComponent !== void 0) existingComponent.updateToolCall(toolCall);
|
|
100476
101578
|
else if (existing === void 0) {
|
|
100477
101579
|
flushTurnBuffers(ectx, "tool");
|
|
100478
|
-
ectx.onToolCallStart(toolCall);
|
|
101580
|
+
if (data.name !== "Agent") ectx.onToolCallStart(toolCall);
|
|
100479
101581
|
}
|
|
100480
101582
|
ectx.patchLivePane({
|
|
100481
101583
|
mode: "tool",
|
|
@@ -100498,13 +101600,15 @@ function handleToolCallDelta(ectx, data) {
|
|
|
100498
101600
|
id,
|
|
100499
101601
|
name,
|
|
100500
101602
|
args: parseStreamingArgs(argumentsText),
|
|
100501
|
-
streamingArguments: argumentsText
|
|
101603
|
+
streamingArguments: argumentsText,
|
|
101604
|
+
step: ectx.state.currentStep,
|
|
101605
|
+
turnId: ectx.state.currentTurnId
|
|
100502
101606
|
};
|
|
100503
101607
|
ectx.state.activeToolCalls.set(id, toolCall);
|
|
100504
|
-
if (ectx.state.thinkingDraft.length > 0) flushTurnBuffers(ectx, "tool");
|
|
101608
|
+
if (ectx.state.thinkingDraft.length > 0 || ectx.state.assistantStreamActive) flushTurnBuffers(ectx, "tool");
|
|
100505
101609
|
const existingComponent = ectx.state.pendingToolComponents.get(id);
|
|
100506
101610
|
if (existingComponent !== void 0) existingComponent.updateToolCall(toolCall);
|
|
100507
|
-
else ectx.onToolCallStart(toolCall);
|
|
101611
|
+
else if (name !== "Agent") ectx.onToolCallStart(toolCall);
|
|
100508
101612
|
ectx.patchLivePane({
|
|
100509
101613
|
mode: "tool",
|
|
100510
101614
|
pendingToolCall: toolCall,
|
|
@@ -100575,6 +101679,8 @@ function handleSessionMetaChanged(ectx, data) {
|
|
|
100575
101679
|
for (const fn of ectx.state.sessionMetaChangedListeners) fn(data);
|
|
100576
101680
|
}
|
|
100577
101681
|
function handleSessionError(ectx, data) {
|
|
101682
|
+
closePendingAgentGroup(ectx.state);
|
|
101683
|
+
closePendingReadGroup(ectx.state);
|
|
100578
101684
|
flushTurnBuffers(ectx, "idle");
|
|
100579
101685
|
const detail = data.error_type !== void 0 ? ` (${data.error_type})` : "";
|
|
100580
101686
|
ectx.addTranscriptEntry(makeEntry(ectx, "status", `Error${detail}: ${data.error}`, "plain", { color: ectx.colors.error }));
|
|
@@ -100643,7 +101749,7 @@ function dispatchEvent(event, ectx, sendQueued) {
|
|
|
100643
101749
|
handleTurnEnd(ectx, event.data, sendQueued);
|
|
100644
101750
|
break;
|
|
100645
101751
|
case "step.begin":
|
|
100646
|
-
handleStepBegin(ectx);
|
|
101752
|
+
handleStepBegin(ectx, event.data);
|
|
100647
101753
|
break;
|
|
100648
101754
|
case "step.interrupted":
|
|
100649
101755
|
handleStepInterrupted(ectx, event.data);
|