@codelia/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2341 -0
- package/dist/index.d.cts +638 -0
- package/dist/index.d.ts +638 -0
- package/dist/index.js +2283 -0
- package/package.json +36 -0
- package/prompts/system.md +147 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2283 @@
|
|
|
1
|
+
// src/config/register.ts
|
|
2
|
+
import { configRegistry } from "@codelia/config";
|
|
3
|
+
|
|
4
|
+
// src/models/openai.ts
|
|
5
|
+
var OPENAI_DEFAULT_MODEL = "gpt-5.2-codex";
|
|
6
|
+
var OPENAI_DEFAULT_REASONING_EFFORT = "medium";
|
|
7
|
+
var OPENAI_MODELS = [
|
|
8
|
+
{
|
|
9
|
+
id: OPENAI_DEFAULT_MODEL,
|
|
10
|
+
provider: "openai",
|
|
11
|
+
aliases: ["default"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: "gpt-5.2",
|
|
15
|
+
provider: "openai"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "gpt-5.3-codex",
|
|
19
|
+
provider: "openai"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "gpt-5.2-2025-12-11",
|
|
23
|
+
provider: "openai"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "gpt-5.2-pro",
|
|
27
|
+
provider: "openai"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "gpt-5.2-pro-2025-12-11",
|
|
31
|
+
provider: "openai"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "gpt-5.1",
|
|
35
|
+
provider: "openai"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "gpt-5.1-2025-11-13",
|
|
39
|
+
provider: "openai"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "gpt-5",
|
|
43
|
+
provider: "openai"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "gpt-5-mini",
|
|
47
|
+
provider: "openai"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "gpt-5-mini-2025-08-07",
|
|
51
|
+
provider: "openai"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "gpt-5-nano",
|
|
55
|
+
provider: "openai"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "gpt-5-nano-2025-08-07",
|
|
59
|
+
provider: "openai"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "gpt-5.1-codex",
|
|
63
|
+
provider: "openai"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "gpt-5.1-codex-max",
|
|
67
|
+
provider: "openai"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "gpt-5.1-codex-mini",
|
|
71
|
+
provider: "openai"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "gpt-5-codex",
|
|
75
|
+
provider: "openai"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "gpt-5-codex-mini",
|
|
79
|
+
provider: "openai"
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
// src/config/register.ts
|
|
84
|
+
configRegistry.registerDefaults({
|
|
85
|
+
model: {
|
|
86
|
+
provider: "openai",
|
|
87
|
+
name: OPENAI_DEFAULT_MODEL,
|
|
88
|
+
reasoning: OPENAI_DEFAULT_REASONING_EFFORT
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// src/content/stringify.ts
|
|
93
|
+
var stringifyUnknown = (value) => {
|
|
94
|
+
try {
|
|
95
|
+
return JSON.stringify(value);
|
|
96
|
+
} catch {
|
|
97
|
+
return String(value);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var stringifyPart = (part, options) => {
|
|
101
|
+
if (part.type === "text") {
|
|
102
|
+
return part.text;
|
|
103
|
+
}
|
|
104
|
+
if (part.type === "image_url") {
|
|
105
|
+
if (options.mode === "log") {
|
|
106
|
+
return `[image:${part.image_url.media_type ?? "unknown"}]`;
|
|
107
|
+
}
|
|
108
|
+
return "[image]";
|
|
109
|
+
}
|
|
110
|
+
if (part.type === "document") {
|
|
111
|
+
if (options.mode === "log") {
|
|
112
|
+
return "[document:application/pdf]";
|
|
113
|
+
}
|
|
114
|
+
return "[document]";
|
|
115
|
+
}
|
|
116
|
+
if (part.type === "other") {
|
|
117
|
+
const head = `[other:${part.provider}/${part.kind}]`;
|
|
118
|
+
if (options.mode === "log" && options.includeOtherPayload) {
|
|
119
|
+
return `${head} ${stringifyUnknown(part.payload)}`;
|
|
120
|
+
}
|
|
121
|
+
return head;
|
|
122
|
+
}
|
|
123
|
+
return "[content]";
|
|
124
|
+
};
|
|
125
|
+
var stringifyContentParts = (content, options = {}) => {
|
|
126
|
+
const normalized = {
|
|
127
|
+
mode: options.mode ?? "display",
|
|
128
|
+
joiner: options.joiner ?? (options.mode === "log" ? "\n" : ""),
|
|
129
|
+
includeOtherPayload: options.includeOtherPayload ?? false
|
|
130
|
+
};
|
|
131
|
+
const text = content.map((part) => stringifyPart(part, normalized)).join(normalized.joiner);
|
|
132
|
+
return text || stringifyUnknown(content);
|
|
133
|
+
};
|
|
134
|
+
var stringifyContent = (content, options = {}) => {
|
|
135
|
+
if (content == null) return "";
|
|
136
|
+
if (typeof content === "string") return content;
|
|
137
|
+
return stringifyContentParts(content, options);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/history/store.ts
|
|
141
|
+
var MessageHistoryAdapter = class {
|
|
142
|
+
messages = [];
|
|
143
|
+
hasSystem = false;
|
|
144
|
+
enqueueSystem(system) {
|
|
145
|
+
if (system && !this.hasSystem) {
|
|
146
|
+
this.messages.push(system);
|
|
147
|
+
this.hasSystem = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
enqueueUserMessage(content) {
|
|
151
|
+
const message = {
|
|
152
|
+
role: "user",
|
|
153
|
+
content
|
|
154
|
+
};
|
|
155
|
+
this.messages.push(message);
|
|
156
|
+
}
|
|
157
|
+
enqueueToolResult(message) {
|
|
158
|
+
this.messages.push(message);
|
|
159
|
+
}
|
|
160
|
+
commitModelResponse(response) {
|
|
161
|
+
this.messages.push(...response.messages);
|
|
162
|
+
}
|
|
163
|
+
prepareInvokeInput(params) {
|
|
164
|
+
return {
|
|
165
|
+
messages: this.messages,
|
|
166
|
+
tools: params.tools ?? null,
|
|
167
|
+
toolChoice: params.toolChoice ?? null
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
getViewMessages() {
|
|
171
|
+
return this.messages;
|
|
172
|
+
}
|
|
173
|
+
replaceViewMessages(messages) {
|
|
174
|
+
this.messages = messages;
|
|
175
|
+
this.hasSystem = messages.some((message) => message.role === "system");
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// src/llm/openai/history.ts
|
|
180
|
+
var OpenAIHistoryAdapter = class extends MessageHistoryAdapter {
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// src/models/anthropic.ts
|
|
184
|
+
var ANTHROPIC_DEFAULT_MODEL = "claude-sonnet-4-5";
|
|
185
|
+
var ANTHROPIC_MODELS = [
|
|
186
|
+
{
|
|
187
|
+
id: ANTHROPIC_DEFAULT_MODEL,
|
|
188
|
+
provider: "anthropic",
|
|
189
|
+
aliases: ["default"]
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "claude-opus-4-6",
|
|
193
|
+
provider: "anthropic"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "claude-opus-4-5",
|
|
197
|
+
provider: "anthropic"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "claude-opus-4-5-20251201",
|
|
201
|
+
provider: "anthropic"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: "claude-sonnet-4-5-20250929",
|
|
205
|
+
provider: "anthropic"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: "claude-haiku-4-5",
|
|
209
|
+
provider: "anthropic"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: "claude-haiku-4-5-20250929",
|
|
213
|
+
provider: "anthropic"
|
|
214
|
+
}
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
// src/models/google.ts
|
|
218
|
+
var GOOGLE_MODELS = [
|
|
219
|
+
{
|
|
220
|
+
id: "gemini-3-pro-preview",
|
|
221
|
+
provider: "google"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: "gemini-3-pro-image-preview",
|
|
225
|
+
provider: "google"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "gemini-3-flash-preview",
|
|
229
|
+
provider: "google"
|
|
230
|
+
}
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
// src/models/registry.ts
|
|
234
|
+
function createModelRegistry(specs) {
|
|
235
|
+
const registry = {
|
|
236
|
+
modelsById: {},
|
|
237
|
+
aliasesByProvider: {
|
|
238
|
+
openai: {},
|
|
239
|
+
anthropic: {},
|
|
240
|
+
google: {}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
registerModels(registry, specs);
|
|
244
|
+
return registry;
|
|
245
|
+
}
|
|
246
|
+
function registerModels(registry, specs) {
|
|
247
|
+
for (const spec of specs) {
|
|
248
|
+
registry.modelsById[spec.id] = spec;
|
|
249
|
+
const aliasBucket = registry.aliasesByProvider[spec.provider];
|
|
250
|
+
for (const alias of spec.aliases ?? []) {
|
|
251
|
+
aliasBucket[alias] = spec.id;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function resolveModel(registry, idOrAlias, provider) {
|
|
256
|
+
const direct = registry.modelsById[idOrAlias];
|
|
257
|
+
if (direct) {
|
|
258
|
+
return direct;
|
|
259
|
+
}
|
|
260
|
+
if (provider) {
|
|
261
|
+
const aliasId = registry.aliasesByProvider[provider][idOrAlias];
|
|
262
|
+
return aliasId ? registry.modelsById[aliasId] : void 0;
|
|
263
|
+
}
|
|
264
|
+
let resolved;
|
|
265
|
+
for (const providerName of Object.keys(
|
|
266
|
+
registry.aliasesByProvider
|
|
267
|
+
)) {
|
|
268
|
+
const aliasId = registry.aliasesByProvider[providerName][idOrAlias];
|
|
269
|
+
if (!aliasId) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (resolved) {
|
|
273
|
+
return void 0;
|
|
274
|
+
}
|
|
275
|
+
resolved = registry.modelsById[aliasId];
|
|
276
|
+
}
|
|
277
|
+
return resolved;
|
|
278
|
+
}
|
|
279
|
+
function listModels(registry, provider) {
|
|
280
|
+
const all = Object.values(registry.modelsById);
|
|
281
|
+
return provider ? all.filter((model) => model.provider === provider) : all;
|
|
282
|
+
}
|
|
283
|
+
function cloneAliases(aliasesByProvider) {
|
|
284
|
+
return {
|
|
285
|
+
openai: { ...aliasesByProvider.openai },
|
|
286
|
+
anthropic: { ...aliasesByProvider.anthropic },
|
|
287
|
+
google: { ...aliasesByProvider.google }
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function applyModelMetadata(registry, index) {
|
|
291
|
+
const next = {
|
|
292
|
+
modelsById: { ...registry.modelsById },
|
|
293
|
+
aliasesByProvider: cloneAliases(registry.aliasesByProvider)
|
|
294
|
+
};
|
|
295
|
+
for (const [providerId, providerModels] of Object.entries(index.models)) {
|
|
296
|
+
if (providerId !== "openai" && providerId !== "anthropic" && providerId !== "google") {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const provider = providerId;
|
|
300
|
+
for (const [modelId, entry] of Object.entries(providerModels)) {
|
|
301
|
+
const fullId = `${provider}/${modelId}`;
|
|
302
|
+
const spec = resolveModel(next, fullId, provider) ?? resolveModel(next, modelId, provider);
|
|
303
|
+
if (!spec) continue;
|
|
304
|
+
const limits = entry.limits;
|
|
305
|
+
if (!limits) continue;
|
|
306
|
+
next.modelsById[spec.id] = {
|
|
307
|
+
...spec,
|
|
308
|
+
contextWindow: spec.contextWindow ?? limits.contextWindow,
|
|
309
|
+
maxInputTokens: spec.maxInputTokens ?? limits.inputTokens,
|
|
310
|
+
maxOutputTokens: spec.maxOutputTokens ?? limits.outputTokens
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return next;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/models/index.ts
|
|
318
|
+
var DEFAULT_MODEL_REGISTRY = createModelRegistry([
|
|
319
|
+
...OPENAI_MODELS,
|
|
320
|
+
...ANTHROPIC_MODELS,
|
|
321
|
+
...GOOGLE_MODELS
|
|
322
|
+
]);
|
|
323
|
+
|
|
324
|
+
// src/services/compaction/service.ts
|
|
325
|
+
var DEFAULT_THRESHOLD_RATIO = 0.8;
|
|
326
|
+
var DEFAULT_RETAIN_LAST_TURNS = 1;
|
|
327
|
+
var DEFAULT_SUMMARY_PROMPT = "Summarize the conversation so it can be continued later. Focus on decisions, results, constraints, and next steps. Keep it concise and factual.";
|
|
328
|
+
var DEFAULT_RETAIN_PROMPT = "List concrete details that must be preserved verbatim. Include tool output refs, file paths, identifiers, commands, TODOs, and any critical decisions.";
|
|
329
|
+
var CompactionService = class _CompactionService {
|
|
330
|
+
config;
|
|
331
|
+
modelRegistry;
|
|
332
|
+
constructor(config, deps) {
|
|
333
|
+
this.config = _CompactionService.normalizeConfig(config);
|
|
334
|
+
this.modelRegistry = deps.modelRegistry;
|
|
335
|
+
}
|
|
336
|
+
async shouldCompact(llm, usage) {
|
|
337
|
+
if (!this.config.enabled) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
if (!this.config.auto) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
if (!usage) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
const contextLimit = await this.resolveContextLimit(llm, usage);
|
|
347
|
+
const threshold = Math.floor(contextLimit * this.config.thresholdRatio);
|
|
348
|
+
return usage.total_tokens >= threshold;
|
|
349
|
+
}
|
|
350
|
+
async compact(llm, messages, options) {
|
|
351
|
+
if (!this.config.enabled) {
|
|
352
|
+
return {
|
|
353
|
+
compacted: false,
|
|
354
|
+
compactedMessages: messages,
|
|
355
|
+
usage: null
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const preparedMessages = this.prepareMessagesForSummary(messages);
|
|
359
|
+
const prompt = this.buildCompactionPrompt();
|
|
360
|
+
const interruptMessage = {
|
|
361
|
+
role: "user",
|
|
362
|
+
content: prompt
|
|
363
|
+
};
|
|
364
|
+
let response;
|
|
365
|
+
try {
|
|
366
|
+
response = await llm.ainvoke({
|
|
367
|
+
messages: [...preparedMessages, interruptMessage],
|
|
368
|
+
tools: null,
|
|
369
|
+
toolChoice: "none",
|
|
370
|
+
...options?.signal ? { signal: options.signal } : {},
|
|
371
|
+
...this.config.model ? { model: this.config.model } : {}
|
|
372
|
+
});
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (error instanceof Error && (error.name === "AbortError" || error.name === "APIUserAbortError" || error.name === "AbortSignal")) {
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
compacted: false,
|
|
379
|
+
compactedMessages: messages,
|
|
380
|
+
usage: null
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
const responseText = extractAssistantText(response.messages);
|
|
384
|
+
const parsed = this.parseCompactionResponse(responseText);
|
|
385
|
+
const summary = parsed.summary || parsed.fallbackSummary;
|
|
386
|
+
const retain = parsed.retain;
|
|
387
|
+
if (!summary && !retain) {
|
|
388
|
+
return {
|
|
389
|
+
compacted: false,
|
|
390
|
+
compactedMessages: messages,
|
|
391
|
+
usage: response.usage ?? null
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const compactedMessages = this.buildCompactedMessages(
|
|
395
|
+
messages,
|
|
396
|
+
retain,
|
|
397
|
+
summary
|
|
398
|
+
);
|
|
399
|
+
return {
|
|
400
|
+
compacted: true,
|
|
401
|
+
compactedMessages,
|
|
402
|
+
usage: response.usage ?? null
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
prepareMessagesForSummary(messages) {
|
|
406
|
+
if (messages.length === 0) return messages;
|
|
407
|
+
const prepared = messages.map((message) => ({ ...message }));
|
|
408
|
+
const last = prepared[prepared.length - 1];
|
|
409
|
+
if (last.role === "assistant" && last.tool_calls?.length) {
|
|
410
|
+
if (last.content) {
|
|
411
|
+
const replacement = {
|
|
412
|
+
role: "assistant",
|
|
413
|
+
content: last.content,
|
|
414
|
+
name: last.name
|
|
415
|
+
};
|
|
416
|
+
prepared[prepared.length - 1] = replacement;
|
|
417
|
+
} else {
|
|
418
|
+
prepared.pop();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return prepared;
|
|
422
|
+
}
|
|
423
|
+
buildCompactionPrompt() {
|
|
424
|
+
const lines = [
|
|
425
|
+
"You are summarizing the conversation for context compaction.",
|
|
426
|
+
"Respond only with the XML-like tags below and nothing else:"
|
|
427
|
+
];
|
|
428
|
+
if (this.config.retainPrompt !== null) {
|
|
429
|
+
lines.push("<retain>...</retain>");
|
|
430
|
+
}
|
|
431
|
+
lines.push("<summary>...</summary>", "");
|
|
432
|
+
if (this.config.retainPrompt !== null) {
|
|
433
|
+
lines.push("Retain instructions:", this.config.retainPrompt.trim());
|
|
434
|
+
lines.push(
|
|
435
|
+
...this.config.retainDirectives.map((directive) => `- ${directive}`)
|
|
436
|
+
);
|
|
437
|
+
lines.push("");
|
|
438
|
+
}
|
|
439
|
+
lines.push("Summary instructions:", this.config.summaryPrompt.trim());
|
|
440
|
+
lines.push(
|
|
441
|
+
...this.config.summaryDirectives.map((directive) => `- ${directive}`)
|
|
442
|
+
);
|
|
443
|
+
return lines.join("\n").trim();
|
|
444
|
+
}
|
|
445
|
+
parseCompactionResponse(text) {
|
|
446
|
+
const retain = this.config.retainPrompt === null ? "" : extractTag(text, "retain");
|
|
447
|
+
const summary = extractTag(text, "summary");
|
|
448
|
+
const fallbackSummary = text.replace(/<retain>[\s\S]*?<\/retain>/gi, "").replace(/<summary>|<\/summary>/gi, "").trim();
|
|
449
|
+
return {
|
|
450
|
+
retain: retain.trim(),
|
|
451
|
+
summary: summary.trim(),
|
|
452
|
+
fallbackSummary
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
buildCompactedMessages(messages, retain, summary) {
|
|
456
|
+
const systemMessages = [];
|
|
457
|
+
const nonSystemMessages = [];
|
|
458
|
+
for (const message of messages) {
|
|
459
|
+
if (message.role === "system") {
|
|
460
|
+
systemMessages.push(message);
|
|
461
|
+
} else {
|
|
462
|
+
nonSystemMessages.push(message);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const tail = this.getLastTurns(
|
|
466
|
+
nonSystemMessages,
|
|
467
|
+
this.config.retainLastTurns
|
|
468
|
+
);
|
|
469
|
+
const compacted = [...systemMessages];
|
|
470
|
+
if (retain) {
|
|
471
|
+
compacted.push({
|
|
472
|
+
role: "user",
|
|
473
|
+
content: retain
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
if (summary) {
|
|
477
|
+
compacted.push({
|
|
478
|
+
role: "user",
|
|
479
|
+
content: summary
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
compacted.push(...tail);
|
|
483
|
+
return compacted;
|
|
484
|
+
}
|
|
485
|
+
getLastTurns(messages, retainLastTurns) {
|
|
486
|
+
if (retainLastTurns <= 0) return [];
|
|
487
|
+
let remaining = retainLastTurns;
|
|
488
|
+
let startIndex = 0;
|
|
489
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
490
|
+
if (messages[i].role === "user") {
|
|
491
|
+
remaining -= 1;
|
|
492
|
+
if (remaining === 0) {
|
|
493
|
+
startIndex = i;
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (remaining > 0) {
|
|
499
|
+
startIndex = 0;
|
|
500
|
+
}
|
|
501
|
+
return messages.slice(startIndex);
|
|
502
|
+
}
|
|
503
|
+
async resolveContextLimit(llm, usage) {
|
|
504
|
+
const modelId = usage.model ?? llm.model;
|
|
505
|
+
const modelSpec = this.resolveModelSpecWithSnapshotFallback(
|
|
506
|
+
llm.provider,
|
|
507
|
+
modelId
|
|
508
|
+
);
|
|
509
|
+
if (modelSpec?.contextWindow && modelSpec.contextWindow > 0) {
|
|
510
|
+
return modelSpec.contextWindow;
|
|
511
|
+
}
|
|
512
|
+
if (modelSpec?.maxInputTokens && modelSpec.maxInputTokens > 0) {
|
|
513
|
+
return modelSpec.maxInputTokens;
|
|
514
|
+
}
|
|
515
|
+
throw new Error(
|
|
516
|
+
`Missing context limit for ${llm.provider}/${modelId} in model registry`
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
resolveModelSpecWithSnapshotFallback(provider, modelId) {
|
|
520
|
+
const direct = resolveModel(this.modelRegistry, modelId, provider);
|
|
521
|
+
if (direct) return direct;
|
|
522
|
+
const baseId = stripSnapshotSuffix(modelId);
|
|
523
|
+
if (!baseId || baseId === modelId) return void 0;
|
|
524
|
+
return resolveModel(this.modelRegistry, baseId, provider);
|
|
525
|
+
}
|
|
526
|
+
static normalizeConfig(config) {
|
|
527
|
+
const retainLastTurnsRaw = config.retainLastTurns ?? DEFAULT_RETAIN_LAST_TURNS;
|
|
528
|
+
return {
|
|
529
|
+
enabled: config.enabled ?? true,
|
|
530
|
+
auto: config.auto ?? true,
|
|
531
|
+
thresholdRatio: config.thresholdRatio ?? DEFAULT_THRESHOLD_RATIO,
|
|
532
|
+
model: config.model ?? null,
|
|
533
|
+
summaryPrompt: config.summaryPrompt ?? DEFAULT_SUMMARY_PROMPT,
|
|
534
|
+
summaryDirectives: config.summaryDirectives ?? [],
|
|
535
|
+
retainPrompt: config.retainPrompt === void 0 ? DEFAULT_RETAIN_PROMPT : config.retainPrompt,
|
|
536
|
+
retainDirectives: config.retainDirectives ?? [],
|
|
537
|
+
retainLastTurns: Math.max(0, Math.floor(retainLastTurnsRaw))
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
var extractTag = (text, tag) => {
|
|
542
|
+
const regex = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, "i");
|
|
543
|
+
const match = text.match(regex);
|
|
544
|
+
return match?.[1]?.trim() ?? "";
|
|
545
|
+
};
|
|
546
|
+
var stripSnapshotSuffix = (modelId) => modelId.replace(/-[0-9]{4}-[0-9]{2}-[0-9]{2}$/, "");
|
|
547
|
+
var extractAssistantText = (messages) => messages.flatMap((message) => {
|
|
548
|
+
if (message.role !== "assistant" || message.content == null) {
|
|
549
|
+
return [];
|
|
550
|
+
}
|
|
551
|
+
if (typeof message.content === "string") {
|
|
552
|
+
return [message.content];
|
|
553
|
+
}
|
|
554
|
+
return [
|
|
555
|
+
message.content.map((part) => {
|
|
556
|
+
if (part.type === "text") return part.text;
|
|
557
|
+
if (part.type === "other") {
|
|
558
|
+
try {
|
|
559
|
+
return JSON.stringify(part.payload);
|
|
560
|
+
} catch {
|
|
561
|
+
return String(part.payload);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return "";
|
|
565
|
+
}).join("")
|
|
566
|
+
];
|
|
567
|
+
}).join("\n").trim();
|
|
568
|
+
|
|
569
|
+
// src/services/tool-output-cache/service.ts
|
|
570
|
+
var DEFAULT_MAX_MESSAGE_BYTES = 50 * 1024;
|
|
571
|
+
var DEFAULT_CONTEXT_RATIO = 0.25;
|
|
572
|
+
var MIN_CONTEXT_BUDGET = 2e4;
|
|
573
|
+
var MAX_CONTEXT_BUDGET = 6e4;
|
|
574
|
+
var APPROX_BYTES_PER_TOKEN = 4;
|
|
575
|
+
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
576
|
+
var contentToText = (content) => {
|
|
577
|
+
if (typeof content === "string") return content;
|
|
578
|
+
return content.map((part) => {
|
|
579
|
+
switch (part.type) {
|
|
580
|
+
case "text":
|
|
581
|
+
return part.text;
|
|
582
|
+
case "image_url":
|
|
583
|
+
return "[image]";
|
|
584
|
+
case "document":
|
|
585
|
+
return "[document]";
|
|
586
|
+
case "other":
|
|
587
|
+
return `[other:${part.provider}/${part.kind}]`;
|
|
588
|
+
default:
|
|
589
|
+
return "[content]";
|
|
590
|
+
}
|
|
591
|
+
}).join("");
|
|
592
|
+
};
|
|
593
|
+
var estimateTokens = (text) => Math.ceil(Buffer.byteLength(text, "utf8") / APPROX_BYTES_PER_TOKEN);
|
|
594
|
+
var shouldBypassImmediateTruncation = (toolName) => toolName === "tool_output_cache" || toolName === "tool_output_cache_grep";
|
|
595
|
+
var truncateForContext = (content, maxMessageBytes) => {
|
|
596
|
+
if (!content) return { output: "", truncated: false };
|
|
597
|
+
const lines = content.split(/\r?\n/);
|
|
598
|
+
const outputLines = [];
|
|
599
|
+
let bytes = 0;
|
|
600
|
+
let modified = false;
|
|
601
|
+
for (const line of lines) {
|
|
602
|
+
const size = Buffer.byteLength(line, "utf8") + (outputLines.length ? 1 : 0);
|
|
603
|
+
if (bytes + size > maxMessageBytes) {
|
|
604
|
+
modified = true;
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
outputLines.push(line);
|
|
608
|
+
bytes += size;
|
|
609
|
+
}
|
|
610
|
+
return { output: outputLines.join("\n"), truncated: modified };
|
|
611
|
+
};
|
|
612
|
+
var ToolOutputCacheService = class {
|
|
613
|
+
config;
|
|
614
|
+
modelRegistry;
|
|
615
|
+
store;
|
|
616
|
+
constructor(config, deps) {
|
|
617
|
+
this.config = config;
|
|
618
|
+
this.modelRegistry = deps.modelRegistry;
|
|
619
|
+
this.store = deps.store;
|
|
620
|
+
}
|
|
621
|
+
async processToolMessage(message) {
|
|
622
|
+
if (this.config.enabled === false) return message;
|
|
623
|
+
const raw = contentToText(message.content);
|
|
624
|
+
const outputRef = await this.persistToolOutput(message, raw);
|
|
625
|
+
const truncated = shouldBypassImmediateTruncation(message.tool_name) ? { output: raw, truncated: false } : truncateForContext(
|
|
626
|
+
raw,
|
|
627
|
+
this.config.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES
|
|
628
|
+
);
|
|
629
|
+
const trimmed = truncated.truncated;
|
|
630
|
+
let output = truncated.output;
|
|
631
|
+
if (trimmed) {
|
|
632
|
+
const refLabel = outputRef?.id ? `; ref=${outputRef.id}` : "";
|
|
633
|
+
output += `
|
|
634
|
+
|
|
635
|
+
[tool output truncated${refLabel}]`;
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
...message,
|
|
639
|
+
content: output,
|
|
640
|
+
output_ref: outputRef,
|
|
641
|
+
trimmed: trimmed || message.trimmed
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
async trimMessages(llm, messages) {
|
|
645
|
+
if (this.config.enabled === false) {
|
|
646
|
+
return { messages, trimmed: false };
|
|
647
|
+
}
|
|
648
|
+
const budget = await this.resolveContextBudgetTokens(llm);
|
|
649
|
+
let total = 0;
|
|
650
|
+
for (const message of messages) {
|
|
651
|
+
if (message.role !== "tool") continue;
|
|
652
|
+
total += estimateTokens(contentToText(message.content));
|
|
653
|
+
}
|
|
654
|
+
if (total <= budget) return { messages, trimmed: false };
|
|
655
|
+
const updated = messages.map((message) => ({ ...message }));
|
|
656
|
+
let trimmedAny = false;
|
|
657
|
+
for (const message of updated) {
|
|
658
|
+
if (message.role !== "tool") continue;
|
|
659
|
+
if (total <= budget) break;
|
|
660
|
+
const toolMessage = message;
|
|
661
|
+
const contentText = contentToText(toolMessage.content);
|
|
662
|
+
const refId = toolMessage.output_ref?.id ?? null;
|
|
663
|
+
const placeholder = refId ? `[tool output trimmed; ref=${refId}]` : "[tool output trimmed]";
|
|
664
|
+
const tokens = estimateTokens(contentText);
|
|
665
|
+
const placeholderTokens = estimateTokens(placeholder);
|
|
666
|
+
toolMessage.content = placeholder;
|
|
667
|
+
toolMessage.trimmed = true;
|
|
668
|
+
total = Math.max(0, total - tokens + placeholderTokens);
|
|
669
|
+
trimmedAny = true;
|
|
670
|
+
}
|
|
671
|
+
return { messages: updated, trimmed: trimmedAny };
|
|
672
|
+
}
|
|
673
|
+
async persistToolOutput(message, content) {
|
|
674
|
+
if (!this.store) return void 0;
|
|
675
|
+
try {
|
|
676
|
+
const result = await this.store.save({
|
|
677
|
+
tool_call_id: message.tool_call_id,
|
|
678
|
+
tool_name: message.tool_name,
|
|
679
|
+
content,
|
|
680
|
+
is_error: message.is_error
|
|
681
|
+
});
|
|
682
|
+
return result;
|
|
683
|
+
} catch {
|
|
684
|
+
return void 0;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
async resolveContextBudgetTokens(llm) {
|
|
688
|
+
if (this.config.contextBudgetTokens !== void 0 && this.config.contextBudgetTokens !== null) {
|
|
689
|
+
return this.config.contextBudgetTokens;
|
|
690
|
+
}
|
|
691
|
+
const modelSpec = resolveModel(this.modelRegistry, llm.model, llm.provider);
|
|
692
|
+
const contextLimit = modelSpec?.contextWindow ?? modelSpec?.maxInputTokens ?? null;
|
|
693
|
+
if (!contextLimit || contextLimit <= 0) {
|
|
694
|
+
return MAX_CONTEXT_BUDGET;
|
|
695
|
+
}
|
|
696
|
+
const derived = Math.floor(contextLimit * DEFAULT_CONTEXT_RATIO);
|
|
697
|
+
return clamp(derived, MIN_CONTEXT_BUDGET, MAX_CONTEXT_BUDGET);
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
// src/services/usage/service.ts
|
|
702
|
+
var TokenUsageService = class {
|
|
703
|
+
constructor(config) {
|
|
704
|
+
this.config = config;
|
|
705
|
+
this.usageSummary = {
|
|
706
|
+
total_calls: 0,
|
|
707
|
+
total_tokens: 0,
|
|
708
|
+
total_input_tokens: 0,
|
|
709
|
+
total_output_tokens: 0,
|
|
710
|
+
total_cached_input_tokens: 0,
|
|
711
|
+
total_cache_creation_tokens: 0,
|
|
712
|
+
total_cost_usd: 0,
|
|
713
|
+
by_model: {}
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
usageSummary;
|
|
717
|
+
lastUsage = null;
|
|
718
|
+
updateUsageSummary(usage) {
|
|
719
|
+
this.lastUsage = usage ?? null;
|
|
720
|
+
if (!this.config.enabled || !usage) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
this.usageSummary.total_calls++;
|
|
724
|
+
this.usageSummary.total_tokens += usage.total_tokens;
|
|
725
|
+
this.usageSummary.total_input_tokens += usage.input_tokens;
|
|
726
|
+
this.usageSummary.total_output_tokens += usage.output_tokens;
|
|
727
|
+
this.usageSummary.total_cached_input_tokens += usage.input_cached_tokens ?? 0;
|
|
728
|
+
this.usageSummary.total_cache_creation_tokens += usage.input_cache_creation_tokens ?? 0;
|
|
729
|
+
const model = usage.model ?? "unknown";
|
|
730
|
+
if (!this.usageSummary.by_model[model]) {
|
|
731
|
+
this.usageSummary.by_model[model] = {
|
|
732
|
+
calls: 1,
|
|
733
|
+
input_tokens: usage.input_tokens,
|
|
734
|
+
output_tokens: usage.output_tokens,
|
|
735
|
+
cached_input_tokens: usage.input_cached_tokens ?? 0,
|
|
736
|
+
cache_creation_tokens: usage.input_cache_creation_tokens ?? 0,
|
|
737
|
+
total_tokens: usage.total_tokens
|
|
738
|
+
};
|
|
739
|
+
} else {
|
|
740
|
+
this.usageSummary.by_model[model].calls++;
|
|
741
|
+
this.usageSummary.by_model[model].input_tokens += usage.input_tokens;
|
|
742
|
+
this.usageSummary.by_model[model].output_tokens += usage.output_tokens;
|
|
743
|
+
this.usageSummary.by_model[model].cached_input_tokens += usage.input_cached_tokens ?? 0;
|
|
744
|
+
this.usageSummary.by_model[model].cache_creation_tokens += usage.input_cache_creation_tokens ?? 0;
|
|
745
|
+
this.usageSummary.by_model[model].total_tokens += usage.total_tokens;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
getUsageSummary() {
|
|
749
|
+
return this.usageSummary;
|
|
750
|
+
}
|
|
751
|
+
getLastUsage() {
|
|
752
|
+
return this.lastUsage;
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// src/tools/done.ts
|
|
757
|
+
var TaskComplete = class extends Error {
|
|
758
|
+
finalMessage;
|
|
759
|
+
constructor(finalMessage) {
|
|
760
|
+
super("Task complete");
|
|
761
|
+
this.name = "TaskComplete";
|
|
762
|
+
this.finalMessage = finalMessage;
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
// src/agent/agent.ts
|
|
767
|
+
var DEFAULT_MAX_ITERATIONS = 200;
|
|
768
|
+
function toolResultToContent(result) {
|
|
769
|
+
if (result.type === "text") return result.text;
|
|
770
|
+
if (result.type === "parts") return result.parts;
|
|
771
|
+
try {
|
|
772
|
+
return JSON.stringify(result.value);
|
|
773
|
+
} catch {
|
|
774
|
+
return String(result.value);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
var collectModelOutput = (messages) => {
|
|
778
|
+
const reasoningTexts = [];
|
|
779
|
+
const assistantTexts = [];
|
|
780
|
+
const toolCalls = [];
|
|
781
|
+
for (const message of messages) {
|
|
782
|
+
if (message.role === "reasoning") {
|
|
783
|
+
const text = message.content ?? "";
|
|
784
|
+
if (text) {
|
|
785
|
+
reasoningTexts.push(text);
|
|
786
|
+
}
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
if (message.role !== "assistant") {
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
if (message.content) {
|
|
793
|
+
const text = stringifyContent(message.content, { mode: "display" });
|
|
794
|
+
if (text) {
|
|
795
|
+
assistantTexts.push(text);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (message.tool_calls?.length) {
|
|
799
|
+
toolCalls.push(...message.tool_calls);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return { reasoningTexts, assistantTexts, toolCalls };
|
|
803
|
+
};
|
|
804
|
+
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
805
|
+
var createAbortError = () => {
|
|
806
|
+
const error = new Error("Operation aborted");
|
|
807
|
+
error.name = "AbortError";
|
|
808
|
+
return error;
|
|
809
|
+
};
|
|
810
|
+
var isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "APIUserAbortError" || error.name === "AbortSignal");
|
|
811
|
+
var throwIfAborted = (signal) => {
|
|
812
|
+
if (signal?.aborted) {
|
|
813
|
+
throw createAbortError();
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
var Agent = class {
|
|
817
|
+
llm;
|
|
818
|
+
tools;
|
|
819
|
+
systemPrompt;
|
|
820
|
+
maxIterations;
|
|
821
|
+
toolChoice;
|
|
822
|
+
requireDoneTool;
|
|
823
|
+
compactionService;
|
|
824
|
+
toolOutputCacheService;
|
|
825
|
+
services;
|
|
826
|
+
modelRegistry;
|
|
827
|
+
canExecuteTool;
|
|
828
|
+
//private readonly dependencyOverrides?: DependencyOverrides;
|
|
829
|
+
history;
|
|
830
|
+
usageService;
|
|
831
|
+
constructor(options) {
|
|
832
|
+
this.llm = options.llm;
|
|
833
|
+
this.history = this.llm.provider === "openai" ? new OpenAIHistoryAdapter() : new MessageHistoryAdapter();
|
|
834
|
+
this.tools = options.tools;
|
|
835
|
+
this.systemPrompt = options.systemPrompt ?? void 0;
|
|
836
|
+
this.maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
837
|
+
this.toolChoice = options.toolChoice ?? void 0;
|
|
838
|
+
this.requireDoneTool = options.requireDoneTool ?? false;
|
|
839
|
+
this.services = options.services ?? {};
|
|
840
|
+
this.modelRegistry = options.modelRegistry ?? DEFAULT_MODEL_REGISTRY;
|
|
841
|
+
this.compactionService = options.compaction === null ? null : new CompactionService(options.compaction ?? {}, {
|
|
842
|
+
modelRegistry: this.modelRegistry
|
|
843
|
+
});
|
|
844
|
+
this.toolOutputCacheService = options.toolOutputCache === null ? null : new ToolOutputCacheService(options.toolOutputCache ?? {}, {
|
|
845
|
+
modelRegistry: this.modelRegistry,
|
|
846
|
+
store: this.services.toolOutputCacheStore ?? null
|
|
847
|
+
});
|
|
848
|
+
this.usageService = new TokenUsageService({
|
|
849
|
+
enabled: options.enableUsageTracking ?? true,
|
|
850
|
+
thresholdRatio: 0.5
|
|
851
|
+
});
|
|
852
|
+
this.canExecuteTool = options.canExecuteTool;
|
|
853
|
+
}
|
|
854
|
+
getUsageSummary() {
|
|
855
|
+
return this.usageService.getUsageSummary();
|
|
856
|
+
}
|
|
857
|
+
getContextLeftPercent() {
|
|
858
|
+
const usage = this.usageService.getLastUsage();
|
|
859
|
+
if (!usage) {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
const modelId = usage.model ?? this.llm.model;
|
|
863
|
+
const modelSpec = resolveModel(
|
|
864
|
+
this.modelRegistry,
|
|
865
|
+
modelId,
|
|
866
|
+
this.llm.provider
|
|
867
|
+
);
|
|
868
|
+
const contextLimit = modelSpec?.contextWindow ?? modelSpec?.maxInputTokens ?? null;
|
|
869
|
+
if (!contextLimit || contextLimit <= 0) {
|
|
870
|
+
return null;
|
|
871
|
+
}
|
|
872
|
+
const used = usage.total_tokens;
|
|
873
|
+
if (!Number.isFinite(used) || used <= 0) {
|
|
874
|
+
return 100;
|
|
875
|
+
}
|
|
876
|
+
const leftRatio = 1 - used / contextLimit;
|
|
877
|
+
const percent = Math.round(leftRatio * 100);
|
|
878
|
+
return Math.max(0, Math.min(100, percent));
|
|
879
|
+
}
|
|
880
|
+
getHistoryMessages() {
|
|
881
|
+
return this.history.getViewMessages();
|
|
882
|
+
}
|
|
883
|
+
replaceHistoryMessages(messages) {
|
|
884
|
+
this.history.replaceViewMessages(messages);
|
|
885
|
+
}
|
|
886
|
+
async *checkAndCompact(signal, options = {}) {
|
|
887
|
+
throwIfAborted(signal);
|
|
888
|
+
await this.trimToolOutputs();
|
|
889
|
+
const shouldCompact = options.force ? true : await this.compactionService?.shouldCompact(
|
|
890
|
+
this.llm,
|
|
891
|
+
this.usageService.getLastUsage()
|
|
892
|
+
);
|
|
893
|
+
if (shouldCompact && this.compactionService) {
|
|
894
|
+
throwIfAborted(signal);
|
|
895
|
+
const startEvent = {
|
|
896
|
+
type: "compaction_start",
|
|
897
|
+
timestamp: Date.now()
|
|
898
|
+
};
|
|
899
|
+
yield startEvent;
|
|
900
|
+
const { compacted, compactedMessages, usage } = await this.compactionService.compact(
|
|
901
|
+
this.llm,
|
|
902
|
+
this.history.getViewMessages(),
|
|
903
|
+
{ signal }
|
|
904
|
+
);
|
|
905
|
+
this.usageService.updateUsageSummary(usage);
|
|
906
|
+
if (compacted && compactedMessages) {
|
|
907
|
+
this.history.replaceViewMessages(compactedMessages);
|
|
908
|
+
}
|
|
909
|
+
const completeEvent = {
|
|
910
|
+
type: "compaction_complete",
|
|
911
|
+
timestamp: Date.now(),
|
|
912
|
+
compacted
|
|
913
|
+
};
|
|
914
|
+
yield completeEvent;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
async trimToolOutputs() {
|
|
918
|
+
if (!this.toolOutputCacheService) return;
|
|
919
|
+
const { messages, trimmed } = await this.toolOutputCacheService.trimMessages(
|
|
920
|
+
this.llm,
|
|
921
|
+
this.history.getViewMessages()
|
|
922
|
+
);
|
|
923
|
+
if (trimmed) {
|
|
924
|
+
this.history.replaceViewMessages(messages);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
async processToolMessage(message) {
|
|
928
|
+
if (!this.toolOutputCacheService) return message;
|
|
929
|
+
return this.toolOutputCacheService.processToolMessage(message);
|
|
930
|
+
}
|
|
931
|
+
buildToolContext(signal) {
|
|
932
|
+
const deps = /* @__PURE__ */ Object.create(null);
|
|
933
|
+
const cache = /* @__PURE__ */ new Map();
|
|
934
|
+
const resolve = async (key) => {
|
|
935
|
+
if (cache.has(key.id)) {
|
|
936
|
+
return cache.get(key.id);
|
|
937
|
+
}
|
|
938
|
+
const value = await key.create();
|
|
939
|
+
cache.set(key.id, value);
|
|
940
|
+
return value;
|
|
941
|
+
};
|
|
942
|
+
return {
|
|
943
|
+
deps,
|
|
944
|
+
resolve,
|
|
945
|
+
signal,
|
|
946
|
+
now: () => /* @__PURE__ */ new Date()
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
recordLlmRequest(session, input) {
|
|
950
|
+
if (!session) return null;
|
|
951
|
+
const seq = (session.invoke_seq ?? 0) + 1;
|
|
952
|
+
session.invoke_seq = seq;
|
|
953
|
+
const modelName = input.model ?? this.llm.model;
|
|
954
|
+
session.append({
|
|
955
|
+
type: "llm.request",
|
|
956
|
+
run_id: session.run_id,
|
|
957
|
+
ts: nowIso(),
|
|
958
|
+
seq,
|
|
959
|
+
model: {
|
|
960
|
+
provider: this.llm.provider,
|
|
961
|
+
name: modelName
|
|
962
|
+
},
|
|
963
|
+
input: {
|
|
964
|
+
messages: input.messages,
|
|
965
|
+
tools: input.tools ?? null,
|
|
966
|
+
tool_choice: input.toolChoice ?? null,
|
|
967
|
+
model: input.model
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
return seq;
|
|
971
|
+
}
|
|
972
|
+
recordLlmResponse(session, seq, response) {
|
|
973
|
+
if (!session || seq === null) return;
|
|
974
|
+
session.append({
|
|
975
|
+
type: "llm.response",
|
|
976
|
+
run_id: session.run_id,
|
|
977
|
+
ts: nowIso(),
|
|
978
|
+
seq,
|
|
979
|
+
output: {
|
|
980
|
+
messages: response.messages,
|
|
981
|
+
usage: response.usage ?? null,
|
|
982
|
+
stop_reason: response.stop_reason ?? null,
|
|
983
|
+
provider_meta: response.provider_meta ?? null
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
async run(message, options = {}) {
|
|
988
|
+
let finalResponse = "";
|
|
989
|
+
for await (const event of this.runStream(message, options)) {
|
|
990
|
+
if (event.type === "final") {
|
|
991
|
+
finalResponse = event.content;
|
|
992
|
+
break;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return finalResponse;
|
|
996
|
+
}
|
|
997
|
+
async *runStream(message, options = {}) {
|
|
998
|
+
const session = options.session;
|
|
999
|
+
const signal = options.signal;
|
|
1000
|
+
const forceCompaction = options.forceCompaction ?? false;
|
|
1001
|
+
if (this.systemPrompt) {
|
|
1002
|
+
const systemMessage = {
|
|
1003
|
+
role: "system",
|
|
1004
|
+
content: this.systemPrompt
|
|
1005
|
+
};
|
|
1006
|
+
this.history.enqueueSystem(systemMessage);
|
|
1007
|
+
}
|
|
1008
|
+
if (forceCompaction) {
|
|
1009
|
+
yield* this.checkAndCompact(signal, { force: true });
|
|
1010
|
+
const finalResponseEvent2 = {
|
|
1011
|
+
type: "final",
|
|
1012
|
+
content: "Compaction run completed."
|
|
1013
|
+
};
|
|
1014
|
+
yield finalResponseEvent2;
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
this.history.enqueueUserMessage(message);
|
|
1018
|
+
let iterations = 0;
|
|
1019
|
+
while (iterations < this.maxIterations) {
|
|
1020
|
+
iterations++;
|
|
1021
|
+
throwIfAborted(signal);
|
|
1022
|
+
await this.trimToolOutputs();
|
|
1023
|
+
throwIfAborted(signal);
|
|
1024
|
+
const invokeInput = this.history.prepareInvokeInput({
|
|
1025
|
+
tools: this.tools.map((t) => t.definition),
|
|
1026
|
+
toolChoice: this.toolChoice
|
|
1027
|
+
});
|
|
1028
|
+
const seq = this.recordLlmRequest(session, invokeInput);
|
|
1029
|
+
const response = await this.llm.ainvoke({
|
|
1030
|
+
...invokeInput,
|
|
1031
|
+
...signal ? { signal } : {}
|
|
1032
|
+
});
|
|
1033
|
+
this.recordLlmResponse(session, seq, response);
|
|
1034
|
+
this.usageService.updateUsageSummary(response.usage);
|
|
1035
|
+
this.history.commitModelResponse(response);
|
|
1036
|
+
const { reasoningTexts, assistantTexts, toolCalls } = collectModelOutput(
|
|
1037
|
+
response.messages
|
|
1038
|
+
);
|
|
1039
|
+
for (const reasoningText of reasoningTexts) {
|
|
1040
|
+
const reasoningEvent = {
|
|
1041
|
+
type: "reasoning",
|
|
1042
|
+
content: reasoningText,
|
|
1043
|
+
timestamp: Date.now()
|
|
1044
|
+
};
|
|
1045
|
+
yield reasoningEvent;
|
|
1046
|
+
}
|
|
1047
|
+
const hasToolCalls = toolCalls.length > 0;
|
|
1048
|
+
const shouldEmitFinalOnly = !hasToolCalls && !this.requireDoneTool;
|
|
1049
|
+
if (!shouldEmitFinalOnly) {
|
|
1050
|
+
for (const assistantText of assistantTexts) {
|
|
1051
|
+
const textEvent = {
|
|
1052
|
+
type: "text",
|
|
1053
|
+
content: assistantText,
|
|
1054
|
+
timestamp: Date.now()
|
|
1055
|
+
};
|
|
1056
|
+
yield textEvent;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (!hasToolCalls) {
|
|
1060
|
+
if (!this.requireDoneTool) {
|
|
1061
|
+
yield* this.checkAndCompact(signal);
|
|
1062
|
+
const finalText = assistantTexts.join("\n").trim();
|
|
1063
|
+
const finalResponseEvent2 = {
|
|
1064
|
+
type: "final",
|
|
1065
|
+
content: finalText
|
|
1066
|
+
};
|
|
1067
|
+
yield finalResponseEvent2;
|
|
1068
|
+
return;
|
|
1069
|
+
} else {
|
|
1070
|
+
yield* this.checkAndCompact(signal);
|
|
1071
|
+
}
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
let stepNumber = 0;
|
|
1075
|
+
for (const toolCall of toolCalls) {
|
|
1076
|
+
stepNumber++;
|
|
1077
|
+
let jsonArgs;
|
|
1078
|
+
try {
|
|
1079
|
+
jsonArgs = JSON.parse(toolCall.function.arguments);
|
|
1080
|
+
} catch (e) {
|
|
1081
|
+
if (e instanceof SyntaxError) {
|
|
1082
|
+
jsonArgs = { _raw: toolCall.function.arguments };
|
|
1083
|
+
} else {
|
|
1084
|
+
throw e;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
const stepStartEvent = {
|
|
1088
|
+
type: "step_start",
|
|
1089
|
+
step_id: toolCall.id,
|
|
1090
|
+
title: toolCall.function.name,
|
|
1091
|
+
step_number: stepNumber
|
|
1092
|
+
};
|
|
1093
|
+
yield stepStartEvent;
|
|
1094
|
+
const toolCallEvent = {
|
|
1095
|
+
type: "tool_call",
|
|
1096
|
+
tool: toolCall.function.name,
|
|
1097
|
+
args: jsonArgs,
|
|
1098
|
+
tool_call_id: toolCall.id
|
|
1099
|
+
};
|
|
1100
|
+
yield toolCallEvent;
|
|
1101
|
+
const startTime = Date.now();
|
|
1102
|
+
try {
|
|
1103
|
+
throwIfAborted(signal);
|
|
1104
|
+
const execution = await this.executeToolCall(toolCall, signal);
|
|
1105
|
+
const rawOutput = stringifyContent(execution.message.content, {
|
|
1106
|
+
mode: "display"
|
|
1107
|
+
});
|
|
1108
|
+
const processedMessage = await this.processToolMessage(
|
|
1109
|
+
execution.message
|
|
1110
|
+
);
|
|
1111
|
+
if (session) {
|
|
1112
|
+
session.append({
|
|
1113
|
+
type: "tool.output",
|
|
1114
|
+
run_id: session.run_id,
|
|
1115
|
+
ts: nowIso(),
|
|
1116
|
+
tool: toolCall.function.name,
|
|
1117
|
+
tool_call_id: toolCall.id,
|
|
1118
|
+
result_raw: rawOutput,
|
|
1119
|
+
is_error: processedMessage.is_error,
|
|
1120
|
+
output_ref: processedMessage.output_ref
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
this.history.enqueueToolResult(processedMessage);
|
|
1124
|
+
const toolResultEvent = {
|
|
1125
|
+
type: "tool_result",
|
|
1126
|
+
tool: toolCall.function.name,
|
|
1127
|
+
result: stringifyContent(processedMessage.content, {
|
|
1128
|
+
mode: "display"
|
|
1129
|
+
}),
|
|
1130
|
+
tool_call_id: toolCall.id,
|
|
1131
|
+
is_error: processedMessage.is_error
|
|
1132
|
+
};
|
|
1133
|
+
yield toolResultEvent;
|
|
1134
|
+
const durationMs = Date.now() - startTime;
|
|
1135
|
+
const stepCompleteEvent = {
|
|
1136
|
+
type: "step_complete",
|
|
1137
|
+
step_id: toolCall.id,
|
|
1138
|
+
status: execution.message.is_error ? "error" : "completed",
|
|
1139
|
+
duration_ms: durationMs
|
|
1140
|
+
};
|
|
1141
|
+
yield stepCompleteEvent;
|
|
1142
|
+
if (execution.done) {
|
|
1143
|
+
const finalResponseEvent2 = {
|
|
1144
|
+
type: "final",
|
|
1145
|
+
content: execution.finalMessage ?? assistantTexts.join("\n").trim()
|
|
1146
|
+
};
|
|
1147
|
+
yield finalResponseEvent2;
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
if (isAbortError(error)) {
|
|
1152
|
+
throw error;
|
|
1153
|
+
}
|
|
1154
|
+
const content = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
1155
|
+
const errorToolMessage = {
|
|
1156
|
+
role: "tool",
|
|
1157
|
+
tool_call_id: toolCall.id,
|
|
1158
|
+
tool_name: toolCall.function.name,
|
|
1159
|
+
content,
|
|
1160
|
+
is_error: true
|
|
1161
|
+
};
|
|
1162
|
+
const processedMessage = await this.processToolMessage(errorToolMessage);
|
|
1163
|
+
const rawOutput = stringifyContent(errorToolMessage.content, {
|
|
1164
|
+
mode: "display"
|
|
1165
|
+
});
|
|
1166
|
+
if (session) {
|
|
1167
|
+
session.append({
|
|
1168
|
+
type: "tool.output",
|
|
1169
|
+
run_id: session.run_id,
|
|
1170
|
+
ts: nowIso(),
|
|
1171
|
+
tool: toolCall.function.name,
|
|
1172
|
+
tool_call_id: toolCall.id,
|
|
1173
|
+
result_raw: rawOutput,
|
|
1174
|
+
is_error: true,
|
|
1175
|
+
output_ref: processedMessage.output_ref
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
this.history.enqueueToolResult(processedMessage);
|
|
1179
|
+
const toolResultEvent = {
|
|
1180
|
+
type: "tool_result",
|
|
1181
|
+
tool: toolCall.function.name,
|
|
1182
|
+
result: stringifyContent(processedMessage.content, {
|
|
1183
|
+
mode: "display"
|
|
1184
|
+
}),
|
|
1185
|
+
tool_call_id: toolCall.id,
|
|
1186
|
+
is_error: true
|
|
1187
|
+
};
|
|
1188
|
+
yield toolResultEvent;
|
|
1189
|
+
const durationMs = Date.now() - startTime;
|
|
1190
|
+
const stepCompleteEvent = {
|
|
1191
|
+
type: "step_complete",
|
|
1192
|
+
step_id: toolCall.id,
|
|
1193
|
+
status: "error",
|
|
1194
|
+
duration_ms: durationMs
|
|
1195
|
+
};
|
|
1196
|
+
yield stepCompleteEvent;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
yield* this.checkAndCompact(signal);
|
|
1200
|
+
}
|
|
1201
|
+
const finalResponse = await this.generateFinalResponse(session, signal);
|
|
1202
|
+
const finalResponseEvent = {
|
|
1203
|
+
type: "final",
|
|
1204
|
+
content: finalResponse
|
|
1205
|
+
};
|
|
1206
|
+
yield finalResponseEvent;
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
async generateFinalResponse(session, signal) {
|
|
1210
|
+
const summaryMessage = {
|
|
1211
|
+
role: "user",
|
|
1212
|
+
content: `You are generating the final response for the user after the agent reached max iterations. Summarize what was completed, what is pending, and any blockers. Be concise, user-facing, and do not mention internal agent mechanics. If there is a clear next step, suggest it as a short list.`
|
|
1213
|
+
};
|
|
1214
|
+
try {
|
|
1215
|
+
const input = {
|
|
1216
|
+
messages: [...this.history.getViewMessages(), summaryMessage],
|
|
1217
|
+
// temporal messages for summary
|
|
1218
|
+
tools: null,
|
|
1219
|
+
// no tools are allowed at this point
|
|
1220
|
+
toolChoice: "none"
|
|
1221
|
+
};
|
|
1222
|
+
const seq = this.recordLlmRequest(session, input);
|
|
1223
|
+
const summary = await this.llm.ainvoke({
|
|
1224
|
+
...input,
|
|
1225
|
+
...signal ? { signal } : {}
|
|
1226
|
+
});
|
|
1227
|
+
this.recordLlmResponse(session, seq, summary);
|
|
1228
|
+
this.usageService.updateUsageSummary(summary.usage);
|
|
1229
|
+
const { assistantTexts } = collectModelOutput(summary.messages);
|
|
1230
|
+
const finalResponse = `[Max Iterations Reached]
|
|
1231
|
+
|
|
1232
|
+
${assistantTexts.join("\n").trim()}`;
|
|
1233
|
+
return finalResponse;
|
|
1234
|
+
} catch {
|
|
1235
|
+
if (signal?.aborted) {
|
|
1236
|
+
throw createAbortError();
|
|
1237
|
+
}
|
|
1238
|
+
return "[Max Iterations Reached]\n\nSummary unavailable due to an internal error.";
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
async executeToolCall(toolCall, signal) {
|
|
1242
|
+
const toolName = toolCall.function.name;
|
|
1243
|
+
const tool = this.tools.find((t) => t.name === toolName);
|
|
1244
|
+
if (!tool) {
|
|
1245
|
+
return {
|
|
1246
|
+
message: {
|
|
1247
|
+
role: "tool",
|
|
1248
|
+
tool_call_id: toolCall.id,
|
|
1249
|
+
tool_name: toolName,
|
|
1250
|
+
content: `Error: Unknown tool '${toolName}'`,
|
|
1251
|
+
is_error: true
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
if (this.canExecuteTool) {
|
|
1256
|
+
try {
|
|
1257
|
+
const decision = await this.canExecuteTool(
|
|
1258
|
+
toolCall,
|
|
1259
|
+
toolCall.function.arguments,
|
|
1260
|
+
this.buildToolContext(signal)
|
|
1261
|
+
);
|
|
1262
|
+
if (decision.decision === "deny") {
|
|
1263
|
+
const deniedContent = `Permission denied${decision.reason ? `: ${decision.reason}` : ""}`;
|
|
1264
|
+
return {
|
|
1265
|
+
message: {
|
|
1266
|
+
role: "tool",
|
|
1267
|
+
tool_call_id: toolCall.id,
|
|
1268
|
+
tool_name: toolName,
|
|
1269
|
+
content: deniedContent,
|
|
1270
|
+
is_error: true
|
|
1271
|
+
},
|
|
1272
|
+
...decision.stop_turn ? {
|
|
1273
|
+
done: true,
|
|
1274
|
+
finalMessage: "Permission request was denied. Turn stopped. Please send your next input to continue."
|
|
1275
|
+
} : {}
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
return {
|
|
1280
|
+
message: {
|
|
1281
|
+
role: "tool",
|
|
1282
|
+
tool_call_id: toolCall.id,
|
|
1283
|
+
tool_name: toolName,
|
|
1284
|
+
content: `Permission check failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1285
|
+
is_error: true
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
try {
|
|
1291
|
+
const result = await tool.executeRaw(
|
|
1292
|
+
toolCall.function.arguments,
|
|
1293
|
+
this.buildToolContext(signal)
|
|
1294
|
+
);
|
|
1295
|
+
return {
|
|
1296
|
+
message: {
|
|
1297
|
+
role: "tool",
|
|
1298
|
+
tool_call_id: toolCall.id,
|
|
1299
|
+
tool_name: toolName,
|
|
1300
|
+
content: toolResultToContent(result)
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
} catch (error) {
|
|
1304
|
+
if (error instanceof TaskComplete) {
|
|
1305
|
+
return {
|
|
1306
|
+
message: {
|
|
1307
|
+
role: "tool",
|
|
1308
|
+
tool_call_id: toolCall.id,
|
|
1309
|
+
tool_name: toolName,
|
|
1310
|
+
content: "Task complete"
|
|
1311
|
+
},
|
|
1312
|
+
done: true,
|
|
1313
|
+
finalMessage: error.finalMessage
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
return {
|
|
1317
|
+
message: {
|
|
1318
|
+
role: "tool",
|
|
1319
|
+
tool_call_id: toolCall.id,
|
|
1320
|
+
tool_name: toolName,
|
|
1321
|
+
content: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1322
|
+
is_error: true
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
// src/llm/anthropic/chat.ts
|
|
1330
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
1331
|
+
|
|
1332
|
+
// src/llm/anthropic/serializer.ts
|
|
1333
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
1334
|
+
var stringifyUnknown2 = (value) => {
|
|
1335
|
+
if (value == null) return "";
|
|
1336
|
+
if (typeof value === "string") return value;
|
|
1337
|
+
try {
|
|
1338
|
+
return JSON.stringify(value);
|
|
1339
|
+
} catch {
|
|
1340
|
+
return String(value);
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
var formatOtherPart = (part) => {
|
|
1344
|
+
const payloadText = stringifyUnknown2(part.payload);
|
|
1345
|
+
return payloadText ? `[other:${part.provider}/${part.kind}] ${payloadText}` : `[other:${part.provider}/${part.kind}]`;
|
|
1346
|
+
};
|
|
1347
|
+
var parseDataUrl = (url) => {
|
|
1348
|
+
const match = /^data:([^;]+);base64,(.+)$/.exec(url);
|
|
1349
|
+
if (!match) return null;
|
|
1350
|
+
return { mediaType: match[1], data: match[2] };
|
|
1351
|
+
};
|
|
1352
|
+
var isSupportedImageMediaType = (value) => value === "image/png" || value === "image/jpeg" || value === "image/webp" || value === "image/gif";
|
|
1353
|
+
var isAnthropicContentBlock = (value) => {
|
|
1354
|
+
if (!isRecord(value) || typeof value.type !== "string") return false;
|
|
1355
|
+
switch (value.type) {
|
|
1356
|
+
case "text":
|
|
1357
|
+
return typeof value.text === "string";
|
|
1358
|
+
case "image":
|
|
1359
|
+
return isRecord(value.source);
|
|
1360
|
+
case "document":
|
|
1361
|
+
return isRecord(value.source);
|
|
1362
|
+
case "search_result":
|
|
1363
|
+
return true;
|
|
1364
|
+
default:
|
|
1365
|
+
return false;
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
var contentPartsToText = (content) => {
|
|
1369
|
+
if (content == null) return "";
|
|
1370
|
+
if (typeof content === "string") return content;
|
|
1371
|
+
return content.map((part) => {
|
|
1372
|
+
switch (part.type) {
|
|
1373
|
+
case "text":
|
|
1374
|
+
return part.text;
|
|
1375
|
+
case "image_url":
|
|
1376
|
+
return "[image]";
|
|
1377
|
+
case "document":
|
|
1378
|
+
return "[document]";
|
|
1379
|
+
case "other":
|
|
1380
|
+
return `[other:${part.provider}/${part.kind}]`;
|
|
1381
|
+
default:
|
|
1382
|
+
return "[content]";
|
|
1383
|
+
}
|
|
1384
|
+
}).join("");
|
|
1385
|
+
};
|
|
1386
|
+
var toTextBlock = (text) => ({
|
|
1387
|
+
type: "text",
|
|
1388
|
+
text
|
|
1389
|
+
});
|
|
1390
|
+
var toContentBlocks = (content) => {
|
|
1391
|
+
if (content == null) return [];
|
|
1392
|
+
if (typeof content === "string") {
|
|
1393
|
+
return content ? [toTextBlock(content)] : [];
|
|
1394
|
+
}
|
|
1395
|
+
return content.map((part) => {
|
|
1396
|
+
switch (part.type) {
|
|
1397
|
+
case "text":
|
|
1398
|
+
return toTextBlock(part.text);
|
|
1399
|
+
case "image_url": {
|
|
1400
|
+
const dataUrl = parseDataUrl(part.image_url.url);
|
|
1401
|
+
if (dataUrl) {
|
|
1402
|
+
const mediaType = part.image_url.media_type ?? (isSupportedImageMediaType(dataUrl.mediaType) ? dataUrl.mediaType : void 0);
|
|
1403
|
+
if (!mediaType) {
|
|
1404
|
+
return toTextBlock(part.image_url.url);
|
|
1405
|
+
}
|
|
1406
|
+
return {
|
|
1407
|
+
type: "image",
|
|
1408
|
+
source: {
|
|
1409
|
+
type: "base64",
|
|
1410
|
+
media_type: mediaType,
|
|
1411
|
+
data: dataUrl.data
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
return toTextBlock(part.image_url.url);
|
|
1416
|
+
}
|
|
1417
|
+
case "document":
|
|
1418
|
+
return toTextBlock("[document]");
|
|
1419
|
+
case "other":
|
|
1420
|
+
if (part.provider === "anthropic" && isAnthropicContentBlock(part.payload)) {
|
|
1421
|
+
return part.payload;
|
|
1422
|
+
}
|
|
1423
|
+
return toTextBlock(formatOtherPart(part));
|
|
1424
|
+
default:
|
|
1425
|
+
return toTextBlock("");
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
};
|
|
1429
|
+
var ensureToolResultBlocks = (blocks) => blocks.length > 0 ? blocks : [toTextBlock("")];
|
|
1430
|
+
var ensureMessageBlocks = (blocks) => blocks.length > 0 ? blocks : [toTextBlock("")];
|
|
1431
|
+
var toToolUseBlock = (call) => {
|
|
1432
|
+
let input = call.function.arguments;
|
|
1433
|
+
if (call.function.arguments) {
|
|
1434
|
+
try {
|
|
1435
|
+
input = JSON.parse(call.function.arguments);
|
|
1436
|
+
} catch {
|
|
1437
|
+
input = { value: call.function.arguments };
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return {
|
|
1441
|
+
type: "tool_use",
|
|
1442
|
+
id: call.id,
|
|
1443
|
+
name: call.function.name,
|
|
1444
|
+
input
|
|
1445
|
+
};
|
|
1446
|
+
};
|
|
1447
|
+
var toToolResultBlock = (message) => {
|
|
1448
|
+
const content = typeof message.content === "string" ? message.content : ensureToolResultBlocks(toContentBlocks(message.content));
|
|
1449
|
+
return {
|
|
1450
|
+
type: "tool_result",
|
|
1451
|
+
tool_use_id: message.tool_call_id,
|
|
1452
|
+
content,
|
|
1453
|
+
...message.is_error ? { is_error: true } : {}
|
|
1454
|
+
};
|
|
1455
|
+
};
|
|
1456
|
+
var toAnthropicTools = (tools) => {
|
|
1457
|
+
if (!tools || tools.length === 0) return void 0;
|
|
1458
|
+
return tools.map((tool) => ({
|
|
1459
|
+
name: tool.name,
|
|
1460
|
+
description: tool.description,
|
|
1461
|
+
input_schema: tool.parameters
|
|
1462
|
+
}));
|
|
1463
|
+
};
|
|
1464
|
+
var toAnthropicToolChoice = (choice) => {
|
|
1465
|
+
if (!choice) return void 0;
|
|
1466
|
+
if (choice === "auto") return { type: "auto" };
|
|
1467
|
+
if (choice === "required") return { type: "any" };
|
|
1468
|
+
if (choice === "none") return void 0;
|
|
1469
|
+
return { type: "tool", name: choice };
|
|
1470
|
+
};
|
|
1471
|
+
var toAnthropicMessages = (messages) => {
|
|
1472
|
+
const systemParts = [];
|
|
1473
|
+
const output = [];
|
|
1474
|
+
for (const message of messages) {
|
|
1475
|
+
switch (message.role) {
|
|
1476
|
+
case "system": {
|
|
1477
|
+
const text = contentPartsToText(message.content);
|
|
1478
|
+
if (text) systemParts.push(text);
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1481
|
+
case "reasoning":
|
|
1482
|
+
break;
|
|
1483
|
+
case "tool": {
|
|
1484
|
+
output.push({
|
|
1485
|
+
role: "user",
|
|
1486
|
+
content: [toToolResultBlock(message)]
|
|
1487
|
+
});
|
|
1488
|
+
break;
|
|
1489
|
+
}
|
|
1490
|
+
case "assistant": {
|
|
1491
|
+
const blocks = [];
|
|
1492
|
+
blocks.push(...toContentBlocks(message.content));
|
|
1493
|
+
if (message.tool_calls?.length) {
|
|
1494
|
+
blocks.push(...message.tool_calls.map(toToolUseBlock));
|
|
1495
|
+
}
|
|
1496
|
+
output.push({
|
|
1497
|
+
role: "assistant",
|
|
1498
|
+
content: ensureMessageBlocks(blocks)
|
|
1499
|
+
});
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
case "user": {
|
|
1503
|
+
const blocks = ensureToolResultBlocks(toContentBlocks(message.content));
|
|
1504
|
+
output.push({
|
|
1505
|
+
role: "user",
|
|
1506
|
+
content: blocks
|
|
1507
|
+
});
|
|
1508
|
+
break;
|
|
1509
|
+
}
|
|
1510
|
+
default:
|
|
1511
|
+
break;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
const system = systemParts.length ? systemParts.join("\n\n") : void 0;
|
|
1515
|
+
return { system, messages: output };
|
|
1516
|
+
};
|
|
1517
|
+
var extractText = (blocks) => blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
1518
|
+
var toUsage = (response) => {
|
|
1519
|
+
if (!response.usage) return null;
|
|
1520
|
+
const inputTokens = response.usage.input_tokens ?? 0;
|
|
1521
|
+
const outputTokens = response.usage.output_tokens ?? 0;
|
|
1522
|
+
return {
|
|
1523
|
+
model: response.model ?? "",
|
|
1524
|
+
input_tokens: inputTokens,
|
|
1525
|
+
input_cached_tokens: response.usage.cache_read_input_tokens ?? null,
|
|
1526
|
+
input_cache_creation_tokens: response.usage.cache_creation_input_tokens ?? null,
|
|
1527
|
+
output_tokens: outputTokens,
|
|
1528
|
+
total_tokens: inputTokens + outputTokens
|
|
1529
|
+
};
|
|
1530
|
+
};
|
|
1531
|
+
var toChatInvokeCompletion = (response) => {
|
|
1532
|
+
const blocks = response.content ?? [];
|
|
1533
|
+
const messages = [];
|
|
1534
|
+
for (const block of blocks) {
|
|
1535
|
+
switch (block.type) {
|
|
1536
|
+
case "text":
|
|
1537
|
+
messages.push({
|
|
1538
|
+
role: "assistant",
|
|
1539
|
+
content: block.text
|
|
1540
|
+
});
|
|
1541
|
+
break;
|
|
1542
|
+
case "tool_use":
|
|
1543
|
+
messages.push({
|
|
1544
|
+
role: "assistant",
|
|
1545
|
+
content: null,
|
|
1546
|
+
tool_calls: [
|
|
1547
|
+
{
|
|
1548
|
+
id: block.id,
|
|
1549
|
+
type: "function",
|
|
1550
|
+
function: {
|
|
1551
|
+
name: block.name,
|
|
1552
|
+
arguments: isRecord(block.input) ? JSON.stringify(block.input) : JSON.stringify({ value: block.input })
|
|
1553
|
+
},
|
|
1554
|
+
provider_meta: block
|
|
1555
|
+
}
|
|
1556
|
+
]
|
|
1557
|
+
});
|
|
1558
|
+
break;
|
|
1559
|
+
case "thinking":
|
|
1560
|
+
messages.push({
|
|
1561
|
+
role: "reasoning",
|
|
1562
|
+
content: block.thinking,
|
|
1563
|
+
raw_item: block
|
|
1564
|
+
});
|
|
1565
|
+
break;
|
|
1566
|
+
case "redacted_thinking":
|
|
1567
|
+
messages.push({
|
|
1568
|
+
role: "reasoning",
|
|
1569
|
+
content: "[redacted]",
|
|
1570
|
+
raw_item: block
|
|
1571
|
+
});
|
|
1572
|
+
break;
|
|
1573
|
+
default:
|
|
1574
|
+
messages.push({
|
|
1575
|
+
role: "assistant",
|
|
1576
|
+
content: [
|
|
1577
|
+
{
|
|
1578
|
+
type: "other",
|
|
1579
|
+
provider: "anthropic",
|
|
1580
|
+
kind: block.type,
|
|
1581
|
+
payload: block
|
|
1582
|
+
}
|
|
1583
|
+
]
|
|
1584
|
+
});
|
|
1585
|
+
break;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
if (messages.length === 0) {
|
|
1589
|
+
const fallback = extractText(blocks);
|
|
1590
|
+
if (fallback) {
|
|
1591
|
+
messages.push({ role: "assistant", content: fallback });
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
return {
|
|
1595
|
+
messages,
|
|
1596
|
+
usage: toUsage(response),
|
|
1597
|
+
stop_reason: response.stop_reason ?? response.stop_sequence ?? null,
|
|
1598
|
+
provider_meta: {
|
|
1599
|
+
response_id: response.id,
|
|
1600
|
+
model: response.model,
|
|
1601
|
+
raw_output_text: stringifyUnknown2(extractText(blocks))
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
// src/llm/anthropic/chat.ts
|
|
1607
|
+
var PROVIDER_NAME = "anthropic";
|
|
1608
|
+
var DEFAULT_MODEL = ANTHROPIC_DEFAULT_MODEL;
|
|
1609
|
+
var DEFAULT_MAX_TOKENS = 4096;
|
|
1610
|
+
var ChatAnthropic = class {
|
|
1611
|
+
provider = PROVIDER_NAME;
|
|
1612
|
+
model;
|
|
1613
|
+
client;
|
|
1614
|
+
defaultMaxTokens;
|
|
1615
|
+
constructor(options = {}) {
|
|
1616
|
+
this.client = options.client ?? new Anthropic(options.clientOptions);
|
|
1617
|
+
this.model = options.model ?? DEFAULT_MODEL;
|
|
1618
|
+
this.defaultMaxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
1619
|
+
}
|
|
1620
|
+
async ainvoke(input, verbose = false) {
|
|
1621
|
+
const {
|
|
1622
|
+
messages,
|
|
1623
|
+
tools: toolDefs,
|
|
1624
|
+
toolChoice,
|
|
1625
|
+
options,
|
|
1626
|
+
model,
|
|
1627
|
+
signal
|
|
1628
|
+
} = input;
|
|
1629
|
+
const { system, messages: anthropicMessages } = toAnthropicMessages(messages);
|
|
1630
|
+
const enableTools = toolChoice !== "none";
|
|
1631
|
+
const tools = enableTools ? toAnthropicTools(toolDefs) : void 0;
|
|
1632
|
+
const tool_choice = enableTools ? toAnthropicToolChoice(toolChoice) : void 0;
|
|
1633
|
+
const { max_tokens, ...rest } = options ?? {};
|
|
1634
|
+
const request = {
|
|
1635
|
+
model: model ?? this.model,
|
|
1636
|
+
max_tokens: max_tokens ?? this.defaultMaxTokens,
|
|
1637
|
+
messages: anthropicMessages,
|
|
1638
|
+
...rest,
|
|
1639
|
+
...system ? { system } : {},
|
|
1640
|
+
...tools ? { tools } : {},
|
|
1641
|
+
...tool_choice ? { tool_choice } : {}
|
|
1642
|
+
};
|
|
1643
|
+
if (verbose) {
|
|
1644
|
+
console.debug(request);
|
|
1645
|
+
}
|
|
1646
|
+
const response = await this.client.messages.create(
|
|
1647
|
+
request,
|
|
1648
|
+
signal ? {
|
|
1649
|
+
signal
|
|
1650
|
+
} : void 0
|
|
1651
|
+
);
|
|
1652
|
+
if (verbose) {
|
|
1653
|
+
console.debug(response);
|
|
1654
|
+
}
|
|
1655
|
+
return toChatInvokeCompletion(response);
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
|
|
1659
|
+
// src/llm/openai/chat.ts
|
|
1660
|
+
import OpenAI from "openai";
|
|
1661
|
+
|
|
1662
|
+
// src/llm/openai/response-utils.ts
|
|
1663
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null;
|
|
1664
|
+
var stringifyUnknown3 = (value) => {
|
|
1665
|
+
if (value == null) return "";
|
|
1666
|
+
if (typeof value === "string") return value;
|
|
1667
|
+
try {
|
|
1668
|
+
return JSON.stringify(value);
|
|
1669
|
+
} catch {
|
|
1670
|
+
return String(value);
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
var formatOtherPart2 = (part) => {
|
|
1674
|
+
const payloadText = stringifyUnknown3(part.payload);
|
|
1675
|
+
return payloadText ? `[other:${part.provider}/${part.kind}] ${payloadText}` : `[other:${part.provider}/${part.kind}]`;
|
|
1676
|
+
};
|
|
1677
|
+
var isOpenAiInputContent = (value) => {
|
|
1678
|
+
if (!isRecord2(value)) return false;
|
|
1679
|
+
const type = value.type;
|
|
1680
|
+
if (type === "input_text") {
|
|
1681
|
+
return typeof value.text === "string";
|
|
1682
|
+
}
|
|
1683
|
+
if (type === "input_image") {
|
|
1684
|
+
return typeof value.image_url === "string";
|
|
1685
|
+
}
|
|
1686
|
+
if (type === "input_file") {
|
|
1687
|
+
return typeof value.file_data === "string" || typeof value.file_id === "string";
|
|
1688
|
+
}
|
|
1689
|
+
return false;
|
|
1690
|
+
};
|
|
1691
|
+
var extractOutputText = (items) => {
|
|
1692
|
+
const texts = [];
|
|
1693
|
+
for (const item of items) {
|
|
1694
|
+
if (item.type !== "message") continue;
|
|
1695
|
+
for (const part of item.content ?? []) {
|
|
1696
|
+
if (part.type === "output_text") {
|
|
1697
|
+
texts.push(part.text);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
return texts.join("");
|
|
1702
|
+
};
|
|
1703
|
+
var toResponseInputContent = (part) => {
|
|
1704
|
+
switch (part.type) {
|
|
1705
|
+
case "text":
|
|
1706
|
+
return { type: "input_text", text: part.text };
|
|
1707
|
+
case "image_url":
|
|
1708
|
+
return {
|
|
1709
|
+
type: "input_image",
|
|
1710
|
+
image_url: part.image_url.url,
|
|
1711
|
+
detail: part.image_url.detail ?? "auto"
|
|
1712
|
+
};
|
|
1713
|
+
case "document":
|
|
1714
|
+
return {
|
|
1715
|
+
type: "input_file",
|
|
1716
|
+
file_data: part.source.data,
|
|
1717
|
+
filename: "document.pdf"
|
|
1718
|
+
};
|
|
1719
|
+
case "other":
|
|
1720
|
+
if (part.provider === "openai" && isOpenAiInputContent(part.payload)) {
|
|
1721
|
+
return part.payload;
|
|
1722
|
+
}
|
|
1723
|
+
return { type: "input_text", text: formatOtherPart2(part) };
|
|
1724
|
+
default:
|
|
1725
|
+
return { type: "input_text", text: "" };
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
var toResponseInputContents = (content) => {
|
|
1729
|
+
if (content == null) {
|
|
1730
|
+
return "";
|
|
1731
|
+
}
|
|
1732
|
+
if (typeof content === "string") {
|
|
1733
|
+
return content;
|
|
1734
|
+
}
|
|
1735
|
+
return content.map(toResponseInputContent);
|
|
1736
|
+
};
|
|
1737
|
+
var toFunctionCallOutput = (content) => {
|
|
1738
|
+
if (typeof content === "string") {
|
|
1739
|
+
return content;
|
|
1740
|
+
}
|
|
1741
|
+
return content.map(toResponseInputContent);
|
|
1742
|
+
};
|
|
1743
|
+
|
|
1744
|
+
// src/llm/openai/serializer.ts
|
|
1745
|
+
var toOpenAiOtherPart = (kind, payload) => ({
|
|
1746
|
+
type: "other",
|
|
1747
|
+
provider: "openai",
|
|
1748
|
+
kind,
|
|
1749
|
+
payload
|
|
1750
|
+
});
|
|
1751
|
+
var toAssistantOutputMessageContent = (item) => {
|
|
1752
|
+
const contents = item.content ?? [];
|
|
1753
|
+
if (contents.length === 0) {
|
|
1754
|
+
return null;
|
|
1755
|
+
}
|
|
1756
|
+
const parts = contents.map((part) => {
|
|
1757
|
+
if (part.type === "output_text") {
|
|
1758
|
+
return { type: "text", text: part.text };
|
|
1759
|
+
}
|
|
1760
|
+
return toOpenAiOtherPart(part.type, part);
|
|
1761
|
+
});
|
|
1762
|
+
return parts;
|
|
1763
|
+
};
|
|
1764
|
+
var stringifyUnknown4 = (value) => {
|
|
1765
|
+
if (value == null) return "";
|
|
1766
|
+
if (typeof value === "string") return value;
|
|
1767
|
+
try {
|
|
1768
|
+
return JSON.stringify(value);
|
|
1769
|
+
} catch {
|
|
1770
|
+
return String(value);
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
var formatOtherPart3 = (part) => {
|
|
1774
|
+
const payloadText = stringifyUnknown4(part.payload);
|
|
1775
|
+
return payloadText ? `[other:${part.provider}/${part.kind}] ${payloadText}` : `[other:${part.provider}/${part.kind}]`;
|
|
1776
|
+
};
|
|
1777
|
+
var isOpenAIAssistantInputContent = (value) => {
|
|
1778
|
+
if (!value || typeof value !== "object") return false;
|
|
1779
|
+
const record = value;
|
|
1780
|
+
if (record.type === "output_text") {
|
|
1781
|
+
return typeof record.text === "string";
|
|
1782
|
+
}
|
|
1783
|
+
if (record.type === "refusal") {
|
|
1784
|
+
return typeof record.refusal === "string";
|
|
1785
|
+
}
|
|
1786
|
+
return false;
|
|
1787
|
+
};
|
|
1788
|
+
var toAssistantInputContent = (part) => {
|
|
1789
|
+
switch (part.type) {
|
|
1790
|
+
case "text":
|
|
1791
|
+
return { type: "output_text", text: part.text };
|
|
1792
|
+
case "other":
|
|
1793
|
+
if (part.provider === "openai" && isOpenAIAssistantInputContent(part.payload)) {
|
|
1794
|
+
return part.payload;
|
|
1795
|
+
}
|
|
1796
|
+
return { type: "output_text", text: formatOtherPart3(part) };
|
|
1797
|
+
case "image_url":
|
|
1798
|
+
return { type: "output_text", text: "[image]" };
|
|
1799
|
+
case "document":
|
|
1800
|
+
return { type: "output_text", text: "[document]" };
|
|
1801
|
+
default:
|
|
1802
|
+
return { type: "output_text", text: "" };
|
|
1803
|
+
}
|
|
1804
|
+
};
|
|
1805
|
+
var toAssistantInputMessageContent = (content, refusal) => {
|
|
1806
|
+
const parts = [];
|
|
1807
|
+
if (typeof content === "string") {
|
|
1808
|
+
if (content) {
|
|
1809
|
+
parts.push({ type: "output_text", text: content });
|
|
1810
|
+
}
|
|
1811
|
+
} else if (Array.isArray(content)) {
|
|
1812
|
+
parts.push(...content.map(toAssistantInputContent));
|
|
1813
|
+
}
|
|
1814
|
+
if (refusal) {
|
|
1815
|
+
parts.push({ type: "refusal", refusal });
|
|
1816
|
+
}
|
|
1817
|
+
return parts;
|
|
1818
|
+
};
|
|
1819
|
+
var extractReasoningText = (item) => {
|
|
1820
|
+
const summaryText = (item.summary ?? []).map(
|
|
1821
|
+
(part) => part && typeof part === "object" && "text" in part ? String(part.text ?? "") : ""
|
|
1822
|
+
).filter((text) => text.length > 0).join("\n");
|
|
1823
|
+
if (summaryText) return summaryText;
|
|
1824
|
+
const contentText = (item.content ?? []).map(
|
|
1825
|
+
(part) => part && typeof part === "object" && "text" in part ? String(part.text ?? "") : ""
|
|
1826
|
+
).filter((text) => text.length > 0).join("\n");
|
|
1827
|
+
return contentText;
|
|
1828
|
+
};
|
|
1829
|
+
var toMessageSequence = (response) => {
|
|
1830
|
+
const messages = [];
|
|
1831
|
+
for (const item of response.output) {
|
|
1832
|
+
switch (item.type) {
|
|
1833
|
+
case "reasoning": {
|
|
1834
|
+
messages.push({
|
|
1835
|
+
role: "reasoning",
|
|
1836
|
+
content: extractReasoningText(item),
|
|
1837
|
+
raw_item: item
|
|
1838
|
+
});
|
|
1839
|
+
break;
|
|
1840
|
+
}
|
|
1841
|
+
case "function_call": {
|
|
1842
|
+
const toolCall = {
|
|
1843
|
+
id: item.call_id,
|
|
1844
|
+
type: "function",
|
|
1845
|
+
function: { name: item.name, arguments: item.arguments },
|
|
1846
|
+
provider_meta: item
|
|
1847
|
+
};
|
|
1848
|
+
messages.push({
|
|
1849
|
+
role: "assistant",
|
|
1850
|
+
content: null,
|
|
1851
|
+
tool_calls: [toolCall]
|
|
1852
|
+
});
|
|
1853
|
+
break;
|
|
1854
|
+
}
|
|
1855
|
+
case "message": {
|
|
1856
|
+
messages.push({
|
|
1857
|
+
role: "assistant",
|
|
1858
|
+
content: toAssistantOutputMessageContent(item)
|
|
1859
|
+
});
|
|
1860
|
+
break;
|
|
1861
|
+
}
|
|
1862
|
+
default: {
|
|
1863
|
+
messages.push({
|
|
1864
|
+
role: "assistant",
|
|
1865
|
+
content: [toOpenAiOtherPart(item.type, item)]
|
|
1866
|
+
});
|
|
1867
|
+
break;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
return messages;
|
|
1872
|
+
};
|
|
1873
|
+
function extractInstructions(messages) {
|
|
1874
|
+
const chunks = [];
|
|
1875
|
+
for (const message of messages) {
|
|
1876
|
+
if (message.role !== "system") {
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
const content = message.content;
|
|
1880
|
+
if (content == null) {
|
|
1881
|
+
continue;
|
|
1882
|
+
}
|
|
1883
|
+
if (typeof content === "string") {
|
|
1884
|
+
const trimmed = content.trim();
|
|
1885
|
+
if (trimmed) {
|
|
1886
|
+
chunks.push(trimmed);
|
|
1887
|
+
}
|
|
1888
|
+
continue;
|
|
1889
|
+
}
|
|
1890
|
+
const text = content.map((part) => {
|
|
1891
|
+
switch (part.type) {
|
|
1892
|
+
case "text":
|
|
1893
|
+
return part.text;
|
|
1894
|
+
default:
|
|
1895
|
+
return "";
|
|
1896
|
+
}
|
|
1897
|
+
}).join("").trim();
|
|
1898
|
+
if (text) {
|
|
1899
|
+
chunks.push(text);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return chunks.length ? chunks.join("\n\n") : void 0;
|
|
1903
|
+
}
|
|
1904
|
+
function toResponsesInput(messages) {
|
|
1905
|
+
const items = [];
|
|
1906
|
+
for (const message of messages) {
|
|
1907
|
+
if (message.role === "system") {
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
if (message.role === "tool") {
|
|
1911
|
+
items.push(toFunctionCallOutputItem(message));
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1914
|
+
if (message.role === "assistant" && message.tool_calls?.length) {
|
|
1915
|
+
const assistantMessageItem = toAssistantMessageItem(message);
|
|
1916
|
+
if (assistantMessageItem) {
|
|
1917
|
+
items.push(assistantMessageItem);
|
|
1918
|
+
}
|
|
1919
|
+
for (const call of message.tool_calls) {
|
|
1920
|
+
items.push(toFunctionCallItem(call));
|
|
1921
|
+
}
|
|
1922
|
+
continue;
|
|
1923
|
+
}
|
|
1924
|
+
if (message.role === "reasoning") {
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
if (message.role === "assistant") {
|
|
1928
|
+
const assistantMessageItem = toAssistantMessageItem(message);
|
|
1929
|
+
if (assistantMessageItem) {
|
|
1930
|
+
items.push(assistantMessageItem);
|
|
1931
|
+
}
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
items.push({
|
|
1935
|
+
type: "message",
|
|
1936
|
+
role: message.role,
|
|
1937
|
+
content: toUserMessageContent(message.content)
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
return items;
|
|
1941
|
+
}
|
|
1942
|
+
function toResponsesTools(tools) {
|
|
1943
|
+
if (!tools || tools.length === 0) {
|
|
1944
|
+
return void 0;
|
|
1945
|
+
}
|
|
1946
|
+
return tools.map((tool) => ({
|
|
1947
|
+
type: "function",
|
|
1948
|
+
name: tool.name,
|
|
1949
|
+
description: tool.description,
|
|
1950
|
+
parameters: tool.parameters,
|
|
1951
|
+
strict: tool.strict ?? false
|
|
1952
|
+
}));
|
|
1953
|
+
}
|
|
1954
|
+
function toResponsesToolChoice(choice) {
|
|
1955
|
+
if (!choice) {
|
|
1956
|
+
return void 0;
|
|
1957
|
+
}
|
|
1958
|
+
if (choice === "auto" || choice === "required" || choice === "none") {
|
|
1959
|
+
return choice;
|
|
1960
|
+
}
|
|
1961
|
+
return { type: "function", name: choice };
|
|
1962
|
+
}
|
|
1963
|
+
function toChatInvokeCompletion2(response) {
|
|
1964
|
+
const usage = response.usage ? {
|
|
1965
|
+
model: response.model,
|
|
1966
|
+
input_tokens: response.usage.input_tokens,
|
|
1967
|
+
input_cached_tokens: response.usage.input_tokens_details?.cached_tokens,
|
|
1968
|
+
output_tokens: response.usage.output_tokens,
|
|
1969
|
+
total_tokens: response.usage.total_tokens
|
|
1970
|
+
} : null;
|
|
1971
|
+
const messages = toMessageSequence(response);
|
|
1972
|
+
if (!messages.length) {
|
|
1973
|
+
const fallbackText = typeof response.output_text === "string" ? response.output_text : extractOutputText(response.output);
|
|
1974
|
+
if (fallbackText) {
|
|
1975
|
+
messages.push({ role: "assistant", content: fallbackText });
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
return {
|
|
1979
|
+
messages,
|
|
1980
|
+
usage,
|
|
1981
|
+
stop_reason: response.incomplete_details?.reason ?? response.status ?? null,
|
|
1982
|
+
provider_meta: {
|
|
1983
|
+
response_id: response.id
|
|
1984
|
+
}
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
function toUserMessageContent(content) {
|
|
1988
|
+
return toResponseInputContents(content);
|
|
1989
|
+
}
|
|
1990
|
+
function toAssistantMessageItem(message) {
|
|
1991
|
+
const content = toAssistantInputMessageContent(
|
|
1992
|
+
message.content,
|
|
1993
|
+
message.refusal
|
|
1994
|
+
);
|
|
1995
|
+
if (content.length === 0) {
|
|
1996
|
+
return null;
|
|
1997
|
+
}
|
|
1998
|
+
return {
|
|
1999
|
+
type: "message",
|
|
2000
|
+
role: "assistant",
|
|
2001
|
+
content
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
function toFunctionCallItem(call) {
|
|
2005
|
+
return {
|
|
2006
|
+
type: "function_call",
|
|
2007
|
+
call_id: call.id,
|
|
2008
|
+
name: call.function.name,
|
|
2009
|
+
arguments: call.function.arguments
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
function toFunctionCallOutputItem(message) {
|
|
2013
|
+
return {
|
|
2014
|
+
type: "function_call_output",
|
|
2015
|
+
call_id: message.tool_call_id,
|
|
2016
|
+
output: toFunctionCallOutput(message.content)
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
// src/llm/openai/chat.ts
|
|
2021
|
+
var PROVIDER_NAME2 = "openai";
|
|
2022
|
+
var DEFAULT_MODEL2 = OPENAI_DEFAULT_MODEL;
|
|
2023
|
+
var DEFAULT_REASONING_EFFORT = OPENAI_DEFAULT_REASONING_EFFORT;
|
|
2024
|
+
var DEFAULT_REASONING_SUMMARY = "auto";
|
|
2025
|
+
var ChatOpenAI = class {
|
|
2026
|
+
provider = PROVIDER_NAME2;
|
|
2027
|
+
model;
|
|
2028
|
+
client;
|
|
2029
|
+
defaultReasoningEffort;
|
|
2030
|
+
defaultTextVerbosity;
|
|
2031
|
+
constructor(options = {}) {
|
|
2032
|
+
this.client = options.client ?? new OpenAI(options.clientOptions);
|
|
2033
|
+
this.model = options.model ?? DEFAULT_MODEL2;
|
|
2034
|
+
this.defaultReasoningEffort = options.reasoningEffort ?? DEFAULT_REASONING_EFFORT;
|
|
2035
|
+
this.defaultTextVerbosity = options.textVerbosity;
|
|
2036
|
+
}
|
|
2037
|
+
async ainvoke(input, verbose = false) {
|
|
2038
|
+
const {
|
|
2039
|
+
messages,
|
|
2040
|
+
tools: toolDefs,
|
|
2041
|
+
toolChoice,
|
|
2042
|
+
options,
|
|
2043
|
+
model,
|
|
2044
|
+
signal
|
|
2045
|
+
} = input;
|
|
2046
|
+
const inputItems = toResponsesInput(messages);
|
|
2047
|
+
const instructions = extractInstructions(messages);
|
|
2048
|
+
const tools = toResponsesTools(toolDefs);
|
|
2049
|
+
const tool_choice = toResponsesToolChoice(toolChoice);
|
|
2050
|
+
const { reasoningEffort, textVerbosity, ...rest } = options ?? {};
|
|
2051
|
+
const request = {
|
|
2052
|
+
model: model ?? this.model,
|
|
2053
|
+
input: inputItems,
|
|
2054
|
+
...rest,
|
|
2055
|
+
...tools ? { tools } : {},
|
|
2056
|
+
...tool_choice ? { tool_choice } : {}
|
|
2057
|
+
};
|
|
2058
|
+
if (request.store === void 0) {
|
|
2059
|
+
request.store = false;
|
|
2060
|
+
}
|
|
2061
|
+
if (instructions) {
|
|
2062
|
+
request.instructions = instructions;
|
|
2063
|
+
}
|
|
2064
|
+
request.include = ["reasoning.encrypted_content"];
|
|
2065
|
+
const effort = reasoningEffort ?? this.defaultReasoningEffort;
|
|
2066
|
+
request.reasoning = { effort, summary: DEFAULT_REASONING_SUMMARY };
|
|
2067
|
+
const verbosity = textVerbosity ?? this.defaultTextVerbosity;
|
|
2068
|
+
if (verbosity) {
|
|
2069
|
+
request.text = {
|
|
2070
|
+
...request.text ?? {},
|
|
2071
|
+
verbosity
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
if (verbose) {
|
|
2075
|
+
console.debug(request);
|
|
2076
|
+
}
|
|
2077
|
+
const streamRequest = {
|
|
2078
|
+
...request,
|
|
2079
|
+
stream: true
|
|
2080
|
+
};
|
|
2081
|
+
const response = await this.client.responses.stream(
|
|
2082
|
+
streamRequest,
|
|
2083
|
+
signal ? {
|
|
2084
|
+
signal
|
|
2085
|
+
} : void 0
|
|
2086
|
+
).finalResponse();
|
|
2087
|
+
if (verbose) {
|
|
2088
|
+
console.debug(response);
|
|
2089
|
+
}
|
|
2090
|
+
return toChatInvokeCompletion2(response);
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
2093
|
+
|
|
2094
|
+
// src/prompts.ts
|
|
2095
|
+
import path from "path";
|
|
2096
|
+
import { fileURLToPath } from "url";
|
|
2097
|
+
var resolvePromptPath = () => {
|
|
2098
|
+
if (typeof __filename === "string") {
|
|
2099
|
+
return path.resolve(path.dirname(__filename), "../prompts/system.md");
|
|
2100
|
+
}
|
|
2101
|
+
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
2102
|
+
return fileURLToPath(new URL("../prompts/system.md", import.meta.url));
|
|
2103
|
+
}
|
|
2104
|
+
return path.resolve("prompts/system.md");
|
|
2105
|
+
};
|
|
2106
|
+
var promptPath = resolvePromptPath();
|
|
2107
|
+
var getDefaultSystemPromptPath = () => promptPath;
|
|
2108
|
+
|
|
2109
|
+
// src/tools/define.ts
|
|
2110
|
+
import { toJSONSchema } from "zod";
|
|
2111
|
+
|
|
2112
|
+
// src/types/llm/guards.ts
|
|
2113
|
+
var TOOL_RESULT_TYPES = /* @__PURE__ */ new Set(["text", "parts", "json"]);
|
|
2114
|
+
function isTextPart(value) {
|
|
2115
|
+
if (!value || typeof value !== "object") return false;
|
|
2116
|
+
const v = value;
|
|
2117
|
+
return v.type === "text" && typeof v.text === "string";
|
|
2118
|
+
}
|
|
2119
|
+
function isImagePart(value) {
|
|
2120
|
+
if (!value || typeof value !== "object") return false;
|
|
2121
|
+
const v = value;
|
|
2122
|
+
if (v.type !== "image_url") return false;
|
|
2123
|
+
if (!v.image_url || typeof v.image_url !== "object") return false;
|
|
2124
|
+
const url = v.image_url.url;
|
|
2125
|
+
return typeof url === "string";
|
|
2126
|
+
}
|
|
2127
|
+
function isDocumentPart(value) {
|
|
2128
|
+
if (!value || typeof value !== "object") return false;
|
|
2129
|
+
const v = value;
|
|
2130
|
+
if (v.type !== "document") return false;
|
|
2131
|
+
if (!v.source || typeof v.source !== "object") return false;
|
|
2132
|
+
const source = v.source;
|
|
2133
|
+
return typeof source.data === "string" && source.media_type === "application/pdf";
|
|
2134
|
+
}
|
|
2135
|
+
function isOtherPart(value) {
|
|
2136
|
+
if (!value || typeof value !== "object") return false;
|
|
2137
|
+
const v = value;
|
|
2138
|
+
return v.type === "other" && typeof v.provider === "string" && typeof v.kind === "string";
|
|
2139
|
+
}
|
|
2140
|
+
function isContentPart(value) {
|
|
2141
|
+
return isTextPart(value) || isImagePart(value) || isDocumentPart(value) || isOtherPart(value);
|
|
2142
|
+
}
|
|
2143
|
+
function isToolResult(value) {
|
|
2144
|
+
if (!value || typeof value !== "object") return false;
|
|
2145
|
+
const v = value;
|
|
2146
|
+
if (!TOOL_RESULT_TYPES.has(String(v.type))) return false;
|
|
2147
|
+
if (v.type === "text")
|
|
2148
|
+
return typeof value.text === "string";
|
|
2149
|
+
if (v.type === "json") return "value" in value;
|
|
2150
|
+
if (v.type === "parts") {
|
|
2151
|
+
const parts = value.parts;
|
|
2152
|
+
return Array.isArray(parts) && parts.every(isContentPart);
|
|
2153
|
+
}
|
|
2154
|
+
return false;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
// src/tools/define.ts
|
|
2158
|
+
var mapSchema = (schema, transform) => {
|
|
2159
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
2160
|
+
const next = transform({ ...schema });
|
|
2161
|
+
if (next.properties) {
|
|
2162
|
+
const updated = {};
|
|
2163
|
+
for (const [key, value] of Object.entries(next.properties)) {
|
|
2164
|
+
updated[key] = mapSchema(value, transform);
|
|
2165
|
+
}
|
|
2166
|
+
next.properties = updated;
|
|
2167
|
+
}
|
|
2168
|
+
if (next.items) {
|
|
2169
|
+
if (Array.isArray(next.items)) {
|
|
2170
|
+
next.items = next.items.map(
|
|
2171
|
+
(item) => mapSchema(item, transform)
|
|
2172
|
+
);
|
|
2173
|
+
} else {
|
|
2174
|
+
next.items = mapSchema(next.items, transform);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
if (next.anyOf) {
|
|
2178
|
+
next.anyOf = next.anyOf.map(
|
|
2179
|
+
(item) => mapSchema(item, transform)
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2182
|
+
if (next.oneOf) {
|
|
2183
|
+
next.oneOf = next.oneOf.map(
|
|
2184
|
+
(item) => mapSchema(item, transform)
|
|
2185
|
+
);
|
|
2186
|
+
}
|
|
2187
|
+
if (next.allOf) {
|
|
2188
|
+
next.allOf = next.allOf.map(
|
|
2189
|
+
(item) => mapSchema(item, transform)
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2192
|
+
if (next.not) {
|
|
2193
|
+
next.not = mapSchema(next.not, transform);
|
|
2194
|
+
}
|
|
2195
|
+
return next;
|
|
2196
|
+
};
|
|
2197
|
+
var withNoAdditionalProperties = (schema) => mapSchema(schema, (next) => {
|
|
2198
|
+
if (next.type === "object" && next.additionalProperties === void 0) {
|
|
2199
|
+
next.additionalProperties = false;
|
|
2200
|
+
}
|
|
2201
|
+
return next;
|
|
2202
|
+
});
|
|
2203
|
+
var withRequiredProperties = (schema) => mapSchema(schema, (next) => {
|
|
2204
|
+
if (next.type === "object" && next.properties) {
|
|
2205
|
+
const propertyKeys = Object.keys(next.properties);
|
|
2206
|
+
if (propertyKeys.length > 0) {
|
|
2207
|
+
next.required = Array.from(
|
|
2208
|
+
/* @__PURE__ */ new Set([...next.required ?? [], ...propertyKeys])
|
|
2209
|
+
);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
return next;
|
|
2213
|
+
});
|
|
2214
|
+
function toToolResult(value) {
|
|
2215
|
+
if (isToolResult(value)) return value;
|
|
2216
|
+
if (typeof value === "string") return { type: "text", text: value };
|
|
2217
|
+
if (Array.isArray(value) && value.every(isContentPart))
|
|
2218
|
+
return { type: "parts", parts: value };
|
|
2219
|
+
return { type: "json", value };
|
|
2220
|
+
}
|
|
2221
|
+
function defineTool(toolOptions) {
|
|
2222
|
+
const parameters = toJSONSchema(toolOptions.input, {
|
|
2223
|
+
target: "draft-07",
|
|
2224
|
+
io: "input"
|
|
2225
|
+
});
|
|
2226
|
+
const strictParameters = withRequiredProperties(
|
|
2227
|
+
withNoAdditionalProperties(parameters)
|
|
2228
|
+
);
|
|
2229
|
+
return {
|
|
2230
|
+
name: toolOptions.name,
|
|
2231
|
+
description: toolOptions.description,
|
|
2232
|
+
definition: {
|
|
2233
|
+
name: toolOptions.name,
|
|
2234
|
+
description: toolOptions.description,
|
|
2235
|
+
parameters: strictParameters,
|
|
2236
|
+
strict: true
|
|
2237
|
+
},
|
|
2238
|
+
executeRaw: (rawArgsJson, ctx) => {
|
|
2239
|
+
let rawArgs;
|
|
2240
|
+
try {
|
|
2241
|
+
rawArgs = JSON.parse(rawArgsJson);
|
|
2242
|
+
} catch (error) {
|
|
2243
|
+
throw new Error(
|
|
2244
|
+
`Invalid tool arguments JSON for ${toolOptions.name}: ${String(error)}`
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
const parsed = toolOptions.input.safeParse(rawArgs);
|
|
2248
|
+
if (!parsed.success) {
|
|
2249
|
+
throw new Error(
|
|
2250
|
+
`Tool input validation failed for ${toolOptions.name}: ${parsed.error.message}`
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2253
|
+
const result = toolOptions.execute(parsed.data, ctx);
|
|
2254
|
+
if (result instanceof Promise) {
|
|
2255
|
+
return result.then((result2) => toToolResult(result2));
|
|
2256
|
+
}
|
|
2257
|
+
return Promise.resolve(toToolResult(result));
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
export {
|
|
2262
|
+
ANTHROPIC_DEFAULT_MODEL,
|
|
2263
|
+
ANTHROPIC_MODELS,
|
|
2264
|
+
Agent,
|
|
2265
|
+
ChatAnthropic,
|
|
2266
|
+
ChatOpenAI,
|
|
2267
|
+
DEFAULT_MODEL_REGISTRY,
|
|
2268
|
+
GOOGLE_MODELS,
|
|
2269
|
+
OPENAI_DEFAULT_MODEL,
|
|
2270
|
+
OPENAI_DEFAULT_REASONING_EFFORT,
|
|
2271
|
+
OPENAI_MODELS,
|
|
2272
|
+
TaskComplete,
|
|
2273
|
+
ToolOutputCacheService,
|
|
2274
|
+
applyModelMetadata,
|
|
2275
|
+
createModelRegistry,
|
|
2276
|
+
defineTool,
|
|
2277
|
+
getDefaultSystemPromptPath,
|
|
2278
|
+
listModels,
|
|
2279
|
+
registerModels,
|
|
2280
|
+
resolveModel,
|
|
2281
|
+
stringifyContent,
|
|
2282
|
+
stringifyContentParts
|
|
2283
|
+
};
|