@immediately-run/sdk 0.57.1 → 0.58.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/dist/agentChatClient.cjs +112 -0
- package/dist/agentChatClient.cjs.map +1 -0
- package/dist/agentChatClient.d.cts +9 -0
- package/dist/agentChatClient.d.ts +9 -0
- package/dist/agentChatClient.js +89 -0
- package/dist/agentChatClient.js.map +1 -0
- package/dist/agentContext.cjs +69 -0
- package/dist/agentContext.cjs.map +1 -0
- package/dist/agentContext.d.cts +45 -0
- package/dist/agentContext.d.ts +45 -0
- package/dist/agentContext.js +45 -0
- package/dist/agentContext.js.map +1 -0
- package/dist/agentLoop.cjs +301 -0
- package/dist/agentLoop.cjs.map +1 -0
- package/dist/agentLoop.d.cts +267 -0
- package/dist/agentLoop.d.ts +267 -0
- package/dist/agentLoop.js +271 -0
- package/dist/agentLoop.js.map +1 -0
- package/dist/agentSteering.cjs +127 -0
- package/dist/agentSteering.cjs.map +1 -0
- package/dist/agentSteering.d.cts +80 -0
- package/dist/agentSteering.d.ts +80 -0
- package/dist/agentSteering.js +98 -0
- package/dist/agentSteering.js.map +1 -0
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +33 -2
- package/dist/auth.d.ts +33 -2
- package/dist/auth.js.map +1 -1
- package/dist/bundle.cjs +91 -0
- package/dist/bundle.cjs.map +1 -0
- package/dist/bundle.d.cts +63 -0
- package/dist/bundle.d.ts +63 -0
- package/dist/bundle.js +62 -0
- package/dist/bundle.js.map +1 -0
- package/dist/collectHeadings.cjs +59 -0
- package/dist/collectHeadings.cjs.map +1 -0
- package/dist/collectHeadings.d.cts +16 -0
- package/dist/collectHeadings.d.ts +16 -0
- package/dist/collectHeadings.js +36 -0
- package/dist/collectHeadings.js.map +1 -0
- package/dist/corpus.cjs +9 -58
- package/dist/corpus.cjs.map +1 -1
- package/dist/corpus.d.cts +3 -63
- package/dist/corpus.d.ts +3 -63
- package/dist/corpus.js +15 -57
- package/dist/corpus.js.map +1 -1
- package/dist/debug.cjs +115 -0
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.js +115 -0
- package/dist/debug.js.map +1 -1
- package/dist/fence.cjs +49 -0
- package/dist/fence.cjs.map +1 -0
- package/dist/fence.d.cts +4 -0
- package/dist/fence.d.ts +4 -0
- package/dist/fence.js +26 -0
- package/dist/fence.js.map +1 -0
- package/dist/formFactor.cjs.map +1 -1
- package/dist/formFactor.d.cts +11 -2
- package/dist/formFactor.d.ts +11 -2
- package/dist/formFactor.js.map +1 -1
- package/dist/fs.cjs.map +1 -1
- package/dist/fs.d.cts +16 -6
- package/dist/fs.d.ts +16 -6
- package/dist/fs.js.map +1 -1
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +7 -0
- package/dist/hooks.d.ts +7 -0
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +21 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/launch.cjs +45 -6
- package/dist/launch.cjs.map +1 -1
- package/dist/launch.d.cts +3 -0
- package/dist/launch.d.ts +3 -0
- package/dist/launch.js +45 -6
- package/dist/launch.js.map +1 -1
- package/dist/llm.cjs.map +1 -1
- package/dist/llm.d.cts +20 -3
- package/dist/llm.d.ts +20 -3
- package/dist/llm.js.map +1 -1
- package/dist/metadataQueryTool.cjs +271 -0
- package/dist/metadataQueryTool.cjs.map +1 -0
- package/dist/metadataQueryTool.d.cts +159 -0
- package/dist/metadataQueryTool.d.ts +159 -0
- package/dist/metadataQueryTool.js +241 -0
- package/dist/metadataQueryTool.js.map +1 -0
- package/dist/mounts.cjs.map +1 -1
- package/dist/mounts.d.cts +22 -3
- package/dist/mounts.d.ts +22 -3
- package/dist/mounts.js.map +1 -1
- package/dist/onFsChange.cjs +13 -1
- package/dist/onFsChange.cjs.map +1 -1
- package/dist/onFsChange.d.cts +21 -1
- package/dist/onFsChange.d.ts +21 -1
- package/dist/onFsChange.js +13 -1
- package/dist/onFsChange.js.map +1 -1
- package/dist/protocolSchemes.cjs +1 -0
- package/dist/protocolSchemes.cjs.map +1 -1
- package/dist/protocolSchemes.d.cts +1 -0
- package/dist/protocolSchemes.d.ts +1 -0
- package/dist/protocolSchemes.js +2 -0
- package/dist/protocolSchemes.js.map +1 -1
- package/dist/recents.cjs +41 -0
- package/dist/recents.cjs.map +1 -0
- package/dist/recents.d.cts +23 -0
- package/dist/recents.d.ts +23 -0
- package/dist/recents.js +17 -0
- package/dist/recents.js.map +1 -0
- package/dist/tasks.cjs +41 -7
- package/dist/tasks.cjs.map +1 -1
- package/dist/tasks.d.cts +136 -7
- package/dist/tasks.d.ts +136 -7
- package/dist/tasks.js +39 -7
- package/dist/tasks.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/workspace.cjs +53 -0
- package/dist/workspace.cjs.map +1 -0
- package/dist/workspace.d.cts +58 -0
- package/dist/workspace.d.ts +58 -0
- package/dist/workspace.js +28 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +6 -5
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { SteerMessage, SteerSource } from './agentSteering.cjs';
|
|
2
|
+
|
|
3
|
+
type TextBlock = {
|
|
4
|
+
type: 'text';
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* An image the model can look at (R3-339). `data` is base64 with no `data:` prefix,
|
|
9
|
+
* matching the SDK `ContentPart` the transport already accepts.
|
|
10
|
+
*
|
|
11
|
+
* Carried as its OWN block rather than stuffed inside a `tool_result`, because a tool
|
|
12
|
+
* result's content is a string on the wire — the loop appends the image to the same
|
|
13
|
+
* user message that carries the results, which is the shape both host adapters map.
|
|
14
|
+
*/
|
|
15
|
+
type ImageBlock = {
|
|
16
|
+
type: 'image';
|
|
17
|
+
mimeType: string;
|
|
18
|
+
data: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A block of the model's own reasoning (R3-335).
|
|
22
|
+
*
|
|
23
|
+
* Kept in the message sequence rather than rendered and thrown away, for two reasons:
|
|
24
|
+
* the user needs to see what the model is doing during the long stretches compaction
|
|
25
|
+
* now makes possible, and some providers REQUIRE the block echoed back — with its
|
|
26
|
+
* `signature` — for the following turn of a tool-use chain to stay valid. A loop that
|
|
27
|
+
* drops them is quietly lossy in a way that shows up as degraded output, not an error.
|
|
28
|
+
*
|
|
29
|
+
* `redactedData` carries provider-redacted reasoning: opaque bytes with no readable
|
|
30
|
+
* text, which still have to be replayed in place. Never render it.
|
|
31
|
+
*/
|
|
32
|
+
type ReasoningBlock = {
|
|
33
|
+
type: 'reasoning';
|
|
34
|
+
text: string;
|
|
35
|
+
signature?: string;
|
|
36
|
+
redactedData?: string;
|
|
37
|
+
};
|
|
38
|
+
type ToolUseBlock = {
|
|
39
|
+
type: 'tool_use';
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
input: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
type ToolResultBlock = {
|
|
45
|
+
type: 'tool_result';
|
|
46
|
+
tool_use_id: string;
|
|
47
|
+
content: string;
|
|
48
|
+
is_error?: boolean;
|
|
49
|
+
};
|
|
50
|
+
type ContentBlock = TextBlock | ToolUseBlock | ToolResultBlock | ImageBlock | ReasoningBlock;
|
|
51
|
+
/** A tool the model may call: name, description, and a JSON Schema for its input
|
|
52
|
+
* (`input_schema`, the Anthropic wire name — {@link createChatModelClient} maps it to the
|
|
53
|
+
* chat slot's `ToolDef`). */
|
|
54
|
+
interface AgentTool {
|
|
55
|
+
name: string;
|
|
56
|
+
description: string;
|
|
57
|
+
input_schema: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
type AgentRole = 'user' | 'assistant';
|
|
60
|
+
interface AgentMessage {
|
|
61
|
+
role: AgentRole;
|
|
62
|
+
content: ContentBlock[];
|
|
63
|
+
}
|
|
64
|
+
/** Provider-reported token counts for one turn (R3-220). `inputTokens` is the size
|
|
65
|
+
* of everything the provider processed this turn; `outputTokens` is what it
|
|
66
|
+
* generated. Absent when the provider emits no `usage` delta. */
|
|
67
|
+
interface TokenUsage {
|
|
68
|
+
inputTokens: number;
|
|
69
|
+
outputTokens: number;
|
|
70
|
+
/** R3-336 — prompt-cache counters, present only where the provider reports them.
|
|
71
|
+
* ABSENT is not zero: it means this provider says nothing about caching, which is a
|
|
72
|
+
* different fact from "nothing was cached", and conflating them would turn a
|
|
73
|
+
* measurement into a guess. */
|
|
74
|
+
cacheReadTokens?: number;
|
|
75
|
+
cacheWriteTokens?: number;
|
|
76
|
+
}
|
|
77
|
+
/** One model turn: the assistant's emitted blocks + why it stopped (+ usage). */
|
|
78
|
+
interface ModelResponse {
|
|
79
|
+
content: (TextBlock | ToolUseBlock | ReasoningBlock)[];
|
|
80
|
+
/** Anthropic stop_reason: 'end_turn' | 'tool_use' | 'max_tokens' | 'refusal' | … */
|
|
81
|
+
stopReason: string;
|
|
82
|
+
/** Provider token counts for this turn, when reported (R3-220 accounting). */
|
|
83
|
+
usage?: TokenUsage;
|
|
84
|
+
}
|
|
85
|
+
/** The provider seam — one model turn. Implemented by `chatModelClient.ts` over
|
|
86
|
+
* the host `chat()` slot; faked in tests. When the client streams, it calls
|
|
87
|
+
* `onTextDelta` with each token slice as it arrives (the assembled turn is still
|
|
88
|
+
* returned whole); a non-streaming client simply never calls it. */
|
|
89
|
+
interface ModelClient {
|
|
90
|
+
createMessage(req: {
|
|
91
|
+
system?: string;
|
|
92
|
+
messages: AgentMessage[];
|
|
93
|
+
tools: AgentTool[];
|
|
94
|
+
/** Called with incremental assistant-text slices during a streamed turn. */
|
|
95
|
+
onTextDelta?: (text: string) => void;
|
|
96
|
+
/** R3-335: incremental REASONING slices, for a live thinking surface. Never called
|
|
97
|
+
* by a provider that does not emit reasoning. */
|
|
98
|
+
onReasoningDelta?: (text: string) => void;
|
|
99
|
+
/** R3-224: aborts the in-flight turn — the host stops the upstream provider
|
|
100
|
+
* request and stops billing, not just the app-side stream (§3.3). */
|
|
101
|
+
signal?: AbortSignal;
|
|
102
|
+
}): Promise<ModelResponse>;
|
|
103
|
+
}
|
|
104
|
+
/** Executes one tool call, returning a string result (and whether it errored —
|
|
105
|
+
* a `forbidden`/failed call comes back as `is_error` so the model can adapt). */
|
|
106
|
+
type ToolExecutor = (name: string, input: Record<string, unknown>) => Promise<ToolOutcome>;
|
|
107
|
+
/** What one tool call produced. `images` (R3-339) is how a tool hands the model
|
|
108
|
+
* something to LOOK at; `content` still carries the text the model reads. */
|
|
109
|
+
interface ToolOutcome {
|
|
110
|
+
content: string;
|
|
111
|
+
isError?: boolean;
|
|
112
|
+
images?: ImageBlock[];
|
|
113
|
+
}
|
|
114
|
+
/** Why a no-tool-call turn looked like a stall rather than a genuine finish. */
|
|
115
|
+
type StallReason = 'empty' | 'announced-no-call';
|
|
116
|
+
/** Optional UI hooks so a panel can render the loop as it runs. */
|
|
117
|
+
interface AgentEvents {
|
|
118
|
+
/** A streamed token slice of the in-flight assistant turn (live preview). */
|
|
119
|
+
onAssistantDelta?(text: string): void;
|
|
120
|
+
/** The complete assistant text for a turn, once the turn is in. */
|
|
121
|
+
onAssistantText?(text: string): void;
|
|
122
|
+
onToolUse?(name: string, input: Record<string, unknown>): void;
|
|
123
|
+
onToolResult?(name: string, result: ToolOutcome): void;
|
|
124
|
+
/** Fired when the loop nudges a STALLED turn (the model ended without a tool
|
|
125
|
+
* call despite empty or "I'll do X" intent text) back into action, so a panel
|
|
126
|
+
* can show "nudging the model to continue" rather than a silent stall. */
|
|
127
|
+
onNudge?(reason: StallReason): void;
|
|
128
|
+
/** Fired after every turn with the running context size + window (R3-220
|
|
129
|
+
* loop-observability). `contextTokens` is provider-reported when available, else
|
|
130
|
+
* a char/4 estimate. */
|
|
131
|
+
onUsage?(usage: {
|
|
132
|
+
contextTokens: number;
|
|
133
|
+
window?: number;
|
|
134
|
+
spentTokens: number;
|
|
135
|
+
/** R3-336 — cumulative cache reads/writes across the run, on providers that report
|
|
136
|
+
* them. Surfacing this is what makes the caching claim verifiable rather than
|
|
137
|
+
* believed; `undefined` means the provider reported nothing. */
|
|
138
|
+
cacheReadTokens?: number;
|
|
139
|
+
cacheWriteTokens?: number;
|
|
140
|
+
}): void;
|
|
141
|
+
/** Fired when the loop compacts the transcript to stay under the context window;
|
|
142
|
+
* `summarizedCount` is how many older messages were folded into the summary.
|
|
143
|
+
*
|
|
144
|
+
* R3-336: a compaction invalidates the conversation-prefix cache it rewrote — the
|
|
145
|
+
* durable system+tools prefix survives it — so the next turn pays one prefix
|
|
146
|
+
* re-write. `cacheReadTokens`/`cacheWriteTokens` are the run totals AT the
|
|
147
|
+
* compaction, which is what lets the cost curve across it be read off rather than
|
|
148
|
+
* assumed (exit 2). */
|
|
149
|
+
onCompact?(info: {
|
|
150
|
+
summarizedCount: number;
|
|
151
|
+
cacheReadTokens?: number;
|
|
152
|
+
cacheWriteTokens?: number;
|
|
153
|
+
}): void;
|
|
154
|
+
/** Fired when the loop stops because the token/spend budget was exhausted. */
|
|
155
|
+
onBudgetStop?(info: {
|
|
156
|
+
spentTokens: number;
|
|
157
|
+
tokenBudget: number;
|
|
158
|
+
}): void;
|
|
159
|
+
/** Fired when a turn was truncated (`max_tokens`) while emitting tool calls, so
|
|
160
|
+
* the partial calls were failed-and-re-prompted rather than executed (R3-220 F3). */
|
|
161
|
+
onTruncatedToolCall?(): void;
|
|
162
|
+
/** R3-335: a streamed slice of the model's reasoning, for a live thinking surface. */
|
|
163
|
+
onReasoningDelta?(text: string): void;
|
|
164
|
+
/** R3-335: the complete reasoning block for a turn, once the turn is in. */
|
|
165
|
+
onReasoning?(block: ReasoningBlock): void;
|
|
166
|
+
/** R3-333: the loop applied the user's mid-run correction(s). `interrupted` is
|
|
167
|
+
* true when an `interrupt`-mode steer cut an in-flight model turn short (as
|
|
168
|
+
* opposed to being applied at an ordinary turn boundary). */
|
|
169
|
+
onSteer?(info: {
|
|
170
|
+
messages: SteerMessage[];
|
|
171
|
+
interrupted: boolean;
|
|
172
|
+
}): void;
|
|
173
|
+
}
|
|
174
|
+
interface RunAgentOptions {
|
|
175
|
+
client: ModelClient;
|
|
176
|
+
tools: AgentTool[];
|
|
177
|
+
execute: ToolExecutor;
|
|
178
|
+
system?: string;
|
|
179
|
+
/** Prior turns of this conversation, replayed before the new prompt so a
|
|
180
|
+
* follow-up has context (the conversation stage seeds this from the store). */
|
|
181
|
+
history?: AgentMessage[];
|
|
182
|
+
/** The user's instruction that kicks off the loop. */
|
|
183
|
+
prompt: string;
|
|
184
|
+
/** Large safety-stop on model turns (default 100). No longer the primary bound —
|
|
185
|
+
* a long task is bounded by `tokenBudget` + compaction; this just backstops a
|
|
186
|
+
* pathological loop the budget/compaction somehow miss. */
|
|
187
|
+
maxTurns?: number;
|
|
188
|
+
/** Max consecutive "you announced work but emitted no tool call" nudges before
|
|
189
|
+
* the loop gives up (default 1). GLM-over-OpenRouter intermittently ends a turn
|
|
190
|
+
* with future-tense intent ("I'll read the files…") or an EMPTY turn right after
|
|
191
|
+
* a tool error — no tool call, a silent stall (tutorial findings §2). One nudge
|
|
192
|
+
* recovers most of these; the cap keeps a genuinely-finished model (which answers
|
|
193
|
+
* the nudge with another call-free turn) from looping, and the budget resets on
|
|
194
|
+
* any turn that DID call a tool, so a long task's later stall is still covered.
|
|
195
|
+
* Set 0 to disable the backstop. */
|
|
196
|
+
maxNudges?: number;
|
|
197
|
+
/** The resolved provider's context window (`describeChat().features.maxContextTokens`).
|
|
198
|
+
* Compaction is disabled when this is absent/0 — the loop then behaves as before. */
|
|
199
|
+
contextWindow?: number;
|
|
200
|
+
/** Headroom left below the window before compacting (default: 25% of the window). */
|
|
201
|
+
reserveTokens?: number;
|
|
202
|
+
/** Recent messages kept verbatim across a compaction (default 8). */
|
|
203
|
+
keepRecentTurns?: number;
|
|
204
|
+
/** Cumulative token budget (input+output across turns). When exceeded the loop
|
|
205
|
+
* stops — the runaway-cost guard that replaces the raw 12-turn cap. Off when unset. */
|
|
206
|
+
tokenBudget?: number;
|
|
207
|
+
/** Max consecutive truncated-tool-call re-prompts before giving up (default 2). */
|
|
208
|
+
maxTruncationRetries?: number;
|
|
209
|
+
/** R3-224 (§3.3): the stop button. When it fires the loop stops between turns AND
|
|
210
|
+
* aborts the in-flight model turn (the host tears down the upstream provider
|
|
211
|
+
* request and stops billing) — not merely the between-turn loop. The transcript so
|
|
212
|
+
* far is returned; an abort is a clean stop, never a thrown error. */
|
|
213
|
+
signal?: AbortSignal;
|
|
214
|
+
/** R3-333: the mid-run steering queue. The loop drains it at every turn boundary
|
|
215
|
+
* and folds each correction in as a `user` message, so the human can redirect a
|
|
216
|
+
* run without restarting it and paying for the transcript again. Its `interrupt`
|
|
217
|
+
* signal aborts the in-flight MODEL turn only — never a tool batch, which must
|
|
218
|
+
* keep every `tool_use` paired with a `tool_result`. Absent ⇒ the loop behaves
|
|
219
|
+
* exactly as before. */
|
|
220
|
+
steering?: SteerSource;
|
|
221
|
+
events?: AgentEvents;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Classify a NO-tool-call turn as a stall (nudge-worthy) vs a genuine finish.
|
|
225
|
+
* GLM-over-OpenRouter intermittently (a) writes "I'll read the files…" then ends
|
|
226
|
+
* with no call, or (b) returns an EMPTY turn after a tool error — both silent
|
|
227
|
+
* give-ups (tutorial findings §2). Conservative on purpose: a real wrap-up (a
|
|
228
|
+
* summary, "Done", "I've created…") returns null so the loop never nudges a
|
|
229
|
+
* finished agent. Empty text is always a stall (there is nothing a finished agent
|
|
230
|
+
* would say with zero words).
|
|
231
|
+
*/
|
|
232
|
+
declare function detectStall(text: string): StallReason | null;
|
|
233
|
+
declare const NUDGE_TEXT = "You ended your turn without calling a tool. If the task is already complete, say so plainly in one line and stop. Otherwise don't just describe the next step \u2014 emit the tool call now.";
|
|
234
|
+
/** Rough token estimate (~4 chars/token) over a message array, used only when the
|
|
235
|
+
* provider reports no `usage` delta. Conservative by design (over- not under-counts
|
|
236
|
+
* by treating structured blocks as their JSON length). */
|
|
237
|
+
declare function estimateTokens(messages: AgentMessage[]): number;
|
|
238
|
+
/** Should the loop compact now? True once the running context passes
|
|
239
|
+
* `window − reserveTokens`. Disabled (false) when there is no window. */
|
|
240
|
+
declare function shouldCompact(contextTokens: number, window: number | undefined, reserveTokens: number): boolean;
|
|
241
|
+
/** Prefix marking a `user` message as a compaction summary (not a real user turn),
|
|
242
|
+
* so the transcript renderer shows a "compacted N turns" affordance on replay. */
|
|
243
|
+
declare const COMPACTION_MARKER = "\u241F[compacted-context]\n";
|
|
244
|
+
/** Compact `messages` by folding the older head into a structured summary and keeping
|
|
245
|
+
* a verbatim recent tail. The tail is snapped to start at an `assistant` message so a
|
|
246
|
+
* `tool_use`/`tool_result` pair is never split (which would malform the next request).
|
|
247
|
+
* The taint tier is NOT modelled on messages (it is run-scoped host state, R-ASG-2):
|
|
248
|
+
* this is a pure content transform over the SAME session — it starts no new external
|
|
249
|
+
* read — so it cannot launder taint (F6). Returns the original array unchanged when
|
|
250
|
+
* there is nothing safe to summarize. */
|
|
251
|
+
declare function compactTranscript(messages: AgentMessage[], client: ModelClient, keepRecentTurns: number): Promise<{
|
|
252
|
+
messages: AgentMessage[];
|
|
253
|
+
summarizedCount: number;
|
|
254
|
+
}>;
|
|
255
|
+
/** Does this thrown error look like a hard context-window overflow? Used to trigger
|
|
256
|
+
* recover-then-retry compaction (F3/exit-c) rather than a dead loop. */
|
|
257
|
+
declare function isContextOverflow(e: unknown): boolean;
|
|
258
|
+
/**
|
|
259
|
+
* Drive the agent loop to completion. Returns the full message transcript
|
|
260
|
+
* (including the kickoff user turn). Stops when the model returns without tool
|
|
261
|
+
* calls (or a terminal stop reason), when the token budget is exhausted, or when
|
|
262
|
+
* `maxTurns` (a large safety-stop) is reached. With a `contextWindow` set, the loop
|
|
263
|
+
* accounts tokens and compacts automatically so it can run long.
|
|
264
|
+
*/
|
|
265
|
+
declare function runAgent(opts: RunAgentOptions): Promise<AgentMessage[]>;
|
|
266
|
+
|
|
267
|
+
export { type AgentEvents, type AgentMessage, type AgentRole, type AgentTool, COMPACTION_MARKER, type ContentBlock, type ImageBlock, type ModelClient, type ModelResponse, NUDGE_TEXT, type ReasoningBlock, type RunAgentOptions, type StallReason, type TextBlock, type TokenUsage, type ToolExecutor, type ToolOutcome, type ToolResultBlock, type ToolUseBlock, compactTranscript, detectStall, estimateTokens, isContextOverflow, runAgent, shouldCompact };
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { SteerMessage, SteerSource } from './agentSteering.js';
|
|
2
|
+
|
|
3
|
+
type TextBlock = {
|
|
4
|
+
type: 'text';
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* An image the model can look at (R3-339). `data` is base64 with no `data:` prefix,
|
|
9
|
+
* matching the SDK `ContentPart` the transport already accepts.
|
|
10
|
+
*
|
|
11
|
+
* Carried as its OWN block rather than stuffed inside a `tool_result`, because a tool
|
|
12
|
+
* result's content is a string on the wire — the loop appends the image to the same
|
|
13
|
+
* user message that carries the results, which is the shape both host adapters map.
|
|
14
|
+
*/
|
|
15
|
+
type ImageBlock = {
|
|
16
|
+
type: 'image';
|
|
17
|
+
mimeType: string;
|
|
18
|
+
data: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A block of the model's own reasoning (R3-335).
|
|
22
|
+
*
|
|
23
|
+
* Kept in the message sequence rather than rendered and thrown away, for two reasons:
|
|
24
|
+
* the user needs to see what the model is doing during the long stretches compaction
|
|
25
|
+
* now makes possible, and some providers REQUIRE the block echoed back — with its
|
|
26
|
+
* `signature` — for the following turn of a tool-use chain to stay valid. A loop that
|
|
27
|
+
* drops them is quietly lossy in a way that shows up as degraded output, not an error.
|
|
28
|
+
*
|
|
29
|
+
* `redactedData` carries provider-redacted reasoning: opaque bytes with no readable
|
|
30
|
+
* text, which still have to be replayed in place. Never render it.
|
|
31
|
+
*/
|
|
32
|
+
type ReasoningBlock = {
|
|
33
|
+
type: 'reasoning';
|
|
34
|
+
text: string;
|
|
35
|
+
signature?: string;
|
|
36
|
+
redactedData?: string;
|
|
37
|
+
};
|
|
38
|
+
type ToolUseBlock = {
|
|
39
|
+
type: 'tool_use';
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
input: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
type ToolResultBlock = {
|
|
45
|
+
type: 'tool_result';
|
|
46
|
+
tool_use_id: string;
|
|
47
|
+
content: string;
|
|
48
|
+
is_error?: boolean;
|
|
49
|
+
};
|
|
50
|
+
type ContentBlock = TextBlock | ToolUseBlock | ToolResultBlock | ImageBlock | ReasoningBlock;
|
|
51
|
+
/** A tool the model may call: name, description, and a JSON Schema for its input
|
|
52
|
+
* (`input_schema`, the Anthropic wire name — {@link createChatModelClient} maps it to the
|
|
53
|
+
* chat slot's `ToolDef`). */
|
|
54
|
+
interface AgentTool {
|
|
55
|
+
name: string;
|
|
56
|
+
description: string;
|
|
57
|
+
input_schema: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
type AgentRole = 'user' | 'assistant';
|
|
60
|
+
interface AgentMessage {
|
|
61
|
+
role: AgentRole;
|
|
62
|
+
content: ContentBlock[];
|
|
63
|
+
}
|
|
64
|
+
/** Provider-reported token counts for one turn (R3-220). `inputTokens` is the size
|
|
65
|
+
* of everything the provider processed this turn; `outputTokens` is what it
|
|
66
|
+
* generated. Absent when the provider emits no `usage` delta. */
|
|
67
|
+
interface TokenUsage {
|
|
68
|
+
inputTokens: number;
|
|
69
|
+
outputTokens: number;
|
|
70
|
+
/** R3-336 — prompt-cache counters, present only where the provider reports them.
|
|
71
|
+
* ABSENT is not zero: it means this provider says nothing about caching, which is a
|
|
72
|
+
* different fact from "nothing was cached", and conflating them would turn a
|
|
73
|
+
* measurement into a guess. */
|
|
74
|
+
cacheReadTokens?: number;
|
|
75
|
+
cacheWriteTokens?: number;
|
|
76
|
+
}
|
|
77
|
+
/** One model turn: the assistant's emitted blocks + why it stopped (+ usage). */
|
|
78
|
+
interface ModelResponse {
|
|
79
|
+
content: (TextBlock | ToolUseBlock | ReasoningBlock)[];
|
|
80
|
+
/** Anthropic stop_reason: 'end_turn' | 'tool_use' | 'max_tokens' | 'refusal' | … */
|
|
81
|
+
stopReason: string;
|
|
82
|
+
/** Provider token counts for this turn, when reported (R3-220 accounting). */
|
|
83
|
+
usage?: TokenUsage;
|
|
84
|
+
}
|
|
85
|
+
/** The provider seam — one model turn. Implemented by `chatModelClient.ts` over
|
|
86
|
+
* the host `chat()` slot; faked in tests. When the client streams, it calls
|
|
87
|
+
* `onTextDelta` with each token slice as it arrives (the assembled turn is still
|
|
88
|
+
* returned whole); a non-streaming client simply never calls it. */
|
|
89
|
+
interface ModelClient {
|
|
90
|
+
createMessage(req: {
|
|
91
|
+
system?: string;
|
|
92
|
+
messages: AgentMessage[];
|
|
93
|
+
tools: AgentTool[];
|
|
94
|
+
/** Called with incremental assistant-text slices during a streamed turn. */
|
|
95
|
+
onTextDelta?: (text: string) => void;
|
|
96
|
+
/** R3-335: incremental REASONING slices, for a live thinking surface. Never called
|
|
97
|
+
* by a provider that does not emit reasoning. */
|
|
98
|
+
onReasoningDelta?: (text: string) => void;
|
|
99
|
+
/** R3-224: aborts the in-flight turn — the host stops the upstream provider
|
|
100
|
+
* request and stops billing, not just the app-side stream (§3.3). */
|
|
101
|
+
signal?: AbortSignal;
|
|
102
|
+
}): Promise<ModelResponse>;
|
|
103
|
+
}
|
|
104
|
+
/** Executes one tool call, returning a string result (and whether it errored —
|
|
105
|
+
* a `forbidden`/failed call comes back as `is_error` so the model can adapt). */
|
|
106
|
+
type ToolExecutor = (name: string, input: Record<string, unknown>) => Promise<ToolOutcome>;
|
|
107
|
+
/** What one tool call produced. `images` (R3-339) is how a tool hands the model
|
|
108
|
+
* something to LOOK at; `content` still carries the text the model reads. */
|
|
109
|
+
interface ToolOutcome {
|
|
110
|
+
content: string;
|
|
111
|
+
isError?: boolean;
|
|
112
|
+
images?: ImageBlock[];
|
|
113
|
+
}
|
|
114
|
+
/** Why a no-tool-call turn looked like a stall rather than a genuine finish. */
|
|
115
|
+
type StallReason = 'empty' | 'announced-no-call';
|
|
116
|
+
/** Optional UI hooks so a panel can render the loop as it runs. */
|
|
117
|
+
interface AgentEvents {
|
|
118
|
+
/** A streamed token slice of the in-flight assistant turn (live preview). */
|
|
119
|
+
onAssistantDelta?(text: string): void;
|
|
120
|
+
/** The complete assistant text for a turn, once the turn is in. */
|
|
121
|
+
onAssistantText?(text: string): void;
|
|
122
|
+
onToolUse?(name: string, input: Record<string, unknown>): void;
|
|
123
|
+
onToolResult?(name: string, result: ToolOutcome): void;
|
|
124
|
+
/** Fired when the loop nudges a STALLED turn (the model ended without a tool
|
|
125
|
+
* call despite empty or "I'll do X" intent text) back into action, so a panel
|
|
126
|
+
* can show "nudging the model to continue" rather than a silent stall. */
|
|
127
|
+
onNudge?(reason: StallReason): void;
|
|
128
|
+
/** Fired after every turn with the running context size + window (R3-220
|
|
129
|
+
* loop-observability). `contextTokens` is provider-reported when available, else
|
|
130
|
+
* a char/4 estimate. */
|
|
131
|
+
onUsage?(usage: {
|
|
132
|
+
contextTokens: number;
|
|
133
|
+
window?: number;
|
|
134
|
+
spentTokens: number;
|
|
135
|
+
/** R3-336 — cumulative cache reads/writes across the run, on providers that report
|
|
136
|
+
* them. Surfacing this is what makes the caching claim verifiable rather than
|
|
137
|
+
* believed; `undefined` means the provider reported nothing. */
|
|
138
|
+
cacheReadTokens?: number;
|
|
139
|
+
cacheWriteTokens?: number;
|
|
140
|
+
}): void;
|
|
141
|
+
/** Fired when the loop compacts the transcript to stay under the context window;
|
|
142
|
+
* `summarizedCount` is how many older messages were folded into the summary.
|
|
143
|
+
*
|
|
144
|
+
* R3-336: a compaction invalidates the conversation-prefix cache it rewrote — the
|
|
145
|
+
* durable system+tools prefix survives it — so the next turn pays one prefix
|
|
146
|
+
* re-write. `cacheReadTokens`/`cacheWriteTokens` are the run totals AT the
|
|
147
|
+
* compaction, which is what lets the cost curve across it be read off rather than
|
|
148
|
+
* assumed (exit 2). */
|
|
149
|
+
onCompact?(info: {
|
|
150
|
+
summarizedCount: number;
|
|
151
|
+
cacheReadTokens?: number;
|
|
152
|
+
cacheWriteTokens?: number;
|
|
153
|
+
}): void;
|
|
154
|
+
/** Fired when the loop stops because the token/spend budget was exhausted. */
|
|
155
|
+
onBudgetStop?(info: {
|
|
156
|
+
spentTokens: number;
|
|
157
|
+
tokenBudget: number;
|
|
158
|
+
}): void;
|
|
159
|
+
/** Fired when a turn was truncated (`max_tokens`) while emitting tool calls, so
|
|
160
|
+
* the partial calls were failed-and-re-prompted rather than executed (R3-220 F3). */
|
|
161
|
+
onTruncatedToolCall?(): void;
|
|
162
|
+
/** R3-335: a streamed slice of the model's reasoning, for a live thinking surface. */
|
|
163
|
+
onReasoningDelta?(text: string): void;
|
|
164
|
+
/** R3-335: the complete reasoning block for a turn, once the turn is in. */
|
|
165
|
+
onReasoning?(block: ReasoningBlock): void;
|
|
166
|
+
/** R3-333: the loop applied the user's mid-run correction(s). `interrupted` is
|
|
167
|
+
* true when an `interrupt`-mode steer cut an in-flight model turn short (as
|
|
168
|
+
* opposed to being applied at an ordinary turn boundary). */
|
|
169
|
+
onSteer?(info: {
|
|
170
|
+
messages: SteerMessage[];
|
|
171
|
+
interrupted: boolean;
|
|
172
|
+
}): void;
|
|
173
|
+
}
|
|
174
|
+
interface RunAgentOptions {
|
|
175
|
+
client: ModelClient;
|
|
176
|
+
tools: AgentTool[];
|
|
177
|
+
execute: ToolExecutor;
|
|
178
|
+
system?: string;
|
|
179
|
+
/** Prior turns of this conversation, replayed before the new prompt so a
|
|
180
|
+
* follow-up has context (the conversation stage seeds this from the store). */
|
|
181
|
+
history?: AgentMessage[];
|
|
182
|
+
/** The user's instruction that kicks off the loop. */
|
|
183
|
+
prompt: string;
|
|
184
|
+
/** Large safety-stop on model turns (default 100). No longer the primary bound —
|
|
185
|
+
* a long task is bounded by `tokenBudget` + compaction; this just backstops a
|
|
186
|
+
* pathological loop the budget/compaction somehow miss. */
|
|
187
|
+
maxTurns?: number;
|
|
188
|
+
/** Max consecutive "you announced work but emitted no tool call" nudges before
|
|
189
|
+
* the loop gives up (default 1). GLM-over-OpenRouter intermittently ends a turn
|
|
190
|
+
* with future-tense intent ("I'll read the files…") or an EMPTY turn right after
|
|
191
|
+
* a tool error — no tool call, a silent stall (tutorial findings §2). One nudge
|
|
192
|
+
* recovers most of these; the cap keeps a genuinely-finished model (which answers
|
|
193
|
+
* the nudge with another call-free turn) from looping, and the budget resets on
|
|
194
|
+
* any turn that DID call a tool, so a long task's later stall is still covered.
|
|
195
|
+
* Set 0 to disable the backstop. */
|
|
196
|
+
maxNudges?: number;
|
|
197
|
+
/** The resolved provider's context window (`describeChat().features.maxContextTokens`).
|
|
198
|
+
* Compaction is disabled when this is absent/0 — the loop then behaves as before. */
|
|
199
|
+
contextWindow?: number;
|
|
200
|
+
/** Headroom left below the window before compacting (default: 25% of the window). */
|
|
201
|
+
reserveTokens?: number;
|
|
202
|
+
/** Recent messages kept verbatim across a compaction (default 8). */
|
|
203
|
+
keepRecentTurns?: number;
|
|
204
|
+
/** Cumulative token budget (input+output across turns). When exceeded the loop
|
|
205
|
+
* stops — the runaway-cost guard that replaces the raw 12-turn cap. Off when unset. */
|
|
206
|
+
tokenBudget?: number;
|
|
207
|
+
/** Max consecutive truncated-tool-call re-prompts before giving up (default 2). */
|
|
208
|
+
maxTruncationRetries?: number;
|
|
209
|
+
/** R3-224 (§3.3): the stop button. When it fires the loop stops between turns AND
|
|
210
|
+
* aborts the in-flight model turn (the host tears down the upstream provider
|
|
211
|
+
* request and stops billing) — not merely the between-turn loop. The transcript so
|
|
212
|
+
* far is returned; an abort is a clean stop, never a thrown error. */
|
|
213
|
+
signal?: AbortSignal;
|
|
214
|
+
/** R3-333: the mid-run steering queue. The loop drains it at every turn boundary
|
|
215
|
+
* and folds each correction in as a `user` message, so the human can redirect a
|
|
216
|
+
* run without restarting it and paying for the transcript again. Its `interrupt`
|
|
217
|
+
* signal aborts the in-flight MODEL turn only — never a tool batch, which must
|
|
218
|
+
* keep every `tool_use` paired with a `tool_result`. Absent ⇒ the loop behaves
|
|
219
|
+
* exactly as before. */
|
|
220
|
+
steering?: SteerSource;
|
|
221
|
+
events?: AgentEvents;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Classify a NO-tool-call turn as a stall (nudge-worthy) vs a genuine finish.
|
|
225
|
+
* GLM-over-OpenRouter intermittently (a) writes "I'll read the files…" then ends
|
|
226
|
+
* with no call, or (b) returns an EMPTY turn after a tool error — both silent
|
|
227
|
+
* give-ups (tutorial findings §2). Conservative on purpose: a real wrap-up (a
|
|
228
|
+
* summary, "Done", "I've created…") returns null so the loop never nudges a
|
|
229
|
+
* finished agent. Empty text is always a stall (there is nothing a finished agent
|
|
230
|
+
* would say with zero words).
|
|
231
|
+
*/
|
|
232
|
+
declare function detectStall(text: string): StallReason | null;
|
|
233
|
+
declare const NUDGE_TEXT = "You ended your turn without calling a tool. If the task is already complete, say so plainly in one line and stop. Otherwise don't just describe the next step \u2014 emit the tool call now.";
|
|
234
|
+
/** Rough token estimate (~4 chars/token) over a message array, used only when the
|
|
235
|
+
* provider reports no `usage` delta. Conservative by design (over- not under-counts
|
|
236
|
+
* by treating structured blocks as their JSON length). */
|
|
237
|
+
declare function estimateTokens(messages: AgentMessage[]): number;
|
|
238
|
+
/** Should the loop compact now? True once the running context passes
|
|
239
|
+
* `window − reserveTokens`. Disabled (false) when there is no window. */
|
|
240
|
+
declare function shouldCompact(contextTokens: number, window: number | undefined, reserveTokens: number): boolean;
|
|
241
|
+
/** Prefix marking a `user` message as a compaction summary (not a real user turn),
|
|
242
|
+
* so the transcript renderer shows a "compacted N turns" affordance on replay. */
|
|
243
|
+
declare const COMPACTION_MARKER = "\u241F[compacted-context]\n";
|
|
244
|
+
/** Compact `messages` by folding the older head into a structured summary and keeping
|
|
245
|
+
* a verbatim recent tail. The tail is snapped to start at an `assistant` message so a
|
|
246
|
+
* `tool_use`/`tool_result` pair is never split (which would malform the next request).
|
|
247
|
+
* The taint tier is NOT modelled on messages (it is run-scoped host state, R-ASG-2):
|
|
248
|
+
* this is a pure content transform over the SAME session — it starts no new external
|
|
249
|
+
* read — so it cannot launder taint (F6). Returns the original array unchanged when
|
|
250
|
+
* there is nothing safe to summarize. */
|
|
251
|
+
declare function compactTranscript(messages: AgentMessage[], client: ModelClient, keepRecentTurns: number): Promise<{
|
|
252
|
+
messages: AgentMessage[];
|
|
253
|
+
summarizedCount: number;
|
|
254
|
+
}>;
|
|
255
|
+
/** Does this thrown error look like a hard context-window overflow? Used to trigger
|
|
256
|
+
* recover-then-retry compaction (F3/exit-c) rather than a dead loop. */
|
|
257
|
+
declare function isContextOverflow(e: unknown): boolean;
|
|
258
|
+
/**
|
|
259
|
+
* Drive the agent loop to completion. Returns the full message transcript
|
|
260
|
+
* (including the kickoff user turn). Stops when the model returns without tool
|
|
261
|
+
* calls (or a terminal stop reason), when the token budget is exhausted, or when
|
|
262
|
+
* `maxTurns` (a large safety-stop) is reached. With a `contextWindow` set, the loop
|
|
263
|
+
* accounts tokens and compacts automatically so it can run long.
|
|
264
|
+
*/
|
|
265
|
+
declare function runAgent(opts: RunAgentOptions): Promise<AgentMessage[]>;
|
|
266
|
+
|
|
267
|
+
export { type AgentEvents, type AgentMessage, type AgentRole, type AgentTool, COMPACTION_MARKER, type ContentBlock, type ImageBlock, type ModelClient, type ModelResponse, NUDGE_TEXT, type ReasoningBlock, type RunAgentOptions, type StallReason, type TextBlock, type TokenUsage, type ToolExecutor, type ToolOutcome, type ToolResultBlock, type ToolUseBlock, compactTranscript, detectStall, estimateTokens, isContextOverflow, runAgent, shouldCompact };
|