oricore 1.0.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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/dist/agent/agent/agentManager.d.ts +38 -0
  4. package/dist/agent/agent/builtin/common.d.ts +5 -0
  5. package/dist/agent/agent/builtin/explore.d.ts +5 -0
  6. package/dist/agent/agent/builtin/general-purpose.d.ts +5 -0
  7. package/dist/agent/agent/builtin/index.d.ts +5 -0
  8. package/dist/agent/agent/executor.d.ts +2 -0
  9. package/dist/agent/agent/types.d.ts +98 -0
  10. package/dist/api/engine.d.ts +213 -0
  11. package/dist/communication/index.d.ts +4 -0
  12. package/dist/communication/messageBus.d.ts +71 -0
  13. package/dist/core/at.d.ts +26 -0
  14. package/dist/core/backgroundTaskManager.d.ts +27 -0
  15. package/dist/core/compact.d.ts +9 -0
  16. package/dist/core/config.d.ts +103 -0
  17. package/dist/core/constants.d.ts +32 -0
  18. package/dist/core/context.d.ts +57 -0
  19. package/dist/core/globalData.d.ts +21 -0
  20. package/dist/core/history.d.ts +24 -0
  21. package/dist/core/ide.d.ts +103 -0
  22. package/dist/core/jsonl.d.ts +37 -0
  23. package/dist/core/llmsContext.d.ts +14 -0
  24. package/dist/core/loop.d.ts +82 -0
  25. package/dist/core/message.d.ts +132 -0
  26. package/dist/core/model.d.ts +79 -0
  27. package/dist/core/output-style/builtin/default.d.ts +2 -0
  28. package/dist/core/output-style/builtin/explanatory.d.ts +2 -0
  29. package/dist/core/output-style/builtin/index.d.ts +6 -0
  30. package/dist/core/output-style/builtin/miao.d.ts +2 -0
  31. package/dist/core/output-style/builtin/minimal.d.ts +2 -0
  32. package/dist/core/output-style/types.d.ts +6 -0
  33. package/dist/core/outputFormat.d.ts +29 -0
  34. package/dist/core/outputStyle.d.ts +43 -0
  35. package/dist/core/paths.d.ts +20 -0
  36. package/dist/core/planSystemPrompt.d.ts +5 -0
  37. package/dist/core/plugin.d.ts +138 -0
  38. package/dist/core/project.d.ts +64 -0
  39. package/dist/core/promptCache.d.ts +3 -0
  40. package/dist/core/query.d.ts +14 -0
  41. package/dist/core/rules.d.ts +8 -0
  42. package/dist/core/systemPrompt.d.ts +9 -0
  43. package/dist/core/thinking-config.d.ts +3 -0
  44. package/dist/core/usage.d.ts +14 -0
  45. package/dist/index.d.ts +16 -0
  46. package/dist/index.js +144432 -0
  47. package/dist/mcp/mcp.d.ts +49 -0
  48. package/dist/modes/builtin.d.ts +34 -0
  49. package/dist/modes/index.d.ts +8 -0
  50. package/dist/modes/registry.d.ts +18 -0
  51. package/dist/modes/types.d.ts +51 -0
  52. package/dist/platform/index.d.ts +5 -0
  53. package/dist/platform/node.d.ts +28 -0
  54. package/dist/platform/types.d.ts +41 -0
  55. package/dist/session/session.d.ts +43 -0
  56. package/dist/skill/skill.d.ts +79 -0
  57. package/dist/tools/tool.d.ts +119 -0
  58. package/dist/tools/tools/askUserQuestion.d.ts +48 -0
  59. package/dist/tools/tools/bash.d.ts +43 -0
  60. package/dist/tools/tools/edit.d.ts +9 -0
  61. package/dist/tools/tools/fetch.d.ts +9 -0
  62. package/dist/tools/tools/glob.d.ts +7 -0
  63. package/dist/tools/tools/grep.d.ts +22 -0
  64. package/dist/tools/tools/ls.d.ts +6 -0
  65. package/dist/tools/tools/read.d.ts +9 -0
  66. package/dist/tools/tools/skill.d.ts +7 -0
  67. package/dist/tools/tools/task.d.ts +14 -0
  68. package/dist/tools/tools/todo.d.ts +37 -0
  69. package/dist/tools/tools/write.d.ts +7 -0
  70. package/dist/utils/apiKeyRotation.d.ts +2 -0
  71. package/dist/utils/applyEdit.d.ts +17 -0
  72. package/dist/utils/background-detection.d.ts +2 -0
  73. package/dist/utils/dotenv.d.ts +9 -0
  74. package/dist/utils/env.d.ts +6 -0
  75. package/dist/utils/error.d.ts +11 -0
  76. package/dist/utils/execFileNoThrow.d.ts +8 -0
  77. package/dist/utils/files.d.ts +10 -0
  78. package/dist/utils/git.d.ts +163 -0
  79. package/dist/utils/ide.d.ts +27 -0
  80. package/dist/utils/ignore.d.ts +6 -0
  81. package/dist/utils/isLocal.d.ts +1 -0
  82. package/dist/utils/language.d.ts +9 -0
  83. package/dist/utils/list.d.ts +20 -0
  84. package/dist/utils/mergeSystemMessagesMiddleware.d.ts +2 -0
  85. package/dist/utils/messageNormalization.d.ts +22 -0
  86. package/dist/utils/path.d.ts +34 -0
  87. package/dist/utils/prependSystemMessageMiddleware.d.ts +2 -0
  88. package/dist/utils/project.d.ts +1 -0
  89. package/dist/utils/proxy.d.ts +18 -0
  90. package/dist/utils/randomUUID.d.ts +5 -0
  91. package/dist/utils/renderSessionMarkdown.d.ts +10 -0
  92. package/dist/utils/ripgrep.d.ts +16 -0
  93. package/dist/utils/safeFrontMatter.d.ts +11 -0
  94. package/dist/utils/safeParseJson.d.ts +1 -0
  95. package/dist/utils/safeStringify.d.ts +1 -0
  96. package/dist/utils/sanitizeAIResponse.d.ts +30 -0
  97. package/dist/utils/setTerminalTitle.d.ts +1 -0
  98. package/dist/utils/shell-execution.d.ts +44 -0
  99. package/dist/utils/string.d.ts +8 -0
  100. package/dist/utils/symbols.d.ts +14 -0
  101. package/dist/utils/system-encoding.d.ts +40 -0
  102. package/dist/utils/tokenCounter.d.ts +8 -0
  103. package/dist/utils/username.d.ts +1 -0
  104. package/package.json +106 -0
  105. package/src/agent/agent/agentManager.test.ts +124 -0
  106. package/src/agent/agent/agentManager.ts +372 -0
  107. package/src/agent/agent/builtin/common.ts +20 -0
  108. package/src/agent/agent/builtin/explore.ts +53 -0
  109. package/src/agent/agent/builtin/general-purpose.ts +38 -0
  110. package/src/agent/agent/builtin/index.ts +13 -0
  111. package/src/agent/agent/executor.test.ts +339 -0
  112. package/src/agent/agent/executor.ts +224 -0
  113. package/src/agent/agent/types.ts +119 -0
  114. package/src/api/engine.ts +466 -0
  115. package/src/communication/index.ts +18 -0
  116. package/src/communication/messageBus.ts +393 -0
  117. package/src/core/at.ts +315 -0
  118. package/src/core/backgroundTaskManager.ts +129 -0
  119. package/src/core/compact.ts +95 -0
  120. package/src/core/config.ts +441 -0
  121. package/src/core/constants.ts +82 -0
  122. package/src/core/context.ts +214 -0
  123. package/src/core/globalData.ts +77 -0
  124. package/src/core/history.ts +323 -0
  125. package/src/core/ide.ts +325 -0
  126. package/src/core/jsonl.ts +100 -0
  127. package/src/core/llmsContext.ts +117 -0
  128. package/src/core/loop.ts +638 -0
  129. package/src/core/message.ts +304 -0
  130. package/src/core/model.ts +2198 -0
  131. package/src/core/output-style/builtin/default.ts +9 -0
  132. package/src/core/output-style/builtin/explanatory.ts +22 -0
  133. package/src/core/output-style/builtin/index.ts +19 -0
  134. package/src/core/output-style/builtin/miao.ts +22 -0
  135. package/src/core/output-style/builtin/minimal.ts +8 -0
  136. package/src/core/output-style/types.ts +6 -0
  137. package/src/core/outputFormat.ts +93 -0
  138. package/src/core/outputStyle.ts +255 -0
  139. package/src/core/paths.ts +161 -0
  140. package/src/core/planSystemPrompt.ts +46 -0
  141. package/src/core/plugin.ts +299 -0
  142. package/src/core/project.ts +492 -0
  143. package/src/core/promptCache.ts +32 -0
  144. package/src/core/query.ts +46 -0
  145. package/src/core/rules.ts +56 -0
  146. package/src/core/systemPrompt.ts +176 -0
  147. package/src/core/thinking-config.ts +98 -0
  148. package/src/core/usage.ts +68 -0
  149. package/src/index.ts +39 -0
  150. package/src/mcp/mcp.ts +637 -0
  151. package/src/modes/builtin.ts +305 -0
  152. package/src/modes/index.ts +22 -0
  153. package/src/modes/registry.ts +39 -0
  154. package/src/modes/types.ts +56 -0
  155. package/src/platform/index.ts +6 -0
  156. package/src/platform/node.ts +108 -0
  157. package/src/platform/types.ts +54 -0
  158. package/src/plugins/index.ts +15 -0
  159. package/src/session/session.ts +187 -0
  160. package/src/skill/skill.ts +702 -0
  161. package/src/tools/tool.ts +378 -0
  162. package/src/tools/tools/askUserQuestion.ts +134 -0
  163. package/src/tools/tools/bash.test.ts +425 -0
  164. package/src/tools/tools/bash.ts +999 -0
  165. package/src/tools/tools/edit.ts +86 -0
  166. package/src/tools/tools/fetch.ts +129 -0
  167. package/src/tools/tools/glob.ts +69 -0
  168. package/src/tools/tools/grep.test.ts +194 -0
  169. package/src/tools/tools/grep.ts +358 -0
  170. package/src/tools/tools/ls.ts +51 -0
  171. package/src/tools/tools/read.test.ts +169 -0
  172. package/src/tools/tools/read.ts +284 -0
  173. package/src/tools/tools/skill.ts +73 -0
  174. package/src/tools/tools/task.test.ts +262 -0
  175. package/src/tools/tools/task.ts +284 -0
  176. package/src/tools/tools/todo.ts +269 -0
  177. package/src/tools/tools/write.ts +71 -0
  178. package/src/types.d.ts +18 -0
  179. package/src/utils/apiKeyRotation.test.ts +70 -0
  180. package/src/utils/apiKeyRotation.ts +24 -0
  181. package/src/utils/applyEdit.test.ts +388 -0
  182. package/src/utils/applyEdit.ts +547 -0
  183. package/src/utils/background-detection.test.ts +61 -0
  184. package/src/utils/background-detection.ts +58 -0
  185. package/src/utils/dotenv.ts +26 -0
  186. package/src/utils/env.ts +90 -0
  187. package/src/utils/error.ts +38 -0
  188. package/src/utils/execFileNoThrow.ts +49 -0
  189. package/src/utils/files.ts +93 -0
  190. package/src/utils/git.ts +1152 -0
  191. package/src/utils/ide.ts +279 -0
  192. package/src/utils/ignore.ts +275 -0
  193. package/src/utils/isLocal.ts +6 -0
  194. package/src/utils/language.ts +33 -0
  195. package/src/utils/list.ts +200 -0
  196. package/src/utils/mergeSystemMessagesMiddleware.ts +32 -0
  197. package/src/utils/messageNormalization.test.ts +401 -0
  198. package/src/utils/messageNormalization.ts +168 -0
  199. package/src/utils/path.ts +98 -0
  200. package/src/utils/prependSystemMessageMiddleware.ts +16 -0
  201. package/src/utils/project.ts +32 -0
  202. package/src/utils/proxy.ts +102 -0
  203. package/src/utils/randomUUID.ts +11 -0
  204. package/src/utils/renderSessionMarkdown.ts +175 -0
  205. package/src/utils/ripgrep.ts +189 -0
  206. package/src/utils/safeFrontMatter.test.ts +118 -0
  207. package/src/utils/safeFrontMatter.ts +68 -0
  208. package/src/utils/safeParseJson.ts +7 -0
  209. package/src/utils/safeStringify.ts +10 -0
  210. package/src/utils/sanitizeAIResponse.test.ts +135 -0
  211. package/src/utils/sanitizeAIResponse.ts +55 -0
  212. package/src/utils/setTerminalTitle.ts +7 -0
  213. package/src/utils/shell-execution.test.ts +237 -0
  214. package/src/utils/shell-execution.ts +279 -0
  215. package/src/utils/string.ts +13 -0
  216. package/src/utils/symbols.ts +18 -0
  217. package/src/utils/system-encoding.test.ts +164 -0
  218. package/src/utils/system-encoding.ts +296 -0
  219. package/src/utils/tokenCounter.test.ts +38 -0
  220. package/src/utils/tokenCounter.ts +19 -0
  221. package/src/utils/username.ts +21 -0
