agent-accelerator 0.5.1 → 0.5.3
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/README.md +98 -8
- package/package.json +1 -1
- package/src/agent/agent.ts +283 -15
- package/src/agent/context.ts +7 -2
- package/src/agent/loop.ts +137 -12
- package/src/index.ts +4 -1
- package/src/models/catalog-cache.ts +3 -10
- package/src/providers/google.ts +378 -120
- package/src/providers/openai-compat.ts +2 -0
- package/src/providers/openai.ts +2 -0
- package/src/providers/openrouter.ts +2 -0
- package/src/providers.ts +16 -0
- package/src/scheduler/scheduler.ts +2 -0
- package/src/scheduler/store.ts +34 -21
- package/src/session/journal.ts +201 -0
- package/src/session/store.ts +181 -82
- package/src/session/tool-log.ts +62 -0
- package/src/telemetry-file.ts +4 -1
- package/src/tools/executor.ts +72 -15
- package/src/tools/tool.ts +0 -6
- package/src/types/agent.ts +26 -0
- package/src/types/message.ts +19 -0
- package/src/types/model.ts +5 -1
- package/src/types/response.ts +7 -0
- package/src/types/tool.ts +18 -6
- package/src/utils/durable.ts +218 -0
- package/src/utils/headers.ts +1 -1
- package/src/utils/retry.ts +17 -2
package/src/agent/loop.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
shouldBlockRepeat,
|
|
19
19
|
toolCallFingerprint,
|
|
20
20
|
traceToolEvent,
|
|
21
|
+
type ExecuteToolsOptions,
|
|
21
22
|
} from "../tools/executor.ts";
|
|
22
23
|
import { getModelFromCatalog, ensureModelCatalogFresh, validateModelThinking } from "../models/catalog.ts";
|
|
23
24
|
import { enforceFileAccess } from "../utils/safe-io.ts";
|
|
@@ -70,6 +71,15 @@ export interface AgentLoopConfig {
|
|
|
70
71
|
enforceBudget?: (usage?: TokenUsage) => void;
|
|
71
72
|
/** Called after every turn for durable persistence checkpoints (never fails a turn). */
|
|
72
73
|
onProgress?: () => void;
|
|
74
|
+
/** Called the moment each tool call finishes, before the rest of its batch (the crash-proof record of finished work). */
|
|
75
|
+
onToolResult?: (result: ToolResultRecord) => void;
|
|
76
|
+
/** Opens the log file of one tool call (see `ToolLogs`); unset when the Agent does not persist to a folder. */
|
|
77
|
+
openToolLog?: ExecuteToolsOptions["openToolLog"];
|
|
78
|
+
/**
|
|
79
|
+
* Continue a run that a crash cut off instead of starting from a new prompt: tool calls the last assistant turn made
|
|
80
|
+
* without a stored result are finished first (a result in `stored` is reused, anything else runs now), then the loop goes on.
|
|
81
|
+
*/
|
|
82
|
+
resume?: { stored: Map<string, ToolResultRecord> };
|
|
73
83
|
/**
|
|
74
84
|
* Shared steer inbox for mid-session redirection. Entries are drained
|
|
75
85
|
* between turns (after the current provider/tool work finishes) and
|
|
@@ -438,7 +448,7 @@ export function computeCostFromPricing(usage: TokenUsage, spec?: ModelSpec): Tok
|
|
|
438
448
|
// This function never mutates `source` (Q-35): the priced cost is cloned
|
|
439
449
|
// into a local before folding into the target.
|
|
440
450
|
// ---------------------------------------------------------------------------
|
|
441
|
-
function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: ModelSpec): void {
|
|
451
|
+
export function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: ModelSpec): void {
|
|
442
452
|
target.inputTokens += source.inputTokens || 0;
|
|
443
453
|
target.outputTokens += source.outputTokens || 0;
|
|
444
454
|
target.totalTokens += source.totalTokens || 0;
|
|
@@ -506,6 +516,8 @@ function sanitizeToolResult(r: ToolResultRecord): { sanitized: ToolResultRecord;
|
|
|
506
516
|
async function executeToolCallsWithRepeatGuard(options: {
|
|
507
517
|
tools: Record<string, ToolDefinition>;
|
|
508
518
|
toolCalls: ToolCallRecord[];
|
|
519
|
+
onResult?: (result: ToolResultRecord) => void;
|
|
520
|
+
openToolLog?: ExecuteToolsOptions["openToolLog"];
|
|
509
521
|
previousFingerprints: Set<string>;
|
|
510
522
|
repeatCounts: Map<string, number>;
|
|
511
523
|
agentName?: string;
|
|
@@ -546,6 +558,8 @@ async function executeToolCallsWithRepeatGuard(options: {
|
|
|
546
558
|
signal: options.signal,
|
|
547
559
|
sessionId: options.sessionId,
|
|
548
560
|
onSubagentEvent: options.onSubagentEvent,
|
|
561
|
+
onResult: options.onResult,
|
|
562
|
+
openToolLog: options.openToolLog,
|
|
549
563
|
})
|
|
550
564
|
: [];
|
|
551
565
|
const byId = new Map<string, ToolResultRecord[]>();
|
|
@@ -572,6 +586,73 @@ async function executeToolCallsWithRepeatGuard(options: {
|
|
|
572
586
|
};
|
|
573
587
|
}
|
|
574
588
|
|
|
589
|
+
/** Tool calls of the LAST assistant message that have no result in the conversation yet (a crash or interrupt cut them off). */
|
|
590
|
+
export function unfinishedToolCalls(messages: AgentContext["messages"]): ToolCallRecord[] {
|
|
591
|
+
let lastAssistant = -1;
|
|
592
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
593
|
+
if (messages[i]!.role === "assistant") {
|
|
594
|
+
lastAssistant = i;
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const calls = lastAssistant < 0 ? [] : messages[lastAssistant]!.content;
|
|
599
|
+
if (!Array.isArray(calls)) return [];
|
|
600
|
+
const answered = new Set<string>();
|
|
601
|
+
for (const m of messages.slice(lastAssistant + 1)) {
|
|
602
|
+
if (m.role !== "tool" || !Array.isArray(m.content)) continue;
|
|
603
|
+
for (const p of m.content) if (p.type === "tool_result") answered.add(p.id);
|
|
604
|
+
}
|
|
605
|
+
const open: ToolCallRecord[] = [];
|
|
606
|
+
for (const p of calls) {
|
|
607
|
+
if (p.type === "tool_call" && !answered.has(p.id)) {
|
|
608
|
+
open.push({ id: p.id, name: p.name, arguments: p.arguments, ...(p.rawArguments ? { rawArguments: p.rawArguments } : {}), ...(p.callId ? { callId: p.callId } : {}) });
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return open;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/** True when the conversation ends with tool results of a batch in which a tool called `ctx.stop` (the run was meant to end there). */
|
|
615
|
+
export function endsWithStop(messages: AgentContext["messages"]): boolean {
|
|
616
|
+
for (let i = messages.length - 1; i >= 0 && messages[i]!.role === "tool"; i--) {
|
|
617
|
+
const content = messages[i]!.content;
|
|
618
|
+
if (Array.isArray(content) && content.some((p) => p.type === "tool_result" && p.stopped === true)) return true;
|
|
619
|
+
}
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Finishes the tool calls a crash left open (stored result reused, anything else executed now) and records the results in the conversation; in: a resumed run's config, out: tool results in context + what the response should report.
|
|
624
|
+
async function finishInterruptedTools(
|
|
625
|
+
config: AgentLoopConfig
|
|
626
|
+
): Promise<{ calls: ToolCallRecord[]; results: ToolResultRecord[]; subagents: SubAgentExecutionMetadata[] }> {
|
|
627
|
+
const open = unfinishedToolCalls(config.context.messages);
|
|
628
|
+
const none = { calls: [], results: [], subagents: [] };
|
|
629
|
+
if (open.length === 0 || !config.resume) return none;
|
|
630
|
+
const stored = config.resume.stored;
|
|
631
|
+
const toRun = open.filter((c) => !stored.has(c.id));
|
|
632
|
+
const executed = toRun.length
|
|
633
|
+
? await executeToolCalls({
|
|
634
|
+
tools: config.tools,
|
|
635
|
+
toolCalls: toRun,
|
|
636
|
+
agentName: config.agentName,
|
|
637
|
+
parallel: true,
|
|
638
|
+
signal: config.runOptions?.signal,
|
|
639
|
+
sessionId: config.runOptions?.sessionId || config.options?.sessionId,
|
|
640
|
+
onResult: config.onToolResult,
|
|
641
|
+
openToolLog: config.openToolLog,
|
|
642
|
+
})
|
|
643
|
+
: [];
|
|
644
|
+
const ran = new Map(executed.map((r) => [r.id, r]));
|
|
645
|
+
const subagents: SubAgentExecutionMetadata[] = [];
|
|
646
|
+
const results = open.map((call) => {
|
|
647
|
+
const { sanitized, metas } = sanitizeToolResult(ran.get(call.id) ?? stored.get(call.id)!);
|
|
648
|
+
subagents.push(...metas);
|
|
649
|
+
return sanitized;
|
|
650
|
+
});
|
|
651
|
+
config.context.addToolResults(results);
|
|
652
|
+
emitProgress(config);
|
|
653
|
+
return { calls: open, results, subagents };
|
|
654
|
+
}
|
|
655
|
+
|
|
575
656
|
/**
|
|
576
657
|
* Runs a single non-streaming agent turn or multi-turn loop
|
|
577
658
|
*/
|
|
@@ -614,13 +695,28 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
614
695
|
let finalResult: any = null;
|
|
615
696
|
let turns = 0;
|
|
616
697
|
let toolsExecuted = false;
|
|
698
|
+
let stoppedByTool = false;
|
|
617
699
|
let previousToolCallFingerprints = new Set<string>();
|
|
618
700
|
const repeatCounts = new Map<string, number>();
|
|
619
701
|
|
|
620
702
|
try {
|
|
621
703
|
const standardTools = toStandardToolDeclarations(tools);
|
|
622
704
|
|
|
623
|
-
|
|
705
|
+
if (config.resume) {
|
|
706
|
+
const finished = await finishInterruptedTools(config);
|
|
707
|
+
stoppedByTool = finished.results.some((r) => r.stopped) || endsWithStop(context.messages);
|
|
708
|
+
if (finished.calls.length > 0) {
|
|
709
|
+
toolsExecuted = true;
|
|
710
|
+
allToolCalls.push(...finished.calls);
|
|
711
|
+
allToolResults.push(...finished.results);
|
|
712
|
+
for (const meta of finished.subagents) {
|
|
713
|
+
allSubagents.push(meta);
|
|
714
|
+
accumulateUsage(accumulatedUsage, meta.usage, getModelFromCatalog(meta.provider, meta.model));
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
while (!stoppedByTool && turns < maxTurns) {
|
|
624
720
|
// Q-21: fail fast on abort (throws AbortError with partial).
|
|
625
721
|
throwIfAborted(runOptions?.signal, { usage: accumulatedUsage, turns, text: finalResult?.text });
|
|
626
722
|
checkDeadline(startTime, limits.deadlineMs);
|
|
@@ -666,6 +762,8 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
666
762
|
if (gTier === "paid" || gTier === "free") providerOptions.googleTier = gTier;
|
|
667
763
|
} catch {}
|
|
668
764
|
|
|
765
|
+
// A long-running call (e.g. Google long decode) times each HTTP request itself: no whole-call race.
|
|
766
|
+
const longRunning = provider.isLongRunning?.(modelId, providerOptions) === true;
|
|
669
767
|
const genResult = await generateWithTimeout(
|
|
670
768
|
() => provider.generate(
|
|
671
769
|
modelId,
|
|
@@ -675,7 +773,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
675
773
|
},
|
|
676
774
|
providerOptions
|
|
677
775
|
),
|
|
678
|
-
limits.requestTimeoutMs
|
|
776
|
+
longRunning ? Infinity : limits.requestTimeoutMs
|
|
679
777
|
);
|
|
680
778
|
|
|
681
779
|
finalResult = genResult;
|
|
@@ -710,7 +808,8 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
710
808
|
genResult.text,
|
|
711
809
|
genResult.toolCalls,
|
|
712
810
|
genResult.thinking,
|
|
713
|
-
genResult.thoughtSignature
|
|
811
|
+
genResult.thoughtSignature,
|
|
812
|
+
genResult.providerSteps
|
|
714
813
|
);
|
|
715
814
|
|
|
716
815
|
// Thinking-trace telemetry (observability-only, full text, no limit):
|
|
@@ -755,6 +854,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
755
854
|
// Execute tool calls — always parallel (model-driven, bloatfree DX7)
|
|
756
855
|
// Q-21: check abort before tools (throws AbortError with partial).
|
|
757
856
|
throwIfAborted(runOptions?.signal, { usage: accumulatedUsage, turns, text: genResult.text });
|
|
857
|
+
emitProgress(config); // the calls are on record BEFORE they run, so a crash can finish them instead of forgetting them
|
|
758
858
|
allToolCalls.push(...genResult.toolCalls);
|
|
759
859
|
// Q-22: per-run tool-call budget.
|
|
760
860
|
if (allToolCalls.length > limits.maxToolCallsPerRun) {
|
|
@@ -771,6 +871,8 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
771
871
|
agentName,
|
|
772
872
|
signal: runOptions?.signal,
|
|
773
873
|
sessionId: runOptions?.sessionId || options?.sessionId,
|
|
874
|
+
onResult: config.onToolResult,
|
|
875
|
+
openToolLog: config.openToolLog,
|
|
774
876
|
});
|
|
775
877
|
const results = guarded.results;
|
|
776
878
|
previousToolCallFingerprints = guarded.fingerprints;
|
|
@@ -808,6 +910,12 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
808
910
|
toolCalls: genResult.toolCalls,
|
|
809
911
|
toolResults: sanitizedResults,
|
|
810
912
|
});
|
|
913
|
+
// A tool ended the run (`ctx.stop`): the whole batch has finished, so stop here. Waiting steers become follow-ups.
|
|
914
|
+
if (sanitizedResults.some((r) => r.stopped)) {
|
|
915
|
+
stoppedByTool = true;
|
|
916
|
+
emitProgress(config);
|
|
917
|
+
break;
|
|
918
|
+
}
|
|
811
919
|
// Steer drain AFTER tool results so call/result pairs stay adjacent.
|
|
812
920
|
// The next loop iteration sends the injected user message to the model
|
|
813
921
|
// within the same run (same usage/turn budget).
|
|
@@ -826,7 +934,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
826
934
|
// results above were never sent back, so the run is truncated — surface
|
|
827
935
|
// it via finishReason + warning instead of silently returning.
|
|
828
936
|
const truncatedByMaxTurns =
|
|
829
|
-
turns >= maxTurns && !!finalResult?.toolCalls && finalResult.toolCalls.length > 0;
|
|
937
|
+
!stoppedByTool && turns >= maxTurns && !!finalResult?.toolCalls && finalResult.toolCalls.length > 0;
|
|
830
938
|
if (truncatedByMaxTurns) {
|
|
831
939
|
warnMaxTurnsHit(
|
|
832
940
|
agentName,
|
|
@@ -838,7 +946,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
838
946
|
// Structured outputs: validate final text only (tool turns skip this).
|
|
839
947
|
// Strict-fail throws ValidationError with partial attached via catch below.
|
|
840
948
|
let parsedOutput: unknown | undefined;
|
|
841
|
-
if (loopOutput.raw !== undefined && !truncatedByMaxTurns) {
|
|
949
|
+
if (loopOutput.raw !== undefined && !truncatedByMaxTurns && !stoppedByTool) {
|
|
842
950
|
parsedOutput = parseFinalStructuredOutput(finalResult?.text || "", loopOutput.raw, loopOutput.spec);
|
|
843
951
|
}
|
|
844
952
|
|
|
@@ -855,7 +963,9 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
855
963
|
model: modelId,
|
|
856
964
|
provider: provider.id,
|
|
857
965
|
// Q-04: canonical finish reason + raw vendor passthrough.
|
|
858
|
-
...
|
|
966
|
+
...(stoppedByTool
|
|
967
|
+
? { finishReason: "stopped" as const, ...(finalResult?.finishReason ? { rawFinishReason: finalResult.finishReason } : {}) }
|
|
968
|
+
: canonicalFinishReason(finalResult?.finishReason, truncatedByMaxTurns)),
|
|
859
969
|
durationMs: Date.now() - startTime,
|
|
860
970
|
raw: applyCaptureRaw(finalResult?.raw, config.captureRaw) as ProviderRawData,
|
|
861
971
|
turns,
|
|
@@ -1013,6 +1123,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1013
1123
|
turns = 0;
|
|
1014
1124
|
let previousToolCallFingerprints = new Set<string>();
|
|
1015
1125
|
const repeatCounts = new Map<string, number>();
|
|
1126
|
+
let stoppedByTool = false;
|
|
1016
1127
|
|
|
1017
1128
|
while (turns < maxTurns) {
|
|
1018
1129
|
throwIfCancelled({ usage: accumulatedUsage, turns, text: lastResponse?.text });
|
|
@@ -1053,6 +1164,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1053
1164
|
if (gTier === "paid" || gTier === "free") providerOptions.googleTier = gTier;
|
|
1054
1165
|
} catch {}
|
|
1055
1166
|
|
|
1167
|
+
const longRunning = provider.isLongRunning?.(modelId, providerOptions) === true;
|
|
1056
1168
|
const innerStream = provider.stream(
|
|
1057
1169
|
modelId,
|
|
1058
1170
|
{
|
|
@@ -1071,7 +1183,8 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1071
1183
|
let first = true;
|
|
1072
1184
|
let doneIter = false;
|
|
1073
1185
|
while (!doneIter) {
|
|
1074
|
-
|
|
1186
|
+
// Long-running streams pause for minutes between chunks; the adapter guards stalls and reconnects.
|
|
1187
|
+
const waitMs = longRunning ? Infinity : first ? streamLimits.firstByteTimeoutMs : streamLimits.streamIdleTimeoutMs;
|
|
1075
1188
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1076
1189
|
const timeoutP =
|
|
1077
1190
|
Number.isFinite(waitMs) && waitMs !== Infinity && waitMs > 0
|
|
@@ -1147,7 +1260,8 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1147
1260
|
turnResponse.text,
|
|
1148
1261
|
turnResponse.toolCalls,
|
|
1149
1262
|
turnResponse.thinking,
|
|
1150
|
-
turnResponse.thoughtSignature
|
|
1263
|
+
turnResponse.thoughtSignature,
|
|
1264
|
+
turnResponse.providerSteps
|
|
1151
1265
|
);
|
|
1152
1266
|
|
|
1153
1267
|
// Thinking-trace telemetry (observability-only, full text, no limit):
|
|
@@ -1192,6 +1306,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1192
1306
|
}
|
|
1193
1307
|
|
|
1194
1308
|
throwIfCancelled({ usage: accumulatedUsage, turns, text: turnResponse.text });
|
|
1309
|
+
emitProgress(config); // the calls are on record BEFORE they run, so a crash can finish them instead of forgetting them
|
|
1195
1310
|
allToolCalls.push(...turnResponse.toolCalls);
|
|
1196
1311
|
if (allToolCalls.length > streamLimits.maxToolCallsPerRun) {
|
|
1197
1312
|
throw new Error(
|
|
@@ -1213,6 +1328,8 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1213
1328
|
agentName,
|
|
1214
1329
|
signal: linked.signal,
|
|
1215
1330
|
sessionId: runOptions?.sessionId || options?.sessionId,
|
|
1331
|
+
onResult: config.onToolResult,
|
|
1332
|
+
openToolLog: config.openToolLog,
|
|
1216
1333
|
onSubagentEvent: (ev) => {
|
|
1217
1334
|
try {
|
|
1218
1335
|
outerStream.push({
|
|
@@ -1278,6 +1395,12 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1278
1395
|
toolCalls: turnResponse.toolCalls,
|
|
1279
1396
|
toolResults: sanitizedResults,
|
|
1280
1397
|
});
|
|
1398
|
+
// A tool ended the run (`ctx.stop`): the whole batch has finished, so stop here. Waiting steers become follow-ups.
|
|
1399
|
+
if (sanitizedResults.some((r) => r.stopped)) {
|
|
1400
|
+
stoppedByTool = true;
|
|
1401
|
+
emitProgress(config);
|
|
1402
|
+
break;
|
|
1403
|
+
}
|
|
1281
1404
|
const toolSteered = drainSteerInbox(context, config.steerInbox);
|
|
1282
1405
|
if (toolSteered.length > 0) {
|
|
1283
1406
|
for (const preview of toolSteered) {
|
|
@@ -1295,7 +1418,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1295
1418
|
}
|
|
1296
1419
|
|
|
1297
1420
|
const truncatedByMaxTurns =
|
|
1298
|
-
turns >= maxTurns && !!lastResponse?.toolCalls && lastResponse.toolCalls.length > 0;
|
|
1421
|
+
!stoppedByTool && turns >= maxTurns && !!lastResponse?.toolCalls && lastResponse.toolCalls.length > 0;
|
|
1299
1422
|
if (truncatedByMaxTurns) {
|
|
1300
1423
|
warnMaxTurnsHit(
|
|
1301
1424
|
agentName,
|
|
@@ -1303,10 +1426,12 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
1303
1426
|
lastResponse!.toolCalls.map((c) => c.name)
|
|
1304
1427
|
);
|
|
1305
1428
|
}
|
|
1306
|
-
const truncatedFinish =
|
|
1429
|
+
const truncatedFinish = stoppedByTool
|
|
1430
|
+
? { finishReason: "stopped" as const, ...(lastResponse?.finishReason ? { rawFinishReason: lastResponse.finishReason } : {}) }
|
|
1431
|
+
: canonicalFinishReason(lastResponse?.finishReason, truncatedByMaxTurns);
|
|
1307
1432
|
|
|
1308
1433
|
let streamParsed: unknown | undefined;
|
|
1309
|
-
if (streamOutputRaw !== undefined && !truncatedByMaxTurns) {
|
|
1434
|
+
if (streamOutputRaw !== undefined && !truncatedByMaxTurns && !stoppedByTool) {
|
|
1310
1435
|
try {
|
|
1311
1436
|
streamParsed = parseFinalStructuredOutput(lastResponse?.text || "", streamOutputRaw, streamOutputSpec);
|
|
1312
1437
|
} catch (err) {
|
package/src/index.ts
CHANGED
|
@@ -317,7 +317,7 @@ export {
|
|
|
317
317
|
formatSessionBanner,
|
|
318
318
|
emptyTotals,
|
|
319
319
|
} from "./session/store.ts";
|
|
320
|
-
export type { SessionLimits, SessionPayloadStatus, PersistedAgentSession, SessionTotals, SessionAgentLike, LoadedSessionDir, PersistedSubAgentTrace } from "./session/store.ts";
|
|
320
|
+
export type { SessionLimits, SessionPayloadStatus, PersistedAgentSession, SessionTotals, SessionAgentLike, LoadedSessionDir, PersistedSubAgentTrace, SessionRunMark, SessionRunEnd, SessionPending, SessionPendingPrompt, SessionToolRecord } from "./session/store.ts";
|
|
321
321
|
|
|
322
322
|
// Utilities
|
|
323
323
|
/**
|
|
@@ -362,6 +362,8 @@ export type {
|
|
|
362
362
|
ToolResultPart,
|
|
363
363
|
ThinkingPart,
|
|
364
364
|
ProviderContext,
|
|
365
|
+
ProviderSteps,
|
|
366
|
+
MediaResolution,
|
|
365
367
|
} from "./types/message.ts";
|
|
366
368
|
|
|
367
369
|
export type {
|
|
@@ -412,6 +414,7 @@ export type {
|
|
|
412
414
|
Budget,
|
|
413
415
|
BudgetState,
|
|
414
416
|
SubAgentRunIds,
|
|
417
|
+
InterruptedRun,
|
|
415
418
|
} from "./types/agent.ts";
|
|
416
419
|
|
|
417
420
|
// Minimalist core: harness modules (RLM/RSI, OTel, testing, CLI doctor, hooks,
|
|
@@ -4,6 +4,7 @@ import { createHash } from "node:crypto";
|
|
|
4
4
|
import { getEnv } from "../utils/env.ts";
|
|
5
5
|
import { telemetryBus } from "../telemetry.ts";
|
|
6
6
|
import { onHomeChange, resolveHome } from "../utils/home.ts";
|
|
7
|
+
import { writeFileDurable } from "../utils/durable.ts";
|
|
7
8
|
|
|
8
9
|
/** Default TTL: 12 Hours (in milliseconds). */
|
|
9
10
|
export const DEFAULT_CATALOG_TTL_MS = 12 * 60 * 60 * 1000;
|
|
@@ -102,17 +103,9 @@ function readJsonFileSync(filePath: string): any {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
// Writes the catalog cache durably but without a `.bak` (it is re-downloadable); in: path + payload from refreshModelCatalog, out: the cache file.
|
|
105
107
|
function writeJsonFileSync(filePath: string, data: any): void {
|
|
106
|
-
|
|
107
|
-
if (!fs.existsSync(dir)) {
|
|
108
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
109
|
-
}
|
|
110
|
-
// Atomic tmp+rename with 0600 so a crash never leaves half-written JSON (Q-16a).
|
|
111
|
-
const rand = Math.random().toString(36).slice(2, 10);
|
|
112
|
-
const tmp = `${filePath}.tmp-${process.pid}-${rand}`;
|
|
113
|
-
fs.writeFileSync(tmp, JSON.stringify(data), { encoding: "utf-8", mode: 0o600 });
|
|
114
|
-
try { fs.chmodSync(tmp, 0o600); } catch {}
|
|
115
|
-
fs.renameSync(tmp, filePath);
|
|
108
|
+
writeFileDurable(filePath, JSON.stringify(data), { backup: false });
|
|
116
109
|
}
|
|
117
110
|
|
|
118
111
|
/** Verifies optional sha256 pin from MODELS_DEV_SHA256 (Q-16a). Returns false on mismatch. */
|