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.
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/dist/agent/agent/agentManager.d.ts +38 -0
- package/dist/agent/agent/builtin/common.d.ts +5 -0
- package/dist/agent/agent/builtin/explore.d.ts +5 -0
- package/dist/agent/agent/builtin/general-purpose.d.ts +5 -0
- package/dist/agent/agent/builtin/index.d.ts +5 -0
- package/dist/agent/agent/executor.d.ts +2 -0
- package/dist/agent/agent/types.d.ts +98 -0
- package/dist/api/engine.d.ts +213 -0
- package/dist/communication/index.d.ts +4 -0
- package/dist/communication/messageBus.d.ts +71 -0
- package/dist/core/at.d.ts +26 -0
- package/dist/core/backgroundTaskManager.d.ts +27 -0
- package/dist/core/compact.d.ts +9 -0
- package/dist/core/config.d.ts +103 -0
- package/dist/core/constants.d.ts +32 -0
- package/dist/core/context.d.ts +57 -0
- package/dist/core/globalData.d.ts +21 -0
- package/dist/core/history.d.ts +24 -0
- package/dist/core/ide.d.ts +103 -0
- package/dist/core/jsonl.d.ts +37 -0
- package/dist/core/llmsContext.d.ts +14 -0
- package/dist/core/loop.d.ts +82 -0
- package/dist/core/message.d.ts +132 -0
- package/dist/core/model.d.ts +79 -0
- package/dist/core/output-style/builtin/default.d.ts +2 -0
- package/dist/core/output-style/builtin/explanatory.d.ts +2 -0
- package/dist/core/output-style/builtin/index.d.ts +6 -0
- package/dist/core/output-style/builtin/miao.d.ts +2 -0
- package/dist/core/output-style/builtin/minimal.d.ts +2 -0
- package/dist/core/output-style/types.d.ts +6 -0
- package/dist/core/outputFormat.d.ts +29 -0
- package/dist/core/outputStyle.d.ts +43 -0
- package/dist/core/paths.d.ts +20 -0
- package/dist/core/planSystemPrompt.d.ts +5 -0
- package/dist/core/plugin.d.ts +138 -0
- package/dist/core/project.d.ts +64 -0
- package/dist/core/promptCache.d.ts +3 -0
- package/dist/core/query.d.ts +14 -0
- package/dist/core/rules.d.ts +8 -0
- package/dist/core/systemPrompt.d.ts +9 -0
- package/dist/core/thinking-config.d.ts +3 -0
- package/dist/core/usage.d.ts +14 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +144432 -0
- package/dist/mcp/mcp.d.ts +49 -0
- package/dist/modes/builtin.d.ts +34 -0
- package/dist/modes/index.d.ts +8 -0
- package/dist/modes/registry.d.ts +18 -0
- package/dist/modes/types.d.ts +51 -0
- package/dist/platform/index.d.ts +5 -0
- package/dist/platform/node.d.ts +28 -0
- package/dist/platform/types.d.ts +41 -0
- package/dist/session/session.d.ts +43 -0
- package/dist/skill/skill.d.ts +79 -0
- package/dist/tools/tool.d.ts +119 -0
- package/dist/tools/tools/askUserQuestion.d.ts +48 -0
- package/dist/tools/tools/bash.d.ts +43 -0
- package/dist/tools/tools/edit.d.ts +9 -0
- package/dist/tools/tools/fetch.d.ts +9 -0
- package/dist/tools/tools/glob.d.ts +7 -0
- package/dist/tools/tools/grep.d.ts +22 -0
- package/dist/tools/tools/ls.d.ts +6 -0
- package/dist/tools/tools/read.d.ts +9 -0
- package/dist/tools/tools/skill.d.ts +7 -0
- package/dist/tools/tools/task.d.ts +14 -0
- package/dist/tools/tools/todo.d.ts +37 -0
- package/dist/tools/tools/write.d.ts +7 -0
- package/dist/utils/apiKeyRotation.d.ts +2 -0
- package/dist/utils/applyEdit.d.ts +17 -0
- package/dist/utils/background-detection.d.ts +2 -0
- package/dist/utils/dotenv.d.ts +9 -0
- package/dist/utils/env.d.ts +6 -0
- package/dist/utils/error.d.ts +11 -0
- package/dist/utils/execFileNoThrow.d.ts +8 -0
- package/dist/utils/files.d.ts +10 -0
- package/dist/utils/git.d.ts +163 -0
- package/dist/utils/ide.d.ts +27 -0
- package/dist/utils/ignore.d.ts +6 -0
- package/dist/utils/isLocal.d.ts +1 -0
- package/dist/utils/language.d.ts +9 -0
- package/dist/utils/list.d.ts +20 -0
- package/dist/utils/mergeSystemMessagesMiddleware.d.ts +2 -0
- package/dist/utils/messageNormalization.d.ts +22 -0
- package/dist/utils/path.d.ts +34 -0
- package/dist/utils/prependSystemMessageMiddleware.d.ts +2 -0
- package/dist/utils/project.d.ts +1 -0
- package/dist/utils/proxy.d.ts +18 -0
- package/dist/utils/randomUUID.d.ts +5 -0
- package/dist/utils/renderSessionMarkdown.d.ts +10 -0
- package/dist/utils/ripgrep.d.ts +16 -0
- package/dist/utils/safeFrontMatter.d.ts +11 -0
- package/dist/utils/safeParseJson.d.ts +1 -0
- package/dist/utils/safeStringify.d.ts +1 -0
- package/dist/utils/sanitizeAIResponse.d.ts +30 -0
- package/dist/utils/setTerminalTitle.d.ts +1 -0
- package/dist/utils/shell-execution.d.ts +44 -0
- package/dist/utils/string.d.ts +8 -0
- package/dist/utils/symbols.d.ts +14 -0
- package/dist/utils/system-encoding.d.ts +40 -0
- package/dist/utils/tokenCounter.d.ts +8 -0
- package/dist/utils/username.d.ts +1 -0
- package/package.json +106 -0
- package/src/agent/agent/agentManager.test.ts +124 -0
- package/src/agent/agent/agentManager.ts +372 -0
- package/src/agent/agent/builtin/common.ts +20 -0
- package/src/agent/agent/builtin/explore.ts +53 -0
- package/src/agent/agent/builtin/general-purpose.ts +38 -0
- package/src/agent/agent/builtin/index.ts +13 -0
- package/src/agent/agent/executor.test.ts +339 -0
- package/src/agent/agent/executor.ts +224 -0
- package/src/agent/agent/types.ts +119 -0
- package/src/api/engine.ts +466 -0
- package/src/communication/index.ts +18 -0
- package/src/communication/messageBus.ts +393 -0
- package/src/core/at.ts +315 -0
- package/src/core/backgroundTaskManager.ts +129 -0
- package/src/core/compact.ts +95 -0
- package/src/core/config.ts +441 -0
- package/src/core/constants.ts +82 -0
- package/src/core/context.ts +214 -0
- package/src/core/globalData.ts +77 -0
- package/src/core/history.ts +323 -0
- package/src/core/ide.ts +325 -0
- package/src/core/jsonl.ts +100 -0
- package/src/core/llmsContext.ts +117 -0
- package/src/core/loop.ts +638 -0
- package/src/core/message.ts +304 -0
- package/src/core/model.ts +2198 -0
- package/src/core/output-style/builtin/default.ts +9 -0
- package/src/core/output-style/builtin/explanatory.ts +22 -0
- package/src/core/output-style/builtin/index.ts +19 -0
- package/src/core/output-style/builtin/miao.ts +22 -0
- package/src/core/output-style/builtin/minimal.ts +8 -0
- package/src/core/output-style/types.ts +6 -0
- package/src/core/outputFormat.ts +93 -0
- package/src/core/outputStyle.ts +255 -0
- package/src/core/paths.ts +161 -0
- package/src/core/planSystemPrompt.ts +46 -0
- package/src/core/plugin.ts +299 -0
- package/src/core/project.ts +492 -0
- package/src/core/promptCache.ts +32 -0
- package/src/core/query.ts +46 -0
- package/src/core/rules.ts +56 -0
- package/src/core/systemPrompt.ts +176 -0
- package/src/core/thinking-config.ts +98 -0
- package/src/core/usage.ts +68 -0
- package/src/index.ts +39 -0
- package/src/mcp/mcp.ts +637 -0
- package/src/modes/builtin.ts +305 -0
- package/src/modes/index.ts +22 -0
- package/src/modes/registry.ts +39 -0
- package/src/modes/types.ts +56 -0
- package/src/platform/index.ts +6 -0
- package/src/platform/node.ts +108 -0
- package/src/platform/types.ts +54 -0
- package/src/plugins/index.ts +15 -0
- package/src/session/session.ts +187 -0
- package/src/skill/skill.ts +702 -0
- package/src/tools/tool.ts +378 -0
- package/src/tools/tools/askUserQuestion.ts +134 -0
- package/src/tools/tools/bash.test.ts +425 -0
- package/src/tools/tools/bash.ts +999 -0
- package/src/tools/tools/edit.ts +86 -0
- package/src/tools/tools/fetch.ts +129 -0
- package/src/tools/tools/glob.ts +69 -0
- package/src/tools/tools/grep.test.ts +194 -0
- package/src/tools/tools/grep.ts +358 -0
- package/src/tools/tools/ls.ts +51 -0
- package/src/tools/tools/read.test.ts +169 -0
- package/src/tools/tools/read.ts +284 -0
- package/src/tools/tools/skill.ts +73 -0
- package/src/tools/tools/task.test.ts +262 -0
- package/src/tools/tools/task.ts +284 -0
- package/src/tools/tools/todo.ts +269 -0
- package/src/tools/tools/write.ts +71 -0
- package/src/types.d.ts +18 -0
- package/src/utils/apiKeyRotation.test.ts +70 -0
- package/src/utils/apiKeyRotation.ts +24 -0
- package/src/utils/applyEdit.test.ts +388 -0
- package/src/utils/applyEdit.ts +547 -0
- package/src/utils/background-detection.test.ts +61 -0
- package/src/utils/background-detection.ts +58 -0
- package/src/utils/dotenv.ts +26 -0
- package/src/utils/env.ts +90 -0
- package/src/utils/error.ts +38 -0
- package/src/utils/execFileNoThrow.ts +49 -0
- package/src/utils/files.ts +93 -0
- package/src/utils/git.ts +1152 -0
- package/src/utils/ide.ts +279 -0
- package/src/utils/ignore.ts +275 -0
- package/src/utils/isLocal.ts +6 -0
- package/src/utils/language.ts +33 -0
- package/src/utils/list.ts +200 -0
- package/src/utils/mergeSystemMessagesMiddleware.ts +32 -0
- package/src/utils/messageNormalization.test.ts +401 -0
- package/src/utils/messageNormalization.ts +168 -0
- package/src/utils/path.ts +98 -0
- package/src/utils/prependSystemMessageMiddleware.ts +16 -0
- package/src/utils/project.ts +32 -0
- package/src/utils/proxy.ts +102 -0
- package/src/utils/randomUUID.ts +11 -0
- package/src/utils/renderSessionMarkdown.ts +175 -0
- package/src/utils/ripgrep.ts +189 -0
- package/src/utils/safeFrontMatter.test.ts +118 -0
- package/src/utils/safeFrontMatter.ts +68 -0
- package/src/utils/safeParseJson.ts +7 -0
- package/src/utils/safeStringify.ts +10 -0
- package/src/utils/sanitizeAIResponse.test.ts +135 -0
- package/src/utils/sanitizeAIResponse.ts +55 -0
- package/src/utils/setTerminalTitle.ts +7 -0
- package/src/utils/shell-execution.test.ts +237 -0
- package/src/utils/shell-execution.ts +279 -0
- package/src/utils/string.ts +13 -0
- package/src/utils/symbols.ts +18 -0
- package/src/utils/system-encoding.test.ts +164 -0
- package/src/utils/system-encoding.ts +296 -0
- package/src/utils/tokenCounter.test.ts +38 -0
- package/src/utils/tokenCounter.ts +19 -0
- package/src/utils/username.ts +21 -0
package/src/core/loop.ts
ADDED
|
@@ -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
|
+
}
|