fourmis-agents-sdk 0.3.0 → 0.4.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 (102) hide show
  1. package/dist/agent-loop.d.ts +21 -3
  2. package/dist/agent-loop.d.ts.map +1 -1
  3. package/dist/agent-loop.js +294 -90
  4. package/dist/agents/index.js +2798 -1857
  5. package/dist/agents/task-manager.js +15 -0
  6. package/dist/agents/tools.d.ts.map +1 -1
  7. package/dist/agents/tools.js +2798 -1857
  8. package/dist/agents/types.d.ts +4 -0
  9. package/dist/agents/types.d.ts.map +1 -1
  10. package/dist/api.d.ts +8 -5
  11. package/dist/api.d.ts.map +1 -1
  12. package/dist/api.js +2394 -886
  13. package/dist/auth/gemini-oauth.js +15 -0
  14. package/dist/auth/login-openai.js +15 -0
  15. package/dist/auth/openai-oauth.js +15 -0
  16. package/dist/hooks.d.ts +19 -1
  17. package/dist/hooks.d.ts.map +1 -1
  18. package/dist/hooks.js +42 -2
  19. package/dist/index.d.ts +10 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +2407 -887
  22. package/dist/mcp/client.d.ts +7 -0
  23. package/dist/mcp/client.d.ts.map +1 -1
  24. package/dist/mcp/client.js +146 -12
  25. package/dist/mcp/index.js +146 -12
  26. package/dist/mcp/server.js +15 -0
  27. package/dist/mcp/types.d.ts +19 -1
  28. package/dist/mcp/types.d.ts.map +1 -1
  29. package/dist/memory/index.js +15 -0
  30. package/dist/memory/memory-handler.js +15 -0
  31. package/dist/permissions.d.ts.map +1 -1
  32. package/dist/permissions.js +22 -3
  33. package/dist/providers/anthropic.d.ts.map +1 -1
  34. package/dist/providers/anthropic.js +56 -2
  35. package/dist/providers/gemini.js +15 -0
  36. package/dist/providers/openai.js +15 -0
  37. package/dist/providers/registry.js +56 -2
  38. package/dist/providers/types.d.ts +4 -1
  39. package/dist/providers/types.d.ts.map +1 -1
  40. package/dist/query.d.ts +21 -2
  41. package/dist/query.d.ts.map +1 -1
  42. package/dist/query.js +84 -1
  43. package/dist/settings.js +15 -0
  44. package/dist/skills/frontmatter.d.ts +15 -0
  45. package/dist/skills/frontmatter.d.ts.map +1 -0
  46. package/dist/skills/frontmatter.js +66 -0
  47. package/dist/skills/index.d.ts +8 -0
  48. package/dist/skills/index.d.ts.map +1 -0
  49. package/dist/skills/index.js +326 -0
  50. package/dist/skills/skills.d.ts +94 -0
  51. package/dist/skills/skills.d.ts.map +1 -0
  52. package/dist/skills/skills.js +324 -0
  53. package/dist/tools/ask-user-question.d.ts +7 -0
  54. package/dist/tools/ask-user-question.d.ts.map +1 -0
  55. package/dist/tools/ask-user-question.js +63 -0
  56. package/dist/tools/bash.d.ts.map +1 -1
  57. package/dist/tools/bash.js +62 -2
  58. package/dist/tools/config.d.ts +7 -0
  59. package/dist/tools/config.d.ts.map +1 -0
  60. package/dist/tools/config.js +129 -0
  61. package/dist/tools/edit.js +15 -0
  62. package/dist/tools/exit-plan-mode.d.ts +7 -0
  63. package/dist/tools/exit-plan-mode.d.ts.map +1 -0
  64. package/dist/tools/exit-plan-mode.js +49 -0
  65. package/dist/tools/glob.js +15 -0
  66. package/dist/tools/grep.js +15 -0
  67. package/dist/tools/index.d.ts +7 -0
  68. package/dist/tools/index.d.ts.map +1 -1
  69. package/dist/tools/index.js +521 -9
  70. package/dist/tools/mcp-resources.js +15 -0
  71. package/dist/tools/notebook-edit.d.ts +7 -0
  72. package/dist/tools/notebook-edit.d.ts.map +1 -0
  73. package/dist/tools/notebook-edit.js +98 -0
  74. package/dist/tools/presets.d.ts +2 -1
  75. package/dist/tools/presets.d.ts.map +1 -1
  76. package/dist/tools/presets.js +37 -4
  77. package/dist/tools/read.d.ts.map +1 -1
  78. package/dist/tools/read.js +27 -1
  79. package/dist/tools/registry.d.ts +2 -0
  80. package/dist/tools/registry.d.ts.map +1 -1
  81. package/dist/tools/registry.js +25 -0
  82. package/dist/tools/todo-write.d.ts +7 -0
  83. package/dist/tools/todo-write.d.ts.map +1 -0
  84. package/dist/tools/todo-write.js +84 -0
  85. package/dist/tools/web-fetch.d.ts +6 -0
  86. package/dist/tools/web-fetch.d.ts.map +1 -0
  87. package/dist/tools/web-fetch.js +100 -0
  88. package/dist/tools/web-search.d.ts +7 -0
  89. package/dist/tools/web-search.d.ts.map +1 -0
  90. package/dist/tools/web-search.js +93 -0
  91. package/dist/tools/write.js +15 -0
  92. package/dist/types.d.ts +360 -42
  93. package/dist/types.d.ts.map +1 -1
  94. package/dist/types.js +15 -0
  95. package/dist/utils/cost.js +15 -0
  96. package/dist/utils/session-store.d.ts +1 -1
  97. package/dist/utils/session-store.d.ts.map +1 -1
  98. package/dist/utils/session-store.js +64 -2
  99. package/dist/utils/system-prompt.d.ts +4 -0
  100. package/dist/utils/system-prompt.d.ts.map +1 -1
  101. package/dist/utils/system-prompt.js +326 -6
  102. package/package.json +4 -2
