@qwen-code/arena-agent 0.0.10-dev.8-test.0 → 0.0.10-test.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/.env.example +61 -10
- package/CHANGELOG.md +148 -17
- package/README.md +309 -46
- package/dist/agent.d.ts +20 -6
- package/dist/agent.js +353 -72
- package/dist/agent.js.map +1 -1
- package/dist/anthropic.d.ts +1 -1
- package/dist/anthropic.js +46 -57
- package/dist/anthropic.js.map +1 -1
- package/dist/arena-history.d.ts +12 -0
- package/dist/arena-history.js +119 -0
- package/dist/arena-history.js.map +1 -0
- package/dist/ask-user.js +44 -2
- package/dist/ask-user.js.map +1 -1
- package/dist/attachment-limits.d.ts +4 -0
- package/dist/attachment-limits.js +10 -0
- package/dist/attachment-limits.js.map +1 -0
- package/dist/attachments.d.ts +4 -2
- package/dist/attachments.js +9 -11
- package/dist/attachments.js.map +1 -1
- package/dist/brave-search.js +11 -7
- package/dist/brave-search.js.map +1 -1
- package/dist/canonical-data.d.ts +9 -0
- package/dist/canonical-data.js +12 -3
- package/dist/canonical-data.js.map +1 -1
- package/dist/cli.js +140 -40
- package/dist/cli.js.map +1 -1
- package/dist/code-tool-schema.d.ts +3 -0
- package/dist/code-tool-schema.js +92 -0
- package/dist/code-tool-schema.js.map +1 -0
- package/dist/code-tools.d.ts +2 -1
- package/dist/code-tools.js +103 -31
- package/dist/code-tools.js.map +1 -1
- package/dist/compaction-threshold.d.ts +3 -0
- package/dist/compaction-threshold.js +6 -0
- package/dist/compaction-threshold.js.map +1 -0
- package/dist/compaction.d.ts +24 -7
- package/dist/compaction.js +242 -66
- package/dist/compaction.js.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.js +30 -5
- package/dist/config.js.map +1 -1
- package/dist/connector-errors.d.ts +16 -0
- package/dist/connector-errors.js +69 -0
- package/dist/connector-errors.js.map +1 -0
- package/dist/connector-files.d.ts +31 -0
- package/dist/connector-files.js +249 -0
- package/dist/connector-files.js.map +1 -0
- package/dist/connector-input.d.ts +10 -0
- package/dist/connector-input.js +79 -0
- package/dist/connector-input.js.map +1 -0
- package/dist/connector-mcp.d.ts +60 -0
- package/dist/connector-mcp.js +474 -0
- package/dist/connector-mcp.js.map +1 -0
- package/dist/connectors.d.ts +13 -0
- package/dist/connectors.js +69 -0
- package/dist/connectors.js.map +1 -0
- package/dist/fetch-page.js +2 -2
- package/dist/fetch-page.js.map +1 -1
- package/dist/history.d.ts +16 -5
- package/dist/history.js +73 -14
- package/dist/history.js.map +1 -1
- package/dist/image-choice.d.ts +23 -0
- package/dist/image-choice.js +190 -0
- package/dist/image-choice.js.map +1 -0
- package/dist/image-input-fallback.js +5 -0
- package/dist/image-input-fallback.js.map +1 -1
- package/dist/image-model.d.ts +6 -0
- package/dist/image-model.js +32 -0
- package/dist/image-model.js.map +1 -0
- package/dist/midturn-checkpoint-hook.d.ts +14 -1
- package/dist/midturn-checkpoint-hook.js.map +1 -1
- package/dist/mode.js +1 -1
- package/dist/mode.js.map +1 -1
- package/dist/model-capabilities.js +18 -8
- package/dist/model-capabilities.js.map +1 -1
- package/dist/model-router-media.js +55 -3
- package/dist/model-router-media.js.map +1 -1
- package/dist/model-stream.d.ts +16 -0
- package/dist/model-stream.js +129 -0
- package/dist/model-stream.js.map +1 -0
- package/dist/openai-responses.d.ts +2 -1
- package/dist/openai-responses.js +85 -88
- package/dist/openai-responses.js.map +1 -1
- package/dist/process-manager.js +9 -9
- package/dist/process-manager.js.map +1 -1
- package/dist/prompts.d.ts +0 -2
- package/dist/prompts.js +10 -60
- package/dist/prompts.js.map +1 -1
- package/dist/protocol.d.ts +5 -1
- package/dist/protocol.js +23 -2
- package/dist/protocol.js.map +1 -1
- package/dist/provider.d.ts +18 -0
- package/dist/provider.js +120 -51
- package/dist/provider.js.map +1 -1
- package/dist/repo.d.ts +1 -1
- package/dist/repo.js +26 -4
- package/dist/repo.js.map +1 -1
- package/dist/routify-web-fetch.js +2 -2
- package/dist/routify-web-fetch.js.map +1 -1
- package/dist/server.js +55 -27
- package/dist/server.js.map +1 -1
- package/dist/session-memory-prompts.d.ts +3 -1
- package/dist/session-memory-prompts.js +4 -2
- package/dist/session-memory-prompts.js.map +1 -1
- package/dist/side-query.js +5 -0
- package/dist/side-query.js.map +1 -1
- package/dist/spec.d.ts +5 -4
- package/dist/spec.js +31 -23
- package/dist/spec.js.map +1 -1
- package/dist/startup-config.d.ts +39 -0
- package/dist/startup-config.js +347 -0
- package/dist/startup-config.js.map +1 -0
- package/dist/startup-main-config.d.ts +63 -0
- package/dist/startup-main-config.js +94 -0
- package/dist/startup-main-config.js.map +1 -0
- package/dist/startup-tool-config.d.ts +8 -0
- package/dist/startup-tool-config.js +116 -0
- package/dist/startup-tool-config.js.map +1 -0
- package/dist/summary-compaction.d.ts +29 -0
- package/dist/{legacy-compaction.js → summary-compaction.js} +79 -14
- package/dist/summary-compaction.js.map +1 -0
- package/dist/tools.d.ts +5 -1
- package/dist/tools.js +195 -68
- package/dist/tools.js.map +1 -1
- package/dist/tui.js +26 -6
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +21 -1
- package/dist/web/app.js +30 -5
- package/dist/web/app.js.map +1 -1
- package/dist/workspace.js +61 -13
- package/dist/workspace.js.map +1 -1
- package/docs/BINARY_DISTRIBUTION.md +44 -8
- package/docs/MODES_AND_RELEASES.md +79 -6
- package/docs/SDK.md +1 -0
- package/modes/code-arena/capture-provenance.json +57 -0
- package/modes/code-arena/full-stack/dependencies.lock.json +8670 -0
- package/modes/code-arena/full-stack/system-prompt.txt +1 -1
- package/modes/code-arena/full-stack/template/eslint.config.mjs +3 -4
- package/modes/code-arena/full-stack/template/postcss.config.mjs +2 -2
- package/modes/code-arena/full-stack/tools.json +186 -186
- package/modes/code-arena/html/system-prompt.txt +1 -1
- package/modes/code-arena/html/tools.json +123 -123
- package/modes/code-arena/react/dependencies.lock.json +2707 -0
- package/modes/code-arena/react/system-prompt.txt +4 -3
- package/modes/code-arena/react/tools.json +178 -184
- package/package.json +7 -2
- package/scripts/README.md +23 -4
- package/web/styles.css +22 -0
- package/dist/legacy-compaction.d.ts +0 -16
- package/dist/legacy-compaction.js.map +0 -1
package/dist/agent.js
CHANGED
|
@@ -1,20 +1,78 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { agentArenaCompactionThreshold } from "./compaction-threshold.js";
|
|
2
4
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
5
|
import { supportsModelInput, } from "./config.js";
|
|
4
|
-
import { compactMessagesIfNeeded, maintainSessionMemory, contextFromHistory } from "./compaction.js";
|
|
6
|
+
import { compactMessagesIfNeeded, maintainSessionMemory, contextFromHistory, sessionMemoryMatchesHistory, settleSessionMemory, storedSessionMemory } from "./compaction.js";
|
|
5
7
|
import { ANTHROPIC_INCOMPLETE_STREAM_ERROR_CODE } from "./anthropic.js";
|
|
8
|
+
import { rememberRestoredCalls, restoredArenaHistory } from "./arena-history.js";
|
|
6
9
|
import { modelCallLogPath, withoutLeadingSystemMessage } from "./history.js";
|
|
7
10
|
import { MockModelClient } from "./mock.js";
|
|
11
|
+
import { CAPACITY_MODEL_ERROR, INCOMPLETE_MODEL_RESPONSE, TRANSIENT_MODEL_ERROR, incompleteModelResponse } from "./model-stream.js";
|
|
8
12
|
import { contentPartMediaTypes, JsonlModelCallDiagnostics, } from "./model-diagnostics.js";
|
|
9
|
-
import {
|
|
13
|
+
import { RESPONSES_INCOMPLETE_STREAM_ERROR_CODE } from "./openai-responses.js";
|
|
10
14
|
import { HttpModelClient, ModelCapabilityError } from "./provider.js";
|
|
11
15
|
import { RepetitiveOutputDetector } from "./repetitive-output.js";
|
|
12
16
|
import { detectRepoContext } from "./repo.js";
|
|
13
|
-
import { isCodeArenaMode } from "./mode.js";
|
|
17
|
+
import { defaultStepLimit, isCodeArenaMode } from "./mode.js";
|
|
14
18
|
import { defaultClientContext, interruptedUserMessage, repoSystemMessage, systemMessage, } from "./spec.js";
|
|
15
19
|
import { imageInputFallbackConnection, ToolExecutor, } from "./tools.js";
|
|
16
20
|
import { initializeWorkspace } from "./workspace.js";
|
|
17
21
|
import { checkpointAction, } from "./midturn-checkpoint-hook.js";
|
|
22
|
+
function wrapMalformedToolArguments(argumentsText) {
|
|
23
|
+
try {
|
|
24
|
+
JSON.parse(argumentsText);
|
|
25
|
+
return argumentsText;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return JSON.stringify({ __malformedToolArguments: argumentsText });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Keep Agent Arena history replayable without losing the model's invalid input. */
|
|
32
|
+
function normalizeToolCallHistory(message, mode, historyCall) {
|
|
33
|
+
if (isCodeArenaMode(mode) || message.role !== "assistant")
|
|
34
|
+
return message;
|
|
35
|
+
// A failed Responses stream can retain completed native items without the
|
|
36
|
+
// assembled tool_calls batch. Restore their identities so resume can pair
|
|
37
|
+
// them with interrupted results instead of replaying orphaned calls.
|
|
38
|
+
const calls = message.tool_calls?.length ? message.tool_calls
|
|
39
|
+
: message.openai_response_items?.flatMap((item) => item.type === "function_call" && typeof item.call_id === "string" &&
|
|
40
|
+
typeof item.name === "string" && typeof item.arguments === "string"
|
|
41
|
+
? [{ id: item.call_id, type: "function", function: { name: item.name, arguments: item.arguments } }]
|
|
42
|
+
: []) ?? message.tool_calls;
|
|
43
|
+
let changed = Boolean(calls?.length && calls !== message.tool_calls);
|
|
44
|
+
const toolCalls = calls?.map((original) => {
|
|
45
|
+
const call = historyCall ? historyCall(original) : original;
|
|
46
|
+
if (call !== original)
|
|
47
|
+
changed = true;
|
|
48
|
+
if (typeof call.function?.arguments !== "string")
|
|
49
|
+
return call;
|
|
50
|
+
const argumentsText = wrapMalformedToolArguments(call.function.arguments);
|
|
51
|
+
if (argumentsText === call.function.arguments)
|
|
52
|
+
return call;
|
|
53
|
+
changed = true;
|
|
54
|
+
return { ...call, function: { ...call.function, arguments: argumentsText } };
|
|
55
|
+
});
|
|
56
|
+
// Native Responses replays these items instead of tool_calls. Normalize both
|
|
57
|
+
// representations while leaving reasoning and other opaque items intact.
|
|
58
|
+
const responseItems = message.openai_response_items?.map((item) => {
|
|
59
|
+
if (item.type !== "function_call" || typeof item.arguments !== "string")
|
|
60
|
+
return item;
|
|
61
|
+
const paired = toolCalls?.find(call => call.id === item.call_id);
|
|
62
|
+
const argumentsText = paired?.function.arguments ?? wrapMalformedToolArguments(item.arguments);
|
|
63
|
+
if (argumentsText === item.arguments)
|
|
64
|
+
return item;
|
|
65
|
+
changed = true;
|
|
66
|
+
return { ...item, arguments: argumentsText };
|
|
67
|
+
});
|
|
68
|
+
return changed
|
|
69
|
+
? {
|
|
70
|
+
...message,
|
|
71
|
+
...(toolCalls ? { tool_calls: toolCalls } : {}),
|
|
72
|
+
...(responseItems ? { openai_response_items: responseItems } : {}),
|
|
73
|
+
}
|
|
74
|
+
: message;
|
|
75
|
+
}
|
|
18
76
|
function parseToolResult(content) {
|
|
19
77
|
try {
|
|
20
78
|
return JSON.parse(content);
|
|
@@ -23,6 +81,41 @@ function parseToolResult(content) {
|
|
|
23
81
|
return {};
|
|
24
82
|
}
|
|
25
83
|
}
|
|
84
|
+
/** Live Agent Arena results arrive in completion order. Checkpoint writes stay
|
|
85
|
+
* serialized by the consumer, and every started operation settles before exit.
|
|
86
|
+
* Code Arena retains its sequential execution path.
|
|
87
|
+
*/
|
|
88
|
+
async function* completedTools(calls, parallel, execute) {
|
|
89
|
+
if (!parallel) {
|
|
90
|
+
for (const call of calls)
|
|
91
|
+
yield { call, message: await execute(call) };
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const queue = [];
|
|
95
|
+
let wake;
|
|
96
|
+
const settled = Promise.all(calls.map(async (call) => {
|
|
97
|
+
try {
|
|
98
|
+
queue.push({ status: "fulfilled", value: { call, message: await execute(call) } });
|
|
99
|
+
}
|
|
100
|
+
catch (reason) {
|
|
101
|
+
queue.push({ status: "rejected", reason });
|
|
102
|
+
}
|
|
103
|
+
wake?.();
|
|
104
|
+
}));
|
|
105
|
+
try {
|
|
106
|
+
for (let count = 0; count < calls.length; count++) {
|
|
107
|
+
if (!queue.length)
|
|
108
|
+
await new Promise(resolve => { wake = resolve; });
|
|
109
|
+
const result = queue.shift();
|
|
110
|
+
if (result.status === "rejected")
|
|
111
|
+
throw result.reason;
|
|
112
|
+
yield result.value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await settled;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
26
119
|
// Network faults that abort an in-flight stream (a dropped socket, a reset, a
|
|
27
120
|
// DNS blip). undici surfaces these mid-stream as `TypeError: fetch failed` or
|
|
28
121
|
// `TypeError: terminated`; some are also reachable via an errno-style code on
|
|
@@ -43,8 +136,11 @@ const retryableStreamCodes = new Set([
|
|
|
43
136
|
"UND_ERR_BODY_TIMEOUT",
|
|
44
137
|
"ARENA_STREAM_IDLE_TIMEOUT",
|
|
45
138
|
ANTHROPIC_INCOMPLETE_STREAM_ERROR_CODE,
|
|
46
|
-
|
|
47
|
-
|
|
139
|
+
CAPACITY_MODEL_ERROR,
|
|
140
|
+
TRANSIENT_MODEL_ERROR,
|
|
141
|
+
RESPONSES_INCOMPLETE_STREAM_ERROR_CODE,
|
|
142
|
+
"ARENA_EMPTY_MODEL_RESPONSE",
|
|
143
|
+
INCOMPLETE_MODEL_RESPONSE,
|
|
48
144
|
]);
|
|
49
145
|
const capacityErrorMaxRetries = 5;
|
|
50
146
|
function describeError(error) {
|
|
@@ -64,7 +160,7 @@ function isRetryableStreamError(error) {
|
|
|
64
160
|
return code !== undefined && retryableStreamCodes.has(code);
|
|
65
161
|
}
|
|
66
162
|
function streamRetryLimit(error, configuredLimit) {
|
|
67
|
-
return errorCode(error) ===
|
|
163
|
+
return errorCode(error) === CAPACITY_MODEL_ERROR
|
|
68
164
|
? Math.min(configuredLimit, capacityErrorMaxRetries)
|
|
69
165
|
: configuredLimit;
|
|
70
166
|
}
|
|
@@ -270,15 +366,20 @@ class CheckpointFailure extends Error {
|
|
|
270
366
|
this.cause = cause;
|
|
271
367
|
}
|
|
272
368
|
}
|
|
273
|
-
//
|
|
369
|
+
// Refresh live Git state on every run, using the saved target when available.
|
|
274
370
|
// The history argument is retained for callers; it never supplies runtime state.
|
|
275
|
-
export function resolveRepoContext(workspace, _history, mode = "arena") {
|
|
276
|
-
return isCodeArenaMode(mode) ? undefined : detectRepoContext(workspace);
|
|
371
|
+
export function resolveRepoContext(workspace, _history, mode = "arena", repoDirectory) {
|
|
372
|
+
return isCodeArenaMode(mode) ? undefined : detectRepoContext(workspace, repoDirectory);
|
|
277
373
|
}
|
|
278
374
|
export class Agent {
|
|
279
375
|
config;
|
|
280
376
|
sink;
|
|
281
377
|
options;
|
|
378
|
+
checkpointQueue = Promise.resolve();
|
|
379
|
+
restoredToolCalls = new Set();
|
|
380
|
+
checkpointError;
|
|
381
|
+
persistedMemory;
|
|
382
|
+
repoDirectory;
|
|
282
383
|
memoryCache;
|
|
283
384
|
client;
|
|
284
385
|
tools;
|
|
@@ -290,6 +391,7 @@ export class Agent {
|
|
|
290
391
|
this.sink = sink;
|
|
291
392
|
this.options = options;
|
|
292
393
|
this.memoryCache = options.sessionMemory || {};
|
|
394
|
+
this.repoDirectory = options.repoDirectory;
|
|
293
395
|
this.sessionId =
|
|
294
396
|
options.sessionId || crypto.randomUUID().replaceAll("-", "");
|
|
295
397
|
this.sessionAffinityId = options.sessionAffinityId || crypto.randomUUID();
|
|
@@ -305,19 +407,24 @@ export class Agent {
|
|
|
305
407
|
(!needsMainHttpClient && !needsFallbackHttpClient)
|
|
306
408
|
? undefined
|
|
307
409
|
: new JsonlModelCallDiagnostics(modelCallLogPath(config.stateRoot, config.tenantId, this.sessionId));
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
(
|
|
311
|
-
|
|
312
|
-
:
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
410
|
+
const client = options.client ||
|
|
411
|
+
(config.model.startsWith("mock/")
|
|
412
|
+
? new MockModelClient()
|
|
413
|
+
: new HttpModelClient(config, {
|
|
414
|
+
sessionAffinityId: this.sessionAffinityId,
|
|
415
|
+
onRequest: options.onModelRequest,
|
|
416
|
+
...(diagnostics ? { diagnostics } : {}),
|
|
417
|
+
}));
|
|
418
|
+
this.client = isCodeArenaMode(config.mode) ? client : {
|
|
419
|
+
stream: request => client.stream({ ...request,
|
|
420
|
+
messages: restoredArenaHistory(request.messages, this.restoredToolCalls) }),
|
|
421
|
+
};
|
|
316
422
|
const imageInputFallbackClient = options.imageInputFallbackClient ||
|
|
317
423
|
(fallbackConnection
|
|
318
424
|
? new HttpModelClient(config, {
|
|
319
425
|
connection: fallbackConnection,
|
|
320
426
|
sessionAffinityId: this.sessionAffinityId,
|
|
427
|
+
onRequest: options.onModelRequest,
|
|
321
428
|
...(diagnostics ? { diagnostics } : {}),
|
|
322
429
|
})
|
|
323
430
|
: undefined);
|
|
@@ -332,6 +439,16 @@ export class Agent {
|
|
|
332
439
|
});
|
|
333
440
|
}
|
|
334
441
|
async run(prompt, history, runOptions = {}) {
|
|
442
|
+
try {
|
|
443
|
+
return await this.runTurn(prompt, history, runOptions);
|
|
444
|
+
}
|
|
445
|
+
finally {
|
|
446
|
+
await this.tools.closeConnectors?.();
|
|
447
|
+
await settleSessionMemory(this.memoryCache);
|
|
448
|
+
await this.checkpointQueue;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async runTurn(prompt, history, runOptions) {
|
|
335
452
|
if (runOptions.resumeFromHistory && !history?.length) {
|
|
336
453
|
throw new Error("resumeFromHistory requires a non-empty history");
|
|
337
454
|
}
|
|
@@ -344,15 +461,25 @@ export class Agent {
|
|
|
344
461
|
? interruptedUserMessage(baseUserMessage, this.config.mode)
|
|
345
462
|
: structuredClone(baseUserMessage);
|
|
346
463
|
const clientContext = this.options.clientContext || defaultClientContext();
|
|
347
|
-
const repoContext = resolveRepoContext(this.config.workspace, history, this.config.mode);
|
|
464
|
+
const repoContext = resolveRepoContext(this.config.workspace, history, this.config.mode, this.repoDirectory);
|
|
465
|
+
if (repoContext) {
|
|
466
|
+
this.repoDirectory = path.relative(this.config.workspace, repoContext.checkoutPath);
|
|
467
|
+
}
|
|
348
468
|
// System instructions belong to this invocation, never to the saved dialogue.
|
|
349
469
|
// Older local histories included a system message; accept them without
|
|
350
470
|
// carrying the old runtime configuration into the next request.
|
|
351
|
-
const dialogue = withoutLeadingSystemMessage(history || []);
|
|
471
|
+
const dialogue = withoutLeadingSystemMessage(history || []).map((message) => normalizeToolCallHistory(message, this.config.mode));
|
|
472
|
+
this.restoredToolCalls.clear();
|
|
473
|
+
if (!isCodeArenaMode(this.config.mode))
|
|
474
|
+
rememberRestoredCalls(dialogue, this.restoredToolCalls);
|
|
352
475
|
const trajectory = completeInterruptedToolCalls(this.options.reuseHistoryMessages ? [...dialogue] : structuredClone(dialogue));
|
|
476
|
+
const connectors = this.config.disallowedTools?.includes("list_connector_tools")
|
|
477
|
+
? undefined : this.config.connectors;
|
|
478
|
+
const connectedConnectors = this.config.disallowedTools?.includes("list_connector_tools")
|
|
479
|
+
? undefined : this.config.connectedConnectors;
|
|
353
480
|
const runtimeSystemMessage = repoContext
|
|
354
|
-
? repoSystemMessage(clientContext, repoContext, this.config.mode, this.workspaceContext, this.config.maxTurns)
|
|
355
|
-
: systemMessage(clientContext, this.workspaceContext, this.config.mode, this.config.maxTurns);
|
|
481
|
+
? repoSystemMessage(clientContext, repoContext, this.config.mode, this.workspaceContext, this.config.maxTurns, connectors, connectedConnectors)
|
|
482
|
+
: systemMessage(clientContext, this.workspaceContext, this.config.mode, this.config.maxTurns, connectors, connectedConnectors);
|
|
356
483
|
applySystemPromptConfig(runtimeSystemMessage, this.config);
|
|
357
484
|
if (runOptions.resumeFromHistory) {
|
|
358
485
|
if (!isContinuableHistory(dialogue)) {
|
|
@@ -362,25 +489,65 @@ export class Agent {
|
|
|
362
489
|
else {
|
|
363
490
|
trajectory.push(userMessage);
|
|
364
491
|
}
|
|
492
|
+
// A new human reply requires discovery again. Same-turn continuation only
|
|
493
|
+
// reloads schemas from paired discovery results; it never repeats writes.
|
|
494
|
+
await this.tools.beginConnectorTurn?.(runOptions.resumeFromHistory ? dialogue : undefined);
|
|
365
495
|
// Compaction projects model input with a runtime system prefix; the lossless
|
|
366
496
|
// trajectory and its checkpoints remain in the official dialogue format.
|
|
367
497
|
const messages = modelContextFromTrajectory([runtimeSystemMessage, ...trajectory]);
|
|
498
|
+
if (!isCodeArenaMode(this.config.mode)) {
|
|
499
|
+
const saved = this.persistedMemory ?? this.options.storedSessionMemory ?? storedSessionMemory(this.memoryCache);
|
|
500
|
+
preflightUnsupportedMedia(this.config, messages);
|
|
501
|
+
// Opting out of restoration must not delete a still-valid durable snapshot.
|
|
502
|
+
this.persistedMemory = saved?.version === 1 ? structuredClone(saved) : undefined;
|
|
503
|
+
if (this.persistedMemory?.latest && !sessionMemoryMatchesHistory(this.persistedMemory.latest, messages)) {
|
|
504
|
+
this.persistedMemory.latest = undefined;
|
|
505
|
+
}
|
|
506
|
+
if (this.persistedMemory?.update?.status === "pending") {
|
|
507
|
+
this.persistedMemory.update = { status: "cancelled", code: "MEMORY_INTERRUPTED",
|
|
508
|
+
error: "Previous process ended during a memory update" };
|
|
509
|
+
}
|
|
510
|
+
this.memoryCache.latest = undefined;
|
|
511
|
+
this.memoryCache.state = undefined;
|
|
512
|
+
this.memoryCache.latestUsage = undefined;
|
|
513
|
+
this.memoryCache.attemptedPrefix = undefined;
|
|
514
|
+
this.memoryCache.update = undefined;
|
|
515
|
+
if (this.config.compactionMode !== "summary" &&
|
|
516
|
+
(runOptions.restoreSessionMemory ?? this.config.restoreSessionMemory ?? true) && saved?.version === 1) {
|
|
517
|
+
if (sessionMemoryMatchesHistory(saved.latest, messages)) {
|
|
518
|
+
this.memoryCache.latest = structuredClone(saved.latest);
|
|
519
|
+
}
|
|
520
|
+
this.memoryCache.update = this.persistedMemory?.update;
|
|
521
|
+
if (saved.latest && !this.memoryCache.latest) {
|
|
522
|
+
this.memoryCache.update = { status: "error", code: "MEMORY_STALE_HISTORY",
|
|
523
|
+
error: "Saved memory does not match the current history prefix" };
|
|
524
|
+
}
|
|
525
|
+
if (this.memoryCache.update)
|
|
526
|
+
this.sink.emit({ type: "memory.update", session_id: this.sessionId, ...this.memoryCache.update });
|
|
527
|
+
}
|
|
528
|
+
}
|
|
368
529
|
const recordMessage = async (message) => {
|
|
369
|
-
|
|
370
|
-
|
|
530
|
+
const normalized = normalizeToolCallHistory(message, this.config.mode, call => this.tools.historyCall(call));
|
|
531
|
+
messages.push(normalized);
|
|
532
|
+
const recorded = structuredClone(normalized);
|
|
371
533
|
trajectory.push(recorded);
|
|
372
534
|
this.sink.message(this.sessionId, recorded, trajectory);
|
|
373
535
|
await this.checkpoint(trajectory);
|
|
374
536
|
};
|
|
375
|
-
await this.maybeCompact(messages, trajectory);
|
|
376
|
-
this.sink.start(this.sessionId, trajectory);
|
|
377
537
|
try {
|
|
538
|
+
const compaction = await this.maybeCompact(messages, trajectory);
|
|
539
|
+
this.sink.start(this.sessionId, trajectory);
|
|
540
|
+
if (compaction === "checkpoint")
|
|
541
|
+
return await this.pause(trajectory, "checkpoint");
|
|
378
542
|
await this.checkpoint(trajectory);
|
|
379
543
|
const maxTurns = this.config.maxTurns
|
|
380
544
|
? Math.max(1, Math.floor(this.config.maxTurns))
|
|
381
|
-
: undefined;
|
|
545
|
+
: isCodeArenaMode(this.config.mode) ? undefined : defaultStepLimit("arena");
|
|
382
546
|
for (let step = 1;; step += 1) {
|
|
383
547
|
if (maxTurns !== undefined && step > maxTurns) {
|
|
548
|
+
if (!isCodeArenaMode(this.config.mode)) {
|
|
549
|
+
return await this.pause(trajectory, "step_limit");
|
|
550
|
+
}
|
|
384
551
|
throw new Error(`agent reached max_turns limit (${maxTurns})`);
|
|
385
552
|
}
|
|
386
553
|
if (this.options.signal?.aborted) {
|
|
@@ -389,13 +556,15 @@ export class Agent {
|
|
|
389
556
|
// Every transport is replayable from local history. Native Responses
|
|
390
557
|
// stores its encrypted reasoning output items there, so compaction is
|
|
391
558
|
// safe at the same tool-step boundaries as the other protocols.
|
|
392
|
-
if (step > 1)
|
|
393
|
-
await this.
|
|
559
|
+
if (step > 1 && await this.maybeCompact(messages, trajectory, "inner") === "checkpoint") {
|
|
560
|
+
return await this.pause(trajectory, "checkpoint");
|
|
561
|
+
}
|
|
394
562
|
const offeredTools = this.tools.availableTools();
|
|
395
563
|
this.sink.modelStart(this.sessionId, step, offeredTools.map((tool) => tool.function.name));
|
|
396
564
|
let assistant;
|
|
565
|
+
let finishReason;
|
|
397
566
|
try {
|
|
398
|
-
assistant = await this.assistantTurn(messages, offeredTools, trajectory);
|
|
567
|
+
({ assistant, finishReason } = await this.assistantTurn(messages, offeredTools, trajectory));
|
|
399
568
|
}
|
|
400
569
|
catch (error) {
|
|
401
570
|
if (error instanceof CheckpointExit) {
|
|
@@ -415,8 +584,19 @@ export class Agent {
|
|
|
415
584
|
this.options.onModelFailure?.(trajectory, error);
|
|
416
585
|
throw error;
|
|
417
586
|
}
|
|
418
|
-
const finishReason = assistant.tool_calls?.length ? "tool_calls" : "stop";
|
|
419
587
|
this.sink.modelComplete(this.sessionId, step, finishReason, assistant.usage);
|
|
588
|
+
if (finishReason === "length") {
|
|
589
|
+
// A completed but truncated request is not a transport failure. Keep
|
|
590
|
+
// its evidence without retrying it or executing unfinished tool calls.
|
|
591
|
+
await recordMessage(assistant);
|
|
592
|
+
throw new Error("Model response was truncated (finish_reason=length). " +
|
|
593
|
+
"The turn did not complete; any partial output has been preserved.");
|
|
594
|
+
}
|
|
595
|
+
if (finishReason === "incomplete" || finishReason === "content_filter") {
|
|
596
|
+
await recordMessage(assistant);
|
|
597
|
+
throw new Error(`Model response was incomplete (finish_reason=${finishReason}). ` +
|
|
598
|
+
"The turn did not complete; any partial output has been preserved.");
|
|
599
|
+
}
|
|
420
600
|
if (assistant.tool_calls?.length &&
|
|
421
601
|
this.options.midturnCheckpointHook) {
|
|
422
602
|
const event = {
|
|
@@ -434,49 +614,65 @@ export class Agent {
|
|
|
434
614
|
}
|
|
435
615
|
await recordMessage(assistant);
|
|
436
616
|
if (!assistant.tool_calls?.length) {
|
|
437
|
-
return this.
|
|
617
|
+
return await this.finish(trajectory, "success");
|
|
438
618
|
}
|
|
439
619
|
const completedToolCallIds = [];
|
|
440
|
-
|
|
620
|
+
let needsInput = false;
|
|
621
|
+
const execute = async (call) => {
|
|
441
622
|
const toolCallId = String(call.id || "");
|
|
442
623
|
const toolName = String(call.function?.name || "tool");
|
|
443
624
|
this.sink.toolStart(this.sessionId, toolCallId, toolName);
|
|
444
|
-
|
|
625
|
+
return this.tools.execute(call, toolName === "ask_user" || toolName === "add_voice" ||
|
|
626
|
+
(!isCodeArenaMode(this.config.mode) && toolName === "generate_image")
|
|
445
627
|
? {
|
|
446
628
|
sessionId: this.sessionId,
|
|
447
629
|
messages: structuredClone(trajectory),
|
|
448
630
|
signal: this.options.signal,
|
|
449
631
|
}
|
|
450
632
|
: undefined);
|
|
633
|
+
};
|
|
634
|
+
for await (const { call, message: toolMessage } of completedTools(assistant.tool_calls, !isCodeArenaMode(this.config.mode), execute)) {
|
|
635
|
+
const toolCallId = String(call.id || "");
|
|
636
|
+
const toolName = String(call.function?.name || "tool");
|
|
451
637
|
const toolResult = parseToolResult(toolMessage.content);
|
|
452
638
|
const skippedAskUser = toolName === "ask_user" && toolResult.skipped === true;
|
|
453
639
|
if (this.options.signal?.aborted && !skippedAskUser) {
|
|
454
640
|
throw this.options.signal.reason || new Error("operation cancelled");
|
|
455
641
|
}
|
|
456
642
|
await recordMessage(toolMessage);
|
|
643
|
+
if (this.tools.takeInteractionBoundary(toolCallId)) {
|
|
644
|
+
rememberRestoredCalls(messages, this.restoredToolCalls);
|
|
645
|
+
}
|
|
457
646
|
completedToolCallIds.push(toolCallId);
|
|
458
647
|
this.sink.toolComplete(this.sessionId, toolCallId, toolName);
|
|
459
|
-
if (toolName === "ask_user" || toolName === "add_voice"
|
|
648
|
+
if (toolName === "ask_user" || toolName === "add_voice" ||
|
|
649
|
+
(!isCodeArenaMode(this.config.mode) && toolName === "generate_image")) {
|
|
460
650
|
if (toolResult.status === "needs_input") {
|
|
461
|
-
const visualMessage = this.tools.takeVisualMessage(completedToolCallIds);
|
|
462
|
-
if (visualMessage) {
|
|
463
|
-
await recordMessage(visualMessage);
|
|
464
|
-
}
|
|
465
651
|
const questions = Array.isArray(toolResult.questions)
|
|
466
652
|
? toolResult.questions
|
|
467
653
|
: [];
|
|
468
654
|
this.sink.inputRequired(this.sessionId, toolCallId, questions);
|
|
469
|
-
|
|
655
|
+
if (isCodeArenaMode(this.config.mode)) {
|
|
656
|
+
const visualMessage = this.tools.takeVisualMessage(completedToolCallIds);
|
|
657
|
+
if (visualMessage)
|
|
658
|
+
await recordMessage(visualMessage);
|
|
659
|
+
return await this.finish(trajectory, "needs_input");
|
|
660
|
+
}
|
|
661
|
+
needsInput = true;
|
|
470
662
|
}
|
|
471
663
|
}
|
|
472
664
|
if (this.options.signal?.aborted) {
|
|
473
665
|
throw this.options.signal.reason || new Error("operation cancelled");
|
|
474
666
|
}
|
|
475
667
|
}
|
|
476
|
-
|
|
668
|
+
// Image parts retain the assistant's declaration order independently
|
|
669
|
+
// of which tool result completed first.
|
|
670
|
+
const visualMessage = this.tools.takeVisualMessage(assistant.tool_calls.map(call => call.id));
|
|
477
671
|
if (visualMessage) {
|
|
478
672
|
await recordMessage(visualMessage);
|
|
479
673
|
}
|
|
674
|
+
if (needsInput)
|
|
675
|
+
return await this.finish(trajectory, "needs_input");
|
|
480
676
|
if (runOptions.singleStep) {
|
|
481
677
|
return await this.pause(trajectory, "single_step");
|
|
482
678
|
}
|
|
@@ -484,13 +680,38 @@ export class Agent {
|
|
|
484
680
|
}
|
|
485
681
|
catch (error) {
|
|
486
682
|
const message = error instanceof Error ? error.message : String(error);
|
|
487
|
-
return this.
|
|
683
|
+
return await this.finish(trajectory, "error", message);
|
|
488
684
|
}
|
|
489
685
|
}
|
|
490
686
|
async checkpoint(messages) {
|
|
491
|
-
|
|
687
|
+
const snapshot = [...messages];
|
|
688
|
+
const tools = this.tools.availableTools();
|
|
689
|
+
const currentMemory = storedSessionMemory(this.memoryCache);
|
|
690
|
+
const memory = isCodeArenaMode(this.config.mode) ? undefined : (currentMemory || this.persistedMemory
|
|
691
|
+
? { version: 1, latest: currentMemory?.latest ?? this.persistedMemory?.latest,
|
|
692
|
+
update: currentMemory?.update ?? this.persistedMemory?.update }
|
|
693
|
+
: null);
|
|
694
|
+
if (memory !== undefined)
|
|
695
|
+
this.persistedMemory = memory ?? undefined;
|
|
696
|
+
const write = this.checkpointQueue.then(() => this.options.onCheckpoint?.(snapshot, tools, memory));
|
|
697
|
+
this.checkpointQueue = write.catch(error => { this.checkpointError ??= error; });
|
|
698
|
+
await write;
|
|
699
|
+
if (this.checkpointError)
|
|
700
|
+
throw this.checkpointError;
|
|
701
|
+
}
|
|
702
|
+
async finish(messages, status, error) {
|
|
703
|
+
await settleSessionMemory(this.memoryCache);
|
|
704
|
+
try {
|
|
705
|
+
await this.checkpoint(messages);
|
|
706
|
+
}
|
|
707
|
+
catch (failure) {
|
|
708
|
+
status = "error";
|
|
709
|
+
error ??= failure instanceof Error ? failure.message : String(failure);
|
|
710
|
+
}
|
|
711
|
+
return this.sink.result(this.sessionId, status, messages, error, undefined, this.memoryCache.update);
|
|
492
712
|
}
|
|
493
713
|
async makeCheckpoint(messages, tools, event, action) {
|
|
714
|
+
await settleSessionMemory(this.memoryCache, event.type !== "before_compaction" || action !== "snapshot_continue");
|
|
494
715
|
await this.checkpoint(messages);
|
|
495
716
|
if (!this.options.onMidturnCheckpoint) {
|
|
496
717
|
throw new Error("mid-turn checkpoint hook triggered without a snapshot handler");
|
|
@@ -499,9 +720,12 @@ export class Agent {
|
|
|
499
720
|
}
|
|
500
721
|
async pause(messages, reason) {
|
|
501
722
|
this.options.signal?.throwIfAborted();
|
|
723
|
+
await settleSessionMemory(this.memoryCache, reason !== "single_step");
|
|
724
|
+
if (reason === "single_step" || reason === "step_limit")
|
|
725
|
+
await this.checkpoint(messages);
|
|
502
726
|
await this.options.onPause?.(messages, this.tools.availableTools());
|
|
503
727
|
this.options.signal?.throwIfAborted();
|
|
504
|
-
return this.sink.result(this.sessionId, "paused", messages, undefined, reason);
|
|
728
|
+
return this.sink.result(this.sessionId, "paused", messages, undefined, reason, this.memoryCache.update);
|
|
505
729
|
}
|
|
506
730
|
async maybeCompact(messages, trajectory, source = "outer") {
|
|
507
731
|
preflightUnsupportedMedia(this.config, messages);
|
|
@@ -511,50 +735,83 @@ export class Agent {
|
|
|
511
735
|
triggerRatio: this.config.contextCompactionRatio,
|
|
512
736
|
summaryMaxTokens: this.config.compactionSummaryMaxTokens,
|
|
513
737
|
reasoningEffort: this.config.reasoningEffort,
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
738
|
+
compactionThresholdTokens: isCodeArenaMode(this.config.mode) ? undefined
|
|
739
|
+
: agentArenaCompactionThreshold(this.config.contextWindow, this.config.contextCompactionRatio, this.options.compactionThresholdTokens ?? this.config.compactionThresholdTokens),
|
|
740
|
+
legacy: isCodeArenaMode(this.config.mode) || this.config.compactionMode === "summary",
|
|
741
|
+
onBeforeCompaction: !isCodeArenaMode(this.config.mode) && this.options.midturnCheckpointHook
|
|
742
|
+
? async (estimatedTokensBefore, thresholdTokens) => {
|
|
743
|
+
const event = {
|
|
744
|
+
type: "before_compaction", sessionId: this.sessionId, toolCalls: [],
|
|
745
|
+
compaction: {
|
|
746
|
+
count: trajectory.filter(message => message.role === "compaction" && !message.undone).length + 1,
|
|
747
|
+
mode: this.config.compactionMode === "summary" ? "summary" : "rolling",
|
|
748
|
+
source, estimatedTokensBefore, thresholdTokens,
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
let action;
|
|
752
|
+
try {
|
|
753
|
+
action = checkpointAction(await this.options.midturnCheckpointHook(event));
|
|
754
|
+
if (action !== "continue") {
|
|
755
|
+
await this.makeCheckpoint(trajectory, this.tools.availableTools(), event, action);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
throw new CheckpointFailure(error);
|
|
760
|
+
}
|
|
761
|
+
if (action === "snapshot_exit")
|
|
762
|
+
throw new CheckpointExit(event);
|
|
763
|
+
}
|
|
764
|
+
: undefined,
|
|
518
765
|
};
|
|
519
766
|
const contextLength = messages.length;
|
|
520
767
|
const result = settings.legacy
|
|
521
768
|
? await compactMessagesIfNeeded(messages, this.tools.availableTools(), this.client, this.config.model, settings, this.options.signal)
|
|
522
|
-
: await maintainSessionMemory(messages, messages, this.memoryCache, this.tools.availableTools(), this.client, this.config.model, settings, this.config.memoryUpdateTokens, this.options.signal,
|
|
523
|
-
|
|
769
|
+
: await maintainSessionMemory(messages, messages, this.memoryCache, this.tools.availableTools(), this.client, this.config.model, settings, this.config.memoryUpdateTokens, this.options.signal, update => {
|
|
770
|
+
this.sink.emit({ type: "memory.update", session_id: this.sessionId, ...update });
|
|
771
|
+
// Serialized with message checkpoints; persistence failures surface at
|
|
772
|
+
// the next checkpoint/finish instead of becoming unhandled rejections.
|
|
773
|
+
return this.checkpoint(trajectory).catch(() => undefined);
|
|
774
|
+
});
|
|
524
775
|
if (result.compacted) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
776
|
+
const covered = settings.legacy ? result.coveredMessages : this.memoryCache.state.coveredMessages;
|
|
777
|
+
const retained = contextLength - covered;
|
|
778
|
+
// Insert at the covered boundary, before the retained original tail.
|
|
779
|
+
// Marker position is the resume boundary; no separate index is stored.
|
|
780
|
+
let insertion = trajectory.length;
|
|
781
|
+
let remaining = retained;
|
|
782
|
+
while (remaining > 0 && insertion > 0) {
|
|
783
|
+
if (trajectory[--insertion].role !== "compaction")
|
|
784
|
+
remaining--;
|
|
785
|
+
}
|
|
786
|
+
trajectory.splice(insertion, 0, {
|
|
787
|
+
role: "compaction", content: [{ type: "text", text: result.memory }],
|
|
788
|
+
undone: false,
|
|
789
|
+
compaction: {
|
|
790
|
+
ratio: result.estimatedTokensBefore / this.config.contextWindow,
|
|
791
|
+
tokens_at_compaction: result.estimatedTokensBefore,
|
|
792
|
+
source,
|
|
793
|
+
retained_messages: retained,
|
|
794
|
+
},
|
|
795
|
+
});
|
|
796
|
+
if (!isCodeArenaMode(this.config.mode)) {
|
|
545
797
|
// Coverage indices referred to the pre-compaction history. The next
|
|
546
798
|
// update reads its prior memory from the newly embedded block.
|
|
547
799
|
this.memoryCache.state = undefined;
|
|
548
800
|
this.memoryCache.latest = undefined;
|
|
549
801
|
this.memoryCache.latestUsage = undefined;
|
|
550
802
|
this.memoryCache.attemptedPrefix = undefined;
|
|
803
|
+
this.persistedMemory = undefined;
|
|
551
804
|
}
|
|
552
805
|
await this.checkpoint(trajectory);
|
|
553
806
|
this.sink.compaction(this.sessionId, result.usage);
|
|
554
807
|
}
|
|
555
808
|
return result.compacted;
|
|
556
809
|
}
|
|
557
|
-
catch {
|
|
810
|
+
catch (error) {
|
|
811
|
+
if (error instanceof CheckpointExit)
|
|
812
|
+
return "checkpoint";
|
|
813
|
+
if (error instanceof CheckpointFailure)
|
|
814
|
+
throw error.cause;
|
|
558
815
|
// Compaction is protective rather than task-critical. If its dedicated
|
|
559
816
|
// summary request fails, keep the original history and let the normal
|
|
560
817
|
// model request (and its retry policy) decide whether it still fits.
|
|
@@ -581,6 +838,7 @@ export class Agent {
|
|
|
581
838
|
const calls = new Map();
|
|
582
839
|
const responseItems = new Map();
|
|
583
840
|
let usage;
|
|
841
|
+
let finishReason;
|
|
584
842
|
let reasoningDetails;
|
|
585
843
|
const detectors = this.options.midturnCheckpointHook ? {
|
|
586
844
|
content: new RepetitiveOutputDetector(),
|
|
@@ -649,6 +907,9 @@ export class Agent {
|
|
|
649
907
|
// letting the last non-empty value win.
|
|
650
908
|
if (chunk.usage)
|
|
651
909
|
usage = chunk.usage;
|
|
910
|
+
if (chunk.choices?.[0]?.finish_reason) {
|
|
911
|
+
finishReason = chunk.choices[0].finish_reason;
|
|
912
|
+
}
|
|
652
913
|
if (chunk.reasoning_details?.length) {
|
|
653
914
|
reasoningDetails = chunk.reasoning_details;
|
|
654
915
|
}
|
|
@@ -687,6 +948,11 @@ export class Agent {
|
|
|
687
948
|
if (fragment.function?.arguments) {
|
|
688
949
|
call.function.arguments += String(fragment.function.arguments);
|
|
689
950
|
}
|
|
951
|
+
// Metadata is an opaque value for this index, not a text delta.
|
|
952
|
+
// Later fragments that omit it must not erase an earlier signature.
|
|
953
|
+
if (fragment.extra_content !== undefined) {
|
|
954
|
+
call.extra_content = structuredClone(fragment.extra_content);
|
|
955
|
+
}
|
|
690
956
|
calls.set(index, call);
|
|
691
957
|
this.sink.delta(this.sessionId, {
|
|
692
958
|
tool_calls: [
|
|
@@ -698,11 +964,23 @@ export class Agent {
|
|
|
698
964
|
name: String(fragment.function?.name || ""),
|
|
699
965
|
arguments: String(fragment.function?.arguments || ""),
|
|
700
966
|
},
|
|
967
|
+
...(fragment.extra_content !== undefined
|
|
968
|
+
? { extra_content: structuredClone(fragment.extra_content) }
|
|
969
|
+
: {}),
|
|
701
970
|
},
|
|
702
971
|
],
|
|
703
972
|
});
|
|
704
973
|
}
|
|
705
974
|
}
|
|
975
|
+
if (finishReason === "tool_calls" && !calls.size) {
|
|
976
|
+
throw incompleteModelResponse("Model stopped for tool calls but returned no tool calls");
|
|
977
|
+
}
|
|
978
|
+
if (finishReason !== "length" && finishReason !== "incomplete" && finishReason !== "content_filter" &&
|
|
979
|
+
!calls.size && !contentParts.join("").trim()) {
|
|
980
|
+
throw Object.assign(new Error("Model returned an empty response without tool calls"), {
|
|
981
|
+
code: "ARENA_EMPTY_MODEL_RESPONSE",
|
|
982
|
+
});
|
|
983
|
+
}
|
|
706
984
|
}
|
|
707
985
|
catch (error) {
|
|
708
986
|
// Iterator cleanup may itself report the intentional transport abort.
|
|
@@ -734,7 +1012,7 @@ export class Agent {
|
|
|
734
1012
|
const openaiResponseItems = [...responseItems.entries()]
|
|
735
1013
|
.sort(([left], [right]) => left - right)
|
|
736
1014
|
.map(([, item]) => item);
|
|
737
|
-
const partial = content || reasoningContent || openaiResponseItems.length
|
|
1015
|
+
const partial = content || reasoningContent || openaiResponseItems.length || usage
|
|
738
1016
|
? {
|
|
739
1017
|
role: "assistant",
|
|
740
1018
|
content,
|
|
@@ -770,7 +1048,10 @@ export class Agent {
|
|
|
770
1048
|
.sort(([left], [right]) => left - right)
|
|
771
1049
|
.map(([, call]) => call);
|
|
772
1050
|
}
|
|
773
|
-
return
|
|
1051
|
+
return {
|
|
1052
|
+
assistant,
|
|
1053
|
+
finishReason: finishReason ?? (assistant.tool_calls?.length ? "tool_calls" : "stop"),
|
|
1054
|
+
};
|
|
774
1055
|
}
|
|
775
1056
|
}
|
|
776
1057
|
async retryBackoff(attempt) {
|