@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.
Files changed (131) hide show
  1. package/dist/agentChatClient.cjs +112 -0
  2. package/dist/agentChatClient.cjs.map +1 -0
  3. package/dist/agentChatClient.d.cts +9 -0
  4. package/dist/agentChatClient.d.ts +9 -0
  5. package/dist/agentChatClient.js +89 -0
  6. package/dist/agentChatClient.js.map +1 -0
  7. package/dist/agentContext.cjs +69 -0
  8. package/dist/agentContext.cjs.map +1 -0
  9. package/dist/agentContext.d.cts +45 -0
  10. package/dist/agentContext.d.ts +45 -0
  11. package/dist/agentContext.js +45 -0
  12. package/dist/agentContext.js.map +1 -0
  13. package/dist/agentLoop.cjs +301 -0
  14. package/dist/agentLoop.cjs.map +1 -0
  15. package/dist/agentLoop.d.cts +267 -0
  16. package/dist/agentLoop.d.ts +267 -0
  17. package/dist/agentLoop.js +271 -0
  18. package/dist/agentLoop.js.map +1 -0
  19. package/dist/agentSteering.cjs +127 -0
  20. package/dist/agentSteering.cjs.map +1 -0
  21. package/dist/agentSteering.d.cts +80 -0
  22. package/dist/agentSteering.d.ts +80 -0
  23. package/dist/agentSteering.js +98 -0
  24. package/dist/agentSteering.js.map +1 -0
  25. package/dist/auth.cjs.map +1 -1
  26. package/dist/auth.d.cts +33 -2
  27. package/dist/auth.d.ts +33 -2
  28. package/dist/auth.js.map +1 -1
  29. package/dist/bundle.cjs +91 -0
  30. package/dist/bundle.cjs.map +1 -0
  31. package/dist/bundle.d.cts +63 -0
  32. package/dist/bundle.d.ts +63 -0
  33. package/dist/bundle.js +62 -0
  34. package/dist/bundle.js.map +1 -0
  35. package/dist/collectHeadings.cjs +59 -0
  36. package/dist/collectHeadings.cjs.map +1 -0
  37. package/dist/collectHeadings.d.cts +16 -0
  38. package/dist/collectHeadings.d.ts +16 -0
  39. package/dist/collectHeadings.js +36 -0
  40. package/dist/collectHeadings.js.map +1 -0
  41. package/dist/corpus.cjs +9 -58
  42. package/dist/corpus.cjs.map +1 -1
  43. package/dist/corpus.d.cts +3 -63
  44. package/dist/corpus.d.ts +3 -63
  45. package/dist/corpus.js +15 -57
  46. package/dist/corpus.js.map +1 -1
  47. package/dist/debug.cjs +115 -0
  48. package/dist/debug.cjs.map +1 -1
  49. package/dist/debug.js +115 -0
  50. package/dist/debug.js.map +1 -1
  51. package/dist/fence.cjs +49 -0
  52. package/dist/fence.cjs.map +1 -0
  53. package/dist/fence.d.cts +4 -0
  54. package/dist/fence.d.ts +4 -0
  55. package/dist/fence.js +26 -0
  56. package/dist/fence.js.map +1 -0
  57. package/dist/formFactor.cjs.map +1 -1
  58. package/dist/formFactor.d.cts +11 -2
  59. package/dist/formFactor.d.ts +11 -2
  60. package/dist/formFactor.js.map +1 -1
  61. package/dist/fs.cjs.map +1 -1
  62. package/dist/fs.d.cts +16 -6
  63. package/dist/fs.d.ts +16 -6
  64. package/dist/fs.js.map +1 -1
  65. package/dist/hooks.cjs.map +1 -1
  66. package/dist/hooks.d.cts +7 -0
  67. package/dist/hooks.d.ts +7 -0
  68. package/dist/hooks.js.map +1 -1
  69. package/dist/index.cjs +21 -1
  70. package/dist/index.cjs.map +1 -1
  71. package/dist/index.d.cts +12 -3
  72. package/dist/index.d.ts +12 -3
  73. package/dist/index.js +10 -0
  74. package/dist/index.js.map +1 -1
  75. package/dist/launch.cjs +45 -6
  76. package/dist/launch.cjs.map +1 -1
  77. package/dist/launch.d.cts +3 -0
  78. package/dist/launch.d.ts +3 -0
  79. package/dist/launch.js +45 -6
  80. package/dist/launch.js.map +1 -1
  81. package/dist/llm.cjs.map +1 -1
  82. package/dist/llm.d.cts +20 -3
  83. package/dist/llm.d.ts +20 -3
  84. package/dist/llm.js.map +1 -1
  85. package/dist/metadataQueryTool.cjs +271 -0
  86. package/dist/metadataQueryTool.cjs.map +1 -0
  87. package/dist/metadataQueryTool.d.cts +159 -0
  88. package/dist/metadataQueryTool.d.ts +159 -0
  89. package/dist/metadataQueryTool.js +241 -0
  90. package/dist/metadataQueryTool.js.map +1 -0
  91. package/dist/mounts.cjs.map +1 -1
  92. package/dist/mounts.d.cts +22 -3
  93. package/dist/mounts.d.ts +22 -3
  94. package/dist/mounts.js.map +1 -1
  95. package/dist/onFsChange.cjs +13 -1
  96. package/dist/onFsChange.cjs.map +1 -1
  97. package/dist/onFsChange.d.cts +21 -1
  98. package/dist/onFsChange.d.ts +21 -1
  99. package/dist/onFsChange.js +13 -1
  100. package/dist/onFsChange.js.map +1 -1
  101. package/dist/protocolSchemes.cjs +1 -0
  102. package/dist/protocolSchemes.cjs.map +1 -1
  103. package/dist/protocolSchemes.d.cts +1 -0
  104. package/dist/protocolSchemes.d.ts +1 -0
  105. package/dist/protocolSchemes.js +2 -0
  106. package/dist/protocolSchemes.js.map +1 -1
  107. package/dist/recents.cjs +41 -0
  108. package/dist/recents.cjs.map +1 -0
  109. package/dist/recents.d.cts +23 -0
  110. package/dist/recents.d.ts +23 -0
  111. package/dist/recents.js +17 -0
  112. package/dist/recents.js.map +1 -0
  113. package/dist/tasks.cjs +41 -7
  114. package/dist/tasks.cjs.map +1 -1
  115. package/dist/tasks.d.cts +136 -7
  116. package/dist/tasks.d.ts +136 -7
  117. package/dist/tasks.js +39 -7
  118. package/dist/tasks.js.map +1 -1
  119. package/dist/version.cjs +1 -1
  120. package/dist/version.cjs.map +1 -1
  121. package/dist/version.d.cts +1 -1
  122. package/dist/version.d.ts +1 -1
  123. package/dist/version.js +1 -1
  124. package/dist/version.js.map +1 -1
  125. package/dist/workspace.cjs +53 -0
  126. package/dist/workspace.cjs.map +1 -0
  127. package/dist/workspace.d.cts +58 -0
  128. package/dist/workspace.d.ts +58 -0
  129. package/dist/workspace.js +28 -0
  130. package/dist/workspace.js.map +1 -0
  131. 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 };