@@ -3,13 +3,13 @@
3
3
  *
4
4
  * AsyncGenerator that orchestrates:
5
5
  * 1. Call LLM via provider adapter
6
- * 2. Stream text deltas as AgentMessage events
6
+ * 2. Stream partial assistant deltas (optional)
7
7
  * 3. Collect tool calls
8
8
  * 4. Execute tools with permission checks
9
9
  * 5. Feed results back to LLM
10
10
  * 6. Repeat until done or limits reached
11
11
  */
12
- import type { AgentMessage } from "./types.js";
12
+ import type { AgentMessage, OutputFormat, ThinkingConfig, Effort, SdkBeta, SdkPluginConfig } from "./types.js";
13
13
  import type { ProviderAdapter, NormalizedMessage, NormalizedContent } from "./providers/types.js";
14
14
  import type { ToolRegistry } from "./tools/registry.js";
15
15
  import type { PermissionManager } from "./permissions.js";
@@ -20,6 +20,16 @@ export type SessionLogger = (role: "user" | "assistant", content: NormalizedCont
20
20
  export type AgentLoopOptions = {
21
21
  provider: ProviderAdapter;
22
22
  model: string;
23
+ fallbackModel?: string;
24
+ modelState?: {
25
+ current: string;
26
+ };
27
+ maxThinkingTokensState?: {
28
+ current: number | undefined;
29
+ };
30
+ thinking?: ThinkingConfig;
31
+ effort?: Effort;
32
+ outputFormat?: OutputFormat;
23
33
  systemPrompt: string;
24
34
  tools: ToolRegistry;
25
35
  permissions: PermissionManager;
@@ -27,7 +37,7 @@ export type AgentLoopOptions = {
27
37
  sessionId: string;
28
38
  maxTurns: number;
29
39
  maxBudgetUsd: number;
30
- includeStreamEvents: boolean;
40
+ includePartialMessages: boolean;
31
41
  signal: AbortSignal;
32
42
  env?: Record<string, string>;
33
43
  debug?: boolean;
@@ -37,6 +47,14 @@ export type AgentLoopOptions = {
37
47
  sessionLogger?: SessionLogger;
38
48
  /** Native memory tool for Anthropic provider (handled specially) */
39
49
  nativeMemoryTool?: NativeMemoryTool;
50
+ initMeta?: {
51
+ agents?: string[];
52
+ betas?: SdkBeta[];
53
+ slashCommands?: string[];
54
+ outputStyle?: string;
55
+ skills?: string[];
56
+ plugins?: SdkPluginConfig[];
57
+ };
40
58
  };
41
59
  export declare function agentLoop(prompt: string, options: AgentLoopOptions): AsyncGenerator<AgentMessage>;
42
60
  //# sourceMappingURL=agent-loop.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,iBAAiB,EAAa,MAAM,sBAAsB,CAAC;AAC7G,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,qBAAqB,CAAC;AACrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,MAAM,MAAM,aAAa,GAAG,CAC1B,IAAI,EAAE,MAAM,GAAG,WAAW,EAC1B,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,EACrC,UAAU,EAAE,MAAM,GAAG,IAAI,KACtB,MAAM,CAAC;AAEZ,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,MAAM,EAAE,WAAW,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,gBAAgB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACvC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC,CAAC;AAEF,wBAAuB,SAAS,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,gBAAgB,GACxB,cAAc,CAAC,YAAY,CAAC,CAkZ9B"}
1
+ {"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,YAAY,EAMZ,YAAY,EACZ,cAAc,EACd,MAAM,EAEN,OAAO,EACP,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAClG,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,qBAAqB,CAAC;AACrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,MAAM,MAAM,aAAa,GAAG,CAC1B,IAAI,EAAE,MAAM,GAAG,WAAW,EAC1B,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,EACrC,UAAU,EAAE,MAAM,GAAG,IAAI,KACtB,MAAM,CAAC;AAEZ,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC,sBAAsB,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACzD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,OAAO,CAAC;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,gBAAgB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACvC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;QAClB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;KAC7B,CAAC;CACH,CAAC;AA+DF,wBAAuB,SAAS,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,gBAAgB,GACxB,cAAc,CAAC,YAAY,CAAC,CAwjB9B"}
@@ -1,5 +1,20 @@
1
1
  // @bun
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
2
4
  var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
3
18
  var __export = (target, all) => {
4
19
  for (var name in all)
5
20
  __defProp(target, name, {
@@ -104,10 +119,57 @@ function mergeUsage(a, b) {
104
119
  }
105
120
 
106
121
  // src/agent-loop.ts
122
+ function makeModelUsageEntry() {
123
+ return {
124
+ inputTokens: 0,
125
+ outputTokens: 0,
126
+ cacheReadInputTokens: 0,
127
+ cacheCreationInputTokens: 0,
128
+ totalCostUsd: 0
129
+ };
130
+ }
131
+ function makeErrorResult(params) {
132
+ return {
133
+ type: "result",
134
+ subtype: params.subtype,
135
+ duration_ms: Date.now() - params.startTime,
136
+ duration_api_ms: params.apiTimeMs,
137
+ is_error: true,
138
+ num_turns: params.turns,
139
+ stop_reason: null,
140
+ total_cost_usd: params.costUsd,
141
+ usage: params.usage,
142
+ modelUsage: params.modelUsage,
143
+ permission_denials: params.permissionDenials,
144
+ errors: params.errors,
145
+ uuid: uuid(),
146
+ session_id: params.sessionId
147
+ };
148
+ }
149
+ function extractStructuredJson(text) {
150
+ const trimmed = text.trim();
151
+ if (!trimmed) {
152
+ return { ok: false, error: "Empty result text; expected JSON output." };
153
+ }
154
+ const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
155
+ const candidate = fenced ? fenced[1].trim() : trimmed;
156
+ try {
157
+ return { ok: true, value: JSON.parse(candidate) };
158
+ } catch (err) {
159
+ const message = err instanceof Error ? err.message : String(err);
160
+ return { ok: false, error: `Invalid JSON output: ${message}` };
161
+ }
162
+ }
107
163
  async function* agentLoop(prompt, options) {
108
164
  const {
109
165
  provider,
110
166
  model,
167
+ fallbackModel,
168
+ modelState,
169
+ maxThinkingTokensState,
170
+ thinking,
171
+ effort,
172
+ outputFormat,
111
173
  systemPrompt,
112
174
  tools,
113
175
  permissions,
@@ -115,7 +177,7 @@ async function* agentLoop(prompt, options) {
115
177
  sessionId,
116
178
  maxTurns,
117
179
  maxBudgetUsd,
118
- includeStreamEvents,
180
+ includePartialMessages,
119
181
  signal,
120
182
  env,
121
183
  debug,
@@ -123,14 +185,17 @@ async function* agentLoop(prompt, options) {
123
185
  mcpClient,
124
186
  previousMessages,
125
187
  sessionLogger,
126
- nativeMemoryTool
188
+ nativeMemoryTool,
189
+ initMeta
127
190
  } = options;
191
+ const effectiveModelState = modelState ?? { current: model };
128
192
  const startTime = Date.now();
129
193
  let apiTimeMs = 0;
130
194
  let turns = 0;
131
195
  let totalUsage = emptyTokenUsage();
132
196
  let costUsd = 0;
133
197
  const modelUsage = {};
198
+ const permissionDenials = [];
134
199
  if (mcpClient) {
135
200
  await mcpClient.connectAll();
136
201
  for (const tool of mcpClient.getTools()) {
@@ -148,56 +213,135 @@ async function* agentLoop(prompt, options) {
148
213
  sessionLogger("user", prompt, null);
149
214
  }
150
215
  yield {
151
- type: "init",
152
- sessionId,
153
- model,
154
- provider: provider.name,
216
+ type: "system",
217
+ subtype: "init",
218
+ apiKeySource: "user",
219
+ claude_code_version: "fourmis-agent-sdk",
220
+ session_id: sessionId,
221
+ model: effectiveModelState.current,
155
222
  tools: tools.list(),
156
223
  cwd,
224
+ mcp_servers: (mcpClient?.status() ?? []).map((s) => ({ name: s.name, status: s.status })),
225
+ permissionMode: permissions.getMode(),
226
+ agents: initMeta?.agents,
227
+ betas: initMeta?.betas,
228
+ slash_commands: initMeta?.slashCommands ?? [],
229
+ output_style: initMeta?.outputStyle ?? "default",
230
+ skills: initMeta?.skills ?? [],
231
+ plugins: (initMeta?.plugins ?? []).map((p) => ({ name: p.path.split("/").pop() ?? p.path, path: p.path })),
157
232
  uuid: uuid()
158
233
  };
159
234
  if (hooks) {
160
- await hooks.fire("SessionStart", { event: "SessionStart", session_id: sessionId }, undefined, { signal });
235
+ await hooks.fire("Setup", {
236
+ event: "Setup",
237
+ hook_event_name: "Setup",
238
+ trigger: "init",
239
+ session_id: sessionId,
240
+ cwd,
241
+ permission_mode: permissions.getMode()
242
+ }, undefined, { signal });
243
+ }
244
+ if (hooks) {
245
+ await hooks.fire("SessionStart", {
246
+ event: "SessionStart",
247
+ hook_event_name: "SessionStart",
248
+ session_id: sessionId,
249
+ source: "startup",
250
+ model: effectiveModelState.current,
251
+ cwd,
252
+ permission_mode: permissions.getMode()
253
+ }, undefined, { signal });
161
254
  }
162
255
  while (true) {
163
256
  if (signal.aborted) {
164
- yield makeError("error_execution", ["Aborted"], turns, costUsd, sessionId, startTime);
257
+ yield makeErrorResult({
258
+ subtype: "error_during_execution",
259
+ errors: ["Aborted"],
260
+ turns,
261
+ costUsd,
262
+ sessionId,
263
+ startTime,
264
+ apiTimeMs,
265
+ usage: totalUsage,
266
+ modelUsage,
267
+ permissionDenials
268
+ });
165
269
  return;
166
270
  }
167
271
  if (turns >= maxTurns) {
168
- yield makeError("error_max_turns", [`Reached maximum turns (${maxTurns})`], turns, costUsd, sessionId, startTime);
272
+ yield makeErrorResult({
273
+ subtype: "error_max_turns",
274
+ errors: [`Reached maximum turns (${maxTurns})`],
275
+ turns,
276
+ costUsd,
277
+ sessionId,
278
+ startTime,
279
+ apiTimeMs,
280
+ usage: totalUsage,
281
+ modelUsage,
282
+ permissionDenials
283
+ });
169
284
  return;
170
285
  }
171
286
  if (maxBudgetUsd > 0 && costUsd >= maxBudgetUsd) {
172
- yield makeError("error_max_budget", [`Reached budget limit ($${maxBudgetUsd})`], turns, costUsd, sessionId, startTime);
287
+ yield makeErrorResult({
288
+ subtype: "error_max_budget_usd",
289
+ errors: [],
290
+ turns,
291
+ costUsd,
292
+ sessionId,
293
+ startTime,
294
+ apiTimeMs,
295
+ usage: totalUsage,
296
+ modelUsage,
297
+ permissionDenials
298
+ });
173
299
  return;
174
300
  }
301
+ const activeModel = effectiveModelState.current;
175
302
  const toolDefs = tools.getDefinitions();
176
303
  const apiStart = Date.now();
177
304
  let assistantTextParts = [];
178
- let toolCalls = [];
305
+ const toolCalls = [];
179
306
  let turnUsage = emptyTokenUsage();
307
+ let turnStopReason = null;
180
308
  const nativeTools = nativeMemoryTool ? [nativeMemoryTool.definition] : undefined;
181
309
  try {
182
310
  const chunks = provider.chat({
183
- model,
311
+ model: activeModel,
184
312
  messages,
185
313
  tools: toolDefs.length > 0 ? toolDefs : undefined,
186
314
  systemPrompt,
187
315
  signal,
188
- nativeTools
316
+ nativeTools,
317
+ thinkingBudget: maxThinkingTokensState?.current,
318
+ thinking,
319
+ effort,
320
+ outputFormat
189
321
  });
190
322
  for await (const chunk of chunks) {
191
323
  switch (chunk.type) {
192
324
  case "text_delta":
193
325
  assistantTextParts.push(chunk.text);
194
- if (includeStreamEvents) {
195
- yield { type: "stream", subtype: "text_delta", text: chunk.text, uuid: uuid() };
326
+ if (includePartialMessages) {
327
+ yield {
328
+ type: "stream_event",
329
+ event: { type: "text_delta", text: chunk.text },
330
+ parent_tool_use_id: null,
331
+ uuid: uuid(),
332
+ session_id: sessionId
333
+ };
196
334
  }
197
335
  break;
198
336
  case "thinking_delta":
199
- if (includeStreamEvents) {
200
- yield { type: "stream", subtype: "thinking_delta", text: chunk.text, uuid: uuid() };
337
+ if (includePartialMessages) {
338
+ yield {
339
+ type: "stream_event",
340
+ event: { type: "thinking_delta", thinking: chunk.text },
341
+ parent_tool_use_id: null,
342
+ uuid: uuid(),
343
+ session_id: sessionId
344
+ };
201
345
  }
202
346
  break;
203
347
  case "tool_call":
@@ -207,33 +351,54 @@ async function* agentLoop(prompt, options) {
207
351
  turnUsage = mergeUsage(turnUsage, chunk.usage);
208
352
  break;
209
353
  case "done":
354
+ turnStopReason = chunk.stopReason ?? null;
210
355
  break;
211
356
  }
212
357
  }
213
358
  } catch (err) {
214
359
  const message = err instanceof Error ? err.message : String(err);
215
- yield makeError("error_execution", [`API error: ${message}`], turns, costUsd, sessionId, startTime);
360
+ if (fallbackModel && activeModel !== fallbackModel) {
361
+ effectiveModelState.current = fallbackModel;
362
+ yield {
363
+ type: "system",
364
+ subtype: "status",
365
+ status: null,
366
+ permissionMode: permissions.getMode(),
367
+ uuid: uuid(),
368
+ session_id: sessionId
369
+ };
370
+ continue;
371
+ }
372
+ yield makeErrorResult({
373
+ subtype: "error_during_execution",
374
+ errors: [`API error: ${message}`],
375
+ turns,
376
+ costUsd,
377
+ sessionId,
378
+ startTime,
379
+ apiTimeMs,
380
+ usage: totalUsage,
381
+ modelUsage,
382
+ permissionDenials
383
+ });
216
384
  return;
217
385
  }
218
386
  apiTimeMs += Date.now() - apiStart;
219
387
  turns++;
220
388
  totalUsage = mergeUsage(totalUsage, turnUsage);
221
- const turnCost = provider.calculateCost(model, turnUsage);
389
+ const turnCost = provider.calculateCost(activeModel, turnUsage);
222
390
  costUsd += turnCost;
223
- if (!modelUsage[model]) {
224
- modelUsage[model] = {
225
- inputTokens: 0,
226
- outputTokens: 0,
227
- cacheReadInputTokens: 0,
228
- cacheCreationInputTokens: 0,
229
- totalCostUsd: 0
230
- };
391
+ if (!modelUsage[activeModel]) {
392
+ modelUsage[activeModel] = makeModelUsageEntry();
231
393
  }
232
- modelUsage[model].inputTokens += turnUsage.inputTokens;
233
- modelUsage[model].outputTokens += turnUsage.outputTokens;
234
- modelUsage[model].cacheReadInputTokens += turnUsage.cacheReadInputTokens;
235
- modelUsage[model].cacheCreationInputTokens += turnUsage.cacheCreationInputTokens;
236
- modelUsage[model].totalCostUsd += turnCost;
394
+ modelUsage[activeModel].inputTokens += turnUsage.inputTokens;
395
+ modelUsage[activeModel].outputTokens += turnUsage.outputTokens;
396
+ modelUsage[activeModel].cacheReadInputTokens += turnUsage.cacheReadInputTokens;
397
+ modelUsage[activeModel].cacheCreationInputTokens += turnUsage.cacheCreationInputTokens;
398
+ modelUsage[activeModel].totalCostUsd += turnCost;
399
+ modelUsage[activeModel].webSearchRequests = (modelUsage[activeModel].webSearchRequests ?? 0) + (turnUsage.webSearchRequests ?? 0);
400
+ modelUsage[activeModel].costUSD = modelUsage[activeModel].totalCostUsd;
401
+ modelUsage[activeModel].contextWindow = provider.getContextWindow(activeModel);
237
402
  const assistantText = assistantTextParts.join("");
238
403
  const assistantContent = [];
239
404
  if (assistantText) {
@@ -251,28 +416,72 @@ async function* agentLoop(prompt, options) {
251
416
  if (sessionLogger) {
252
417
  sessionLogger("assistant", assistantContent, null);
253
418
  }
254
- if (assistantText) {
255
- yield { type: "text", text: assistantText, uuid: uuid() };
256
- }
419
+ yield {
420
+ type: "assistant",
421
+ message: {
422
+ role: "assistant",
423
+ content: assistantContent
424
+ },
425
+ parent_tool_use_id: null,
426
+ uuid: uuid(),
427
+ session_id: sessionId
428
+ };
257
429
  if (toolCalls.length === 0) {
430
+ let structuredOutput;
431
+ if (outputFormat?.type === "json_schema") {
432
+ const parsed = extractStructuredJson(assistantText);
433
+ if (!parsed.ok) {
434
+ yield makeErrorResult({
435
+ subtype: "error_max_structured_output_retries",
436
+ errors: [parsed.error],
437
+ turns,
438
+ costUsd,
439
+ sessionId,
440
+ startTime,
441
+ apiTimeMs,
442
+ usage: totalUsage,
443
+ modelUsage,
444
+ permissionDenials
445
+ });
446
+ return;
447
+ }
448
+ structuredOutput = parsed.value;
449
+ }
258
450
  if (hooks) {
259
- await hooks.fire("Stop", { event: "Stop", session_id: sessionId, text: assistantText || undefined }, undefined, { signal });
451
+ await hooks.fire("Stop", {
452
+ event: "Stop",
453
+ hook_event_name: "Stop",
454
+ session_id: sessionId,
455
+ text: assistantText || undefined,
456
+ stop_reason: turnStopReason ?? undefined
457
+ }, undefined, {
458
+ signal
459
+ });
260
460
  }
261
461
  if (hooks) {
262
- await hooks.fire("SessionEnd", { event: "SessionEnd", session_id: sessionId }, undefined, { signal });
462
+ await hooks.fire("SessionEnd", {
463
+ event: "SessionEnd",
464
+ hook_event_name: "SessionEnd",
465
+ session_id: sessionId,
466
+ reason: "other"
467
+ }, undefined, { signal });
263
468
  }
264
469
  yield {
265
470
  type: "result",
266
471
  subtype: "success",
267
- text: assistantText || null,
268
- turns,
269
- costUsd,
270
- durationMs: Date.now() - startTime,
271
- durationApiMs: apiTimeMs,
272
- sessionId,
472
+ duration_ms: Date.now() - startTime,
473
+ duration_api_ms: apiTimeMs,
474
+ is_error: false,
475
+ num_turns: turns,
476
+ result: assistantText,
477
+ stop_reason: turnStopReason,
478
+ total_cost_usd: costUsd,
273
479
  usage: totalUsage,
274
480
  modelUsage,
275
- uuid: uuid()
481
+ permission_denials: permissionDenials,
482
+ structured_output: structuredOutput,
483
+ uuid: uuid(),
484
+ session_id: sessionId
276
485
  };
277
486
  return;
278
487
  }
@@ -281,7 +490,13 @@ async function* agentLoop(prompt, options) {
281
490
  let hookDenied = false;
282
491
  let hookUpdatedInput;
283
492
  if (hooks) {
284
- const hookResult = await hooks.fire("PreToolUse", { event: "PreToolUse", tool_name: call.name, tool_input: call.input, session_id: sessionId }, call.id, { signal });
493
+ const hookResult = await hooks.fire("PreToolUse", {
494
+ event: "PreToolUse",
495
+ hook_event_name: "PreToolUse",
496
+ tool_name: call.name,
497
+ tool_input: call.input,
498
+ session_id: sessionId
499
+ }, call.id, { signal });
285
500
  if (hookResult) {
286
501
  if (hookResult.permissionDecision === "deny") {
287
502
  hookDenied = true;
@@ -293,14 +508,6 @@ async function* agentLoop(prompt, options) {
293
508
  }
294
509
  if (hookDenied) {
295
510
  const denyContent = "Denied by hook";
296
- yield {
297
- type: "tool_result",
298
- id: call.id,
299
- name: call.name,
300
- content: denyContent,
301
- isError: true,
302
- uuid: uuid()
303
- };
304
511
  toolResults.push({
305
512
  type: "tool_result",
306
513
  tool_use_id: call.id,
@@ -316,14 +523,11 @@ async function* agentLoop(prompt, options) {
316
523
  const permResult = await permissions.check(call.name, inputAfterHook ?? {}, { signal, toolUseId: call.id });
317
524
  if (permResult.behavior === "deny") {
318
525
  const denyContent = `Permission denied: ${permResult.message}`;
319
- yield {
320
- type: "tool_result",
321
- id: call.id,
322
- name: call.name,
323
- content: denyContent,
324
- isError: true,
325
- uuid: uuid()
326
- };
526
+ permissionDenials.push({
527
+ tool_name: call.name,
528
+ tool_use_id: call.id,
529
+ tool_input: inputAfterHook ?? {}
530
+ });
327
531
  toolResults.push({
328
532
  type: "tool_result",
329
533
  tool_use_id: call.id,
@@ -336,13 +540,6 @@ async function* agentLoop(prompt, options) {
336
540
  continue;
337
541
  }
338
542
  const toolInput = permResult.behavior === "allow" && permResult.updatedInput ? permResult.updatedInput : inputAfterHook;
339
- yield {
340
- type: "tool_use",
341
- id: call.id,
342
- name: call.name,
343
- input: toolInput,
344
- uuid: uuid()
345
- };
346
543
  let result;
347
544
  if (call.name === "memory" && nativeMemoryTool) {
348
545
  try {
@@ -361,28 +558,36 @@ async function* agentLoop(prompt, options) {
361
558
  };
362
559
  result = await tools.execute(call.name, toolInput, toolCtx);
363
560
  }
561
+ if (call.name === "ExitPlanMode") {
562
+ permissions.setMode("default");
563
+ }
364
564
  if (debug) {
365
565
  console.error(`[debug] Tool ${call.name}: ${result.isError ? "ERROR" : "OK"} (${result.content.length} chars)`);
366
566
  }
367
567
  if (hooks) {
368
568
  if (result.isError) {
369
- await hooks.fire("PostToolUseFailure", { event: "PostToolUseFailure", tool_name: call.name, tool_result: result.content, tool_error: true, session_id: sessionId }, call.id, { signal });
569
+ await hooks.fire("PostToolUseFailure", {
570
+ event: "PostToolUseFailure",
571
+ hook_event_name: "PostToolUseFailure",
572
+ tool_name: call.name,
573
+ tool_result: result.content,
574
+ tool_error: true,
575
+ session_id: sessionId
576
+ }, call.id, { signal });
370
577
  } else {
371
- const postResult = await hooks.fire("PostToolUse", { event: "PostToolUse", tool_name: call.name, tool_result: result.content, session_id: sessionId }, call.id, { signal });
578
+ const postResult = await hooks.fire("PostToolUse", {
579
+ event: "PostToolUse",
580
+ hook_event_name: "PostToolUse",
581
+ tool_name: call.name,
582
+ tool_result: result.content,
583
+ session_id: sessionId
584
+ }, call.id, { signal });
372
585
  if (postResult?.additionalContext) {
373
586
  result.content += `
374
587
  ${postResult.additionalContext}`;
375
588
  }
376
589
  }
377
590
  }
378
- yield {
379
- type: "tool_result",
380
- id: call.id,
381
- name: call.name,
382
- content: result.content,
383
- isError: result.isError,
384
- uuid: uuid()
385
- };
386
591
  toolResults.push({
387
592
  type: "tool_result",
388
593
  tool_use_id: call.id,
@@ -394,20 +599,19 @@ ${postResult.additionalContext}`;
394
599
  if (sessionLogger) {
395
600
  sessionLogger("user", toolResults, null);
396
601
  }
602
+ yield {
603
+ type: "user",
604
+ message: {
605
+ role: "user",
606
+ content: toolResults
607
+ },
608
+ parent_tool_use_id: null,
609
+ isSynthetic: true,
610
+ uuid: uuid(),
611
+ session_id: sessionId
612
+ };
397
613
  }
398
614
  }
399
- function makeError(subtype, errors, turns, costUsd, sessionId, startTime) {
400
- return {
401
- type: "result",
402
- subtype,
403
- errors,
404
- turns,
405
- costUsd,
406
- durationMs: Date.now() - startTime,
407
- sessionId,
408
- uuid: uuid()
409
- };
410
- }
411
615
  export {
412
616
  agentLoop
413
617
  };