agent-accelerator 0.1.0 → 0.3.0
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 +174 -123
- package/{SYSTEM_PROMPT_AGENT.md → examples/prompts/SYSTEM_PROMPT_AGENT.md} +1 -1
- package/package.json +14 -9
- package/src/agent/agent.ts +385 -73
- package/src/agent/context.ts +1 -0
- package/src/agent/delegation.ts +443 -30
- package/src/agent/loop.ts +183 -53
- package/src/data/README.md +6 -6
- package/src/index.ts +163 -46
- package/src/models/catalog-cache.ts +60 -9
- package/src/models/catalog.ts +55 -8
- package/src/providers/google.ts +926 -0
- package/src/providers/openai-compat.ts +1147 -0
- package/src/providers/openai.ts +959 -0
- package/src/providers/openrouter-responses.ts +949 -0
- package/src/providers/openrouter.ts +1037 -0
- package/src/{ai-sdk → providers}/registry.ts +43 -55
- package/src/providers.ts +492 -0
- package/src/session/store.ts +806 -0
- package/src/streaming/sse-parser.ts +6 -4
- package/src/tools/executor.ts +23 -7
- package/src/tools/schema.ts +21 -11
- package/src/types/agent.ts +33 -2
- package/src/types/core.ts +3 -2
- package/src/types/message.ts +5 -0
- package/src/types/model.ts +3 -7
- package/src/types/provider-payloads.ts +2 -84
- package/src/types/response.ts +24 -0
- package/src/types/tool.ts +8 -0
- package/src/update-models.ts +56 -0
- package/src/utils/cache.ts +1 -1
- package/src/utils/documents.ts +517 -0
- package/src/utils/env.ts +0 -7
- package/src/{ai-sdk → utils}/errors.ts +61 -2
- package/src/utils/headers.ts +10 -20
- package/src/utils/media.ts +5 -2
- package/src/utils/retry.ts +89 -0
- package/src/utils/serialization.ts +15 -0
- package/src/utils/session.ts +36 -0
- package/src/ai-sdk/converters.ts +0 -342
- package/src/ai-sdk/executor.ts +0 -454
- package/src/ai-sdk/index.ts +0 -55
- package/src/ai-sdk/model-provider.ts +0 -303
- package/src/ai-sdk/options.ts +0 -306
- package/src/ai-sdk/provider.ts +0 -415
- package/src/tokens/counter.ts +0 -136
- /package/{SYSTEM_PROMPT.md → examples/prompts/SYSTEM_PROMPT.md} +0 -0
- /package/{SYSTEM_PROMPT_TOOLS.md → examples/prompts/SYSTEM_PROMPT_TOOLS.md} +0 -0
package/src/agent/loop.ts
CHANGED
|
@@ -7,8 +7,9 @@ import { AssistantMessageEventStream } from "../streaming/event-stream.ts";
|
|
|
7
7
|
import { AgentContext } from "./context.ts";
|
|
8
8
|
import { toStandardToolDeclarations } from "../tools/tool.ts";
|
|
9
9
|
import { executeToolCalls } from "../tools/executor.ts";
|
|
10
|
-
import { getModelFromCatalog, ensureModelCatalogFresh } from "../models/catalog.ts";
|
|
11
|
-
import {
|
|
10
|
+
import { getModelFromCatalog, ensureModelCatalogFresh, validateModelThinking } from "../models/catalog.ts";
|
|
11
|
+
import { preprocessFilePartsForBypass } from "../utils/documents.ts";
|
|
12
|
+
import { noteProviderTurn } from "../providers.ts";
|
|
12
13
|
|
|
13
14
|
export interface AgentLoopConfig {
|
|
14
15
|
agentName?: string;
|
|
@@ -19,6 +20,55 @@ export interface AgentLoopConfig {
|
|
|
19
20
|
options?: ProviderRequestOptions;
|
|
20
21
|
runOptions?: AgentRunOptions;
|
|
21
22
|
maxTurns?: number;
|
|
23
|
+
/** Convert `file` parts client-side for pdf-incapable models (see Agent flag). */
|
|
24
|
+
bypassInputFileModality?: boolean;
|
|
25
|
+
/** Called after every turn for durable persistence checkpoints (never fails a turn). */
|
|
26
|
+
onProgress?: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function emitTurn(
|
|
30
|
+
runOptions: AgentRunOptions | undefined,
|
|
31
|
+
turn: { turns: number; text?: string; thinking?: string; toolCalls?: ToolCallRecord[]; toolResults?: ToolResultRecord[] }
|
|
32
|
+
): void {
|
|
33
|
+
try {
|
|
34
|
+
runOptions?.onTurn?.({
|
|
35
|
+
turns: turn.turns,
|
|
36
|
+
text: turn.text,
|
|
37
|
+
thinking: turn.thinking,
|
|
38
|
+
toolCalls: turn.toolCalls?.map((c) => ({ id: c.id, name: c.name })),
|
|
39
|
+
toolResults: turn.toolResults?.map((r) => ({ id: r.id, name: r.name, isError: r.isError })),
|
|
40
|
+
});
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function emitProgress(config: { onProgress?: () => void }): void {
|
|
45
|
+
try { config.onProgress?.(); } catch {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Normalizes `maxTurns` to a safe positive integer (default Infinity).
|
|
50
|
+
* `0` means infinite (no limit), matching the `timeout: 0` convention.
|
|
51
|
+
* Prevents silent zero-turn runs from negative values (clamped to 1).
|
|
52
|
+
*/
|
|
53
|
+
function normalizeMaxTurns(value?: number): number {
|
|
54
|
+
if (value === undefined) return Infinity;
|
|
55
|
+
if (value === 0 || value === Infinity) return Infinity;
|
|
56
|
+
if (!Number.isFinite(value as number)) return Infinity;
|
|
57
|
+
return Math.max(1, Math.floor(value as number));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Warns when an agent run stops because `maxTurns` was exhausted while
|
|
62
|
+
* the model still wanted to call tools. Without this, the loop simply
|
|
63
|
+
* returns the last tool-turn text (often empty) with `finishReason:
|
|
64
|
+
* "tool_calls"`, which looks like success while dropping pending work.
|
|
65
|
+
*/
|
|
66
|
+
function warnMaxTurnsHit(agentName: string | undefined, maxTurns: number, pendingTools: string[]): void {
|
|
67
|
+
const who = agentName ? `"${agentName}"` : "Agent";
|
|
68
|
+
const tools = pendingTools.length > 0 ? ` Pending tool calls dropped: ${pendingTools.join(", ")}.` : "";
|
|
69
|
+
// Leading newline: runs often end mid-line on streamed output; without it
|
|
70
|
+
// the warning glues onto the last streamed chunk.
|
|
71
|
+
console.warn(`\n[Agent Accelerator] WARNING [agent] ${who} hit maxTurns (${maxTurns}) with unfinished tool calls.${tools} Increase maxTurns or split the task.`);
|
|
22
72
|
}
|
|
23
73
|
|
|
24
74
|
export function computeCostFromPricing(usage: TokenUsage, spec?: ModelSpec): TokenUsage["cost"] {
|
|
@@ -67,6 +117,13 @@ function accumulateUsage(target: TokenUsage, source: TokenUsage, spec?: ModelSpe
|
|
|
67
117
|
target.cacheWriteTokens = (target.cacheWriteTokens ?? 0) + (source.cacheWriteTokens ?? 0);
|
|
68
118
|
target.thinkingTokens = (target.thinkingTokens ?? 0) + (source.thinkingTokens ?? 0);
|
|
69
119
|
|
|
120
|
+
// Canonical invariant (provider-agnostic): cache hits are a subset of
|
|
121
|
+
// input. Some providers occasionally report cached > input on long chained
|
|
122
|
+
// runs, which surfaces as >100% hit rates downstream. Clamp the aggregate
|
|
123
|
+
// so no consumer can observe an impossible ratio.
|
|
124
|
+
target.cachedTokens = Math.min(target.cachedTokens ?? 0, target.inputTokens);
|
|
125
|
+
target.cacheReadTokens = Math.min(target.cacheReadTokens ?? 0, target.inputTokens);
|
|
126
|
+
|
|
70
127
|
const sourceCost = computeCostFromPricing(source, spec);
|
|
71
128
|
if (sourceCost) {
|
|
72
129
|
source.cost = sourceCost;
|
|
@@ -121,6 +178,7 @@ async function executeToolCallsWithRepeatGuard(options: {
|
|
|
121
178
|
agentName?: string;
|
|
122
179
|
signal?: AbortSignal;
|
|
123
180
|
sessionId?: string;
|
|
181
|
+
onSubagentEvent?: (event: { trackingId: string; delta?: string; thinkingDelta?: string; partialText?: string; partialThinking?: string }) => void;
|
|
124
182
|
}): Promise<{ results: ToolResultRecord[]; fingerprints: Set<string> }> {
|
|
125
183
|
const { toolCalls, previousFingerprints } = options;
|
|
126
184
|
const currentFingerprints = new Set<string>();
|
|
@@ -153,14 +211,29 @@ async function executeToolCallsWithRepeatGuard(options: {
|
|
|
153
211
|
parallel: true,
|
|
154
212
|
signal: options.signal,
|
|
155
213
|
sessionId: options.sessionId,
|
|
214
|
+
onSubagentEvent: options.onSubagentEvent,
|
|
156
215
|
})
|
|
157
216
|
: [];
|
|
158
|
-
const byId = new Map<string, ToolResultRecord>();
|
|
159
|
-
for (const result of executed)
|
|
160
|
-
|
|
217
|
+
const byId = new Map<string, ToolResultRecord[]>();
|
|
218
|
+
for (const result of executed) {
|
|
219
|
+
const queue = byId.get(result.id) ?? [];
|
|
220
|
+
queue.push(result);
|
|
221
|
+
byId.set(result.id, queue);
|
|
222
|
+
}
|
|
223
|
+
for (const [id, result] of blocked) {
|
|
224
|
+
const queue = byId.get(id) ?? [];
|
|
225
|
+
queue.push(result);
|
|
226
|
+
byId.set(id, queue);
|
|
227
|
+
}
|
|
161
228
|
|
|
162
229
|
return {
|
|
163
|
-
|
|
230
|
+
// Consume per-id queues in call order: some open-weights models reuse one
|
|
231
|
+
// id for several distinct calls in a turn, and a plain id->result map
|
|
232
|
+
// would hand every call the last result. Queues keep each result aligned
|
|
233
|
+
// with its own call.
|
|
234
|
+
results: toolCalls
|
|
235
|
+
.map((call) => byId.get(call.id)?.shift())
|
|
236
|
+
.filter((r): r is ToolResultRecord => Boolean(r)),
|
|
164
237
|
fingerprints: currentFingerprints,
|
|
165
238
|
};
|
|
166
239
|
}
|
|
@@ -178,8 +251,17 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
178
251
|
tools,
|
|
179
252
|
options,
|
|
180
253
|
runOptions,
|
|
181
|
-
maxTurns =
|
|
254
|
+
maxTurns: rawMaxTurns = Infinity,
|
|
182
255
|
} = config;
|
|
256
|
+
const maxTurns = normalizeMaxTurns(rawMaxTurns);
|
|
257
|
+
|
|
258
|
+
if (config.bypassInputFileModality) {
|
|
259
|
+
await preprocessFilePartsForBypass(config.context.messages, {
|
|
260
|
+
providerId: provider.id,
|
|
261
|
+
modelId,
|
|
262
|
+
signal: runOptions?.signal,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
183
265
|
|
|
184
266
|
const standardTools = toStandardToolDeclarations(tools);
|
|
185
267
|
const startTime = Date.now();
|
|
@@ -204,30 +286,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
204
286
|
while (turns < maxTurns) {
|
|
205
287
|
turns++;
|
|
206
288
|
|
|
207
|
-
// Battle-tested context window check — trim oldest history if needed, keep cached prefix (system+tools)
|
|
208
289
|
const spec = getModelFromCatalog(provider.id, modelId);
|
|
209
|
-
const contextWindow = spec?.limit?.context ?? spec?.contextWindow ?? 128000;
|
|
210
|
-
const maxOutput = spec?.limit?.output ?? spec?.maxOutputTokens ?? 8192;
|
|
211
|
-
// Reserve for output + 10% headroom, ensure first turn already cache-friendly
|
|
212
|
-
const budgetForInput = Math.floor(contextWindow * 0.9) - maxOutput;
|
|
213
|
-
let estimated = 0;
|
|
214
|
-
try {
|
|
215
|
-
estimated = countTokens({ systemPrompt: context.systemPrompt, messages: context.messages, tools: standardTools as any });
|
|
216
|
-
} catch {}
|
|
217
|
-
if (estimated > budgetForInput && context.messages.length > 2) {
|
|
218
|
-
// Keep system + last 70% of history, drop oldest middle (preserve cached prefix stability)
|
|
219
|
-
const keepCount = Math.max(2, Math.floor(context.messages.length * 0.7));
|
|
220
|
-
const toKeep = context.messages.slice(-keepCount);
|
|
221
|
-
// Preserve at least system and first user if possible
|
|
222
|
-
const firstUserIdx = context.messages.findIndex((m) => m.role === "user");
|
|
223
|
-
if (firstUserIdx >= 0 && firstUserIdx < context.messages.length - keepCount) {
|
|
224
|
-
// Drop middle, keep head + tail for cache stability
|
|
225
|
-
const head = context.messages.slice(0, 1);
|
|
226
|
-
context.messages = [...head, ...toKeep];
|
|
227
|
-
} else {
|
|
228
|
-
context.messages = toKeep;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
290
|
|
|
232
291
|
const providerOptions: ProviderRequestOptions = {
|
|
233
292
|
...options,
|
|
@@ -250,6 +309,12 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
250
309
|
|
|
251
310
|
finalResult = genResult;
|
|
252
311
|
|
|
312
|
+
// Canonical session routing: lets adapters detect provider switches.
|
|
313
|
+
noteProviderTurn(
|
|
314
|
+
runOptions?.sessionId || options?.sessionId || options?.cache?.sessionId,
|
|
315
|
+
provider.id
|
|
316
|
+
);
|
|
317
|
+
|
|
253
318
|
// Safety fallback: if no tool calls and text is empty, rescue answer from thinking
|
|
254
319
|
if ((!genResult.text || genResult.text.trim() === "") && (!genResult.toolCalls || genResult.toolCalls.length === 0) && genResult.thinking) {
|
|
255
320
|
if (genResult.thinking.includes("</think>")) {
|
|
@@ -274,6 +339,8 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
274
339
|
|
|
275
340
|
// If no tool calls, generation is complete!
|
|
276
341
|
if (!genResult.toolCalls || genResult.toolCalls.length === 0) {
|
|
342
|
+
emitTurn(runOptions, { turns, text: genResult.text, thinking: genResult.thinking });
|
|
343
|
+
emitProgress(config);
|
|
277
344
|
break;
|
|
278
345
|
}
|
|
279
346
|
|
|
@@ -310,10 +377,31 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
310
377
|
|
|
311
378
|
allToolResults.push(...sanitizedResults);
|
|
312
379
|
context.addToolResults(sanitizedResults);
|
|
380
|
+
emitTurn(runOptions, {
|
|
381
|
+
turns,
|
|
382
|
+
text: genResult.text,
|
|
383
|
+
thinking: genResult.thinking,
|
|
384
|
+
toolCalls: genResult.toolCalls,
|
|
385
|
+
toolResults: sanitizedResults,
|
|
386
|
+
});
|
|
387
|
+
emitProgress(config);
|
|
313
388
|
|
|
314
389
|
if (runOptions?.signal?.aborted) break;
|
|
315
390
|
}
|
|
316
391
|
|
|
392
|
+
// maxTurns exhausted while the model still requested tools: the tool
|
|
393
|
+
// results above were never sent back, so the run is truncated — surface
|
|
394
|
+
// it via finishReason + warning instead of silently returning.
|
|
395
|
+
const truncatedByMaxTurns =
|
|
396
|
+
turns >= maxTurns && !!finalResult?.toolCalls && finalResult.toolCalls.length > 0;
|
|
397
|
+
if (truncatedByMaxTurns) {
|
|
398
|
+
warnMaxTurnsHit(
|
|
399
|
+
agentName,
|
|
400
|
+
maxTurns,
|
|
401
|
+
finalResult.toolCalls.map((c: ToolCallRecord) => c.name)
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
317
405
|
return new AgentResponse({
|
|
318
406
|
text: finalResult?.text || "",
|
|
319
407
|
thinking: finalResult?.thinking,
|
|
@@ -325,7 +413,7 @@ export async function runAgentLoop(config: AgentLoopConfig): Promise<AgentRespon
|
|
|
325
413
|
responseId: finalResult?.responseId,
|
|
326
414
|
model: modelId,
|
|
327
415
|
provider: provider.id,
|
|
328
|
-
finishReason: finalResult?.finishReason,
|
|
416
|
+
finishReason: truncatedByMaxTurns ? "max_turns" : finalResult?.finishReason,
|
|
329
417
|
durationMs: Date.now() - startTime,
|
|
330
418
|
raw: finalResult?.raw,
|
|
331
419
|
turns,
|
|
@@ -340,7 +428,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
340
428
|
const startTime = Date.now();
|
|
341
429
|
|
|
342
430
|
// Cancellation: merge user signal + outer cancel() into one linked controller.
|
|
343
|
-
//
|
|
431
|
+
// Native fetch honors abortSignal on every provider, so aborting this
|
|
344
432
|
// stops the HTTP request; we also cancel the active inner provider stream.
|
|
345
433
|
const linked = new AbortController();
|
|
346
434
|
const userSignal = config.runOptions?.signal;
|
|
@@ -365,6 +453,27 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
365
453
|
(async () => {
|
|
366
454
|
try {
|
|
367
455
|
await ensureModelCatalogFresh();
|
|
456
|
+
// Re-validate after refresh: Agent.stream() validates before the catalog
|
|
457
|
+
// is guaranteed fresh, so a cold cache validates permissively. A mismatch
|
|
458
|
+
// discovered here fails the stream instead of reaching the provider.
|
|
459
|
+
{
|
|
460
|
+
const lvl = config.runOptions?.thinkingLevel ?? config.options?.thinking?.level;
|
|
461
|
+
if (lvl) {
|
|
462
|
+
try {
|
|
463
|
+
validateModelThinking(config.provider.id, config.modelId, lvl);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
outerStream.fail(err instanceof Error ? err : new Error(String(err)));
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (config.bypassInputFileModality) {
|
|
471
|
+
await preprocessFilePartsForBypass(config.context.messages, {
|
|
472
|
+
providerId: config.provider.id,
|
|
473
|
+
modelId: config.modelId,
|
|
474
|
+
signal: config.runOptions?.signal,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
368
477
|
const {
|
|
369
478
|
agentName,
|
|
370
479
|
provider,
|
|
@@ -373,8 +482,9 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
373
482
|
tools,
|
|
374
483
|
options,
|
|
375
484
|
runOptions,
|
|
376
|
-
maxTurns =
|
|
485
|
+
maxTurns: rawMaxTurns = Infinity,
|
|
377
486
|
} = config;
|
|
487
|
+
const maxTurns = normalizeMaxTurns(rawMaxTurns);
|
|
378
488
|
|
|
379
489
|
const standardTools = toStandardToolDeclarations(tools);
|
|
380
490
|
|
|
@@ -399,25 +509,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
399
509
|
throwIfCancelled();
|
|
400
510
|
turns++;
|
|
401
511
|
|
|
402
|
-
// Same context-window trim as non-stream (ensure cache prefix stable)
|
|
403
512
|
const spec2 = getModelFromCatalog(provider.id, modelId);
|
|
404
|
-
const cw2 = spec2?.limit?.context ?? spec2?.contextWindow ?? 128000;
|
|
405
|
-
const mo2 = spec2?.limit?.output ?? spec2?.maxOutputTokens ?? 8192;
|
|
406
|
-
const budget2 = Math.floor(cw2 * 0.9) - mo2;
|
|
407
|
-
try {
|
|
408
|
-
const est2 = countTokens({ systemPrompt: context.systemPrompt, messages: context.messages, tools: standardTools as any });
|
|
409
|
-
if (est2 > budget2 && context.messages.length > 2) {
|
|
410
|
-
const keep2 = Math.max(2, Math.floor(context.messages.length * 0.7));
|
|
411
|
-
const toKeep = context.messages.slice(-keep2);
|
|
412
|
-
const firstUserIdx = context.messages.findIndex((m) => m.role === "user");
|
|
413
|
-
if (firstUserIdx >= 0 && firstUserIdx < context.messages.length - keep2) {
|
|
414
|
-
const head = context.messages.slice(0, 1);
|
|
415
|
-
context.messages = [...head, ...toKeep];
|
|
416
|
-
} else {
|
|
417
|
-
context.messages = toKeep;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
} catch {}
|
|
421
513
|
|
|
422
514
|
const providerOptions: ProviderRequestOptions = {
|
|
423
515
|
...options,
|
|
@@ -449,6 +541,11 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
449
541
|
currentInner = null;
|
|
450
542
|
throwIfCancelled();
|
|
451
543
|
lastResponse = turnResponse;
|
|
544
|
+
// Canonical session routing: lets adapters detect provider switches.
|
|
545
|
+
noteProviderTurn(
|
|
546
|
+
runOptions?.sessionId || options?.sessionId || options?.cache?.sessionId,
|
|
547
|
+
provider.id
|
|
548
|
+
);
|
|
452
549
|
|
|
453
550
|
// Safety fallback: if no tool calls and text is empty, rescue answer from thinking
|
|
454
551
|
if ((!turnResponse.text || turnResponse.text.trim() === "") && (!turnResponse.toolCalls || turnResponse.toolCalls.length === 0) && turnResponse.thinking) {
|
|
@@ -472,6 +569,8 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
472
569
|
);
|
|
473
570
|
|
|
474
571
|
if (!turnResponse.toolCalls || turnResponse.toolCalls.length === 0) {
|
|
572
|
+
emitTurn(runOptions, { turns, text: turnResponse.text, thinking: turnResponse.thinking });
|
|
573
|
+
emitProgress(config);
|
|
475
574
|
break;
|
|
476
575
|
}
|
|
477
576
|
|
|
@@ -483,6 +582,18 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
483
582
|
agentName,
|
|
484
583
|
signal: linked.signal,
|
|
485
584
|
sessionId: runOptions?.sessionId || options?.sessionId || options?.cache?.sessionId,
|
|
585
|
+
onSubagentEvent: (ev) => {
|
|
586
|
+
try {
|
|
587
|
+
outerStream.push({
|
|
588
|
+
type: "subagent_delta",
|
|
589
|
+
subagentTrackingId: ev.trackingId,
|
|
590
|
+
delta: ev.delta,
|
|
591
|
+
thinkingDelta: ev.thinkingDelta,
|
|
592
|
+
partialText: ev.partialText,
|
|
593
|
+
partialThinking: ev.partialThinking,
|
|
594
|
+
});
|
|
595
|
+
} catch {}
|
|
596
|
+
},
|
|
486
597
|
});
|
|
487
598
|
throwIfCancelled();
|
|
488
599
|
const results = guarded.results;
|
|
@@ -518,7 +629,26 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
518
629
|
|
|
519
630
|
allToolResults.push(...sanitizedResults);
|
|
520
631
|
context.addToolResults(sanitizedResults);
|
|
632
|
+
emitTurn(runOptions, {
|
|
633
|
+
turns,
|
|
634
|
+
text: turnResponse.text,
|
|
635
|
+
thinking: turnResponse.thinking,
|
|
636
|
+
toolCalls: turnResponse.toolCalls,
|
|
637
|
+
toolResults: sanitizedResults,
|
|
638
|
+
});
|
|
639
|
+
emitProgress(config);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const truncatedByMaxTurns =
|
|
643
|
+
turns >= maxTurns && !!lastResponse?.toolCalls && lastResponse.toolCalls.length > 0;
|
|
644
|
+
if (truncatedByMaxTurns) {
|
|
645
|
+
warnMaxTurnsHit(
|
|
646
|
+
agentName,
|
|
647
|
+
maxTurns,
|
|
648
|
+
lastResponse!.toolCalls.map((c) => c.name)
|
|
649
|
+
);
|
|
521
650
|
}
|
|
651
|
+
const truncatedFinishReason = truncatedByMaxTurns ? "max_turns" : lastResponse?.finishReason;
|
|
522
652
|
|
|
523
653
|
const finalAgentResponse = new AgentResponse({
|
|
524
654
|
text: lastResponse?.text || "",
|
|
@@ -531,7 +661,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
531
661
|
responseId: lastResponse?.responseId,
|
|
532
662
|
model: modelId,
|
|
533
663
|
provider: provider.id,
|
|
534
|
-
finishReason:
|
|
664
|
+
finishReason: truncatedFinishReason,
|
|
535
665
|
durationMs: Date.now() - startTime,
|
|
536
666
|
raw: lastResponse?.raw as any,
|
|
537
667
|
turns,
|
|
@@ -541,7 +671,7 @@ export function streamAgentLoop(config: AgentLoopConfig): AssistantMessageEventS
|
|
|
541
671
|
type: "done",
|
|
542
672
|
delta: "",
|
|
543
673
|
usage: accumulatedUsage,
|
|
544
|
-
finishReason:
|
|
674
|
+
finishReason: truncatedFinishReason,
|
|
545
675
|
responseId: lastResponse?.responseId,
|
|
546
676
|
});
|
|
547
677
|
|
package/src/data/README.md
CHANGED
|
@@ -11,13 +11,13 @@ Agent Accelerator references a catalog of **7,500+ models across 200+ providers*
|
|
|
11
11
|
To keep the repository and published npm/Bun package lightweight and blazing fast:
|
|
12
12
|
|
|
13
13
|
1. **Excluded from Production Package & Git**:
|
|
14
|
-
* The ~4.5 MB full catalog snapshot (`models
|
|
15
|
-
*
|
|
14
|
+
* The ~4.5 MB full catalog snapshot (`models.dev.json`) is **never** committed to git and is **excluded** from the npm/Bun distribution bundle.
|
|
15
|
+
* The file is tracked in `.gitignore` and `.npmignore`.
|
|
16
16
|
|
|
17
17
|
2. **Dynamic 12-Hour Automated TTL Cache**:
|
|
18
|
-
* On agent execution (`runAgentLoop` / `streamAgentLoop`), Agent Accelerator checks the status of `src/data/models
|
|
18
|
+
* On agent execution (`runAgentLoop` / `streamAgentLoop`), Agent Accelerator checks the status of `src/data/models.dev.json`.
|
|
19
19
|
* **Cache Hit (< 12h)**: Instant memory/disk load with zero network overhead.
|
|
20
|
-
* **Cache Miss / Expired (≥ 12h)**: Automatically downloads the latest catalog directly from `https://models.dev/api.json`, caches it to `src/data/models
|
|
20
|
+
* **Cache Miss / Expired (≥ 12h)**: Automatically downloads the latest catalog directly from `https://models.dev/api.json`, caches it to `src/data/models.dev.json`, and loads the catalog into memory.
|
|
21
21
|
* **Resilience**: If an existing cache exists on disk, temporary upstream network issues will safely continue using the local cached copy.
|
|
22
22
|
|
|
23
23
|
---
|
|
@@ -36,7 +36,7 @@ bun run update-models
|
|
|
36
36
|
bun run update-models --force
|
|
37
37
|
|
|
38
38
|
# Custom TTL (e.g., 24 hours)
|
|
39
|
-
bun
|
|
39
|
+
bun src/update-models.ts --ttl=24h
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
---
|
|
@@ -80,5 +80,5 @@ setCatalogTTL(6 * 60 * 60 * 1000);
|
|
|
80
80
|
```text
|
|
81
81
|
src/data/
|
|
82
82
|
├── README.md # This documentation
|
|
83
|
-
└── models
|
|
83
|
+
└── models.dev.json # Auto-generated on first run (gitignored, excluded from bundle)
|
|
84
84
|
```
|