@archships/dim-agent-sdk 0.0.5 → 0.0.7
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/README.md +1 -0
- package/dist/index.js +4820 -0
- package/dist/providers/aihubmix/index.js +636 -0
- package/dist/providers/aihubmix-responses/index.js +759 -0
- package/dist/providers/anthropic/index.js +743 -0
- package/dist/providers/core/index.js +92 -0
- package/dist/providers/deepseek/index.js +639 -0
- package/dist/providers/gemini/index.js +756 -0
- package/dist/providers/moonshotai/index.js +639 -0
- package/dist/providers/openai/index.js +499 -0
- package/dist/providers/openai-responses/index.js +727 -0
- package/dist/providers/xai/index.js +639 -0
- package/dist/providers/xai-responses/index.js +728 -0
- package/dist/providers/zenmux/index.js +642 -0
- package/package.json +2 -2
package/dist/index.js
ADDED
|
@@ -0,0 +1,4820 @@
|
|
|
1
|
+
// src/providers/core/create-provider-factory.ts
|
|
2
|
+
function createProviderFactory(definition) {
|
|
3
|
+
return function createConfiguredAdapter(options) {
|
|
4
|
+
const provider = options.provider ?? definition.defaultProvider;
|
|
5
|
+
const defaultModel = {
|
|
6
|
+
provider,
|
|
7
|
+
modelId: options.defaultModel
|
|
8
|
+
};
|
|
9
|
+
return {
|
|
10
|
+
provider,
|
|
11
|
+
defaultModel,
|
|
12
|
+
async* stream(request) {
|
|
13
|
+
const requestId = request.requestId ?? crypto.randomUUID();
|
|
14
|
+
const model = request.model ?? defaultModel;
|
|
15
|
+
yield { type: "response_start", requestId, model };
|
|
16
|
+
const context = {
|
|
17
|
+
options,
|
|
18
|
+
provider,
|
|
19
|
+
defaultModel,
|
|
20
|
+
request: {
|
|
21
|
+
...request,
|
|
22
|
+
model
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
let result;
|
|
26
|
+
try {
|
|
27
|
+
result = await definition.driver.generate(context);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
const payload = definition.driver.normalizeError ? await definition.driver.normalizeError(error, context) : createFallbackErrorPayload(provider, error);
|
|
30
|
+
yield {
|
|
31
|
+
type: "error",
|
|
32
|
+
requestId,
|
|
33
|
+
error: payload,
|
|
34
|
+
terminal: true
|
|
35
|
+
};
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const segment of result.content) {
|
|
39
|
+
if (segment.type === "text") {
|
|
40
|
+
if (segment.text.length > 0)
|
|
41
|
+
yield { type: "text_delta", requestId, delta: segment.text };
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (segment.type === "thinking") {
|
|
45
|
+
if (segment.text.length > 0)
|
|
46
|
+
yield { type: "thinking_delta", requestId, delta: segment.text };
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
yield {
|
|
50
|
+
type: "tool_call_start",
|
|
51
|
+
requestId,
|
|
52
|
+
callId: segment.callId,
|
|
53
|
+
toolName: segment.toolName
|
|
54
|
+
};
|
|
55
|
+
yield {
|
|
56
|
+
type: "tool_call_args_delta",
|
|
57
|
+
requestId,
|
|
58
|
+
callId: segment.callId,
|
|
59
|
+
delta: segment.argsText
|
|
60
|
+
};
|
|
61
|
+
yield {
|
|
62
|
+
type: "tool_call_end",
|
|
63
|
+
requestId,
|
|
64
|
+
callId: segment.callId
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
yield {
|
|
68
|
+
type: "response_end",
|
|
69
|
+
requestId,
|
|
70
|
+
stopReason: result.stopReason,
|
|
71
|
+
usage: result.usage,
|
|
72
|
+
assistantMetadata: result.assistantMetadata
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function createFallbackErrorPayload(provider, error) {
|
|
79
|
+
if (error instanceof Error) {
|
|
80
|
+
return {
|
|
81
|
+
code: `${provider}_request_error`,
|
|
82
|
+
message: error.message
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
code: `${provider}_request_error`,
|
|
87
|
+
message: String(error)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/providers/aihubmix/driver.ts
|
|
92
|
+
import { APICallError } from "@ai-sdk/provider";
|
|
93
|
+
import { createAihubmix } from "@aihubmix/ai-sdk-provider";
|
|
94
|
+
|
|
95
|
+
// src/providers/shared/usage.ts
|
|
96
|
+
function normalizeLanguageModelUsage(raw) {
|
|
97
|
+
if (!raw)
|
|
98
|
+
return;
|
|
99
|
+
const promptTokens = raw.inputTokens.total ?? 0;
|
|
100
|
+
const completionTokens = raw.outputTokens.total ?? 0;
|
|
101
|
+
const totalTokens = promptTokens + completionTokens;
|
|
102
|
+
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0)
|
|
103
|
+
return;
|
|
104
|
+
return {
|
|
105
|
+
promptTokens,
|
|
106
|
+
completionTokens,
|
|
107
|
+
totalTokens
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function mapLanguageModelFinishReason(reason) {
|
|
111
|
+
const raw = reason.raw?.toLowerCase();
|
|
112
|
+
if (raw === "cancelled" || raw === "canceled")
|
|
113
|
+
return "cancelled";
|
|
114
|
+
switch (reason.unified) {
|
|
115
|
+
case "tool-calls":
|
|
116
|
+
return "tool_call";
|
|
117
|
+
case "length":
|
|
118
|
+
return "length";
|
|
119
|
+
case "content-filter":
|
|
120
|
+
case "error":
|
|
121
|
+
return "error";
|
|
122
|
+
case "other":
|
|
123
|
+
return raw === "cancelled" || raw === "canceled" ? "cancelled" : "final";
|
|
124
|
+
case "stop":
|
|
125
|
+
default:
|
|
126
|
+
return "final";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/providers/shared/ai-sdk-driver.ts
|
|
131
|
+
async function generateWithAiSdk(input) {
|
|
132
|
+
const result = await input.model.doGenerate(input.callOptions);
|
|
133
|
+
const content = [];
|
|
134
|
+
for (const part of result.content) {
|
|
135
|
+
if (part.type === "text") {
|
|
136
|
+
if (part.text.length > 0)
|
|
137
|
+
content.push({ type: "text", text: part.text });
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (part.type === "reasoning") {
|
|
141
|
+
if (part.text.length > 0)
|
|
142
|
+
content.push({ type: "thinking", text: part.text });
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (part.type === "tool-call") {
|
|
146
|
+
content.push({
|
|
147
|
+
type: "tool_call",
|
|
148
|
+
callId: part.toolCallId,
|
|
149
|
+
toolName: part.toolName,
|
|
150
|
+
argsText: part.input
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
content,
|
|
156
|
+
stopReason: mapLanguageModelFinishReason(result.finishReason),
|
|
157
|
+
usage: normalizeLanguageModelUsage(result.usage),
|
|
158
|
+
assistantMetadata: input.createAssistantMetadata?.(result)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/providers/shared/auth-warning.ts
|
|
163
|
+
var warnedProviders = new Set;
|
|
164
|
+
function warnIfPlaceholderApiKey(input) {
|
|
165
|
+
if (!input.shouldWarn)
|
|
166
|
+
return;
|
|
167
|
+
if (false)
|
|
168
|
+
;
|
|
169
|
+
if (warnedProviders.has(input.provider))
|
|
170
|
+
return;
|
|
171
|
+
warnedProviders.add(input.provider);
|
|
172
|
+
const suffix = input.hint ? ` ${input.hint}` : "";
|
|
173
|
+
console.warn(`[dim-agent-sdk] ${input.provider} is using a placeholder API key because no apiKey or provider-native auth configuration was provided. ` + `If you rely on external authentication, ensure your custom fetch or provider-native headers add real credentials before the request is sent upstream.${suffix}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/providers/shared/fetch.ts
|
|
177
|
+
function createProviderFetch(input) {
|
|
178
|
+
const fetchImpl = input.fetch ?? globalThis.fetch;
|
|
179
|
+
if (!input.stripWhen || !input.stripHeaders || input.stripHeaders.length === 0)
|
|
180
|
+
return fetchImpl;
|
|
181
|
+
const headersToStrip = new Set(input.stripHeaders.map((header) => header.toLowerCase()));
|
|
182
|
+
const wrappedFetch = Object.assign((resource, init) => {
|
|
183
|
+
if (typeof resource === "string" || resource instanceof URL) {
|
|
184
|
+
const headers = new Headers(init?.headers);
|
|
185
|
+
for (const header of headersToStrip)
|
|
186
|
+
headers.delete(header);
|
|
187
|
+
return fetchImpl(resource, { ...init, headers });
|
|
188
|
+
}
|
|
189
|
+
const request = new Request(resource, init);
|
|
190
|
+
for (const header of headersToStrip)
|
|
191
|
+
request.headers.delete(header);
|
|
192
|
+
return fetchImpl(request);
|
|
193
|
+
}, {
|
|
194
|
+
preconnect: typeof fetchImpl.preconnect === "function" ? fetchImpl.preconnect.bind(fetchImpl) : (_url, _options) => {}
|
|
195
|
+
});
|
|
196
|
+
return wrappedFetch;
|
|
197
|
+
}
|
|
198
|
+
function hasHeader(headers, target) {
|
|
199
|
+
if (!headers)
|
|
200
|
+
return false;
|
|
201
|
+
const normalized = target.toLowerCase();
|
|
202
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === normalized);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/providers/shared/http-error.ts
|
|
206
|
+
import { STATUS_CODES } from "node:http";
|
|
207
|
+
var MAX_TEXT_CHARS = 4000;
|
|
208
|
+
var MAX_JSON_DEPTH = 4;
|
|
209
|
+
var MAX_JSON_ARRAY_ITEMS = 20;
|
|
210
|
+
var MAX_JSON_OBJECT_KEYS = 40;
|
|
211
|
+
var TRUNCATED_SUFFIX = "...[truncated]";
|
|
212
|
+
function createApiCallErrorPayload(input) {
|
|
213
|
+
const responseBody = input.error.responseBody ? parseErrorBody(input.error.responseBody) : limitJsonValue(input.error.data);
|
|
214
|
+
return {
|
|
215
|
+
code: input.code,
|
|
216
|
+
message: `${input.provider} request failed${input.error.statusCode ? ` with status ${input.error.statusCode}` : ""}`,
|
|
217
|
+
status: input.error.statusCode,
|
|
218
|
+
retryable: input.error.isRetryable,
|
|
219
|
+
details: {
|
|
220
|
+
provider: input.provider,
|
|
221
|
+
endpoint: input.error.url,
|
|
222
|
+
...input.error.statusCode ? { statusText: STATUS_CODES[input.error.statusCode] ?? undefined } : {},
|
|
223
|
+
...responseBody === undefined ? {} : { responseBody }
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function createRequestErrorPayload(input) {
|
|
228
|
+
if (input.error instanceof Error) {
|
|
229
|
+
return {
|
|
230
|
+
code: input.code,
|
|
231
|
+
message: input.error.message
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
code: input.code,
|
|
236
|
+
message: String(input.error)
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function parseErrorBody(value) {
|
|
240
|
+
const normalized = value.trim();
|
|
241
|
+
if (!normalized)
|
|
242
|
+
return;
|
|
243
|
+
try {
|
|
244
|
+
return limitJsonValue(JSON.parse(normalized));
|
|
245
|
+
} catch {
|
|
246
|
+
return truncateText(normalized);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function limitJsonValue(value, depth = 0) {
|
|
250
|
+
if (value === undefined)
|
|
251
|
+
return;
|
|
252
|
+
if (value === null || typeof value === "boolean" || typeof value === "number")
|
|
253
|
+
return value;
|
|
254
|
+
if (typeof value === "string")
|
|
255
|
+
return truncateText(value);
|
|
256
|
+
if (Array.isArray(value))
|
|
257
|
+
return value.slice(0, MAX_JSON_ARRAY_ITEMS).map((item) => limitJsonValue(item, depth + 1));
|
|
258
|
+
if (typeof value === "object") {
|
|
259
|
+
if (depth >= MAX_JSON_DEPTH)
|
|
260
|
+
return truncateText(JSON.stringify(value));
|
|
261
|
+
const limited = {};
|
|
262
|
+
for (const [key, child] of Object.entries(value).slice(0, MAX_JSON_OBJECT_KEYS))
|
|
263
|
+
limited[key] = limitJsonValue(child, depth + 1);
|
|
264
|
+
return limited;
|
|
265
|
+
}
|
|
266
|
+
return truncateText(String(value));
|
|
267
|
+
}
|
|
268
|
+
function truncateText(value) {
|
|
269
|
+
if (value.length <= MAX_TEXT_CHARS)
|
|
270
|
+
return value;
|
|
271
|
+
return `${value.slice(0, MAX_TEXT_CHARS)}${TRUNCATED_SUFFIX}`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/utils/guards.ts
|
|
275
|
+
function isRecord(value) {
|
|
276
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
277
|
+
}
|
|
278
|
+
function isStringArray(value) {
|
|
279
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/contracts/content-normalize.ts
|
|
283
|
+
function isTextContent(value) {
|
|
284
|
+
return isRecord(value) && value.type === "text" && typeof value.text === "string" && (value.annotations === undefined || isRecord(value.annotations)) && (value._meta === undefined || isRecord(value._meta));
|
|
285
|
+
}
|
|
286
|
+
function isImageContent(value) {
|
|
287
|
+
return isRecord(value) && value.type === "image" && typeof value.data === "string" && typeof value.mimeType === "string" && (value.annotations === undefined || isRecord(value.annotations)) && (value._meta === undefined || isRecord(value._meta));
|
|
288
|
+
}
|
|
289
|
+
function isContentBlock(value) {
|
|
290
|
+
return isTextContent(value) || isImageContent(value);
|
|
291
|
+
}
|
|
292
|
+
function normalizeContent(input) {
|
|
293
|
+
if (typeof input === "string") {
|
|
294
|
+
return [{ type: "text", text: input }];
|
|
295
|
+
}
|
|
296
|
+
if (Array.isArray(input)) {
|
|
297
|
+
const blocks = input.filter((item) => item != null);
|
|
298
|
+
if (!blocks.every(isContentBlock))
|
|
299
|
+
throw new TypeError("Content array must contain only text or image blocks");
|
|
300
|
+
return blocks.map((block) => ({ ...block }));
|
|
301
|
+
}
|
|
302
|
+
if (!isContentBlock(input))
|
|
303
|
+
throw new TypeError("Content must be a string, content block, or content block array");
|
|
304
|
+
return [{ ...input }];
|
|
305
|
+
}
|
|
306
|
+
function contentToText(content) {
|
|
307
|
+
return content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/utils/json.ts
|
|
311
|
+
function isJsonSafeValue(value) {
|
|
312
|
+
if (value === null)
|
|
313
|
+
return true;
|
|
314
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
|
|
315
|
+
return true;
|
|
316
|
+
if (Array.isArray(value))
|
|
317
|
+
return value.every(isJsonSafeValue);
|
|
318
|
+
if (isRecord(value))
|
|
319
|
+
return Object.values(value).every(isJsonSafeValue);
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
function assertJsonSafeObject(value, label) {
|
|
323
|
+
if (!isRecord(value) || !isJsonSafeValue(value))
|
|
324
|
+
throw new TypeError(`${label} must be a JSON-safe object`);
|
|
325
|
+
}
|
|
326
|
+
function parseJsonObject(value, label) {
|
|
327
|
+
let parsed;
|
|
328
|
+
try {
|
|
329
|
+
parsed = JSON.parse(value);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
332
|
+
throw new Error(`${label} must be valid JSON: ${message}`);
|
|
333
|
+
}
|
|
334
|
+
if (!isRecord(parsed))
|
|
335
|
+
throw new Error(`${label} must decode to a JSON object`);
|
|
336
|
+
return parsed;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/providers/shared/prompt.ts
|
|
340
|
+
function mapToolDefinitionsToLanguageModelTools(tools) {
|
|
341
|
+
if (!tools || tools.length === 0)
|
|
342
|
+
return;
|
|
343
|
+
return tools.map((tool) => ({
|
|
344
|
+
type: "function",
|
|
345
|
+
name: tool.name,
|
|
346
|
+
description: tool.description,
|
|
347
|
+
inputSchema: tool.inputSchema
|
|
348
|
+
}));
|
|
349
|
+
}
|
|
350
|
+
function messagesToLanguageModelPrompt(messages, options = {}) {
|
|
351
|
+
const prompt = [];
|
|
352
|
+
const systemContent = messages.filter((message) => message.role === "system").map((message) => contentToText(message.content)).filter(Boolean).join(`
|
|
353
|
+
|
|
354
|
+
`);
|
|
355
|
+
if (systemContent)
|
|
356
|
+
prompt.push({ role: "system", content: systemContent });
|
|
357
|
+
for (const message of messages) {
|
|
358
|
+
if (message.role === "system")
|
|
359
|
+
continue;
|
|
360
|
+
if (message.role === "tool") {
|
|
361
|
+
prompt.push(toolMessageToPrompt(message));
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (message.role === "assistant") {
|
|
365
|
+
const content = [
|
|
366
|
+
...options.assistantPrefixParts?.(message) ?? [],
|
|
367
|
+
...contentBlocksToPromptParts(message.content),
|
|
368
|
+
...toolCallsToPromptParts(message.toolCalls)
|
|
369
|
+
];
|
|
370
|
+
if (content.length > 0)
|
|
371
|
+
prompt.push({ role: "assistant", content });
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
prompt.push({
|
|
375
|
+
role: "user",
|
|
376
|
+
content: contentBlocksToPromptParts(message.content)
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
return prompt;
|
|
380
|
+
}
|
|
381
|
+
function contentBlocksToPromptParts(content) {
|
|
382
|
+
return content.map((block) => {
|
|
383
|
+
if (block.type === "text")
|
|
384
|
+
return { type: "text", text: block.text };
|
|
385
|
+
return {
|
|
386
|
+
type: "file",
|
|
387
|
+
data: block.data,
|
|
388
|
+
mediaType: block.mimeType
|
|
389
|
+
};
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
function toolCallsToPromptParts(toolCalls) {
|
|
393
|
+
if (!toolCalls || toolCalls.length === 0)
|
|
394
|
+
return [];
|
|
395
|
+
return toolCalls.map((toolCall) => ({
|
|
396
|
+
type: "tool-call",
|
|
397
|
+
toolCallId: toolCall.id,
|
|
398
|
+
toolName: toolCall.function.name,
|
|
399
|
+
input: toolCall.function.arguments
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
function toolMessageToPrompt(message) {
|
|
403
|
+
return {
|
|
404
|
+
role: "tool",
|
|
405
|
+
content: [{
|
|
406
|
+
type: "tool-result",
|
|
407
|
+
toolCallId: message.toolCallId,
|
|
408
|
+
toolName: message.toolName,
|
|
409
|
+
output: toolMessageToOutput(message)
|
|
410
|
+
}]
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function toolMessageToOutput(message) {
|
|
414
|
+
if (message.structuredContent) {
|
|
415
|
+
assertJsonSafeObject(message.structuredContent, "Tool structuredContent");
|
|
416
|
+
return {
|
|
417
|
+
type: "json",
|
|
418
|
+
value: message.structuredContent
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
const text = contentToText(message.content);
|
|
422
|
+
if (message.isError === true) {
|
|
423
|
+
return {
|
|
424
|
+
type: "json",
|
|
425
|
+
value: {
|
|
426
|
+
content: text,
|
|
427
|
+
isError: true
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
type: "text",
|
|
433
|
+
value: text
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/providers/shared/reasoning.ts
|
|
438
|
+
function isRecord2(value) {
|
|
439
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
440
|
+
}
|
|
441
|
+
var LOW_HIGH_REASONING_EFFORT_MAP = {
|
|
442
|
+
none: "low",
|
|
443
|
+
minimal: "low",
|
|
444
|
+
low: "low",
|
|
445
|
+
medium: "high",
|
|
446
|
+
high: "high",
|
|
447
|
+
xhigh: "high"
|
|
448
|
+
};
|
|
449
|
+
var LOW_MEDIUM_HIGH_REASONING_EFFORT_MAP = {
|
|
450
|
+
none: "low",
|
|
451
|
+
minimal: "low",
|
|
452
|
+
low: "low",
|
|
453
|
+
medium: "medium",
|
|
454
|
+
high: "high",
|
|
455
|
+
xhigh: "high"
|
|
456
|
+
};
|
|
457
|
+
function normalizeReasoningConfig(reasoning) {
|
|
458
|
+
if (!isRecord2(reasoning))
|
|
459
|
+
return;
|
|
460
|
+
if (reasoning.enabled === false)
|
|
461
|
+
return;
|
|
462
|
+
return reasoning;
|
|
463
|
+
}
|
|
464
|
+
function resolveReasoningBudget(reasoning, fallback = 1024) {
|
|
465
|
+
const normalized = normalizeReasoningConfig(reasoning);
|
|
466
|
+
if (!normalized)
|
|
467
|
+
return;
|
|
468
|
+
for (const key of ["budgetTokens", "budget_tokens", "thinkingBudget"]) {
|
|
469
|
+
const value = normalized[key];
|
|
470
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0)
|
|
471
|
+
return Math.floor(value);
|
|
472
|
+
}
|
|
473
|
+
return fallback;
|
|
474
|
+
}
|
|
475
|
+
function resolveReasoningEffort(reasoning, allowedValues) {
|
|
476
|
+
const normalized = normalizeReasoningConfig(reasoning);
|
|
477
|
+
if (!normalized)
|
|
478
|
+
return;
|
|
479
|
+
const rawEffort = normalized.reasoningEffort ?? normalized.effort;
|
|
480
|
+
if (typeof rawEffort !== "string" || rawEffort.length === 0)
|
|
481
|
+
return;
|
|
482
|
+
if (!allowedValues || allowedValues.includes(rawEffort))
|
|
483
|
+
return rawEffort;
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
function resolveLowHighReasoningEffort(reasoning) {
|
|
487
|
+
const normalized = normalizeReasoningConfig(reasoning);
|
|
488
|
+
if (!normalized)
|
|
489
|
+
return;
|
|
490
|
+
const rawEffort = normalized.reasoningEffort ?? normalized.effort;
|
|
491
|
+
return typeof rawEffort === "string" ? LOW_HIGH_REASONING_EFFORT_MAP[rawEffort] : undefined;
|
|
492
|
+
}
|
|
493
|
+
function resolveLowMediumHighReasoningEffort(reasoning) {
|
|
494
|
+
const normalized = normalizeReasoningConfig(reasoning);
|
|
495
|
+
if (!normalized)
|
|
496
|
+
return;
|
|
497
|
+
const rawEffort = normalized.reasoningEffort ?? normalized.effort;
|
|
498
|
+
return typeof rawEffort === "string" ? LOW_MEDIUM_HIGH_REASONING_EFFORT_MAP[rawEffort] : undefined;
|
|
499
|
+
}
|
|
500
|
+
function buildOpenAIResponsesProviderOptions(reasoning) {
|
|
501
|
+
const normalized = normalizeReasoningConfig(reasoning);
|
|
502
|
+
if (!normalized)
|
|
503
|
+
return;
|
|
504
|
+
const next = {};
|
|
505
|
+
for (const [key, value] of Object.entries(normalized)) {
|
|
506
|
+
if (key === "enabled" || key === "budgetTokens" || key === "budget_tokens" || key === "thinkingBudget" || key === "includeThoughts")
|
|
507
|
+
continue;
|
|
508
|
+
if (key === "effort") {
|
|
509
|
+
next.reasoningEffort = value;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (key === "summary") {
|
|
513
|
+
next.reasoningSummary = value;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
next[key] = value;
|
|
517
|
+
}
|
|
518
|
+
if (next.reasoningSummary === undefined)
|
|
519
|
+
next.reasoningSummary = "auto";
|
|
520
|
+
return next;
|
|
521
|
+
}
|
|
522
|
+
function buildOpenAIResponsesRequestReasoning(reasoning) {
|
|
523
|
+
const options = buildOpenAIResponsesProviderOptions(reasoning);
|
|
524
|
+
if (!options)
|
|
525
|
+
return;
|
|
526
|
+
const next = {};
|
|
527
|
+
if (typeof options.reasoningEffort === "string")
|
|
528
|
+
next.effort = options.reasoningEffort;
|
|
529
|
+
if (typeof options.reasoningSummary === "string")
|
|
530
|
+
next.summary = options.reasoningSummary;
|
|
531
|
+
return next.effort || next.summary ? next : undefined;
|
|
532
|
+
}
|
|
533
|
+
function buildReasoningEffortProviderOptions(reasoning, allowedValues) {
|
|
534
|
+
const reasoningEffort = resolveReasoningEffort(reasoning, allowedValues);
|
|
535
|
+
return reasoningEffort ? { reasoningEffort } : undefined;
|
|
536
|
+
}
|
|
537
|
+
function buildAnthropicThinkingProviderOptions(reasoning) {
|
|
538
|
+
const budget = resolveReasoningBudget(reasoning);
|
|
539
|
+
if (!budget)
|
|
540
|
+
return { thinking: undefined, sendReasoning: undefined };
|
|
541
|
+
return {
|
|
542
|
+
thinking: {
|
|
543
|
+
type: "enabled",
|
|
544
|
+
budgetTokens: budget
|
|
545
|
+
},
|
|
546
|
+
sendReasoning: true
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function buildGeminiThinkingProviderOptions(reasoning) {
|
|
550
|
+
const normalized = normalizeReasoningConfig(reasoning);
|
|
551
|
+
if (!normalized)
|
|
552
|
+
return;
|
|
553
|
+
return {
|
|
554
|
+
thinkingConfig: {
|
|
555
|
+
thinkingBudget: resolveReasoningBudget(normalized) ?? 1024,
|
|
556
|
+
includeThoughts: normalized.includeThoughts !== false
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
function buildMoonshotThinkingProviderOptions(reasoning) {
|
|
561
|
+
const budget = resolveReasoningBudget(reasoning);
|
|
562
|
+
if (!budget)
|
|
563
|
+
return;
|
|
564
|
+
return {
|
|
565
|
+
thinking: {
|
|
566
|
+
type: "enabled",
|
|
567
|
+
budgetTokens: budget
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function buildDeepSeekThinkingProviderOptions(reasoning) {
|
|
572
|
+
return normalizeReasoningConfig(reasoning) ? { thinking: { type: "enabled" } } : undefined;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/providers/aihubmix/driver.ts
|
|
576
|
+
var PLACEHOLDER_API_KEY = "dim-placeholder-key";
|
|
577
|
+
var AIHUBMIX_REASONING_EFFORTS = ["minimal", "low", "medium", "high", "none", "xhigh"];
|
|
578
|
+
var aihubmixDriver = {
|
|
579
|
+
async generate(context) {
|
|
580
|
+
const model = createProvider(context.options).chat(context.request.model.modelId);
|
|
581
|
+
const aihubmixProviderOptions = buildReasoningEffortProviderOptions(context.request.reasoning, AIHUBMIX_REASONING_EFFORTS);
|
|
582
|
+
const callOptions = {
|
|
583
|
+
prompt: messagesToLanguageModelPrompt(context.request.messages),
|
|
584
|
+
tools: mapToolDefinitionsToLanguageModelTools(context.request.tools),
|
|
585
|
+
maxOutputTokens: context.request.maxOutputTokens,
|
|
586
|
+
temperature: context.request.temperature,
|
|
587
|
+
topP: context.request.topP,
|
|
588
|
+
stopSequences: context.request.stop,
|
|
589
|
+
abortSignal: context.request.signal,
|
|
590
|
+
...aihubmixProviderOptions ? { providerOptions: { openai: aihubmixProviderOptions } } : {}
|
|
591
|
+
};
|
|
592
|
+
return generateWithAiSdk({
|
|
593
|
+
model,
|
|
594
|
+
callOptions
|
|
595
|
+
});
|
|
596
|
+
},
|
|
597
|
+
normalizeError(error) {
|
|
598
|
+
if (APICallError.isInstance(error)) {
|
|
599
|
+
return createApiCallErrorPayload({
|
|
600
|
+
code: "aihubmix_http_error",
|
|
601
|
+
provider: "AIHubMix",
|
|
602
|
+
error
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
return createRequestErrorPayload({
|
|
606
|
+
code: "aihubmix_request_error",
|
|
607
|
+
error
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
function createProvider(options) {
|
|
612
|
+
const stripAuthorization = !options.apiKey;
|
|
613
|
+
warnIfPlaceholderApiKey({
|
|
614
|
+
provider: "AIHubMix",
|
|
615
|
+
shouldWarn: stripAuthorization,
|
|
616
|
+
hint: "This adapter does not expose provider-native auth headers in its options, so external auth must be added inside custom fetch when apiKey is omitted."
|
|
617
|
+
});
|
|
618
|
+
return createAihubmix({
|
|
619
|
+
apiKey: options.apiKey ?? PLACEHOLDER_API_KEY,
|
|
620
|
+
compatibility: options.compatibility,
|
|
621
|
+
fetch: createProviderFetch({
|
|
622
|
+
fetch: options.fetch,
|
|
623
|
+
stripHeaders: ["authorization"],
|
|
624
|
+
stripWhen: stripAuthorization
|
|
625
|
+
})
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/providers/aihubmix/adapter.ts
|
|
630
|
+
var createAihubmixAdapter = createProviderFactory({
|
|
631
|
+
defaultProvider: "aihubmix",
|
|
632
|
+
driver: aihubmixDriver
|
|
633
|
+
});
|
|
634
|
+
// src/providers/aihubmix-responses/driver.ts
|
|
635
|
+
import { APICallError as APICallError2 } from "@ai-sdk/provider";
|
|
636
|
+
import { createAihubmix as createAihubmix2 } from "@aihubmix/ai-sdk-provider";
|
|
637
|
+
|
|
638
|
+
// src/providers/shared/provider-state.ts
|
|
639
|
+
var PROVIDER_STATE_METADATA_KEY = "_dimProviderState";
|
|
640
|
+
function attachProviderState(metadata, provider, state) {
|
|
641
|
+
if (!state || Object.keys(state).length === 0)
|
|
642
|
+
return metadata;
|
|
643
|
+
const nextMetadata = metadata ? structuredClone(metadata) : {};
|
|
644
|
+
const currentBag = isRecord3(nextMetadata[PROVIDER_STATE_METADATA_KEY]) ? nextMetadata[PROVIDER_STATE_METADATA_KEY] : {};
|
|
645
|
+
nextMetadata[PROVIDER_STATE_METADATA_KEY] = {
|
|
646
|
+
...currentBag,
|
|
647
|
+
[provider]: structuredClone(state)
|
|
648
|
+
};
|
|
649
|
+
return nextMetadata;
|
|
650
|
+
}
|
|
651
|
+
function readProviderState(message, provider) {
|
|
652
|
+
if (message.role !== "assistant" || !isRecord3(message.metadata))
|
|
653
|
+
return;
|
|
654
|
+
const bag = message.metadata[PROVIDER_STATE_METADATA_KEY];
|
|
655
|
+
if (!isRecord3(bag))
|
|
656
|
+
return;
|
|
657
|
+
const state = bag[provider];
|
|
658
|
+
return isRecord3(state) ? structuredClone(state) : undefined;
|
|
659
|
+
}
|
|
660
|
+
function isRecord3(value) {
|
|
661
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/providers/shared/responses-state.ts
|
|
665
|
+
function sliceMessagesForResponses(messages, stateKey) {
|
|
666
|
+
let previousResponseId;
|
|
667
|
+
let startIndex = 0;
|
|
668
|
+
for (let index = messages.length - 1;index >= 0; index -= 1) {
|
|
669
|
+
const state = readProviderState(messages[index], stateKey);
|
|
670
|
+
if (typeof state?.responseId === "string") {
|
|
671
|
+
previousResponseId = state.responseId;
|
|
672
|
+
startIndex = index + 1;
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
messages: messages.slice(startIndex),
|
|
678
|
+
previousResponseId
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function createResponsesAssistantMetadata(input) {
|
|
682
|
+
const responseId = resolveResponseId(input.result, input.providerMetadataKey);
|
|
683
|
+
if (!responseId)
|
|
684
|
+
return;
|
|
685
|
+
return attachProviderState(undefined, input.stateKey, { responseId });
|
|
686
|
+
}
|
|
687
|
+
function resolveResponseId(result, providerMetadataKey) {
|
|
688
|
+
const providerMetadata = providerMetadataKey ? result.providerMetadata?.[providerMetadataKey] : undefined;
|
|
689
|
+
if (isRecord(providerMetadata) && typeof providerMetadata.responseId === "string")
|
|
690
|
+
return providerMetadata.responseId;
|
|
691
|
+
if (typeof result.response?.id === "string")
|
|
692
|
+
return result.response.id;
|
|
693
|
+
return extractResponseIdFromBody(result.response?.body);
|
|
694
|
+
}
|
|
695
|
+
function extractResponseIdFromBody(body) {
|
|
696
|
+
if (isRecord(body) && typeof body.id === "string")
|
|
697
|
+
return body.id;
|
|
698
|
+
if (typeof body !== "string")
|
|
699
|
+
return;
|
|
700
|
+
try {
|
|
701
|
+
const parsed = JSON.parse(body);
|
|
702
|
+
return isRecord(parsed) && typeof parsed.id === "string" ? parsed.id : undefined;
|
|
703
|
+
} catch {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/providers/aihubmix-responses/state.ts
|
|
709
|
+
var AIHUBMIX_RESPONSES_STATE_KEY = "aihubmixResponses";
|
|
710
|
+
function sliceMessagesForAihubmixResponses(messages) {
|
|
711
|
+
return sliceMessagesForResponses(messages, AIHUBMIX_RESPONSES_STATE_KEY);
|
|
712
|
+
}
|
|
713
|
+
function createAihubmixResponsesAssistantMetadata(result) {
|
|
714
|
+
return createResponsesAssistantMetadata({
|
|
715
|
+
result,
|
|
716
|
+
stateKey: AIHUBMIX_RESPONSES_STATE_KEY,
|
|
717
|
+
providerMetadataKey: "aihubmix"
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// src/providers/aihubmix-responses/driver.ts
|
|
722
|
+
var PLACEHOLDER_API_KEY2 = "dim-placeholder-key";
|
|
723
|
+
var aihubmixResponsesDriver = {
|
|
724
|
+
async generate(context) {
|
|
725
|
+
const requestReasoning = buildOpenAIResponsesRequestReasoning(context.request.reasoning);
|
|
726
|
+
const model = createProvider2(context.options, requestReasoning).responses(context.request.model.modelId);
|
|
727
|
+
const { messages, previousResponseId } = sliceMessagesForAihubmixResponses(context.request.messages);
|
|
728
|
+
const aihubmixProviderOptions = {
|
|
729
|
+
...buildOpenAIResponsesProviderOptions(context.request.reasoning) ?? {},
|
|
730
|
+
...previousResponseId ? { previousResponseId } : {}
|
|
731
|
+
};
|
|
732
|
+
const callOptions = {
|
|
733
|
+
prompt: messagesToLanguageModelPrompt(messages),
|
|
734
|
+
tools: mapToolDefinitionsToLanguageModelTools(context.request.tools),
|
|
735
|
+
maxOutputTokens: context.request.maxOutputTokens,
|
|
736
|
+
temperature: context.request.temperature,
|
|
737
|
+
topP: context.request.topP,
|
|
738
|
+
stopSequences: context.request.stop,
|
|
739
|
+
abortSignal: context.request.signal,
|
|
740
|
+
...Object.keys(aihubmixProviderOptions).length > 0 ? { providerOptions: { openai: aihubmixProviderOptions } } : {}
|
|
741
|
+
};
|
|
742
|
+
return generateWithAiSdk({
|
|
743
|
+
model,
|
|
744
|
+
callOptions,
|
|
745
|
+
createAssistantMetadata: createAihubmixResponsesAssistantMetadata
|
|
746
|
+
});
|
|
747
|
+
},
|
|
748
|
+
normalizeError(error) {
|
|
749
|
+
if (APICallError2.isInstance(error)) {
|
|
750
|
+
return createApiCallErrorPayload({
|
|
751
|
+
code: "aihubmix_responses_http_error",
|
|
752
|
+
provider: "AIHubMix Responses",
|
|
753
|
+
error
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
return createRequestErrorPayload({
|
|
757
|
+
code: "aihubmix_responses_request_error",
|
|
758
|
+
error
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
function createProvider2(options, requestReasoning) {
|
|
763
|
+
const stripAuthorization = !options.apiKey;
|
|
764
|
+
warnIfPlaceholderApiKey({
|
|
765
|
+
provider: "AIHubMix Responses",
|
|
766
|
+
shouldWarn: stripAuthorization,
|
|
767
|
+
hint: "This adapter does not expose provider-native auth headers in its options, so external auth must be added inside custom fetch when apiKey is omitted."
|
|
768
|
+
});
|
|
769
|
+
return createAihubmix2({
|
|
770
|
+
apiKey: options.apiKey ?? PLACEHOLDER_API_KEY2,
|
|
771
|
+
compatibility: options.compatibility,
|
|
772
|
+
fetch: createReasoningAwareResponsesFetch({
|
|
773
|
+
fetch: options.fetch,
|
|
774
|
+
stripAuthorization,
|
|
775
|
+
requestReasoning
|
|
776
|
+
})
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
function createReasoningAwareResponsesFetch(input) {
|
|
780
|
+
const baseFetch = createProviderFetch({
|
|
781
|
+
fetch: input.fetch,
|
|
782
|
+
stripHeaders: ["authorization"],
|
|
783
|
+
stripWhen: input.stripAuthorization
|
|
784
|
+
});
|
|
785
|
+
if (!input.requestReasoning)
|
|
786
|
+
return baseFetch;
|
|
787
|
+
const wrappedFetch = Object.assign(async (resource, init) => {
|
|
788
|
+
if (!init?.body)
|
|
789
|
+
return baseFetch(resource, init);
|
|
790
|
+
const body = parseJsonBody(init.body);
|
|
791
|
+
if (!isRecord(body) || body.reasoning !== undefined)
|
|
792
|
+
return baseFetch(resource, init);
|
|
793
|
+
return baseFetch(resource, {
|
|
794
|
+
...init,
|
|
795
|
+
body: JSON.stringify({
|
|
796
|
+
...body,
|
|
797
|
+
reasoning: input.requestReasoning
|
|
798
|
+
})
|
|
799
|
+
});
|
|
800
|
+
}, {
|
|
801
|
+
preconnect: typeof baseFetch.preconnect === "function" ? baseFetch.preconnect.bind(baseFetch) : undefined
|
|
802
|
+
});
|
|
803
|
+
return wrappedFetch;
|
|
804
|
+
}
|
|
805
|
+
function parseJsonBody(body) {
|
|
806
|
+
if (typeof body !== "string")
|
|
807
|
+
return;
|
|
808
|
+
try {
|
|
809
|
+
return JSON.parse(body);
|
|
810
|
+
} catch {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// src/providers/aihubmix-responses/adapter.ts
|
|
816
|
+
var createAihubmixResponsesAdapter = createProviderFactory({
|
|
817
|
+
defaultProvider: "aihubmix-responses",
|
|
818
|
+
driver: aihubmixResponsesDriver
|
|
819
|
+
});
|
|
820
|
+
// src/providers/anthropic/driver.ts
|
|
821
|
+
import { APICallError as APICallError3 } from "@ai-sdk/provider";
|
|
822
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
823
|
+
|
|
824
|
+
// src/providers/anthropic/state.ts
|
|
825
|
+
var ANTHROPIC_STATE_KEY = "anthropic";
|
|
826
|
+
function anthropicAssistantPrefixParts(message) {
|
|
827
|
+
const state = readProviderState(message, ANTHROPIC_STATE_KEY);
|
|
828
|
+
const rawBlocks = state?.blocks;
|
|
829
|
+
if (!Array.isArray(rawBlocks))
|
|
830
|
+
return [];
|
|
831
|
+
const parts = [];
|
|
832
|
+
for (const rawBlock of rawBlocks) {
|
|
833
|
+
if (!isRecord(rawBlock) || typeof rawBlock.type !== "string")
|
|
834
|
+
continue;
|
|
835
|
+
if (rawBlock.type === "thinking" && typeof rawBlock.signature === "string") {
|
|
836
|
+
parts.push({
|
|
837
|
+
type: "reasoning",
|
|
838
|
+
text: typeof rawBlock.thinking === "string" ? rawBlock.thinking : "",
|
|
839
|
+
providerOptions: {
|
|
840
|
+
anthropic: {
|
|
841
|
+
signature: rawBlock.signature
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
if (rawBlock.type === "redacted_thinking" && typeof rawBlock.data === "string") {
|
|
848
|
+
parts.push({
|
|
849
|
+
type: "reasoning",
|
|
850
|
+
text: "",
|
|
851
|
+
providerOptions: {
|
|
852
|
+
anthropic: {
|
|
853
|
+
redactedData: rawBlock.data
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return parts;
|
|
860
|
+
}
|
|
861
|
+
function createAnthropicAssistantMetadata(result) {
|
|
862
|
+
const blocks = result.content.flatMap((part) => {
|
|
863
|
+
if (part.type !== "reasoning")
|
|
864
|
+
return [];
|
|
865
|
+
const metadata = part.providerMetadata?.anthropic;
|
|
866
|
+
if (!isRecord(metadata))
|
|
867
|
+
return [];
|
|
868
|
+
if (typeof metadata.signature === "string") {
|
|
869
|
+
return [{
|
|
870
|
+
type: "thinking",
|
|
871
|
+
thinking: part.text,
|
|
872
|
+
signature: metadata.signature
|
|
873
|
+
}];
|
|
874
|
+
}
|
|
875
|
+
if (typeof metadata.redactedData === "string") {
|
|
876
|
+
return [{
|
|
877
|
+
type: "redacted_thinking",
|
|
878
|
+
data: metadata.redactedData
|
|
879
|
+
}];
|
|
880
|
+
}
|
|
881
|
+
return [];
|
|
882
|
+
});
|
|
883
|
+
if (blocks.length === 0)
|
|
884
|
+
return;
|
|
885
|
+
return attachProviderState(undefined, ANTHROPIC_STATE_KEY, { blocks });
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/providers/anthropic/driver.ts
|
|
889
|
+
var DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
|
|
890
|
+
var PLACEHOLDER_API_KEY3 = "dim-placeholder-key";
|
|
891
|
+
var anthropicDriver = {
|
|
892
|
+
async generate(context) {
|
|
893
|
+
const model = createProvider3(context.options)(context.request.model.modelId);
|
|
894
|
+
const prompt = messagesToLanguageModelPrompt(context.request.messages, {
|
|
895
|
+
assistantPrefixParts: anthropicAssistantPrefixParts
|
|
896
|
+
});
|
|
897
|
+
const anthropicProviderOptions = {
|
|
898
|
+
...buildAnthropicThinkingProviderOptions(context.request.reasoning) ?? {},
|
|
899
|
+
...hasReasoningState(context.request.messages) ? { sendReasoning: true } : {}
|
|
900
|
+
};
|
|
901
|
+
const callOptions = {
|
|
902
|
+
prompt,
|
|
903
|
+
tools: mapToolDefinitionsToLanguageModelTools(context.request.tools),
|
|
904
|
+
maxOutputTokens: context.request.maxOutputTokens,
|
|
905
|
+
temperature: context.request.temperature,
|
|
906
|
+
topP: context.request.topP,
|
|
907
|
+
stopSequences: context.request.stop,
|
|
908
|
+
abortSignal: context.request.signal,
|
|
909
|
+
...Object.keys(anthropicProviderOptions).length > 0 ? { providerOptions: { anthropic: anthropicProviderOptions } } : {}
|
|
910
|
+
};
|
|
911
|
+
return generateWithAiSdk({
|
|
912
|
+
model,
|
|
913
|
+
callOptions,
|
|
914
|
+
createAssistantMetadata: createAnthropicAssistantMetadata
|
|
915
|
+
});
|
|
916
|
+
},
|
|
917
|
+
normalizeError(error) {
|
|
918
|
+
if (APICallError3.isInstance(error)) {
|
|
919
|
+
return createApiCallErrorPayload({
|
|
920
|
+
code: "anthropic_http_error",
|
|
921
|
+
provider: "Anthropic",
|
|
922
|
+
error
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
return createRequestErrorPayload({
|
|
926
|
+
code: "anthropic_request_error",
|
|
927
|
+
error
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
function createProvider3(options) {
|
|
932
|
+
const stripAuthentication = !options.apiKey && !hasHeader(options.headers, "x-api-key") && !hasHeader(options.headers, "authorization");
|
|
933
|
+
warnIfPlaceholderApiKey({
|
|
934
|
+
provider: "Anthropic",
|
|
935
|
+
shouldWarn: stripAuthentication
|
|
936
|
+
});
|
|
937
|
+
return createAnthropic({
|
|
938
|
+
apiKey: options.apiKey ?? PLACEHOLDER_API_KEY3,
|
|
939
|
+
...options.baseUrl ? { baseURL: normalizeBaseUrl(options.baseUrl) } : {},
|
|
940
|
+
headers: {
|
|
941
|
+
"anthropic-version": options.anthropicVersion ?? DEFAULT_ANTHROPIC_VERSION,
|
|
942
|
+
...options.headers
|
|
943
|
+
},
|
|
944
|
+
fetch: createProviderFetch({
|
|
945
|
+
fetch: options.fetch,
|
|
946
|
+
stripHeaders: ["x-api-key", "authorization"],
|
|
947
|
+
stripWhen: stripAuthentication
|
|
948
|
+
})
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
function hasReasoningState(messages) {
|
|
952
|
+
return messages.some((message) => message.role === "assistant" && anthropicAssistantPrefixParts(message).length > 0);
|
|
953
|
+
}
|
|
954
|
+
function normalizeBaseUrl(baseUrl) {
|
|
955
|
+
const normalized = (baseUrl ?? "").replace(/\/$/, "");
|
|
956
|
+
return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/providers/anthropic/adapter.ts
|
|
960
|
+
var createAnthropicAdapter = createProviderFactory({
|
|
961
|
+
defaultProvider: "anthropic",
|
|
962
|
+
driver: anthropicDriver
|
|
963
|
+
});
|
|
964
|
+
// src/providers/deepseek/driver.ts
|
|
965
|
+
import { APICallError as APICallError4 } from "@ai-sdk/provider";
|
|
966
|
+
import { createDeepSeek } from "@ai-sdk/deepseek";
|
|
967
|
+
var DEFAULT_BASE_URL = "https://api.deepseek.com";
|
|
968
|
+
var PLACEHOLDER_API_KEY4 = "dim-placeholder-key";
|
|
969
|
+
var deepSeekDriver = {
|
|
970
|
+
async generate(context) {
|
|
971
|
+
const model = createProvider4(context.options).chat(context.request.model.modelId);
|
|
972
|
+
const deepseekProviderOptions = buildDeepSeekThinkingProviderOptions(context.request.reasoning);
|
|
973
|
+
const callOptions = {
|
|
974
|
+
prompt: messagesToLanguageModelPrompt(context.request.messages),
|
|
975
|
+
tools: mapToolDefinitionsToLanguageModelTools(context.request.tools),
|
|
976
|
+
maxOutputTokens: context.request.maxOutputTokens,
|
|
977
|
+
temperature: context.request.temperature,
|
|
978
|
+
topP: context.request.topP,
|
|
979
|
+
stopSequences: context.request.stop,
|
|
980
|
+
abortSignal: context.request.signal,
|
|
981
|
+
...deepseekProviderOptions ? { providerOptions: { deepseek: deepseekProviderOptions } } : {}
|
|
982
|
+
};
|
|
983
|
+
return generateWithAiSdk({
|
|
984
|
+
model,
|
|
985
|
+
callOptions
|
|
986
|
+
});
|
|
987
|
+
},
|
|
988
|
+
normalizeError(error) {
|
|
989
|
+
if (APICallError4.isInstance(error)) {
|
|
990
|
+
return createApiCallErrorPayload({
|
|
991
|
+
code: "deepseek_http_error",
|
|
992
|
+
provider: "DeepSeek",
|
|
993
|
+
error
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
return createRequestErrorPayload({
|
|
997
|
+
code: "deepseek_request_error",
|
|
998
|
+
error
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
function createProvider4(options) {
|
|
1003
|
+
const stripAuthorization = !options.apiKey && !hasHeader(options.headers, "authorization");
|
|
1004
|
+
warnIfPlaceholderApiKey({
|
|
1005
|
+
provider: "DeepSeek",
|
|
1006
|
+
shouldWarn: stripAuthorization
|
|
1007
|
+
});
|
|
1008
|
+
return createDeepSeek({
|
|
1009
|
+
apiKey: options.apiKey ?? PLACEHOLDER_API_KEY4,
|
|
1010
|
+
baseURL: normalizeBaseUrl2(options.baseUrl),
|
|
1011
|
+
headers: options.headers,
|
|
1012
|
+
fetch: createProviderFetch({
|
|
1013
|
+
fetch: options.fetch,
|
|
1014
|
+
stripHeaders: ["authorization"],
|
|
1015
|
+
stripWhen: stripAuthorization
|
|
1016
|
+
})
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
function normalizeBaseUrl2(baseUrl) {
|
|
1020
|
+
return (baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/providers/deepseek/adapter.ts
|
|
1024
|
+
var createDeepSeekAdapter = createProviderFactory({
|
|
1025
|
+
defaultProvider: "deepseek",
|
|
1026
|
+
driver: deepSeekDriver
|
|
1027
|
+
});
|
|
1028
|
+
// src/providers/gemini/driver.ts
|
|
1029
|
+
import { APICallError as APICallError5 } from "@ai-sdk/provider";
|
|
1030
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
1031
|
+
|
|
1032
|
+
// src/providers/gemini/state.ts
|
|
1033
|
+
var GEMINI_STATE_KEY = "gemini";
|
|
1034
|
+
function geminiAssistantPrefixParts(message) {
|
|
1035
|
+
const state = readProviderState(message, GEMINI_STATE_KEY);
|
|
1036
|
+
const rawParts = state?.parts;
|
|
1037
|
+
if (!Array.isArray(rawParts))
|
|
1038
|
+
return [];
|
|
1039
|
+
const parts = [];
|
|
1040
|
+
for (const rawPart of rawParts) {
|
|
1041
|
+
if (!isRecord(rawPart))
|
|
1042
|
+
continue;
|
|
1043
|
+
const thoughtSignature = typeof rawPart.thoughtSignature === "string" ? rawPart.thoughtSignature : undefined;
|
|
1044
|
+
if (isRecord(rawPart.functionCall) && typeof rawPart.functionCall.name === "string") {
|
|
1045
|
+
parts.push({
|
|
1046
|
+
type: "tool-call",
|
|
1047
|
+
toolCallId: crypto.randomUUID(),
|
|
1048
|
+
toolName: rawPart.functionCall.name,
|
|
1049
|
+
input: isRecord(rawPart.functionCall.args) ? rawPart.functionCall.args : {},
|
|
1050
|
+
...thoughtSignature ? {
|
|
1051
|
+
providerOptions: {
|
|
1052
|
+
google: {
|
|
1053
|
+
thoughtSignature
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
} : {}
|
|
1057
|
+
});
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (typeof rawPart.text === "string") {
|
|
1061
|
+
parts.push({
|
|
1062
|
+
type: rawPart.thought === true ? "reasoning" : "text",
|
|
1063
|
+
text: rawPart.text,
|
|
1064
|
+
...thoughtSignature ? {
|
|
1065
|
+
providerOptions: {
|
|
1066
|
+
google: {
|
|
1067
|
+
thoughtSignature
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
} : {}
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return parts;
|
|
1075
|
+
}
|
|
1076
|
+
function createGeminiAssistantMetadata(result) {
|
|
1077
|
+
const parts = result.content.flatMap((part) => {
|
|
1078
|
+
const metadata = part.providerMetadata?.google;
|
|
1079
|
+
const thoughtSignature = isRecord(metadata) && typeof metadata.thoughtSignature === "string" ? metadata.thoughtSignature : undefined;
|
|
1080
|
+
if (!thoughtSignature && part.type !== "reasoning")
|
|
1081
|
+
return [];
|
|
1082
|
+
if (part.type === "reasoning") {
|
|
1083
|
+
return [{
|
|
1084
|
+
text: part.text,
|
|
1085
|
+
thought: true,
|
|
1086
|
+
thoughtSignature
|
|
1087
|
+
}];
|
|
1088
|
+
}
|
|
1089
|
+
if (part.type === "tool-call") {
|
|
1090
|
+
return [{
|
|
1091
|
+
functionCall: {
|
|
1092
|
+
name: part.toolName,
|
|
1093
|
+
args: parseToolArguments(part.input)
|
|
1094
|
+
},
|
|
1095
|
+
thoughtSignature
|
|
1096
|
+
}];
|
|
1097
|
+
}
|
|
1098
|
+
if (part.type === "text") {
|
|
1099
|
+
return [{
|
|
1100
|
+
text: part.text,
|
|
1101
|
+
thoughtSignature
|
|
1102
|
+
}];
|
|
1103
|
+
}
|
|
1104
|
+
return [];
|
|
1105
|
+
});
|
|
1106
|
+
if (parts.length === 0)
|
|
1107
|
+
return;
|
|
1108
|
+
return attachProviderState(undefined, GEMINI_STATE_KEY, { parts });
|
|
1109
|
+
}
|
|
1110
|
+
function parseToolArguments(input) {
|
|
1111
|
+
try {
|
|
1112
|
+
const parsed = JSON.parse(input);
|
|
1113
|
+
return isRecord(parsed) ? parsed : {};
|
|
1114
|
+
} catch {
|
|
1115
|
+
return {};
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// src/providers/gemini/driver.ts
|
|
1120
|
+
var DEFAULT_BASE_URL2 = "https://generativelanguage.googleapis.com";
|
|
1121
|
+
var PLACEHOLDER_API_KEY5 = "dim-placeholder-key";
|
|
1122
|
+
var geminiDriver = {
|
|
1123
|
+
async generate(context) {
|
|
1124
|
+
const model = createProvider5(context.options)(context.request.model.modelId);
|
|
1125
|
+
const googleProviderOptions = buildGeminiThinkingProviderOptions(context.request.reasoning);
|
|
1126
|
+
const callOptions = {
|
|
1127
|
+
prompt: messagesToLanguageModelPrompt(context.request.messages, {
|
|
1128
|
+
assistantPrefixParts: geminiAssistantPrefixParts
|
|
1129
|
+
}),
|
|
1130
|
+
tools: mapToolDefinitionsToLanguageModelTools(context.request.tools),
|
|
1131
|
+
maxOutputTokens: context.request.maxOutputTokens,
|
|
1132
|
+
temperature: context.request.temperature,
|
|
1133
|
+
topP: context.request.topP,
|
|
1134
|
+
stopSequences: context.request.stop,
|
|
1135
|
+
abortSignal: context.request.signal,
|
|
1136
|
+
...googleProviderOptions ? { providerOptions: { google: googleProviderOptions } } : {}
|
|
1137
|
+
};
|
|
1138
|
+
return generateWithAiSdk({
|
|
1139
|
+
model,
|
|
1140
|
+
callOptions,
|
|
1141
|
+
createAssistantMetadata: createGeminiAssistantMetadata
|
|
1142
|
+
});
|
|
1143
|
+
},
|
|
1144
|
+
normalizeError(error) {
|
|
1145
|
+
if (APICallError5.isInstance(error)) {
|
|
1146
|
+
return createApiCallErrorPayload({
|
|
1147
|
+
code: "gemini_http_error",
|
|
1148
|
+
provider: "Gemini",
|
|
1149
|
+
error
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
return createRequestErrorPayload({
|
|
1153
|
+
code: "gemini_request_error",
|
|
1154
|
+
error
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
function createProvider5(options) {
|
|
1159
|
+
const stripApiKey = !options.apiKey && !hasHeader(options.headers, "x-goog-api-key");
|
|
1160
|
+
warnIfPlaceholderApiKey({
|
|
1161
|
+
provider: "Gemini",
|
|
1162
|
+
shouldWarn: stripApiKey
|
|
1163
|
+
});
|
|
1164
|
+
return createGoogleGenerativeAI({
|
|
1165
|
+
apiKey: options.apiKey ?? PLACEHOLDER_API_KEY5,
|
|
1166
|
+
baseURL: normalizeBaseUrl3(options.baseUrl),
|
|
1167
|
+
headers: options.headers,
|
|
1168
|
+
fetch: createProviderFetch({
|
|
1169
|
+
fetch: options.fetch,
|
|
1170
|
+
stripHeaders: ["x-goog-api-key"],
|
|
1171
|
+
stripWhen: stripApiKey
|
|
1172
|
+
})
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
function normalizeBaseUrl3(baseUrl) {
|
|
1176
|
+
const normalized = (baseUrl ?? DEFAULT_BASE_URL2).replace(/\/$/, "");
|
|
1177
|
+
return normalized.endsWith("/v1beta") ? normalized : `${normalized}/v1beta`;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/providers/gemini/adapter.ts
|
|
1181
|
+
var createGeminiAdapter = createProviderFactory({
|
|
1182
|
+
defaultProvider: "gemini",
|
|
1183
|
+
driver: geminiDriver
|
|
1184
|
+
});
|
|
1185
|
+
// src/providers/moonshotai/driver.ts
|
|
1186
|
+
import { APICallError as APICallError6 } from "@ai-sdk/provider";
|
|
1187
|
+
import { createMoonshotAI } from "@ai-sdk/moonshotai";
|
|
1188
|
+
var DEFAULT_BASE_URL3 = "https://api.moonshot.ai/v1";
|
|
1189
|
+
var PLACEHOLDER_API_KEY6 = "dim-placeholder-key";
|
|
1190
|
+
var moonshotAIDriver = {
|
|
1191
|
+
async generate(context) {
|
|
1192
|
+
const model = createProvider6(context.options).languageModel(context.request.model.modelId);
|
|
1193
|
+
const moonshotaiProviderOptions = buildMoonshotThinkingProviderOptions(context.request.reasoning);
|
|
1194
|
+
const callOptions = {
|
|
1195
|
+
prompt: messagesToLanguageModelPrompt(context.request.messages),
|
|
1196
|
+
tools: mapToolDefinitionsToLanguageModelTools(context.request.tools),
|
|
1197
|
+
maxOutputTokens: context.request.maxOutputTokens,
|
|
1198
|
+
temperature: context.request.temperature,
|
|
1199
|
+
topP: context.request.topP,
|
|
1200
|
+
stopSequences: context.request.stop,
|
|
1201
|
+
abortSignal: context.request.signal,
|
|
1202
|
+
...moonshotaiProviderOptions ? { providerOptions: { moonshotai: moonshotaiProviderOptions } } : {}
|
|
1203
|
+
};
|
|
1204
|
+
return generateWithAiSdk({
|
|
1205
|
+
model,
|
|
1206
|
+
callOptions
|
|
1207
|
+
});
|
|
1208
|
+
},
|
|
1209
|
+
normalizeError(error) {
|
|
1210
|
+
if (APICallError6.isInstance(error)) {
|
|
1211
|
+
return createApiCallErrorPayload({
|
|
1212
|
+
code: "moonshotai_http_error",
|
|
1213
|
+
provider: "MoonshotAI",
|
|
1214
|
+
error
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
return createRequestErrorPayload({
|
|
1218
|
+
code: "moonshotai_request_error",
|
|
1219
|
+
error
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
function createProvider6(options) {
|
|
1224
|
+
const stripAuthorization = !options.apiKey && !hasHeader(options.headers, "authorization");
|
|
1225
|
+
warnIfPlaceholderApiKey({
|
|
1226
|
+
provider: "MoonshotAI",
|
|
1227
|
+
shouldWarn: stripAuthorization
|
|
1228
|
+
});
|
|
1229
|
+
return createMoonshotAI({
|
|
1230
|
+
apiKey: options.apiKey ?? PLACEHOLDER_API_KEY6,
|
|
1231
|
+
baseURL: normalizeBaseUrl4(options.baseUrl),
|
|
1232
|
+
headers: options.headers,
|
|
1233
|
+
fetch: createProviderFetch({
|
|
1234
|
+
fetch: options.fetch,
|
|
1235
|
+
stripHeaders: ["authorization"],
|
|
1236
|
+
stripWhen: stripAuthorization
|
|
1237
|
+
})
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
function normalizeBaseUrl4(baseUrl) {
|
|
1241
|
+
return (baseUrl ?? DEFAULT_BASE_URL3).replace(/\/$/, "");
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// src/providers/moonshotai/adapter.ts
|
|
1245
|
+
var createMoonshotAIAdapter = createProviderFactory({
|
|
1246
|
+
defaultProvider: "moonshotai",
|
|
1247
|
+
driver: moonshotAIDriver
|
|
1248
|
+
});
|
|
1249
|
+
// src/providers/openai/driver.ts
|
|
1250
|
+
import { APICallError as APICallError7 } from "@ai-sdk/provider";
|
|
1251
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
1252
|
+
var DEFAULT_BASE_URL4 = "https://api.openai.com/v1";
|
|
1253
|
+
var PLACEHOLDER_API_KEY7 = "dim-placeholder-key";
|
|
1254
|
+
var openAIDriver = {
|
|
1255
|
+
async generate(context) {
|
|
1256
|
+
const model = createProvider7(context.options).chat(context.request.model.modelId);
|
|
1257
|
+
const callOptions = {
|
|
1258
|
+
prompt: messagesToLanguageModelPrompt(context.request.messages),
|
|
1259
|
+
tools: mapToolDefinitionsToLanguageModelTools(context.request.tools),
|
|
1260
|
+
maxOutputTokens: context.request.maxOutputTokens,
|
|
1261
|
+
temperature: context.request.temperature,
|
|
1262
|
+
topP: context.request.topP,
|
|
1263
|
+
stopSequences: context.request.stop,
|
|
1264
|
+
abortSignal: context.request.signal
|
|
1265
|
+
};
|
|
1266
|
+
return generateWithAiSdk({
|
|
1267
|
+
model,
|
|
1268
|
+
callOptions
|
|
1269
|
+
});
|
|
1270
|
+
},
|
|
1271
|
+
normalizeError(error) {
|
|
1272
|
+
if (APICallError7.isInstance(error)) {
|
|
1273
|
+
return createApiCallErrorPayload({
|
|
1274
|
+
code: "openai_http_error",
|
|
1275
|
+
provider: "OpenAI-compatible",
|
|
1276
|
+
error
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
return createRequestErrorPayload({
|
|
1280
|
+
code: "openai_request_error",
|
|
1281
|
+
error
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
function createProvider7(options) {
|
|
1286
|
+
const stripAuthorization = !options.apiKey && !hasHeader(options.headers, "authorization");
|
|
1287
|
+
warnIfPlaceholderApiKey({
|
|
1288
|
+
provider: "OpenAI-compatible",
|
|
1289
|
+
shouldWarn: stripAuthorization
|
|
1290
|
+
});
|
|
1291
|
+
return createOpenAI({
|
|
1292
|
+
apiKey: options.apiKey ?? PLACEHOLDER_API_KEY7,
|
|
1293
|
+
baseURL: normalizeBaseUrl5(options.baseUrl),
|
|
1294
|
+
headers: options.headers,
|
|
1295
|
+
fetch: createProviderFetch({
|
|
1296
|
+
fetch: options.fetch,
|
|
1297
|
+
stripHeaders: ["authorization"],
|
|
1298
|
+
stripWhen: stripAuthorization
|
|
1299
|
+
})
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
function normalizeBaseUrl5(baseUrl) {
|
|
1303
|
+
return (baseUrl ?? DEFAULT_BASE_URL4).replace(/\/$/, "");
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// src/providers/openai/adapter.ts
|
|
1307
|
+
var createOpenAIAdapter = createProviderFactory({
|
|
1308
|
+
defaultProvider: "openai-compatible",
|
|
1309
|
+
driver: openAIDriver
|
|
1310
|
+
});
|
|
1311
|
+
// src/providers/openai-responses/driver.ts
|
|
1312
|
+
import { APICallError as APICallError8 } from "@ai-sdk/provider";
|
|
1313
|
+
import { createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
|
|
1314
|
+
|
|
1315
|
+
// src/providers/openai-responses/state.ts
|
|
1316
|
+
var OPENAI_RESPONSES_STATE_KEY = "openaiResponses";
|
|
1317
|
+
function sliceMessagesForOpenAIResponses(messages) {
|
|
1318
|
+
return sliceMessagesForResponses(messages, OPENAI_RESPONSES_STATE_KEY);
|
|
1319
|
+
}
|
|
1320
|
+
function createOpenAIResponsesAssistantMetadata(result) {
|
|
1321
|
+
return createResponsesAssistantMetadata({
|
|
1322
|
+
result,
|
|
1323
|
+
stateKey: OPENAI_RESPONSES_STATE_KEY,
|
|
1324
|
+
providerMetadataKey: "openai"
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// src/providers/openai-responses/driver.ts
|
|
1329
|
+
var DEFAULT_BASE_URL5 = "https://api.openai.com/v1";
|
|
1330
|
+
var PLACEHOLDER_API_KEY8 = "dim-placeholder-key";
|
|
1331
|
+
var openAIResponsesDriver = {
|
|
1332
|
+
async generate(context) {
|
|
1333
|
+
const model = createProvider8(context.options).responses(context.request.model.modelId);
|
|
1334
|
+
const { messages, previousResponseId } = sliceMessagesForOpenAIResponses(context.request.messages);
|
|
1335
|
+
const openaiProviderOptions = {
|
|
1336
|
+
...buildOpenAIResponsesProviderOptions(context.request.reasoning) ?? {},
|
|
1337
|
+
...previousResponseId ? { previousResponseId } : {}
|
|
1338
|
+
};
|
|
1339
|
+
const callOptions = {
|
|
1340
|
+
prompt: messagesToLanguageModelPrompt(messages),
|
|
1341
|
+
tools: mapToolDefinitionsToLanguageModelTools(context.request.tools),
|
|
1342
|
+
maxOutputTokens: context.request.maxOutputTokens,
|
|
1343
|
+
temperature: context.request.temperature,
|
|
1344
|
+
topP: context.request.topP,
|
|
1345
|
+
stopSequences: context.request.stop,
|
|
1346
|
+
abortSignal: context.request.signal,
|
|
1347
|
+
...Object.keys(openaiProviderOptions).length > 0 ? { providerOptions: { openai: openaiProviderOptions } } : {}
|
|
1348
|
+
};
|
|
1349
|
+
return generateWithAiSdk({
|
|
1350
|
+
model,
|
|
1351
|
+
callOptions,
|
|
1352
|
+
createAssistantMetadata: createOpenAIResponsesAssistantMetadata
|
|
1353
|
+
});
|
|
1354
|
+
},
|
|
1355
|
+
normalizeError(error) {
|
|
1356
|
+
if (APICallError8.isInstance(error)) {
|
|
1357
|
+
return createApiCallErrorPayload({
|
|
1358
|
+
code: "openai_responses_http_error",
|
|
1359
|
+
provider: "OpenAI Responses",
|
|
1360
|
+
error
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
return createRequestErrorPayload({
|
|
1364
|
+
code: "openai_responses_request_error",
|
|
1365
|
+
error
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
function createProvider8(options) {
|
|
1370
|
+
const stripAuthorization = !options.apiKey && !hasHeader(options.headers, "authorization");
|
|
1371
|
+
warnIfPlaceholderApiKey({
|
|
1372
|
+
provider: "OpenAI Responses",
|
|
1373
|
+
shouldWarn: stripAuthorization
|
|
1374
|
+
});
|
|
1375
|
+
return createOpenAI2({
|
|
1376
|
+
apiKey: options.apiKey ?? PLACEHOLDER_API_KEY8,
|
|
1377
|
+
baseURL: normalizeBaseUrl6(options.baseUrl),
|
|
1378
|
+
headers: options.headers,
|
|
1379
|
+
fetch: createProviderFetch({
|
|
1380
|
+
fetch: options.fetch,
|
|
1381
|
+
stripHeaders: ["authorization"],
|
|
1382
|
+
stripWhen: stripAuthorization
|
|
1383
|
+
})
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
function normalizeBaseUrl6(baseUrl) {
|
|
1387
|
+
return (baseUrl ?? DEFAULT_BASE_URL5).replace(/\/$/, "");
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// src/providers/openai-responses/adapter.ts
|
|
1391
|
+
var createOpenAIResponsesAdapter = createProviderFactory({
|
|
1392
|
+
defaultProvider: "openai-responses",
|
|
1393
|
+
driver: openAIResponsesDriver
|
|
1394
|
+
});
|
|
1395
|
+
// src/providers/xai/driver.ts
|
|
1396
|
+
import { APICallError as APICallError9 } from "@ai-sdk/provider";
|
|
1397
|
+
import { createXai } from "@ai-sdk/xai";
|
|
1398
|
+
var DEFAULT_BASE_URL6 = "https://api.x.ai/v1";
|
|
1399
|
+
var PLACEHOLDER_API_KEY9 = "dim-placeholder-key";
|
|
1400
|
+
var xaiDriver = {
|
|
1401
|
+
async generate(context) {
|
|
1402
|
+
const model = createProvider9(context.options).chat(context.request.model.modelId);
|
|
1403
|
+
const reasoningEffort = resolveLowHighReasoningEffort(context.request.reasoning);
|
|
1404
|
+
const callOptions = {
|
|
1405
|
+
prompt: messagesToLanguageModelPrompt(context.request.messages),
|
|
1406
|
+
tools: mapToolDefinitionsToLanguageModelTools(context.request.tools),
|
|
1407
|
+
maxOutputTokens: context.request.maxOutputTokens,
|
|
1408
|
+
temperature: context.request.temperature,
|
|
1409
|
+
topP: context.request.topP,
|
|
1410
|
+
stopSequences: context.request.stop,
|
|
1411
|
+
abortSignal: context.request.signal,
|
|
1412
|
+
...reasoningEffort ? { providerOptions: { xai: { reasoningEffort } } } : {}
|
|
1413
|
+
};
|
|
1414
|
+
return generateWithAiSdk({
|
|
1415
|
+
model,
|
|
1416
|
+
callOptions
|
|
1417
|
+
});
|
|
1418
|
+
},
|
|
1419
|
+
normalizeError(error) {
|
|
1420
|
+
if (APICallError9.isInstance(error)) {
|
|
1421
|
+
return createApiCallErrorPayload({
|
|
1422
|
+
code: "xai_http_error",
|
|
1423
|
+
provider: "xAI",
|
|
1424
|
+
error
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
return createRequestErrorPayload({
|
|
1428
|
+
code: "xai_request_error",
|
|
1429
|
+
error
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
function createProvider9(options) {
|
|
1434
|
+
const stripAuthorization = !options.apiKey && !hasHeader(options.headers, "authorization");
|
|
1435
|
+
warnIfPlaceholderApiKey({
|
|
1436
|
+
provider: "xAI",
|
|
1437
|
+
shouldWarn: stripAuthorization
|
|
1438
|
+
});
|
|
1439
|
+
return createXai({
|
|
1440
|
+
apiKey: options.apiKey ?? PLACEHOLDER_API_KEY9,
|
|
1441
|
+
baseURL: normalizeBaseUrl7(options.baseUrl),
|
|
1442
|
+
headers: options.headers,
|
|
1443
|
+
fetch: createProviderFetch({
|
|
1444
|
+
fetch: options.fetch,
|
|
1445
|
+
stripHeaders: ["authorization"],
|
|
1446
|
+
stripWhen: stripAuthorization
|
|
1447
|
+
})
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
function normalizeBaseUrl7(baseUrl) {
|
|
1451
|
+
return (baseUrl ?? DEFAULT_BASE_URL6).replace(/\/$/, "");
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// src/providers/xai/adapter.ts
|
|
1455
|
+
var createXaiAdapter = createProviderFactory({
|
|
1456
|
+
defaultProvider: "xai",
|
|
1457
|
+
driver: xaiDriver
|
|
1458
|
+
});
|
|
1459
|
+
// src/providers/xai-responses/driver.ts
|
|
1460
|
+
import { APICallError as APICallError10 } from "@ai-sdk/provider";
|
|
1461
|
+
import { createXai as createXai2 } from "@ai-sdk/xai";
|
|
1462
|
+
|
|
1463
|
+
// src/providers/xai-responses/state.ts
|
|
1464
|
+
var XAI_RESPONSES_STATE_KEY = "xaiResponses";
|
|
1465
|
+
function sliceMessagesForXaiResponses(messages) {
|
|
1466
|
+
return sliceMessagesForResponses(messages, XAI_RESPONSES_STATE_KEY);
|
|
1467
|
+
}
|
|
1468
|
+
function createXaiResponsesAssistantMetadata(result) {
|
|
1469
|
+
return createResponsesAssistantMetadata({
|
|
1470
|
+
result,
|
|
1471
|
+
stateKey: XAI_RESPONSES_STATE_KEY,
|
|
1472
|
+
providerMetadataKey: "xai"
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// src/providers/xai-responses/driver.ts
|
|
1477
|
+
var DEFAULT_BASE_URL7 = "https://api.x.ai/v1";
|
|
1478
|
+
var PLACEHOLDER_API_KEY10 = "dim-placeholder-key";
|
|
1479
|
+
var xaiResponsesDriver = {
|
|
1480
|
+
async generate(context) {
|
|
1481
|
+
const model = createProvider10(context.options).responses(context.request.model.modelId);
|
|
1482
|
+
const { messages, previousResponseId } = sliceMessagesForXaiResponses(context.request.messages);
|
|
1483
|
+
const reasoningEffort = resolveLowMediumHighReasoningEffort(context.request.reasoning);
|
|
1484
|
+
const xaiProviderOptions = {
|
|
1485
|
+
...reasoningEffort ? { reasoningEffort } : {},
|
|
1486
|
+
...previousResponseId ? { previousResponseId } : {}
|
|
1487
|
+
};
|
|
1488
|
+
const callOptions = {
|
|
1489
|
+
prompt: messagesToLanguageModelPrompt(messages),
|
|
1490
|
+
tools: mapToolDefinitionsToLanguageModelTools(context.request.tools),
|
|
1491
|
+
maxOutputTokens: context.request.maxOutputTokens,
|
|
1492
|
+
temperature: context.request.temperature,
|
|
1493
|
+
topP: context.request.topP,
|
|
1494
|
+
stopSequences: context.request.stop,
|
|
1495
|
+
abortSignal: context.request.signal,
|
|
1496
|
+
...Object.keys(xaiProviderOptions).length > 0 ? { providerOptions: { xai: xaiProviderOptions } } : {}
|
|
1497
|
+
};
|
|
1498
|
+
return generateWithAiSdk({
|
|
1499
|
+
model,
|
|
1500
|
+
callOptions,
|
|
1501
|
+
createAssistantMetadata: createXaiResponsesAssistantMetadata
|
|
1502
|
+
});
|
|
1503
|
+
},
|
|
1504
|
+
normalizeError(error) {
|
|
1505
|
+
if (APICallError10.isInstance(error)) {
|
|
1506
|
+
return createApiCallErrorPayload({
|
|
1507
|
+
code: "xai_responses_http_error",
|
|
1508
|
+
provider: "xAI Responses",
|
|
1509
|
+
error
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
return createRequestErrorPayload({
|
|
1513
|
+
code: "xai_responses_request_error",
|
|
1514
|
+
error
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
};
|
|
1518
|
+
function createProvider10(options) {
|
|
1519
|
+
const stripAuthorization = !options.apiKey && !hasHeader(options.headers, "authorization");
|
|
1520
|
+
warnIfPlaceholderApiKey({
|
|
1521
|
+
provider: "xAI Responses",
|
|
1522
|
+
shouldWarn: stripAuthorization
|
|
1523
|
+
});
|
|
1524
|
+
return createXai2({
|
|
1525
|
+
apiKey: options.apiKey ?? PLACEHOLDER_API_KEY10,
|
|
1526
|
+
baseURL: normalizeBaseUrl8(options.baseUrl),
|
|
1527
|
+
headers: options.headers,
|
|
1528
|
+
fetch: createProviderFetch({
|
|
1529
|
+
fetch: options.fetch,
|
|
1530
|
+
stripHeaders: ["authorization"],
|
|
1531
|
+
stripWhen: stripAuthorization
|
|
1532
|
+
})
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
function normalizeBaseUrl8(baseUrl) {
|
|
1536
|
+
return (baseUrl ?? DEFAULT_BASE_URL7).replace(/\/$/, "");
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/providers/xai-responses/adapter.ts
|
|
1540
|
+
var createXaiResponsesAdapter = createProviderFactory({
|
|
1541
|
+
defaultProvider: "xai-responses",
|
|
1542
|
+
driver: xaiResponsesDriver
|
|
1543
|
+
});
|
|
1544
|
+
// src/providers/zenmux/driver.ts
|
|
1545
|
+
import { APICallError as APICallError11 } from "@ai-sdk/provider";
|
|
1546
|
+
import { createZenMux } from "@zenmux/ai-sdk-provider";
|
|
1547
|
+
var DEFAULT_BASE_URL8 = "https://zenmux.ai/api/v1";
|
|
1548
|
+
var PLACEHOLDER_API_KEY11 = "dim-placeholder-key";
|
|
1549
|
+
var ZENMUX_REASONING_EFFORTS = ["minimal", "low", "medium", "high"];
|
|
1550
|
+
var zenmuxDriver = {
|
|
1551
|
+
async generate(context) {
|
|
1552
|
+
const model = createProvider11(context.options).chat(context.request.model.modelId);
|
|
1553
|
+
const zenmuxProviderOptions = buildReasoningEffortProviderOptions(context.request.reasoning, ZENMUX_REASONING_EFFORTS);
|
|
1554
|
+
const callOptions = {
|
|
1555
|
+
prompt: messagesToLanguageModelPrompt(context.request.messages),
|
|
1556
|
+
tools: mapToolDefinitionsToLanguageModelTools(context.request.tools),
|
|
1557
|
+
maxOutputTokens: context.request.maxOutputTokens,
|
|
1558
|
+
temperature: context.request.temperature,
|
|
1559
|
+
topP: context.request.topP,
|
|
1560
|
+
stopSequences: context.request.stop,
|
|
1561
|
+
abortSignal: context.request.signal,
|
|
1562
|
+
...zenmuxProviderOptions ? { providerOptions: { zenmux: zenmuxProviderOptions } } : {}
|
|
1563
|
+
};
|
|
1564
|
+
return generateWithAiSdk({
|
|
1565
|
+
model,
|
|
1566
|
+
callOptions
|
|
1567
|
+
});
|
|
1568
|
+
},
|
|
1569
|
+
normalizeError(error) {
|
|
1570
|
+
if (APICallError11.isInstance(error)) {
|
|
1571
|
+
return createApiCallErrorPayload({
|
|
1572
|
+
code: "zenmux_http_error",
|
|
1573
|
+
provider: "ZenMux",
|
|
1574
|
+
error
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
return createRequestErrorPayload({
|
|
1578
|
+
code: "zenmux_request_error",
|
|
1579
|
+
error
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
function createProvider11(options) {
|
|
1584
|
+
const stripAuthorization = !options.apiKey && !hasHeader(options.headers, "authorization");
|
|
1585
|
+
warnIfPlaceholderApiKey({
|
|
1586
|
+
provider: "ZenMux",
|
|
1587
|
+
shouldWarn: stripAuthorization
|
|
1588
|
+
});
|
|
1589
|
+
return createZenMux({
|
|
1590
|
+
apiKey: options.apiKey ?? PLACEHOLDER_API_KEY11,
|
|
1591
|
+
baseURL: normalizeBaseUrl9(options.baseUrl),
|
|
1592
|
+
headers: options.headers,
|
|
1593
|
+
organization: options.organization,
|
|
1594
|
+
project: options.project,
|
|
1595
|
+
fetch: createProviderFetch({
|
|
1596
|
+
fetch: options.fetch,
|
|
1597
|
+
stripHeaders: ["authorization"],
|
|
1598
|
+
stripWhen: stripAuthorization
|
|
1599
|
+
})
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
function normalizeBaseUrl9(baseUrl) {
|
|
1603
|
+
return (baseUrl ?? DEFAULT_BASE_URL8).replace(/\/$/, "");
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// src/providers/zenmux/adapter.ts
|
|
1607
|
+
var createZenMuxAdapter = createProviderFactory({
|
|
1608
|
+
defaultProvider: "zenmux",
|
|
1609
|
+
driver: zenmuxDriver
|
|
1610
|
+
});
|
|
1611
|
+
// src/context/auto-context-manager.ts
|
|
1612
|
+
import path from "node:path";
|
|
1613
|
+
|
|
1614
|
+
// src/utils/id.ts
|
|
1615
|
+
function createId(prefix) {
|
|
1616
|
+
return `${prefix}_${crypto.randomUUID()}`;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// src/context/auto-context-manager.ts
|
|
1620
|
+
class AutoContextManager {
|
|
1621
|
+
services;
|
|
1622
|
+
pluginHost;
|
|
1623
|
+
maxItems;
|
|
1624
|
+
maxChars;
|
|
1625
|
+
constructor(options) {
|
|
1626
|
+
this.services = options.services;
|
|
1627
|
+
this.pluginHost = options.pluginHost;
|
|
1628
|
+
this.maxItems = options.maxItems ?? 8;
|
|
1629
|
+
this.maxChars = options.maxChars ?? 8000;
|
|
1630
|
+
}
|
|
1631
|
+
async resolve(options) {
|
|
1632
|
+
const items = [];
|
|
1633
|
+
if (options.cwd) {
|
|
1634
|
+
items.push({
|
|
1635
|
+
id: createId("ctx"),
|
|
1636
|
+
type: "cwd",
|
|
1637
|
+
title: "Working directory",
|
|
1638
|
+
content: options.cwd,
|
|
1639
|
+
priority: 10,
|
|
1640
|
+
source: "agent"
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
items.push(...await this.resolveExplicitFiles(options.input, options.cwd));
|
|
1644
|
+
items.push(...await this.resolveRecentFiles(options.cwd));
|
|
1645
|
+
items.push(...await this.resolveGitState(options.cwd));
|
|
1646
|
+
const contributed = await this.pluginHost?.collectContext({
|
|
1647
|
+
sessionId: options.sessionId,
|
|
1648
|
+
input: options.input,
|
|
1649
|
+
messages: options.messages,
|
|
1650
|
+
cwd: options.cwd
|
|
1651
|
+
});
|
|
1652
|
+
if (contributed)
|
|
1653
|
+
items.push(...contributed);
|
|
1654
|
+
return budgetContext(items, this.maxItems, this.maxChars);
|
|
1655
|
+
}
|
|
1656
|
+
format(items) {
|
|
1657
|
+
if (items.length === 0)
|
|
1658
|
+
return [];
|
|
1659
|
+
const sections = items.map((item) => {
|
|
1660
|
+
return [`[${item.title}]`, item.content].join(`
|
|
1661
|
+
`);
|
|
1662
|
+
});
|
|
1663
|
+
return [`Additional context for this turn:
|
|
1664
|
+
|
|
1665
|
+
${sections.join(`
|
|
1666
|
+
|
|
1667
|
+
`)}`];
|
|
1668
|
+
}
|
|
1669
|
+
async resolveExplicitFiles(input, cwd) {
|
|
1670
|
+
const text = inputToText(input);
|
|
1671
|
+
const candidates = extractFileCandidates(text);
|
|
1672
|
+
const items = [];
|
|
1673
|
+
for (const candidate of candidates) {
|
|
1674
|
+
if (!cwd)
|
|
1675
|
+
break;
|
|
1676
|
+
if (!await this.services.fileSystem.exists(candidate, { cwd }))
|
|
1677
|
+
continue;
|
|
1678
|
+
const content = await safeReadText(this.services, candidate, cwd);
|
|
1679
|
+
if (!content)
|
|
1680
|
+
continue;
|
|
1681
|
+
items.push({
|
|
1682
|
+
id: createId("ctx"),
|
|
1683
|
+
type: "file",
|
|
1684
|
+
title: `File: ${candidate}`,
|
|
1685
|
+
content,
|
|
1686
|
+
priority: 100,
|
|
1687
|
+
source: "explicit-file"
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
return items;
|
|
1691
|
+
}
|
|
1692
|
+
async resolveRecentFiles(cwd) {
|
|
1693
|
+
if (!cwd || !this.pluginHost)
|
|
1694
|
+
return [];
|
|
1695
|
+
const items = [];
|
|
1696
|
+
for (const filePath of this.pluginHost.recentFiles(3)) {
|
|
1697
|
+
const relative = toRelative(cwd, filePath);
|
|
1698
|
+
const content = await safeReadText(this.services, relative, cwd);
|
|
1699
|
+
if (!content)
|
|
1700
|
+
continue;
|
|
1701
|
+
items.push({
|
|
1702
|
+
id: createId("ctx"),
|
|
1703
|
+
type: "file",
|
|
1704
|
+
title: `Recent file: ${relative}`,
|
|
1705
|
+
content,
|
|
1706
|
+
priority: 70,
|
|
1707
|
+
source: "recent-file"
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
return items;
|
|
1711
|
+
}
|
|
1712
|
+
async resolveGitState(cwd) {
|
|
1713
|
+
if (!cwd)
|
|
1714
|
+
return [];
|
|
1715
|
+
const items = [];
|
|
1716
|
+
const status = await this.services.git.status({ cwd });
|
|
1717
|
+
if (status) {
|
|
1718
|
+
items.push({
|
|
1719
|
+
id: createId("ctx"),
|
|
1720
|
+
type: "git_status",
|
|
1721
|
+
title: "Git status",
|
|
1722
|
+
content: status,
|
|
1723
|
+
priority: 60,
|
|
1724
|
+
source: "git"
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
const diff = await this.services.git.diff({ cwd });
|
|
1728
|
+
if (diff) {
|
|
1729
|
+
items.push({
|
|
1730
|
+
id: createId("ctx"),
|
|
1731
|
+
type: "git_diff",
|
|
1732
|
+
title: "Git diff",
|
|
1733
|
+
content: trimText(diff, 4000),
|
|
1734
|
+
priority: 50,
|
|
1735
|
+
source: "git"
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
return items;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
function inputToText(input) {
|
|
1742
|
+
return contentToText(normalizeContent(input));
|
|
1743
|
+
}
|
|
1744
|
+
function extractFileCandidates(text) {
|
|
1745
|
+
const matches = text.match(/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\.[A-Za-z0-9_-]+/g) ?? [];
|
|
1746
|
+
return [...new Set(matches)];
|
|
1747
|
+
}
|
|
1748
|
+
function trimText(text, maxChars) {
|
|
1749
|
+
return text.length <= maxChars ? text : `${text.slice(0, maxChars)}
|
|
1750
|
+
...`;
|
|
1751
|
+
}
|
|
1752
|
+
async function safeReadText(services, filePath, cwd) {
|
|
1753
|
+
try {
|
|
1754
|
+
const text = await services.fileSystem.readText(filePath, { cwd });
|
|
1755
|
+
return trimText(text, 2000);
|
|
1756
|
+
} catch {
|
|
1757
|
+
return null;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
function budgetContext(items, maxItems, maxChars) {
|
|
1761
|
+
const sorted = [...items].sort((left, right) => right.priority - left.priority);
|
|
1762
|
+
const selected = [];
|
|
1763
|
+
let totalChars = 0;
|
|
1764
|
+
for (const item of sorted) {
|
|
1765
|
+
if (selected.length >= maxItems)
|
|
1766
|
+
break;
|
|
1767
|
+
if (totalChars + item.content.length > maxChars && selected.length > 0)
|
|
1768
|
+
continue;
|
|
1769
|
+
selected.push(item);
|
|
1770
|
+
totalChars += item.content.length;
|
|
1771
|
+
}
|
|
1772
|
+
return selected;
|
|
1773
|
+
}
|
|
1774
|
+
function toRelative(cwd, filePath) {
|
|
1775
|
+
if (!path.isAbsolute(filePath))
|
|
1776
|
+
return filePath;
|
|
1777
|
+
return path.relative(cwd, filePath) || path.basename(filePath);
|
|
1778
|
+
}
|
|
1779
|
+
// src/plugin-host/helpers.ts
|
|
1780
|
+
function isHookControlResult(value) {
|
|
1781
|
+
if (!value || typeof value !== "object")
|
|
1782
|
+
return false;
|
|
1783
|
+
const candidate = value;
|
|
1784
|
+
return (candidate.type === "continue" || candidate.type === "replace" || candidate.type === "stop") && "payload" in candidate;
|
|
1785
|
+
}
|
|
1786
|
+
function isToolBeforeExecuteControlResult(value) {
|
|
1787
|
+
if (!value || typeof value !== "object")
|
|
1788
|
+
return false;
|
|
1789
|
+
const candidate = value;
|
|
1790
|
+
return candidate.type === "allow" || candidate.type === "deny" || candidate.type === "ask" || candidate.type === "replaceToolCall" || candidate.type === "provideResult";
|
|
1791
|
+
}
|
|
1792
|
+
function isNotificationControlResult(value) {
|
|
1793
|
+
if (!value || typeof value !== "object")
|
|
1794
|
+
return false;
|
|
1795
|
+
const candidate = value;
|
|
1796
|
+
return candidate.type === "continue" || candidate.type === "replace" || candidate.type === "suppress";
|
|
1797
|
+
}
|
|
1798
|
+
function isRunStopControlResult(value) {
|
|
1799
|
+
if (!value || typeof value !== "object")
|
|
1800
|
+
return false;
|
|
1801
|
+
const candidate = value;
|
|
1802
|
+
return candidate.type === "continue" || candidate.type === "finalize";
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
// src/plugin-host/hook-pipeline.ts
|
|
1806
|
+
class HookPipeline {
|
|
1807
|
+
registrations = [];
|
|
1808
|
+
nextOrder = 0;
|
|
1809
|
+
register(pluginId, pluginPriority, hooks, services) {
|
|
1810
|
+
if (!hooks)
|
|
1811
|
+
return;
|
|
1812
|
+
for (const entry of hooks) {
|
|
1813
|
+
if (!entry.middleware?.length && !entry.observers?.length)
|
|
1814
|
+
continue;
|
|
1815
|
+
if (entry.descriptor.name === "model.event" && entry.middleware && entry.middleware.length > 0)
|
|
1816
|
+
throw new Error(`Plugin ${pluginId} cannot register middleware for model.event`);
|
|
1817
|
+
if (entry.descriptor.mode === "async" && entry.middleware && entry.middleware.length > 0)
|
|
1818
|
+
throw new Error(`Plugin ${pluginId} cannot register async middleware for ${entry.descriptor.name}`);
|
|
1819
|
+
this.registrations.push({
|
|
1820
|
+
pluginId,
|
|
1821
|
+
order: this.nextOrder++,
|
|
1822
|
+
priority: entry.descriptor.priority ?? pluginPriority,
|
|
1823
|
+
descriptor: entry.descriptor,
|
|
1824
|
+
services,
|
|
1825
|
+
middleware: [...entry.middleware ?? []],
|
|
1826
|
+
observers: [...entry.observers ?? []]
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
this.registrations.sort((left, right) => {
|
|
1830
|
+
if (left.priority !== right.priority)
|
|
1831
|
+
return right.priority - left.priority;
|
|
1832
|
+
return left.order - right.order;
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
async runMiddleware(name, payload, context) {
|
|
1836
|
+
let current = payload;
|
|
1837
|
+
for (const registration of this.matchingRegistrations(name, current)) {
|
|
1838
|
+
for (const middleware of registration.middleware) {
|
|
1839
|
+
const result = await this.runMiddlewareHandler(registration, middleware, current, context);
|
|
1840
|
+
if (result === undefined)
|
|
1841
|
+
continue;
|
|
1842
|
+
if (isHookControlResult(result)) {
|
|
1843
|
+
current = result.payload;
|
|
1844
|
+
if (result.type === "stop")
|
|
1845
|
+
return current;
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1848
|
+
if (isNotificationControlResult(result) || isRunStopControlResult(result) || isToolBeforeExecuteControlResult(result))
|
|
1849
|
+
throw new Error(`Hook ${registration.descriptor.name} returned an unsupported control result`);
|
|
1850
|
+
current = result;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
return current;
|
|
1854
|
+
}
|
|
1855
|
+
async runToolBeforeExecute(payload, context) {
|
|
1856
|
+
let current = payload;
|
|
1857
|
+
for (const registration of this.matchingRegistrations("tool.beforeExecute", current)) {
|
|
1858
|
+
for (const middleware of registration.middleware) {
|
|
1859
|
+
const result = await this.runMiddlewareHandler(registration, middleware, current, context);
|
|
1860
|
+
if (result === undefined)
|
|
1861
|
+
continue;
|
|
1862
|
+
if (isHookControlResult(result)) {
|
|
1863
|
+
current = result.payload;
|
|
1864
|
+
if (result.type === "stop")
|
|
1865
|
+
return { payload: current };
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
if (isToolBeforeExecuteControlResult(result)) {
|
|
1869
|
+
if (result.type === "replaceToolCall") {
|
|
1870
|
+
current = { ...current, toolCall: result.toolCall };
|
|
1871
|
+
continue;
|
|
1872
|
+
}
|
|
1873
|
+
if (result.type === "ask" && result.toolCall)
|
|
1874
|
+
current = { ...current, toolCall: result.toolCall };
|
|
1875
|
+
return { payload: current, control: result };
|
|
1876
|
+
}
|
|
1877
|
+
current = result;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
return { payload: current };
|
|
1881
|
+
}
|
|
1882
|
+
async runNotifyMessage(payload, context) {
|
|
1883
|
+
let current = payload;
|
|
1884
|
+
for (const registration of this.matchingRegistrations("notify.message", current)) {
|
|
1885
|
+
for (const middleware of registration.middleware) {
|
|
1886
|
+
const result = await this.runMiddlewareHandler(registration, middleware, current, context);
|
|
1887
|
+
if (result === undefined)
|
|
1888
|
+
continue;
|
|
1889
|
+
if (isHookControlResult(result)) {
|
|
1890
|
+
current = result.payload;
|
|
1891
|
+
if (result.type === "stop")
|
|
1892
|
+
return { payload: current };
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
if (isNotificationControlResult(result)) {
|
|
1896
|
+
if (result.type === "replace") {
|
|
1897
|
+
current = { ...current, notification: result.notification };
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
if (result.type === "suppress")
|
|
1901
|
+
return { payload: current, suppressed: true };
|
|
1902
|
+
continue;
|
|
1903
|
+
}
|
|
1904
|
+
current = result;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
return { payload: current };
|
|
1908
|
+
}
|
|
1909
|
+
async runRunStop(payload, context) {
|
|
1910
|
+
let current = payload;
|
|
1911
|
+
for (const registration of this.matchingRegistrations("run.stop", current)) {
|
|
1912
|
+
for (const middleware of registration.middleware) {
|
|
1913
|
+
const result = await this.runMiddlewareHandler(registration, middleware, current, context);
|
|
1914
|
+
if (result === undefined)
|
|
1915
|
+
continue;
|
|
1916
|
+
if (isHookControlResult(result)) {
|
|
1917
|
+
current = result.payload;
|
|
1918
|
+
if (result.type === "stop")
|
|
1919
|
+
return { payload: current };
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
if (isRunStopControlResult(result))
|
|
1923
|
+
return { payload: current, control: result };
|
|
1924
|
+
current = result;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
return { payload: current };
|
|
1928
|
+
}
|
|
1929
|
+
async runObservers(name, payload, context) {
|
|
1930
|
+
for (const registration of this.matchingRegistrations(name, payload)) {
|
|
1931
|
+
for (const observer of registration.observers) {
|
|
1932
|
+
const task = this.runObserverHandler(registration, observer, payload, context);
|
|
1933
|
+
if (registration.descriptor.mode === "async") {
|
|
1934
|
+
task.catch(() => {});
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
await task.catch(() => {});
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
matchingRegistrations(name, payload) {
|
|
1942
|
+
return this.registrations.filter((registration) => {
|
|
1943
|
+
return registration.descriptor.name === name && matchesHookDescriptor(registration.descriptor, payload);
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
async runMiddlewareHandler(registration, middleware, payload, context) {
|
|
1947
|
+
const task = Promise.resolve(middleware({
|
|
1948
|
+
payload,
|
|
1949
|
+
context: this.withHandlerMetadata(context, registration)
|
|
1950
|
+
}));
|
|
1951
|
+
return await withTimeout(task, registration.descriptor.timeoutMs, () => {
|
|
1952
|
+
return new Error(`Hook ${registration.descriptor.name} for plugin ${registration.pluginId} timed out after ${registration.descriptor.timeoutMs}ms`);
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
async runObserverHandler(registration, observer, payload, context) {
|
|
1956
|
+
const task = Promise.resolve(observer({
|
|
1957
|
+
payload,
|
|
1958
|
+
context: this.withHandlerMetadata(context, registration)
|
|
1959
|
+
}));
|
|
1960
|
+
await withTimeout(task, registration.descriptor.timeoutMs, () => {
|
|
1961
|
+
return new Error(`Hook ${registration.descriptor.name} for plugin ${registration.pluginId} timed out after ${registration.descriptor.timeoutMs}ms`);
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
withHandlerMetadata(context, registration) {
|
|
1965
|
+
return {
|
|
1966
|
+
...context,
|
|
1967
|
+
services: registration.services ?? context.services,
|
|
1968
|
+
status: context.status ? structuredClone(context.status) : undefined,
|
|
1969
|
+
metadata: {
|
|
1970
|
+
...context.metadata,
|
|
1971
|
+
pluginId: registration.pluginId,
|
|
1972
|
+
hookName: registration.descriptor.name
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
function matchesHookDescriptor(descriptor, payload) {
|
|
1978
|
+
const matcher = descriptor.when;
|
|
1979
|
+
if (!matcher)
|
|
1980
|
+
return true;
|
|
1981
|
+
if (matcher.toolName && !matchesValue(matcher.toolName, readToolName(payload)))
|
|
1982
|
+
return false;
|
|
1983
|
+
if (matcher.notificationType && !matchesValue(matcher.notificationType, readNotificationType(payload)))
|
|
1984
|
+
return false;
|
|
1985
|
+
if (matcher.stopReason && !matchesValue(matcher.stopReason, readStopReason(payload)))
|
|
1986
|
+
return false;
|
|
1987
|
+
if (matcher.source && !matchesValue(matcher.source, readSource(payload)))
|
|
1988
|
+
return false;
|
|
1989
|
+
return true;
|
|
1990
|
+
}
|
|
1991
|
+
function readToolName(payload) {
|
|
1992
|
+
if ("toolCall" in payload)
|
|
1993
|
+
return payload.toolCall.function.name;
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
function readNotificationType(payload) {
|
|
1997
|
+
if ("notification" in payload)
|
|
1998
|
+
return payload.notification.type;
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
function readStopReason(payload) {
|
|
2002
|
+
if ("stopReason" in payload)
|
|
2003
|
+
return payload.stopReason;
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
function readSource(payload) {
|
|
2007
|
+
if ("notification" in payload)
|
|
2008
|
+
return payload.notification.source;
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
function matchesValue(expected, actual) {
|
|
2012
|
+
if (!actual)
|
|
2013
|
+
return false;
|
|
2014
|
+
if (Array.isArray(expected))
|
|
2015
|
+
return expected.includes(actual);
|
|
2016
|
+
return expected === actual;
|
|
2017
|
+
}
|
|
2018
|
+
async function withTimeout(promise, timeoutMs, createError) {
|
|
2019
|
+
if (!timeoutMs || timeoutMs <= 0)
|
|
2020
|
+
return await promise;
|
|
2021
|
+
let timeoutHandle;
|
|
2022
|
+
try {
|
|
2023
|
+
return await Promise.race([
|
|
2024
|
+
promise,
|
|
2025
|
+
new Promise((_, reject) => {
|
|
2026
|
+
timeoutHandle = setTimeout(() => reject(createError()), timeoutMs);
|
|
2027
|
+
})
|
|
2028
|
+
]);
|
|
2029
|
+
} finally {
|
|
2030
|
+
if (timeoutHandle)
|
|
2031
|
+
clearTimeout(timeoutHandle);
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
// src/plugin-host/plugin-host.ts
|
|
2036
|
+
class PluginHost {
|
|
2037
|
+
pipeline = new HookPipeline;
|
|
2038
|
+
contextContributors = [];
|
|
2039
|
+
promptContributors = [];
|
|
2040
|
+
tools = [];
|
|
2041
|
+
sessionControllerFactories = [];
|
|
2042
|
+
services;
|
|
2043
|
+
cwd;
|
|
2044
|
+
agentId;
|
|
2045
|
+
tracker;
|
|
2046
|
+
constructor(options) {
|
|
2047
|
+
this.agentId = options.agentId;
|
|
2048
|
+
this.cwd = options.cwd;
|
|
2049
|
+
this.services = options.services;
|
|
2050
|
+
this.tracker = options.tracker;
|
|
2051
|
+
for (const plugin of options.plugins ?? []) {
|
|
2052
|
+
const priority = plugin.manifest.priority ?? 0;
|
|
2053
|
+
const scopedServices = options.permissionGateway.scopeServices(this.services, plugin.manifest.id, plugin.manifest.permissions);
|
|
2054
|
+
const setup = plugin.setup?.({
|
|
2055
|
+
cwd: this.cwd,
|
|
2056
|
+
manifest: plugin.manifest,
|
|
2057
|
+
services: scopedServices
|
|
2058
|
+
});
|
|
2059
|
+
this.pipeline.register(plugin.manifest.id, priority, setup?.hooks, scopedServices);
|
|
2060
|
+
for (const tool of setup?.tools ?? []) {
|
|
2061
|
+
this.tools.push({
|
|
2062
|
+
pluginId: plugin.manifest.id,
|
|
2063
|
+
tool: wrapTool(plugin.manifest.id, tool, scopedServices)
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
for (const contributor of setup?.contextContributors ?? [])
|
|
2067
|
+
this.contextContributors.push({ pluginId: plugin.manifest.id, contributor });
|
|
2068
|
+
for (const contributor of setup?.promptContributors ?? [])
|
|
2069
|
+
this.promptContributors.push({ pluginId: plugin.manifest.id, contributor });
|
|
2070
|
+
if (setup?.createSessionController) {
|
|
2071
|
+
this.sessionControllerFactories.push({
|
|
2072
|
+
pluginId: plugin.manifest.id,
|
|
2073
|
+
factory: setup.createSessionController,
|
|
2074
|
+
services: scopedServices
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
pluginTools() {
|
|
2080
|
+
return this.tools.map((entry) => entry.tool);
|
|
2081
|
+
}
|
|
2082
|
+
recentFiles(limit = 5) {
|
|
2083
|
+
return this.tracker?.recentFiles(limit) ?? [];
|
|
2084
|
+
}
|
|
2085
|
+
createSessionControllers(input) {
|
|
2086
|
+
const controllers = new Map;
|
|
2087
|
+
for (const entry of this.sessionControllerFactories) {
|
|
2088
|
+
controllers.set(entry.pluginId, entry.factory({
|
|
2089
|
+
sessionId: input.sessionId,
|
|
2090
|
+
metadata: input.metadata ? structuredClone(input.metadata) : undefined,
|
|
2091
|
+
getStatus: input.getStatus,
|
|
2092
|
+
pluginState: entry.services.pluginState,
|
|
2093
|
+
save: input.save,
|
|
2094
|
+
isRunning: input.isRunning
|
|
2095
|
+
}));
|
|
2096
|
+
}
|
|
2097
|
+
return controllers;
|
|
2098
|
+
}
|
|
2099
|
+
async runMiddleware(name, payload, context = {}) {
|
|
2100
|
+
return this.pipeline.runMiddleware(name, payload, this.createRuntimeContext(context));
|
|
2101
|
+
}
|
|
2102
|
+
async runToolBeforeExecute(payload, context = {}) {
|
|
2103
|
+
return this.pipeline.runToolBeforeExecute(payload, this.createRuntimeContext(context));
|
|
2104
|
+
}
|
|
2105
|
+
async runNotifyMessage(payload, context = {}) {
|
|
2106
|
+
return this.pipeline.runNotifyMessage(payload, this.createRuntimeContext(context));
|
|
2107
|
+
}
|
|
2108
|
+
async runRunStop(payload, context = {}) {
|
|
2109
|
+
return this.pipeline.runRunStop(payload, this.createRuntimeContext(context));
|
|
2110
|
+
}
|
|
2111
|
+
async runObservers(name, payload, context = {}) {
|
|
2112
|
+
await this.pipeline.runObservers(name, payload, this.createRuntimeContext(context));
|
|
2113
|
+
}
|
|
2114
|
+
async collectContext(input) {
|
|
2115
|
+
const items = [];
|
|
2116
|
+
for (const entry of this.contextContributors) {
|
|
2117
|
+
const next = await entry.contributor(input);
|
|
2118
|
+
if (next.length > 0)
|
|
2119
|
+
items.push(...next);
|
|
2120
|
+
}
|
|
2121
|
+
return items;
|
|
2122
|
+
}
|
|
2123
|
+
async collectPromptSegments(input) {
|
|
2124
|
+
const segments = [];
|
|
2125
|
+
for (const entry of this.promptContributors) {
|
|
2126
|
+
const next = await entry.contributor(input);
|
|
2127
|
+
if (!next)
|
|
2128
|
+
continue;
|
|
2129
|
+
if (Array.isArray(next))
|
|
2130
|
+
segments.push(...next.filter(Boolean));
|
|
2131
|
+
else
|
|
2132
|
+
segments.push(next);
|
|
2133
|
+
}
|
|
2134
|
+
return segments;
|
|
2135
|
+
}
|
|
2136
|
+
createRuntimeContext(context) {
|
|
2137
|
+
return {
|
|
2138
|
+
agentId: this.agentId,
|
|
2139
|
+
cwd: context.cwd ?? this.cwd,
|
|
2140
|
+
sessionId: context.sessionId,
|
|
2141
|
+
requestId: context.requestId,
|
|
2142
|
+
iteration: context.iteration,
|
|
2143
|
+
status: context.status ? structuredClone(context.status) : undefined,
|
|
2144
|
+
metadata: context.metadata,
|
|
2145
|
+
services: context.services ?? this.services
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
function wrapTool(pluginId, tool, services) {
|
|
2150
|
+
return {
|
|
2151
|
+
definition: tool.definition,
|
|
2152
|
+
async execute(args, context) {
|
|
2153
|
+
return tool.execute(args, {
|
|
2154
|
+
...context,
|
|
2155
|
+
metadata: {
|
|
2156
|
+
...context.metadata,
|
|
2157
|
+
pluginId
|
|
2158
|
+
},
|
|
2159
|
+
emitEvent: (event) => context.emitEvent?.({
|
|
2160
|
+
...event,
|
|
2161
|
+
pluginId: event.pluginId ?? pluginId
|
|
2162
|
+
}),
|
|
2163
|
+
services
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// src/services/activity.ts
|
|
2170
|
+
class ActivityTracker {
|
|
2171
|
+
records = [];
|
|
2172
|
+
record(record) {
|
|
2173
|
+
this.records.push({ ...record, timestamp: record.timestamp });
|
|
2174
|
+
if (this.records.length > 200)
|
|
2175
|
+
this.records.splice(0, this.records.length - 200);
|
|
2176
|
+
}
|
|
2177
|
+
recentFiles(limit = 5) {
|
|
2178
|
+
const seen = new Set;
|
|
2179
|
+
const files = [];
|
|
2180
|
+
for (let index = this.records.length - 1;index >= 0; index -= 1) {
|
|
2181
|
+
const record = this.records[index];
|
|
2182
|
+
if (!record || record.type !== "file")
|
|
2183
|
+
continue;
|
|
2184
|
+
if (seen.has(record.label))
|
|
2185
|
+
continue;
|
|
2186
|
+
seen.add(record.label);
|
|
2187
|
+
files.push(record.label);
|
|
2188
|
+
if (files.length >= limit)
|
|
2189
|
+
break;
|
|
2190
|
+
}
|
|
2191
|
+
return files;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
// src/services/exec-gateway.ts
|
|
2195
|
+
import { spawn } from "node:child_process";
|
|
2196
|
+
|
|
2197
|
+
class NodeExecGateway {
|
|
2198
|
+
cwd;
|
|
2199
|
+
env;
|
|
2200
|
+
tracker;
|
|
2201
|
+
constructor(options = {}) {
|
|
2202
|
+
this.cwd = options.cwd;
|
|
2203
|
+
this.env = options.env ?? process.env;
|
|
2204
|
+
this.tracker = options.tracker;
|
|
2205
|
+
}
|
|
2206
|
+
async execute(command, options = {}) {
|
|
2207
|
+
const cwd = options.cwd ?? this.cwd;
|
|
2208
|
+
const env = {
|
|
2209
|
+
...this.env,
|
|
2210
|
+
...options.env
|
|
2211
|
+
};
|
|
2212
|
+
const result = await new Promise((resolve, reject) => {
|
|
2213
|
+
const child = spawn(command, {
|
|
2214
|
+
cwd,
|
|
2215
|
+
env,
|
|
2216
|
+
shell: true,
|
|
2217
|
+
signal: options.signal
|
|
2218
|
+
});
|
|
2219
|
+
let stdout = "";
|
|
2220
|
+
let stderr = "";
|
|
2221
|
+
let settled = false;
|
|
2222
|
+
let timedOut = false;
|
|
2223
|
+
let timeout;
|
|
2224
|
+
const finish = (value, isError) => {
|
|
2225
|
+
if (settled)
|
|
2226
|
+
return;
|
|
2227
|
+
settled = true;
|
|
2228
|
+
if (timeout)
|
|
2229
|
+
clearTimeout(timeout);
|
|
2230
|
+
if (isError)
|
|
2231
|
+
reject(value);
|
|
2232
|
+
else
|
|
2233
|
+
resolve(value);
|
|
2234
|
+
};
|
|
2235
|
+
if (options.timeoutMs !== undefined) {
|
|
2236
|
+
timeout = setTimeout(() => {
|
|
2237
|
+
timedOut = true;
|
|
2238
|
+
child.kill("SIGTERM");
|
|
2239
|
+
}, options.timeoutMs);
|
|
2240
|
+
}
|
|
2241
|
+
child.stdout.on("data", (chunk) => {
|
|
2242
|
+
stdout += String(chunk);
|
|
2243
|
+
});
|
|
2244
|
+
child.stderr.on("data", (chunk) => {
|
|
2245
|
+
stderr += String(chunk);
|
|
2246
|
+
});
|
|
2247
|
+
child.on("error", (error) => finish(error, true));
|
|
2248
|
+
child.on("close", (code, signal) => {
|
|
2249
|
+
finish({
|
|
2250
|
+
command,
|
|
2251
|
+
cwd: cwd ?? null,
|
|
2252
|
+
stdout,
|
|
2253
|
+
stderr,
|
|
2254
|
+
exitCode: code,
|
|
2255
|
+
signal: signal ?? null,
|
|
2256
|
+
timedOut
|
|
2257
|
+
}, false);
|
|
2258
|
+
});
|
|
2259
|
+
});
|
|
2260
|
+
this.tracker?.record({
|
|
2261
|
+
type: "command",
|
|
2262
|
+
label: command,
|
|
2263
|
+
timestamp: Date.now()
|
|
2264
|
+
});
|
|
2265
|
+
return result;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
// src/services/file-system-gateway.ts
|
|
2269
|
+
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
2270
|
+
import path2 from "node:path";
|
|
2271
|
+
|
|
2272
|
+
class NodeFileSystemGateway {
|
|
2273
|
+
cwd;
|
|
2274
|
+
tracker;
|
|
2275
|
+
constructor(options = {}) {
|
|
2276
|
+
this.cwd = options.cwd;
|
|
2277
|
+
this.tracker = options.tracker;
|
|
2278
|
+
}
|
|
2279
|
+
async readText(targetPath, options = {}) {
|
|
2280
|
+
const resolved = resolvePath(this.cwd, targetPath, options.cwd);
|
|
2281
|
+
const text = await readFile(resolved, "utf8");
|
|
2282
|
+
this.recordFile(resolved);
|
|
2283
|
+
return text;
|
|
2284
|
+
}
|
|
2285
|
+
async writeText(targetPath, content, options = {}) {
|
|
2286
|
+
const resolved = resolvePath(this.cwd, targetPath, options.cwd);
|
|
2287
|
+
await mkdir(path2.dirname(resolved), { recursive: true });
|
|
2288
|
+
await writeFile(resolved, content, "utf8");
|
|
2289
|
+
this.recordFile(resolved);
|
|
2290
|
+
return {
|
|
2291
|
+
path: resolved,
|
|
2292
|
+
bytes: Buffer.byteLength(content, "utf8")
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
async editText(targetPath, options) {
|
|
2296
|
+
const resolved = resolvePath(this.cwd, targetPath, options.cwd);
|
|
2297
|
+
const source = await readFile(resolved, "utf8");
|
|
2298
|
+
const matches = source.split(options.oldText).length - 1;
|
|
2299
|
+
if (matches === 0)
|
|
2300
|
+
throw new Error("oldText was not found in the target file");
|
|
2301
|
+
if (!options.replaceAll && matches > 1)
|
|
2302
|
+
throw new Error("oldText matched more than once; set replaceAll to true to replace all matches");
|
|
2303
|
+
const updated = options.replaceAll ? source.split(options.oldText).join(options.newText) : source.replace(options.oldText, options.newText);
|
|
2304
|
+
await writeFile(resolved, updated, "utf8");
|
|
2305
|
+
this.recordFile(resolved);
|
|
2306
|
+
return {
|
|
2307
|
+
path: resolved,
|
|
2308
|
+
replacements: options.replaceAll ? matches : 1
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
async exists(targetPath, options = {}) {
|
|
2312
|
+
try {
|
|
2313
|
+
const resolved = resolvePath(this.cwd, targetPath, options.cwd);
|
|
2314
|
+
await stat(resolved);
|
|
2315
|
+
return true;
|
|
2316
|
+
} catch {
|
|
2317
|
+
return false;
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
async glob(pattern, options = {}) {
|
|
2321
|
+
const root = path2.resolve(options.cwd ?? this.cwd ?? process.cwd());
|
|
2322
|
+
const matcher = compileGlob(pattern);
|
|
2323
|
+
const limit = options.limit ?? 100;
|
|
2324
|
+
const matches = [];
|
|
2325
|
+
await walk(root, root, async (absolutePath, relativePath) => {
|
|
2326
|
+
if (matches.length >= limit)
|
|
2327
|
+
return false;
|
|
2328
|
+
if (matcher.test(relativePath))
|
|
2329
|
+
matches.push(absolutePath);
|
|
2330
|
+
return true;
|
|
2331
|
+
});
|
|
2332
|
+
return matches;
|
|
2333
|
+
}
|
|
2334
|
+
async grep(query, options = {}) {
|
|
2335
|
+
const pattern = options.pattern ?? "**/*";
|
|
2336
|
+
const files = await this.glob(pattern, options);
|
|
2337
|
+
const matches = [];
|
|
2338
|
+
const limit = options.limit ?? 50;
|
|
2339
|
+
for (const filePath of files) {
|
|
2340
|
+
if (matches.length >= limit)
|
|
2341
|
+
break;
|
|
2342
|
+
let text;
|
|
2343
|
+
try {
|
|
2344
|
+
text = await readFile(filePath, "utf8");
|
|
2345
|
+
} catch {
|
|
2346
|
+
continue;
|
|
2347
|
+
}
|
|
2348
|
+
const lines = text.split(/\r?\n/g);
|
|
2349
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
2350
|
+
const line = lines[index];
|
|
2351
|
+
if (!line || !line.includes(query))
|
|
2352
|
+
continue;
|
|
2353
|
+
matches.push({
|
|
2354
|
+
path: filePath,
|
|
2355
|
+
lineNumber: index + 1,
|
|
2356
|
+
line
|
|
2357
|
+
});
|
|
2358
|
+
if (matches.length >= limit)
|
|
2359
|
+
break;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return matches;
|
|
2363
|
+
}
|
|
2364
|
+
recordFile(filePath) {
|
|
2365
|
+
this.tracker?.record({
|
|
2366
|
+
type: "file",
|
|
2367
|
+
label: filePath,
|
|
2368
|
+
timestamp: Date.now()
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
function resolvePath(defaultCwd, targetPath, cwdOverride) {
|
|
2373
|
+
if (!targetPath.trim())
|
|
2374
|
+
throw new Error("path is required");
|
|
2375
|
+
if (path2.isAbsolute(targetPath))
|
|
2376
|
+
return path2.normalize(targetPath);
|
|
2377
|
+
return path2.resolve(cwdOverride ?? defaultCwd ?? process.cwd(), targetPath);
|
|
2378
|
+
}
|
|
2379
|
+
function compileGlob(pattern) {
|
|
2380
|
+
const normalized = pattern.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2381
|
+
let expression = "^";
|
|
2382
|
+
for (let index = 0;index < normalized.length; index += 1) {
|
|
2383
|
+
const character = normalized[index];
|
|
2384
|
+
const next = normalized[index + 1];
|
|
2385
|
+
if (character === "*" && next === "*") {
|
|
2386
|
+
expression += ".*";
|
|
2387
|
+
index += 1;
|
|
2388
|
+
continue;
|
|
2389
|
+
}
|
|
2390
|
+
if (character === "*") {
|
|
2391
|
+
expression += "[^/]*";
|
|
2392
|
+
continue;
|
|
2393
|
+
}
|
|
2394
|
+
expression += /[.+^${}()|[\]\\]/.test(character) ? `\\${character}` : character;
|
|
2395
|
+
}
|
|
2396
|
+
expression += "$";
|
|
2397
|
+
return new RegExp(expression);
|
|
2398
|
+
}
|
|
2399
|
+
async function walk(root, current, visit) {
|
|
2400
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
2401
|
+
for (const entry of entries) {
|
|
2402
|
+
if (entry.name === "node_modules" || entry.name === ".git")
|
|
2403
|
+
continue;
|
|
2404
|
+
const absolutePath = path2.join(current, entry.name);
|
|
2405
|
+
const relativePath = path2.relative(root, absolutePath).replace(/\\/g, "/");
|
|
2406
|
+
if (entry.isDirectory()) {
|
|
2407
|
+
const keepGoing = await visit(absolutePath, relativePath);
|
|
2408
|
+
if (keepGoing !== false)
|
|
2409
|
+
await walk(root, absolutePath, visit);
|
|
2410
|
+
continue;
|
|
2411
|
+
}
|
|
2412
|
+
await visit(absolutePath, relativePath);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
// src/services/git-gateway.ts
|
|
2416
|
+
class DefaultGitGateway {
|
|
2417
|
+
exec;
|
|
2418
|
+
cwd;
|
|
2419
|
+
constructor(options) {
|
|
2420
|
+
this.exec = options.exec;
|
|
2421
|
+
this.cwd = options.cwd;
|
|
2422
|
+
}
|
|
2423
|
+
async status(options = {}) {
|
|
2424
|
+
return this.runGitCommand("git status --short", options.cwd);
|
|
2425
|
+
}
|
|
2426
|
+
async diff(options = {}) {
|
|
2427
|
+
return this.runGitCommand('git diff --stat && printf "\\n---\\n" && git diff -- .', options.cwd);
|
|
2428
|
+
}
|
|
2429
|
+
async runGitCommand(command, cwd) {
|
|
2430
|
+
try {
|
|
2431
|
+
const result = await this.exec.execute(command, { cwd: cwd ?? this.cwd, timeoutMs: 1e4 });
|
|
2432
|
+
if (result.exitCode !== 0)
|
|
2433
|
+
return null;
|
|
2434
|
+
const text = [result.stdout, result.stderr].filter(Boolean).join(result.stdout && result.stderr ? `
|
|
2435
|
+
` : "");
|
|
2436
|
+
return text.trim() || null;
|
|
2437
|
+
} catch {
|
|
2438
|
+
return null;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
// src/services/model-gateway.ts
|
|
2443
|
+
class DefaultModelGateway {
|
|
2444
|
+
model;
|
|
2445
|
+
constructor(model) {
|
|
2446
|
+
this.model = model;
|
|
2447
|
+
}
|
|
2448
|
+
stream(request) {
|
|
2449
|
+
return this.model.stream(request);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
// src/services/network-gateway.ts
|
|
2453
|
+
class DefaultNetworkGateway {
|
|
2454
|
+
fetchImpl;
|
|
2455
|
+
constructor(fetchImpl = globalThis.fetch) {
|
|
2456
|
+
this.fetchImpl = fetchImpl;
|
|
2457
|
+
}
|
|
2458
|
+
fetch(input, init) {
|
|
2459
|
+
return this.fetchImpl(input, init);
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
// src/services/permissions.ts
|
|
2463
|
+
var fullPermissions = {
|
|
2464
|
+
fs: "full",
|
|
2465
|
+
git: true,
|
|
2466
|
+
network: true,
|
|
2467
|
+
process: true,
|
|
2468
|
+
model: true,
|
|
2469
|
+
mcp: true
|
|
2470
|
+
};
|
|
2471
|
+
|
|
2472
|
+
class PermissionDeniedError extends Error {
|
|
2473
|
+
code = "permission_denied";
|
|
2474
|
+
constructor(message) {
|
|
2475
|
+
super(message);
|
|
2476
|
+
this.name = "PermissionDeniedError";
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
function normalizePermissions(input) {
|
|
2480
|
+
return {
|
|
2481
|
+
...fullPermissions,
|
|
2482
|
+
...input
|
|
2483
|
+
};
|
|
2484
|
+
}
|
|
2485
|
+
function canReadFs(permissions) {
|
|
2486
|
+
return permissions.fs === true || permissions.fs === "read" || permissions.fs === "full";
|
|
2487
|
+
}
|
|
2488
|
+
function canWriteFs(permissions) {
|
|
2489
|
+
return permissions.fs === true || permissions.fs === "write" || permissions.fs === "full";
|
|
2490
|
+
}
|
|
2491
|
+
// src/services/permission-gateway.ts
|
|
2492
|
+
class PermissionGateway {
|
|
2493
|
+
permissions;
|
|
2494
|
+
compactionOwnerPluginId;
|
|
2495
|
+
constructor(options = {}) {
|
|
2496
|
+
this.permissions = normalizePermissions(options.permissions);
|
|
2497
|
+
this.compactionOwnerPluginId = options.compactionOwnerPluginId;
|
|
2498
|
+
}
|
|
2499
|
+
get agentPermissions() {
|
|
2500
|
+
return { ...this.permissions };
|
|
2501
|
+
}
|
|
2502
|
+
scopeServices(services, pluginId, requested) {
|
|
2503
|
+
const effective = intersectPermissions(this.permissions, requested);
|
|
2504
|
+
const pluginState = services.pluginState;
|
|
2505
|
+
return {
|
|
2506
|
+
fileSystem: {
|
|
2507
|
+
readText: (path3, options) => {
|
|
2508
|
+
if (!canReadFs(effective))
|
|
2509
|
+
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to read files`);
|
|
2510
|
+
return services.fileSystem.readText(path3, options);
|
|
2511
|
+
},
|
|
2512
|
+
writeText: (path3, content, options) => {
|
|
2513
|
+
if (!canWriteFs(effective))
|
|
2514
|
+
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to write files`);
|
|
2515
|
+
return services.fileSystem.writeText(path3, content, options);
|
|
2516
|
+
},
|
|
2517
|
+
editText: (path3, options) => {
|
|
2518
|
+
if (!canWriteFs(effective))
|
|
2519
|
+
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to edit files`);
|
|
2520
|
+
return services.fileSystem.editText(path3, options);
|
|
2521
|
+
},
|
|
2522
|
+
exists: (path3, options) => {
|
|
2523
|
+
if (!canReadFs(effective))
|
|
2524
|
+
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to inspect files`);
|
|
2525
|
+
return services.fileSystem.exists(path3, options);
|
|
2526
|
+
},
|
|
2527
|
+
glob: (pattern, options) => {
|
|
2528
|
+
if (!canReadFs(effective))
|
|
2529
|
+
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to glob files`);
|
|
2530
|
+
return services.fileSystem.glob(pattern, options);
|
|
2531
|
+
},
|
|
2532
|
+
grep: (query, options) => {
|
|
2533
|
+
if (!canReadFs(effective))
|
|
2534
|
+
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to grep files`);
|
|
2535
|
+
return services.fileSystem.grep(query, options);
|
|
2536
|
+
}
|
|
2537
|
+
},
|
|
2538
|
+
exec: {
|
|
2539
|
+
execute: (command, options) => {
|
|
2540
|
+
if (effective.process !== true)
|
|
2541
|
+
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to execute commands`);
|
|
2542
|
+
return services.exec.execute(command, options);
|
|
2543
|
+
}
|
|
2544
|
+
},
|
|
2545
|
+
git: {
|
|
2546
|
+
status: (options) => {
|
|
2547
|
+
if (effective.git !== true)
|
|
2548
|
+
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to inspect git state`);
|
|
2549
|
+
return services.git.status(options);
|
|
2550
|
+
},
|
|
2551
|
+
diff: (options) => {
|
|
2552
|
+
if (effective.git !== true)
|
|
2553
|
+
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to inspect git diff`);
|
|
2554
|
+
return services.git.diff(options);
|
|
2555
|
+
}
|
|
2556
|
+
},
|
|
2557
|
+
network: {
|
|
2558
|
+
fetch: (input, init) => {
|
|
2559
|
+
if (effective.network !== true)
|
|
2560
|
+
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to access the network`);
|
|
2561
|
+
return services.network.fetch(input, init);
|
|
2562
|
+
}
|
|
2563
|
+
},
|
|
2564
|
+
model: {
|
|
2565
|
+
stream: (request) => {
|
|
2566
|
+
if (effective.model !== true)
|
|
2567
|
+
throw new PermissionDeniedError(`Plugin ${pluginId} is not allowed to call the model gateway`);
|
|
2568
|
+
return services.model.stream(request);
|
|
2569
|
+
}
|
|
2570
|
+
},
|
|
2571
|
+
compaction: {
|
|
2572
|
+
getState: (sessionId) => services.compaction.getState(sessionId),
|
|
2573
|
+
apply: (update) => {
|
|
2574
|
+
this.ensureCompactionOwner(pluginId);
|
|
2575
|
+
return services.compaction.apply(update);
|
|
2576
|
+
},
|
|
2577
|
+
clear: (sessionId) => {
|
|
2578
|
+
this.ensureCompactionOwner(pluginId);
|
|
2579
|
+
return services.compaction.clear(sessionId);
|
|
2580
|
+
}
|
|
2581
|
+
},
|
|
2582
|
+
pluginState: {
|
|
2583
|
+
get: (sessionId) => pluginState.getForPlugin(sessionId, pluginId),
|
|
2584
|
+
replace: (sessionId, entry) => pluginState.replaceForPlugin(sessionId, pluginId, entry),
|
|
2585
|
+
clear: (sessionId) => pluginState.clearForPlugin(sessionId, pluginId)
|
|
2586
|
+
}
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
ensureCompactionOwner(pluginId) {
|
|
2590
|
+
if (this.compactionOwnerPluginId === pluginId)
|
|
2591
|
+
return;
|
|
2592
|
+
const message = this.compactionOwnerPluginId ? `Plugin ${pluginId} is not the configured compaction owner (${this.compactionOwnerPluginId})` : `Plugin ${pluginId} cannot modify canonical compaction without compaction.ownerPluginId`;
|
|
2593
|
+
const error = new Error(message);
|
|
2594
|
+
error.code = "compaction_owner_required";
|
|
2595
|
+
throw error;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
function intersectPermissions(agent, requested) {
|
|
2599
|
+
const normalized = requested ?? {};
|
|
2600
|
+
return {
|
|
2601
|
+
fs: intersectFs(agent.fs, normalized.fs ?? false),
|
|
2602
|
+
git: agent.git === true && normalized.git === true,
|
|
2603
|
+
network: agent.network === true && normalized.network === true,
|
|
2604
|
+
process: agent.process === true && normalized.process === true,
|
|
2605
|
+
model: agent.model === true && normalized.model === true,
|
|
2606
|
+
mcp: agent.mcp === true && normalized.mcp === true
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
function intersectFs(left, right) {
|
|
2610
|
+
const rank = new Map([
|
|
2611
|
+
[false, 0],
|
|
2612
|
+
["read", 1],
|
|
2613
|
+
["write", 1],
|
|
2614
|
+
[true, 2],
|
|
2615
|
+
["full", 2]
|
|
2616
|
+
]);
|
|
2617
|
+
const leftValue = left ?? false;
|
|
2618
|
+
const rightValue = right ?? false;
|
|
2619
|
+
if (leftValue === "full" || leftValue === true)
|
|
2620
|
+
return rightValue;
|
|
2621
|
+
if (rightValue === "full" || rightValue === true)
|
|
2622
|
+
return leftValue;
|
|
2623
|
+
if (leftValue === rightValue)
|
|
2624
|
+
return leftValue;
|
|
2625
|
+
if (rank.get(leftValue) === 0 || rank.get(rightValue) === 0)
|
|
2626
|
+
return false;
|
|
2627
|
+
if (leftValue === "read" && rightValue === "write" || leftValue === "write" && rightValue === "read")
|
|
2628
|
+
return false;
|
|
2629
|
+
return leftValue;
|
|
2630
|
+
}
|
|
2631
|
+
// src/contracts/tool-normalize.ts
|
|
2632
|
+
function normalizeToolSchema(schema) {
|
|
2633
|
+
if (!isRecord(schema) || schema.type !== "object")
|
|
2634
|
+
throw new TypeError('Tool input schema must have type "object"');
|
|
2635
|
+
const properties = schema.properties;
|
|
2636
|
+
if (properties !== undefined && !isRecord(properties))
|
|
2637
|
+
throw new TypeError("Tool input schema properties must be an object");
|
|
2638
|
+
const required = schema.required;
|
|
2639
|
+
if (required !== undefined && !isStringArray(required))
|
|
2640
|
+
throw new TypeError("Tool input schema required must be a string array");
|
|
2641
|
+
const normalized = {
|
|
2642
|
+
...schema,
|
|
2643
|
+
type: "object",
|
|
2644
|
+
properties: properties ? cloneSchemaRecord(properties) : {}
|
|
2645
|
+
};
|
|
2646
|
+
return normalized;
|
|
2647
|
+
}
|
|
2648
|
+
function cloneSchemaRecord(record) {
|
|
2649
|
+
return Object.fromEntries(Object.entries(record).map(([key, value]) => {
|
|
2650
|
+
if (!isRecord(value))
|
|
2651
|
+
throw new TypeError(`Tool schema property ${key} must be an object`);
|
|
2652
|
+
return [key, { ...value }];
|
|
2653
|
+
}));
|
|
2654
|
+
}
|
|
2655
|
+
function normalizeToolResult(result) {
|
|
2656
|
+
const normalized = {
|
|
2657
|
+
content: normalizeContent(result.content),
|
|
2658
|
+
isError: result.isError === true
|
|
2659
|
+
};
|
|
2660
|
+
if (result.structuredContent !== undefined) {
|
|
2661
|
+
assertJsonSafeObject(result.structuredContent, "Tool structuredContent");
|
|
2662
|
+
normalized.structuredContent = { ...result.structuredContent };
|
|
2663
|
+
}
|
|
2664
|
+
return normalized;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
// src/tools/base-tool.ts
|
|
2668
|
+
class BaseTool {
|
|
2669
|
+
definition;
|
|
2670
|
+
constructor(definition) {
|
|
2671
|
+
this.definition = {
|
|
2672
|
+
...definition,
|
|
2673
|
+
inputSchema: normalizeToolSchema(definition.inputSchema)
|
|
2674
|
+
};
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
// src/tools/result.ts
|
|
2678
|
+
function textToolResult(text) {
|
|
2679
|
+
return {
|
|
2680
|
+
content: normalizeContent(text)
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
// src/tools/tool-registry.ts
|
|
2684
|
+
class ToolRegistry {
|
|
2685
|
+
tools = new Map;
|
|
2686
|
+
register(tool) {
|
|
2687
|
+
const name = tool.definition.name.trim();
|
|
2688
|
+
if (!name)
|
|
2689
|
+
throw new Error("Tool name is required");
|
|
2690
|
+
if (this.tools.has(name))
|
|
2691
|
+
throw new Error(`Tool already registered: ${name}`);
|
|
2692
|
+
const normalizedDefinition = {
|
|
2693
|
+
...tool.definition,
|
|
2694
|
+
name,
|
|
2695
|
+
description: tool.definition.description.trim(),
|
|
2696
|
+
inputSchema: normalizeToolSchema(tool.definition.inputSchema)
|
|
2697
|
+
};
|
|
2698
|
+
const normalizedTool = {
|
|
2699
|
+
definition: normalizedDefinition,
|
|
2700
|
+
execute(args, context) {
|
|
2701
|
+
return tool.execute(args, context);
|
|
2702
|
+
}
|
|
2703
|
+
};
|
|
2704
|
+
this.tools.set(name, normalizedTool);
|
|
2705
|
+
return this;
|
|
2706
|
+
}
|
|
2707
|
+
unregister(name) {
|
|
2708
|
+
return this.tools.delete(name);
|
|
2709
|
+
}
|
|
2710
|
+
get(name) {
|
|
2711
|
+
return this.tools.get(name);
|
|
2712
|
+
}
|
|
2713
|
+
list() {
|
|
2714
|
+
return [...this.tools.values()];
|
|
2715
|
+
}
|
|
2716
|
+
definitions() {
|
|
2717
|
+
return this.list().map((tool) => ({
|
|
2718
|
+
...tool.definition,
|
|
2719
|
+
inputSchema: normalizeToolSchema(tool.definition.inputSchema)
|
|
2720
|
+
}));
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
// src/tools/builtins/utils.ts
|
|
2724
|
+
function readStringArg(args, key, options = {}) {
|
|
2725
|
+
const value = args[key];
|
|
2726
|
+
if (typeof value !== "string")
|
|
2727
|
+
throw new Error(`${key} must be a string`);
|
|
2728
|
+
if (!options.allowEmpty && value.length === 0)
|
|
2729
|
+
throw new Error(`${key} must be a non-empty string`);
|
|
2730
|
+
return value;
|
|
2731
|
+
}
|
|
2732
|
+
function readOptionalBooleanArg(args, key) {
|
|
2733
|
+
const value = args[key];
|
|
2734
|
+
if (value === undefined)
|
|
2735
|
+
return;
|
|
2736
|
+
if (typeof value !== "boolean")
|
|
2737
|
+
throw new Error(`${key} must be a boolean`);
|
|
2738
|
+
return value;
|
|
2739
|
+
}
|
|
2740
|
+
function readOptionalNumberArg(args, key) {
|
|
2741
|
+
const value = args[key];
|
|
2742
|
+
if (value === undefined)
|
|
2743
|
+
return;
|
|
2744
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
2745
|
+
throw new Error(`${key} must be a finite number`);
|
|
2746
|
+
return value;
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
// src/tools/builtins/edit-tool.ts
|
|
2750
|
+
class EditTool extends BaseTool {
|
|
2751
|
+
constructor() {
|
|
2752
|
+
super({
|
|
2753
|
+
name: "edit",
|
|
2754
|
+
description: "Replace text in a UTF-8 file.",
|
|
2755
|
+
inputSchema: {
|
|
2756
|
+
type: "object",
|
|
2757
|
+
properties: {
|
|
2758
|
+
path: { type: "string", description: "File path to edit." },
|
|
2759
|
+
oldText: { type: "string", description: "Text to replace." },
|
|
2760
|
+
newText: { type: "string", description: "Replacement text." },
|
|
2761
|
+
replaceAll: { type: "boolean", description: "Replace all matches when true." }
|
|
2762
|
+
},
|
|
2763
|
+
required: ["path", "oldText", "newText"]
|
|
2764
|
+
}
|
|
2765
|
+
});
|
|
2766
|
+
}
|
|
2767
|
+
async execute(args, context) {
|
|
2768
|
+
const fileSystem = context.services?.fileSystem;
|
|
2769
|
+
if (!fileSystem)
|
|
2770
|
+
throw new Error("FileSystemGateway is not available in the tool execution context");
|
|
2771
|
+
const targetPath = readStringArg(args, "path");
|
|
2772
|
+
const oldText = readStringArg(args, "oldText");
|
|
2773
|
+
const newText = readStringArg(args, "newText", { allowEmpty: true });
|
|
2774
|
+
const replaceAll = readOptionalBooleanArg(args, "replaceAll") ?? false;
|
|
2775
|
+
const result = await fileSystem.editText(targetPath, {
|
|
2776
|
+
oldText,
|
|
2777
|
+
newText,
|
|
2778
|
+
replaceAll,
|
|
2779
|
+
cwd: context.cwd
|
|
2780
|
+
});
|
|
2781
|
+
return normalizeToolResult({
|
|
2782
|
+
content: [{ type: "text", text: `Edited ${result.path}` }],
|
|
2783
|
+
structuredContent: result
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
// src/tools/builtins/exec-tool.ts
|
|
2788
|
+
class ExecTool extends BaseTool {
|
|
2789
|
+
constructor() {
|
|
2790
|
+
super({
|
|
2791
|
+
name: "exec",
|
|
2792
|
+
description: "Execute a shell command.",
|
|
2793
|
+
inputSchema: {
|
|
2794
|
+
type: "object",
|
|
2795
|
+
properties: {
|
|
2796
|
+
command: { type: "string", description: "Shell command to execute." },
|
|
2797
|
+
cwd: { type: "string", description: "Optional working directory override." },
|
|
2798
|
+
timeoutMs: { type: "number", description: "Optional timeout in milliseconds." }
|
|
2799
|
+
},
|
|
2800
|
+
required: ["command"]
|
|
2801
|
+
}
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
async execute(args, context) {
|
|
2805
|
+
const exec = context.services?.exec;
|
|
2806
|
+
if (!exec)
|
|
2807
|
+
throw new Error("ExecGateway is not available in the tool execution context");
|
|
2808
|
+
const command = readStringArg(args, "command");
|
|
2809
|
+
const cwd = typeof args.cwd === "string" && args.cwd.length > 0 ? args.cwd : context.cwd;
|
|
2810
|
+
const timeoutMs = readOptionalNumberArg(args, "timeoutMs");
|
|
2811
|
+
const result = await exec.execute(command, {
|
|
2812
|
+
cwd,
|
|
2813
|
+
timeoutMs,
|
|
2814
|
+
signal: context.signal
|
|
2815
|
+
});
|
|
2816
|
+
const text = [result.stdout, result.stderr].filter(Boolean).join(result.stdout && result.stderr ? `
|
|
2817
|
+
` : "");
|
|
2818
|
+
return normalizeToolResult({
|
|
2819
|
+
content: [{ type: "text", text: text || `Command exited with code ${result.exitCode ?? -1}` }],
|
|
2820
|
+
structuredContent: { ...result },
|
|
2821
|
+
isError: result.exitCode !== 0
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
// src/tools/builtins/read-tool.ts
|
|
2826
|
+
class ReadTool extends BaseTool {
|
|
2827
|
+
constructor() {
|
|
2828
|
+
super({
|
|
2829
|
+
name: "read",
|
|
2830
|
+
description: "Read a UTF-8 text file.",
|
|
2831
|
+
inputSchema: {
|
|
2832
|
+
type: "object",
|
|
2833
|
+
properties: {
|
|
2834
|
+
path: { type: "string", description: "File path to read." }
|
|
2835
|
+
},
|
|
2836
|
+
required: ["path"]
|
|
2837
|
+
}
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
async execute(args, context) {
|
|
2841
|
+
const fileSystem = context.services?.fileSystem;
|
|
2842
|
+
if (!fileSystem)
|
|
2843
|
+
throw new Error("FileSystemGateway is not available in the tool execution context");
|
|
2844
|
+
const targetPath = readStringArg(args, "path");
|
|
2845
|
+
const text = await fileSystem.readText(targetPath, { cwd: context.cwd });
|
|
2846
|
+
return normalizeToolResult({
|
|
2847
|
+
content: [{ type: "text", text }],
|
|
2848
|
+
structuredContent: {
|
|
2849
|
+
path: targetPath,
|
|
2850
|
+
size: Buffer.byteLength(text, "utf8")
|
|
2851
|
+
}
|
|
2852
|
+
});
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
// src/tools/builtins/write-tool.ts
|
|
2856
|
+
class WriteTool extends BaseTool {
|
|
2857
|
+
constructor() {
|
|
2858
|
+
super({
|
|
2859
|
+
name: "write",
|
|
2860
|
+
description: "Write a UTF-8 text file.",
|
|
2861
|
+
inputSchema: {
|
|
2862
|
+
type: "object",
|
|
2863
|
+
properties: {
|
|
2864
|
+
path: { type: "string", description: "File path to write." },
|
|
2865
|
+
content: { type: "string", description: "Text content to write." }
|
|
2866
|
+
},
|
|
2867
|
+
required: ["path", "content"]
|
|
2868
|
+
}
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
async execute(args, context) {
|
|
2872
|
+
const fileSystem = context.services?.fileSystem;
|
|
2873
|
+
if (!fileSystem)
|
|
2874
|
+
throw new Error("FileSystemGateway is not available in the tool execution context");
|
|
2875
|
+
const targetPath = readStringArg(args, "path");
|
|
2876
|
+
const content = readStringArg(args, "content", { allowEmpty: true });
|
|
2877
|
+
const result = await fileSystem.writeText(targetPath, content, { cwd: context.cwd });
|
|
2878
|
+
return normalizeToolResult({
|
|
2879
|
+
content: [{ type: "text", text: `Wrote ${result.path}` }],
|
|
2880
|
+
structuredContent: result
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
// src/agent-core/approvals.ts
|
|
2885
|
+
class DefaultApprovalManager {
|
|
2886
|
+
handler;
|
|
2887
|
+
notifications;
|
|
2888
|
+
constructor(options = {}) {
|
|
2889
|
+
this.handler = options.handler;
|
|
2890
|
+
this.notifications = options.notifications;
|
|
2891
|
+
}
|
|
2892
|
+
async requestApproval(request, context) {
|
|
2893
|
+
const normalizedRequest = {
|
|
2894
|
+
...request,
|
|
2895
|
+
id: request.id ?? createId("approval")
|
|
2896
|
+
};
|
|
2897
|
+
await this.notifications?.emit({
|
|
2898
|
+
type: "approval.requested",
|
|
2899
|
+
source: "approval",
|
|
2900
|
+
level: "warning",
|
|
2901
|
+
message: normalizedRequest.message,
|
|
2902
|
+
metadata: {
|
|
2903
|
+
approvalId: normalizedRequest.id,
|
|
2904
|
+
toolName: normalizedRequest.toolCall.function.name
|
|
2905
|
+
}
|
|
2906
|
+
}, context);
|
|
2907
|
+
if (!this.handler) {
|
|
2908
|
+
const denied = {
|
|
2909
|
+
type: "denied",
|
|
2910
|
+
reason: "No approval handler configured"
|
|
2911
|
+
};
|
|
2912
|
+
await this.notifications?.emit({
|
|
2913
|
+
type: "approval.denied",
|
|
2914
|
+
source: "approval",
|
|
2915
|
+
level: "warning",
|
|
2916
|
+
message: denied.reason,
|
|
2917
|
+
metadata: {
|
|
2918
|
+
approvalId: normalizedRequest.id,
|
|
2919
|
+
toolName: normalizedRequest.toolCall.function.name
|
|
2920
|
+
}
|
|
2921
|
+
}, context);
|
|
2922
|
+
return denied;
|
|
2923
|
+
}
|
|
2924
|
+
const decision = await this.handler(normalizedRequest);
|
|
2925
|
+
await this.notifications?.emit({
|
|
2926
|
+
type: decision.type === "approved" ? "approval.approved" : "approval.denied",
|
|
2927
|
+
source: "approval",
|
|
2928
|
+
level: decision.type === "approved" ? "info" : "warning",
|
|
2929
|
+
message: decision.type === "approved" ? `Approved tool call: ${normalizedRequest.toolCall.function.name}` : decision.reason ?? `Denied tool call: ${normalizedRequest.toolCall.function.name}`,
|
|
2930
|
+
metadata: {
|
|
2931
|
+
approvalId: normalizedRequest.id,
|
|
2932
|
+
toolName: normalizedRequest.toolCall.function.name
|
|
2933
|
+
}
|
|
2934
|
+
}, context);
|
|
2935
|
+
return decision;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
// src/agent-core/session-state.ts
|
|
2940
|
+
function touchRuntimeSessionState(state) {
|
|
2941
|
+
state.updatedAt = Date.now();
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
// src/agent-core/compaction.ts
|
|
2945
|
+
function createEmptyCompactionState() {
|
|
2946
|
+
return {
|
|
2947
|
+
cursor: 0,
|
|
2948
|
+
systemSegments: [],
|
|
2949
|
+
checkpoints: []
|
|
2950
|
+
};
|
|
2951
|
+
}
|
|
2952
|
+
function cloneCompactionState(state) {
|
|
2953
|
+
if (!state)
|
|
2954
|
+
return createEmptyCompactionState();
|
|
2955
|
+
return structuredClone({
|
|
2956
|
+
cursor: state.cursor,
|
|
2957
|
+
systemSegments: [...state.systemSegments],
|
|
2958
|
+
checkpoints: state.checkpoints.map((checkpoint) => ({
|
|
2959
|
+
...checkpoint,
|
|
2960
|
+
systemSegments: [...checkpoint.systemSegments],
|
|
2961
|
+
metadata: checkpoint.metadata ? structuredClone(checkpoint.metadata) : undefined
|
|
2962
|
+
}))
|
|
2963
|
+
});
|
|
2964
|
+
}
|
|
2965
|
+
function normalizeCompactionState(state, messages) {
|
|
2966
|
+
const current = cloneCompactionState(state);
|
|
2967
|
+
return {
|
|
2968
|
+
cursor: clampCursor(current.cursor, messages.length),
|
|
2969
|
+
systemSegments: current.systemSegments.filter(Boolean),
|
|
2970
|
+
checkpoints: current.checkpoints.map((checkpoint) => ({
|
|
2971
|
+
...checkpoint,
|
|
2972
|
+
cursor: clampCursor(checkpoint.cursor, messages.length),
|
|
2973
|
+
systemSegments: checkpoint.systemSegments.filter(Boolean)
|
|
2974
|
+
}))
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
function applyCompactionUpdate(state, update) {
|
|
2978
|
+
const nextCursor = clampCursor(update.cursor, state.messages.length);
|
|
2979
|
+
const nextSystemSegments = [...update.systemSegments ?? state.compaction.systemSegments].filter(Boolean);
|
|
2980
|
+
const nextCheckpoint = {
|
|
2981
|
+
id: createId("compact"),
|
|
2982
|
+
cursor: nextCursor,
|
|
2983
|
+
systemSegments: nextSystemSegments,
|
|
2984
|
+
summary: update.summary,
|
|
2985
|
+
reason: update.reason ?? "manual",
|
|
2986
|
+
createdAt: Date.now(),
|
|
2987
|
+
metadata: update.metadata ? structuredClone(update.metadata) : undefined
|
|
2988
|
+
};
|
|
2989
|
+
state.compaction = {
|
|
2990
|
+
cursor: nextCursor,
|
|
2991
|
+
systemSegments: nextSystemSegments,
|
|
2992
|
+
checkpoints: [...state.compaction.checkpoints, nextCheckpoint]
|
|
2993
|
+
};
|
|
2994
|
+
touchRuntimeSessionState(state);
|
|
2995
|
+
return cloneCompactionState(state.compaction);
|
|
2996
|
+
}
|
|
2997
|
+
function clearCompactionState(state) {
|
|
2998
|
+
state.compaction = createEmptyCompactionState();
|
|
2999
|
+
touchRuntimeSessionState(state);
|
|
3000
|
+
return cloneCompactionState(state.compaction);
|
|
3001
|
+
}
|
|
3002
|
+
function projectMessagesForRequest(messages, compaction) {
|
|
3003
|
+
const leadingSystemMessages = readLeadingSystemMessages(messages);
|
|
3004
|
+
const startIndex = Math.max(compaction.cursor, leadingSystemMessages.length);
|
|
3005
|
+
return [
|
|
3006
|
+
...leadingSystemMessages,
|
|
3007
|
+
...messages.slice(startIndex)
|
|
3008
|
+
];
|
|
3009
|
+
}
|
|
3010
|
+
function buildCompactionPromptSegments(compaction) {
|
|
3011
|
+
return compaction.systemSegments.filter(Boolean);
|
|
3012
|
+
}
|
|
3013
|
+
function calculateThresholdTokens(options) {
|
|
3014
|
+
if (options.triggerTokens !== undefined)
|
|
3015
|
+
return options.triggerTokens;
|
|
3016
|
+
const ratio = options.triggerRatio ?? 0.8;
|
|
3017
|
+
return Math.max(1, Math.floor(options.maxInputTokens * ratio));
|
|
3018
|
+
}
|
|
3019
|
+
async function estimateCompactionBudget(options, input) {
|
|
3020
|
+
const estimatedInputTokens = options.estimator ? await options.estimator(input) : estimateByHeuristic(input);
|
|
3021
|
+
return {
|
|
3022
|
+
estimatedInputTokens,
|
|
3023
|
+
thresholdTokens: calculateThresholdTokens(options),
|
|
3024
|
+
maxInputTokens: options.maxInputTokens
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
3027
|
+
function isCompactionRequired(options, budget) {
|
|
3028
|
+
if (!options || options.enabled === false || !budget)
|
|
3029
|
+
return false;
|
|
3030
|
+
return budget.estimatedInputTokens >= budget.thresholdTokens;
|
|
3031
|
+
}
|
|
3032
|
+
function didCompactionStateChange(before, after) {
|
|
3033
|
+
return before.cursor !== after.cursor || JSON.stringify(before.systemSegments) !== JSON.stringify(after.systemSegments) || before.checkpoints.length !== after.checkpoints.length;
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
class DefaultCompactionService {
|
|
3037
|
+
sessions = new Map;
|
|
3038
|
+
registerSession(state) {
|
|
3039
|
+
state.compaction = normalizeCompactionState(state.compaction, state.messages);
|
|
3040
|
+
this.sessions.set(state.id, state);
|
|
3041
|
+
}
|
|
3042
|
+
unregisterSession(sessionId) {
|
|
3043
|
+
this.sessions.delete(sessionId);
|
|
3044
|
+
}
|
|
3045
|
+
async getState(sessionId) {
|
|
3046
|
+
return cloneCompactionState(this.requireSession(sessionId).compaction);
|
|
3047
|
+
}
|
|
3048
|
+
async apply(update) {
|
|
3049
|
+
return applyCompactionUpdate(this.requireSession(update.sessionId), update);
|
|
3050
|
+
}
|
|
3051
|
+
async clear(sessionId) {
|
|
3052
|
+
return clearCompactionState(this.requireSession(sessionId));
|
|
3053
|
+
}
|
|
3054
|
+
requireSession(sessionId) {
|
|
3055
|
+
const state = this.sessions.get(sessionId);
|
|
3056
|
+
if (!state)
|
|
3057
|
+
throw new Error(`Unknown session for compaction: ${sessionId}`);
|
|
3058
|
+
return state;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
function clampCursor(cursor, max) {
|
|
3062
|
+
if (!Number.isFinite(cursor))
|
|
3063
|
+
return 0;
|
|
3064
|
+
return Math.max(0, Math.min(Math.floor(cursor), max));
|
|
3065
|
+
}
|
|
3066
|
+
function readLeadingSystemMessages(messages) {
|
|
3067
|
+
const systemMessages = [];
|
|
3068
|
+
for (const message of messages) {
|
|
3069
|
+
if (message.role !== "system")
|
|
3070
|
+
break;
|
|
3071
|
+
systemMessages.push(message);
|
|
3072
|
+
}
|
|
3073
|
+
return systemMessages;
|
|
3074
|
+
}
|
|
3075
|
+
function estimateByHeuristic(input) {
|
|
3076
|
+
const messageChars = input.messages.reduce((total, message) => {
|
|
3077
|
+
const contentChars = JSON.stringify(message.content).length;
|
|
3078
|
+
const metadataChars = message.metadata ? JSON.stringify(message.metadata).length : 0;
|
|
3079
|
+
return total + message.role.length + contentChars + metadataChars;
|
|
3080
|
+
}, 0);
|
|
3081
|
+
const toolChars = input.tools ? JSON.stringify(input.tools).length : 0;
|
|
3082
|
+
const modelChars = `${input.model.provider}:${input.model.modelId}`.length;
|
|
3083
|
+
return Math.max(1, Math.ceil((messageChars + toolChars + modelChars) / 4));
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// src/agent-core/plugin-state.ts
|
|
3087
|
+
var APP_PLUGIN_NAMESPACE = "__agent__";
|
|
3088
|
+
function clonePluginSessionStateEntry(entry) {
|
|
3089
|
+
if (!entry)
|
|
3090
|
+
return null;
|
|
3091
|
+
return structuredClone({
|
|
3092
|
+
version: entry.version,
|
|
3093
|
+
data: structuredClone(entry.data),
|
|
3094
|
+
updatedAt: entry.updatedAt
|
|
3095
|
+
});
|
|
3096
|
+
}
|
|
3097
|
+
function clonePluginStateMap(state) {
|
|
3098
|
+
if (!state)
|
|
3099
|
+
return {};
|
|
3100
|
+
return Object.fromEntries(Object.entries(state).map(([pluginId, entry]) => [pluginId, clonePluginSessionStateEntry(entry)]));
|
|
3101
|
+
}
|
|
3102
|
+
function normalizePluginStateMap(state) {
|
|
3103
|
+
if (!state)
|
|
3104
|
+
return {};
|
|
3105
|
+
return Object.fromEntries(Object.entries(state).map(([pluginId, entry]) => [pluginId, normalizePluginSessionStateEntry(entry, `Plugin session state for ${pluginId}`)]));
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
class DefaultPluginStateService {
|
|
3109
|
+
sessions = new Map;
|
|
3110
|
+
registerSession(state) {
|
|
3111
|
+
state.pluginState = normalizePluginStateMap(state.pluginState);
|
|
3112
|
+
this.sessions.set(state.id, state);
|
|
3113
|
+
}
|
|
3114
|
+
unregisterSession(sessionId) {
|
|
3115
|
+
this.sessions.delete(sessionId);
|
|
3116
|
+
}
|
|
3117
|
+
async get(sessionId) {
|
|
3118
|
+
return this.getForPlugin(sessionId, APP_PLUGIN_NAMESPACE);
|
|
3119
|
+
}
|
|
3120
|
+
async replace(sessionId, entry) {
|
|
3121
|
+
return this.replaceForPlugin(sessionId, APP_PLUGIN_NAMESPACE, entry);
|
|
3122
|
+
}
|
|
3123
|
+
async clear(sessionId) {
|
|
3124
|
+
await this.clearForPlugin(sessionId, APP_PLUGIN_NAMESPACE);
|
|
3125
|
+
}
|
|
3126
|
+
async getForPlugin(sessionId, pluginId) {
|
|
3127
|
+
const state = this.requireSession(sessionId);
|
|
3128
|
+
return clonePluginSessionStateEntry(state.pluginState[pluginId]);
|
|
3129
|
+
}
|
|
3130
|
+
async replaceForPlugin(sessionId, pluginId, entry) {
|
|
3131
|
+
const state = this.requireSession(sessionId);
|
|
3132
|
+
const nextEntry = normalizePluginSessionStateEntry(entry, `Plugin session state for ${pluginId}`);
|
|
3133
|
+
state.pluginState = {
|
|
3134
|
+
...state.pluginState,
|
|
3135
|
+
[pluginId]: nextEntry
|
|
3136
|
+
};
|
|
3137
|
+
touchRuntimeSessionState(state);
|
|
3138
|
+
return clonePluginSessionStateEntry(nextEntry);
|
|
3139
|
+
}
|
|
3140
|
+
async clearForPlugin(sessionId, pluginId) {
|
|
3141
|
+
const state = this.requireSession(sessionId);
|
|
3142
|
+
if (!(pluginId in state.pluginState))
|
|
3143
|
+
return;
|
|
3144
|
+
const nextState = { ...state.pluginState };
|
|
3145
|
+
delete nextState[pluginId];
|
|
3146
|
+
state.pluginState = nextState;
|
|
3147
|
+
touchRuntimeSessionState(state);
|
|
3148
|
+
}
|
|
3149
|
+
async list(sessionId) {
|
|
3150
|
+
return clonePluginStateMap(this.requireSession(sessionId).pluginState);
|
|
3151
|
+
}
|
|
3152
|
+
requireSession(sessionId) {
|
|
3153
|
+
const state = this.sessions.get(sessionId);
|
|
3154
|
+
if (!state)
|
|
3155
|
+
throw new Error(`Unknown session for plugin state: ${sessionId}`);
|
|
3156
|
+
return state;
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
function normalizePluginSessionStateEntry(entry, label) {
|
|
3160
|
+
if (!Number.isFinite(entry.version))
|
|
3161
|
+
throw new TypeError(`${label} version must be a finite number`);
|
|
3162
|
+
if (!Number.isFinite(entry.updatedAt))
|
|
3163
|
+
throw new TypeError(`${label} updatedAt must be a finite number`);
|
|
3164
|
+
assertJsonSafeObject(entry.data, `${label} data`);
|
|
3165
|
+
return {
|
|
3166
|
+
version: Math.floor(entry.version),
|
|
3167
|
+
data: structuredClone(entry.data),
|
|
3168
|
+
updatedAt: entry.updatedAt
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
// src/agent-core/message-factory.ts
|
|
3173
|
+
class DefaultMessageFactory {
|
|
3174
|
+
createSystemMessage(input) {
|
|
3175
|
+
return {
|
|
3176
|
+
id: createId("msg"),
|
|
3177
|
+
role: "system",
|
|
3178
|
+
content: normalizeContent(input),
|
|
3179
|
+
createdAt: Date.now()
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
createUserMessage(input) {
|
|
3183
|
+
return {
|
|
3184
|
+
id: createId("msg"),
|
|
3185
|
+
role: "user",
|
|
3186
|
+
content: normalizeContent(input),
|
|
3187
|
+
createdAt: Date.now()
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
3190
|
+
createAssistantMessage(input) {
|
|
3191
|
+
return {
|
|
3192
|
+
id: createId("msg"),
|
|
3193
|
+
role: "assistant",
|
|
3194
|
+
content: input.text.length > 0 ? normalizeContent(input.text) : [],
|
|
3195
|
+
createdAt: Date.now(),
|
|
3196
|
+
thinking: input.thinking,
|
|
3197
|
+
toolCalls: input.toolCalls.length > 0 ? input.toolCalls : undefined,
|
|
3198
|
+
stopReason: input.stopReason,
|
|
3199
|
+
metadata: input.metadata
|
|
3200
|
+
};
|
|
3201
|
+
}
|
|
3202
|
+
createToolMessage(toolCall, result) {
|
|
3203
|
+
return {
|
|
3204
|
+
id: createId("msg"),
|
|
3205
|
+
role: "tool",
|
|
3206
|
+
content: result.content,
|
|
3207
|
+
createdAt: Date.now(),
|
|
3208
|
+
toolCallId: toolCall.id,
|
|
3209
|
+
toolName: toolCall.function.name,
|
|
3210
|
+
structuredContent: result.structuredContent,
|
|
3211
|
+
isError: result.isError
|
|
3212
|
+
};
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
// src/agent-core/notifications.ts
|
|
3217
|
+
class DefaultNotificationBus {
|
|
3218
|
+
pluginHost;
|
|
3219
|
+
services;
|
|
3220
|
+
constructor(options) {
|
|
3221
|
+
this.pluginHost = options.pluginHost;
|
|
3222
|
+
this.services = options.services;
|
|
3223
|
+
}
|
|
3224
|
+
async emit(notification, context = {}) {
|
|
3225
|
+
const normalized = {
|
|
3226
|
+
id: notification.id ?? createId("note"),
|
|
3227
|
+
level: notification.level ?? "info",
|
|
3228
|
+
...notification
|
|
3229
|
+
};
|
|
3230
|
+
if (!this.pluginHost)
|
|
3231
|
+
return normalized;
|
|
3232
|
+
const result = await this.pluginHost.runNotifyMessage({ notification: normalized }, {
|
|
3233
|
+
sessionId: context.sessionId,
|
|
3234
|
+
requestId: context.requestId,
|
|
3235
|
+
iteration: context.iteration,
|
|
3236
|
+
cwd: context.cwd,
|
|
3237
|
+
status: context.status,
|
|
3238
|
+
metadata: context.metadata,
|
|
3239
|
+
services: this.services
|
|
3240
|
+
});
|
|
3241
|
+
if (result.suppressed)
|
|
3242
|
+
return;
|
|
3243
|
+
await this.pluginHost.runObservers("notify.message", result.payload, {
|
|
3244
|
+
sessionId: context.sessionId,
|
|
3245
|
+
requestId: context.requestId,
|
|
3246
|
+
iteration: context.iteration,
|
|
3247
|
+
cwd: context.cwd,
|
|
3248
|
+
status: context.status,
|
|
3249
|
+
metadata: context.metadata,
|
|
3250
|
+
services: this.services
|
|
3251
|
+
});
|
|
3252
|
+
return result.payload.notification;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
// src/persistence/snapshot-codec.ts
|
|
3257
|
+
class JsonSnapshotCodec {
|
|
3258
|
+
encode(input) {
|
|
3259
|
+
return structuredClone({
|
|
3260
|
+
schemaVersion: 2,
|
|
3261
|
+
sessionId: input.sessionId,
|
|
3262
|
+
model: input.model,
|
|
3263
|
+
cwd: input.cwd,
|
|
3264
|
+
messages: input.messages,
|
|
3265
|
+
usage: input.usage,
|
|
3266
|
+
compaction: input.compaction,
|
|
3267
|
+
pluginState: input.pluginState,
|
|
3268
|
+
createdAt: input.createdAt,
|
|
3269
|
+
updatedAt: input.updatedAt,
|
|
3270
|
+
metadata: input.metadata,
|
|
3271
|
+
messageQueue: input.messageQueue
|
|
3272
|
+
});
|
|
3273
|
+
}
|
|
3274
|
+
decode(value) {
|
|
3275
|
+
if (!value || typeof value !== "object")
|
|
3276
|
+
throw new TypeError("Session snapshot must be an object");
|
|
3277
|
+
const snapshot = value;
|
|
3278
|
+
if (snapshot.schemaVersion !== 1 && snapshot.schemaVersion !== 2)
|
|
3279
|
+
throw new TypeError("Unsupported session snapshot schemaVersion");
|
|
3280
|
+
if (typeof snapshot.sessionId !== "string")
|
|
3281
|
+
throw new TypeError("Session snapshot sessionId is required");
|
|
3282
|
+
if (!Array.isArray(snapshot.messages))
|
|
3283
|
+
throw new TypeError("Session snapshot messages must be an array");
|
|
3284
|
+
if (typeof snapshot.createdAt !== "number" || typeof snapshot.updatedAt !== "number")
|
|
3285
|
+
throw new TypeError("Session snapshot timestamps are required");
|
|
3286
|
+
if (snapshot.schemaVersion === 1) {
|
|
3287
|
+
return structuredClone({
|
|
3288
|
+
...snapshot,
|
|
3289
|
+
schemaVersion: 1
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
return structuredClone(snapshot);
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
var sessionSnapshotCodec = new JsonSnapshotCodec;
|
|
3296
|
+
|
|
3297
|
+
// src/utils/usage.ts
|
|
3298
|
+
function createEmptyUsage() {
|
|
3299
|
+
return {
|
|
3300
|
+
promptTokens: 0,
|
|
3301
|
+
completionTokens: 0,
|
|
3302
|
+
totalTokens: 0
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
function addUsage(left, right) {
|
|
3306
|
+
if (!right)
|
|
3307
|
+
return { ...left };
|
|
3308
|
+
return {
|
|
3309
|
+
promptTokens: left.promptTokens + right.promptTokens,
|
|
3310
|
+
completionTokens: left.completionTokens + right.completionTokens,
|
|
3311
|
+
totalTokens: left.totalTokens + right.totalTokens
|
|
3312
|
+
};
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
// src/agent-core/errors.ts
|
|
3316
|
+
class SessionExecutionError extends Error {
|
|
3317
|
+
payload;
|
|
3318
|
+
payloadSummary;
|
|
3319
|
+
constructor(payload) {
|
|
3320
|
+
super(payload.message);
|
|
3321
|
+
this.name = "SessionExecutionError";
|
|
3322
|
+
this.payload = payload;
|
|
3323
|
+
this.payloadSummary = stringifyPayload(payload);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
function stringifyPayload(payload) {
|
|
3327
|
+
try {
|
|
3328
|
+
return JSON.stringify(payload, null, 2);
|
|
3329
|
+
} catch {
|
|
3330
|
+
return String(payload);
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
// src/agent-core/tool-call.ts
|
|
3335
|
+
function startToolCall(callId, toolName) {
|
|
3336
|
+
if (!callId.trim())
|
|
3337
|
+
throw new Error("Tool call id is required");
|
|
3338
|
+
if (!toolName.trim())
|
|
3339
|
+
throw new Error("Tool name is required");
|
|
3340
|
+
return {
|
|
3341
|
+
callId,
|
|
3342
|
+
toolName,
|
|
3343
|
+
argsText: ""
|
|
3344
|
+
};
|
|
3345
|
+
}
|
|
3346
|
+
function appendToolCallArgsDelta(draft, delta) {
|
|
3347
|
+
return {
|
|
3348
|
+
...draft,
|
|
3349
|
+
argsText: `${draft.argsText}${delta}`
|
|
3350
|
+
};
|
|
3351
|
+
}
|
|
3352
|
+
function finalizeToolCall(draft) {
|
|
3353
|
+
const rawArgumentsText = draft.argsText.trim();
|
|
3354
|
+
const argumentsObject = rawArgumentsText.length === 0 ? {} : parseJsonObject(rawArgumentsText, `Tool call ${draft.callId} arguments`);
|
|
3355
|
+
return {
|
|
3356
|
+
id: draft.callId,
|
|
3357
|
+
type: "function",
|
|
3358
|
+
function: {
|
|
3359
|
+
name: draft.toolName,
|
|
3360
|
+
arguments: argumentsObject
|
|
3361
|
+
},
|
|
3362
|
+
rawArgumentsText
|
|
3363
|
+
};
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
// src/agent-core/model-turn-collector.ts
|
|
3367
|
+
class DefaultModelTurnCollector {
|
|
3368
|
+
async* collect(options) {
|
|
3369
|
+
const assistantTextParts = [];
|
|
3370
|
+
const thinkingParts = [];
|
|
3371
|
+
const toolCalls = [];
|
|
3372
|
+
const drafts = new Map;
|
|
3373
|
+
let stopReason = "final";
|
|
3374
|
+
let responseUsage;
|
|
3375
|
+
let assistantMetadata;
|
|
3376
|
+
for await (const event of options.events) {
|
|
3377
|
+
await options.onEvent?.(event);
|
|
3378
|
+
if (event.type === "response_start")
|
|
3379
|
+
continue;
|
|
3380
|
+
if (event.type === "text_delta") {
|
|
3381
|
+
assistantTextParts.push(event.delta);
|
|
3382
|
+
yield { type: "text_delta", sessionId: options.sessionId, delta: event.delta };
|
|
3383
|
+
continue;
|
|
3384
|
+
}
|
|
3385
|
+
if (event.type === "thinking_delta") {
|
|
3386
|
+
thinkingParts.push(event.delta);
|
|
3387
|
+
yield { type: "thinking_delta", sessionId: options.sessionId, delta: event.delta };
|
|
3388
|
+
continue;
|
|
3389
|
+
}
|
|
3390
|
+
if (event.type === "tool_call_start") {
|
|
3391
|
+
drafts.set(event.callId, startToolCall(event.callId, event.toolName));
|
|
3392
|
+
continue;
|
|
3393
|
+
}
|
|
3394
|
+
if (event.type === "tool_call_args_delta") {
|
|
3395
|
+
const draft = drafts.get(event.callId);
|
|
3396
|
+
if (!draft) {
|
|
3397
|
+
const payload2 = {
|
|
3398
|
+
code: "tool_call_state_error",
|
|
3399
|
+
message: `Received tool_call_args_delta before tool_call_start for ${event.callId}`,
|
|
3400
|
+
requestId: options.requestId
|
|
3401
|
+
};
|
|
3402
|
+
yield { type: "error", sessionId: options.sessionId, error: payload2 };
|
|
3403
|
+
throw new SessionExecutionError(payload2);
|
|
3404
|
+
}
|
|
3405
|
+
drafts.set(event.callId, appendToolCallArgsDelta(draft, event.delta));
|
|
3406
|
+
continue;
|
|
3407
|
+
}
|
|
3408
|
+
if (event.type === "tool_call_end") {
|
|
3409
|
+
const draft = drafts.get(event.callId);
|
|
3410
|
+
if (!draft) {
|
|
3411
|
+
const payload2 = {
|
|
3412
|
+
code: "tool_call_state_error",
|
|
3413
|
+
message: `Received tool_call_end before tool_call_start for ${event.callId}`,
|
|
3414
|
+
requestId: options.requestId
|
|
3415
|
+
};
|
|
3416
|
+
yield { type: "error", sessionId: options.sessionId, error: payload2 };
|
|
3417
|
+
throw new SessionExecutionError(payload2);
|
|
3418
|
+
}
|
|
3419
|
+
try {
|
|
3420
|
+
toolCalls.push(finalizeToolCall(draft));
|
|
3421
|
+
} catch (error) {
|
|
3422
|
+
const payload2 = {
|
|
3423
|
+
code: "tool_call_parse_error",
|
|
3424
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3425
|
+
requestId: options.requestId
|
|
3426
|
+
};
|
|
3427
|
+
yield { type: "error", sessionId: options.sessionId, error: payload2 };
|
|
3428
|
+
throw new SessionExecutionError(payload2);
|
|
3429
|
+
}
|
|
3430
|
+
continue;
|
|
3431
|
+
}
|
|
3432
|
+
if (event.type === "response_end") {
|
|
3433
|
+
stopReason = event.stopReason;
|
|
3434
|
+
responseUsage = event.usage;
|
|
3435
|
+
assistantMetadata = event.assistantMetadata;
|
|
3436
|
+
continue;
|
|
3437
|
+
}
|
|
3438
|
+
const payload = {
|
|
3439
|
+
...event.error,
|
|
3440
|
+
requestId: options.requestId
|
|
3441
|
+
};
|
|
3442
|
+
yield { type: "error", sessionId: options.sessionId, error: payload };
|
|
3443
|
+
throw new SessionExecutionError(payload);
|
|
3444
|
+
}
|
|
3445
|
+
return {
|
|
3446
|
+
requestId: options.requestId,
|
|
3447
|
+
text: assistantTextParts.join(""),
|
|
3448
|
+
thinking: thinkingParts.join("") || undefined,
|
|
3449
|
+
toolCalls,
|
|
3450
|
+
stopReason,
|
|
3451
|
+
usage: responseUsage,
|
|
3452
|
+
assistantMetadata
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
// src/agent-core/session-status.ts
|
|
3458
|
+
function createSessionStatus(state) {
|
|
3459
|
+
return {
|
|
3460
|
+
sessionId: state.id,
|
|
3461
|
+
model: { ...state.modelRef },
|
|
3462
|
+
cwd: state.cwd,
|
|
3463
|
+
usage: { ...state.usage },
|
|
3464
|
+
compaction: cloneCompactionState(state.compaction),
|
|
3465
|
+
messageCount: state.messages.length,
|
|
3466
|
+
createdAt: state.createdAt,
|
|
3467
|
+
updatedAt: state.updatedAt,
|
|
3468
|
+
metadata: state.metadata ? structuredClone(state.metadata) : undefined
|
|
3469
|
+
};
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
// src/agent-core/loop-runner.ts
|
|
3473
|
+
class LoopRunner {
|
|
3474
|
+
messageFactory;
|
|
3475
|
+
turnCollector;
|
|
3476
|
+
toolExecutor;
|
|
3477
|
+
terminationPolicy;
|
|
3478
|
+
contextManager;
|
|
3479
|
+
pluginHost;
|
|
3480
|
+
notificationBus;
|
|
3481
|
+
constructor(dependencies) {
|
|
3482
|
+
this.messageFactory = dependencies.messageFactory;
|
|
3483
|
+
this.turnCollector = dependencies.turnCollector;
|
|
3484
|
+
this.toolExecutor = dependencies.toolExecutor;
|
|
3485
|
+
this.terminationPolicy = dependencies.terminationPolicy;
|
|
3486
|
+
this.contextManager = dependencies.contextManager;
|
|
3487
|
+
this.pluginHost = dependencies.pluginHost;
|
|
3488
|
+
this.notificationBus = dependencies.notificationBus;
|
|
3489
|
+
}
|
|
3490
|
+
async* run(state, input, options = {}) {
|
|
3491
|
+
const startPayload = await this.pluginHost?.runMiddleware("run.start", {
|
|
3492
|
+
sessionId: state.id,
|
|
3493
|
+
input
|
|
3494
|
+
}, this.createHookContext(state));
|
|
3495
|
+
const initialInput = startPayload?.input ?? input;
|
|
3496
|
+
await this.pluginHost?.runObservers("run.start", {
|
|
3497
|
+
sessionId: state.id,
|
|
3498
|
+
input: initialInput
|
|
3499
|
+
}, this.createHookContext(state));
|
|
3500
|
+
const userMessage = this.messageFactory.createUserMessage(initialInput);
|
|
3501
|
+
state.messages.push(userMessage);
|
|
3502
|
+
touchRuntimeSessionState(state);
|
|
3503
|
+
await this.notificationBus.emit({
|
|
3504
|
+
type: "run.start",
|
|
3505
|
+
source: "runtime",
|
|
3506
|
+
message: `Run started for session ${state.id}`,
|
|
3507
|
+
metadata: {
|
|
3508
|
+
sessionId: state.id
|
|
3509
|
+
}
|
|
3510
|
+
}, {
|
|
3511
|
+
...this.createHookContext(state)
|
|
3512
|
+
});
|
|
3513
|
+
let lastRequestId;
|
|
3514
|
+
let lastIteration = 0;
|
|
3515
|
+
let pendingContextSegments = [];
|
|
3516
|
+
try {
|
|
3517
|
+
for (let iteration = 0;iteration < state.maxIterations; iteration += 1) {
|
|
3518
|
+
lastIteration = iteration;
|
|
3519
|
+
const requestId = createId("req");
|
|
3520
|
+
lastRequestId = requestId;
|
|
3521
|
+
const turnStart = await this.pluginHost?.runMiddleware("turn.start", {
|
|
3522
|
+
requestId,
|
|
3523
|
+
iteration,
|
|
3524
|
+
messages: [...state.messages]
|
|
3525
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
3526
|
+
const contextItems = await this.contextManager.resolve({
|
|
3527
|
+
sessionId: state.id,
|
|
3528
|
+
input: initialInput,
|
|
3529
|
+
messages: state.messages,
|
|
3530
|
+
cwd: state.cwd
|
|
3531
|
+
});
|
|
3532
|
+
const resolvedContext = await this.pluginHost?.runMiddleware("context.resolve", {
|
|
3533
|
+
contextItems,
|
|
3534
|
+
input: initialInput,
|
|
3535
|
+
messages: [...state.messages]
|
|
3536
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
3537
|
+
const effectiveContext = resolvedContext?.contextItems ?? contextItems;
|
|
3538
|
+
const pluginPromptSegments = await this.pluginHost?.collectPromptSegments({
|
|
3539
|
+
sessionId: state.id,
|
|
3540
|
+
input: initialInput,
|
|
3541
|
+
messages: state.messages,
|
|
3542
|
+
contextItems: effectiveContext,
|
|
3543
|
+
cwd: state.cwd
|
|
3544
|
+
}) ?? [];
|
|
3545
|
+
const promptSegments = [
|
|
3546
|
+
...options.persistentPromptSegments ?? [],
|
|
3547
|
+
...pendingContextSegments,
|
|
3548
|
+
...this.contextManager.format(effectiveContext),
|
|
3549
|
+
...pluginPromptSegments
|
|
3550
|
+
];
|
|
3551
|
+
pendingContextSegments = [];
|
|
3552
|
+
const resolvedPrompt = await this.pluginHost?.runMiddleware("prompt.resolve", {
|
|
3553
|
+
promptSegments,
|
|
3554
|
+
contextItems: effectiveContext,
|
|
3555
|
+
messages: [...state.messages]
|
|
3556
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
3557
|
+
const effectivePromptSegments = resolvedPrompt?.promptSegments ?? promptSegments;
|
|
3558
|
+
const toolDefinitions = state.tools?.definitions() ?? [];
|
|
3559
|
+
const resolvedToolset = await this.pluginHost?.runMiddleware("toolset.resolve", {
|
|
3560
|
+
tools: toolDefinitions
|
|
3561
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
3562
|
+
const effectiveTools = resolvedToolset?.tools ?? toolDefinitions;
|
|
3563
|
+
const requestMessages = await this.resolveRequestMessages({
|
|
3564
|
+
state,
|
|
3565
|
+
requestId,
|
|
3566
|
+
iteration,
|
|
3567
|
+
baseMessages: [...turnStart?.messages ?? state.messages],
|
|
3568
|
+
runtimePromptSegments: effectivePromptSegments,
|
|
3569
|
+
contextItems: effectiveContext,
|
|
3570
|
+
tools: effectiveTools
|
|
3571
|
+
});
|
|
3572
|
+
const request = {
|
|
3573
|
+
requestId,
|
|
3574
|
+
model: state.modelRef,
|
|
3575
|
+
messages: requestMessages,
|
|
3576
|
+
tools: effectiveTools,
|
|
3577
|
+
reasoning: state.reasoning,
|
|
3578
|
+
signal: options.signal
|
|
3579
|
+
};
|
|
3580
|
+
const resolvedRequest = await this.pluginHost?.runMiddleware("model.request", {
|
|
3581
|
+
request
|
|
3582
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
3583
|
+
const effectiveRequest = resolvedRequest?.request ?? request;
|
|
3584
|
+
const turn = yield* this.turnCollector.collect({
|
|
3585
|
+
sessionId: state.id,
|
|
3586
|
+
requestId,
|
|
3587
|
+
events: state.model.stream(effectiveRequest),
|
|
3588
|
+
onEvent: async (event) => {
|
|
3589
|
+
if (event.type === "error") {
|
|
3590
|
+
await this.notificationBus.emit({
|
|
3591
|
+
type: "provider.error",
|
|
3592
|
+
source: "provider",
|
|
3593
|
+
level: "error",
|
|
3594
|
+
message: event.error.message,
|
|
3595
|
+
metadata: {
|
|
3596
|
+
requestId,
|
|
3597
|
+
code: event.error.code,
|
|
3598
|
+
status: event.error.status
|
|
3599
|
+
}
|
|
3600
|
+
}, {
|
|
3601
|
+
...this.createHookContext(state, { requestId, iteration })
|
|
3602
|
+
});
|
|
3603
|
+
}
|
|
3604
|
+
await this.pluginHost?.runObservers("model.event", { event }, {
|
|
3605
|
+
...this.createHookContext(state, { requestId, iteration })
|
|
3606
|
+
});
|
|
3607
|
+
}
|
|
3608
|
+
});
|
|
3609
|
+
const assistantDraft = this.messageFactory.createAssistantMessage({
|
|
3610
|
+
text: turn.text,
|
|
3611
|
+
thinking: turn.thinking,
|
|
3612
|
+
toolCalls: turn.toolCalls,
|
|
3613
|
+
stopReason: turn.stopReason,
|
|
3614
|
+
metadata: turn.assistantMetadata
|
|
3615
|
+
});
|
|
3616
|
+
const assistantPayload = await this.pluginHost?.runMiddleware("assistant.message", {
|
|
3617
|
+
message: assistantDraft
|
|
3618
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
3619
|
+
let assistantMessage = assistantPayload?.message ?? assistantDraft;
|
|
3620
|
+
state.messages.push(assistantMessage);
|
|
3621
|
+
state.usage = addUsage(state.usage, turn.usage);
|
|
3622
|
+
touchRuntimeSessionState(state);
|
|
3623
|
+
const baseDecision = this.terminationPolicy.afterAssistantTurn(turn.toolCalls);
|
|
3624
|
+
const stopBasePayload = {
|
|
3625
|
+
assistantMessage,
|
|
3626
|
+
toolCalls: turn.toolCalls,
|
|
3627
|
+
stopReason: turn.stopReason,
|
|
3628
|
+
decision: baseDecision
|
|
3629
|
+
};
|
|
3630
|
+
const stopResult = await this.pluginHost?.runRunStop(stopBasePayload, {
|
|
3631
|
+
...this.createHookContext(state, { requestId, iteration })
|
|
3632
|
+
});
|
|
3633
|
+
const stopPayload = stopResult?.payload ?? stopBasePayload;
|
|
3634
|
+
const stopControl = stopResult?.control;
|
|
3635
|
+
await this.pluginHost?.runObservers("run.stop", {
|
|
3636
|
+
...stopPayload,
|
|
3637
|
+
additionalContext: stopControl?.type === "finalize" ? stopControl.additionalContext ?? stopPayload.additionalContext : stopPayload.additionalContext,
|
|
3638
|
+
finalMessage: stopControl?.type === "finalize" ? stopControl.finalMessage ?? stopPayload.finalMessage : stopPayload.finalMessage
|
|
3639
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
3640
|
+
const stopDecision = stopControl?.type === "finalize" ? { type: "done" } : stopPayload.decision;
|
|
3641
|
+
const additionalContext = stopControl?.type === "finalize" ? stopControl.additionalContext : stopPayload.additionalContext;
|
|
3642
|
+
const finalMessage = stopControl?.type === "finalize" ? stopControl.finalMessage ?? stopPayload.finalMessage ?? stopPayload.assistantMessage : stopPayload.finalMessage ?? stopPayload.assistantMessage;
|
|
3643
|
+
if (additionalContext && additionalContext.length > 0 && stopDecision.type === "continue")
|
|
3644
|
+
pendingContextSegments = [...pendingContextSegments, ...additionalContext];
|
|
3645
|
+
if (stopDecision.type === "done") {
|
|
3646
|
+
assistantMessage = finalMessage;
|
|
3647
|
+
state.messages[state.messages.length - 1] = assistantMessage;
|
|
3648
|
+
touchRuntimeSessionState(state);
|
|
3649
|
+
const endPayload = await this.pluginHost?.runMiddleware("run.end", {
|
|
3650
|
+
message: assistantMessage,
|
|
3651
|
+
usage: { ...state.usage }
|
|
3652
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
3653
|
+
const completedMessage = endPayload?.message ?? assistantMessage;
|
|
3654
|
+
const completedUsage = endPayload?.usage ?? { ...state.usage };
|
|
3655
|
+
state.messages[state.messages.length - 1] = completedMessage;
|
|
3656
|
+
state.usage = { ...completedUsage };
|
|
3657
|
+
touchRuntimeSessionState(state);
|
|
3658
|
+
await this.notificationBus.emit({
|
|
3659
|
+
type: "run.end",
|
|
3660
|
+
source: "runtime",
|
|
3661
|
+
message: `Run completed for session ${state.id}`,
|
|
3662
|
+
metadata: {
|
|
3663
|
+
sessionId: state.id,
|
|
3664
|
+
requestId
|
|
3665
|
+
}
|
|
3666
|
+
}, {
|
|
3667
|
+
...this.createHookContext(state, { requestId, iteration })
|
|
3668
|
+
});
|
|
3669
|
+
yield {
|
|
3670
|
+
type: "done",
|
|
3671
|
+
sessionId: state.id,
|
|
3672
|
+
message: completedMessage,
|
|
3673
|
+
usage: completedUsage
|
|
3674
|
+
};
|
|
3675
|
+
await this.pluginHost?.runObservers("run.end", {
|
|
3676
|
+
message: completedMessage,
|
|
3677
|
+
usage: completedUsage
|
|
3678
|
+
}, this.createHookContext(state, { requestId, iteration }));
|
|
3679
|
+
await options.onDone?.(completedMessage);
|
|
3680
|
+
return completedMessage;
|
|
3681
|
+
}
|
|
3682
|
+
for (const toolCall of turn.toolCalls) {
|
|
3683
|
+
yield { type: "tool_call", sessionId: state.id, toolCall };
|
|
3684
|
+
const pluginEvents = [];
|
|
3685
|
+
const result = await this.toolExecutor.execute(toolCall, {
|
|
3686
|
+
signal: options.signal,
|
|
3687
|
+
cwd: state.cwd,
|
|
3688
|
+
requestId,
|
|
3689
|
+
iteration,
|
|
3690
|
+
status: createSessionStatus(state),
|
|
3691
|
+
onEvent: async (event) => {
|
|
3692
|
+
pluginEvents.push(event);
|
|
3693
|
+
}
|
|
3694
|
+
});
|
|
3695
|
+
for (const event of pluginEvents)
|
|
3696
|
+
yield event;
|
|
3697
|
+
const toolMessage = this.messageFactory.createToolMessage(toolCall, result);
|
|
3698
|
+
state.messages.push(toolMessage);
|
|
3699
|
+
touchRuntimeSessionState(state);
|
|
3700
|
+
yield { type: "tool_result", sessionId: state.id, toolCallId: toolCall.id, result };
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
} catch (error) {
|
|
3704
|
+
if (error instanceof SessionExecutionError) {
|
|
3705
|
+
await this.notificationBus.emit({
|
|
3706
|
+
type: error.payload.code.startsWith("openai_") || error.payload.code.startsWith("anthropic_") || error.payload.code.startsWith("gemini_") ? "provider.error" : "runtime.error",
|
|
3707
|
+
source: error.payload.code.includes("_http_") || error.payload.code.includes("_request_") ? "provider" : "runtime",
|
|
3708
|
+
level: "error",
|
|
3709
|
+
message: error.payload.message,
|
|
3710
|
+
metadata: {
|
|
3711
|
+
requestId: lastRequestId,
|
|
3712
|
+
code: error.payload.code
|
|
3713
|
+
}
|
|
3714
|
+
}, {
|
|
3715
|
+
...this.createHookContext(state, { requestId: lastRequestId, iteration: lastIteration })
|
|
3716
|
+
});
|
|
3717
|
+
await this.pluginHost?.runObservers("session.error", { error: error.payload }, {
|
|
3718
|
+
...this.createHookContext(state, { requestId: lastRequestId })
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
3721
|
+
throw error;
|
|
3722
|
+
}
|
|
3723
|
+
const payload = this.terminationPolicy.onMaxIterationsExceeded(state.maxIterations, lastRequestId);
|
|
3724
|
+
yield { type: "error", sessionId: state.id, error: payload };
|
|
3725
|
+
await this.notificationBus.emit({
|
|
3726
|
+
type: "runtime.error",
|
|
3727
|
+
source: "runtime",
|
|
3728
|
+
level: "error",
|
|
3729
|
+
message: payload.message,
|
|
3730
|
+
metadata: {
|
|
3731
|
+
requestId: lastRequestId,
|
|
3732
|
+
code: payload.code
|
|
3733
|
+
}
|
|
3734
|
+
}, {
|
|
3735
|
+
...this.createHookContext(state, { requestId: lastRequestId, iteration: lastIteration })
|
|
3736
|
+
});
|
|
3737
|
+
await this.pluginHost?.runObservers("session.error", { error: payload }, {
|
|
3738
|
+
...this.createHookContext(state, { requestId: lastRequestId })
|
|
3739
|
+
});
|
|
3740
|
+
throw new SessionExecutionError(payload);
|
|
3741
|
+
}
|
|
3742
|
+
async resolveRequestMessages(input) {
|
|
3743
|
+
const initialAttempt = await this.buildProjectedRequestMessages(input.state, input.baseMessages, input.runtimePromptSegments, input.tools);
|
|
3744
|
+
if (!isCompactionRequired(input.state.compactionOptions, initialAttempt.budget))
|
|
3745
|
+
return initialAttempt.messages;
|
|
3746
|
+
const budget = initialAttempt.budget;
|
|
3747
|
+
if (!budget)
|
|
3748
|
+
return initialAttempt.messages;
|
|
3749
|
+
await this.notificationBus.emit({
|
|
3750
|
+
type: "context.compaction.required",
|
|
3751
|
+
source: "runtime",
|
|
3752
|
+
level: "warning",
|
|
3753
|
+
message: `Context compaction required for session ${input.state.id}`,
|
|
3754
|
+
metadata: {
|
|
3755
|
+
requestId: input.requestId,
|
|
3756
|
+
estimatedInputTokens: budget.estimatedInputTokens,
|
|
3757
|
+
thresholdTokens: budget.thresholdTokens,
|
|
3758
|
+
maxInputTokens: budget.maxInputTokens,
|
|
3759
|
+
cursor: input.state.compaction.cursor
|
|
3760
|
+
}
|
|
3761
|
+
}, {
|
|
3762
|
+
...this.createHookContext(input.state, { requestId: input.requestId, iteration: input.iteration })
|
|
3763
|
+
});
|
|
3764
|
+
const beforeState = cloneCompactionState(input.state.compaction);
|
|
3765
|
+
const compactPayload = await this.pluginHost?.runMiddleware("context.compact.before", {
|
|
3766
|
+
sessionId: input.state.id,
|
|
3767
|
+
messages: [...input.state.messages],
|
|
3768
|
+
cursor: beforeState.cursor,
|
|
3769
|
+
systemSegments: [...beforeState.systemSegments],
|
|
3770
|
+
contextItems: input.contextItems,
|
|
3771
|
+
estimatedInputTokens: budget.estimatedInputTokens,
|
|
3772
|
+
thresholdTokens: budget.thresholdTokens,
|
|
3773
|
+
maxInputTokens: budget.maxInputTokens,
|
|
3774
|
+
trigger: "threshold"
|
|
3775
|
+
}, {
|
|
3776
|
+
...this.createHookContext(input.state, { requestId: input.requestId, iteration: input.iteration })
|
|
3777
|
+
});
|
|
3778
|
+
await this.pluginHost?.runObservers("context.compact.before", compactPayload ?? {
|
|
3779
|
+
sessionId: input.state.id,
|
|
3780
|
+
messages: [...input.state.messages],
|
|
3781
|
+
cursor: beforeState.cursor,
|
|
3782
|
+
systemSegments: [...beforeState.systemSegments],
|
|
3783
|
+
contextItems: input.contextItems,
|
|
3784
|
+
estimatedInputTokens: budget.estimatedInputTokens,
|
|
3785
|
+
thresholdTokens: budget.thresholdTokens,
|
|
3786
|
+
maxInputTokens: budget.maxInputTokens,
|
|
3787
|
+
trigger: "threshold"
|
|
3788
|
+
}, {
|
|
3789
|
+
...this.createHookContext(input.state, { requestId: input.requestId, iteration: input.iteration })
|
|
3790
|
+
});
|
|
3791
|
+
input.state.compaction = normalizeCompactionState(input.state.compaction, input.state.messages);
|
|
3792
|
+
const afterState = cloneCompactionState(input.state.compaction);
|
|
3793
|
+
if (didCompactionStateChange(beforeState, afterState)) {
|
|
3794
|
+
const latestCheckpoint = afterState.checkpoints.at(-1);
|
|
3795
|
+
await this.notificationBus.emit({
|
|
3796
|
+
type: "context.compacted",
|
|
3797
|
+
source: "runtime",
|
|
3798
|
+
level: "info",
|
|
3799
|
+
message: `Context compacted for session ${input.state.id}`,
|
|
3800
|
+
metadata: {
|
|
3801
|
+
requestId: input.requestId,
|
|
3802
|
+
cursor: afterState.cursor,
|
|
3803
|
+
checkpointId: latestCheckpoint?.id,
|
|
3804
|
+
reason: latestCheckpoint?.reason
|
|
3805
|
+
}
|
|
3806
|
+
}, {
|
|
3807
|
+
...this.createHookContext(input.state, { requestId: input.requestId, iteration: input.iteration })
|
|
3808
|
+
});
|
|
3809
|
+
}
|
|
3810
|
+
const compactedAttempt = await this.buildProjectedRequestMessages(input.state, input.baseMessages, input.runtimePromptSegments, input.tools);
|
|
3811
|
+
if (!isCompactionRequired(input.state.compactionOptions, compactedAttempt.budget))
|
|
3812
|
+
return compactedAttempt.messages;
|
|
3813
|
+
throw new SessionExecutionError({
|
|
3814
|
+
code: "context_compaction_required",
|
|
3815
|
+
message: `Context compaction required before continuing the run (estimated ${budget.estimatedInputTokens} tokens, threshold ${budget.thresholdTokens})`,
|
|
3816
|
+
requestId: input.requestId
|
|
3817
|
+
});
|
|
3818
|
+
}
|
|
3819
|
+
async buildProjectedRequestMessages(state, baseMessages, runtimePromptSegments, tools) {
|
|
3820
|
+
const projectedMessages = projectMessagesForRequest(baseMessages, state.compaction);
|
|
3821
|
+
const promptSegments = [
|
|
3822
|
+
...buildCompactionPromptSegments(state.compaction),
|
|
3823
|
+
...runtimePromptSegments
|
|
3824
|
+
];
|
|
3825
|
+
const requestMessages = [...projectedMessages];
|
|
3826
|
+
if (promptSegments.length > 0)
|
|
3827
|
+
requestMessages.unshift(this.messageFactory.createSystemMessage(promptSegments.join(`
|
|
3828
|
+
|
|
3829
|
+
`)));
|
|
3830
|
+
const budget = state.compactionOptions && state.compactionOptions.enabled !== false ? await estimateCompactionBudget(state.compactionOptions, {
|
|
3831
|
+
model: state.modelRef,
|
|
3832
|
+
messages: requestMessages,
|
|
3833
|
+
tools
|
|
3834
|
+
}) : undefined;
|
|
3835
|
+
return {
|
|
3836
|
+
messages: requestMessages,
|
|
3837
|
+
budget
|
|
3838
|
+
};
|
|
3839
|
+
}
|
|
3840
|
+
createHookContext(state, input = {}) {
|
|
3841
|
+
return {
|
|
3842
|
+
sessionId: state.id,
|
|
3843
|
+
requestId: input.requestId,
|
|
3844
|
+
iteration: input.iteration,
|
|
3845
|
+
cwd: state.cwd,
|
|
3846
|
+
metadata: state.metadata,
|
|
3847
|
+
status: createSessionStatus(state)
|
|
3848
|
+
};
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
// src/agent-core/termination-policy.ts
|
|
3853
|
+
class DefaultTerminationPolicy {
|
|
3854
|
+
afterAssistantTurn(toolCalls) {
|
|
3855
|
+
return toolCalls.length === 0 ? { type: "done" } : { type: "continue" };
|
|
3856
|
+
}
|
|
3857
|
+
onMaxIterationsExceeded(maxIterations, requestId) {
|
|
3858
|
+
return {
|
|
3859
|
+
code: "max_iterations_exceeded",
|
|
3860
|
+
message: `Session exceeded maxIterations (${maxIterations})`,
|
|
3861
|
+
requestId
|
|
3862
|
+
};
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
// src/agent-core/tool-executor.ts
|
|
3867
|
+
class DefaultToolExecutor {
|
|
3868
|
+
sessionId;
|
|
3869
|
+
tools;
|
|
3870
|
+
metadata;
|
|
3871
|
+
services;
|
|
3872
|
+
pluginHost;
|
|
3873
|
+
approvalManager;
|
|
3874
|
+
notificationBus;
|
|
3875
|
+
constructor(options) {
|
|
3876
|
+
this.sessionId = options.sessionId;
|
|
3877
|
+
this.tools = options.tools;
|
|
3878
|
+
this.metadata = options.metadata;
|
|
3879
|
+
this.services = options.services;
|
|
3880
|
+
this.pluginHost = options.pluginHost;
|
|
3881
|
+
this.approvalManager = options.approvalManager;
|
|
3882
|
+
this.notificationBus = options.notificationBus;
|
|
3883
|
+
}
|
|
3884
|
+
async execute(toolCall, options = {}) {
|
|
3885
|
+
const hookContext = {
|
|
3886
|
+
sessionId: this.sessionId,
|
|
3887
|
+
requestId: options.requestId,
|
|
3888
|
+
iteration: options.iteration,
|
|
3889
|
+
cwd: options.cwd,
|
|
3890
|
+
status: options.status,
|
|
3891
|
+
services: this.services,
|
|
3892
|
+
metadata: this.metadata
|
|
3893
|
+
};
|
|
3894
|
+
const before = await this.pluginHost?.runToolBeforeExecute({
|
|
3895
|
+
toolCall
|
|
3896
|
+
}, hookContext);
|
|
3897
|
+
let currentToolCall = before?.payload.toolCall ?? toolCall;
|
|
3898
|
+
let result;
|
|
3899
|
+
if (before?.control) {
|
|
3900
|
+
if (before.control.type === "provideResult") {
|
|
3901
|
+
result = normalizeToolResult(before.control.result);
|
|
3902
|
+
} else if (before.control.type === "deny") {
|
|
3903
|
+
result = normalizeToolResult(before.control.result ?? {
|
|
3904
|
+
...textToolResult(before.control.reason ?? `Tool denied: ${currentToolCall.function.name}`),
|
|
3905
|
+
isError: true
|
|
3906
|
+
});
|
|
3907
|
+
await this.notificationBus.emit({
|
|
3908
|
+
type: "tool.denied",
|
|
3909
|
+
source: "permission",
|
|
3910
|
+
level: "warning",
|
|
3911
|
+
message: before.control.reason ?? `Tool denied: ${currentToolCall.function.name}`,
|
|
3912
|
+
metadata: {
|
|
3913
|
+
toolName: currentToolCall.function.name
|
|
3914
|
+
}
|
|
3915
|
+
}, hookContext);
|
|
3916
|
+
} else if (before.control.type === "ask") {
|
|
3917
|
+
currentToolCall = before.control.toolCall ?? currentToolCall;
|
|
3918
|
+
const approvalRequest = {
|
|
3919
|
+
id: undefined,
|
|
3920
|
+
kind: "tool_execution",
|
|
3921
|
+
toolCall: currentToolCall,
|
|
3922
|
+
message: before.control.request?.message ?? `Approval required to execute tool: ${currentToolCall.function.name}`,
|
|
3923
|
+
metadata: before.control.request?.metadata
|
|
3924
|
+
};
|
|
3925
|
+
const decision = await this.approvalManager.requestApproval(approvalRequest, hookContext);
|
|
3926
|
+
if (decision.type === "denied") {
|
|
3927
|
+
result = normalizeToolResult({
|
|
3928
|
+
...textToolResult(decision.reason ?? `Tool approval denied: ${currentToolCall.function.name}`),
|
|
3929
|
+
isError: true
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
if (!result) {
|
|
3935
|
+
const tool = this.tools?.get(currentToolCall.function.name);
|
|
3936
|
+
if (!tool) {
|
|
3937
|
+
result = normalizeToolResult({ ...textToolResult(`Tool not found: ${currentToolCall.function.name}`), isError: true });
|
|
3938
|
+
} else {
|
|
3939
|
+
try {
|
|
3940
|
+
result = normalizeToolResult(await tool.execute(currentToolCall.function.arguments, {
|
|
3941
|
+
sessionId: this.sessionId,
|
|
3942
|
+
cwd: options.cwd,
|
|
3943
|
+
signal: options.signal,
|
|
3944
|
+
metadata: this.metadata,
|
|
3945
|
+
services: this.services,
|
|
3946
|
+
emitEvent: async (event) => {
|
|
3947
|
+
const pluginId = event.pluginId;
|
|
3948
|
+
if (!pluginId)
|
|
3949
|
+
throw new Error(`Tool ${currentToolCall.function.name} emitted a plugin event without pluginId`);
|
|
3950
|
+
await options.onEvent?.({
|
|
3951
|
+
type: "plugin_event",
|
|
3952
|
+
sessionId: this.sessionId,
|
|
3953
|
+
pluginId,
|
|
3954
|
+
event: event.event,
|
|
3955
|
+
data: event.data ? structuredClone(event.data) : undefined
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
}));
|
|
3959
|
+
} catch (error) {
|
|
3960
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3961
|
+
result = normalizeToolResult({
|
|
3962
|
+
...textToolResult(message),
|
|
3963
|
+
isError: true
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
const after = await this.pluginHost?.runMiddleware("tool.afterExecute", {
|
|
3969
|
+
toolCall: currentToolCall,
|
|
3970
|
+
result
|
|
3971
|
+
}, hookContext);
|
|
3972
|
+
const finalResult = normalizeToolResult(after?.result ?? result);
|
|
3973
|
+
await this.notificationBus.emit({
|
|
3974
|
+
type: finalResult.isError ? "tool.failed" : "tool.completed",
|
|
3975
|
+
source: "tool",
|
|
3976
|
+
level: finalResult.isError ? "error" : "info",
|
|
3977
|
+
message: finalResult.isError ? `Tool failed: ${currentToolCall.function.name}` : `Tool completed: ${currentToolCall.function.name}`,
|
|
3978
|
+
metadata: {
|
|
3979
|
+
toolName: currentToolCall.function.name,
|
|
3980
|
+
isError: finalResult.isError === true
|
|
3981
|
+
}
|
|
3982
|
+
}, hookContext);
|
|
3983
|
+
return finalResult;
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
|
|
3987
|
+
// src/agent-core/session.ts
|
|
3988
|
+
var DEFAULT_MESSAGE_QUEUE_CONFIG = {
|
|
3989
|
+
autoProcessQueue: true
|
|
3990
|
+
};
|
|
3991
|
+
|
|
3992
|
+
class Session {
|
|
3993
|
+
id;
|
|
3994
|
+
createdAt;
|
|
3995
|
+
stateStore;
|
|
3996
|
+
state;
|
|
3997
|
+
pluginHost;
|
|
3998
|
+
contextManager;
|
|
3999
|
+
services;
|
|
4000
|
+
approvalManager;
|
|
4001
|
+
notificationBus;
|
|
4002
|
+
compactionService;
|
|
4003
|
+
pluginStateService;
|
|
4004
|
+
pluginControllers;
|
|
4005
|
+
bufferedEvents = [];
|
|
4006
|
+
stateChangeWaiters = [];
|
|
4007
|
+
activeRunCount = 0;
|
|
4008
|
+
processingPromise = null;
|
|
4009
|
+
receiveActive = false;
|
|
4010
|
+
activeReceiveSignal;
|
|
4011
|
+
haltProcessingUntilReceive = false;
|
|
4012
|
+
saveChain = Promise.resolve();
|
|
4013
|
+
constructor(options) {
|
|
4014
|
+
const now = Date.now();
|
|
4015
|
+
this.id = options.id ?? createId("session");
|
|
4016
|
+
this.createdAt = options.createdAt ?? now;
|
|
4017
|
+
this.stateStore = options.stateStore;
|
|
4018
|
+
this.pluginHost = options.pluginHost;
|
|
4019
|
+
this.contextManager = options.contextManager;
|
|
4020
|
+
this.services = options.services;
|
|
4021
|
+
this.approvalManager = options.approvalManager;
|
|
4022
|
+
this.notificationBus = options.notificationBus;
|
|
4023
|
+
this.compactionService = options.compactionService;
|
|
4024
|
+
this.pluginStateService = options.pluginStateService;
|
|
4025
|
+
this.state = {
|
|
4026
|
+
id: this.id,
|
|
4027
|
+
model: options.model,
|
|
4028
|
+
modelRef: options.modelRef ?? options.model.model,
|
|
4029
|
+
tools: options.tools,
|
|
4030
|
+
maxIterations: options.maxIterations ?? 8,
|
|
4031
|
+
cwd: options.cwd,
|
|
4032
|
+
metadata: options.metadata,
|
|
4033
|
+
reasoning: options.reasoning,
|
|
4034
|
+
messages: [...options.messages ?? []],
|
|
4035
|
+
usage: options.usage ? { ...options.usage } : createEmptyUsage(),
|
|
4036
|
+
compaction: cloneCompactionState(options.compaction ?? createEmptyCompactionState()),
|
|
4037
|
+
pluginState: clonePluginStateMap(options.pluginState),
|
|
4038
|
+
messageQueue: cloneMessageQueueSnapshot(options.messageQueue ?? {
|
|
4039
|
+
items: [],
|
|
4040
|
+
config: createMessageQueueConfig(options.messageQueueConfig)
|
|
4041
|
+
}),
|
|
4042
|
+
compactionOptions: options.compactionOptions,
|
|
4043
|
+
createdAt: this.createdAt,
|
|
4044
|
+
updatedAt: options.updatedAt ?? now
|
|
4045
|
+
};
|
|
4046
|
+
if (options.messageQueueConfig)
|
|
4047
|
+
this.state.messageQueue.config = createMessageQueueConfig(options.messageQueueConfig, this.state.messageQueue.config);
|
|
4048
|
+
this.compactionService.registerSession(this.state);
|
|
4049
|
+
this.pluginStateService.registerSession(this.state);
|
|
4050
|
+
this.pluginControllers = this.pluginHost?.createSessionControllers({
|
|
4051
|
+
sessionId: this.id,
|
|
4052
|
+
metadata: this.state.metadata,
|
|
4053
|
+
getStatus: () => this.getStatus(),
|
|
4054
|
+
save: () => this.save(),
|
|
4055
|
+
isRunning: () => this.activeRunCount > 0
|
|
4056
|
+
}) ?? new Map;
|
|
4057
|
+
}
|
|
4058
|
+
get messages() {
|
|
4059
|
+
return [...this.state.messages];
|
|
4060
|
+
}
|
|
4061
|
+
get usage() {
|
|
4062
|
+
return { ...this.state.usage };
|
|
4063
|
+
}
|
|
4064
|
+
getStatus() {
|
|
4065
|
+
return createSessionStatus(this.state);
|
|
4066
|
+
}
|
|
4067
|
+
getCompactionState() {
|
|
4068
|
+
return cloneCompactionState(this.state.compaction);
|
|
4069
|
+
}
|
|
4070
|
+
getPluginState(pluginId) {
|
|
4071
|
+
return clonePluginSessionStateEntry(this.state.pluginState[pluginId]);
|
|
4072
|
+
}
|
|
4073
|
+
listPluginStates() {
|
|
4074
|
+
return clonePluginStateMap(this.state.pluginState);
|
|
4075
|
+
}
|
|
4076
|
+
getPlugin(pluginId) {
|
|
4077
|
+
return this.pluginControllers.get(pluginId) ?? null;
|
|
4078
|
+
}
|
|
4079
|
+
get updatedAt() {
|
|
4080
|
+
return this.state.updatedAt;
|
|
4081
|
+
}
|
|
4082
|
+
getCwd() {
|
|
4083
|
+
return this.state.cwd;
|
|
4084
|
+
}
|
|
4085
|
+
setCwd(cwd) {
|
|
4086
|
+
this.state.cwd = cwd;
|
|
4087
|
+
touchRuntimeSessionState(this.state);
|
|
4088
|
+
}
|
|
4089
|
+
send(input, options = {}) {
|
|
4090
|
+
return this.enqueueItem("user", input, options);
|
|
4091
|
+
}
|
|
4092
|
+
sendBatch(items) {
|
|
4093
|
+
const maxQueueSize = this.state.messageQueue.config.maxQueueSize;
|
|
4094
|
+
if (maxQueueSize !== undefined && this.state.messageQueue.items.length + items.length > maxQueueSize)
|
|
4095
|
+
throw new SessionExecutionError(createQueueError("DIM_QUEUE_FULL", `Session queue is full (max ${maxQueueSize})`));
|
|
4096
|
+
const prepared = items.map(({ input, options }) => prepareQueueItem("user", input, options));
|
|
4097
|
+
for (const item of prepared)
|
|
4098
|
+
this.state.messageQueue.items.push(item);
|
|
4099
|
+
if (prepared.length > 0)
|
|
4100
|
+
this.onQueueMutated();
|
|
4101
|
+
return prepared.map((item) => item.id);
|
|
4102
|
+
}
|
|
4103
|
+
steer(input, options = {}) {
|
|
4104
|
+
return this.enqueueItem("steer", input, options);
|
|
4105
|
+
}
|
|
4106
|
+
cancelQueuedItem(itemId) {
|
|
4107
|
+
const index = this.state.messageQueue.items.findIndex((item) => item.id === itemId);
|
|
4108
|
+
if (index < 0)
|
|
4109
|
+
return false;
|
|
4110
|
+
this.state.messageQueue.items.splice(index, 1);
|
|
4111
|
+
this.onQueueMutated();
|
|
4112
|
+
return true;
|
|
4113
|
+
}
|
|
4114
|
+
getQueueStatus() {
|
|
4115
|
+
const items = [...this.state.messageQueue.items].sort(compareQueueItems).map((item) => ({
|
|
4116
|
+
id: item.id,
|
|
4117
|
+
kind: item.kind,
|
|
4118
|
+
priority: item.priority,
|
|
4119
|
+
enqueuedAt: item.enqueuedAt,
|
|
4120
|
+
preview: createQueuePreview(item.input)
|
|
4121
|
+
}));
|
|
4122
|
+
return {
|
|
4123
|
+
length: items.length,
|
|
4124
|
+
items,
|
|
4125
|
+
isProcessing: this.processingPromise !== null,
|
|
4126
|
+
config: cloneMessageQueueConfig(this.state.messageQueue.config)
|
|
4127
|
+
};
|
|
4128
|
+
}
|
|
4129
|
+
clearQueue(filter = {}) {
|
|
4130
|
+
const before = this.state.messageQueue.items.length;
|
|
4131
|
+
if (!filter.kind) {
|
|
4132
|
+
this.state.messageQueue.items = [];
|
|
4133
|
+
} else {
|
|
4134
|
+
this.state.messageQueue.items = this.state.messageQueue.items.filter((item) => item.kind !== filter.kind);
|
|
4135
|
+
}
|
|
4136
|
+
const cleared = before - this.state.messageQueue.items.length;
|
|
4137
|
+
if (cleared > 0)
|
|
4138
|
+
this.onQueueMutated();
|
|
4139
|
+
return cleared;
|
|
4140
|
+
}
|
|
4141
|
+
async* receive(options = {}) {
|
|
4142
|
+
if (this.receiveActive)
|
|
4143
|
+
throw new SessionExecutionError(createQueueError("DIM_QUEUE_BUSY", "Only one receive() call can be active for a session"));
|
|
4144
|
+
this.receiveActive = true;
|
|
4145
|
+
this.activeReceiveSignal = options.signal;
|
|
4146
|
+
this.haltProcessingUntilReceive = false;
|
|
4147
|
+
let deliveredEvent = null;
|
|
4148
|
+
try {
|
|
4149
|
+
while (true) {
|
|
4150
|
+
if (deliveredEvent) {
|
|
4151
|
+
deliveredEvent.ack();
|
|
4152
|
+
deliveredEvent = null;
|
|
4153
|
+
}
|
|
4154
|
+
this.ensureQueueProcessing("receive");
|
|
4155
|
+
const nextBufferedEvent = this.bufferedEvents.shift();
|
|
4156
|
+
if (nextBufferedEvent) {
|
|
4157
|
+
deliveredEvent = nextBufferedEvent;
|
|
4158
|
+
yield nextBufferedEvent.event;
|
|
4159
|
+
continue;
|
|
4160
|
+
}
|
|
4161
|
+
if (!this.processingPromise && !this.hasRunnableItems())
|
|
4162
|
+
return;
|
|
4163
|
+
await this.waitForStateChange(options.signal);
|
|
4164
|
+
}
|
|
4165
|
+
} finally {
|
|
4166
|
+
if (deliveredEvent)
|
|
4167
|
+
deliveredEvent.ack();
|
|
4168
|
+
this.receiveActive = false;
|
|
4169
|
+
if (this.activeReceiveSignal === options.signal)
|
|
4170
|
+
this.activeReceiveSignal = undefined;
|
|
4171
|
+
this.notifyStateChange();
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
async compact(update) {
|
|
4175
|
+
const beforeState = this.getCompactionState();
|
|
4176
|
+
const estimatedInputTokens = await this.readEstimatedInputTokens();
|
|
4177
|
+
const thresholdTokens = this.state.compactionOptions?.triggerTokens ?? (this.state.compactionOptions ? Math.max(1, Math.floor(this.state.compactionOptions.maxInputTokens * (this.state.compactionOptions.triggerRatio ?? 0.8))) : 0);
|
|
4178
|
+
const maxInputTokens = this.state.compactionOptions?.maxInputTokens ?? 0;
|
|
4179
|
+
const payload = {
|
|
4180
|
+
sessionId: this.id,
|
|
4181
|
+
messages: this.messages,
|
|
4182
|
+
cursor: beforeState.cursor,
|
|
4183
|
+
systemSegments: [...beforeState.systemSegments],
|
|
4184
|
+
contextItems: undefined,
|
|
4185
|
+
estimatedInputTokens,
|
|
4186
|
+
thresholdTokens,
|
|
4187
|
+
maxInputTokens,
|
|
4188
|
+
trigger: "manual"
|
|
4189
|
+
};
|
|
4190
|
+
if (this.pluginHost) {
|
|
4191
|
+
const compactPayload = await this.pluginHost.runMiddleware("context.compact.before", payload, {
|
|
4192
|
+
...this.createHookContext(),
|
|
4193
|
+
services: this.services
|
|
4194
|
+
});
|
|
4195
|
+
await this.pluginHost.runObservers("context.compact.before", compactPayload, {
|
|
4196
|
+
...this.createHookContext(),
|
|
4197
|
+
services: this.services
|
|
4198
|
+
});
|
|
4199
|
+
}
|
|
4200
|
+
await this.compactionService.apply({
|
|
4201
|
+
sessionId: this.id,
|
|
4202
|
+
...update,
|
|
4203
|
+
reason: update.reason ?? "manual"
|
|
4204
|
+
});
|
|
4205
|
+
const afterState = this.getCompactionState();
|
|
4206
|
+
const latestCheckpoint = afterState.checkpoints.at(-1);
|
|
4207
|
+
await this.notificationBus.emit({
|
|
4208
|
+
type: "context.compacted",
|
|
4209
|
+
source: "runtime",
|
|
4210
|
+
level: "info",
|
|
4211
|
+
message: `Context compacted for session ${this.id}`,
|
|
4212
|
+
metadata: {
|
|
4213
|
+
cursor: afterState.cursor,
|
|
4214
|
+
checkpointId: latestCheckpoint?.id,
|
|
4215
|
+
reason: latestCheckpoint?.reason
|
|
4216
|
+
}
|
|
4217
|
+
}, {
|
|
4218
|
+
...this.createHookContext()
|
|
4219
|
+
});
|
|
4220
|
+
}
|
|
4221
|
+
async save() {
|
|
4222
|
+
return this.enqueueSave();
|
|
4223
|
+
}
|
|
4224
|
+
async writeSnapshot() {
|
|
4225
|
+
if (!this.stateStore)
|
|
4226
|
+
return;
|
|
4227
|
+
let snapshot = this.toSnapshot();
|
|
4228
|
+
if (this.pluginHost) {
|
|
4229
|
+
snapshot = await this.pluginHost.runMiddleware("snapshot.encode", { snapshot }, {
|
|
4230
|
+
...this.createHookContext(),
|
|
4231
|
+
services: this.services
|
|
4232
|
+
}).then((payload) => payload.snapshot);
|
|
4233
|
+
}
|
|
4234
|
+
await this.stateStore.save(snapshot);
|
|
4235
|
+
await this.notificationBus.emit({
|
|
4236
|
+
type: "snapshot.saved",
|
|
4237
|
+
source: "persistence",
|
|
4238
|
+
level: "info",
|
|
4239
|
+
message: `Saved snapshot for session ${this.id}`,
|
|
4240
|
+
metadata: {
|
|
4241
|
+
sessionId: this.id,
|
|
4242
|
+
schemaVersion: snapshot.schemaVersion
|
|
4243
|
+
}
|
|
4244
|
+
}, {
|
|
4245
|
+
...this.createHookContext()
|
|
4246
|
+
});
|
|
4247
|
+
}
|
|
4248
|
+
toSnapshot() {
|
|
4249
|
+
return sessionSnapshotCodec.encode({
|
|
4250
|
+
sessionId: this.id,
|
|
4251
|
+
model: this.state.modelRef,
|
|
4252
|
+
cwd: this.state.cwd,
|
|
4253
|
+
messages: this.messages,
|
|
4254
|
+
usage: this.usage,
|
|
4255
|
+
compaction: this.getCompactionState(),
|
|
4256
|
+
pluginState: this.listPluginStates(),
|
|
4257
|
+
createdAt: this.createdAt,
|
|
4258
|
+
updatedAt: this.updatedAt,
|
|
4259
|
+
metadata: this.state.metadata ? { ...this.state.metadata } : undefined,
|
|
4260
|
+
messageQueue: cloneMessageQueueSnapshot(this.state.messageQueue)
|
|
4261
|
+
});
|
|
4262
|
+
}
|
|
4263
|
+
enqueueItem(kind, input, options) {
|
|
4264
|
+
const maxQueueSize = this.state.messageQueue.config.maxQueueSize;
|
|
4265
|
+
if (maxQueueSize !== undefined && this.state.messageQueue.items.length >= maxQueueSize)
|
|
4266
|
+
throw new SessionExecutionError(createQueueError("DIM_QUEUE_FULL", `Session queue is full (max ${maxQueueSize})`));
|
|
4267
|
+
const item = prepareQueueItem(kind, input, options);
|
|
4268
|
+
this.state.messageQueue.items.push(item);
|
|
4269
|
+
this.onQueueMutated();
|
|
4270
|
+
return item.id;
|
|
4271
|
+
}
|
|
4272
|
+
onQueueMutated() {
|
|
4273
|
+
touchRuntimeSessionState(this.state);
|
|
4274
|
+
this.notifyStateChange();
|
|
4275
|
+
this.scheduleSave();
|
|
4276
|
+
this.ensureQueueProcessing("auto");
|
|
4277
|
+
}
|
|
4278
|
+
hasRunnableItems() {
|
|
4279
|
+
return this.state.messageQueue.items.some((item) => item.kind === "user");
|
|
4280
|
+
}
|
|
4281
|
+
ensureQueueProcessing(source) {
|
|
4282
|
+
if (this.processingPromise)
|
|
4283
|
+
return;
|
|
4284
|
+
if (!this.hasRunnableItems())
|
|
4285
|
+
return;
|
|
4286
|
+
if (this.haltProcessingUntilReceive && source !== "receive")
|
|
4287
|
+
return;
|
|
4288
|
+
const signal = source === "receive" ? this.activeReceiveSignal : undefined;
|
|
4289
|
+
this.processingPromise = this.processQueue(signal).finally(() => {
|
|
4290
|
+
this.processingPromise = null;
|
|
4291
|
+
this.notifyStateChange();
|
|
4292
|
+
if (!this.haltProcessingUntilReceive && this.state.messageQueue.config.autoProcessQueue)
|
|
4293
|
+
this.ensureQueueProcessing("auto");
|
|
4294
|
+
});
|
|
4295
|
+
this.notifyStateChange();
|
|
4296
|
+
}
|
|
4297
|
+
async processQueue(signal) {
|
|
4298
|
+
while (true) {
|
|
4299
|
+
const nextItem = this.dequeueNextRunnableItem();
|
|
4300
|
+
if (!nextItem)
|
|
4301
|
+
return;
|
|
4302
|
+
const completed = await this.processQueuedItem(nextItem, signal);
|
|
4303
|
+
await this.persistStateQuietly();
|
|
4304
|
+
if (!completed) {
|
|
4305
|
+
this.haltProcessingUntilReceive = true;
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
dequeueNextRunnableItem() {
|
|
4311
|
+
const rankedItems = [...this.state.messageQueue.items].sort(compareQueueItems);
|
|
4312
|
+
const userIndex = rankedItems.findIndex((item) => item.kind === "user");
|
|
4313
|
+
if (userIndex < 0)
|
|
4314
|
+
return null;
|
|
4315
|
+
const userItem = rankedItems[userIndex];
|
|
4316
|
+
const steerItems = rankedItems.slice(0, userIndex).filter((item) => item.kind === "steer");
|
|
4317
|
+
const consumedIds = new Set([userItem.id, ...steerItems.map((item) => item.id)]);
|
|
4318
|
+
this.state.messageQueue.items = this.state.messageQueue.items.filter((item) => !consumedIds.has(item.id));
|
|
4319
|
+
touchRuntimeSessionState(this.state);
|
|
4320
|
+
this.notifyStateChange();
|
|
4321
|
+
return {
|
|
4322
|
+
item: userItem,
|
|
4323
|
+
steerSegments: steerItems.map((item) => contentToText(normalizeContent(item.input)).trim()).filter((segment) => segment.length > 0)
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
async processQueuedItem(input, signal) {
|
|
4327
|
+
const runner = this.createLoopRunner();
|
|
4328
|
+
let emittedError = false;
|
|
4329
|
+
this.activeRunCount += 1;
|
|
4330
|
+
try {
|
|
4331
|
+
const iterator = runner.run(this.state, input.item.input, {
|
|
4332
|
+
signal,
|
|
4333
|
+
persistentPromptSegments: input.steerSegments
|
|
4334
|
+
});
|
|
4335
|
+
while (true) {
|
|
4336
|
+
const next = await iterator.next();
|
|
4337
|
+
if (next.done)
|
|
4338
|
+
return true;
|
|
4339
|
+
const queuedEvent = this.attachQueueContext(next.value, input.item);
|
|
4340
|
+
if (queuedEvent.type === "error")
|
|
4341
|
+
emittedError = true;
|
|
4342
|
+
await this.pushBufferedEvent(queuedEvent);
|
|
4343
|
+
}
|
|
4344
|
+
} catch (error) {
|
|
4345
|
+
if (!emittedError)
|
|
4346
|
+
await this.pushBufferedEvent(this.createErrorEvent(input.item, error));
|
|
4347
|
+
return false;
|
|
4348
|
+
} finally {
|
|
4349
|
+
this.activeRunCount = Math.max(0, this.activeRunCount - 1);
|
|
4350
|
+
this.notifyStateChange();
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
attachQueueContext(event, item) {
|
|
4354
|
+
return {
|
|
4355
|
+
...event,
|
|
4356
|
+
itemId: item.id,
|
|
4357
|
+
itemKind: item.kind,
|
|
4358
|
+
priority: item.priority
|
|
4359
|
+
};
|
|
4360
|
+
}
|
|
4361
|
+
createErrorEvent(item, error) {
|
|
4362
|
+
const payload = error instanceof SessionExecutionError ? error.payload : {
|
|
4363
|
+
code: "runtime_error",
|
|
4364
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4365
|
+
};
|
|
4366
|
+
return {
|
|
4367
|
+
type: "error",
|
|
4368
|
+
sessionId: this.id,
|
|
4369
|
+
error: payload,
|
|
4370
|
+
itemId: item.id,
|
|
4371
|
+
itemKind: item.kind,
|
|
4372
|
+
priority: item.priority
|
|
4373
|
+
};
|
|
4374
|
+
}
|
|
4375
|
+
async pushBufferedEvent(event) {
|
|
4376
|
+
let resolved = false;
|
|
4377
|
+
const ack = () => {
|
|
4378
|
+
if (resolved)
|
|
4379
|
+
return;
|
|
4380
|
+
resolved = true;
|
|
4381
|
+
this.notifyStateChange();
|
|
4382
|
+
};
|
|
4383
|
+
this.bufferedEvents.push({ event, ack });
|
|
4384
|
+
this.notifyStateChange();
|
|
4385
|
+
while (!resolved)
|
|
4386
|
+
await this.waitForStateChange();
|
|
4387
|
+
}
|
|
4388
|
+
scheduleSave() {
|
|
4389
|
+
if (!this.stateStore)
|
|
4390
|
+
return;
|
|
4391
|
+
this.enqueueSave().catch(() => {
|
|
4392
|
+
return;
|
|
4393
|
+
});
|
|
4394
|
+
}
|
|
4395
|
+
async persistStateQuietly() {
|
|
4396
|
+
try {
|
|
4397
|
+
await this.enqueueSave();
|
|
4398
|
+
} catch {}
|
|
4399
|
+
}
|
|
4400
|
+
enqueueSave() {
|
|
4401
|
+
if (!this.stateStore)
|
|
4402
|
+
return Promise.resolve();
|
|
4403
|
+
this.saveChain = this.saveChain.catch(() => {
|
|
4404
|
+
return;
|
|
4405
|
+
}).then(() => this.writeSnapshot());
|
|
4406
|
+
return this.saveChain;
|
|
4407
|
+
}
|
|
4408
|
+
waitForStateChange(signal) {
|
|
4409
|
+
if (signal?.aborted)
|
|
4410
|
+
return Promise.reject(new SessionExecutionError(createQueueError("session_receive_aborted", "Session receive aborted")));
|
|
4411
|
+
return new Promise((resolve, reject) => {
|
|
4412
|
+
const onAbort = () => {
|
|
4413
|
+
cleanup();
|
|
4414
|
+
reject(new SessionExecutionError(createQueueError("session_receive_aborted", "Session receive aborted")));
|
|
4415
|
+
};
|
|
4416
|
+
const resume = () => {
|
|
4417
|
+
cleanup();
|
|
4418
|
+
resolve();
|
|
4419
|
+
};
|
|
4420
|
+
const cleanup = () => {
|
|
4421
|
+
const index = this.stateChangeWaiters.indexOf(resume);
|
|
4422
|
+
if (index >= 0)
|
|
4423
|
+
this.stateChangeWaiters.splice(index, 1);
|
|
4424
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4425
|
+
};
|
|
4426
|
+
this.stateChangeWaiters.push(resume);
|
|
4427
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4428
|
+
});
|
|
4429
|
+
}
|
|
4430
|
+
notifyStateChange() {
|
|
4431
|
+
while (this.stateChangeWaiters.length > 0)
|
|
4432
|
+
this.stateChangeWaiters.shift()?.();
|
|
4433
|
+
}
|
|
4434
|
+
createLoopRunner() {
|
|
4435
|
+
return new LoopRunner({
|
|
4436
|
+
messageFactory: new DefaultMessageFactory,
|
|
4437
|
+
turnCollector: new DefaultModelTurnCollector,
|
|
4438
|
+
toolExecutor: new DefaultToolExecutor({
|
|
4439
|
+
sessionId: this.id,
|
|
4440
|
+
tools: this.state.tools,
|
|
4441
|
+
metadata: this.state.metadata,
|
|
4442
|
+
services: this.services,
|
|
4443
|
+
pluginHost: this.pluginHost,
|
|
4444
|
+
approvalManager: this.approvalManager,
|
|
4445
|
+
notificationBus: this.notificationBus
|
|
4446
|
+
}),
|
|
4447
|
+
terminationPolicy: new DefaultTerminationPolicy,
|
|
4448
|
+
contextManager: this.contextManager,
|
|
4449
|
+
pluginHost: this.pluginHost,
|
|
4450
|
+
notificationBus: this.notificationBus
|
|
4451
|
+
});
|
|
4452
|
+
}
|
|
4453
|
+
async readEstimatedInputTokens() {
|
|
4454
|
+
if (!this.state.compactionOptions || this.state.compactionOptions.enabled === false)
|
|
4455
|
+
return 0;
|
|
4456
|
+
const projectedMessages = projectMessagesForRequest(this.state.messages, this.state.compaction);
|
|
4457
|
+
const promptSegments = buildCompactionPromptSegments(this.state.compaction);
|
|
4458
|
+
const requestMessages = [...projectedMessages];
|
|
4459
|
+
if (promptSegments.length > 0)
|
|
4460
|
+
requestMessages.unshift(new DefaultMessageFactory().createSystemMessage(promptSegments.join(`
|
|
4461
|
+
|
|
4462
|
+
`)));
|
|
4463
|
+
const budget = await estimateCompactionBudget(this.state.compactionOptions, {
|
|
4464
|
+
model: this.state.modelRef,
|
|
4465
|
+
messages: requestMessages,
|
|
4466
|
+
tools: this.state.tools?.definitions() ?? []
|
|
4467
|
+
});
|
|
4468
|
+
return budget.estimatedInputTokens;
|
|
4469
|
+
}
|
|
4470
|
+
createHookContext(input = {}) {
|
|
4471
|
+
return {
|
|
4472
|
+
sessionId: this.id,
|
|
4473
|
+
requestId: input.requestId,
|
|
4474
|
+
iteration: input.iteration,
|
|
4475
|
+
cwd: this.state.cwd,
|
|
4476
|
+
metadata: this.state.metadata,
|
|
4477
|
+
status: this.getStatus()
|
|
4478
|
+
};
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
function createMessageQueueConfig(overrides, base = DEFAULT_MESSAGE_QUEUE_CONFIG) {
|
|
4482
|
+
return {
|
|
4483
|
+
autoProcessQueue: overrides?.autoProcessQueue ?? base.autoProcessQueue,
|
|
4484
|
+
maxQueueSize: overrides?.maxQueueSize ?? base.maxQueueSize
|
|
4485
|
+
};
|
|
4486
|
+
}
|
|
4487
|
+
function cloneMessageQueueConfig(config) {
|
|
4488
|
+
return {
|
|
4489
|
+
autoProcessQueue: config.autoProcessQueue,
|
|
4490
|
+
maxQueueSize: config.maxQueueSize
|
|
4491
|
+
};
|
|
4492
|
+
}
|
|
4493
|
+
function clonePendingMessage(item) {
|
|
4494
|
+
return {
|
|
4495
|
+
id: item.id,
|
|
4496
|
+
input: structuredClone(item.input),
|
|
4497
|
+
kind: item.kind,
|
|
4498
|
+
priority: item.priority,
|
|
4499
|
+
enqueuedAt: item.enqueuedAt
|
|
4500
|
+
};
|
|
4501
|
+
}
|
|
4502
|
+
function cloneMessageQueueSnapshot(snapshot) {
|
|
4503
|
+
return {
|
|
4504
|
+
items: snapshot.items.map(clonePendingMessage),
|
|
4505
|
+
config: cloneMessageQueueConfig(snapshot.config)
|
|
4506
|
+
};
|
|
4507
|
+
}
|
|
4508
|
+
function prepareQueueItem(kind, input, options = {}) {
|
|
4509
|
+
const normalizedInput = normalizeContent(input);
|
|
4510
|
+
if (kind === "steer" && contentToText(normalizedInput).trim().length === 0)
|
|
4511
|
+
throw new TypeError("Steer content must contain text");
|
|
4512
|
+
return {
|
|
4513
|
+
id: createId("item"),
|
|
4514
|
+
input: structuredClone(input),
|
|
4515
|
+
kind,
|
|
4516
|
+
priority: options.priority ?? 0,
|
|
4517
|
+
enqueuedAt: Date.now()
|
|
4518
|
+
};
|
|
4519
|
+
}
|
|
4520
|
+
function compareQueueItems(left, right) {
|
|
4521
|
+
if (left.priority !== right.priority)
|
|
4522
|
+
return right.priority - left.priority;
|
|
4523
|
+
if (left.enqueuedAt !== right.enqueuedAt)
|
|
4524
|
+
return left.enqueuedAt - right.enqueuedAt;
|
|
4525
|
+
return left.id.localeCompare(right.id);
|
|
4526
|
+
}
|
|
4527
|
+
function createQueuePreview(input) {
|
|
4528
|
+
const text = contentToText(normalizeContent(input)).trim();
|
|
4529
|
+
if (text.length === 0)
|
|
4530
|
+
return "[non-text content]";
|
|
4531
|
+
if (text.length <= 80)
|
|
4532
|
+
return text;
|
|
4533
|
+
return `${text.slice(0, 77)}...`;
|
|
4534
|
+
}
|
|
4535
|
+
function createQueueError(code, message) {
|
|
4536
|
+
return { code, message };
|
|
4537
|
+
}
|
|
4538
|
+
|
|
4539
|
+
// src/agent-core/agent.ts
|
|
4540
|
+
class Agent {
|
|
4541
|
+
id;
|
|
4542
|
+
pluginHost;
|
|
4543
|
+
services;
|
|
4544
|
+
tools;
|
|
4545
|
+
permissions;
|
|
4546
|
+
options;
|
|
4547
|
+
messageFactory = new DefaultMessageFactory;
|
|
4548
|
+
contextManager;
|
|
4549
|
+
tracker;
|
|
4550
|
+
permissionGateway;
|
|
4551
|
+
approvalManager;
|
|
4552
|
+
notificationBus;
|
|
4553
|
+
compactionService;
|
|
4554
|
+
pluginStateService;
|
|
4555
|
+
constructor(options) {
|
|
4556
|
+
this.id = createId("agent");
|
|
4557
|
+
this.options = options;
|
|
4558
|
+
this.permissions = options.permissions;
|
|
4559
|
+
validateCompactionOwner(options);
|
|
4560
|
+
this.tracker = new ActivityTracker;
|
|
4561
|
+
this.compactionService = new DefaultCompactionService;
|
|
4562
|
+
this.pluginStateService = new DefaultPluginStateService;
|
|
4563
|
+
this.services = createServices(options, this.tracker, this.compactionService, this.pluginStateService);
|
|
4564
|
+
this.permissionGateway = new PermissionGateway({
|
|
4565
|
+
permissions: options.permissions,
|
|
4566
|
+
compactionOwnerPluginId: options.compaction?.ownerPluginId
|
|
4567
|
+
});
|
|
4568
|
+
this.pluginHost = new PluginHost({
|
|
4569
|
+
agentId: this.id,
|
|
4570
|
+
cwd: options.cwd,
|
|
4571
|
+
plugins: options.plugins,
|
|
4572
|
+
services: this.services,
|
|
4573
|
+
permissionGateway: this.permissionGateway,
|
|
4574
|
+
tracker: this.tracker
|
|
4575
|
+
});
|
|
4576
|
+
this.notificationBus = new DefaultNotificationBus({
|
|
4577
|
+
pluginHost: this.pluginHost,
|
|
4578
|
+
services: this.services
|
|
4579
|
+
});
|
|
4580
|
+
this.approvalManager = new DefaultApprovalManager({
|
|
4581
|
+
handler: options.approvalHandler,
|
|
4582
|
+
notifications: this.notificationBus
|
|
4583
|
+
});
|
|
4584
|
+
this.contextManager = options.contextManager ?? new AutoContextManager({
|
|
4585
|
+
services: this.services,
|
|
4586
|
+
pluginHost: this.pluginHost
|
|
4587
|
+
});
|
|
4588
|
+
this.tools = createToolRegistry({
|
|
4589
|
+
includeBuiltinTools: options.includeBuiltinTools !== false,
|
|
4590
|
+
tools: options.tools,
|
|
4591
|
+
pluginTools: this.pluginHost.pluginTools()
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
4594
|
+
async createSession(options = {}) {
|
|
4595
|
+
const messages = options.messages ? [...options.messages] : [];
|
|
4596
|
+
if (options.systemPrompt)
|
|
4597
|
+
messages.unshift(this.messageFactory.createSystemMessage(options.systemPrompt));
|
|
4598
|
+
return new Session({
|
|
4599
|
+
id: options.sessionId ?? createId("session"),
|
|
4600
|
+
model: this.options.model,
|
|
4601
|
+
modelRef: this.options.model.model,
|
|
4602
|
+
tools: this.tools,
|
|
4603
|
+
stateStore: this.options.stateStore,
|
|
4604
|
+
maxIterations: this.options.maxIterations,
|
|
4605
|
+
cwd: options.cwd ?? this.options.cwd,
|
|
4606
|
+
metadata: options.metadata ?? this.options.metadata,
|
|
4607
|
+
reasoning: this.options.reasoning,
|
|
4608
|
+
compactionOptions: this.options.compaction,
|
|
4609
|
+
messageQueueConfig: options.messageQueueConfig,
|
|
4610
|
+
messages,
|
|
4611
|
+
pluginHost: this.pluginHost,
|
|
4612
|
+
contextManager: this.contextManager,
|
|
4613
|
+
services: this.services,
|
|
4614
|
+
approvalManager: this.approvalManager,
|
|
4615
|
+
notificationBus: this.notificationBus,
|
|
4616
|
+
compactionService: this.compactionService,
|
|
4617
|
+
pluginStateService: this.pluginStateService
|
|
4618
|
+
});
|
|
4619
|
+
}
|
|
4620
|
+
async restoreSession(sessionId) {
|
|
4621
|
+
if (!this.options.stateStore)
|
|
4622
|
+
return null;
|
|
4623
|
+
const snapshot = await this.options.stateStore.load(sessionId);
|
|
4624
|
+
if (!snapshot)
|
|
4625
|
+
return null;
|
|
4626
|
+
return this.sessionFromSnapshot(snapshot);
|
|
4627
|
+
}
|
|
4628
|
+
sessionFromSnapshot(snapshot) {
|
|
4629
|
+
return new Session({
|
|
4630
|
+
id: snapshot.sessionId,
|
|
4631
|
+
model: this.options.model,
|
|
4632
|
+
modelRef: snapshot.model ?? this.options.model.model,
|
|
4633
|
+
tools: this.tools,
|
|
4634
|
+
stateStore: this.options.stateStore,
|
|
4635
|
+
maxIterations: this.options.maxIterations,
|
|
4636
|
+
cwd: snapshot.cwd ?? this.options.cwd,
|
|
4637
|
+
metadata: snapshot.metadata ?? this.options.metadata,
|
|
4638
|
+
reasoning: this.options.reasoning,
|
|
4639
|
+
messages: snapshot.messages,
|
|
4640
|
+
usage: snapshot.usage,
|
|
4641
|
+
compaction: snapshot.compaction,
|
|
4642
|
+
pluginState: snapshot.pluginState,
|
|
4643
|
+
messageQueue: snapshot.messageQueue,
|
|
4644
|
+
compactionOptions: this.options.compaction,
|
|
4645
|
+
createdAt: snapshot.createdAt,
|
|
4646
|
+
updatedAt: snapshot.updatedAt,
|
|
4647
|
+
pluginHost: this.pluginHost,
|
|
4648
|
+
contextManager: this.contextManager,
|
|
4649
|
+
services: this.services,
|
|
4650
|
+
approvalManager: this.approvalManager,
|
|
4651
|
+
notificationBus: this.notificationBus,
|
|
4652
|
+
compactionService: this.compactionService,
|
|
4653
|
+
pluginStateService: this.pluginStateService
|
|
4654
|
+
});
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4657
|
+
function createAgent(options) {
|
|
4658
|
+
return new Agent(options);
|
|
4659
|
+
}
|
|
4660
|
+
function createServices(options, tracker, compaction, pluginState) {
|
|
4661
|
+
const exec = new NodeExecGateway({ cwd: options.cwd, tracker });
|
|
4662
|
+
return {
|
|
4663
|
+
fileSystem: new NodeFileSystemGateway({ cwd: options.cwd, tracker }),
|
|
4664
|
+
exec,
|
|
4665
|
+
git: new DefaultGitGateway({ exec, cwd: options.cwd }),
|
|
4666
|
+
network: new DefaultNetworkGateway,
|
|
4667
|
+
model: new DefaultModelGateway(options.model),
|
|
4668
|
+
compaction,
|
|
4669
|
+
pluginState
|
|
4670
|
+
};
|
|
4671
|
+
}
|
|
4672
|
+
function validateCompactionOwner(options) {
|
|
4673
|
+
const ownerPluginId = options.compaction?.ownerPluginId;
|
|
4674
|
+
if (!ownerPluginId)
|
|
4675
|
+
return;
|
|
4676
|
+
const loadedPluginIds = new Set((options.plugins ?? []).map((plugin) => plugin.manifest.id));
|
|
4677
|
+
if (!loadedPluginIds.has(ownerPluginId))
|
|
4678
|
+
throw new Error(`Compaction owner plugin "${ownerPluginId}" is not loaded`);
|
|
4679
|
+
}
|
|
4680
|
+
function createToolRegistry(input) {
|
|
4681
|
+
const registry = input.tools instanceof ToolRegistry ? cloneRegistry(input.tools) : new ToolRegistry;
|
|
4682
|
+
if (input.includeBuiltinTools) {
|
|
4683
|
+
safeRegister(registry, new ReadTool);
|
|
4684
|
+
safeRegister(registry, new WriteTool);
|
|
4685
|
+
safeRegister(registry, new EditTool);
|
|
4686
|
+
safeRegister(registry, new ExecTool);
|
|
4687
|
+
}
|
|
4688
|
+
for (const tool of Array.isArray(input.tools) ? input.tools : [])
|
|
4689
|
+
safeRegister(registry, tool);
|
|
4690
|
+
for (const tool of input.pluginTools)
|
|
4691
|
+
safeRegister(registry, tool);
|
|
4692
|
+
return registry;
|
|
4693
|
+
}
|
|
4694
|
+
function cloneRegistry(source) {
|
|
4695
|
+
const registry = new ToolRegistry;
|
|
4696
|
+
for (const tool of source.list())
|
|
4697
|
+
registry.register(tool);
|
|
4698
|
+
return registry;
|
|
4699
|
+
}
|
|
4700
|
+
function safeRegister(registry, tool) {
|
|
4701
|
+
if (!registry.get(tool.definition.name))
|
|
4702
|
+
registry.register(tool);
|
|
4703
|
+
}
|
|
4704
|
+
// src/persistence/file-state-store.ts
|
|
4705
|
+
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
4706
|
+
import path3 from "node:path";
|
|
4707
|
+
class FileStateStore {
|
|
4708
|
+
directory;
|
|
4709
|
+
constructor(options) {
|
|
4710
|
+
this.directory = path3.resolve(options.directory);
|
|
4711
|
+
}
|
|
4712
|
+
async save(snapshot) {
|
|
4713
|
+
const validated = sessionSnapshotCodec.decode(snapshot);
|
|
4714
|
+
await mkdir2(this.directory, { recursive: true });
|
|
4715
|
+
await writeFile2(this.filePath(validated.sessionId), JSON.stringify(validated, null, 2), "utf8");
|
|
4716
|
+
}
|
|
4717
|
+
async load(sessionId) {
|
|
4718
|
+
try {
|
|
4719
|
+
const raw = await readFile2(this.filePath(sessionId), "utf8");
|
|
4720
|
+
return sessionSnapshotCodec.decode(JSON.parse(raw));
|
|
4721
|
+
} catch (error) {
|
|
4722
|
+
if (error.code === "ENOENT")
|
|
4723
|
+
return null;
|
|
4724
|
+
throw error;
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
async delete(sessionId) {
|
|
4728
|
+
await rm(this.filePath(sessionId), { force: true });
|
|
4729
|
+
}
|
|
4730
|
+
async list() {
|
|
4731
|
+
await mkdir2(this.directory, { recursive: true });
|
|
4732
|
+
const entries = (await readdir2(this.directory)).sort();
|
|
4733
|
+
const snapshots = [];
|
|
4734
|
+
for (const entry of entries) {
|
|
4735
|
+
if (!entry.endsWith(".json"))
|
|
4736
|
+
continue;
|
|
4737
|
+
const raw = await readFile2(path3.join(this.directory, entry), "utf8");
|
|
4738
|
+
snapshots.push(sessionSnapshotCodec.decode(JSON.parse(raw)));
|
|
4739
|
+
}
|
|
4740
|
+
return snapshots;
|
|
4741
|
+
}
|
|
4742
|
+
filePath(sessionId) {
|
|
4743
|
+
return path3.join(this.directory, `${encodeURIComponent(sessionId)}.json`);
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
// src/persistence/in-memory-state-store.ts
|
|
4747
|
+
class InMemoryStateStore {
|
|
4748
|
+
snapshots = new Map;
|
|
4749
|
+
async save(snapshot) {
|
|
4750
|
+
const validated = sessionSnapshotCodec.decode(snapshot);
|
|
4751
|
+
this.snapshots.set(validated.sessionId, validated);
|
|
4752
|
+
}
|
|
4753
|
+
async load(sessionId) {
|
|
4754
|
+
const snapshot = this.snapshots.get(sessionId);
|
|
4755
|
+
return snapshot ? sessionSnapshotCodec.decode(snapshot) : null;
|
|
4756
|
+
}
|
|
4757
|
+
async delete(sessionId) {
|
|
4758
|
+
this.snapshots.delete(sessionId);
|
|
4759
|
+
}
|
|
4760
|
+
async list() {
|
|
4761
|
+
return [...this.snapshots.values()].map((snapshot) => sessionSnapshotCodec.decode(snapshot));
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
// src/agent-core/create-model.ts
|
|
4765
|
+
function createModel(adapter) {
|
|
4766
|
+
return {
|
|
4767
|
+
adapter,
|
|
4768
|
+
model: adapter.defaultModel,
|
|
4769
|
+
stream(request) {
|
|
4770
|
+
return adapter.stream({
|
|
4771
|
+
...request,
|
|
4772
|
+
model: request.model ?? adapter.defaultModel
|
|
4773
|
+
});
|
|
4774
|
+
}
|
|
4775
|
+
};
|
|
4776
|
+
}
|
|
4777
|
+
export {
|
|
4778
|
+
startToolCall,
|
|
4779
|
+
normalizeToolSchema,
|
|
4780
|
+
normalizeToolResult,
|
|
4781
|
+
normalizePermissions,
|
|
4782
|
+
normalizeContent,
|
|
4783
|
+
fullPermissions,
|
|
4784
|
+
finalizeToolCall,
|
|
4785
|
+
createZenMuxAdapter,
|
|
4786
|
+
createXaiResponsesAdapter,
|
|
4787
|
+
createXaiAdapter,
|
|
4788
|
+
createOpenAIResponsesAdapter,
|
|
4789
|
+
createOpenAIAdapter,
|
|
4790
|
+
createMoonshotAIAdapter,
|
|
4791
|
+
createModel,
|
|
4792
|
+
createGeminiAdapter,
|
|
4793
|
+
createDeepSeekAdapter,
|
|
4794
|
+
createAnthropicAdapter,
|
|
4795
|
+
createAihubmixResponsesAdapter,
|
|
4796
|
+
createAihubmixAdapter,
|
|
4797
|
+
createAgent,
|
|
4798
|
+
appendToolCallArgsDelta,
|
|
4799
|
+
WriteTool,
|
|
4800
|
+
ToolRegistry,
|
|
4801
|
+
SessionExecutionError,
|
|
4802
|
+
Session,
|
|
4803
|
+
ReadTool,
|
|
4804
|
+
PermissionGateway,
|
|
4805
|
+
PermissionDeniedError,
|
|
4806
|
+
NodeFileSystemGateway,
|
|
4807
|
+
NodeExecGateway,
|
|
4808
|
+
InMemoryStateStore,
|
|
4809
|
+
FileStateStore,
|
|
4810
|
+
ExecTool,
|
|
4811
|
+
EditTool,
|
|
4812
|
+
DefaultNetworkGateway,
|
|
4813
|
+
DefaultModelGateway,
|
|
4814
|
+
DefaultGitGateway,
|
|
4815
|
+
DefaultCompactionService,
|
|
4816
|
+
BaseTool,
|
|
4817
|
+
AutoContextManager,
|
|
4818
|
+
Agent,
|
|
4819
|
+
ActivityTracker
|
|
4820
|
+
};
|