@@ -0,0 +1,638 @@
1
+ import type {
2
+ LanguageModelV2,
3
+ LanguageModelV2FunctionTool,
4
+ LanguageModelV2Message,
5
+ LanguageModelV2Prompt,
6
+ SharedV2Headers,
7
+ } from '@ai-sdk/provider';
8
+ import createDebug from 'debug';
9
+ import { At } from './at';
10
+ import { History, type OnMessage } from '../core/history';
11
+ import {
12
+ type AssistantContent,
13
+ createToolResultPart2,
14
+ type NormalizedMessage,
15
+ type ToolUsePart,
16
+ } from '../core/message';
17
+ import type { ModelInfo } from '../core/model';
18
+ import { addPromptCache } from './promptCache';
19
+ import { getThinkingConfig, type ReasoningEffort } from './thinking-config';
20
+ import type {
21
+ ToolApprovalResult,
22
+ ToolParams,
23
+ ToolResult,
24
+ Tools,
25
+ ToolUse,
26
+ } from '../tools/tool';
27
+ import { Usage } from '../core/usage';
28
+ import { randomUUID } from '../utils/randomUUID';
29
+ import { safeParseJson } from '../utils/safeParseJson';
30
+
31
+ const DEFAULT_MAX_TURNS = 50;
32
+ const DEFAULT_ERROR_RETRY_TURNS = 10;
33
+
34
+ const debug = createDebug('oricore:loop');
35
+
36
+ async function exponentialBackoffWithCancellation(
37
+ attempt: number,
38
+ signal?: AbortSignal,
39
+ ): Promise<void> {
40
+ const baseDelay = 1000;
41
+ const delay = baseDelay * Math.pow(2, attempt - 1);
42
+ const checkInterval = 100;
43
+
44
+ const startTime = Date.now();
45
+ while (Date.now() - startTime < delay) {
46
+ if (signal?.aborted) {
47
+ throw new Error('Cancelled during retry backoff');
48
+ }
49
+ await new Promise((resolve) =>
50
+ setTimeout(
51
+ resolve,
52
+ Math.min(checkInterval, delay - (Date.now() - startTime)),
53
+ ),
54
+ );
55
+ }
56
+ }
57
+
58
+ export type LoopResult =
59
+ | {
60
+ success: true;
61
+ data: Record<string, any>;
62
+ metadata: {
63
+ turnsCount: number;
64
+ toolCallsCount: number;
65
+ duration: number;
66
+ };
67
+ }
68
+ | {
69
+ success: false;
70
+ error: {
71
+ type: 'tool_denied' | 'max_turns_exceeded' | 'api_error' | 'canceled';
72
+ message: string;
73
+ details?: Record<string, any>;
74
+ };
75
+ };
76
+
77
+ type StreamResultBase = {
78
+ requestId: string;
79
+ prompt: LanguageModelV2Prompt;
80
+ model: ModelInfo;
81
+ tools: LanguageModelV2FunctionTool[];
82
+ };
83
+ export type StreamResult = StreamResultBase & {
84
+ request?: {
85
+ body?: unknown;
86
+ };
87
+ response?: {
88
+ headers?: SharedV2Headers;
89
+ statusCode?: number;
90
+ body?: unknown;
91
+ };
92
+ error?: any;
93
+ };
94
+
95
+ export type ResponseFormat =
96
+ | {
97
+ type: 'text';
98
+ }
99
+ | {
100
+ type: 'json';
101
+ schema?: any;
102
+ name?: string;
103
+ description?: string;
104
+ };
105
+ export type ThinkingConfig = {
106
+ effort: ReasoningEffort;
107
+ };
108
+
109
+ type RunLoopOpts = {
110
+ input: string | NormalizedMessage[];
111
+ model: ModelInfo;
112
+ tools: Tools;
113
+ cwd: string;
114
+ systemPrompt?: string;
115
+ maxTurns?: number;
116
+ errorRetryTurns?: number;
117
+ signal?: AbortSignal;
118
+ llmsContexts?: string[];
119
+ autoCompact?: boolean;
120
+ thinking?: ThinkingConfig;
121
+ temperature?: number;
122
+ responseFormat?: ResponseFormat;
123
+ onTextDelta?: (text: string) => Promise<void>;
124
+ onText?: (text: string) => Promise<void>;
125
+ onReasoning?: (text: string) => Promise<void>;
126
+ onStreamResult?: (result: StreamResult) => Promise<void>;
127
+ onChunk?: (chunk: any, requestId: string) => Promise<void>;
128
+ onToolUse?: (toolUse: ToolUse) => Promise<ToolUse>;
129
+ onToolResult?: (
130
+ toolUse: ToolUse,
131
+ toolResult: ToolResult,
132
+ approved: boolean,
133
+ ) => Promise<ToolResult>;
134
+ onTurn?: (turn: {
135
+ usage: Usage;
136
+ startTime: Date;
137
+ endTime: Date;
138
+ }) => Promise<void>;
139
+ onToolApprove?: (toolUse: ToolUse) => Promise<ToolApprovalResult>;
140
+ onMessage?: OnMessage;
141
+ };
142
+
143
+ export async function runLoop(opts: RunLoopOpts): Promise<LoopResult> {
144
+ const startTime = Date.now();
145
+ let turnsCount = 0;
146
+ let toolCallsCount = 0;
147
+ let finalText = '';
148
+ let lastUsage = Usage.empty();
149
+ const totalUsage = Usage.empty();
150
+ const history = new History({
151
+ messages: Array.isArray(opts.input)
152
+ ? opts.input
153
+ : [
154
+ {
155
+ role: 'user',
156
+ content: opts.input,
157
+ type: 'message',
158
+ timestamp: new Date().toISOString(),
159
+ uuid: randomUUID(),
160
+ parentUuid: null,
161
+ },
162
+ ],
163
+ onMessage: opts.onMessage,
164
+ });
165
+
166
+ const maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
167
+ const abortController = new AbortController();
168
+
169
+ const createCancelError = (): LoopResult => ({
170
+ success: false,
171
+ error: {
172
+ type: 'canceled',
173
+ message: 'Operation was canceled',
174
+ details: { turnsCount, history, usage: totalUsage },
175
+ },
176
+ });
177
+
178
+ let shouldAtNormalize = true;
179
+ let shouldThinking = true;
180
+ while (true) {
181
+ // Must use separate abortController to prevent ReadStream locking
182
+ if (opts.signal?.aborted && !abortController.signal.aborted) {
183
+ abortController.abort();
184
+ return createCancelError();
185
+ }
186
+
187
+ const startTime = new Date();
188
+ turnsCount++;
189
+
190
+ if (turnsCount > maxTurns) {
191
+ return {
192
+ success: false,
193
+ error: {
194
+ type: 'max_turns_exceeded',
195
+ message: `Maximum turns (${maxTurns}) exceeded`,
196
+ details: {
197
+ turnsCount,
198
+ history,
199
+ usage: totalUsage,
200
+ },
201
+ },
202
+ };
203
+ }
204
+ if (opts.autoCompact) {
205
+ const compressed = await history.compress(opts.model);
206
+ if (compressed.compressed) {
207
+ debug('history compressed', compressed);
208
+ }
209
+ }
210
+ lastUsage.reset();
211
+
212
+ const systemPromptMessage = {
213
+ role: 'system',
214
+ content: opts.systemPrompt || '',
215
+ } as LanguageModelV2Message;
216
+ const llmsContexts = opts.llmsContexts || [];
217
+ const llmsContextMessages = llmsContexts.map((llmsContext) => {
218
+ return {
219
+ role: 'system',
220
+ content: llmsContext,
221
+ } as LanguageModelV2Message;
222
+ });
223
+ let prompt: LanguageModelV2Prompt = [
224
+ systemPromptMessage,
225
+ ...llmsContextMessages,
226
+ ...history.toLanguageV2Messages(),
227
+ ];
228
+
229
+ if (shouldAtNormalize) {
230
+ // add file and directory contents for the last user prompt
231
+ prompt = At.normalizeLanguageV2Prompt({
232
+ input: prompt,
233
+ cwd: opts.cwd,
234
+ });
235
+ shouldAtNormalize = false;
236
+ }
237
+
238
+ prompt = addPromptCache(prompt, opts.model);
239
+
240
+ let text = '';
241
+ let reasoning = '';
242
+ const toolCalls: Array<{
243
+ providerMetadata?: any;
244
+ toolCallId: string;
245
+ toolName: string;
246
+ input: string;
247
+ }> = [];
248
+
249
+ const requestId = randomUUID();
250
+ const m: LanguageModelV2 = await opts.model._mCreator();
251
+ const tools = opts.tools.toLanguageV2Tools();
252
+
253
+ // Get thinking config based on model's reasoning capability
254
+ let thinkingConfig: Record<string, any> | undefined = undefined;
255
+ if (shouldThinking && opts.thinking) {
256
+ thinkingConfig = getThinkingConfig(opts.model, opts.thinking.effort);
257
+ shouldThinking = false;
258
+ }
259
+
260
+ let retryCount = 0;
261
+ const errorRetryTurns = opts.errorRetryTurns ?? DEFAULT_ERROR_RETRY_TURNS;
262
+ let reasoningProviderMetadata: any | undefined = undefined;
263
+
264
+ while (retryCount <= errorRetryTurns) {
265
+ if (opts.signal?.aborted) {
266
+ return createCancelError();
267
+ }
268
+
269
+ try {
270
+ const result = await m.doStream({
271
+ prompt: prompt,
272
+ tools,
273
+ toolChoice: { type: 'auto' },
274
+ abortSignal: abortController.signal,
275
+ ...thinkingConfig,
276
+ ...(opts.temperature !== undefined && {
277
+ temperature: opts.temperature,
278
+ }),
279
+ ...(opts.responseFormat !== undefined && {
280
+ responseFormat: opts.responseFormat,
281
+ }),
282
+ });
283
+ opts.onStreamResult?.({
284
+ requestId,
285
+ prompt,
286
+ model: opts.model,
287
+ tools,
288
+ request: result.request,
289
+ response: result.response,
290
+ });
291
+
292
+ for await (const chunk of result.stream) {
293
+ if (opts.signal?.aborted) {
294
+ return createCancelError();
295
+ }
296
+ await opts.onChunk?.(chunk, requestId);
297
+ switch (chunk.type) {
298
+ case 'text-delta': {
299
+ const textDelta = chunk.delta;
300
+ text += textDelta;
301
+ await opts.onTextDelta?.(textDelta);
302
+ break;
303
+ }
304
+ case 'reasoning-delta':
305
+ reasoning += chunk.delta;
306
+ break;
307
+ case 'reasoning-end':
308
+ if (chunk.providerMetadata) {
309
+ reasoningProviderMetadata = chunk.providerMetadata;
310
+ }
311
+ break;
312
+ case 'tool-call':
313
+ toolCalls.push({
314
+ toolCallId: chunk.toolCallId,
315
+ toolName: chunk.toolName,
316
+ input: chunk.input,
317
+ ...(chunk.providerMetadata && {
318
+ providerMetadata: chunk.providerMetadata,
319
+ }),
320
+ });
321
+ break;
322
+ case 'finish':
323
+ lastUsage = Usage.fromEventUsage(chunk.usage);
324
+ totalUsage.add(lastUsage);
325
+ if (toolCalls.length === 0 && text.trim() === '') {
326
+ const error = new Error(
327
+ 'Empty response: no text or tool calls received',
328
+ );
329
+ (error as any).isRetryable = true;
330
+ throw error;
331
+ }
332
+ break;
333
+ case 'error': {
334
+ const message = (() => {
335
+ if ((chunk as any).error.message) {
336
+ return (chunk as any).error.message;
337
+ }
338
+ try {
339
+ const message = JSON.parse(
340
+ (chunk as any).error.value?.details,
341
+ )?.error?.message;
342
+ if (message) {
343
+ return message;
344
+ }
345
+ } catch (_e) {}
346
+ return JSON.stringify(chunk.error);
347
+ })();
348
+ const error = new Error(message);
349
+ (error as any).isRetryable = false;
350
+ const value = (chunk.error as any).value;
351
+ if (value) {
352
+ (error as any).statusCode = value?.status;
353
+ }
354
+ throw error;
355
+ }
356
+ default:
357
+ break;
358
+ }
359
+ }
360
+
361
+ break;
362
+ } catch (error: any) {
363
+ const nextRetryCount = retryCount + 1;
364
+ const retryDelayMs = 1000 * Math.pow(2, nextRetryCount - 1);
365
+ const retryStartTime = Date.now();
366
+ opts.onStreamResult?.({
367
+ requestId,
368
+ prompt,
369
+ model: opts.model,
370
+ tools,
371
+ response: {
372
+ statusCode: error.statusCode,
373
+ headers: error.responseHeaders,
374
+ body: error.responseBody,
375
+ },
376
+ error: {
377
+ data: error.data || error.message,
378
+ isRetryable: error.isRetryable,
379
+ retryAttempt: retryCount,
380
+ maxRetries: errorRetryTurns,
381
+ retryDelayMs,
382
+ retryStartTime,
383
+ },
384
+ });
385
+
386
+ if (error.isRetryable && retryCount < errorRetryTurns) {
387
+ retryCount++;
388
+ try {
389
+ await exponentialBackoffWithCancellation(retryCount, opts.signal);
390
+ } catch {
391
+ return createCancelError();
392
+ }
393
+ continue;
394
+ }
395
+
396
+ return {
397
+ success: false,
398
+ error: {
399
+ type: 'api_error',
400
+ message:
401
+ error instanceof Error
402
+ ? error.message
403
+ : 'Unknown streaming error',
404
+ details: {
405
+ code: error.data?.error?.code,
406
+ status: error.data?.error?.status,
407
+ url: error.url,
408
+ error,
409
+ stack: error.stack,
410
+ retriesAttempted: retryCount,
411
+ },
412
+ },
413
+ };
414
+ }
415
+ }
416
+
417
+ // Exit early if cancellation signal is received
418
+ if (opts.signal?.aborted) {
419
+ return createCancelError();
420
+ }
421
+
422
+ await opts.onText?.(text);
423
+
424
+ // some model may return multiple \n in the end of the reasoning
425
+ // e.g. antigravity/gemini-3-pro-high
426
+ if (reasoning) {
427
+ reasoning = reasoning.trim();
428
+ }
429
+
430
+ if (reasoning) {
431
+ await opts.onReasoning?.(reasoning);
432
+ }
433
+
434
+ const endTime = new Date();
435
+ opts.onTurn?.({
436
+ usage: lastUsage,
437
+ startTime,
438
+ endTime,
439
+ });
440
+ const model = `${opts.model.provider.id}/${opts.model.model.id}`;
441
+ const assistantContent: AssistantContent = [];
442
+ if (reasoning) {
443
+ assistantContent.push({
444
+ type: 'reasoning',
445
+ text: reasoning,
446
+ ...(reasoningProviderMetadata && {
447
+ providerMetadata: reasoningProviderMetadata,
448
+ }),
449
+ });
450
+ }
451
+ if (text) {
452
+ finalText = text;
453
+ assistantContent.push({
454
+ type: 'text',
455
+ text: text,
456
+ });
457
+ }
458
+ for (const toolCall of toolCalls) {
459
+ const tool = opts.tools.get(toolCall.toolName);
460
+ // compatible with models that may return an empty value instead of a JSON string for input
461
+ const input = safeParseJson(toolCall.input);
462
+ const description = tool?.getDescription?.({
463
+ params: input,
464
+ cwd: opts.cwd,
465
+ });
466
+ const displayName = tool?.displayName;
467
+ const toolUse: ToolUsePart = {
468
+ type: 'tool_use',
469
+ id: toolCall.toolCallId,
470
+ name: toolCall.toolName,
471
+ input: input,
472
+ };
473
+ if (description) {
474
+ toolUse.description = description;
475
+ }
476
+ if (displayName) {
477
+ toolUse.displayName = displayName;
478
+ }
479
+ if (toolCall.providerMetadata) {
480
+ // @ts-ignore
481
+ toolUse.providerMetadata = toolCall.providerMetadata;
482
+ }
483
+ assistantContent.push(toolUse);
484
+ }
485
+ await history.addMessage(
486
+ {
487
+ role: 'assistant',
488
+ content: assistantContent,
489
+ text,
490
+ model,
491
+ usage: {
492
+ input_tokens: lastUsage.promptTokens,
493
+ output_tokens: lastUsage.completionTokens,
494
+ },
495
+ },
496
+ requestId,
497
+ );
498
+ if (!toolCalls.length) {
499
+ break;
500
+ }
501
+
502
+ const toolResults: {
503
+ toolCallId: string;
504
+ toolName: string;
505
+ input: Record<string, any>;
506
+ result: ToolResult;
507
+ }[] = [];
508
+ for (const toolCall of toolCalls) {
509
+ let toolUse: ToolUse = {
510
+ name: toolCall.toolName,
511
+ params: safeParseJson(toolCall.input),
512
+ callId: toolCall.toolCallId,
513
+ };
514
+ if (opts.onToolUse) {
515
+ toolUse = await opts.onToolUse(toolUse as ToolUse);
516
+ }
517
+ let approved = true;
518
+ let updatedParams: ToolParams | undefined = undefined;
519
+ let denyReason: string | undefined = undefined;
520
+
521
+ if (opts.onToolApprove) {
522
+ const approvalResult = await opts.onToolApprove(toolUse as ToolUse);
523
+ if (typeof approvalResult === 'object') {
524
+ approved = approvalResult.approved;
525
+ updatedParams = approvalResult.params;
526
+ denyReason = approvalResult.denyReason;
527
+ } else {
528
+ approved = approvalResult;
529
+ }
530
+ }
531
+
532
+ if (approved) {
533
+ toolCallsCount++;
534
+ if (updatedParams) {
535
+ toolUse.params = { ...toolUse.params, ...updatedParams };
536
+ }
537
+ let toolResult = await opts.tools.invoke(
538
+ toolUse.name,
539
+ JSON.stringify(toolUse.params),
540
+ toolUse.callId,
541
+ );
542
+ if (opts.onToolResult) {
543
+ toolResult = await opts.onToolResult(toolUse, toolResult, approved);
544
+ }
545
+ toolResults.push({
546
+ toolCallId: toolUse.callId,
547
+ toolName: toolUse.name,
548
+ input: toolUse.params,
549
+ result: toolResult,
550
+ });
551
+ // Prevent normal turns from being terminated due to exceeding the limit
552
+ turnsCount--;
553
+ } else {
554
+ let message = 'Error: Tool execution was denied by user.';
555
+ if (denyReason) {
556
+ message = `Tool use rejected with user message: ${denyReason}`;
557
+ }
558
+ let toolResult: ToolResult = {
559
+ llmContent: message,
560
+ isError: true,
561
+ };
562
+ if (opts.onToolResult) {
563
+ toolResult = await opts.onToolResult(toolUse, toolResult, approved);
564
+ }
565
+ toolResults.push({
566
+ toolCallId: toolUse.callId,
567
+ toolName: toolUse.name,
568
+ input: toolUse.params,
569
+ result: toolResult,
570
+ });
571
+
572
+ if (!denyReason) {
573
+ await history.addMessage({
574
+ role: 'tool',
575
+ content: toolResults.map((tr) =>
576
+ createToolResultPart2(
577
+ tr.toolCallId,
578
+ tr.toolName,
579
+ tr.input,
580
+ tr.result,
581
+ ),
582
+ ),
583
+ });
584
+ return {
585
+ success: false,
586
+ error: {
587
+ type: 'tool_denied',
588
+ message,
589
+ details: {
590
+ toolUse,
591
+ history,
592
+ usage: totalUsage,
593
+ },
594
+ },
595
+ };
596
+ } else {
597
+ // When denyReason is provided, we should break out of the tool loop
598
+ // to let the model react to the rejection before continuing
599
+ break;
600
+ }
601
+ }
602
+ }
603
+
604
+ // Check for cancellation before adding tool results
605
+ // session.cancel already handles adding tool results for incomplete tools
606
+ if (opts.signal?.aborted) {
607
+ return createCancelError();
608
+ }
609
+
610
+ if (toolResults.length) {
611
+ await history.addMessage({
612
+ role: 'tool',
613
+ content: toolResults.map((tr) =>
614
+ createToolResultPart2(
615
+ tr.toolCallId,
616
+ tr.toolName,
617
+ tr.input,
618
+ tr.result,
619
+ ),
620
+ ),
621
+ });
622
+ }
623
+ }
624
+ const duration = Date.now() - startTime;
625
+ return {
626
+ success: true,
627
+ data: {
628
+ text: finalText,
629
+ history,
630
+ usage: totalUsage,
631
+ },
632
+ metadata: {
633
+ turnsCount,
634
+ toolCallsCount,
635
+ duration,
636
+ },
637
+ };
638
+ }