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