@agentdevjs/llm 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.js ADDED
@@ -0,0 +1,2057 @@
1
+ // src/custom-headers.ts
2
+ import { randomUUID } from "crypto";
3
+ function resolveCustomHeaders(headers) {
4
+ if (!headers || headers.length === 0) return {};
5
+ const result = {};
6
+ for (const h of headers) {
7
+ const key = (h.key ?? "").trim();
8
+ if (!key) continue;
9
+ const mode = h.valueMode ?? "static";
10
+ if (mode === "uuid") {
11
+ result[key] = randomUUID();
12
+ } else if (mode === "random") {
13
+ result[key] = String(Math.floor(Math.random() * 1e16));
14
+ } else {
15
+ result[key] = h.value ?? "";
16
+ }
17
+ }
18
+ return result;
19
+ }
20
+
21
+ // src/anthropic.ts
22
+ import { getRetryDelay, parseRetryAfter, shouldRetry, resolveModelCallPolicy, withDeadline } from "@agentdevjs/core";
23
+ import { classifyAndWrapError } from "@agentdevjs/core";
24
+
25
+ // src/http-client.ts
26
+ import { createLogger } from "@agentdevjs/core";
27
+ var logger = createLogger("llm.http-client");
28
+ var undici;
29
+ var undiciLoadPromise = null;
30
+ var initialGlobalDispatcher = null;
31
+ var HTTP_CONNECT_TIMEOUT_MS = 1e4;
32
+ var HTTP_HEADERS_TIMEOUT_MS = 6e4;
33
+ var HTTP_BODY_TIMEOUT_MS = 6e4;
34
+ function buildHttpDispatcherOptions(noProxy) {
35
+ return {
36
+ ...noProxy ? { noProxy } : {},
37
+ headersTimeout: HTTP_HEADERS_TIMEOUT_MS,
38
+ bodyTimeout: HTTP_BODY_TIMEOUT_MS,
39
+ connect: { timeout: HTTP_CONNECT_TIMEOUT_MS }
40
+ };
41
+ }
42
+ function isExternallyManagedDispatcher(current, initial) {
43
+ return current != null && initial != null && current !== initial;
44
+ }
45
+ async function loadUndici() {
46
+ if (undiciLoadPromise) {
47
+ return undiciLoadPromise;
48
+ }
49
+ undiciLoadPromise = (async () => {
50
+ try {
51
+ const mod = await import("undici");
52
+ return mod;
53
+ } catch {
54
+ try {
55
+ const { createRequire } = await import("module");
56
+ const _require = createRequire(import.meta.url);
57
+ return _require("undici");
58
+ } catch {
59
+ logger.warn("Failed to load undici, proxy and DNS caching will not be available");
60
+ return null;
61
+ }
62
+ }
63
+ })();
64
+ return undiciLoadPromise;
65
+ }
66
+ undici = await loadUndici();
67
+ initialGlobalDispatcher = undici?.getGlobalDispatcher?.() ?? null;
68
+ function getProxyUrl() {
69
+ return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
70
+ }
71
+ function maskProxyUrl(url) {
72
+ try {
73
+ const parsed = new URL(url);
74
+ if (parsed.password) {
75
+ parsed.password = "***";
76
+ }
77
+ return parsed.toString();
78
+ } catch {
79
+ return url;
80
+ }
81
+ }
82
+ var httpClientInitialized = false;
83
+ var _dispatcher = null;
84
+ function getGlobalDispatcher() {
85
+ return _dispatcher;
86
+ }
87
+ async function initHttpClient() {
88
+ if (httpClientInitialized) return;
89
+ httpClientInitialized = true;
90
+ if (!undici) {
91
+ undici = await loadUndici();
92
+ if (!undici) {
93
+ logger.warn("undici not available, skipping HTTP client initialization");
94
+ return;
95
+ }
96
+ }
97
+ const proxyUrl = getProxyUrl();
98
+ try {
99
+ const currentDispatcher = undici.getGlobalDispatcher?.() ?? null;
100
+ if (isExternallyManagedDispatcher(currentDispatcher, initialGlobalDispatcher)) {
101
+ _dispatcher = currentDispatcher;
102
+ logger.info("Using host-managed Undici dispatcher");
103
+ return;
104
+ }
105
+ if (proxyUrl) {
106
+ const proxyAgent = new undici.EnvHttpProxyAgent({
107
+ httpProxy: proxyUrl,
108
+ httpsProxy: proxyUrl,
109
+ ...buildHttpDispatcherOptions(process.env.NO_PROXY || process.env.no_proxy)
110
+ });
111
+ undici.setGlobalDispatcher(proxyAgent);
112
+ _dispatcher = proxyAgent;
113
+ logger.info("Proxy Agent configured (EnvHttpProxyAgent)", {
114
+ proxy: maskProxyUrl(proxyUrl)
115
+ });
116
+ } else {
117
+ const agent = new undici.Agent({
118
+ ...buildHttpDispatcherOptions(),
119
+ keepAliveTimeout: 3e4,
120
+ keepAliveMaxTimeout: 3e5,
121
+ connections: 50,
122
+ pipelining: 1
123
+ });
124
+ undici.setGlobalDispatcher(agent);
125
+ _dispatcher = agent;
126
+ logger.debug("Undici Agent configured (default, with keep-alive)");
127
+ }
128
+ } catch (e) {
129
+ logger.warn("Failed to set up undici dispatcher, proxy env vars will be ignored", {
130
+ error: e.message || String(e)
131
+ });
132
+ }
133
+ }
134
+
135
+ // src/image-resolver.ts
136
+ import { readFileSync } from "fs";
137
+ function resolveImageBase64(img) {
138
+ if (img.base64) return img.base64;
139
+ if (img.path) {
140
+ try {
141
+ const buf = readFileSync(img.path);
142
+ return buf.toString("base64");
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+ return null;
148
+ }
149
+ function resolveImageDataUri(img) {
150
+ const base64 = resolveImageBase64(img);
151
+ if (!base64) return null;
152
+ const mediaType = img.mediaType || "image/png";
153
+ return `data:${mediaType};base64,${base64}`;
154
+ }
155
+
156
+ // src/schema-sanitizer.ts
157
+ function sanitizeToolSchema(parameters) {
158
+ const base = parameters && typeof parameters === "object" ? parameters : { type: "object", properties: {} };
159
+ return stripSchemaMeta(base);
160
+ }
161
+ function stripSchemaMeta(node) {
162
+ if (Array.isArray(node)) {
163
+ return node.map(stripSchemaMeta);
164
+ }
165
+ if (node && typeof node === "object") {
166
+ const out = {};
167
+ for (const [key, value] of Object.entries(node)) {
168
+ if (key === "$schema") continue;
169
+ out[key] = stripSchemaMeta(value);
170
+ }
171
+ return out;
172
+ }
173
+ return node;
174
+ }
175
+
176
+ // src/retry-observability.ts
177
+ import { classifyAPIError } from "@agentdevjs/core";
178
+ async function emitRetryObservability(params) {
179
+ const { attempt, maxRetries, delayMs, signal, error, status } = params;
180
+ const base = {
181
+ attempt,
182
+ maxRetries,
183
+ ...delayMs !== void 0 ? { delayMs } : {},
184
+ ...status !== void 0 ? { statusCode: status } : {},
185
+ errorType: classifyAPIError(error, status)
186
+ };
187
+ await emitRetryNotification({ ...base, phase: "waiting" });
188
+ await sleepQuietly(delayMs, signal);
189
+ await emitRetryNotification({ ...base, phase: "requesting" });
190
+ }
191
+ async function emitRetryNotification(data) {
192
+ try {
193
+ const { emitNotification, createLLMRetry } = await import("@agentdevjs/core");
194
+ emitNotification(createLLMRetry(data));
195
+ } catch {
196
+ }
197
+ }
198
+ async function sleepQuietly(ms, signal) {
199
+ const { sleep: sleep2 } = await import("@agentdevjs/core");
200
+ await sleep2(ms, signal);
201
+ }
202
+
203
+ // src/reminder.ts
204
+ function wrapReminder(text) {
205
+ const trimmed = text.trim();
206
+ return /^<reminder[\s>]/.test(trimmed) ? trimmed : `<reminder>${trimmed}</reminder>`;
207
+ }
208
+
209
+ // src/anthropic.ts
210
+ var httpClientInitPromise = null;
211
+ function ensureHttpClientInitialized() {
212
+ if (!httpClientInitPromise) {
213
+ httpClientInitPromise = initHttpClient();
214
+ }
215
+ return httpClientInitPromise;
216
+ }
217
+ var DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
218
+ var DEFAULT_MAX_TOKENS = 4096;
219
+ var AnthropicLLM = class {
220
+ constructor(apiKey, _modelName = "claude-sonnet-4-5-20250929", baseUrl = DEFAULT_BASE_URL, maxTokens = DEFAULT_MAX_TOKENS, thinkingEffort, _thinkingBudgetTokens, _thinkingKeepTurns = 5, customHeaders, visionEnabled = false, callPolicy) {
221
+ this.apiKey = apiKey;
222
+ this._modelName = _modelName;
223
+ this.baseUrl = baseUrl;
224
+ this.maxTokens = maxTokens;
225
+ this.thinkingEffort = thinkingEffort;
226
+ this._thinkingBudgetTokens = _thinkingBudgetTokens;
227
+ this._thinkingKeepTurns = _thinkingKeepTurns;
228
+ this.customHeaders = customHeaders;
229
+ this.visionEnabled = visionEnabled;
230
+ const resolved = resolveModelCallPolicy(callPolicy);
231
+ this.maxRetries = resolved.maxRetries;
232
+ this.deadlineMs = resolved.timeoutMs;
233
+ this.initPromise = ensureHttpClientInitialized();
234
+ }
235
+ apiKey;
236
+ _modelName;
237
+ baseUrl;
238
+ maxTokens;
239
+ thinkingEffort;
240
+ _thinkingBudgetTokens;
241
+ _thinkingKeepTurns;
242
+ customHeaders;
243
+ visionEnabled;
244
+ initPromise;
245
+ maxRetries;
246
+ deadlineMs;
247
+ /** 返回当前 LLM 实例使用的模型名 */
248
+ get modelName() {
249
+ return this._modelName;
250
+ }
251
+ async chat(messages, tools, options) {
252
+ await this.initPromise;
253
+ const signal = withDeadline(options?.signal, this.deadlineMs);
254
+ const compiled = compileContextForAnthropic(messages, tools, this.visionEnabled);
255
+ const noStream = options?.noStream === true;
256
+ for (let attempt = 1; attempt <= this.maxRetries + 1; attempt++) {
257
+ let response;
258
+ try {
259
+ if (signal?.aborted) {
260
+ throw new DOMException("Aborted", "AbortError");
261
+ }
262
+ const effectiveMaxTokens = this.maxTokens;
263
+ const effort = this.thinkingEffort;
264
+ const validEfforts = ["low", "medium", "high", "xhigh", "max"];
265
+ const wantsThinking = effort !== void 0 && validEfforts.includes(effort);
266
+ const useDisabled = effort === "none";
267
+ const budget = this._thinkingBudgetTokens;
268
+ const useEnabled = wantsThinking && typeof budget === "number" && budget > 0;
269
+ const effectiveBudget = useEnabled ? Math.min(budget, effectiveMaxTokens - 1) : void 0;
270
+ response = await fetch(resolveAnthropicMessagesUrl(this.baseUrl), {
271
+ method: "POST",
272
+ headers: {
273
+ "content-type": "application/json",
274
+ "x-api-key": this.apiKey,
275
+ "anthropic-version": "2023-06-01",
276
+ ...resolveCustomHeaders(this.customHeaders)
277
+ },
278
+ body: JSON.stringify({
279
+ model: this._modelName,
280
+ max_tokens: effectiveMaxTokens,
281
+ ...noStream ? {} : { stream: true },
282
+ ...useDisabled ? { thinking: { type: "disabled" } } : useEnabled ? { thinking: { type: "enabled", budget_tokens: effectiveBudget } } : {},
283
+ ...compiled.system ? { system: compiled.system } : {},
284
+ messages: compiled.messages,
285
+ ...compiled.tools && compiled.tools.length > 0 ? { tools: compiled.tools } : {}
286
+ }),
287
+ signal
288
+ });
289
+ if (!response.ok) {
290
+ const errorText = await response.text();
291
+ const err = new Error(`Anthropic API error ${response.status}: ${errorText}`);
292
+ err.status = response.status;
293
+ throw err;
294
+ }
295
+ if (noStream) {
296
+ const payload = await response.json();
297
+ if (isCompatErrorPayload(payload)) {
298
+ const err = new Error(`Anthropic-compatible API error ${payload.code ?? "unknown"}: ${payload.msg ?? payload.message ?? "unknown error"}`);
299
+ err.status = payload.code;
300
+ throw err;
301
+ }
302
+ return parseAnthropicJsonResponse(payload);
303
+ }
304
+ const contentType = response.headers.get("content-type") || "";
305
+ if (contentType.includes("application/json")) {
306
+ const payload = await response.json();
307
+ if (isCompatErrorPayload(payload)) {
308
+ const err = new Error(`Anthropic-compatible API error ${payload.code ?? "unknown"}: ${payload.msg ?? payload.message ?? "unknown error"}`);
309
+ err.status = payload.code;
310
+ throw err;
311
+ }
312
+ throw new Error(`Anthropic streaming expected SSE but received JSON: ${JSON.stringify(payload)}`);
313
+ }
314
+ if (!response.body) {
315
+ throw new Error("Anthropic API returned an empty response body");
316
+ }
317
+ return await readAnthropicStream(response.body, signal);
318
+ } catch (error) {
319
+ if (error instanceof DOMException && error.name === "AbortError") {
320
+ throw error;
321
+ }
322
+ if (error instanceof Error && error.name === "AbortError") {
323
+ throw error;
324
+ }
325
+ const status = error?.status;
326
+ if (attempt <= this.maxRetries && shouldRetry(error, status)) {
327
+ const retryAfterMs = parseRetryAfter(response?.headers);
328
+ const delayMs = getRetryDelay(attempt, retryAfterMs);
329
+ await emitRetryObservability({
330
+ attempt,
331
+ maxRetries: this.maxRetries,
332
+ delayMs,
333
+ signal,
334
+ error,
335
+ status
336
+ });
337
+ continue;
338
+ }
339
+ throw classifyAndWrapError(error, status);
340
+ }
341
+ }
342
+ throw new Error("Anthropic API call failed after all retries");
343
+ }
344
+ };
345
+ function resolveAnthropicMessagesUrl(baseUrl) {
346
+ const normalized = baseUrl.replace(/\/+$/, "");
347
+ if (/\/v\d+$/i.test(normalized)) {
348
+ return `${normalized}/messages`;
349
+ }
350
+ return `${normalized}/v1/messages`;
351
+ }
352
+ function isCompatErrorPayload(payload) {
353
+ if (payload.success === false) return true;
354
+ if (typeof payload.code === "number" && payload.code !== 0) return true;
355
+ return false;
356
+ }
357
+ function compileContextForAnthropic(messages, tools, visionEnabled = false) {
358
+ const systemBlocks = [];
359
+ const compiledMessages = [];
360
+ let seenFirstUser = false;
361
+ let pendingUserBlocks = [];
362
+ const flushPendingUserBlocks = () => {
363
+ if (pendingUserBlocks.length === 0) return;
364
+ compiledMessages.push({ role: "user", content: pendingUserBlocks });
365
+ pendingUserBlocks = [];
366
+ };
367
+ for (const message of messages) {
368
+ if (!seenFirstUser && message.role === "system") {
369
+ if (!message.source) {
370
+ systemBlocks.push({
371
+ type: "text",
372
+ text: message.content,
373
+ cache_control: { type: "ephemeral" }
374
+ });
375
+ } else {
376
+ pendingUserBlocks.push({
377
+ type: "text",
378
+ text: wrapReminder(message.content)
379
+ });
380
+ }
381
+ continue;
382
+ }
383
+ if (!seenFirstUser && message.role !== "user") {
384
+ throw new Error(`Anthropic compilation requires the first non-system message to be user, got '${message.role}'`);
385
+ }
386
+ switch (message.role) {
387
+ case "system":
388
+ pendingUserBlocks.push({
389
+ type: "text",
390
+ text: wrapReminder(message.content)
391
+ });
392
+ break;
393
+ case "tool":
394
+ pendingUserBlocks.push(toolMessageToAnthropicBlock(message, visionEnabled));
395
+ break;
396
+ case "user": {
397
+ seenFirstUser = true;
398
+ const contentBlocks = [...pendingUserBlocks];
399
+ let textContent = message.content;
400
+ if (message.images && message.images.length > 0) {
401
+ if (visionEnabled) {
402
+ for (const img of message.images) {
403
+ const data = resolveImageBase64(img);
404
+ if (data) {
405
+ contentBlocks.push({
406
+ type: "image",
407
+ source: {
408
+ type: "base64",
409
+ media_type: img.mediaType || "image/png",
410
+ data
411
+ }
412
+ });
413
+ }
414
+ }
415
+ } else {
416
+ const placeholders = message.images.map((img) => `\u3010Image\u3011${img.source || "(inline image)"}`).join("\n");
417
+ textContent = `${message.content}
418
+ ${placeholders}`;
419
+ }
420
+ }
421
+ contentBlocks.push({ type: "text", text: textContent });
422
+ compiledMessages.push({
423
+ role: "user",
424
+ content: contentBlocks.length === 1 ? textContent : contentBlocks
425
+ });
426
+ pendingUserBlocks = [];
427
+ break;
428
+ }
429
+ case "assistant":
430
+ flushPendingUserBlocks();
431
+ compiledMessages.push({
432
+ role: "assistant",
433
+ content: assistantMessageToAnthropicContent(message)
434
+ });
435
+ break;
436
+ default:
437
+ throw new Error(`Anthropic compilation does not support message role '${message.role}'`);
438
+ }
439
+ }
440
+ flushPendingUserBlocks();
441
+ return {
442
+ ...systemBlocks.length > 0 ? { system: systemBlocks } : {},
443
+ messages: compiledMessages,
444
+ ...tools.length > 0 ? { tools: tools.map(toolToAnthropicDefinition) } : {}
445
+ };
446
+ }
447
+ function toolToAnthropicDefinition(tool) {
448
+ return {
449
+ name: tool.name,
450
+ description: tool.description,
451
+ input_schema: sanitizeToolSchema(tool.parameters)
452
+ };
453
+ }
454
+ function assistantMessageToAnthropicContent(message) {
455
+ const blocks = [];
456
+ if (message.thinkingBlocks) {
457
+ for (const thinkingBlock of message.thinkingBlocks) {
458
+ blocks.push({
459
+ type: "thinking",
460
+ thinking: thinkingBlock.thinking,
461
+ signature: thinkingBlock.signature
462
+ });
463
+ }
464
+ }
465
+ if (message.content) {
466
+ blocks.push({ type: "text", text: message.content });
467
+ }
468
+ if (message.toolCalls) {
469
+ for (const toolCall of message.toolCalls) {
470
+ blocks.push({
471
+ type: "tool_use",
472
+ id: toolCall.id,
473
+ name: toolCall.name,
474
+ input: toolCall.arguments
475
+ });
476
+ }
477
+ }
478
+ if (blocks.length === 0) {
479
+ return "";
480
+ }
481
+ if (blocks.length === 1 && blocks[0].type === "text") {
482
+ return blocks[0].text;
483
+ }
484
+ return blocks;
485
+ }
486
+ function toolMessageToAnthropicBlock(message, visionEnabled) {
487
+ if (!message.toolCallId) {
488
+ throw new Error("Anthropic compilation requires tool messages to include toolCallId");
489
+ }
490
+ const parsed = parseToolPayload(message.content);
491
+ const isError = parsed.isError;
492
+ if (!message.images || message.images.length === 0) {
493
+ return {
494
+ type: "tool_result",
495
+ tool_use_id: message.toolCallId,
496
+ content: parsed.content,
497
+ ...isError ? { is_error: true } : {}
498
+ };
499
+ }
500
+ if (visionEnabled) {
501
+ const contentParts = [
502
+ { type: "text", text: parsed.content }
503
+ ];
504
+ for (const img of message.images) {
505
+ const data = resolveImageBase64(img);
506
+ if (data) {
507
+ contentParts.push({
508
+ type: "image",
509
+ source: {
510
+ type: "base64",
511
+ media_type: img.mediaType || "image/png",
512
+ data
513
+ }
514
+ });
515
+ }
516
+ }
517
+ return {
518
+ type: "tool_result",
519
+ tool_use_id: message.toolCallId,
520
+ content: contentParts,
521
+ ...isError ? { is_error: true } : {}
522
+ };
523
+ }
524
+ const placeholders = message.images.map((img) => `\u3010Image\u3011${img.source || "(inline image)"}`).join("\n");
525
+ return {
526
+ type: "tool_result",
527
+ tool_use_id: message.toolCallId,
528
+ content: `${parsed.content}
529
+ ${placeholders}`,
530
+ ...isError ? { is_error: true } : {}
531
+ };
532
+ }
533
+ function parseToolPayload(content) {
534
+ try {
535
+ const parsed = JSON.parse(content);
536
+ if (parsed && typeof parsed === "object" && ("result" in parsed || "error" in parsed || "success" in parsed)) {
537
+ const isError = parsed.success === false || !!parsed.error;
538
+ if (isError && typeof parsed.error === "string" && parsed.error.trim()) {
539
+ return { content: parsed.error, isError: true };
540
+ }
541
+ if (parsed.result !== void 0) {
542
+ return { content: stringifyToolValue(parsed.result), isError };
543
+ }
544
+ return { content, isError };
545
+ }
546
+ } catch {
547
+ }
548
+ return { content, isError: false };
549
+ }
550
+ function stringifyToolValue(value) {
551
+ if (typeof value === "string") return value;
552
+ return JSON.stringify(value);
553
+ }
554
+ function findDoubleNewlineIndex(buffer) {
555
+ for (let i = 0; i < buffer.length - 1; i++) {
556
+ const ch = buffer[i];
557
+ const next = buffer[i + 1];
558
+ if (ch === "\n" && next === "\n") return { index: i, separatorLen: 2 };
559
+ if (ch === "\r" && next === "\r") return { index: i, separatorLen: 2 };
560
+ if (ch === "\r" && next === "\n" && i + 3 < buffer.length && buffer[i + 2] === "\r" && buffer[i + 3] === "\n") {
561
+ return { index: i, separatorLen: 4 };
562
+ }
563
+ }
564
+ return { index: -1, separatorLen: 0 };
565
+ }
566
+ function parseAnthropicJsonResponse(data) {
567
+ let content = "";
568
+ let reasoning = "";
569
+ const toolCalls = [];
570
+ const thinkingBlocks = [];
571
+ if (Array.isArray(data.content)) {
572
+ for (const block of data.content) {
573
+ if (block.type === "text" && typeof block.text === "string") {
574
+ content += block.text;
575
+ } else if (block.type === "thinking") {
576
+ const thinking = typeof block.thinking === "string" ? block.thinking : "";
577
+ const signature = typeof block.signature === "string" ? block.signature : "";
578
+ if (thinking) reasoning += thinking;
579
+ if (signature.length > 0 && thinking.length > 0) {
580
+ thinkingBlocks.push({ thinking, signature });
581
+ }
582
+ } else if (block.type === "tool_use") {
583
+ toolCalls.push({
584
+ id: String(block.id ?? ""),
585
+ name: String(block.name ?? ""),
586
+ arguments: block.input && typeof block.input === "object" && !Array.isArray(block.input) ? block.input : {}
587
+ });
588
+ }
589
+ }
590
+ }
591
+ const usageRaw = data.usage;
592
+ let usageInfo;
593
+ if (usageRaw && (usageRaw.input_tokens !== void 0 || usageRaw.output_tokens !== void 0)) {
594
+ const realInput = (usageRaw.input_tokens || 0) + (usageRaw.cache_creation_input_tokens || 0) + (usageRaw.cache_read_input_tokens || 0);
595
+ usageInfo = {
596
+ inputTokens: realInput,
597
+ outputTokens: usageRaw.output_tokens || 0,
598
+ totalTokens: realInput + (usageRaw.output_tokens || 0),
599
+ ...usageRaw.cache_creation_input_tokens ? { cacheCreationTokens: usageRaw.cache_creation_input_tokens } : {},
600
+ ...usageRaw.cache_read_input_tokens ? { cacheReadTokens: usageRaw.cache_read_input_tokens } : {}
601
+ };
602
+ }
603
+ return {
604
+ content,
605
+ ...toolCalls.length > 0 ? { toolCalls } : {},
606
+ ...reasoning ? { reasoning } : {},
607
+ ...thinkingBlocks.length > 0 ? { thinkingBlocks } : {},
608
+ ...usageInfo ? { usage: usageInfo } : {},
609
+ stopReason: data.stop_reason ?? null
610
+ };
611
+ }
612
+ async function readAnthropicStream(body, signal) {
613
+ const reader = body.getReader();
614
+ const decoder = new TextDecoder();
615
+ let buffer = "";
616
+ let content = "";
617
+ let reasoning = "";
618
+ const pendingThinkingBlocks = /* @__PURE__ */ new Map();
619
+ let charCount = 0;
620
+ let currentPhase = "content";
621
+ const pendingToolUses = /* @__PURE__ */ new Map();
622
+ let usageInfo = null;
623
+ let stopReason = null;
624
+ let receivedMessageStart = false;
625
+ let receivedMessageStop = false;
626
+ const onStreamAbort = () => reader.cancel().catch(() => {
627
+ });
628
+ if (signal && !signal.aborted) {
629
+ signal.addEventListener("abort", onStreamAbort, { once: true });
630
+ }
631
+ const phaseCharCount = (phase) => phase === "thinking" ? reasoning.length : content.length;
632
+ const applyEvent = (event) => {
633
+ if (event.type === "message_start") receivedMessageStart = true;
634
+ if (event.type === "message_stop") receivedMessageStop = true;
635
+ applyAnthropicStreamEvent(event, pendingThinkingBlocks, pendingToolUses, (delta) => {
636
+ content += delta.content;
637
+ reasoning += delta.reasoning;
638
+ charCount += delta.charCount;
639
+ currentPhase = delta.phase;
640
+ }, (usage) => {
641
+ if (usageInfo) {
642
+ const inputTokens = usage.inputTokens || usageInfo.inputTokens;
643
+ const outputTokens = usage.outputTokens || usageInfo.outputTokens;
644
+ usageInfo = {
645
+ ...usageInfo,
646
+ ...usage,
647
+ inputTokens,
648
+ totalTokens: inputTokens + outputTokens
649
+ };
650
+ } else {
651
+ usageInfo = usage;
652
+ }
653
+ }, (reason) => {
654
+ stopReason = reason;
655
+ });
656
+ };
657
+ try {
658
+ while (true) {
659
+ if (signal?.aborted) {
660
+ throw new DOMException("Aborted", "AbortError");
661
+ }
662
+ const { value, done } = await reader.read();
663
+ if (signal?.aborted) {
664
+ throw new DOMException("Aborted", "AbortError");
665
+ }
666
+ if (done) break;
667
+ buffer += decoder.decode(value, { stream: true });
668
+ let sep = findDoubleNewlineIndex(buffer);
669
+ while (sep.index >= 0) {
670
+ const rawEvent = buffer.slice(0, sep.index);
671
+ buffer = buffer.slice(sep.index + sep.separatorLen);
672
+ const event = parseSSEEvent(rawEvent);
673
+ if (event) {
674
+ applyEvent(event);
675
+ const toolNames = Array.from(pendingToolUses.values()).map((t) => t.name).filter(Boolean);
676
+ await emitAnthropicProgress(
677
+ phaseCharCount(currentPhase),
678
+ currentPhase,
679
+ pendingToolUses.size,
680
+ toolNames,
681
+ reasoning.length,
682
+ content.length
683
+ );
684
+ }
685
+ sep = findDoubleNewlineIndex(buffer);
686
+ }
687
+ }
688
+ } catch (e) {
689
+ if (e instanceof DOMException && e.name === "AbortError") throw e;
690
+ if (e instanceof Error && e.name === "AbortError") throw e;
691
+ throw e;
692
+ } finally {
693
+ signal?.removeEventListener("abort", onStreamAbort);
694
+ }
695
+ if (buffer.trim()) {
696
+ const event = parseSSEEvent(buffer);
697
+ if (event) {
698
+ applyEvent(event);
699
+ const toolNames = Array.from(pendingToolUses.values()).map((t) => t.name).filter(Boolean);
700
+ await emitAnthropicProgress(
701
+ phaseCharCount(currentPhase),
702
+ currentPhase,
703
+ pendingToolUses.size,
704
+ toolNames,
705
+ reasoning.length,
706
+ content.length
707
+ );
708
+ }
709
+ }
710
+ if (receivedMessageStart && !receivedMessageStop && !content && pendingToolUses.size === 0 && !reasoning) {
711
+ throw new Error("Anthropic stream ended incompletely: received message_start but no message_stop or content");
712
+ }
713
+ const toolCalls = finalizeToolCalls(pendingToolUses);
714
+ const thinkingBlocks = finalizeThinkingBlocks(pendingThinkingBlocks);
715
+ await emitAnthropicComplete(charCount);
716
+ return {
717
+ content,
718
+ ...toolCalls.length > 0 ? { toolCalls } : {},
719
+ ...reasoning ? { reasoning } : {},
720
+ ...thinkingBlocks.length > 0 ? { thinkingBlocks } : {},
721
+ ...usageInfo ? { usage: usageInfo } : {},
722
+ stopReason
723
+ };
724
+ }
725
+ function parseSSEEvent(rawEvent) {
726
+ const dataLines = rawEvent.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
727
+ if (dataLines.length === 0) return null;
728
+ const data = dataLines.join("\n");
729
+ if (data === "[DONE]") return null;
730
+ try {
731
+ return JSON.parse(data);
732
+ } catch {
733
+ console.warn(`[Anthropic] Skipping malformed SSE event: ${data.slice(0, 200)}`);
734
+ return null;
735
+ }
736
+ }
737
+ function applyAnthropicStreamEvent(event, pendingThinkingBlocks, pendingToolUses, append, onUsage, onStopReason) {
738
+ switch (event.type) {
739
+ case "content_block_start": {
740
+ if (event.content_block?.type === "thinking" && typeof event.index === "number") {
741
+ pendingThinkingBlocks.set(event.index, {
742
+ thinking: event.content_block.thinking || "",
743
+ signature: event.content_block.signature || ""
744
+ });
745
+ }
746
+ if (event.content_block?.type === "tool_use" && typeof event.index === "number") {
747
+ pendingToolUses.set(event.index, {
748
+ id: event.content_block.id,
749
+ name: event.content_block.name,
750
+ // Always start with empty string — the API sends input: {} at
751
+ // content_block_start but actual content arrives via
752
+ // input_json_delta events. Initializing with a stringified
753
+ // non-empty object would corrupt the accumulated JSON when
754
+ // deltas are appended. (Matches Claude Code's approach.)
755
+ inputJson: ""
756
+ });
757
+ }
758
+ break;
759
+ }
760
+ case "content_block_delta": {
761
+ const deltaType = event.delta?.type;
762
+ if (deltaType === "text_delta") {
763
+ const text = event.delta?.text ?? "";
764
+ append({ content: text, reasoning: "", charCount: text.length, phase: "content" });
765
+ } else if (deltaType === "thinking_delta") {
766
+ const thinking = event.delta?.thinking ?? "";
767
+ if (typeof event.index === "number") {
768
+ const block = pendingThinkingBlocks.get(event.index) ?? { thinking: "", signature: "" };
769
+ block.thinking += thinking;
770
+ pendingThinkingBlocks.set(event.index, block);
771
+ }
772
+ append({ content: "", reasoning: thinking, charCount: thinking.length, phase: "thinking" });
773
+ } else if (deltaType === "signature_delta" && typeof event.index === "number") {
774
+ const block = pendingThinkingBlocks.get(event.index) ?? { thinking: "", signature: "" };
775
+ block.signature += event.delta.signature ?? "";
776
+ pendingThinkingBlocks.set(event.index, block);
777
+ } else if (deltaType === "input_json_delta" && typeof event.index === "number") {
778
+ const toolUse = pendingToolUses.get(event.index);
779
+ if (toolUse) {
780
+ const partial = event.delta?.partial_json ?? "";
781
+ toolUse.inputJson = mergeToolInputJson(toolUse.inputJson, partial);
782
+ append({ content: "", reasoning: "", charCount: 0, phase: "tool_calling" });
783
+ } else {
784
+ console.warn(`[Anthropic] input_json_delta for unknown content block index ${event.index}, skipping`);
785
+ }
786
+ }
787
+ break;
788
+ }
789
+ case "message_start": {
790
+ const usage = event.message?.usage;
791
+ if (usage && (usage.input_tokens !== void 0 || usage.output_tokens !== void 0)) {
792
+ const realInput = (usage.input_tokens || 0) + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
793
+ onUsage({
794
+ inputTokens: realInput,
795
+ outputTokens: usage.output_tokens || 0,
796
+ totalTokens: realInput + (usage.output_tokens || 0),
797
+ ...usage.cache_creation_input_tokens ? { cacheCreationTokens: usage.cache_creation_input_tokens } : {},
798
+ ...usage.cache_read_input_tokens ? { cacheReadTokens: usage.cache_read_input_tokens } : {}
799
+ });
800
+ }
801
+ break;
802
+ }
803
+ case "message_delta": {
804
+ if (event.delta?.stop_reason && onStopReason) {
805
+ onStopReason(event.delta.stop_reason);
806
+ }
807
+ const usage = event.usage;
808
+ if (usage && (usage.input_tokens !== void 0 || usage.output_tokens !== void 0)) {
809
+ const realInput = (usage.input_tokens || 0) + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
810
+ onUsage({
811
+ inputTokens: realInput,
812
+ outputTokens: usage.output_tokens || 0,
813
+ totalTokens: realInput + (usage.output_tokens || 0),
814
+ ...usage.cache_creation_input_tokens ? { cacheCreationTokens: usage.cache_creation_input_tokens } : {},
815
+ ...usage.cache_read_input_tokens ? { cacheReadTokens: usage.cache_read_input_tokens } : {}
816
+ });
817
+ }
818
+ break;
819
+ }
820
+ default:
821
+ break;
822
+ }
823
+ }
824
+ async function emitAnthropicProgress(charCount, phase, toolCallCount, toolNames, thinkingChars, contentChars) {
825
+ try {
826
+ const { emitNotification, createLLMCharCount } = await import("@agentdevjs/core");
827
+ if (charCount > 0 || toolCallCount > 0) {
828
+ emitNotification(createLLMCharCount(charCount, phase, {
829
+ toolCallCount,
830
+ ...typeof thinkingChars === "number" ? { thinkingChars } : {},
831
+ ...typeof contentChars === "number" ? { contentChars } : {},
832
+ ...Array.isArray(toolNames) && toolNames.length > 0 ? { streamToolNames: toolNames } : {}
833
+ }));
834
+ }
835
+ } catch {
836
+ }
837
+ }
838
+ async function emitAnthropicComplete(charCount) {
839
+ try {
840
+ const { emitNotification, createLLMComplete } = await import("@agentdevjs/core");
841
+ emitNotification(createLLMComplete(charCount));
842
+ } catch {
843
+ }
844
+ }
845
+ function mergeToolInputJson(current, partial) {
846
+ if (!current.trim()) return partial;
847
+ return current + partial;
848
+ }
849
+ function finalizeThinkingBlocks(pendingThinkingBlocks) {
850
+ return Array.from(pendingThinkingBlocks.entries()).sort((a, b) => a[0] - b[0]).map(([, block]) => ({
851
+ signature: block.signature,
852
+ thinking: block.thinking
853
+ })).filter((block) => block.signature.length > 0 && block.thinking.length > 0);
854
+ }
855
+ function finalizeToolCalls(pendingToolUses) {
856
+ return Array.from(pendingToolUses.entries()).sort((a, b) => a[0] - b[0]).map(([, toolUse]) => ({
857
+ id: toolUse.id,
858
+ name: toolUse.name,
859
+ arguments: parseToolInput(toolUse.inputJson, toolUse.name)
860
+ }));
861
+ }
862
+ function stripBOM(str) {
863
+ return str.replace(/^/, "");
864
+ }
865
+ function safeParseJSON(json) {
866
+ if (!json) return null;
867
+ try {
868
+ return JSON.parse(stripBOM(json));
869
+ } catch {
870
+ return null;
871
+ }
872
+ }
873
+ function parseToolInput(inputJson, toolName) {
874
+ const trimmed = inputJson.trim();
875
+ if (!trimmed) return {};
876
+ const parsed = safeParseJSON(trimmed);
877
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
878
+ return parsed;
879
+ }
880
+ const braceStart = trimmed.indexOf("{");
881
+ if (braceStart >= 0) {
882
+ let depth = 0;
883
+ let inString = false;
884
+ let escape = false;
885
+ for (let i = braceStart; i < trimmed.length; i++) {
886
+ const ch = trimmed[i];
887
+ if (escape) {
888
+ escape = false;
889
+ continue;
890
+ }
891
+ if (ch === "\\") {
892
+ if (inString) escape = true;
893
+ continue;
894
+ }
895
+ if (ch === '"') {
896
+ inString = !inString;
897
+ continue;
898
+ }
899
+ if (inString) continue;
900
+ if (ch === "{" || ch === "[") depth++;
901
+ else if (ch === "}" || ch === "]") {
902
+ depth--;
903
+ if (depth === 0) {
904
+ const candidate = trimmed.slice(braceStart, i + 1);
905
+ const recovered = safeParseJSON(candidate);
906
+ if (recovered !== null && typeof recovered === "object" && !Array.isArray(recovered)) {
907
+ return recovered;
908
+ }
909
+ break;
910
+ }
911
+ }
912
+ }
913
+ }
914
+ console.warn(
915
+ `[Anthropic] Failed to parse tool input JSON${toolName ? ` for tool "${toolName}"` : ""}. Input length: ${trimmed.length}, preview: ${trimmed.slice(0, 200)}`
916
+ );
917
+ return {};
918
+ }
919
+ function createAnthropicLLM(configOrApiKey, modelName, baseUrl) {
920
+ if (typeof configOrApiKey === "object" && "defaultModel" in configOrApiKey) {
921
+ return new AnthropicLLM(
922
+ configOrApiKey.defaultModel.apiKey,
923
+ configOrApiKey.defaultModel.model,
924
+ configOrApiKey.defaultModel.baseUrl,
925
+ configOrApiKey.defaultModel.maxTokens && configOrApiKey.defaultModel.maxTokens > 0 ? configOrApiKey.defaultModel.maxTokens : DEFAULT_MAX_TOKENS,
926
+ configOrApiKey.defaultModel.thinkingEffort,
927
+ configOrApiKey.defaultModel.thinkingBudgetTokens,
928
+ configOrApiKey.defaultModel.thinkingKeepTurns ?? 5,
929
+ configOrApiKey.defaultModel.customHeaders,
930
+ configOrApiKey.defaultModel.vision ?? false,
931
+ { maxRetries: configOrApiKey.defaultModel.maxRetries, timeoutMs: configOrApiKey.defaultModel.timeoutMs }
932
+ );
933
+ }
934
+ if (typeof configOrApiKey === "object") {
935
+ return new AnthropicLLM(
936
+ configOrApiKey.apiKey,
937
+ configOrApiKey.model,
938
+ configOrApiKey.baseUrl,
939
+ configOrApiKey.maxTokens && configOrApiKey.maxTokens > 0 ? configOrApiKey.maxTokens : DEFAULT_MAX_TOKENS,
940
+ configOrApiKey.thinkingEffort,
941
+ configOrApiKey.thinkingBudgetTokens,
942
+ configOrApiKey.thinkingKeepTurns ?? 5,
943
+ configOrApiKey.customHeaders,
944
+ configOrApiKey.vision ?? false,
945
+ { maxRetries: configOrApiKey.maxRetries, timeoutMs: configOrApiKey.timeoutMs }
946
+ );
947
+ }
948
+ return new AnthropicLLM(configOrApiKey, modelName, baseUrl);
949
+ }
950
+
951
+ // src/openai.ts
952
+ import { OPENAI_THINKING_EFFORTS } from "@agentdevjs/core";
953
+ import OpenAI from "openai";
954
+ import { getRetryDelay as getRetryDelay2, parseRetryAfter as parseRetryAfter2, shouldRetry as shouldRetry2, resolveModelCallPolicy as resolveModelCallPolicy2, withDeadline as withDeadline2 } from "@agentdevjs/core";
955
+ import { classifyAndWrapError as classifyAndWrapError2 } from "@agentdevjs/core";
956
+ var httpClientInitPromise2 = null;
957
+ function ensureHttpClientInitialized2() {
958
+ if (!httpClientInitPromise2) {
959
+ httpClientInitPromise2 = initHttpClient();
960
+ }
961
+ return httpClientInitPromise2;
962
+ }
963
+ function compileChatMessages(messages, visionEnabled = false) {
964
+ const systemPromptParts = [];
965
+ const compiled = [];
966
+ let seenFirstUser = false;
967
+ let pendingReminders = [];
968
+ const flushRemindersAsUserMessage = () => {
969
+ if (pendingReminders.length === 0) return;
970
+ compiled.push({ role: "user", content: pendingReminders.join("\n\n") });
971
+ pendingReminders = [];
972
+ };
973
+ for (const m of messages) {
974
+ if (m.role === "system") {
975
+ if (!seenFirstUser && !m.source) {
976
+ systemPromptParts.push(m.content);
977
+ } else {
978
+ pendingReminders.push(wrapReminder(m.content));
979
+ }
980
+ continue;
981
+ }
982
+ if (m.role === "tool") {
983
+ compiled.push({ role: "tool", content: m.content, tool_call_id: m.toolCallId });
984
+ if (m.images && m.images.length > 0) {
985
+ if (visionEnabled) {
986
+ const parts = [
987
+ { type: "text", text: `[Tool image result for ${m.toolCallId}]` }
988
+ ];
989
+ for (const img of m.images) {
990
+ const url = resolveImageDataUri(img) || img.source;
991
+ if (url) {
992
+ parts.push({ type: "image_url", image_url: { url } });
993
+ }
994
+ }
995
+ compiled.push({ role: "user", content: parts });
996
+ } else {
997
+ const placeholders = m.images.map((img) => `\u3010Image\u3011${img.source || "(inline image)"}`).join("\n");
998
+ compiled.push({ role: "user", content: `[Tool image placeholders]
999
+ ${placeholders}` });
1000
+ }
1001
+ }
1002
+ continue;
1003
+ }
1004
+ if (m.role === "assistant" && Array.isArray(m.toolCalls) && m.toolCalls.length > 0) {
1005
+ compiled.push({
1006
+ role: "assistant",
1007
+ content: m.content ?? "",
1008
+ tool_calls: m.toolCalls.map((tc) => ({
1009
+ id: tc.id,
1010
+ type: "function",
1011
+ function: {
1012
+ name: tc.name,
1013
+ arguments: JSON.stringify(tc.arguments ?? {})
1014
+ }
1015
+ }))
1016
+ });
1017
+ continue;
1018
+ }
1019
+ if (m.role === "user" && m.images && m.images.length > 0) {
1020
+ seenFirstUser = true;
1021
+ const prefix = pendingReminders.length > 0 ? `${pendingReminders.join("\n\n")}
1022
+
1023
+ ` : "";
1024
+ pendingReminders = [];
1025
+ if (visionEnabled) {
1026
+ const parts = [];
1027
+ if (m.content) {
1028
+ parts.push({ type: "text", text: `${prefix}${m.content}` });
1029
+ } else if (prefix) {
1030
+ parts.push({ type: "text", text: prefix.trimEnd() });
1031
+ }
1032
+ for (const img of m.images) {
1033
+ const url = resolveImageDataUri(img) || img.source;
1034
+ if (url) {
1035
+ parts.push({ type: "image_url", image_url: { url } });
1036
+ }
1037
+ }
1038
+ compiled.push({ role: "user", content: parts });
1039
+ } else {
1040
+ const placeholders = m.images.map((img) => `\u3010Image\u3011${img.source || "(inline image)"}`).join("\n");
1041
+ compiled.push({ role: "user", content: `${prefix}${m.content}
1042
+ ${placeholders}` });
1043
+ }
1044
+ continue;
1045
+ }
1046
+ if (m.role === "user") {
1047
+ seenFirstUser = true;
1048
+ if (pendingReminders.length > 0) {
1049
+ compiled.push({ role: "user", content: `${pendingReminders.join("\n\n")}
1050
+
1051
+ ${m.content}` });
1052
+ pendingReminders = [];
1053
+ } else {
1054
+ compiled.push({ role: "user", content: m.content });
1055
+ }
1056
+ continue;
1057
+ }
1058
+ compiled.push({ role: m.role, content: m.content });
1059
+ }
1060
+ flushRemindersAsUserMessage();
1061
+ const result = [];
1062
+ if (systemPromptParts.length > 0) {
1063
+ result.push({ role: "system", content: systemPromptParts.join("\n\n") });
1064
+ }
1065
+ result.push(...compiled);
1066
+ return result;
1067
+ }
1068
+ var OpenAILLM = class {
1069
+ client;
1070
+ _modelName;
1071
+ maxTokens;
1072
+ thinkingEffort;
1073
+ providerOptions;
1074
+ customHeaders;
1075
+ visionEnabled;
1076
+ initPromise;
1077
+ maxRetries;
1078
+ deadlineMs;
1079
+ /** 返回当前 LLM 实例使用的模型名 */
1080
+ get modelName() {
1081
+ return this._modelName;
1082
+ }
1083
+ constructor(apiKey, modelName = "gpt-4o", baseUrl, maxTokens, thinkingEffort, providerOptions, customHeaders, visionEnabled = false, callPolicy) {
1084
+ const resolved = resolveModelCallPolicy2(callPolicy);
1085
+ this.maxRetries = resolved.maxRetries;
1086
+ this.deadlineMs = resolved.timeoutMs;
1087
+ this.client = new OpenAI({
1088
+ apiKey,
1089
+ baseURL: baseUrl,
1090
+ // 通过自定义 fetch 注入动态请求头,使 uuid / random 模式在每次请求时重新生成
1091
+ ...customHeaders && customHeaders.length > 0 ? {
1092
+ fetch: (input, init) => {
1093
+ init = init || {};
1094
+ const headers = new Headers(init.headers);
1095
+ for (const [k, v] of Object.entries(resolveCustomHeaders(customHeaders))) {
1096
+ headers.set(k, v);
1097
+ }
1098
+ headers.delete("content-length");
1099
+ init.headers = headers;
1100
+ return globalThis.fetch(input, init);
1101
+ }
1102
+ } : {}
1103
+ });
1104
+ this._modelName = modelName;
1105
+ this.maxTokens = maxTokens;
1106
+ this.thinkingEffort = thinkingEffort;
1107
+ this.providerOptions = providerOptions;
1108
+ this.customHeaders = customHeaders;
1109
+ this.visionEnabled = visionEnabled;
1110
+ this.initPromise = ensureHttpClientInitialized2();
1111
+ }
1112
+ /**
1113
+ * 聊天 - 核心方法(内部使用流式处理,带重试)
1114
+ */
1115
+ async chat(messages, tools, options) {
1116
+ await this.initPromise;
1117
+ const signal = withDeadline2(options?.signal, this.deadlineMs);
1118
+ const chatMessages = compileChatMessages(messages, this.visionEnabled);
1119
+ const chatTools = tools.map((t) => ({
1120
+ type: "function",
1121
+ function: {
1122
+ name: t.name,
1123
+ description: t.description,
1124
+ parameters: sanitizeToolSchema(t.parameters)
1125
+ }
1126
+ }));
1127
+ const requestBody = {
1128
+ model: this._modelName,
1129
+ messages: chatMessages,
1130
+ tools: chatTools.length > 0 ? chatTools : void 0,
1131
+ stream: true,
1132
+ stream_options: { include_usage: true },
1133
+ ...this.maxTokens ? { max_tokens: this.maxTokens } : {},
1134
+ ...this.thinkingEffort && OPENAI_THINKING_EFFORTS.includes(this.thinkingEffort) ? { reasoning_effort: this.thinkingEffort } : {},
1135
+ ...this.providerOptions ?? {}
1136
+ };
1137
+ for (let attempt = 1; attempt <= this.maxRetries + 1; attempt++) {
1138
+ try {
1139
+ if (signal?.aborted) {
1140
+ throw new DOMException("Aborted", "AbortError");
1141
+ }
1142
+ const stream = await this.client.chat.completions.create(requestBody, {
1143
+ signal
1144
+ });
1145
+ let content = "";
1146
+ let reasoning = "";
1147
+ let currentPhase = "content";
1148
+ const accumulatedToolCalls = /* @__PURE__ */ new Map();
1149
+ let usageInfo = null;
1150
+ let finishReason = null;
1151
+ for await (const chunk of stream) {
1152
+ if (signal?.aborted) {
1153
+ throw new DOMException("Aborted", "AbortError");
1154
+ }
1155
+ if (chunk.usage) {
1156
+ const u = chunk.usage;
1157
+ const extendedDetails = u;
1158
+ let reasoningTokens = 0;
1159
+ if (extendedDetails.prompt_tokens_details?.reasoning_tokens) {
1160
+ reasoningTokens += extendedDetails.prompt_tokens_details.reasoning_tokens;
1161
+ }
1162
+ if (extendedDetails.completion_tokens_details?.reasoning_tokens) {
1163
+ reasoningTokens += extendedDetails.completion_tokens_details.reasoning_tokens;
1164
+ }
1165
+ usageInfo = {
1166
+ inputTokens: u.prompt_tokens || 0,
1167
+ outputTokens: u.completion_tokens || 0,
1168
+ totalTokens: (u.prompt_tokens || 0) + (u.completion_tokens || 0),
1169
+ ...reasoningTokens > 0 ? { reasoningTokens } : {}
1170
+ };
1171
+ }
1172
+ const delta = chunk.choices[0]?.delta;
1173
+ if (!delta) {
1174
+ continue;
1175
+ }
1176
+ const rawDelta = delta;
1177
+ if (rawDelta.reasoning_content) {
1178
+ currentPhase = "thinking";
1179
+ reasoning += rawDelta.reasoning_content;
1180
+ }
1181
+ if (delta.content) {
1182
+ currentPhase = "content";
1183
+ content += delta.content;
1184
+ }
1185
+ if (delta.tool_calls) {
1186
+ currentPhase = "tool_calling";
1187
+ for (const toolCall of delta.tool_calls) {
1188
+ let index = toolCall.index;
1189
+ if (index === void 0 || index === null) {
1190
+ if (toolCall.id) {
1191
+ const existingIndex = Array.from(accumulatedToolCalls.entries()).find(([, v]) => v.id === toolCall.id)?.[0];
1192
+ index = existingIndex ?? accumulatedToolCalls.size;
1193
+ } else {
1194
+ index = Math.max(accumulatedToolCalls.size - 1, 0);
1195
+ }
1196
+ }
1197
+ if (!accumulatedToolCalls.has(index)) {
1198
+ accumulatedToolCalls.set(index, {
1199
+ id: toolCall.id || "",
1200
+ name: toolCall.function?.name || "",
1201
+ arguments: toolCall.function?.arguments || ""
1202
+ });
1203
+ } else {
1204
+ const accumulated = accumulatedToolCalls.get(index);
1205
+ if (toolCall.id) accumulated.id = toolCall.id;
1206
+ if (toolCall.function?.name) accumulated.name += toolCall.function.name;
1207
+ if (toolCall.function?.arguments) accumulated.arguments += toolCall.function.arguments;
1208
+ }
1209
+ }
1210
+ }
1211
+ try {
1212
+ const { emitNotification, createLLMCharCount } = await import("@agentdevjs/core");
1213
+ const phaseCharCount = currentPhase === "thinking" ? reasoning.length : content.length;
1214
+ if (phaseCharCount > 0 || accumulatedToolCalls.size > 0) {
1215
+ const toolNames = Array.from(accumulatedToolCalls.values()).map((tc) => tc.name).filter(Boolean);
1216
+ emitNotification(createLLMCharCount(phaseCharCount, currentPhase, {
1217
+ thinkingChars: reasoning.length,
1218
+ contentChars: content.length,
1219
+ toolCallCount: accumulatedToolCalls.size,
1220
+ ...toolNames.length > 0 ? { streamToolNames: toolNames } : {}
1221
+ }));
1222
+ }
1223
+ } catch {
1224
+ }
1225
+ if (chunk.choices[0]?.finish_reason) {
1226
+ finishReason = chunk.choices[0].finish_reason;
1227
+ }
1228
+ }
1229
+ let toolCalls;
1230
+ if (accumulatedToolCalls.size > 0) {
1231
+ toolCalls = Array.from(accumulatedToolCalls.values()).map((tc) => {
1232
+ let parsedArgs = {};
1233
+ const argStr = tc.arguments.trim();
1234
+ if (argStr) {
1235
+ try {
1236
+ parsedArgs = JSON.parse(argStr);
1237
+ } catch {
1238
+ console.warn(
1239
+ `[OpenAI] Failed to parse tool arguments for tool "${tc.name}". Arguments length: ${argStr.length}, preview: ${argStr.slice(0, 200)}`
1240
+ );
1241
+ }
1242
+ }
1243
+ return {
1244
+ id: tc.id,
1245
+ name: tc.name,
1246
+ arguments: parsedArgs
1247
+ };
1248
+ });
1249
+ }
1250
+ return {
1251
+ content,
1252
+ toolCalls,
1253
+ reasoning,
1254
+ ...usageInfo ? { usage: usageInfo } : {},
1255
+ stopReason: finishReason
1256
+ };
1257
+ } catch (error) {
1258
+ if (error instanceof DOMException && error.name === "AbortError") {
1259
+ throw error;
1260
+ }
1261
+ if (error instanceof Error && error.name === "AbortError") {
1262
+ throw error;
1263
+ }
1264
+ const status = error?.status;
1265
+ if (attempt <= this.maxRetries && shouldRetry2(error, status)) {
1266
+ const retryAfterMs = parseRetryAfter2(error?.headers);
1267
+ const delayMs = getRetryDelay2(attempt, retryAfterMs);
1268
+ await emitRetryObservability({
1269
+ attempt,
1270
+ maxRetries: this.maxRetries,
1271
+ delayMs,
1272
+ signal,
1273
+ error,
1274
+ status
1275
+ });
1276
+ continue;
1277
+ }
1278
+ throw classifyAndWrapError2(error, status);
1279
+ }
1280
+ }
1281
+ throw new Error("OpenAI API call failed after all retries");
1282
+ }
1283
+ };
1284
+ function createOpenAILLM(configOrApiKey, modelName, baseUrl) {
1285
+ if (typeof configOrApiKey === "object" && "defaultModel" in configOrApiKey) {
1286
+ return new OpenAILLM(
1287
+ configOrApiKey.defaultModel.apiKey,
1288
+ configOrApiKey.defaultModel.model,
1289
+ configOrApiKey.defaultModel.baseUrl,
1290
+ configOrApiKey.defaultModel.maxTokens,
1291
+ configOrApiKey.defaultModel.thinkingEffort,
1292
+ configOrApiKey.defaultModel.providerOptions,
1293
+ configOrApiKey.defaultModel.customHeaders,
1294
+ configOrApiKey.defaultModel.vision ?? false,
1295
+ { maxRetries: configOrApiKey.defaultModel.maxRetries, timeoutMs: configOrApiKey.defaultModel.timeoutMs }
1296
+ );
1297
+ }
1298
+ if (typeof configOrApiKey === "object") {
1299
+ return new OpenAILLM(
1300
+ configOrApiKey.apiKey,
1301
+ configOrApiKey.model,
1302
+ configOrApiKey.baseUrl,
1303
+ configOrApiKey.maxTokens,
1304
+ configOrApiKey.thinkingEffort,
1305
+ configOrApiKey.providerOptions,
1306
+ configOrApiKey.customHeaders,
1307
+ configOrApiKey.vision ?? false,
1308
+ { maxRetries: configOrApiKey.maxRetries, timeoutMs: configOrApiKey.timeoutMs }
1309
+ );
1310
+ }
1311
+ return new OpenAILLM(configOrApiKey, modelName, baseUrl);
1312
+ }
1313
+
1314
+ // src/openai-responses.ts
1315
+ import OpenAI2 from "openai";
1316
+ import { OPENAI_THINKING_EFFORTS as OPENAI_THINKING_EFFORTS2 } from "@agentdevjs/core";
1317
+ import { getRetryDelay as getRetryDelay3, parseRetryAfter as parseRetryAfter3, shouldRetry as shouldRetry3, resolveModelCallPolicy as resolveModelCallPolicy3, withDeadline as withDeadline3 } from "@agentdevjs/core";
1318
+ import { classifyAndWrapError as classifyAndWrapError3 } from "@agentdevjs/core";
1319
+ var httpClientInitPromise3 = null;
1320
+ function ensureHttpClientInitialized3() {
1321
+ if (!httpClientInitPromise3) {
1322
+ httpClientInitPromise3 = initHttpClient();
1323
+ }
1324
+ return httpClientInitPromise3;
1325
+ }
1326
+ var DEFAULT_CODEX_INSTRUCTIONS = "You are a helpful assistant.";
1327
+ var OpenAIResponsesLLM = class {
1328
+ client;
1329
+ _modelName;
1330
+ maxTokens;
1331
+ thinkingEffort;
1332
+ thinkingBudgetTokens;
1333
+ providerOptions;
1334
+ customHeaders;
1335
+ visionEnabled;
1336
+ responsesProfile;
1337
+ initPromise;
1338
+ maxRetries;
1339
+ deadlineMs;
1340
+ /** 返回当前 LLM 实例使用的模型名 */
1341
+ get modelName() {
1342
+ return this._modelName;
1343
+ }
1344
+ constructor(apiKey, modelName = "gpt-4o", baseUrl, maxTokens, thinkingEffort, thinkingBudgetTokens, providerOptions, customHeaders, visionEnabled = false, responsesProfile = "standard", callPolicy) {
1345
+ const resolved = resolveModelCallPolicy3(callPolicy);
1346
+ this.maxRetries = resolved.maxRetries;
1347
+ this.deadlineMs = resolved.timeoutMs;
1348
+ this.client = new OpenAI2({
1349
+ apiKey,
1350
+ baseURL: baseUrl,
1351
+ ...customHeaders && customHeaders.length > 0 ? {
1352
+ fetch: (input, init) => {
1353
+ init = init || {};
1354
+ const headers = new Headers(init.headers);
1355
+ for (const [k, v] of Object.entries(resolveCustomHeaders(customHeaders))) {
1356
+ headers.set(k, v);
1357
+ }
1358
+ headers.delete("content-length");
1359
+ init.headers = headers;
1360
+ return globalThis.fetch(input, init);
1361
+ }
1362
+ } : {}
1363
+ });
1364
+ this._modelName = modelName;
1365
+ this.maxTokens = maxTokens;
1366
+ this.thinkingEffort = thinkingEffort;
1367
+ this.thinkingBudgetTokens = thinkingBudgetTokens;
1368
+ this.providerOptions = providerOptions;
1369
+ this.customHeaders = customHeaders;
1370
+ this.visionEnabled = visionEnabled;
1371
+ this.responsesProfile = responsesProfile;
1372
+ this.initPromise = ensureHttpClientInitialized3();
1373
+ }
1374
+ async chat(messages, tools, options) {
1375
+ await this.initPromise;
1376
+ const compiled = compileContextForOpenAIResponses(messages, tools, {
1377
+ modelName: this._modelName,
1378
+ maxTokens: this.maxTokens,
1379
+ thinkingEffort: this.thinkingEffort,
1380
+ thinkingBudgetTokens: this.thinkingBudgetTokens,
1381
+ providerOptions: this.providerOptions,
1382
+ visionEnabled: this.visionEnabled,
1383
+ responsesProfile: this.responsesProfile
1384
+ });
1385
+ let preferNonStreaming = false;
1386
+ const signal = withDeadline3(options?.signal, this.deadlineMs);
1387
+ for (let attempt = 1; attempt <= this.maxRetries + 1; attempt++) {
1388
+ let sawAnyStreamEvent = false;
1389
+ try {
1390
+ if (signal?.aborted) {
1391
+ throw new DOMException("Aborted", "AbortError");
1392
+ }
1393
+ if (preferNonStreaming) {
1394
+ return await this.createResponsesCompletion(compiled, options);
1395
+ }
1396
+ const stream = this.client.responses.stream(
1397
+ {
1398
+ ...compiled,
1399
+ stream: true
1400
+ },
1401
+ { signal }
1402
+ );
1403
+ let content = "";
1404
+ let reasoning = "";
1405
+ let currentPhase = "content";
1406
+ let stopReason = null;
1407
+ let usageInfo = null;
1408
+ let completedSnapshot = null;
1409
+ let completedText = "";
1410
+ const functionCalls = /* @__PURE__ */ new Map();
1411
+ const reasoningItems = /* @__PURE__ */ new Map();
1412
+ for await (const event of stream) {
1413
+ sawAnyStreamEvent = true;
1414
+ if (signal?.aborted) {
1415
+ throw new DOMException("Aborted", "AbortError");
1416
+ }
1417
+ switch (event.type) {
1418
+ case "response.output_text.delta": {
1419
+ const delta = String(event.delta || "");
1420
+ if (delta) {
1421
+ currentPhase = "content";
1422
+ content += delta;
1423
+ }
1424
+ break;
1425
+ }
1426
+ case "response.output_text.done": {
1427
+ completedText = String(event.text || completedText || "");
1428
+ break;
1429
+ }
1430
+ case "response.function_call_arguments.delta": {
1431
+ const key = String(event.item_id || event.output_index);
1432
+ currentPhase = "tool_calling";
1433
+ if (!functionCalls.has(key)) {
1434
+ functionCalls.set(key, {
1435
+ call_id: "",
1436
+ name: "",
1437
+ arguments: ""
1438
+ });
1439
+ }
1440
+ functionCalls.get(key).arguments += String(event.delta || "");
1441
+ break;
1442
+ }
1443
+ case "response.function_call_arguments.done": {
1444
+ const key = String(event.item_id || event.output_index);
1445
+ if (!functionCalls.has(key)) {
1446
+ functionCalls.set(key, {
1447
+ call_id: "",
1448
+ name: "",
1449
+ arguments: ""
1450
+ });
1451
+ }
1452
+ functionCalls.get(key).arguments = String(event.arguments || functionCalls.get(key).arguments || "");
1453
+ break;
1454
+ }
1455
+ case "response.output_item.added":
1456
+ case "response.output_item.done": {
1457
+ const item = event.item || {};
1458
+ if (item.type === "function_call") {
1459
+ const key = String(item.id || event.output_index);
1460
+ currentPhase = "tool_calling";
1461
+ functionCalls.set(key, {
1462
+ call_id: String(item.call_id || ""),
1463
+ name: String(item.name || ""),
1464
+ arguments: String(item.arguments || "")
1465
+ });
1466
+ } else if (item.type === "reasoning") {
1467
+ recordReasoningItem(reasoningItems, item);
1468
+ } else if (item.type === "message" && event.type === "response.output_item.done") {
1469
+ completedText = extractOutputMessageText(item) || completedText;
1470
+ }
1471
+ break;
1472
+ }
1473
+ case "response.reasoning_summary.delta":
1474
+ case "response.reasoning_summary_text.delta": {
1475
+ const key = String(event.item_id || event.output_index);
1476
+ if (!reasoningItems.has(key)) {
1477
+ reasoningItems.set(key, { id: String(event.item_id || ""), summary: [] });
1478
+ }
1479
+ const delta = extractTextDelta(event.delta);
1480
+ if (delta) {
1481
+ const entry = reasoningItems.get(key);
1482
+ if (entry.summary.length === 0) {
1483
+ entry.summary.push(delta);
1484
+ } else {
1485
+ entry.summary[entry.summary.length - 1] += delta;
1486
+ }
1487
+ currentPhase = "thinking";
1488
+ reasoning += delta;
1489
+ }
1490
+ break;
1491
+ }
1492
+ case "response.reasoning_summary.done": {
1493
+ const key = String(event.item_id || event.output_index);
1494
+ const text = String(event.text || "");
1495
+ if (!reasoningItems.has(key)) {
1496
+ reasoningItems.set(key, { id: String(event.item_id || ""), summary: [] });
1497
+ }
1498
+ const entry = reasoningItems.get(key);
1499
+ if (text) {
1500
+ entry.summary = [text];
1501
+ currentPhase = "thinking";
1502
+ reasoning += text;
1503
+ }
1504
+ break;
1505
+ }
1506
+ case "response.reasoning_summary_text.done": {
1507
+ const key = String(event.item_id || event.output_index);
1508
+ const text = String(event.text || "");
1509
+ if (!reasoningItems.has(key)) {
1510
+ reasoningItems.set(key, { id: String(event.item_id || ""), summary: [] });
1511
+ }
1512
+ const entry = reasoningItems.get(key);
1513
+ if (text) {
1514
+ entry.summary = [text];
1515
+ currentPhase = "thinking";
1516
+ reasoning += text;
1517
+ }
1518
+ break;
1519
+ }
1520
+ case "response.completed": {
1521
+ completedSnapshot = event.response;
1522
+ stopReason = mapResponseStatusToStopReason(completedSnapshot);
1523
+ break;
1524
+ }
1525
+ case "response.failed": {
1526
+ completedSnapshot = event.response;
1527
+ stopReason = "failed";
1528
+ break;
1529
+ }
1530
+ case "response.incomplete": {
1531
+ completedSnapshot = event.response;
1532
+ stopReason = mapResponseStatusToStopReason(completedSnapshot);
1533
+ break;
1534
+ }
1535
+ default:
1536
+ break;
1537
+ }
1538
+ try {
1539
+ const { emitNotification, createLLMCharCount } = await import("@agentdevjs/core");
1540
+ const phaseCharCount = currentPhase === "thinking" ? reasoning.length : content.length;
1541
+ if (phaseCharCount > 0 || functionCalls.size > 0) {
1542
+ const toolNames = Array.from(functionCalls.values()).map((fc) => fc.name).filter(Boolean);
1543
+ emitNotification(createLLMCharCount(phaseCharCount, currentPhase, {
1544
+ thinkingChars: reasoning.length,
1545
+ contentChars: content.length,
1546
+ toolCallCount: functionCalls.size,
1547
+ ...toolNames.length > 0 ? { streamToolNames: toolNames } : {}
1548
+ }));
1549
+ }
1550
+ } catch {
1551
+ }
1552
+ }
1553
+ const parsedSnapshot = completedSnapshot ? parseOpenAIResponsesSnapshot(completedSnapshot) : null;
1554
+ if (parsedSnapshot?.usage) {
1555
+ usageInfo = parsedSnapshot.usage;
1556
+ }
1557
+ const parsedToolCalls = parsedSnapshot?.toolCalls?.length ? parsedSnapshot.toolCalls : finalizeToolCalls2(functionCalls);
1558
+ const parsedThinkingBlocks = parsedSnapshot?.thinkingBlocks?.length ? parsedSnapshot.thinkingBlocks : finalizeThinkingBlocks2(reasoningItems);
1559
+ const finalContent = parsedSnapshot?.content || content || completedText;
1560
+ const finalReasoning = parsedSnapshot?.reasoning || reasoning;
1561
+ const finalStopReason = parsedSnapshot?.stopReason ?? stopReason;
1562
+ return {
1563
+ content: finalContent,
1564
+ ...parsedToolCalls.length > 0 ? { toolCalls: parsedToolCalls } : {},
1565
+ ...finalReasoning ? { reasoning: finalReasoning } : {},
1566
+ ...parsedThinkingBlocks.length > 0 ? { thinkingBlocks: parsedThinkingBlocks } : {},
1567
+ ...usageInfo ? { usage: usageInfo } : {},
1568
+ stopReason: finalStopReason
1569
+ };
1570
+ } catch (error) {
1571
+ if (error instanceof DOMException && error.name === "AbortError") {
1572
+ throw error;
1573
+ }
1574
+ if (error instanceof Error && error.name === "AbortError") {
1575
+ throw error;
1576
+ }
1577
+ if (isEmptyResponsesStreamError(error) && !sawAnyStreamEvent) {
1578
+ preferNonStreaming = true;
1579
+ try {
1580
+ return await this.createResponsesCompletion(compiled, options);
1581
+ } catch (fallbackError) {
1582
+ if (fallbackError instanceof DOMException && fallbackError.name === "AbortError") {
1583
+ throw fallbackError;
1584
+ }
1585
+ if (fallbackError instanceof Error && fallbackError.name === "AbortError") {
1586
+ throw fallbackError;
1587
+ }
1588
+ const fallbackStatus = fallbackError?.status;
1589
+ if (attempt <= this.maxRetries && shouldRetry3(fallbackError, fallbackStatus)) {
1590
+ const retryAfterMs = parseRetryAfter3(fallbackError?.headers);
1591
+ const delayMs = getRetryDelay3(attempt, retryAfterMs);
1592
+ await emitRetryObservability({
1593
+ attempt,
1594
+ maxRetries: this.maxRetries,
1595
+ delayMs,
1596
+ signal,
1597
+ error: fallbackError,
1598
+ status: fallbackStatus
1599
+ });
1600
+ continue;
1601
+ }
1602
+ throw classifyAndWrapError3(fallbackError, fallbackStatus);
1603
+ }
1604
+ }
1605
+ const status = error?.status;
1606
+ if (attempt <= this.maxRetries && shouldRetry3(error, status)) {
1607
+ const retryAfterMs = parseRetryAfter3(error?.headers);
1608
+ const delayMs = getRetryDelay3(attempt, retryAfterMs);
1609
+ await emitRetryObservability({
1610
+ attempt,
1611
+ maxRetries: this.maxRetries,
1612
+ delayMs,
1613
+ signal,
1614
+ error,
1615
+ status
1616
+ });
1617
+ continue;
1618
+ }
1619
+ throw classifyAndWrapError3(error, status);
1620
+ }
1621
+ }
1622
+ throw new Error("OpenAI Responses API call failed after all retries");
1623
+ }
1624
+ async createResponsesCompletion(compiled, options) {
1625
+ const response = await this.client.responses.create(compiled, { signal: options?.signal });
1626
+ const parsedSnapshot = parseOpenAIResponsesSnapshot(response);
1627
+ return {
1628
+ content: parsedSnapshot.content,
1629
+ ...parsedSnapshot.toolCalls.length > 0 ? { toolCalls: parsedSnapshot.toolCalls } : {},
1630
+ ...parsedSnapshot.reasoning ? { reasoning: parsedSnapshot.reasoning } : {},
1631
+ ...parsedSnapshot.thinkingBlocks.length > 0 ? { thinkingBlocks: parsedSnapshot.thinkingBlocks } : {},
1632
+ ...parsedSnapshot.usage ? { usage: parsedSnapshot.usage } : {},
1633
+ stopReason: parsedSnapshot.stopReason
1634
+ };
1635
+ }
1636
+ };
1637
+ function compileContextForOpenAIResponses(messages, tools, options = {}) {
1638
+ const input = [];
1639
+ const responsesProfile = options.responsesProfile ?? "standard";
1640
+ const codexInstructionParts = [];
1641
+ let reachedConversation = false;
1642
+ for (const message of messages) {
1643
+ if (!message) continue;
1644
+ if (message.role === "system") {
1645
+ if (responsesProfile === "codex" && !reachedConversation && !message.source) {
1646
+ const instruction = String(message.content ?? "").trim();
1647
+ if (instruction) codexInstructionParts.push(instruction);
1648
+ continue;
1649
+ }
1650
+ input.push({
1651
+ type: "message",
1652
+ // ChatGPT's Codex endpoint only accepts the stable, leading system
1653
+ // identity through `instructions`. Runtime reminders can be injected
1654
+ // after the conversation starts without a `source` marker, so every
1655
+ // remaining system message must be replayed as user input.
1656
+ role: responsesProfile === "codex" ? "user" : "system",
1657
+ content: [
1658
+ {
1659
+ type: "input_text",
1660
+ text: String(message.content ?? "")
1661
+ }
1662
+ ]
1663
+ });
1664
+ if (responsesProfile === "codex") reachedConversation = true;
1665
+ continue;
1666
+ }
1667
+ reachedConversation = true;
1668
+ if (message.role === "user") {
1669
+ const visionEnabled = options.visionEnabled ?? false;
1670
+ let textContent = String(message.content ?? "");
1671
+ if (message.images && message.images.length > 0) {
1672
+ if (visionEnabled) {
1673
+ const contentParts = [
1674
+ { type: "input_text", text: textContent }
1675
+ ];
1676
+ for (const img of message.images) {
1677
+ const url = resolveImageDataUri(img) || img.source;
1678
+ if (url) {
1679
+ contentParts.push({ type: "input_image", image_url: url, detail: "auto" });
1680
+ }
1681
+ }
1682
+ input.push({ type: "message", role: "user", content: contentParts });
1683
+ continue;
1684
+ }
1685
+ const placeholders = message.images.map((img) => `\u3010Image\u3011${img.source || "(inline image)"}`).join("\n");
1686
+ textContent = `${textContent}
1687
+ ${placeholders}`;
1688
+ }
1689
+ input.push({
1690
+ type: "message",
1691
+ role: "user",
1692
+ content: [{ type: "input_text", text: textContent }]
1693
+ });
1694
+ continue;
1695
+ }
1696
+ if (message.role === "assistant") {
1697
+ const assistantItems = compileAssistantMessageToResponsesItems(message, responsesProfile);
1698
+ input.push(...assistantItems);
1699
+ continue;
1700
+ }
1701
+ if (message.role === "tool") {
1702
+ if (!message.toolCallId) {
1703
+ throw new Error("OpenAI Responses compilation requires tool messages to include toolCallId");
1704
+ }
1705
+ input.push({
1706
+ type: "function_call_output",
1707
+ call_id: message.toolCallId,
1708
+ output: String(message.content ?? "")
1709
+ });
1710
+ if (message.images && message.images.length > 0) {
1711
+ const visionEnabled = options.visionEnabled ?? false;
1712
+ if (visionEnabled) {
1713
+ const contentParts = [
1714
+ { type: "input_text", text: `[Tool image result for ${message.toolCallId}]` }
1715
+ ];
1716
+ for (const img of message.images) {
1717
+ const url = resolveImageDataUri(img) || img.source;
1718
+ if (url) {
1719
+ contentParts.push({ type: "input_image", image_url: url, detail: "auto" });
1720
+ }
1721
+ }
1722
+ input.push({ type: "message", role: "user", content: contentParts });
1723
+ } else {
1724
+ const placeholders = message.images.map((img) => `\u3010Image\u3011${img.source || "(inline image)"}`).join("\n");
1725
+ input.push({
1726
+ type: "message",
1727
+ role: "user",
1728
+ content: [{ type: "input_text", text: `[Tool image placeholders]
1729
+ ${placeholders}` }]
1730
+ });
1731
+ }
1732
+ }
1733
+ continue;
1734
+ }
1735
+ input.push({
1736
+ type: "message",
1737
+ role: "user",
1738
+ content: [
1739
+ {
1740
+ type: "input_text",
1741
+ text: String(message.content ?? "")
1742
+ }
1743
+ ]
1744
+ });
1745
+ }
1746
+ const compiledTools = tools.length > 0 ? tools.map((tool) => ({
1747
+ type: "function",
1748
+ name: tool.name,
1749
+ description: tool.description,
1750
+ parameters: normalizeToolParameters(tool.parameters),
1751
+ strict: false
1752
+ })) : void 0;
1753
+ const request = {
1754
+ model: options.modelName || "gpt-4o",
1755
+ input,
1756
+ ...compiledTools ? { tools: compiledTools } : {},
1757
+ ...responsesProfile !== "codex" && typeof options.maxTokens === "number" && Number.isFinite(options.maxTokens) && options.maxTokens > 0 ? { max_output_tokens: options.maxTokens } : {},
1758
+ ...tools.length > 0 ? {
1759
+ parallel_tool_calls: true,
1760
+ ...responsesProfile === "codex" ? { tool_choice: "auto" } : {}
1761
+ } : {},
1762
+ ...options.thinkingEffort && OPENAI_THINKING_EFFORTS2.includes(options.thinkingEffort) ? {
1763
+ reasoning: {
1764
+ effort: options.thinkingEffort,
1765
+ summary: "auto"
1766
+ }
1767
+ } : typeof options.thinkingBudgetTokens === "number" && options.thinkingBudgetTokens > 0 ? {
1768
+ reasoning: {
1769
+ effort: mapThinkingBudgetToEffort(options.thinkingBudgetTokens),
1770
+ summary: "auto"
1771
+ }
1772
+ } : {},
1773
+ ...options.providerOptions ?? {}
1774
+ };
1775
+ if (responsesProfile === "codex") {
1776
+ request.model = options.modelName || "gpt-4o";
1777
+ request.instructions = codexInstructionParts.join("\n\n").trim() || DEFAULT_CODEX_INSTRUCTIONS;
1778
+ request.input = input;
1779
+ request.store = false;
1780
+ }
1781
+ return request;
1782
+ }
1783
+ function compileAssistantMessageToResponsesItems(message, responsesProfile) {
1784
+ const items = [];
1785
+ const reasoningParts = [];
1786
+ if (typeof message.reasoning === "string" && message.reasoning.trim()) {
1787
+ reasoningParts.push(message.reasoning.trim());
1788
+ }
1789
+ if (Array.isArray(message.thinkingBlocks)) {
1790
+ for (const block of message.thinkingBlocks) {
1791
+ if (block?.thinking?.trim()) {
1792
+ reasoningParts.push(block.thinking.trim());
1793
+ }
1794
+ }
1795
+ }
1796
+ if (reasoningParts.length > 0 && responsesProfile !== "codex") {
1797
+ items.push({
1798
+ type: "reasoning",
1799
+ id: `reasoning-${items.length}`,
1800
+ summary: reasoningParts.map((text) => ({
1801
+ type: "summary_text",
1802
+ text
1803
+ })),
1804
+ status: "completed"
1805
+ });
1806
+ }
1807
+ if (typeof message.content === "string" && message.content.trim()) {
1808
+ items.push({
1809
+ type: "message",
1810
+ role: "assistant",
1811
+ content: [
1812
+ {
1813
+ type: "output_text",
1814
+ text: message.content,
1815
+ ...responsesProfile === "standard" ? { annotations: [] } : {}
1816
+ }
1817
+ ]
1818
+ });
1819
+ }
1820
+ if (Array.isArray(message.toolCalls)) {
1821
+ for (const toolCall of message.toolCalls) {
1822
+ items.push({
1823
+ type: "function_call",
1824
+ call_id: toolCall.id,
1825
+ name: toolCall.name,
1826
+ arguments: JSON.stringify(toolCall.arguments ?? {})
1827
+ });
1828
+ }
1829
+ }
1830
+ if (items.length === 0 && responsesProfile === "standard") {
1831
+ items.push({
1832
+ type: "message",
1833
+ role: "assistant",
1834
+ content: []
1835
+ });
1836
+ }
1837
+ return items;
1838
+ }
1839
+ function parseOpenAIResponsesSnapshot(snapshot) {
1840
+ const output = Array.isArray(snapshot?.output) ? snapshot.output : [];
1841
+ const contentParts = [];
1842
+ const reasoningParts = [];
1843
+ const toolCalls = [];
1844
+ const thinkingBlocks = [];
1845
+ for (const item of output) {
1846
+ if (!item || typeof item !== "object") continue;
1847
+ if (item.type === "message" && Array.isArray(item.content)) {
1848
+ for (const part of item.content) {
1849
+ if (part?.type === "output_text" && typeof part.text === "string") {
1850
+ contentParts.push(part.text);
1851
+ }
1852
+ }
1853
+ continue;
1854
+ }
1855
+ if (item.type === "function_call") {
1856
+ toolCalls.push({
1857
+ id: String(item.call_id || item.id || ""),
1858
+ name: String(item.name || ""),
1859
+ arguments: parseToolArguments(String(item.arguments || ""), item.name)
1860
+ });
1861
+ continue;
1862
+ }
1863
+ if (item.type === "reasoning") {
1864
+ const summaryText = Array.isArray(item.summary) ? item.summary.map((part) => String(part?.text || "")).filter(Boolean).join("\n").trim() : "";
1865
+ if (summaryText) {
1866
+ reasoningParts.push(summaryText);
1867
+ thinkingBlocks.push({
1868
+ signature: String(item.id || ""),
1869
+ thinking: summaryText
1870
+ });
1871
+ }
1872
+ }
1873
+ }
1874
+ const usage = snapshot.usage ? {
1875
+ inputTokens: Number(snapshot.usage.input_tokens || 0),
1876
+ outputTokens: Number(snapshot.usage.output_tokens || 0),
1877
+ totalTokens: Number(snapshot.usage.total_tokens || 0),
1878
+ ...Number(snapshot.usage.input_tokens_details?.cached_tokens || 0) > 0 ? { cacheReadTokens: Number(snapshot.usage.input_tokens_details?.cached_tokens || 0) } : {},
1879
+ ...Number(snapshot.usage.output_tokens_details?.reasoning_tokens || 0) > 0 ? { reasoningTokens: Number(snapshot.usage.output_tokens_details?.reasoning_tokens || 0) } : {}
1880
+ } : void 0;
1881
+ const content = typeof snapshot.output_text === "string" && snapshot.output_text.trim() ? snapshot.output_text : contentParts.join("");
1882
+ return {
1883
+ content,
1884
+ toolCalls,
1885
+ ...reasoningParts.length > 0 ? { reasoning: reasoningParts.join("\n") } : {},
1886
+ thinkingBlocks,
1887
+ ...usage ? { usage } : {},
1888
+ stopReason: mapResponseStatusToStopReason(snapshot)
1889
+ };
1890
+ }
1891
+ function finalizeToolCalls2(functionCalls) {
1892
+ return Array.from(functionCalls.values()).map((call) => ({
1893
+ id: call.call_id || call.name || `tool-${Math.random().toString(36).slice(2, 8)}`,
1894
+ name: call.name,
1895
+ arguments: parseToolArguments(call.arguments, call.name)
1896
+ })).filter((call) => Boolean(call.name));
1897
+ }
1898
+ function finalizeThinkingBlocks2(reasoningItems) {
1899
+ return Array.from(reasoningItems.values()).map((item) => ({
1900
+ signature: item.id,
1901
+ thinking: item.summary.join("\n").trim()
1902
+ })).filter((block) => Boolean(block.signature) && Boolean(block.thinking));
1903
+ }
1904
+ function recordReasoningItem(reasoningItems, item) {
1905
+ const id = String(item?.id || "");
1906
+ if (!id) return;
1907
+ const summary = Array.isArray(item.summary) ? item.summary.map((part) => String(part?.text || "")).filter(Boolean) : [];
1908
+ if (!reasoningItems.has(id)) {
1909
+ reasoningItems.set(id, { id, summary: [] });
1910
+ }
1911
+ const entry = reasoningItems.get(id);
1912
+ if (summary.length > 0) {
1913
+ entry.summary = summary;
1914
+ }
1915
+ }
1916
+ function extractTextDelta(delta) {
1917
+ if (typeof delta === "string") return delta;
1918
+ if (delta && typeof delta === "object" && "text" in delta && typeof delta.text === "string") {
1919
+ return delta.text;
1920
+ }
1921
+ return "";
1922
+ }
1923
+ function extractOutputMessageText(item) {
1924
+ if (!Array.isArray(item?.content)) return "";
1925
+ return item.content.filter((part) => part?.type === "output_text" && typeof part.text === "string").map((part) => part.text).join("");
1926
+ }
1927
+ function mapThinkingBudgetToEffort(thinkingBudgetTokens) {
1928
+ if (thinkingBudgetTokens >= 1e5) return "high";
1929
+ if (thinkingBudgetTokens >= 1e4) return "medium";
1930
+ return "low";
1931
+ }
1932
+ function normalizeToolParameters(parameters) {
1933
+ if (!parameters || typeof parameters !== "object") {
1934
+ return { type: "object", properties: {} };
1935
+ }
1936
+ return sanitizeToolSchema(parameters);
1937
+ }
1938
+ function parseToolArguments(raw, toolName) {
1939
+ const trimmed = String(raw || "").trim();
1940
+ if (!trimmed) return {};
1941
+ try {
1942
+ const parsed = JSON.parse(trimmed);
1943
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1944
+ return parsed;
1945
+ }
1946
+ } catch {
1947
+ }
1948
+ console.warn(
1949
+ `[OpenAI Responses] Failed to parse tool arguments${toolName ? ` for tool "${toolName}"` : ""}. Input length: ${trimmed.length}, preview: ${trimmed.slice(0, 200)}`
1950
+ );
1951
+ return {};
1952
+ }
1953
+ function isEmptyResponsesStreamError(error) {
1954
+ return error instanceof Error && /request ended without sending any events/i.test(error.message);
1955
+ }
1956
+ function mapResponseStatusToStopReason(snapshot) {
1957
+ if (!snapshot) return null;
1958
+ if (snapshot.status === "incomplete") {
1959
+ const reason = snapshot.incomplete_details?.reason || "incomplete";
1960
+ return reason === "max_output_tokens" ? "max_tokens" : reason;
1961
+ }
1962
+ if (snapshot.status === "failed" || snapshot.status === "cancelled") {
1963
+ return snapshot.status;
1964
+ }
1965
+ if (snapshot.status === "completed") {
1966
+ if (Array.isArray(snapshot.output) && snapshot.output.some((item) => item?.type === "function_call")) {
1967
+ return "tool_calls";
1968
+ }
1969
+ return "stop";
1970
+ }
1971
+ return snapshot.status || null;
1972
+ }
1973
+ function createOpenAIResponsesLLM(configOrApiKey, modelName, baseUrl) {
1974
+ if (typeof configOrApiKey === "object" && "defaultModel" in configOrApiKey) {
1975
+ return new OpenAIResponsesLLM(
1976
+ configOrApiKey.defaultModel.apiKey,
1977
+ configOrApiKey.defaultModel.model,
1978
+ configOrApiKey.defaultModel.baseUrl,
1979
+ configOrApiKey.defaultModel.maxTokens,
1980
+ configOrApiKey.defaultModel.thinkingEffort,
1981
+ configOrApiKey.defaultModel.thinkingBudgetTokens,
1982
+ configOrApiKey.defaultModel.providerOptions,
1983
+ configOrApiKey.defaultModel.customHeaders,
1984
+ configOrApiKey.defaultModel.vision ?? false,
1985
+ configOrApiKey.defaultModel.responsesProfile ?? "standard",
1986
+ { maxRetries: configOrApiKey.defaultModel.maxRetries, timeoutMs: configOrApiKey.defaultModel.timeoutMs }
1987
+ );
1988
+ }
1989
+ if (typeof configOrApiKey === "object") {
1990
+ return new OpenAIResponsesLLM(
1991
+ configOrApiKey.apiKey,
1992
+ configOrApiKey.model,
1993
+ configOrApiKey.baseUrl,
1994
+ configOrApiKey.maxTokens,
1995
+ configOrApiKey.thinkingEffort,
1996
+ configOrApiKey.thinkingBudgetTokens,
1997
+ configOrApiKey.providerOptions,
1998
+ configOrApiKey.customHeaders,
1999
+ configOrApiKey.vision ?? false,
2000
+ configOrApiKey.responsesProfile ?? "standard",
2001
+ { maxRetries: configOrApiKey.maxRetries, timeoutMs: configOrApiKey.timeoutMs }
2002
+ );
2003
+ }
2004
+ return new OpenAIResponsesLLM(configOrApiKey, modelName, baseUrl);
2005
+ }
2006
+
2007
+ // src/index.ts
2008
+ import { DEFAULT_MAX_RETRIES as DEFAULT_MAX_RETRIES3, getRetryDelay as getRetryDelay4, parseRetryAfter as parseRetryAfter4, shouldRetry as shouldRetry4, sleep } from "@agentdevjs/core";
2009
+ import { ClassifiedAPIError as ClassifiedAPIError4, classifyAPIError as classifyAPIError3, classifyAndWrapError as classifyAndWrapError4, extractConnectionErrorDetails, getUserFriendlyMessage } from "@agentdevjs/core";
2010
+ function createLLM(configOrApiKey, modelName, provider, baseUrl) {
2011
+ if (typeof configOrApiKey === "string") {
2012
+ return provider === "anthropic" ? createAnthropicLLM(configOrApiKey, modelName, baseUrl) : createOpenAILLM(configOrApiKey, modelName, baseUrl);
2013
+ }
2014
+ if ("defaultModel" in configOrApiKey) {
2015
+ switch (configOrApiKey.defaultModel.provider) {
2016
+ case "anthropic":
2017
+ return createAnthropicLLM(configOrApiKey);
2018
+ case "openai":
2019
+ return configOrApiKey.defaultModel.apiSurface === "responses" ? createOpenAIResponsesLLM(configOrApiKey) : createOpenAILLM(configOrApiKey);
2020
+ default:
2021
+ return createOpenAILLM(configOrApiKey);
2022
+ }
2023
+ }
2024
+ switch (configOrApiKey.provider) {
2025
+ case "anthropic":
2026
+ return createAnthropicLLM(configOrApiKey);
2027
+ case "openai":
2028
+ return configOrApiKey.apiSurface === "responses" ? createOpenAIResponsesLLM(configOrApiKey) : createOpenAILLM(configOrApiKey);
2029
+ default:
2030
+ return createOpenAILLM(configOrApiKey);
2031
+ }
2032
+ }
2033
+ export {
2034
+ AnthropicLLM,
2035
+ ClassifiedAPIError4 as ClassifiedAPIError,
2036
+ DEFAULT_MAX_RETRIES3 as DEFAULT_MAX_RETRIES,
2037
+ OpenAILLM,
2038
+ OpenAIResponsesLLM,
2039
+ classifyAPIError3 as classifyAPIError,
2040
+ classifyAndWrapError4 as classifyAndWrapError,
2041
+ compileChatMessages,
2042
+ compileContextForAnthropic,
2043
+ compileContextForOpenAIResponses,
2044
+ createAnthropicLLM,
2045
+ createLLM,
2046
+ createOpenAILLM,
2047
+ createOpenAIResponsesLLM,
2048
+ extractConnectionErrorDetails,
2049
+ getGlobalDispatcher,
2050
+ getRetryDelay4 as getRetryDelay,
2051
+ getUserFriendlyMessage,
2052
+ initHttpClient,
2053
+ parseRetryAfter4 as parseRetryAfter,
2054
+ sleep as retrySleep,
2055
+ shouldRetry4 as shouldRetry
2056
+ };
2057
+ //# sourceMappingURL=index.js.map