@multiplayer-app/agent 0.1.19 → 0.1.20
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/agent/definition.d.ts +13 -0
- package/dist/agent/definition.d.ts.map +1 -1
- package/dist/agent/profiles/research.d.ts.map +1 -1
- package/dist/agent/profiles/research.js +23 -1
- package/dist/agent/profiles/research.js.map +1 -1
- package/dist/agent/turn-history.d.ts +40 -0
- package/dist/agent/turn-history.d.ts.map +1 -0
- package/dist/agent/turn-history.js +161 -0
- package/dist/agent/turn-history.js.map +1 -0
- package/dist/agent/turn-runner.d.ts +14 -1
- package/dist/agent/turn-runner.d.ts.map +1 -1
- package/dist/agent/turn-runner.js +139 -81
- package/dist/agent/turn-runner.js.map +1 -1
- package/dist/context-pruning.d.ts +2 -0
- package/dist/context-pruning.d.ts.map +1 -1
- package/dist/context-pruning.js +3 -2
- package/dist/context-pruning.js.map +1 -1
- package/dist/models/usage.d.ts +6 -0
- package/dist/models/usage.d.ts.map +1 -1
- package/dist/models/usage.js +13 -0
- package/dist/models/usage.js.map +1 -1
- package/dist/research/extract-texts.d.ts.map +1 -1
- package/dist/research/extract-texts.js +28 -3
- package/dist/research/extract-texts.js.map +1 -1
- package/dist/research/prompt.d.ts.map +1 -1
- package/dist/research/prompt.js +2 -1
- package/dist/research/prompt.js.map +1 -1
- package/dist/session/conversation.d.ts +7 -0
- package/dist/session/conversation.d.ts.map +1 -1
- package/dist/session/conversation.js +29 -5
- package/dist/session/conversation.js.map +1 -1
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +2 -1
- package/dist/session/index.js.map +1 -1
- package/dist/session/runtime.d.ts +4 -0
- package/dist/session/runtime.d.ts.map +1 -1
- package/dist/session/runtime.js +18 -4
- package/dist/session/runtime.js.map +1 -1
- package/dist/session/tools.d.ts.map +1 -1
- package/dist/session/tools.js +2 -1
- package/dist/session/tools.js.map +1 -1
- package/package.json +1 -1
|
@@ -7,16 +7,29 @@ export interface AgentModel {
|
|
|
7
7
|
provider: ModelProviderConfig;
|
|
8
8
|
attribution: OpenRouterApp;
|
|
9
9
|
}
|
|
10
|
+
/** The agent's own running notes: what compaction carries forward in place of the steps it removes. */
|
|
11
|
+
export interface WorkingNotes {
|
|
12
|
+
/** Workspace-relative path, named to the agent in the compaction note. */
|
|
13
|
+
path: string;
|
|
14
|
+
(workspaceDir: string): Promise<string | undefined>;
|
|
15
|
+
}
|
|
10
16
|
/** Profile-specific behavior. Definitions have no queue, conversation, or checkpoint state. */
|
|
11
17
|
export interface AgentDefinition {
|
|
12
18
|
profile: AgentProfile;
|
|
13
19
|
model: AgentModel;
|
|
20
|
+
/** Steps per model loop. A turn ends here unless `maxTurnSteps` lets it continue in a new loop. */
|
|
14
21
|
maxSteps: number;
|
|
22
|
+
/**
|
|
23
|
+
* Steps a turn may take in all, continuing past `maxSteps` while the model still wants to work.
|
|
24
|
+
* Unset, a turn is one loop of `maxSteps`. The token budget bounds the spending either way.
|
|
25
|
+
*/
|
|
26
|
+
maxTurnSteps?: number;
|
|
15
27
|
workspaceDirectories: readonly string[];
|
|
16
28
|
instructions(context: {
|
|
17
29
|
restored: boolean;
|
|
18
30
|
tokenBudget?: number;
|
|
19
31
|
}): string;
|
|
20
32
|
createTools(context: SessionToolContext): Record<string, Tool>;
|
|
33
|
+
workingNotes?: WorkingNotes;
|
|
21
34
|
}
|
|
22
35
|
//# sourceMappingURL=definition.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/agent/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,WAAW,EAAE,aAAa,CAAA;CAC3B;AAED,+FAA+F;AAC/F,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,YAAY,CAAA;IACrB,KAAK,EAAE,UAAU,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,YAAY,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAA;IAC1E,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"definition.d.ts","sourceRoot":"","sources":["../../src/agent/definition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,WAAW,EAAE,aAAa,CAAA;CAC3B;AAED,uGAAuG;AACvG,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CACpD;AAED,+FAA+F;AAC/F,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,YAAY,CAAA;IACrB,KAAK,EAAE,UAAU,CAAA;IACjB,mGAAmG;IACnG,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,YAAY,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAA;IAC1E,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAC9D,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"research.d.ts","sourceRoot":"","sources":["../../../src/agent/profiles/research.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"research.d.ts","sourceRoot":"","sources":["../../../src/agent/profiles/research.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,eAAe,CAAA;AAElE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AA0B9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,GAAG,eAAe,CA2CrG"}
|
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.createResearchAgent = createResearchAgent;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
4
9
|
const attribution_1 = require("../../models/attribution");
|
|
5
10
|
const extraction_tool_1 = require("../../research/extraction-tool");
|
|
6
11
|
const filingramp_client_1 = require("../../research/filingramp-client");
|
|
7
12
|
const filingramp_tools_1 = require("../../research/filingramp-tools");
|
|
8
13
|
const prompt_1 = require("../../research/prompt");
|
|
9
14
|
const tools_1 = require("../../session/tools");
|
|
15
|
+
/** Steps per model loop; past it the turn continues in a new loop (see AgentDefinition.maxTurnSteps). */
|
|
16
|
+
const STEPS_PER_LOOP = 80;
|
|
17
|
+
/** A research turn ends on its own or at the budget; this is the guard against a model that never does. */
|
|
18
|
+
const MAX_TURN_STEPS = 480;
|
|
19
|
+
/** The notes file the prompt tells the agent to keep; compaction carries it in place of removed steps. */
|
|
20
|
+
const workingNotes = Object.assign(async (workspaceDir) => {
|
|
21
|
+
try {
|
|
22
|
+
return await node_fs_1.promises.readFile(node_path_1.default.join(workspaceDir, prompt_1.RESEARCH_OUTPUT_FILES.notes), 'utf8');
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error.code === 'ENOENT')
|
|
26
|
+
return undefined;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}, { path: prompt_1.RESEARCH_OUTPUT_FILES.notes });
|
|
10
30
|
function createResearchAgent(config, workspaceDir) {
|
|
11
31
|
// Keep authentication and resolved scope labels across turns. These tools do not own turn state:
|
|
12
32
|
// bulk progress goes to whichever turn is running, through the slot createTools fills each turn.
|
|
@@ -25,8 +45,10 @@ function createResearchAgent(config, workspaceDir) {
|
|
|
25
45
|
return {
|
|
26
46
|
profile: 'research',
|
|
27
47
|
model: { id: config.model, provider: config.provider, attribution: attribution_1.OPENROUTER_APPS.DEEP_RESEARCH },
|
|
28
|
-
maxSteps:
|
|
48
|
+
maxSteps: STEPS_PER_LOOP,
|
|
49
|
+
maxTurnSteps: MAX_TURN_STEPS,
|
|
29
50
|
workspaceDirectories: prompt_1.RESEARCH_WORKSPACE_DIRS,
|
|
51
|
+
workingNotes,
|
|
30
52
|
instructions: ({ restored, tokenBudget }) => {
|
|
31
53
|
var _a;
|
|
32
54
|
return (0, prompt_1.buildResearchSystemPrompt)({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"research.js","sourceRoot":"","sources":["../../../src/agent/profiles/research.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"research.js","sourceRoot":"","sources":["../../../src/agent/profiles/research.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AssistantModelMessage, ModelMessage, ToolModelMessage, UserModelMessage } from 'ai';
|
|
2
|
+
/** One model step: the assistant message and the tool results it produced, if any. */
|
|
3
|
+
export interface TurnStep {
|
|
4
|
+
assistant: AssistantModelMessage;
|
|
5
|
+
tool?: ToolModelMessage;
|
|
6
|
+
}
|
|
7
|
+
export declare function approxChars(message: ModelMessage): number;
|
|
8
|
+
/** Groups a turn's response messages into steps; anything that is not assistant or tool is dropped. */
|
|
9
|
+
export declare function splitSteps(messages: ModelMessage[]): TurnStep[];
|
|
10
|
+
export declare function flattenSteps(steps: TurnStep[]): ModelMessage[];
|
|
11
|
+
/** A deterministic one-paragraph account of steps: tool counts, questions asked, files touched. */
|
|
12
|
+
export declare function describeSteps(steps: TurnStep[]): string;
|
|
13
|
+
/** The text the turn ended with, when it ended on its own (a final step without tool calls). */
|
|
14
|
+
export declare function finalText(steps: TurnStep[]): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* A finished (or interrupted) turn as one assistant message for the turns that follow. The final text is
|
|
17
|
+
* what the agent told the user; the account says what it did, so it can reuse its files instead of
|
|
18
|
+
* redoing the work. Tool calls and results are left out: they are paid for again on every later step
|
|
19
|
+
* and the workspace holds what they produced.
|
|
20
|
+
*/
|
|
21
|
+
export declare function collapseTurn(messages: ModelMessage[]): AssistantModelMessage;
|
|
22
|
+
export interface CompactionOptions {
|
|
23
|
+
/** Compact once the turn's messages (after result pruning) exceed this many characters. */
|
|
24
|
+
triggerChars: number;
|
|
25
|
+
/** Keep the most recent steps up to about this many characters; everything older is replaced by the note. */
|
|
26
|
+
keepChars: number;
|
|
27
|
+
}
|
|
28
|
+
/** chars ≈ tokens × 4: compact past ~80k tokens of turn, keep ~30k tokens of recent steps. */
|
|
29
|
+
export declare const DEFAULT_COMPACTION: CompactionOptions;
|
|
30
|
+
export interface CompactionState {
|
|
31
|
+
/** Leading steps of the turn replaced by `note`. */
|
|
32
|
+
droppedSteps: number;
|
|
33
|
+
note?: UserModelMessage;
|
|
34
|
+
}
|
|
35
|
+
/** Compaction is monotonic: once steps are dropped they stay dropped, so the cached prefix only changes when more are. */
|
|
36
|
+
export declare function stepsToDrop(steps: TurnStep[], state: CompactionState, options: CompactionOptions): number | undefined;
|
|
37
|
+
export declare function compactionNote(dropped: TurnStep[], notesPath: string | undefined, notes: string | undefined): UserModelMessage;
|
|
38
|
+
/** The turn as the model sees it: the compaction note (if any) followed by the steps not yet dropped. */
|
|
39
|
+
export declare function compactedView(steps: TurnStep[], state: CompactionState): ModelMessage[];
|
|
40
|
+
//# sourceMappingURL=turn-history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-history.d.ts","sourceRoot":"","sources":["../../src/agent/turn-history.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,IAAI,CAAA;AAGjG,sFAAsF;AACtF,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,qBAAqB,CAAA;IAChC,IAAI,CAAC,EAAE,gBAAgB,CAAA;CACxB;AAOD,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAEzD;AAED,uGAAuG;AACvG,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,QAAQ,EAAE,CAO/D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,YAAY,EAAE,CAE9D;AAuCD,mGAAmG;AACnG,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAiBvD;AAED,gGAAgG;AAChG,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,GAAG,SAAS,CAK/D;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,qBAAqB,CAU5E;AAID,MAAM,WAAW,iBAAiB;IAChC,2FAA2F;IAC3F,YAAY,EAAE,MAAM,CAAA;IACpB,6GAA6G;IAC7G,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,8FAA8F;AAC9F,eAAO,MAAM,kBAAkB,EAAE,iBAAiE,CAAA;AAKlG,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,gBAAgB,CAAA;CACxB;AAED,0HAA0H;AAC1H,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS,CAWrH;AAID,wBAAgB,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAW9H;AAED,yGAAyG;AACzG,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,eAAe,GAAG,YAAY,EAAE,CAGvF"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_COMPACTION = void 0;
|
|
4
|
+
exports.approxChars = approxChars;
|
|
5
|
+
exports.splitSteps = splitSteps;
|
|
6
|
+
exports.flattenSteps = flattenSteps;
|
|
7
|
+
exports.describeSteps = describeSteps;
|
|
8
|
+
exports.finalText = finalText;
|
|
9
|
+
exports.collapseTurn = collapseTurn;
|
|
10
|
+
exports.stepsToDrop = stepsToDrop;
|
|
11
|
+
exports.compactionNote = compactionNote;
|
|
12
|
+
exports.compactedView = compactedView;
|
|
13
|
+
const context_pruning_1 = require("../context-pruning");
|
|
14
|
+
function approxChars(message) {
|
|
15
|
+
return JSON.stringify(message).length;
|
|
16
|
+
}
|
|
17
|
+
/** Groups a turn's response messages into steps; anything that is not assistant or tool is dropped. */
|
|
18
|
+
function splitSteps(messages) {
|
|
19
|
+
const steps = [];
|
|
20
|
+
for (const message of messages) {
|
|
21
|
+
if (message.role === 'assistant')
|
|
22
|
+
steps.push({ assistant: message });
|
|
23
|
+
else if (message.role === 'tool' && steps.length && !steps[steps.length - 1].tool)
|
|
24
|
+
steps[steps.length - 1].tool = message;
|
|
25
|
+
}
|
|
26
|
+
return steps;
|
|
27
|
+
}
|
|
28
|
+
function flattenSteps(steps) {
|
|
29
|
+
return steps.flatMap((step) => (step.tool ? [step.assistant, step.tool] : [step.assistant]));
|
|
30
|
+
}
|
|
31
|
+
function toolCallsOf(step) {
|
|
32
|
+
if (typeof step.assistant.content === 'string')
|
|
33
|
+
return [];
|
|
34
|
+
return step.assistant.content.flatMap((part) => (part.type === 'tool-call' ? [{ toolName: part.toolName, input: part.input }] : []));
|
|
35
|
+
}
|
|
36
|
+
function textOf(message) {
|
|
37
|
+
if (typeof message.content === 'string')
|
|
38
|
+
return message.content;
|
|
39
|
+
return message.content.flatMap((part) => (part.type === 'text' ? [part.text] : [])).join('');
|
|
40
|
+
}
|
|
41
|
+
/** Workspace files a step wrote or asked a tool to write (inputs) or that a tool reported writing (outputs). */
|
|
42
|
+
function filesOf(step) {
|
|
43
|
+
var _a, _b;
|
|
44
|
+
const files = [];
|
|
45
|
+
for (const call of toolCallsOf(step)) {
|
|
46
|
+
const input = call.input;
|
|
47
|
+
if (!input || typeof input !== 'object')
|
|
48
|
+
continue;
|
|
49
|
+
if (Array.isArray(input.files)) {
|
|
50
|
+
for (const file of input.files)
|
|
51
|
+
if (file && typeof file.path === 'string')
|
|
52
|
+
files.push(file.path);
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(input.paths))
|
|
55
|
+
for (const entry of input.paths)
|
|
56
|
+
if (typeof entry === 'string')
|
|
57
|
+
files.push(entry);
|
|
58
|
+
files.push(...(0, context_pruning_1.mentionedPaths)(input));
|
|
59
|
+
}
|
|
60
|
+
for (const part of (_b = (_a = step.tool) === null || _a === void 0 ? void 0 : _a.content) !== null && _b !== void 0 ? _b : [])
|
|
61
|
+
if (part.type === 'tool-result')
|
|
62
|
+
files.push(...(0, context_pruning_1.mentionedPaths)(part.output));
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
function questionsOf(step) {
|
|
66
|
+
return toolCallsOf(step).flatMap((call) => {
|
|
67
|
+
var _a;
|
|
68
|
+
if (call.toolName !== 'request_clarification')
|
|
69
|
+
return [];
|
|
70
|
+
const question = (_a = call.input) === null || _a === void 0 ? void 0 : _a.question;
|
|
71
|
+
return typeof question === 'string' ? [question] : [];
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const MAX_LISTED_FILES = 40;
|
|
75
|
+
const MAX_QUESTION_CHARS = 1500;
|
|
76
|
+
/** A deterministic one-paragraph account of steps: tool counts, questions asked, files touched. */
|
|
77
|
+
function describeSteps(steps) {
|
|
78
|
+
var _a;
|
|
79
|
+
const counts = new Map();
|
|
80
|
+
const files = new Set();
|
|
81
|
+
const questions = [];
|
|
82
|
+
for (const step of steps) {
|
|
83
|
+
for (const call of toolCallsOf(step))
|
|
84
|
+
counts.set(call.toolName, ((_a = counts.get(call.toolName)) !== null && _a !== void 0 ? _a : 0) + 1);
|
|
85
|
+
for (const file of filesOf(step))
|
|
86
|
+
files.add(file);
|
|
87
|
+
questions.push(...questionsOf(step));
|
|
88
|
+
}
|
|
89
|
+
const parts = [`${steps.length} step${steps.length === 1 ? '' : 's'}`];
|
|
90
|
+
if (counts.size)
|
|
91
|
+
parts.push(`tool calls: ${[...counts].map(([name, count]) => `${name} ×${count}`).join(', ')}`);
|
|
92
|
+
if (files.size) {
|
|
93
|
+
const listed = [...files].slice(0, MAX_LISTED_FILES);
|
|
94
|
+
parts.push(`files written or saved: ${listed.join(', ')}${files.size > listed.length ? ` and ${files.size - listed.length} more` : ''}`);
|
|
95
|
+
}
|
|
96
|
+
for (const question of questions)
|
|
97
|
+
parts.push(`asked the user: "${question.length > MAX_QUESTION_CHARS ? `${question.slice(0, MAX_QUESTION_CHARS)}…` : question}"`);
|
|
98
|
+
return parts.join('; ');
|
|
99
|
+
}
|
|
100
|
+
/** The text the turn ended with, when it ended on its own (a final step without tool calls). */
|
|
101
|
+
function finalText(steps) {
|
|
102
|
+
const last = steps[steps.length - 1];
|
|
103
|
+
if (!last || last.tool || toolCallsOf(last).length)
|
|
104
|
+
return undefined;
|
|
105
|
+
const text = textOf(last.assistant).trim();
|
|
106
|
+
return text || undefined;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* A finished (or interrupted) turn as one assistant message for the turns that follow. The final text is
|
|
110
|
+
* what the agent told the user; the account says what it did, so it can reuse its files instead of
|
|
111
|
+
* redoing the work. Tool calls and results are left out: they are paid for again on every later step
|
|
112
|
+
* and the workspace holds what they produced.
|
|
113
|
+
*/
|
|
114
|
+
function collapseTurn(messages) {
|
|
115
|
+
const steps = splitSteps(messages);
|
|
116
|
+
const text = finalText(steps);
|
|
117
|
+
if (text === undefined) {
|
|
118
|
+
const narration = steps.map((step) => textOf(step.assistant).trim()).filter(Boolean).slice(-1)[0];
|
|
119
|
+
return { role: 'assistant', content: `${narration ? `${narration}\n\n` : ''}[This turn was interrupted before it finished: ${describeSteps(steps)}. Its results are in the workspace files, not in this conversation.]` };
|
|
120
|
+
}
|
|
121
|
+
// The final step is the text itself; the account covers the work before it.
|
|
122
|
+
const work = steps.slice(0, -1);
|
|
123
|
+
return { role: 'assistant', content: work.length ? `${text}\n\n[Earlier in this turn: ${describeSteps(work)}.]` : text };
|
|
124
|
+
}
|
|
125
|
+
/** chars ≈ tokens × 4: compact past ~80k tokens of turn, keep ~30k tokens of recent steps. */
|
|
126
|
+
exports.DEFAULT_COMPACTION = { triggerChars: 320000, keepChars: 120000 };
|
|
127
|
+
/** Steps the model is working on right now are never compacted away, whatever their size. */
|
|
128
|
+
const MIN_KEPT_STEPS = 2;
|
|
129
|
+
/** Compaction is monotonic: once steps are dropped they stay dropped, so the cached prefix only changes when more are. */
|
|
130
|
+
function stepsToDrop(steps, state, options) {
|
|
131
|
+
const kept = steps.slice(state.droppedSteps);
|
|
132
|
+
const stepChars = kept.map((step) => approxChars(step.assistant) + (step.tool ? approxChars(step.tool) : 0));
|
|
133
|
+
let total = stepChars.reduce((sum, chars) => sum + chars, 0) + (state.note ? approxChars(state.note) : 0);
|
|
134
|
+
if (total <= options.triggerChars)
|
|
135
|
+
return undefined;
|
|
136
|
+
let drop = 0;
|
|
137
|
+
while (total > options.keepChars && kept.length - drop > MIN_KEPT_STEPS) {
|
|
138
|
+
total -= stepChars[drop];
|
|
139
|
+
drop += 1;
|
|
140
|
+
}
|
|
141
|
+
return drop ? state.droppedSteps + drop : undefined;
|
|
142
|
+
}
|
|
143
|
+
const MAX_NOTES_CHARS = 12000;
|
|
144
|
+
function compactionNote(dropped, notesPath, notes) {
|
|
145
|
+
const account = describeSteps(dropped);
|
|
146
|
+
const memory = (notes === null || notes === void 0 ? void 0 : notes.trim())
|
|
147
|
+
? `Your notes file (${notesPath}) as of now:\n\n${notes.length > MAX_NOTES_CHARS ? `[earlier part omitted]\n${notes.slice(-MAX_NOTES_CHARS)}` : notes}`
|
|
148
|
+
: notesPath
|
|
149
|
+
? `There is no notes file (${notesPath}) yet. Write one now with what is done, which files hold what, and what is next: the next compaction will carry it instead of the steps it removes.`
|
|
150
|
+
: 'Keep your own notes in a workspace file: the next compaction will carry it instead of the steps it removes.';
|
|
151
|
+
return {
|
|
152
|
+
role: 'user',
|
|
153
|
+
content: `(Automatic note, not from the user.) To stay within the context limit, the first ${dropped.length} steps of this turn were removed from the conversation: ${account}. Everything they produced is still in the workspace; the user's messages above and their answers stand. ${memory}\n\nContinue from the steps that follow. If you need something from the removed steps, read it from the workspace files instead of redoing the work.`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/** The turn as the model sees it: the compaction note (if any) followed by the steps not yet dropped. */
|
|
157
|
+
function compactedView(steps, state) {
|
|
158
|
+
const kept = flattenSteps(steps.slice(state.droppedSteps));
|
|
159
|
+
return state.note ? [state.note, ...kept] : kept;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=turn-history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-history.js","sourceRoot":"","sources":["../../src/agent/turn-history.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,6 +21,11 @@ export interface TurnInput {
|
|
|
21
21
|
wrapUpOnly?: boolean;
|
|
22
22
|
recordUsage(usage: TokenUsage): void;
|
|
23
23
|
emit(event: OutboundEvent): void;
|
|
24
|
+
/**
|
|
25
|
+
* The turn's response messages so far, after every finished step. The runtime persists them, so a
|
|
26
|
+
* turn cut off by a lost VM resumes from its last step instead of from the user's message.
|
|
27
|
+
*/
|
|
28
|
+
onStep?(messages: ModelMessage[]): void;
|
|
24
29
|
}
|
|
25
30
|
interface TurnOutput {
|
|
26
31
|
/** Messages to append to history, including a safe text-only fallback for an interrupted turn. */
|
|
@@ -44,7 +49,15 @@ export type TurnResult = TurnOutput & ({
|
|
|
44
49
|
error: string;
|
|
45
50
|
userMessage: string;
|
|
46
51
|
});
|
|
47
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Executes one turn. It neither mutates conversation history nor checkpoints or schedules work.
|
|
54
|
+
*
|
|
55
|
+
* A turn runs as one or more model loops ("segments") of up to `maxSteps` steps each. A profile with
|
|
56
|
+
* `maxTurnSteps` keeps going in a new segment when a loop ends at the step cap while the model still
|
|
57
|
+
* wants to call tools, until the model stops on its own, asks the user, wraps up for the budget, or
|
|
58
|
+
* reaches `maxTurnSteps`. Across segments the turn is one conversation: long turns are compacted (the
|
|
59
|
+
* oldest steps replaced by a note and the agent's notes file) and bulky old results pruned.
|
|
60
|
+
*/
|
|
48
61
|
export declare class TurnRunner {
|
|
49
62
|
private readonly definition;
|
|
50
63
|
constructor(definition: AgentDefinition);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turn-runner.d.ts","sourceRoot":"","sources":["../../src/agent/turn-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,YAAY,EAAiC,MAAM,IAAI,CAAA;AAC9F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGtD,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"turn-runner.d.ts","sourceRoot":"","sources":["../../src/agent/turn-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,YAAY,EAAiC,MAAM,IAAI,CAAA;AAC9F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGtD,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AA0B1D,sGAAsG;AACtG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,SAAS,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,GAAG,MAAM,CAKnG;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,cAAc,IAAI,OAAO,CAAA;IACzB,sFAAsF;IACtF,UAAU,CAAC,IAAI,OAAO,CAAA;IACtB,wFAAwF;IACxF,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IACpC,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAA;IAChC;;;OAGG;IACH,MAAM,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI,CAAA;CACxC;AAED,UAAU,UAAU;IAClB,kGAAkG;IAClG,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,CAClC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE;AACvB,8GAA8G;GAC5G;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAC3D,CAAA;AAED;;;;;;;;GAQG;AACH,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,eAAe;IAElD,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC;CA4JjD"}
|
|
@@ -8,6 +8,7 @@ const usage_1 = require("../models/usage");
|
|
|
8
8
|
const context_pruning_1 = require("../context-pruning");
|
|
9
9
|
const tool_call_repair_1 = require("../tool-call-repair");
|
|
10
10
|
const model_failure_1 = require("./model-failure");
|
|
11
|
+
const turn_history_1 = require("./turn-history");
|
|
11
12
|
/** Steps the budget wrap-up may take (write notes and findings, publish, summarize) before the turn stops. */
|
|
12
13
|
const WRAP_UP_STEPS = 4;
|
|
13
14
|
const WRAP_UP_TOOLS = ['write_files', 'read_file', 'run_command', 'publish_results'];
|
|
@@ -26,19 +27,31 @@ function stepsSinceProgress(steps) {
|
|
|
26
27
|
}
|
|
27
28
|
return steps.length;
|
|
28
29
|
}
|
|
29
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Executes one turn. It neither mutates conversation history nor checkpoints or schedules work.
|
|
32
|
+
*
|
|
33
|
+
* A turn runs as one or more model loops ("segments") of up to `maxSteps` steps each. A profile with
|
|
34
|
+
* `maxTurnSteps` keeps going in a new segment when a loop ends at the step cap while the model still
|
|
35
|
+
* wants to call tools, until the model stops on its own, asks the user, wraps up for the budget, or
|
|
36
|
+
* reaches `maxTurnSteps`. Across segments the turn is one conversation: long turns are compacted (the
|
|
37
|
+
* oldest steps replaced by a note and the agent's notes file) and bulky old results pruned.
|
|
38
|
+
*/
|
|
30
39
|
class TurnRunner {
|
|
31
40
|
constructor(definition) {
|
|
32
41
|
this.definition = definition;
|
|
33
42
|
}
|
|
34
43
|
async run(input) {
|
|
35
|
-
var _a, _b, _c;
|
|
44
|
+
var _a, _b, _c, _d, _e;
|
|
36
45
|
const { messageId, abortSignal, emit } = input;
|
|
37
46
|
let partialText = '';
|
|
38
47
|
let askedUser = false;
|
|
39
|
-
let wrapUpFrom;
|
|
40
48
|
let saved = false;
|
|
41
|
-
let
|
|
49
|
+
let wrappedUp = false;
|
|
50
|
+
/** Response messages of the segments that finished. */
|
|
51
|
+
const turn = [];
|
|
52
|
+
/** Finished steps of the running segment, as of its last prepareStep. */
|
|
53
|
+
let segmentSteps = [];
|
|
54
|
+
let compaction = { droppedSteps: 0 };
|
|
42
55
|
const recordUsage = (usage, model) => {
|
|
43
56
|
input.recordUsage(usage);
|
|
44
57
|
emit({ type: 'usage', messageId, usage: { ...usage, model } });
|
|
@@ -54,92 +67,137 @@ class TurnRunner {
|
|
|
54
67
|
emit({ type: 'question', messageId, toolCallId, question });
|
|
55
68
|
},
|
|
56
69
|
};
|
|
70
|
+
const history = input.messages;
|
|
71
|
+
const { model, maxSteps, profile } = this.definition;
|
|
72
|
+
const maxTurnSteps = (_a = this.definition.maxTurnSteps) !== null && _a !== void 0 ? _a : maxSteps;
|
|
73
|
+
// The wrap-up needs a research workspace to save into; the coding profile just stops at the budget.
|
|
74
|
+
const research = profile === 'research';
|
|
75
|
+
/** The turn so far as the model should see it: compacted when long, bulky old results pruned. */
|
|
76
|
+
const promptFor = async (turnMessages) => {
|
|
77
|
+
var _a, _b, _c;
|
|
78
|
+
const steps = (0, turn_history_1.splitSteps)(turnMessages);
|
|
79
|
+
const drop = (0, turn_history_1.stepsToDrop)(steps, compaction, turn_history_1.DEFAULT_COMPACTION);
|
|
80
|
+
if (drop !== undefined) {
|
|
81
|
+
const notes = await ((_b = (_a = this.definition).workingNotes) === null || _b === void 0 ? void 0 : _b.call(_a, input.workspaceDir).catch(() => undefined));
|
|
82
|
+
compaction = { droppedSteps: drop, note: (0, turn_history_1.compactionNote)(steps.slice(0, drop), (_c = this.definition.workingNotes) === null || _c === void 0 ? void 0 : _c.path, notes) };
|
|
83
|
+
}
|
|
84
|
+
return (0, context_pruning_1.pruneToolResults)([...history, ...(0, turn_history_1.compactedView)(steps, compaction)]);
|
|
85
|
+
};
|
|
57
86
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
let totalSteps = 0;
|
|
88
|
+
let text = '';
|
|
89
|
+
for (;;) {
|
|
90
|
+
abortSignal.throwIfAborted();
|
|
91
|
+
const tools = this.definition.createTools(context);
|
|
92
|
+
let wrapUpFrom;
|
|
93
|
+
const wrapUpDone = ({ steps }) => wrapUpFrom !== undefined ? steps.length - wrapUpFrom >= WRAP_UP_STEPS : !research && input.budgetExceeded();
|
|
94
|
+
const segmentBase = [...turn];
|
|
95
|
+
const segmentPrompt = await promptFor(segmentBase);
|
|
96
|
+
segmentSteps = [];
|
|
97
|
+
const result = (0, ai_1.streamText)({
|
|
98
|
+
model: (0, resolve_model_1.resolveLanguageModel)(model.provider, model.id, model.attribution),
|
|
99
|
+
system: input.instructions,
|
|
100
|
+
messages: segmentPrompt,
|
|
101
|
+
tools,
|
|
102
|
+
abortSignal,
|
|
103
|
+
stopWhen: [(0, ai_1.stepCountIs)(maxSteps), () => askedUser, wrapUpDone],
|
|
104
|
+
experimental_repairToolCall: tool_call_repair_1.repairStringifiedToolInput,
|
|
105
|
+
prepareStep: async ({ stepNumber, steps, messages }) => {
|
|
106
|
+
var _a, _b;
|
|
107
|
+
// `messages` is the segment's prompt plus the steps finished since; the latter are the
|
|
108
|
+
// part of the turn not persisted yet.
|
|
109
|
+
segmentSteps = messages.slice(segmentPrompt.length);
|
|
110
|
+
if (segmentSteps.length)
|
|
111
|
+
(_a = input.onStep) === null || _a === void 0 ? void 0 : _a.call(input, [...segmentBase, ...segmentSteps]);
|
|
112
|
+
const prompt = await promptFor([...segmentBase, ...segmentSteps]);
|
|
113
|
+
if (wrapUpFrom === undefined && research && (input.wrapUpOnly || ((_b = input.nearBudget) === null || _b === void 0 ? void 0 : _b.call(input))))
|
|
114
|
+
wrapUpFrom = stepNumber;
|
|
115
|
+
if (wrapUpFrom === undefined) {
|
|
116
|
+
// The chat pins the checklist; a stale one tells the user nothing is moving. The note is
|
|
117
|
+
// appended for this step only, after the cached prefix, and is never saved. Once every
|
|
118
|
+
// PROGRESS_REMINDER_STEPS quiet steps, so an agent whose phase has not changed is not nagged.
|
|
119
|
+
const since = stepsSinceProgress(steps);
|
|
120
|
+
const stale = research && 'report_progress' in tools && since > 0 && since % PROGRESS_REMINDER_STEPS === 0;
|
|
121
|
+
return { messages: stale ? [...prompt, { role: 'user', content: PROGRESS_REMINDER }] : prompt };
|
|
122
|
+
}
|
|
123
|
+
// The instructions go last, as a note, rather than into the system prompt: the model acts on
|
|
124
|
+
// the newest message (in one run it ignored a system-only switch and ended on leftover
|
|
125
|
+
// text), and the cached prefix stays intact. A forced tool call (toolChoice "required") is
|
|
126
|
+
// not an option: Anthropic rejects it while extended thinking is on.
|
|
127
|
+
return {
|
|
128
|
+
messages: saved ? prompt : [...prompt, { role: 'user', content: WRAP_UP_INSTRUCTIONS }],
|
|
129
|
+
activeTools: WRAP_UP_TOOLS.filter((name) => name in tools),
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
for await (const part of result.fullStream) {
|
|
134
|
+
switch (part.type) {
|
|
135
|
+
case 'text-delta':
|
|
136
|
+
partialText += part.text;
|
|
137
|
+
emit({ type: 'text_delta', messageId, text: part.text });
|
|
138
|
+
break;
|
|
139
|
+
case 'reasoning-delta':
|
|
140
|
+
emit({ type: 'reasoning_delta', messageId, text: part.text });
|
|
141
|
+
break;
|
|
142
|
+
case 'tool-call':
|
|
143
|
+
if (wrapUpFrom !== undefined && (part.toolName === 'publish_results' || part.toolName === 'write_files'))
|
|
144
|
+
saved = true;
|
|
145
|
+
emit({ type: 'tool_call_start', messageId, toolCallId: part.toolCallId, toolName: part.toolName, input: part.input });
|
|
146
|
+
break;
|
|
147
|
+
case 'tool-result':
|
|
148
|
+
emit({ type: 'tool_call_result', messageId, toolCallId: part.toolCallId, output: part.output });
|
|
149
|
+
break;
|
|
150
|
+
case 'tool-error':
|
|
151
|
+
emit({
|
|
152
|
+
type: 'tool_call_result', messageId, toolCallId: part.toolCallId,
|
|
153
|
+
error: part.error instanceof Error ? part.error.message : String(part.error),
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
case 'finish-step': {
|
|
157
|
+
const usage = (0, usage_1.usageOf)(part);
|
|
158
|
+
if (usage)
|
|
159
|
+
recordUsage(usage, (_c = (_b = part.response) === null || _b === void 0 ? void 0 : _b.modelId) !== null && _c !== void 0 ? _c : model.id);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'error':
|
|
163
|
+
throw part.error;
|
|
84
164
|
}
|
|
85
|
-
// The instructions go last, as a note, rather than into the system prompt: the model acts on
|
|
86
|
-
// the newest message (in one run it ignored a system-only switch and ended on leftover
|
|
87
|
-
// text), and the cached prefix stays intact. A forced tool call (toolChoice "required") is
|
|
88
|
-
// not an option: Anthropic rejects it while extended thinking is on.
|
|
89
|
-
return {
|
|
90
|
-
messages: saved ? pruned : [...pruned, { role: 'user', content: WRAP_UP_INSTRUCTIONS }],
|
|
91
|
-
activeTools: WRAP_UP_TOOLS.filter((name) => name in tools),
|
|
92
|
-
};
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
for await (const part of result.fullStream) {
|
|
96
|
-
switch (part.type) {
|
|
97
|
-
case 'text-delta':
|
|
98
|
-
partialText += part.text;
|
|
99
|
-
emit({ type: 'text_delta', messageId, text: part.text });
|
|
100
|
-
break;
|
|
101
|
-
case 'reasoning-delta':
|
|
102
|
-
emit({ type: 'reasoning_delta', messageId, text: part.text });
|
|
103
|
-
break;
|
|
104
|
-
case 'tool-call':
|
|
105
|
-
if (wrapUpFrom !== undefined && (part.toolName === 'publish_results' || part.toolName === 'write_files'))
|
|
106
|
-
saved = true;
|
|
107
|
-
emit({ type: 'tool_call_start', messageId, toolCallId: part.toolCallId, toolName: part.toolName, input: part.input });
|
|
108
|
-
break;
|
|
109
|
-
case 'tool-result':
|
|
110
|
-
emit({ type: 'tool_call_result', messageId, toolCallId: part.toolCallId, output: part.output });
|
|
111
|
-
break;
|
|
112
|
-
case 'tool-error':
|
|
113
|
-
emit({
|
|
114
|
-
type: 'tool_call_result', messageId, toolCallId: part.toolCallId,
|
|
115
|
-
error: part.error instanceof Error ? part.error.message : String(part.error),
|
|
116
|
-
});
|
|
117
|
-
break;
|
|
118
|
-
case 'finish-step': {
|
|
119
|
-
const usage = (0, usage_1.usageOf)(part);
|
|
120
|
-
if (usage)
|
|
121
|
-
recordUsage(usage, (_b = (_a = part.response) === null || _a === void 0 ? void 0 : _a.modelId) !== null && _b !== void 0 ? _b : model.id);
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
case 'error':
|
|
125
|
-
throw part.error;
|
|
126
165
|
}
|
|
166
|
+
abortSignal.throwIfAborted();
|
|
167
|
+
turn.push(...(await result.response).messages);
|
|
168
|
+
segmentSteps = [];
|
|
169
|
+
const steps = await result.steps;
|
|
170
|
+
totalSteps += steps.length;
|
|
171
|
+
text = await result.text;
|
|
172
|
+
wrappedUp || (wrappedUp = wrapUpFrom !== undefined);
|
|
173
|
+
// The loop ended at its step cap with the model mid-work: carry on in a new segment.
|
|
174
|
+
const atCap = steps.length >= maxSteps && (await result.finishReason) === 'tool-calls' && !askedUser && !wrappedUp;
|
|
175
|
+
if (!atCap)
|
|
176
|
+
break;
|
|
177
|
+
if (totalSteps >= maxTurnSteps) {
|
|
178
|
+
const note = `${partialText ? '\n\n' : ''}I have taken ${totalSteps} steps in this turn, the most one turn allows. My notes and files are saved; reply "continue" and I will carry on from here.`;
|
|
179
|
+
partialText += note;
|
|
180
|
+
text += note;
|
|
181
|
+
emit({ type: 'text_delta', messageId, text: note });
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
(_d = input.onStep) === null || _d === void 0 ? void 0 : _d.call(input, turn);
|
|
127
185
|
}
|
|
128
|
-
|
|
129
|
-
responseMessages = (await result.response).messages;
|
|
130
|
-
const text = await result.text;
|
|
131
|
-
return { status: 'completed', messages: responseMessages, text, partialText, askedUser, wrappedUp: wrapUpFrom !== undefined, saved };
|
|
186
|
+
return { status: 'completed', messages: turn, text, partialText, askedUser, wrappedUp, saved };
|
|
132
187
|
}
|
|
133
188
|
catch (error) {
|
|
134
189
|
const aborted = abortSignal.aborted;
|
|
135
|
-
// A partial stream may end between a tool call and its result
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
190
|
+
// A partial stream may end between a tool call and its result, so only the finished steps are
|
|
191
|
+
// kept, followed by the text streamed since (or a marker), so the turn ends on an assistant message.
|
|
192
|
+
const messages = [
|
|
193
|
+
...turn,
|
|
194
|
+
...segmentSteps,
|
|
195
|
+
{ role: 'assistant', content: partialText || (aborted ? '(stopped by the user)' : '(turn failed)') },
|
|
196
|
+
];
|
|
197
|
+
const output = { messages, partialText, askedUser, wrappedUp, saved };
|
|
140
198
|
return aborted
|
|
141
199
|
? { ...output, status: 'aborted' }
|
|
142
|
-
: { ...output, status: 'failed', error: (0, model_failure_1.describeModelFailure)(error), userMessage: (
|
|
200
|
+
: { ...output, status: 'failed', error: (0, model_failure_1.describeModelFailure)(error), userMessage: (_e = (0, model_failure_1.userFacingModelFailure)(error)) !== null && _e !== void 0 ? _e : (0, model_failure_1.describeModelFailure)(error) };
|
|
143
201
|
}
|
|
144
202
|
}
|
|
145
203
|
}
|