@ljoukov/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.cjs ADDED
@@ -0,0 +1,3470 @@
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
+ LlmJsonCallError: () => LlmJsonCallError,
34
+ appendMarkdownSourcesSection: () => appendMarkdownSourcesSection,
35
+ configureGemini: () => configureGemini,
36
+ convertGooglePartsToLlmParts: () => convertGooglePartsToLlmParts,
37
+ encodeChatGptAuthJson: () => encodeChatGptAuthJson,
38
+ encodeChatGptAuthJsonB64: () => encodeChatGptAuthJsonB64,
39
+ estimateCallCostUsd: () => estimateCallCostUsd,
40
+ exchangeChatGptOauthCode: () => exchangeChatGptOauthCode,
41
+ generateImageInBatches: () => generateImageInBatches,
42
+ generateImages: () => generateImages,
43
+ generateJson: () => generateJson,
44
+ generateText: () => generateText,
45
+ getChatGptAuthProfile: () => getChatGptAuthProfile,
46
+ getCurrentToolCallContext: () => getCurrentToolCallContext,
47
+ isGeminiModelId: () => isGeminiModelId,
48
+ loadEnvFromFile: () => loadEnvFromFile,
49
+ loadLocalEnv: () => loadLocalEnv,
50
+ parseJsonFromLlmText: () => parseJsonFromLlmText,
51
+ refreshChatGptOauthToken: () => refreshChatGptOauthToken,
52
+ runToolLoop: () => runToolLoop,
53
+ sanitisePartForLogging: () => sanitisePartForLogging,
54
+ streamText: () => streamText,
55
+ stripCodexCitationMarkers: () => stripCodexCitationMarkers,
56
+ toGeminiJsonSchema: () => toGeminiJsonSchema,
57
+ tool: () => tool
58
+ });
59
+ module.exports = __toCommonJS(index_exports);
60
+
61
+ // src/llm.ts
62
+ var import_node_buffer2 = require("buffer");
63
+ var import_node_async_hooks = require("async_hooks");
64
+ var import_node_crypto = require("crypto");
65
+ var import_genai2 = require("@google/genai");
66
+ var import_zod_to_json_schema = require("@alcyone-labs/zod-to-json-schema");
67
+ var import_zod3 = require("zod");
68
+
69
+ // src/utils/asyncQueue.ts
70
+ function createAsyncQueue() {
71
+ let closed = false;
72
+ let error = null;
73
+ const values = [];
74
+ let pending = null;
75
+ const push = (value) => {
76
+ if (closed || error) {
77
+ return;
78
+ }
79
+ if (pending) {
80
+ const { resolve } = pending;
81
+ pending = null;
82
+ resolve({ value, done: false });
83
+ return;
84
+ }
85
+ values.push(value);
86
+ };
87
+ const close = () => {
88
+ if (closed || error) {
89
+ return;
90
+ }
91
+ closed = true;
92
+ if (pending) {
93
+ const { resolve } = pending;
94
+ pending = null;
95
+ resolve({ value: void 0, done: true });
96
+ }
97
+ };
98
+ const fail = (err) => {
99
+ if (closed || error) {
100
+ return;
101
+ }
102
+ error = err;
103
+ if (pending) {
104
+ const { reject } = pending;
105
+ pending = null;
106
+ reject(err);
107
+ }
108
+ };
109
+ async function* iterator() {
110
+ while (true) {
111
+ if (error) {
112
+ throw error;
113
+ }
114
+ if (values.length > 0) {
115
+ yield values.shift();
116
+ continue;
117
+ }
118
+ if (closed) {
119
+ return;
120
+ }
121
+ const next = await new Promise((resolve, reject) => {
122
+ pending = { resolve, reject };
123
+ });
124
+ if (next.done) {
125
+ return;
126
+ }
127
+ yield next.value;
128
+ }
129
+ }
130
+ return { push, close, fail, iterable: iterator() };
131
+ }
132
+
133
+ // src/google/pricing.ts
134
+ var GEMINI_3_PRO_PREVIEW_PRICING = {
135
+ threshold: 2e5,
136
+ inputRateLow: 2 / 1e6,
137
+ inputRateHigh: 4 / 1e6,
138
+ cachedRateLow: 0.2 / 1e6,
139
+ cachedRateHigh: 0.4 / 1e6,
140
+ outputRateLow: 12 / 1e6,
141
+ outputRateHigh: 18 / 1e6
142
+ };
143
+ var GEMINI_2_5_PRO_PRICING = {
144
+ threshold: 2e5,
145
+ inputRateLow: 1.25 / 1e6,
146
+ inputRateHigh: 2.5 / 1e6,
147
+ cachedRateLow: 0.125 / 1e6,
148
+ cachedRateHigh: 0.25 / 1e6,
149
+ outputRateLow: 10 / 1e6,
150
+ outputRateHigh: 15 / 1e6
151
+ };
152
+ var GEMINI_IMAGE_PREVIEW_PRICING = {
153
+ inputRate: 2 / 1e6,
154
+ cachedRate: 0.2 / 1e6,
155
+ outputTextRate: 12 / 1e6,
156
+ outputImageRate: 120 / 1e6,
157
+ imagePrices: {
158
+ "1K": 0.134,
159
+ "2K": 0.134,
160
+ "4K": 0.24
161
+ }
162
+ };
163
+ function getGeminiProPricing(modelId) {
164
+ if (modelId.includes("gemini-2.5-pro")) {
165
+ return GEMINI_2_5_PRO_PRICING;
166
+ }
167
+ if (modelId.includes("gemini-3-pro")) {
168
+ return GEMINI_3_PRO_PREVIEW_PRICING;
169
+ }
170
+ return void 0;
171
+ }
172
+ function getGeminiImagePricing(modelId) {
173
+ if (modelId.includes("image-preview")) {
174
+ return GEMINI_IMAGE_PREVIEW_PRICING;
175
+ }
176
+ return void 0;
177
+ }
178
+
179
+ // src/openai/pricing.ts
180
+ var OPENAI_GPT_52_PRICING = {
181
+ inputRate: 1.75 / 1e6,
182
+ cachedRate: 0.175 / 1e6,
183
+ outputRate: 14 / 1e6
184
+ };
185
+ var OPENAI_GPT_51_CODEX_MINI_PRICING = {
186
+ inputRate: 0.25 / 1e6,
187
+ cachedRate: 0.025 / 1e6,
188
+ outputRate: 2 / 1e6
189
+ };
190
+ function getOpenAiPricing(modelId) {
191
+ if (modelId.includes("gpt-5.2")) {
192
+ return OPENAI_GPT_52_PRICING;
193
+ }
194
+ if (modelId.includes("gpt-5.1-codex-mini")) {
195
+ return OPENAI_GPT_51_CODEX_MINI_PRICING;
196
+ }
197
+ return void 0;
198
+ }
199
+
200
+ // src/utils/cost.ts
201
+ function resolveUsageNumber(value) {
202
+ if (typeof value === "number" && Number.isFinite(value)) {
203
+ return Math.max(0, value);
204
+ }
205
+ return 0;
206
+ }
207
+ function estimateCallCostUsd({
208
+ modelId,
209
+ tokens,
210
+ responseImages,
211
+ imageSize
212
+ }) {
213
+ if (!tokens) {
214
+ return 0;
215
+ }
216
+ const promptTokens = resolveUsageNumber(tokens.promptTokens);
217
+ const cachedTokens = resolveUsageNumber(tokens.cachedTokens);
218
+ const responseTokens = resolveUsageNumber(tokens.responseTokens);
219
+ const responseImageTokens = resolveUsageNumber(tokens.responseImageTokens);
220
+ const thinkingTokens = resolveUsageNumber(tokens.thinkingTokens);
221
+ const toolUsePromptTokens = resolveUsageNumber(tokens.toolUsePromptTokens);
222
+ const promptTokenTotal = promptTokens + toolUsePromptTokens;
223
+ const nonCachedPrompt = Math.max(0, promptTokenTotal - cachedTokens);
224
+ const imagePreviewPricing = getGeminiImagePricing(modelId);
225
+ if (imagePreviewPricing) {
226
+ const resolvedImageSize = imageSize && imagePreviewPricing.imagePrices[imageSize] ? imageSize : "2K";
227
+ const imageRate = imagePreviewPricing.imagePrices[resolvedImageSize] ?? 0;
228
+ const tokensPerImage = imagePreviewPricing.outputImageRate > 0 ? imageRate / imagePreviewPricing.outputImageRate : 0;
229
+ let responseTextForPricing = Math.max(0, responseTokens - responseImageTokens);
230
+ let imageTokensForPricing = responseImageTokens;
231
+ if (imageTokensForPricing <= 0 && responseImages > 0 && tokensPerImage > 0) {
232
+ const estimatedImageTokens = responseImages * tokensPerImage;
233
+ imageTokensForPricing = estimatedImageTokens;
234
+ if (responseTextForPricing >= estimatedImageTokens) {
235
+ responseTextForPricing -= estimatedImageTokens;
236
+ }
237
+ }
238
+ const textOutputCost = (responseTextForPricing + thinkingTokens) * imagePreviewPricing.outputTextRate;
239
+ const inputCost = nonCachedPrompt * imagePreviewPricing.inputRate;
240
+ const cachedCost = cachedTokens * imagePreviewPricing.cachedRate;
241
+ const imageOutputCost = imageTokensForPricing * imagePreviewPricing.outputImageRate;
242
+ return inputCost + cachedCost + textOutputCost + imageOutputCost;
243
+ }
244
+ const geminiPricing = getGeminiProPricing(modelId);
245
+ if (geminiPricing) {
246
+ const useHighTier = promptTokenTotal > geminiPricing.threshold;
247
+ const inputRate = useHighTier ? geminiPricing.inputRateHigh : geminiPricing.inputRateLow;
248
+ const cachedRate = useHighTier ? geminiPricing.cachedRateHigh : geminiPricing.cachedRateLow;
249
+ const outputRate = useHighTier ? geminiPricing.outputRateHigh : geminiPricing.outputRateLow;
250
+ const inputCost = nonCachedPrompt * inputRate;
251
+ const cachedCost = cachedTokens * cachedRate;
252
+ const outputTokens = responseTokens + thinkingTokens;
253
+ const outputCost = outputTokens * outputRate;
254
+ return inputCost + cachedCost + outputCost;
255
+ }
256
+ const openAiPricing = getOpenAiPricing(modelId);
257
+ if (openAiPricing) {
258
+ const inputCost = nonCachedPrompt * openAiPricing.inputRate;
259
+ const cachedCost = cachedTokens * openAiPricing.cachedRate;
260
+ const outputTokens = responseTokens + thinkingTokens;
261
+ const outputCost = outputTokens * openAiPricing.outputRate;
262
+ return inputCost + cachedCost + outputCost;
263
+ }
264
+ return 0;
265
+ }
266
+
267
+ // src/openai/chatgpt-codex.ts
268
+ var import_node_os = __toESM(require("os"), 1);
269
+ var import_node_util = require("util");
270
+
271
+ // src/openai/chatgpt-auth.ts
272
+ var import_node_buffer = require("buffer");
273
+ var import_zod = require("zod");
274
+
275
+ // src/utils/env.ts
276
+ var import_node_fs = __toESM(require("fs"), 1);
277
+ var import_node_path = __toESM(require("path"), 1);
278
+ var envLoaded = false;
279
+ function loadLocalEnv() {
280
+ if (envLoaded) {
281
+ return;
282
+ }
283
+ const envPath = import_node_path.default.join(process.cwd(), ".env.local");
284
+ loadEnvFromFile(envPath, { override: false });
285
+ envLoaded = true;
286
+ }
287
+ function loadEnvFromFile(filePath, { override = false } = {}) {
288
+ let content;
289
+ try {
290
+ content = import_node_fs.default.readFileSync(filePath, "utf8");
291
+ } catch (error) {
292
+ if (error?.code === "ENOENT") {
293
+ return;
294
+ }
295
+ throw error;
296
+ }
297
+ for (const line of content.split(/\r?\n/u)) {
298
+ const entry = parseEnvLine(line);
299
+ if (!entry) {
300
+ continue;
301
+ }
302
+ const [key, value] = entry;
303
+ if (override || process.env[key] === void 0) {
304
+ process.env[key] = value;
305
+ }
306
+ }
307
+ }
308
+ function parseEnvLine(line) {
309
+ const trimmed = line.trim();
310
+ if (!trimmed || trimmed.startsWith("#")) {
311
+ return null;
312
+ }
313
+ const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_\-.]*)\s*=\s*(.*)$/u);
314
+ if (!match) {
315
+ return null;
316
+ }
317
+ const key = match[1];
318
+ if (!key) {
319
+ return null;
320
+ }
321
+ let value = match[2] ?? "";
322
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
323
+ value = value.slice(1, -1);
324
+ } else if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
325
+ value = value.slice(1, -1);
326
+ } else {
327
+ const commentIndex = value.indexOf(" #");
328
+ if (commentIndex >= 0) {
329
+ value = value.slice(0, commentIndex);
330
+ }
331
+ value = value.trim();
332
+ }
333
+ return [key, value];
334
+ }
335
+
336
+ // src/openai/chatgpt-auth.ts
337
+ var CHATGPT_AUTH_JSON_ENV = "CHATGPT_AUTH_JSON";
338
+ var CHATGPT_AUTH_JSON_B64_ENV = "CHATGPT_AUTH_JSON_B64";
339
+ var CHATGPT_ACCESS_ENV = "CHATGPT_ACCESS";
340
+ var CHATGPT_REFRESH_ENV = "CHATGPT_REFRESH";
341
+ var CHATGPT_EXPIRES_ENV = "CHATGPT_EXPIRES";
342
+ var CHATGPT_ACCOUNT_ID_ENV = "CHATGPT_ACCOUNT_ID";
343
+ var CHATGPT_ID_TOKEN_ENV = "CHATGPT_ID_TOKEN";
344
+ var CHATGPT_ACCESS_TOKEN_ENV = "CHATGPT_ACCESS_TOKEN";
345
+ var CHATGPT_REFRESH_TOKEN_ENV = "CHATGPT_REFRESH_TOKEN";
346
+ var CHATGPT_EXPIRES_AT_ENV = "CHATGPT_EXPIRES_AT";
347
+ var CHATGPT_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
348
+ var CHATGPT_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
349
+ var CHATGPT_OAUTH_REDIRECT_URI = "http://localhost:1455/auth/callback";
350
+ var TOKEN_EXPIRY_BUFFER_MS = 3e4;
351
+ var AuthInputSchema = import_zod.z.object({
352
+ access: import_zod.z.string().min(1).optional(),
353
+ access_token: import_zod.z.string().min(1).optional(),
354
+ accessToken: import_zod.z.string().min(1).optional(),
355
+ refresh: import_zod.z.string().min(1).optional(),
356
+ refresh_token: import_zod.z.string().min(1).optional(),
357
+ refreshToken: import_zod.z.string().min(1).optional(),
358
+ expires: import_zod.z.union([import_zod.z.number(), import_zod.z.string()]).optional(),
359
+ expires_at: import_zod.z.union([import_zod.z.number(), import_zod.z.string()]).optional(),
360
+ expiresAt: import_zod.z.union([import_zod.z.number(), import_zod.z.string()]).optional(),
361
+ accountId: import_zod.z.string().min(1).optional(),
362
+ account_id: import_zod.z.string().min(1).optional(),
363
+ id_token: import_zod.z.string().optional(),
364
+ idToken: import_zod.z.string().optional()
365
+ }).loose();
366
+ var RefreshResponseSchema = import_zod.z.object({
367
+ access_token: import_zod.z.string().min(1),
368
+ refresh_token: import_zod.z.string().min(1),
369
+ expires_in: import_zod.z.union([import_zod.z.number(), import_zod.z.string()])
370
+ });
371
+ var ExchangeResponseSchema = import_zod.z.object({
372
+ access_token: import_zod.z.string().min(1),
373
+ refresh_token: import_zod.z.string().min(1),
374
+ expires_in: import_zod.z.union([import_zod.z.number(), import_zod.z.string()]),
375
+ id_token: import_zod.z.string().optional()
376
+ });
377
+ var cachedProfile = null;
378
+ var refreshPromise = null;
379
+ function encodeChatGptAuthJson(profile) {
380
+ const payload = {
381
+ access: profile.access,
382
+ refresh: profile.refresh,
383
+ expires: profile.expires,
384
+ accountId: profile.accountId,
385
+ ...profile.idToken ? { id_token: profile.idToken } : {}
386
+ };
387
+ return JSON.stringify(payload);
388
+ }
389
+ function encodeChatGptAuthJsonB64(profile) {
390
+ return import_node_buffer.Buffer.from(encodeChatGptAuthJson(profile)).toString("base64url");
391
+ }
392
+ async function exchangeChatGptOauthCode({
393
+ code,
394
+ verifier,
395
+ redirectUri = CHATGPT_OAUTH_REDIRECT_URI
396
+ }) {
397
+ const params = new URLSearchParams();
398
+ params.set("grant_type", "authorization_code");
399
+ params.set("client_id", CHATGPT_OAUTH_CLIENT_ID);
400
+ params.set("code", code);
401
+ params.set("code_verifier", verifier);
402
+ params.set("redirect_uri", redirectUri);
403
+ const response = await fetch(CHATGPT_OAUTH_TOKEN_URL, {
404
+ method: "POST",
405
+ headers: {
406
+ "Content-Type": "application/x-www-form-urlencoded"
407
+ },
408
+ body: params.toString()
409
+ });
410
+ if (!response.ok) {
411
+ const body = await response.text();
412
+ throw new Error(`ChatGPT OAuth token exchange failed (${response.status}): ${body}`);
413
+ }
414
+ const payload = ExchangeResponseSchema.parse(await response.json());
415
+ return profileFromTokenResponse(payload);
416
+ }
417
+ async function refreshChatGptOauthToken(refreshToken) {
418
+ const params = new URLSearchParams();
419
+ params.set("grant_type", "refresh_token");
420
+ params.set("client_id", CHATGPT_OAUTH_CLIENT_ID);
421
+ params.set("refresh_token", refreshToken);
422
+ const response = await fetch(CHATGPT_OAUTH_TOKEN_URL, {
423
+ method: "POST",
424
+ headers: {
425
+ "Content-Type": "application/x-www-form-urlencoded"
426
+ },
427
+ body: params.toString()
428
+ });
429
+ if (!response.ok) {
430
+ const body = await response.text();
431
+ throw new Error(`ChatGPT OAuth refresh failed (${response.status}): ${body}`);
432
+ }
433
+ const payload = RefreshResponseSchema.parse(await response.json());
434
+ return profileFromTokenResponse(payload);
435
+ }
436
+ async function getChatGptAuthProfile() {
437
+ if (cachedProfile && !isExpired(cachedProfile)) {
438
+ return cachedProfile;
439
+ }
440
+ if (refreshPromise) {
441
+ return refreshPromise;
442
+ }
443
+ refreshPromise = (async () => {
444
+ try {
445
+ const baseProfile = cachedProfile ?? loadAuthProfileFromEnv();
446
+ const profile = isExpired(baseProfile) ? await refreshChatGptOauthToken(baseProfile.refresh) : baseProfile;
447
+ cachedProfile = profile;
448
+ return profile;
449
+ } finally {
450
+ refreshPromise = null;
451
+ }
452
+ })();
453
+ return refreshPromise;
454
+ }
455
+ function profileFromTokenResponse(payload) {
456
+ const expires = Date.now() + normalizeNumber(payload.expires_in) * 1e3;
457
+ const accountId = extractChatGptAccountId(payload.id_token ?? "") ?? extractChatGptAccountId(payload.access_token);
458
+ if (!accountId) {
459
+ throw new Error("Failed to extract chatgpt_account_id from access token.");
460
+ }
461
+ return {
462
+ access: payload.access_token,
463
+ refresh: payload.refresh_token,
464
+ expires,
465
+ accountId,
466
+ idToken: payload.id_token
467
+ };
468
+ }
469
+ function normalizeAuthProfile(data) {
470
+ const access = data.access ?? data.access_token ?? data.accessToken ?? void 0;
471
+ const refresh = data.refresh ?? data.refresh_token ?? data.refreshToken ?? void 0;
472
+ if (!access || !refresh) {
473
+ throw new Error("ChatGPT credentials must include access and refresh.");
474
+ }
475
+ const expiresRaw = data.expires ?? data.expires_at ?? data.expiresAt;
476
+ const idToken = data.idToken ?? data.id_token ?? void 0;
477
+ const expires = normalizeEpochMillis(expiresRaw) ?? extractJwtExpiry(idToken ?? access) ?? Date.now() + 5 * 6e4;
478
+ const accountId = data.accountId ?? data.account_id ?? extractChatGptAccountId(idToken ?? "") ?? extractChatGptAccountId(access);
479
+ if (!accountId) {
480
+ throw new Error("ChatGPT credentials missing chatgpt_account_id.");
481
+ }
482
+ return {
483
+ access,
484
+ refresh,
485
+ expires,
486
+ accountId,
487
+ idToken: idToken ?? void 0
488
+ };
489
+ }
490
+ function normalizeEpochMillis(value) {
491
+ const numeric = normalizeNumber(value);
492
+ if (!Number.isFinite(numeric)) {
493
+ return void 0;
494
+ }
495
+ return numeric < 1e12 ? numeric * 1e3 : numeric;
496
+ }
497
+ function normalizeNumber(value) {
498
+ if (typeof value === "number" && Number.isFinite(value)) {
499
+ return value;
500
+ }
501
+ if (typeof value === "string") {
502
+ const parsed = Number.parseFloat(value);
503
+ if (Number.isFinite(parsed)) {
504
+ return parsed;
505
+ }
506
+ }
507
+ return Number.NaN;
508
+ }
509
+ function isExpired(profile) {
510
+ const expires = profile.expires;
511
+ if (!Number.isFinite(expires)) {
512
+ return true;
513
+ }
514
+ return Date.now() + TOKEN_EXPIRY_BUFFER_MS >= expires;
515
+ }
516
+ function loadAuthProfileFromEnv() {
517
+ loadLocalEnv();
518
+ const rawJson = process.env[CHATGPT_AUTH_JSON_ENV];
519
+ if (rawJson && rawJson.trim().length > 0) {
520
+ return normalizeAuthProfile(AuthInputSchema.parse(JSON.parse(rawJson)));
521
+ }
522
+ const rawB64 = process.env[CHATGPT_AUTH_JSON_B64_ENV];
523
+ if (rawB64 && rawB64.trim().length > 0) {
524
+ const decoded = import_node_buffer.Buffer.from(rawB64.trim(), "base64url").toString("utf8");
525
+ return normalizeAuthProfile(AuthInputSchema.parse(JSON.parse(decoded)));
526
+ }
527
+ const access = process.env[CHATGPT_ACCESS_ENV] ?? process.env[CHATGPT_ACCESS_TOKEN_ENV] ?? void 0;
528
+ const refresh = process.env[CHATGPT_REFRESH_ENV] ?? process.env[CHATGPT_REFRESH_TOKEN_ENV] ?? void 0;
529
+ const expires = process.env[CHATGPT_EXPIRES_ENV] ?? process.env[CHATGPT_EXPIRES_AT_ENV] ?? void 0;
530
+ const accountId = process.env[CHATGPT_ACCOUNT_ID_ENV] ?? void 0;
531
+ const idToken = process.env[CHATGPT_ID_TOKEN_ENV] ?? void 0;
532
+ const parsed = AuthInputSchema.parse({
533
+ access,
534
+ refresh,
535
+ expires,
536
+ accountId,
537
+ idToken
538
+ });
539
+ return normalizeAuthProfile(parsed);
540
+ }
541
+ function decodeJwtPayload(token) {
542
+ const segments = token.split(".");
543
+ if (segments.length < 2) {
544
+ return null;
545
+ }
546
+ const payloadB64 = segments[1] ?? "";
547
+ try {
548
+ const decoded = import_node_buffer.Buffer.from(payloadB64, "base64url").toString("utf8");
549
+ return JSON.parse(decoded);
550
+ } catch {
551
+ return null;
552
+ }
553
+ }
554
+ function extractJwtExpiry(token) {
555
+ const payload = decodeJwtPayload(token);
556
+ if (!payload || typeof payload !== "object") {
557
+ return void 0;
558
+ }
559
+ const exp = payload.exp;
560
+ const parsed = normalizeNumber(exp);
561
+ if (!Number.isFinite(parsed) || parsed <= 0) {
562
+ return void 0;
563
+ }
564
+ return parsed < 1e12 ? parsed * 1e3 : parsed;
565
+ }
566
+ function extractChatGptAccountId(token) {
567
+ const payload = decodeJwtPayload(token);
568
+ if (!payload || typeof payload !== "object") {
569
+ return void 0;
570
+ }
571
+ const accountId = payload.chatgpt_account_id;
572
+ return typeof accountId === "string" && accountId.length > 0 ? accountId : void 0;
573
+ }
574
+
575
+ // src/openai/chatgpt-codex.ts
576
+ var CHATGPT_CODEX_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
577
+ async function streamChatGptCodexResponse(options) {
578
+ const { access, accountId } = await getChatGptAuthProfile();
579
+ const headers = {
580
+ Authorization: `Bearer ${access}`,
581
+ "chatgpt-account-id": accountId,
582
+ "OpenAI-Beta": "responses=experimental",
583
+ originator: "llm",
584
+ "User-Agent": buildUserAgent(),
585
+ Accept: "text/event-stream",
586
+ "Content-Type": "application/json"
587
+ };
588
+ if (options.sessionId) {
589
+ headers.session_id = options.sessionId;
590
+ }
591
+ const response = await fetch(CHATGPT_CODEX_ENDPOINT, {
592
+ method: "POST",
593
+ headers,
594
+ body: JSON.stringify(options.request),
595
+ signal: options.signal
596
+ });
597
+ if (!response.ok) {
598
+ const body2 = await response.text();
599
+ throw new Error(`ChatGPT Codex request failed (${response.status}): ${body2}`);
600
+ }
601
+ const body = response.body;
602
+ if (!body) {
603
+ throw new Error("ChatGPT Codex response body was empty.");
604
+ }
605
+ return parseEventStream(body);
606
+ }
607
+ async function collectChatGptCodexResponse(options) {
608
+ const stream = await streamChatGptCodexResponse(options);
609
+ const toolCalls = /* @__PURE__ */ new Map();
610
+ const toolCallOrder = [];
611
+ const webSearchCalls = /* @__PURE__ */ new Map();
612
+ const webSearchCallOrder = [];
613
+ let text = "";
614
+ const reasoningText = "";
615
+ let reasoningSummaryText = "";
616
+ let usage;
617
+ let model;
618
+ let status;
619
+ let blocked = false;
620
+ for await (const event of stream) {
621
+ const type = typeof event.type === "string" ? event.type : void 0;
622
+ if (type === "response.output_text.delta") {
623
+ const delta = typeof event.delta === "string" ? event.delta : "";
624
+ if (delta.length > 0) {
625
+ text += delta;
626
+ options.onDelta?.({ textDelta: delta });
627
+ }
628
+ continue;
629
+ }
630
+ if (type === "response.reasoning_summary_text.delta") {
631
+ const delta = typeof event.delta === "string" ? event.delta : "";
632
+ if (delta.length > 0) {
633
+ reasoningSummaryText += delta;
634
+ options.onDelta?.({ thoughtDelta: delta });
635
+ }
636
+ continue;
637
+ }
638
+ if (type === "response.reasoning_text.delta") {
639
+ continue;
640
+ }
641
+ if (type === "response.refusal.delta") {
642
+ blocked = true;
643
+ continue;
644
+ }
645
+ if (type === "response.output_item.added" || type === "response.output_item.done") {
646
+ const item = event.item;
647
+ if (item) {
648
+ if (item.type === "function_call") {
649
+ const id = typeof item.id === "string" ? item.id : "";
650
+ const callId = typeof item.call_id === "string" ? item.call_id : id;
651
+ const name = typeof item.name === "string" ? item.name : "";
652
+ const args = typeof item.arguments === "string" ? item.arguments : "";
653
+ if (callId) {
654
+ if (!toolCalls.has(callId)) {
655
+ toolCallOrder.push(callId);
656
+ }
657
+ toolCalls.set(callId, { id, callId, name, arguments: args });
658
+ }
659
+ } else if (item.type === "web_search_call") {
660
+ const id = typeof item.id === "string" ? item.id : "";
661
+ if (id) {
662
+ if (!webSearchCalls.has(id)) {
663
+ webSearchCallOrder.push(id);
664
+ }
665
+ webSearchCalls.set(id, {
666
+ id,
667
+ status: typeof item.status === "string" ? item.status : void 0,
668
+ action: item.action && typeof item.action === "object" ? item.action : void 0
669
+ });
670
+ }
671
+ }
672
+ }
673
+ continue;
674
+ }
675
+ if (type === "response.completed") {
676
+ const response = event.response;
677
+ if (response) {
678
+ usage = response.usage;
679
+ model = typeof response.model === "string" ? response.model : void 0;
680
+ status = typeof response.status === "string" ? response.status : void 0;
681
+ }
682
+ continue;
683
+ }
684
+ if (type === "response.failed") {
685
+ const response = event.response;
686
+ if (response) {
687
+ usage = response.usage;
688
+ model = typeof response.model === "string" ? response.model : void 0;
689
+ status = typeof response.status === "string" ? response.status : void 0;
690
+ }
691
+ continue;
692
+ }
693
+ if (type === "response.in_progress") {
694
+ const response = event.response;
695
+ if (response) {
696
+ usage = response.usage;
697
+ model = typeof response.model === "string" ? response.model : void 0;
698
+ status = typeof response.status === "string" ? response.status : void 0;
699
+ }
700
+ }
701
+ }
702
+ if (!reasoningSummaryText && reasoningText) {
703
+ reasoningSummaryText = reasoningText;
704
+ }
705
+ const orderedToolCalls = toolCallOrder.map((id) => toolCalls.get(id)).filter((call) => call !== void 0);
706
+ const orderedWebSearchCalls = webSearchCallOrder.map((id) => webSearchCalls.get(id)).filter((call) => call !== void 0);
707
+ return {
708
+ text,
709
+ reasoningText,
710
+ reasoningSummaryText,
711
+ toolCalls: orderedToolCalls,
712
+ webSearchCalls: orderedWebSearchCalls,
713
+ usage,
714
+ model,
715
+ status,
716
+ blocked
717
+ };
718
+ }
719
+ function buildUserAgent() {
720
+ const node = process.version;
721
+ const platform = import_node_os.default.platform();
722
+ const release = import_node_os.default.release();
723
+ return `@ljoukov/llm (node ${node}; ${platform} ${release})`;
724
+ }
725
+ async function* parseEventStream(stream) {
726
+ const reader = stream.getReader();
727
+ const decoder = new import_node_util.TextDecoder();
728
+ let buffer = "";
729
+ while (true) {
730
+ const { done, value } = await reader.read();
731
+ if (done) {
732
+ break;
733
+ }
734
+ buffer += decoder.decode(value, { stream: true });
735
+ let sepIndex = buffer.indexOf("\n\n");
736
+ while (sepIndex !== -1) {
737
+ const raw = buffer.slice(0, sepIndex);
738
+ buffer = buffer.slice(sepIndex + 2);
739
+ const event = parseEventBlock(raw);
740
+ if (event) {
741
+ yield event;
742
+ }
743
+ sepIndex = buffer.indexOf("\n\n");
744
+ }
745
+ }
746
+ if (buffer.trim().length > 0) {
747
+ const event = parseEventBlock(buffer);
748
+ if (event) {
749
+ yield event;
750
+ }
751
+ }
752
+ }
753
+ function parseEventBlock(raw) {
754
+ const lines = raw.split(/\r?\n/u).map((line) => line.trimEnd()).filter(Boolean);
755
+ const dataLines = lines.filter((line) => line.startsWith("data:")).map((line) => line.slice("data:".length).trimStart());
756
+ if (dataLines.length === 0) {
757
+ return null;
758
+ }
759
+ const payload = dataLines.join("\n");
760
+ if (payload === "[DONE]") {
761
+ return null;
762
+ }
763
+ try {
764
+ return JSON.parse(payload);
765
+ } catch {
766
+ return null;
767
+ }
768
+ }
769
+
770
+ // src/utils/scheduler.ts
771
+ function sleep(ms) {
772
+ return new Promise((resolve) => {
773
+ setTimeout(resolve, ms);
774
+ });
775
+ }
776
+ function toError(value) {
777
+ if (value instanceof Error) {
778
+ return value;
779
+ }
780
+ if (typeof value === "string") {
781
+ return new Error(value);
782
+ }
783
+ return new Error("Unknown error");
784
+ }
785
+ function createCallScheduler(options = {}) {
786
+ const maxParallelRequests = Math.max(1, Math.floor(options.maxParallelRequests ?? 3));
787
+ const minIntervalBetweenStartMs = Math.max(0, Math.floor(options.minIntervalBetweenStartMs ?? 0));
788
+ const startJitterMs = Math.max(0, Math.floor(options.startJitterMs ?? 0));
789
+ const retryPolicy = options.retry;
790
+ let activeCount = 0;
791
+ let lastStartTime = 0;
792
+ let startSpacingChain = Promise.resolve();
793
+ const queue = [];
794
+ async function applyStartSpacing() {
795
+ const previous = startSpacingChain;
796
+ let release;
797
+ startSpacingChain = new Promise((resolve) => {
798
+ release = resolve;
799
+ });
800
+ await previous;
801
+ try {
802
+ if (lastStartTime > 0 && minIntervalBetweenStartMs > 0) {
803
+ const earliestNext = lastStartTime + minIntervalBetweenStartMs;
804
+ const wait = Math.max(0, earliestNext - Date.now());
805
+ if (wait > 0) {
806
+ await sleep(wait);
807
+ }
808
+ }
809
+ if (startJitterMs > 0) {
810
+ await sleep(Math.floor(Math.random() * (startJitterMs + 1)));
811
+ }
812
+ lastStartTime = Date.now();
813
+ } finally {
814
+ release?.();
815
+ }
816
+ }
817
+ async function attemptWithRetries(fn, attempt) {
818
+ try {
819
+ await applyStartSpacing();
820
+ return await fn();
821
+ } catch (error) {
822
+ const err = toError(error);
823
+ if (!retryPolicy || attempt >= retryPolicy.maxAttempts) {
824
+ throw err;
825
+ }
826
+ let delay = retryPolicy.getDelayMs(attempt, error);
827
+ if (delay === null) {
828
+ throw err;
829
+ }
830
+ if (!Number.isFinite(delay)) {
831
+ delay = 0;
832
+ }
833
+ const normalizedDelay = Math.max(0, delay);
834
+ if (normalizedDelay > 0) {
835
+ await sleep(normalizedDelay);
836
+ }
837
+ return attemptWithRetries(fn, attempt + 1);
838
+ }
839
+ }
840
+ function drainQueue() {
841
+ while (activeCount < maxParallelRequests && queue.length > 0) {
842
+ const task = queue.shift();
843
+ if (!task) {
844
+ continue;
845
+ }
846
+ activeCount += 1;
847
+ void task();
848
+ }
849
+ }
850
+ function run(fn) {
851
+ return new Promise((resolve, reject) => {
852
+ const job = async () => {
853
+ try {
854
+ const result = await attemptWithRetries(fn, 1);
855
+ resolve(result);
856
+ } catch (error) {
857
+ reject(toError(error));
858
+ } finally {
859
+ activeCount -= 1;
860
+ queueMicrotask(drainQueue);
861
+ }
862
+ };
863
+ queue.push(job);
864
+ drainQueue();
865
+ });
866
+ }
867
+ return { run };
868
+ }
869
+
870
+ // src/google/client.ts
871
+ var import_genai = require("@google/genai");
872
+
873
+ // src/google/auth.ts
874
+ var import_google_auth_library = require("google-auth-library");
875
+ var import_zod2 = require("zod");
876
+ var ServiceAccountSchema = import_zod2.z.object({
877
+ project_id: import_zod2.z.string().min(1),
878
+ client_email: import_zod2.z.email(),
879
+ private_key: import_zod2.z.string().min(1),
880
+ token_uri: import_zod2.z.string().optional()
881
+ }).transform(({ project_id, client_email, private_key, token_uri }) => ({
882
+ projectId: project_id,
883
+ clientEmail: client_email,
884
+ privateKey: private_key.replace(/\\n/g, "\n"),
885
+ tokenUri: token_uri
886
+ }));
887
+ var cachedServiceAccount = null;
888
+ function parseGoogleServiceAccount(input) {
889
+ let parsed;
890
+ try {
891
+ parsed = JSON.parse(input);
892
+ } catch (error) {
893
+ throw new Error(`Invalid Google service account JSON: ${error.message}`);
894
+ }
895
+ return ServiceAccountSchema.parse(parsed);
896
+ }
897
+ function getGoogleServiceAccount() {
898
+ if (cachedServiceAccount) {
899
+ return cachedServiceAccount;
900
+ }
901
+ loadLocalEnv();
902
+ const raw = process.env.GOOGLE_SERVICE_ACCOUNT_JSON;
903
+ if (!raw || raw.trim().length === 0) {
904
+ throw new Error("GOOGLE_SERVICE_ACCOUNT_JSON must be provided for Google APIs access.");
905
+ }
906
+ cachedServiceAccount = parseGoogleServiceAccount(raw);
907
+ return cachedServiceAccount;
908
+ }
909
+ function normaliseScopes(scopes) {
910
+ if (!scopes) {
911
+ return void 0;
912
+ }
913
+ if (typeof scopes === "string") {
914
+ return [scopes];
915
+ }
916
+ if (scopes.length === 0) {
917
+ return void 0;
918
+ }
919
+ return Array.from(new Set(scopes)).sort();
920
+ }
921
+ function getGoogleAuthOptions(scopes) {
922
+ const serviceAccount = getGoogleServiceAccount();
923
+ const normalisedScopes = normaliseScopes(scopes);
924
+ const options = {
925
+ credentials: {
926
+ client_email: serviceAccount.clientEmail,
927
+ private_key: serviceAccount.privateKey
928
+ },
929
+ projectId: serviceAccount.projectId,
930
+ scopes: normalisedScopes
931
+ };
932
+ return options;
933
+ }
934
+
935
+ // src/google/client.ts
936
+ var GEMINI_MODEL_IDS = [
937
+ "gemini-3-pro-preview",
938
+ "gemini-2.5-pro",
939
+ "gemini-flash-latest",
940
+ "gemini-flash-lite-latest"
941
+ ];
942
+ function isGeminiModelId(value) {
943
+ return GEMINI_MODEL_IDS.includes(value);
944
+ }
945
+ var CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
946
+ var DEFAULT_VERTEX_LOCATION = "global";
947
+ var geminiConfiguration = {};
948
+ var clientPromise;
949
+ function normaliseConfigValue(value) {
950
+ if (value === void 0 || value === null) {
951
+ return void 0;
952
+ }
953
+ const trimmed = value.trim();
954
+ return trimmed.length > 0 ? trimmed : void 0;
955
+ }
956
+ function configureGemini(options = {}) {
957
+ const nextProjectId = normaliseConfigValue(options.projectId);
958
+ const nextLocation = normaliseConfigValue(options.location);
959
+ geminiConfiguration = {
960
+ projectId: nextProjectId !== void 0 ? nextProjectId : geminiConfiguration.projectId,
961
+ location: nextLocation !== void 0 ? nextLocation : geminiConfiguration.location
962
+ };
963
+ clientPromise = void 0;
964
+ }
965
+ function resolveProjectId() {
966
+ const override = geminiConfiguration.projectId;
967
+ if (override) {
968
+ return override;
969
+ }
970
+ const serviceAccount = getGoogleServiceAccount();
971
+ return serviceAccount.projectId;
972
+ }
973
+ function resolveLocation() {
974
+ const override = geminiConfiguration.location;
975
+ if (override) {
976
+ return override;
977
+ }
978
+ return DEFAULT_VERTEX_LOCATION;
979
+ }
980
+ async function getGeminiClient() {
981
+ if (!clientPromise) {
982
+ clientPromise = Promise.resolve().then(() => {
983
+ const projectId = resolveProjectId();
984
+ const location = resolveLocation();
985
+ const googleAuthOptions = getGoogleAuthOptions(CLOUD_PLATFORM_SCOPE);
986
+ return new import_genai.GoogleGenAI({
987
+ vertexai: true,
988
+ project: projectId,
989
+ location,
990
+ googleAuthOptions
991
+ });
992
+ });
993
+ }
994
+ return clientPromise;
995
+ }
996
+
997
+ // src/google/calls.ts
998
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
999
+ var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set(["ECONNRESET", "ETIMEDOUT", "EAI_AGAIN"]);
1000
+ var RATE_LIMIT_REASONS = /* @__PURE__ */ new Set(["RATE_LIMIT_EXCEEDED", "RESOURCE_EXHAUSTED", "QUOTA_EXCEEDED"]);
1001
+ function getStatus(error) {
1002
+ const maybe = error;
1003
+ const candidates = [maybe?.status, maybe?.statusCode, maybe?.response?.status];
1004
+ for (const value of candidates) {
1005
+ if (typeof value === "number") {
1006
+ return value;
1007
+ }
1008
+ if (typeof value === "string") {
1009
+ const parsed = Number(value);
1010
+ if (!Number.isNaN(parsed)) {
1011
+ return parsed;
1012
+ }
1013
+ }
1014
+ }
1015
+ if (typeof maybe?.code === "number") {
1016
+ return maybe.code;
1017
+ }
1018
+ return void 0;
1019
+ }
1020
+ function getErrorCode(error) {
1021
+ if (!error || typeof error !== "object") {
1022
+ return void 0;
1023
+ }
1024
+ const maybe = error;
1025
+ if (typeof maybe.code === "string") {
1026
+ return maybe.code;
1027
+ }
1028
+ if (maybe.cause && typeof maybe.cause === "object") {
1029
+ const causeCode = maybe.cause.code;
1030
+ if (typeof causeCode === "string") {
1031
+ return causeCode;
1032
+ }
1033
+ }
1034
+ return void 0;
1035
+ }
1036
+ function getErrorReason(error) {
1037
+ if (!error || typeof error !== "object") {
1038
+ return void 0;
1039
+ }
1040
+ const details = error.errorDetails;
1041
+ if (Array.isArray(details) && details.length > 0) {
1042
+ const reason = details[0].reason;
1043
+ if (typeof reason === "string") {
1044
+ return reason;
1045
+ }
1046
+ }
1047
+ const cause = error.cause;
1048
+ if (cause && typeof cause === "object") {
1049
+ const nestedDetails = cause.errorDetails;
1050
+ if (Array.isArray(nestedDetails) && nestedDetails.length > 0) {
1051
+ const reason = nestedDetails[0].reason;
1052
+ if (typeof reason === "string") {
1053
+ return reason;
1054
+ }
1055
+ }
1056
+ }
1057
+ return void 0;
1058
+ }
1059
+ function getErrorMessage(error) {
1060
+ if (error instanceof Error) {
1061
+ return error.message;
1062
+ }
1063
+ if (typeof error === "string") {
1064
+ return error;
1065
+ }
1066
+ return "";
1067
+ }
1068
+ function parseRetryInfo(details) {
1069
+ if (Array.isArray(details)) {
1070
+ for (const entry of details) {
1071
+ const ms = parseRetryInfo(entry);
1072
+ if (ms !== void 0) {
1073
+ return ms;
1074
+ }
1075
+ }
1076
+ return void 0;
1077
+ }
1078
+ if (!details || typeof details !== "object") {
1079
+ return void 0;
1080
+ }
1081
+ const retryDelay = details.retryDelay;
1082
+ if (retryDelay) {
1083
+ const secondsRaw = retryDelay.seconds;
1084
+ const nanosRaw = retryDelay.nanos;
1085
+ const seconds = typeof secondsRaw === "number" ? secondsRaw : typeof secondsRaw === "string" ? Number.parseFloat(secondsRaw) : 0;
1086
+ const nanos = typeof nanosRaw === "number" ? nanosRaw : typeof nanosRaw === "string" ? Number.parseInt(nanosRaw, 10) : 0;
1087
+ if (Number.isFinite(seconds) || Number.isFinite(nanos)) {
1088
+ const totalMs = seconds * 1e3 + nanos / 1e6;
1089
+ if (totalMs > 0) {
1090
+ return totalMs;
1091
+ }
1092
+ }
1093
+ }
1094
+ const nestedDetails = details.details;
1095
+ if (nestedDetails) {
1096
+ const nested = parseRetryInfo(nestedDetails);
1097
+ if (nested !== void 0) {
1098
+ return nested;
1099
+ }
1100
+ }
1101
+ return void 0;
1102
+ }
1103
+ function parseRetryAfterFromMessage(message) {
1104
+ const trimmed = message.trim();
1105
+ if (!trimmed) {
1106
+ return void 0;
1107
+ }
1108
+ const regex = /retry in\\s+([0-9]+(?:\\.[0-9]+)?)\\s*(s|sec|secs|seconds?)/iu;
1109
+ const match = regex.exec(trimmed);
1110
+ if (match?.[1]) {
1111
+ const value = Number.parseFloat(match[1]);
1112
+ if (Number.isFinite(value) && value > 0) {
1113
+ return value * 1e3;
1114
+ }
1115
+ }
1116
+ return void 0;
1117
+ }
1118
+ function getRetryAfterMs(error) {
1119
+ if (!error || typeof error !== "object") {
1120
+ return void 0;
1121
+ }
1122
+ const infoFromDetails = parseRetryInfo(error.errorDetails);
1123
+ if (infoFromDetails !== void 0) {
1124
+ return infoFromDetails;
1125
+ }
1126
+ const cause = error.cause;
1127
+ if (cause && typeof cause === "object") {
1128
+ const nested = getRetryAfterMs(cause);
1129
+ if (nested !== void 0) {
1130
+ return nested;
1131
+ }
1132
+ }
1133
+ const message = getErrorMessage(error);
1134
+ if (message) {
1135
+ const fromMessage = parseRetryAfterFromMessage(message.toLowerCase());
1136
+ if (fromMessage !== void 0) {
1137
+ return fromMessage;
1138
+ }
1139
+ }
1140
+ return void 0;
1141
+ }
1142
+ function shouldRetry(error) {
1143
+ const status = getStatus(error);
1144
+ if (status && RETRYABLE_STATUSES.has(status)) {
1145
+ return true;
1146
+ }
1147
+ const reason = getErrorReason(error);
1148
+ if (reason && RATE_LIMIT_REASONS.has(reason)) {
1149
+ return true;
1150
+ }
1151
+ const code = getErrorCode(error);
1152
+ if (code && RETRYABLE_ERROR_CODES.has(code)) {
1153
+ return true;
1154
+ }
1155
+ const message = getErrorMessage(error).toLowerCase();
1156
+ if (message.includes("rate limit") || message.includes("temporarily unavailable")) {
1157
+ return true;
1158
+ }
1159
+ if (message.includes("fetch failed") || message.includes("socket hang up")) {
1160
+ return true;
1161
+ }
1162
+ if (message.includes("quota") || message.includes("insufficient")) {
1163
+ return false;
1164
+ }
1165
+ if (message.includes("timeout") || message.includes("network")) {
1166
+ return true;
1167
+ }
1168
+ return false;
1169
+ }
1170
+ function retryDelayMs(attempt) {
1171
+ const baseRetryDelayMs = 500;
1172
+ const maxRetryDelayMs = 4e3;
1173
+ const base = Math.min(maxRetryDelayMs, baseRetryDelayMs * 2 ** (attempt - 1));
1174
+ const jitter = Math.floor(Math.random() * 200);
1175
+ return base + jitter;
1176
+ }
1177
+ var scheduler = createCallScheduler({
1178
+ maxParallelRequests: 3,
1179
+ minIntervalBetweenStartMs: 200,
1180
+ startJitterMs: 200,
1181
+ retry: {
1182
+ maxAttempts: 3,
1183
+ getDelayMs: (attempt, error) => {
1184
+ if (!shouldRetry(error)) {
1185
+ return null;
1186
+ }
1187
+ const hintedDelay = getRetryAfterMs(error);
1188
+ return hintedDelay ?? retryDelayMs(attempt);
1189
+ }
1190
+ }
1191
+ });
1192
+ async function runGeminiCall(fn) {
1193
+ return scheduler.run(async () => fn(await getGeminiClient()));
1194
+ }
1195
+
1196
+ // src/openai/client.ts
1197
+ var import_openai = __toESM(require("openai"), 1);
1198
+ var import_undici = require("undici");
1199
+ var cachedApiKey = null;
1200
+ var cachedClient = null;
1201
+ var cachedFetch = null;
1202
+ var cachedTimeoutMs = null;
1203
+ var DEFAULT_OPENAI_TIMEOUT_MS = 15 * 6e4;
1204
+ function resolveOpenAiTimeoutMs() {
1205
+ if (cachedTimeoutMs !== null) {
1206
+ return cachedTimeoutMs;
1207
+ }
1208
+ const raw = process.env.OPENAI_STREAM_TIMEOUT_MS ?? process.env.OPENAI_TIMEOUT_MS;
1209
+ const parsed = raw ? Number(raw) : Number.NaN;
1210
+ cachedTimeoutMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_OPENAI_TIMEOUT_MS;
1211
+ return cachedTimeoutMs;
1212
+ }
1213
+ function getOpenAiFetch() {
1214
+ if (cachedFetch) {
1215
+ return cachedFetch;
1216
+ }
1217
+ const timeoutMs = resolveOpenAiTimeoutMs();
1218
+ const dispatcher = new import_undici.Agent({
1219
+ bodyTimeout: timeoutMs,
1220
+ headersTimeout: timeoutMs
1221
+ });
1222
+ cachedFetch = ((input, init) => {
1223
+ return (0, import_undici.fetch)(input, {
1224
+ ...init ?? {},
1225
+ dispatcher
1226
+ });
1227
+ });
1228
+ return cachedFetch;
1229
+ }
1230
+ function getOpenAiApiKey() {
1231
+ if (cachedApiKey !== null) {
1232
+ return cachedApiKey;
1233
+ }
1234
+ loadLocalEnv();
1235
+ const raw = process.env.OPENAI_API_KEY;
1236
+ const value = raw?.trim();
1237
+ if (!value) {
1238
+ throw new Error("OPENAI_API_KEY must be provided to access OpenAI APIs.");
1239
+ }
1240
+ cachedApiKey = value;
1241
+ return cachedApiKey;
1242
+ }
1243
+ function getOpenAiClient() {
1244
+ if (cachedClient) {
1245
+ return cachedClient;
1246
+ }
1247
+ const apiKey = getOpenAiApiKey();
1248
+ const timeoutMs = resolveOpenAiTimeoutMs();
1249
+ cachedClient = new import_openai.default({
1250
+ apiKey,
1251
+ fetch: getOpenAiFetch(),
1252
+ timeout: timeoutMs
1253
+ });
1254
+ return cachedClient;
1255
+ }
1256
+
1257
+ // src/openai/calls.ts
1258
+ var DEFAULT_OPENAI_REASONING_EFFORT = "medium";
1259
+ var scheduler2 = createCallScheduler({
1260
+ maxParallelRequests: 3,
1261
+ minIntervalBetweenStartMs: 200,
1262
+ startJitterMs: 200
1263
+ });
1264
+ async function runOpenAiCall(fn) {
1265
+ return scheduler2.run(async () => fn(getOpenAiClient()));
1266
+ }
1267
+
1268
+ // src/llm.ts
1269
+ var toolCallContextStorage = new import_node_async_hooks.AsyncLocalStorage();
1270
+ function getCurrentToolCallContext() {
1271
+ return toolCallContextStorage.getStore() ?? null;
1272
+ }
1273
+ var LlmJsonCallError = class extends Error {
1274
+ constructor(message, attempts) {
1275
+ super(message);
1276
+ this.attempts = attempts;
1277
+ this.name = "LlmJsonCallError";
1278
+ }
1279
+ };
1280
+ function tool(options) {
1281
+ return options;
1282
+ }
1283
+ function isPlainRecord(value) {
1284
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1285
+ }
1286
+ function applyNullableJsonSchema(schema) {
1287
+ const anyOf = schema.anyOf;
1288
+ if (Array.isArray(anyOf)) {
1289
+ if (!anyOf.some((entry) => isPlainRecord(entry) && entry.type === "null")) {
1290
+ anyOf.push({ type: "null" });
1291
+ }
1292
+ return;
1293
+ }
1294
+ const type = schema.type;
1295
+ if (typeof type === "string") {
1296
+ schema.type = type === "null" ? "null" : [type, "null"];
1297
+ return;
1298
+ }
1299
+ if (Array.isArray(type)) {
1300
+ const normalized = type.filter((entry) => typeof entry === "string");
1301
+ if (!normalized.includes("null")) {
1302
+ schema.type = [...normalized, "null"];
1303
+ } else {
1304
+ schema.type = normalized;
1305
+ }
1306
+ return;
1307
+ }
1308
+ schema.type = ["null"];
1309
+ }
1310
+ function orderedJsonSchemaKeys(properties, ordering) {
1311
+ const keys = Object.keys(properties);
1312
+ if (!ordering || ordering.length === 0) {
1313
+ return keys;
1314
+ }
1315
+ const ordered = [];
1316
+ const seen = /* @__PURE__ */ new Set();
1317
+ for (const key of ordering) {
1318
+ if (Object.hasOwn(properties, key)) {
1319
+ ordered.push(key);
1320
+ seen.add(key);
1321
+ }
1322
+ }
1323
+ for (const key of keys) {
1324
+ if (!seen.has(key)) {
1325
+ ordered.push(key);
1326
+ }
1327
+ }
1328
+ return ordered;
1329
+ }
1330
+ function addGeminiPropertyOrdering(schema) {
1331
+ if (!isPlainRecord(schema)) {
1332
+ return schema;
1333
+ }
1334
+ if (typeof schema.$ref === "string") {
1335
+ return { $ref: schema.$ref };
1336
+ }
1337
+ const output = {};
1338
+ for (const [key, value] of Object.entries(schema)) {
1339
+ if (key === "properties") {
1340
+ continue;
1341
+ }
1342
+ if (key === "items") {
1343
+ output.items = isPlainRecord(value) ? addGeminiPropertyOrdering(value) : value;
1344
+ continue;
1345
+ }
1346
+ if (key === "anyOf" || key === "oneOf") {
1347
+ output[key] = Array.isArray(value) ? value.map((entry) => addGeminiPropertyOrdering(entry)) : value;
1348
+ continue;
1349
+ }
1350
+ if (key === "$defs" && isPlainRecord(value)) {
1351
+ const defs = {};
1352
+ for (const [defKey, defValue] of Object.entries(value)) {
1353
+ if (isPlainRecord(defValue)) {
1354
+ defs[defKey] = addGeminiPropertyOrdering(defValue);
1355
+ }
1356
+ }
1357
+ output.$defs = defs;
1358
+ continue;
1359
+ }
1360
+ output[key] = value;
1361
+ }
1362
+ const propertiesRaw = schema.properties;
1363
+ if (isPlainRecord(propertiesRaw)) {
1364
+ const properties = {};
1365
+ for (const [key, value] of Object.entries(propertiesRaw)) {
1366
+ properties[key] = isPlainRecord(value) ? addGeminiPropertyOrdering(value) : value;
1367
+ }
1368
+ output.properties = properties;
1369
+ output.propertyOrdering = Object.keys(properties);
1370
+ }
1371
+ if (schema.nullable) {
1372
+ applyNullableJsonSchema(output);
1373
+ }
1374
+ return output;
1375
+ }
1376
+ function normalizeOpenAiSchema(schema) {
1377
+ if (!isPlainRecord(schema)) {
1378
+ return schema;
1379
+ }
1380
+ if (typeof schema.$ref === "string") {
1381
+ return { $ref: schema.$ref };
1382
+ }
1383
+ const output = {};
1384
+ for (const [key, value] of Object.entries(schema)) {
1385
+ if (key === "properties") {
1386
+ continue;
1387
+ }
1388
+ if (key === "required") {
1389
+ continue;
1390
+ }
1391
+ if (key === "additionalProperties") {
1392
+ continue;
1393
+ }
1394
+ if (key === "propertyOrdering") {
1395
+ continue;
1396
+ }
1397
+ if (key === "items") {
1398
+ if (isPlainRecord(value)) {
1399
+ output.items = normalizeOpenAiSchema(value);
1400
+ }
1401
+ continue;
1402
+ }
1403
+ if (key === "anyOf" || key === "oneOf") {
1404
+ if (Array.isArray(value)) {
1405
+ output.anyOf = value.map((entry) => normalizeOpenAiSchema(entry));
1406
+ }
1407
+ continue;
1408
+ }
1409
+ if (key === "$defs" && isPlainRecord(value)) {
1410
+ const defs = {};
1411
+ for (const [defKey, defValue] of Object.entries(value)) {
1412
+ if (isPlainRecord(defValue)) {
1413
+ defs[defKey] = normalizeOpenAiSchema(defValue);
1414
+ }
1415
+ }
1416
+ output.$defs = defs;
1417
+ continue;
1418
+ }
1419
+ output[key] = value;
1420
+ }
1421
+ const propertiesRaw = schema.properties;
1422
+ if (isPlainRecord(propertiesRaw)) {
1423
+ const ordering = Array.isArray(schema.propertyOrdering) ? schema.propertyOrdering : void 0;
1424
+ const orderedKeys = orderedJsonSchemaKeys(propertiesRaw, ordering);
1425
+ const properties = {};
1426
+ for (const key of orderedKeys) {
1427
+ const value = propertiesRaw[key];
1428
+ if (!isPlainRecord(value)) {
1429
+ properties[key] = value;
1430
+ continue;
1431
+ }
1432
+ properties[key] = normalizeOpenAiSchema(value);
1433
+ }
1434
+ output.properties = properties;
1435
+ output.required = orderedKeys;
1436
+ output.additionalProperties = false;
1437
+ }
1438
+ const schemaType = schema.type;
1439
+ if (output.additionalProperties === void 0 && (schemaType === "object" || Array.isArray(schemaType) && schemaType.includes("object"))) {
1440
+ output.additionalProperties = false;
1441
+ if (!Array.isArray(output.required)) {
1442
+ output.required = [];
1443
+ }
1444
+ }
1445
+ const normalizeExclusiveBound = (options) => {
1446
+ const exclusiveValue = output[options.exclusiveKey];
1447
+ if (exclusiveValue === false) {
1448
+ delete output[options.exclusiveKey];
1449
+ return;
1450
+ }
1451
+ const inclusiveValue = output[options.inclusiveKey];
1452
+ if (exclusiveValue === true) {
1453
+ if (typeof inclusiveValue === "number" && Number.isFinite(inclusiveValue)) {
1454
+ output[options.exclusiveKey] = inclusiveValue;
1455
+ delete output[options.inclusiveKey];
1456
+ } else {
1457
+ delete output[options.exclusiveKey];
1458
+ }
1459
+ return;
1460
+ }
1461
+ if (typeof exclusiveValue === "number" && Number.isFinite(exclusiveValue)) {
1462
+ delete output[options.inclusiveKey];
1463
+ }
1464
+ };
1465
+ normalizeExclusiveBound({
1466
+ exclusiveKey: "exclusiveMinimum",
1467
+ inclusiveKey: "minimum"
1468
+ });
1469
+ normalizeExclusiveBound({
1470
+ exclusiveKey: "exclusiveMaximum",
1471
+ inclusiveKey: "maximum"
1472
+ });
1473
+ return output;
1474
+ }
1475
+ function resolveOpenAiSchemaRoot(schema) {
1476
+ if (!isPlainRecord(schema)) {
1477
+ return schema;
1478
+ }
1479
+ if (typeof schema.$ref !== "string") {
1480
+ return schema;
1481
+ }
1482
+ const refMatch = /^#\/(definitions|[$]defs)\/(.+)$/u.exec(schema.$ref);
1483
+ if (!refMatch) {
1484
+ return schema;
1485
+ }
1486
+ const section = refMatch[1];
1487
+ const key = refMatch[2];
1488
+ if (!section || !key) {
1489
+ return schema;
1490
+ }
1491
+ const defsSource = section === "definitions" ? schema.definitions : schema.$defs;
1492
+ if (!isPlainRecord(defsSource)) {
1493
+ return schema;
1494
+ }
1495
+ const resolved = defsSource[key];
1496
+ if (!isPlainRecord(resolved)) {
1497
+ return schema;
1498
+ }
1499
+ return { ...resolved };
1500
+ }
1501
+ function toGeminiJsonSchema(schema, options) {
1502
+ const jsonSchema = (0, import_zod_to_json_schema.zodToJsonSchema)(schema, {
1503
+ name: options?.name,
1504
+ target: "jsonSchema7"
1505
+ });
1506
+ return addGeminiPropertyOrdering(resolveOpenAiSchemaRoot(jsonSchema));
1507
+ }
1508
+ function isJsonSchemaObject(schema) {
1509
+ if (!schema || !isPlainRecord(schema)) {
1510
+ return false;
1511
+ }
1512
+ const type = schema.type;
1513
+ if (type === "object") {
1514
+ return true;
1515
+ }
1516
+ if (Array.isArray(type) && type.includes("object")) {
1517
+ return true;
1518
+ }
1519
+ if (isPlainRecord(schema.properties)) {
1520
+ return true;
1521
+ }
1522
+ return false;
1523
+ }
1524
+ function sanitisePartForLogging(part) {
1525
+ switch (part.type) {
1526
+ case "text":
1527
+ return {
1528
+ type: "text",
1529
+ thought: part.thought === true ? true : void 0,
1530
+ preview: part.text.slice(0, 200)
1531
+ };
1532
+ case "inlineData": {
1533
+ let omittedBytes;
1534
+ try {
1535
+ omittedBytes = import_node_buffer2.Buffer.from(part.data, "base64").byteLength;
1536
+ } catch {
1537
+ omittedBytes = import_node_buffer2.Buffer.byteLength(part.data, "utf8");
1538
+ }
1539
+ return {
1540
+ type: "inlineData",
1541
+ mimeType: part.mimeType,
1542
+ data: `[omitted:${omittedBytes}b]`
1543
+ };
1544
+ }
1545
+ default:
1546
+ return "[unknown part]";
1547
+ }
1548
+ }
1549
+ function convertGooglePartsToLlmParts(parts) {
1550
+ const result = [];
1551
+ for (const part of parts) {
1552
+ if (part.text !== void 0) {
1553
+ result.push({
1554
+ type: "text",
1555
+ text: part.text,
1556
+ thought: part.thought ? true : void 0
1557
+ });
1558
+ continue;
1559
+ }
1560
+ const inline = part.inlineData;
1561
+ if (inline?.data) {
1562
+ result.push({
1563
+ type: "inlineData",
1564
+ data: inline.data,
1565
+ mimeType: inline.mimeType
1566
+ });
1567
+ continue;
1568
+ }
1569
+ if (part.fileData?.fileUri) {
1570
+ throw new Error("fileData parts are not supported");
1571
+ }
1572
+ }
1573
+ return result;
1574
+ }
1575
+ function assertLlmRole(value) {
1576
+ switch (value) {
1577
+ case "user":
1578
+ case "model":
1579
+ case "system":
1580
+ case "tool":
1581
+ return value;
1582
+ default:
1583
+ throw new Error(`Unsupported LLM role: ${String(value)}`);
1584
+ }
1585
+ }
1586
+ function convertGeminiContentToLlmContent(content) {
1587
+ return {
1588
+ role: assertLlmRole(content.role),
1589
+ parts: convertGooglePartsToLlmParts(content.parts ?? [])
1590
+ };
1591
+ }
1592
+ function toGeminiPart(part) {
1593
+ switch (part.type) {
1594
+ case "text":
1595
+ return {
1596
+ text: part.text,
1597
+ thought: part.thought === true ? true : void 0
1598
+ };
1599
+ case "inlineData":
1600
+ return {
1601
+ inlineData: {
1602
+ data: part.data,
1603
+ mimeType: part.mimeType
1604
+ }
1605
+ };
1606
+ default:
1607
+ throw new Error("Unsupported LLM content part");
1608
+ }
1609
+ }
1610
+ function convertLlmContentToGeminiContent(content) {
1611
+ return {
1612
+ role: content.role,
1613
+ parts: content.parts.map(toGeminiPart)
1614
+ };
1615
+ }
1616
+ function resolveProvider(model) {
1617
+ if (model.startsWith("chatgpt-")) {
1618
+ return { provider: "chatgpt", model: model.slice("chatgpt-".length) };
1619
+ }
1620
+ if (model.startsWith("gemini-")) {
1621
+ return { provider: "gemini", model };
1622
+ }
1623
+ return { provider: "openai", model };
1624
+ }
1625
+ function isOpenAiCodexModel(modelId) {
1626
+ return modelId.includes("codex");
1627
+ }
1628
+ function resolveOpenAiReasoningEffort(modelId, override) {
1629
+ if (override) {
1630
+ return override;
1631
+ }
1632
+ if (isOpenAiCodexModel(modelId)) {
1633
+ return "medium";
1634
+ }
1635
+ return DEFAULT_OPENAI_REASONING_EFFORT;
1636
+ }
1637
+ function toOpenAiReasoningEffort(effort) {
1638
+ switch (effort) {
1639
+ case "low":
1640
+ return "low";
1641
+ case "medium":
1642
+ return "medium";
1643
+ case "high":
1644
+ return "high";
1645
+ case "xhigh":
1646
+ return "high";
1647
+ }
1648
+ }
1649
+ function resolveOpenAiVerbosity(modelId) {
1650
+ return isOpenAiCodexModel(modelId) ? "medium" : "high";
1651
+ }
1652
+ function isInlineImageMime(mimeType) {
1653
+ if (!mimeType) {
1654
+ return false;
1655
+ }
1656
+ return mimeType.startsWith("image/");
1657
+ }
1658
+ function mergeConsecutiveTextParts(parts) {
1659
+ if (parts.length === 0) {
1660
+ return [];
1661
+ }
1662
+ const merged = [];
1663
+ for (const part of parts) {
1664
+ if (part.type !== "text") {
1665
+ merged.push({ type: "inlineData", data: part.data, mimeType: part.mimeType });
1666
+ continue;
1667
+ }
1668
+ const isThought = part.thought === true;
1669
+ const last = merged[merged.length - 1];
1670
+ if (last && last.type === "text" && last.thought === true === isThought) {
1671
+ last.text += part.text;
1672
+ last.thought = isThought ? true : void 0;
1673
+ } else {
1674
+ merged.push({
1675
+ type: "text",
1676
+ text: part.text,
1677
+ thought: isThought ? true : void 0
1678
+ });
1679
+ }
1680
+ }
1681
+ return merged;
1682
+ }
1683
+ function extractTextByChannel(content) {
1684
+ if (!content) {
1685
+ return { text: "", thoughts: "" };
1686
+ }
1687
+ let text = "";
1688
+ let thoughts = "";
1689
+ for (const part of content.parts) {
1690
+ if (part.type !== "text") {
1691
+ continue;
1692
+ }
1693
+ if (part.thought === true) {
1694
+ thoughts += part.text;
1695
+ } else {
1696
+ text += part.text;
1697
+ }
1698
+ }
1699
+ return { text: text.trim(), thoughts: thoughts.trim() };
1700
+ }
1701
+ function normalizeJsonText(rawText) {
1702
+ let text = rawText.trim();
1703
+ if (text.startsWith("```")) {
1704
+ text = text.replace(/^```[a-zA-Z0-9_-]*\s*\n?/, "");
1705
+ text = text.replace(/```(?:\s*)?$/, "").trim();
1706
+ }
1707
+ const fenced = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(text);
1708
+ if (fenced?.[1]) {
1709
+ return fenced[1].trim();
1710
+ }
1711
+ if (!text.startsWith("{") && !text.startsWith("[")) {
1712
+ const firstBrace = text.indexOf("{");
1713
+ if (firstBrace !== -1) {
1714
+ const lastBrace = text.lastIndexOf("}");
1715
+ if (lastBrace !== -1 && lastBrace > firstBrace) {
1716
+ text = text.slice(firstBrace, lastBrace + 1).trim();
1717
+ }
1718
+ }
1719
+ }
1720
+ return text;
1721
+ }
1722
+ function escapeNewlinesInStrings(jsonText) {
1723
+ let output = "";
1724
+ let inString = false;
1725
+ let escaped = false;
1726
+ for (let i = 0; i < jsonText.length; i += 1) {
1727
+ const char = jsonText[i] ?? "";
1728
+ if (inString) {
1729
+ if (escaped) {
1730
+ output += char;
1731
+ escaped = false;
1732
+ continue;
1733
+ }
1734
+ if (char === "\\") {
1735
+ output += char;
1736
+ escaped = true;
1737
+ continue;
1738
+ }
1739
+ if (char === '"') {
1740
+ output += char;
1741
+ inString = false;
1742
+ continue;
1743
+ }
1744
+ if (char === "\n") {
1745
+ output += "\\n";
1746
+ continue;
1747
+ }
1748
+ if (char === "\r") {
1749
+ output += "\\r";
1750
+ continue;
1751
+ }
1752
+ output += char;
1753
+ continue;
1754
+ }
1755
+ if (char === '"') {
1756
+ inString = true;
1757
+ output += char;
1758
+ continue;
1759
+ }
1760
+ output += char;
1761
+ }
1762
+ return output;
1763
+ }
1764
+ function parseJsonFromLlmText(rawText) {
1765
+ const cleanedText = normalizeJsonText(rawText);
1766
+ const repairedText = escapeNewlinesInStrings(cleanedText);
1767
+ return JSON.parse(repairedText);
1768
+ }
1769
+ function resolveTextContents(input) {
1770
+ if ("contents" in input) {
1771
+ return input.contents;
1772
+ }
1773
+ const contents = [];
1774
+ if (input.systemPrompt) {
1775
+ contents.push({
1776
+ role: "system",
1777
+ parts: [{ type: "text", text: input.systemPrompt }]
1778
+ });
1779
+ }
1780
+ contents.push({
1781
+ role: "user",
1782
+ parts: [{ type: "text", text: input.prompt }]
1783
+ });
1784
+ return contents;
1785
+ }
1786
+ function toOpenAiInput(contents) {
1787
+ const OPENAI_ROLE_FROM_LLM = {
1788
+ user: "user",
1789
+ model: "assistant",
1790
+ system: "system",
1791
+ tool: "assistant"
1792
+ };
1793
+ return contents.map((content) => {
1794
+ const parts = [];
1795
+ for (const part of content.parts) {
1796
+ if (part.type === "text") {
1797
+ parts.push({ type: "input_text", text: part.text });
1798
+ continue;
1799
+ }
1800
+ const mimeType = part.mimeType ?? "application/octet-stream";
1801
+ const dataUrl = `data:${mimeType};base64,${part.data}`;
1802
+ parts.push({ type: "input_image", image_url: dataUrl, detail: "auto" });
1803
+ }
1804
+ if (parts.length === 1 && parts[0]?.type === "input_text" && typeof parts[0].text === "string") {
1805
+ return {
1806
+ role: OPENAI_ROLE_FROM_LLM[content.role],
1807
+ content: parts[0].text
1808
+ };
1809
+ }
1810
+ return {
1811
+ role: OPENAI_ROLE_FROM_LLM[content.role],
1812
+ content: parts
1813
+ };
1814
+ });
1815
+ }
1816
+ function toChatGptInput(contents) {
1817
+ const instructionsParts = [];
1818
+ const input = [];
1819
+ for (const content of contents) {
1820
+ if (content.role === "system") {
1821
+ for (const part of content.parts) {
1822
+ if (part.type === "text") {
1823
+ instructionsParts.push(part.text);
1824
+ }
1825
+ }
1826
+ continue;
1827
+ }
1828
+ const isAssistant = content.role === "model";
1829
+ const parts = [];
1830
+ for (const part of content.parts) {
1831
+ if (part.type === "text") {
1832
+ parts.push({
1833
+ type: isAssistant ? "output_text" : "input_text",
1834
+ text: part.text
1835
+ });
1836
+ continue;
1837
+ }
1838
+ const mimeType = part.mimeType ?? "application/octet-stream";
1839
+ const dataUrl = `data:${mimeType};base64,${part.data}`;
1840
+ if (isAssistant) {
1841
+ parts.push({
1842
+ type: "output_text",
1843
+ text: `[image:${mimeType}]`
1844
+ });
1845
+ } else {
1846
+ parts.push({
1847
+ type: "input_image",
1848
+ image_url: dataUrl,
1849
+ detail: "auto"
1850
+ });
1851
+ }
1852
+ }
1853
+ if (parts.length === 0) {
1854
+ parts.push({
1855
+ type: isAssistant ? "output_text" : "input_text",
1856
+ text: "(empty content)"
1857
+ });
1858
+ }
1859
+ if (isAssistant) {
1860
+ input.push({
1861
+ type: "message",
1862
+ role: "assistant",
1863
+ status: "completed",
1864
+ content: parts
1865
+ });
1866
+ } else {
1867
+ input.push({
1868
+ role: "user",
1869
+ content: parts
1870
+ });
1871
+ }
1872
+ }
1873
+ const instructions = instructionsParts.map((part) => part.trim()).filter((part) => part.length > 0).join("\\n\\n");
1874
+ return {
1875
+ instructions: instructions.length > 0 ? instructions : void 0,
1876
+ input
1877
+ };
1878
+ }
1879
+ function toGeminiTools(tools) {
1880
+ if (!tools || tools.length === 0) {
1881
+ return void 0;
1882
+ }
1883
+ return tools.map((tool2) => {
1884
+ switch (tool2.type) {
1885
+ case "web-search":
1886
+ return { googleSearch: {} };
1887
+ case "code-execution":
1888
+ return { codeExecution: {} };
1889
+ default:
1890
+ throw new Error("Unsupported tool configuration");
1891
+ }
1892
+ });
1893
+ }
1894
+ function toOpenAiTools(tools) {
1895
+ if (!tools || tools.length === 0) {
1896
+ return void 0;
1897
+ }
1898
+ return tools.map((tool2) => {
1899
+ switch (tool2.type) {
1900
+ case "web-search": {
1901
+ const external_web_access = tool2.mode !== "cached";
1902
+ return { type: "web_search", external_web_access };
1903
+ }
1904
+ case "code-execution": {
1905
+ return { type: "code_interpreter", container: { type: "auto" } };
1906
+ }
1907
+ default:
1908
+ throw new Error("Unsupported tool configuration");
1909
+ }
1910
+ });
1911
+ }
1912
+ function mergeTokenUpdates(current, next) {
1913
+ if (!next) {
1914
+ return current;
1915
+ }
1916
+ if (!current) {
1917
+ return next;
1918
+ }
1919
+ return {
1920
+ promptTokens: next.promptTokens ?? current.promptTokens,
1921
+ cachedTokens: next.cachedTokens ?? current.cachedTokens,
1922
+ responseTokens: next.responseTokens ?? current.responseTokens,
1923
+ responseImageTokens: next.responseImageTokens ?? current.responseImageTokens,
1924
+ thinkingTokens: next.thinkingTokens ?? current.thinkingTokens,
1925
+ totalTokens: next.totalTokens ?? current.totalTokens,
1926
+ toolUsePromptTokens: next.toolUsePromptTokens ?? current.toolUsePromptTokens
1927
+ };
1928
+ }
1929
+ function toMaybeNumber(value) {
1930
+ if (typeof value === "number" && Number.isFinite(value)) {
1931
+ return value;
1932
+ }
1933
+ if (typeof value === "string") {
1934
+ const parsed = Number.parseFloat(value);
1935
+ if (Number.isFinite(parsed)) {
1936
+ return parsed;
1937
+ }
1938
+ }
1939
+ return void 0;
1940
+ }
1941
+ function sumModalityTokenCounts(details, modality) {
1942
+ if (!Array.isArray(details)) {
1943
+ return 0;
1944
+ }
1945
+ let total = 0;
1946
+ for (const entry of details) {
1947
+ const entryModality = entry.modality;
1948
+ if (typeof entryModality !== "string") {
1949
+ continue;
1950
+ }
1951
+ if (entryModality.toUpperCase() !== modality.toUpperCase()) {
1952
+ continue;
1953
+ }
1954
+ const tokenCount = toMaybeNumber(entry.tokenCount);
1955
+ if (tokenCount !== void 0 && tokenCount > 0) {
1956
+ total += tokenCount;
1957
+ }
1958
+ }
1959
+ return total;
1960
+ }
1961
+ function extractGeminiUsageTokens(usage) {
1962
+ if (!usage || typeof usage !== "object") {
1963
+ return void 0;
1964
+ }
1965
+ const promptTokens = toMaybeNumber(usage.promptTokenCount);
1966
+ const cachedTokens = toMaybeNumber(
1967
+ usage.cachedContentTokenCount
1968
+ );
1969
+ const responseTokens = toMaybeNumber(
1970
+ usage.candidatesTokenCount ?? usage.responseTokenCount
1971
+ );
1972
+ const thinkingTokens = toMaybeNumber(
1973
+ usage.thoughtsTokenCount
1974
+ );
1975
+ const totalTokens = toMaybeNumber(usage.totalTokenCount);
1976
+ const toolUsePromptTokens = toMaybeNumber(
1977
+ usage.toolUsePromptTokenCount
1978
+ );
1979
+ const responseDetails = usage.candidatesTokensDetails ?? usage.responseTokensDetails;
1980
+ const responseImageTokens = sumModalityTokenCounts(responseDetails, "IMAGE");
1981
+ if (promptTokens === void 0 && cachedTokens === void 0 && responseTokens === void 0 && responseImageTokens === 0 && thinkingTokens === void 0 && totalTokens === void 0 && toolUsePromptTokens === void 0) {
1982
+ return void 0;
1983
+ }
1984
+ return {
1985
+ promptTokens,
1986
+ cachedTokens,
1987
+ responseTokens,
1988
+ responseImageTokens: responseImageTokens > 0 ? responseImageTokens : void 0,
1989
+ thinkingTokens,
1990
+ totalTokens,
1991
+ toolUsePromptTokens
1992
+ };
1993
+ }
1994
+ function extractOpenAiUsageTokens(usage) {
1995
+ if (!usage || typeof usage !== "object") {
1996
+ return void 0;
1997
+ }
1998
+ const promptTokens = toMaybeNumber(usage.input_tokens);
1999
+ const cachedTokens = toMaybeNumber(
2000
+ usage.input_tokens_details?.cached_tokens
2001
+ );
2002
+ const outputTokensRaw = toMaybeNumber(usage.output_tokens);
2003
+ const reasoningTokens = toMaybeNumber(
2004
+ usage.output_tokens_details?.reasoning_tokens
2005
+ );
2006
+ const totalTokens = toMaybeNumber(usage.total_tokens);
2007
+ let responseTokens;
2008
+ if (outputTokensRaw !== void 0) {
2009
+ const adjusted = outputTokensRaw - (reasoningTokens ?? 0);
2010
+ responseTokens = adjusted >= 0 ? adjusted : 0;
2011
+ }
2012
+ if (promptTokens === void 0 && cachedTokens === void 0 && responseTokens === void 0 && reasoningTokens === void 0 && totalTokens === void 0) {
2013
+ return void 0;
2014
+ }
2015
+ return {
2016
+ promptTokens,
2017
+ cachedTokens,
2018
+ responseTokens,
2019
+ thinkingTokens: reasoningTokens,
2020
+ totalTokens
2021
+ };
2022
+ }
2023
+ function extractChatGptUsageTokens(usage) {
2024
+ if (!usage || typeof usage !== "object") {
2025
+ return void 0;
2026
+ }
2027
+ const promptTokens = toMaybeNumber(usage.input_tokens);
2028
+ const cachedTokens = toMaybeNumber(
2029
+ usage.input_tokens_details?.cached_tokens
2030
+ );
2031
+ const outputTokensRaw = toMaybeNumber(usage.output_tokens);
2032
+ const reasoningTokens = toMaybeNumber(
2033
+ usage.output_tokens_details?.reasoning_tokens
2034
+ );
2035
+ const totalTokens = toMaybeNumber(usage.total_tokens);
2036
+ let responseTokens;
2037
+ if (outputTokensRaw !== void 0) {
2038
+ const adjusted = outputTokensRaw - (reasoningTokens ?? 0);
2039
+ responseTokens = adjusted >= 0 ? adjusted : 0;
2040
+ }
2041
+ if (promptTokens === void 0 && cachedTokens === void 0 && responseTokens === void 0 && reasoningTokens === void 0 && totalTokens === void 0) {
2042
+ return void 0;
2043
+ }
2044
+ return {
2045
+ promptTokens,
2046
+ cachedTokens,
2047
+ responseTokens,
2048
+ thinkingTokens: reasoningTokens,
2049
+ totalTokens
2050
+ };
2051
+ }
2052
+ var MODERATION_FINISH_REASONS = /* @__PURE__ */ new Set([
2053
+ import_genai2.FinishReason.SAFETY,
2054
+ import_genai2.FinishReason.BLOCKLIST,
2055
+ import_genai2.FinishReason.PROHIBITED_CONTENT,
2056
+ import_genai2.FinishReason.SPII
2057
+ ]);
2058
+ function isModerationFinish(reason) {
2059
+ if (!reason) {
2060
+ return false;
2061
+ }
2062
+ return MODERATION_FINISH_REASONS.has(reason);
2063
+ }
2064
+ function mergeToolOutput(value) {
2065
+ if (typeof value === "string") {
2066
+ return value;
2067
+ }
2068
+ try {
2069
+ return JSON.stringify(value);
2070
+ } catch (error) {
2071
+ const message = error instanceof Error ? error.message : String(error);
2072
+ return JSON.stringify({ error: "Failed to serialize tool output", detail: message });
2073
+ }
2074
+ }
2075
+ function parseOpenAiToolArguments(raw) {
2076
+ const trimmed = raw.trim();
2077
+ if (trimmed.length === 0) {
2078
+ return { value: {} };
2079
+ }
2080
+ try {
2081
+ return { value: JSON.parse(trimmed) };
2082
+ } catch (error) {
2083
+ const message = error instanceof Error ? error.message : String(error);
2084
+ return { value: raw, error: message };
2085
+ }
2086
+ }
2087
+ function formatZodIssues(issues) {
2088
+ const messages = [];
2089
+ for (const issue of issues) {
2090
+ const path2 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
2091
+ messages.push(`${path2}: ${issue.message}`);
2092
+ }
2093
+ return messages.join("; ");
2094
+ }
2095
+ function buildToolErrorOutput(message, issues) {
2096
+ const output = { error: message };
2097
+ if (issues && issues.length > 0) {
2098
+ output.issues = issues.map((issue) => ({
2099
+ path: issue.path.map(String),
2100
+ message: issue.message,
2101
+ code: issue.code
2102
+ }));
2103
+ }
2104
+ return output;
2105
+ }
2106
+ async function executeToolCall(params) {
2107
+ const { toolName, tool: tool2, rawInput, parseError } = params;
2108
+ if (!tool2) {
2109
+ const message = `Unknown tool: ${toolName}`;
2110
+ return {
2111
+ result: { toolName, input: rawInput, output: { error: message }, error: message },
2112
+ outputPayload: buildToolErrorOutput(message)
2113
+ };
2114
+ }
2115
+ if (parseError) {
2116
+ const message = `Invalid JSON for tool ${toolName}: ${parseError}`;
2117
+ return {
2118
+ result: { toolName, input: rawInput, output: { error: message }, error: message },
2119
+ outputPayload: buildToolErrorOutput(message)
2120
+ };
2121
+ }
2122
+ const parsed = tool2.inputSchema.safeParse(rawInput);
2123
+ if (!parsed.success) {
2124
+ const message = `Invalid tool arguments for ${toolName}: ${formatZodIssues(parsed.error.issues)}`;
2125
+ const outputPayload = buildToolErrorOutput(message, parsed.error.issues);
2126
+ return {
2127
+ result: { toolName, input: rawInput, output: outputPayload, error: message },
2128
+ outputPayload
2129
+ };
2130
+ }
2131
+ try {
2132
+ const output = await tool2.execute(parsed.data);
2133
+ return {
2134
+ result: { toolName, input: parsed.data, output },
2135
+ outputPayload: output
2136
+ };
2137
+ } catch (error) {
2138
+ const message = error instanceof Error ? error.message : String(error);
2139
+ const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
2140
+ return {
2141
+ result: { toolName, input: parsed.data, output: outputPayload, error: message },
2142
+ outputPayload
2143
+ };
2144
+ }
2145
+ }
2146
+ function buildToolLogId(turn, toolIndex) {
2147
+ return `turn${turn.toString()}/tool${toolIndex.toString()}`;
2148
+ }
2149
+ function sanitizeChatGptToolId(value) {
2150
+ const cleaned = value.replace(/[^A-Za-z0-9_-]/gu, "");
2151
+ if (cleaned.length === 0) {
2152
+ return (0, import_node_crypto.randomBytes)(8).toString("hex");
2153
+ }
2154
+ return cleaned.slice(0, 64);
2155
+ }
2156
+ function normalizeChatGptToolIds(params) {
2157
+ let rawCallId = params.callId ?? "";
2158
+ let rawItemId = params.itemId ?? "";
2159
+ if (rawCallId.includes("|")) {
2160
+ const [nextCallId, nextItemId] = rawCallId.split("|");
2161
+ rawCallId = nextCallId ?? rawCallId;
2162
+ if (nextItemId) {
2163
+ rawItemId = nextItemId;
2164
+ }
2165
+ } else if (rawItemId.includes("|")) {
2166
+ const [nextCallId, nextItemId] = rawItemId.split("|");
2167
+ rawCallId = nextCallId ?? rawCallId;
2168
+ rawItemId = nextItemId ?? rawItemId;
2169
+ }
2170
+ const callValue = sanitizeChatGptToolId(rawCallId || rawItemId || (0, import_node_crypto.randomBytes)(8).toString("hex"));
2171
+ let itemValue = sanitizeChatGptToolId(rawItemId || `fc-${callValue}`);
2172
+ if (!itemValue.startsWith("fc")) {
2173
+ itemValue = `fc-${itemValue}`;
2174
+ }
2175
+ return { callId: callValue, itemId: itemValue };
2176
+ }
2177
+ function extractOpenAiResponseParts(response) {
2178
+ const parts = [];
2179
+ let blocked = false;
2180
+ const output = response.output;
2181
+ if (Array.isArray(output)) {
2182
+ for (const item of output) {
2183
+ if (!item || typeof item !== "object") {
2184
+ continue;
2185
+ }
2186
+ const itemType = item.type;
2187
+ if (itemType === "message") {
2188
+ const content = item.content;
2189
+ if (Array.isArray(content)) {
2190
+ for (const entry of content) {
2191
+ if (!entry || typeof entry !== "object") {
2192
+ continue;
2193
+ }
2194
+ const entryType = entry.type;
2195
+ if (entryType === "output_text") {
2196
+ const text = entry.text;
2197
+ if (typeof text === "string" && text.length > 0) {
2198
+ parts.push({ type: "text", text });
2199
+ }
2200
+ } else if (entryType === "refusal") {
2201
+ blocked = true;
2202
+ }
2203
+ }
2204
+ }
2205
+ } else if (itemType === "reasoning") {
2206
+ const content = item.content;
2207
+ if (Array.isArray(content)) {
2208
+ for (const entry of content) {
2209
+ if (!entry || typeof entry !== "object") {
2210
+ continue;
2211
+ }
2212
+ const entryType = entry.type;
2213
+ if (entryType === "reasoning_summary_text" || entryType === "reasoning_summary") {
2214
+ const entryText = typeof entry.text === "string" ? entry.text : typeof entry.summary === "string" ? entry.summary : void 0;
2215
+ if (typeof entryText === "string" && entryText.length > 0) {
2216
+ parts.push({ type: "text", text: entryText, thought: true });
2217
+ }
2218
+ }
2219
+ }
2220
+ }
2221
+ } else if (itemType === "function_call" || itemType === "tool_call" || itemType === "custom_tool_call") {
2222
+ const serialized = JSON.stringify(item, null, 2);
2223
+ if (serialized.length > 0) {
2224
+ parts.push({ type: "text", text: `[tool-call]\\n${serialized}\\n` });
2225
+ }
2226
+ }
2227
+ }
2228
+ }
2229
+ if (parts.length === 0) {
2230
+ const outputText = response.output_text;
2231
+ if (typeof outputText === "string" && outputText.length > 0) {
2232
+ parts.push({ type: "text", text: outputText });
2233
+ }
2234
+ }
2235
+ return { parts, blocked };
2236
+ }
2237
+ function extractOpenAiFunctionCalls(output) {
2238
+ const calls = [];
2239
+ if (!Array.isArray(output)) {
2240
+ return calls;
2241
+ }
2242
+ for (const item of output) {
2243
+ if (!item || typeof item !== "object") {
2244
+ continue;
2245
+ }
2246
+ if (item.type === "function_call") {
2247
+ const name = typeof item.name === "string" ? item.name : "";
2248
+ const args = typeof item.arguments === "string" ? item.arguments : "";
2249
+ const call_id = typeof item.call_id === "string" ? item.call_id : "";
2250
+ const id = typeof item.id === "string" ? item.id : void 0;
2251
+ if (name && call_id) {
2252
+ calls.push({ name, arguments: args, call_id, id });
2253
+ }
2254
+ }
2255
+ }
2256
+ return calls;
2257
+ }
2258
+ function resolveGeminiThinkingConfig(modelId) {
2259
+ switch (modelId) {
2260
+ case "gemini-3-pro-preview":
2261
+ return { includeThoughts: true };
2262
+ case "gemini-2.5-pro":
2263
+ return { includeThoughts: true, thinkingBudget: 32768 };
2264
+ case "gemini-flash-latest":
2265
+ case "gemini-flash-lite-latest":
2266
+ return { includeThoughts: true, thinkingBudget: 24576 };
2267
+ default:
2268
+ return { includeThoughts: true };
2269
+ }
2270
+ }
2271
+ function decodeInlineDataBuffer(base64) {
2272
+ try {
2273
+ return import_node_buffer2.Buffer.from(base64, "base64");
2274
+ } catch {
2275
+ return import_node_buffer2.Buffer.from(base64, "base64url");
2276
+ }
2277
+ }
2278
+ function extractImages(content) {
2279
+ if (!content) {
2280
+ return [];
2281
+ }
2282
+ const images = [];
2283
+ for (const part of content.parts) {
2284
+ if (part.type !== "inlineData") {
2285
+ continue;
2286
+ }
2287
+ const buffer = decodeInlineDataBuffer(part.data);
2288
+ images.push({ mimeType: part.mimeType, data: buffer });
2289
+ }
2290
+ return images;
2291
+ }
2292
+ async function runTextCall(params) {
2293
+ const { request, queue, abortController } = params;
2294
+ const providerInfo = resolveProvider(request.model);
2295
+ const provider = providerInfo.provider;
2296
+ const modelForProvider = providerInfo.model;
2297
+ const contents = resolveTextContents(request);
2298
+ if (contents.length === 0) {
2299
+ throw new Error("LLM call received an empty prompt.");
2300
+ }
2301
+ let modelVersion = request.model;
2302
+ let blocked = false;
2303
+ let grounding;
2304
+ const responseParts = [];
2305
+ let responseRole;
2306
+ let latestUsage;
2307
+ let responseImages = 0;
2308
+ const pushDelta = (channel, text2) => {
2309
+ if (!text2) {
2310
+ return;
2311
+ }
2312
+ responseParts.push({ type: "text", text: text2, ...channel === "thought" ? { thought: true } : {} });
2313
+ queue.push({ type: "delta", channel, text: text2 });
2314
+ };
2315
+ const pushInline = (data, mimeType) => {
2316
+ if (!data) {
2317
+ return;
2318
+ }
2319
+ responseParts.push({ type: "inlineData", data, mimeType });
2320
+ if (isInlineImageMime(mimeType)) {
2321
+ responseImages += 1;
2322
+ }
2323
+ };
2324
+ const resolveAbortSignal = () => {
2325
+ if (!request.signal) {
2326
+ return abortController.signal;
2327
+ }
2328
+ if (request.signal.aborted) {
2329
+ abortController.abort(request.signal.reason);
2330
+ } else {
2331
+ request.signal.addEventListener(
2332
+ "abort",
2333
+ () => abortController.abort(request.signal?.reason),
2334
+ { once: true }
2335
+ );
2336
+ }
2337
+ return abortController.signal;
2338
+ };
2339
+ const signal = resolveAbortSignal();
2340
+ if (provider === "openai") {
2341
+ const openAiInput = toOpenAiInput(contents);
2342
+ const openAiTools = toOpenAiTools(request.tools);
2343
+ const reasoningEffort = resolveOpenAiReasoningEffort(
2344
+ modelForProvider,
2345
+ request.openAiReasoningEffort
2346
+ );
2347
+ const openAiTextConfig = {
2348
+ format: request.openAiTextFormat ?? { type: "text" },
2349
+ verbosity: resolveOpenAiVerbosity(modelForProvider)
2350
+ };
2351
+ const reasoning = {
2352
+ effort: toOpenAiReasoningEffort(reasoningEffort),
2353
+ summary: "detailed"
2354
+ };
2355
+ await runOpenAiCall(async (client) => {
2356
+ const stream = client.responses.stream(
2357
+ {
2358
+ model: modelForProvider,
2359
+ input: openAiInput,
2360
+ reasoning,
2361
+ text: openAiTextConfig,
2362
+ ...openAiTools ? { tools: openAiTools } : {},
2363
+ include: ["code_interpreter_call.outputs", "reasoning.encrypted_content"]
2364
+ },
2365
+ { signal }
2366
+ );
2367
+ for await (const event of stream) {
2368
+ switch (event.type) {
2369
+ case "response.output_text.delta": {
2370
+ const delta = event.delta ?? "";
2371
+ pushDelta("response", typeof delta === "string" ? delta : "");
2372
+ break;
2373
+ }
2374
+ case "response.reasoning_summary_text.delta": {
2375
+ const delta = event.delta ?? "";
2376
+ pushDelta("thought", typeof delta === "string" ? delta : "");
2377
+ break;
2378
+ }
2379
+ case "response.refusal.delta": {
2380
+ blocked = true;
2381
+ queue.push({ type: "blocked" });
2382
+ break;
2383
+ }
2384
+ default:
2385
+ break;
2386
+ }
2387
+ }
2388
+ const finalResponse = await stream.finalResponse();
2389
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
2390
+ queue.push({ type: "model", modelVersion });
2391
+ if (finalResponse.error) {
2392
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
2393
+ throw new Error(message);
2394
+ }
2395
+ if (finalResponse.status && finalResponse.status !== "completed" && finalResponse.status !== "in_progress") {
2396
+ const detail = finalResponse.incomplete_details?.reason;
2397
+ throw new Error(
2398
+ `OpenAI response status ${finalResponse.status}${detail ? ` (${detail})` : ""}`
2399
+ );
2400
+ }
2401
+ latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
2402
+ if (responseParts.length === 0) {
2403
+ const fallback = extractOpenAiResponseParts(finalResponse);
2404
+ blocked = blocked || fallback.blocked;
2405
+ for (const part of fallback.parts) {
2406
+ if (part.type === "text") {
2407
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
2408
+ } else {
2409
+ pushInline(part.data, part.mimeType);
2410
+ }
2411
+ }
2412
+ }
2413
+ });
2414
+ } else if (provider === "chatgpt") {
2415
+ const chatGptInput = toChatGptInput(contents);
2416
+ const reasoningEffort = resolveOpenAiReasoningEffort(
2417
+ request.model,
2418
+ request.openAiReasoningEffort
2419
+ );
2420
+ const openAiTools = toOpenAiTools(request.tools);
2421
+ const requestPayload = {
2422
+ model: modelForProvider,
2423
+ store: false,
2424
+ stream: true,
2425
+ instructions: chatGptInput.instructions ?? "You are a helpful assistant.",
2426
+ input: chatGptInput.input,
2427
+ include: ["reasoning.encrypted_content"],
2428
+ reasoning: { effort: toOpenAiReasoningEffort(reasoningEffort), summary: "detailed" },
2429
+ text: {
2430
+ format: request.openAiTextFormat ?? { type: "text" },
2431
+ verbosity: resolveOpenAiVerbosity(request.model)
2432
+ },
2433
+ ...openAiTools ? { tools: openAiTools } : {}
2434
+ };
2435
+ let sawResponseDelta = false;
2436
+ let sawThoughtDelta = false;
2437
+ const result = await collectChatGptCodexResponse({
2438
+ request: requestPayload,
2439
+ signal,
2440
+ onDelta: (delta) => {
2441
+ if (delta.thoughtDelta) {
2442
+ sawThoughtDelta = true;
2443
+ pushDelta("thought", delta.thoughtDelta);
2444
+ }
2445
+ if (delta.textDelta) {
2446
+ sawResponseDelta = true;
2447
+ pushDelta("response", delta.textDelta);
2448
+ }
2449
+ }
2450
+ });
2451
+ blocked = blocked || result.blocked;
2452
+ if (blocked) {
2453
+ queue.push({ type: "blocked" });
2454
+ }
2455
+ if (result.model) {
2456
+ modelVersion = `chatgpt-${result.model}`;
2457
+ queue.push({ type: "model", modelVersion });
2458
+ }
2459
+ latestUsage = extractChatGptUsageTokens(result.usage);
2460
+ const fallbackText = typeof result.text === "string" ? result.text : "";
2461
+ const fallbackThoughts = typeof result.reasoningSummaryText === "string" && result.reasoningSummaryText.length > 0 ? result.reasoningSummaryText : typeof result.reasoningText === "string" ? result.reasoningText : "";
2462
+ if (!sawThoughtDelta && fallbackThoughts.length > 0) {
2463
+ pushDelta("thought", fallbackThoughts);
2464
+ }
2465
+ if (!sawResponseDelta && fallbackText.length > 0) {
2466
+ pushDelta("response", fallbackText);
2467
+ }
2468
+ } else {
2469
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
2470
+ const config = {
2471
+ maxOutputTokens: 32e3,
2472
+ thinkingConfig: resolveGeminiThinkingConfig(modelForProvider),
2473
+ ...request.responseMimeType ? { responseMimeType: request.responseMimeType } : {},
2474
+ ...request.responseJsonSchema ? { responseJsonSchema: request.responseJsonSchema } : {},
2475
+ ...request.responseModalities ? { responseModalities: Array.from(request.responseModalities) } : {},
2476
+ ...request.imageAspectRatio || request.imageSize ? {
2477
+ imageConfig: {
2478
+ ...request.imageAspectRatio ? { aspectRatio: request.imageAspectRatio } : {},
2479
+ ...request.imageSize ? { imageSize: request.imageSize } : {}
2480
+ }
2481
+ } : {}
2482
+ };
2483
+ const geminiTools = toGeminiTools(request.tools);
2484
+ if (geminiTools) {
2485
+ config.tools = geminiTools;
2486
+ }
2487
+ await runGeminiCall(async (client) => {
2488
+ const stream = await client.models.generateContentStream({
2489
+ model: modelForProvider,
2490
+ contents: geminiContents,
2491
+ config
2492
+ });
2493
+ let latestGrounding;
2494
+ for await (const chunk of stream) {
2495
+ if (chunk.modelVersion) {
2496
+ modelVersion = chunk.modelVersion;
2497
+ queue.push({ type: "model", modelVersion });
2498
+ }
2499
+ if (chunk.promptFeedback?.blockReason) {
2500
+ blocked = true;
2501
+ queue.push({ type: "blocked" });
2502
+ }
2503
+ latestUsage = mergeTokenUpdates(latestUsage, extractGeminiUsageTokens(chunk.usageMetadata));
2504
+ const candidates = chunk.candidates;
2505
+ if (!candidates || candidates.length === 0) {
2506
+ continue;
2507
+ }
2508
+ const primary = candidates[0];
2509
+ if (primary && isModerationFinish(primary.finishReason)) {
2510
+ blocked = true;
2511
+ queue.push({ type: "blocked" });
2512
+ }
2513
+ for (const candidate of candidates) {
2514
+ const candidateContent = candidate.content;
2515
+ if (!candidateContent) {
2516
+ continue;
2517
+ }
2518
+ if (candidate.groundingMetadata) {
2519
+ latestGrounding = candidate.groundingMetadata;
2520
+ }
2521
+ const content2 = convertGeminiContentToLlmContent(candidateContent);
2522
+ if (!responseRole) {
2523
+ responseRole = content2.role;
2524
+ }
2525
+ for (const part of content2.parts) {
2526
+ if (part.type === "text") {
2527
+ pushDelta(part.thought === true ? "thought" : "response", part.text);
2528
+ } else {
2529
+ pushInline(part.data, part.mimeType);
2530
+ }
2531
+ }
2532
+ }
2533
+ }
2534
+ grounding = latestGrounding;
2535
+ });
2536
+ }
2537
+ const mergedParts = mergeConsecutiveTextParts(responseParts);
2538
+ const content = mergedParts.length > 0 ? { role: responseRole ?? "model", parts: mergedParts } : void 0;
2539
+ const { text, thoughts } = extractTextByChannel(content);
2540
+ const costUsd = estimateCallCostUsd({
2541
+ modelId: modelVersion,
2542
+ tokens: latestUsage,
2543
+ responseImages,
2544
+ imageSize: request.imageSize
2545
+ });
2546
+ if (latestUsage) {
2547
+ queue.push({ type: "usage", usage: latestUsage, costUsd, modelVersion });
2548
+ }
2549
+ return {
2550
+ provider,
2551
+ model: request.model,
2552
+ modelVersion,
2553
+ content,
2554
+ text,
2555
+ thoughts,
2556
+ blocked,
2557
+ usage: latestUsage,
2558
+ costUsd,
2559
+ grounding
2560
+ };
2561
+ }
2562
+ function streamText(request) {
2563
+ const queue = createAsyncQueue();
2564
+ const abortController = new AbortController();
2565
+ const result = (async () => {
2566
+ try {
2567
+ const output = await runTextCall({ request, queue, abortController });
2568
+ queue.close();
2569
+ return output;
2570
+ } catch (error) {
2571
+ const err = error instanceof Error ? error : new Error(String(error));
2572
+ queue.fail(err);
2573
+ throw err;
2574
+ }
2575
+ })();
2576
+ return {
2577
+ events: queue.iterable,
2578
+ result,
2579
+ abort: () => abortController.abort()
2580
+ };
2581
+ }
2582
+ async function generateText(request) {
2583
+ const call = streamText(request);
2584
+ for await (const _event of call.events) {
2585
+ }
2586
+ return await call.result;
2587
+ }
2588
+ async function generateJson(request) {
2589
+ const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 2));
2590
+ const schemaName = (request.openAiSchemaName ?? "llm-response").trim() || "llm-response";
2591
+ const providerInfo = resolveProvider(request.model);
2592
+ const isOpenAiVariant = providerInfo.provider === "openai" || providerInfo.provider === "chatgpt";
2593
+ const baseJsonSchema = (0, import_zod_to_json_schema.zodToJsonSchema)(request.schema, {
2594
+ name: schemaName,
2595
+ target: isOpenAiVariant ? "openAi" : "jsonSchema7"
2596
+ });
2597
+ const responseJsonSchema = isOpenAiVariant ? resolveOpenAiSchemaRoot(baseJsonSchema) : addGeminiPropertyOrdering(baseJsonSchema);
2598
+ if (isOpenAiVariant && !isJsonSchemaObject(responseJsonSchema)) {
2599
+ throw new Error("OpenAI structured outputs require a JSON object schema at the root.");
2600
+ }
2601
+ const openAiTextFormat = providerInfo.provider === "openai" ? {
2602
+ type: "json_schema",
2603
+ name: schemaName,
2604
+ strict: true,
2605
+ schema: normalizeOpenAiSchema(responseJsonSchema)
2606
+ } : void 0;
2607
+ const failures = [];
2608
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
2609
+ let rawText = "";
2610
+ try {
2611
+ const contents = resolveTextContents(request);
2612
+ const call = streamText({
2613
+ model: request.model,
2614
+ contents,
2615
+ tools: request.tools,
2616
+ responseMimeType: request.responseMimeType ?? "application/json",
2617
+ responseJsonSchema,
2618
+ openAiReasoningEffort: request.openAiReasoningEffort,
2619
+ ...openAiTextFormat ? { openAiTextFormat } : {},
2620
+ signal: request.signal
2621
+ });
2622
+ for await (const event of call.events) {
2623
+ if (event.type === "delta" && event.channel === "response") {
2624
+ rawText += event.text;
2625
+ }
2626
+ }
2627
+ const result = await call.result;
2628
+ rawText = rawText || result.text;
2629
+ const cleanedText = normalizeJsonText(rawText);
2630
+ const repairedText = escapeNewlinesInStrings(cleanedText);
2631
+ const payload = JSON.parse(repairedText);
2632
+ const normalized = typeof request.normalizeJson === "function" ? request.normalizeJson(payload) : payload;
2633
+ const parsed = request.schema.parse(normalized);
2634
+ return { value: parsed, rawText, result };
2635
+ } catch (error) {
2636
+ const handled = error instanceof Error ? error : new Error(String(error));
2637
+ failures.push({ attempt, rawText, error: handled });
2638
+ if (attempt >= maxAttempts) {
2639
+ throw new LlmJsonCallError(`LLM JSON call failed after ${attempt} attempt(s)`, failures);
2640
+ }
2641
+ }
2642
+ }
2643
+ throw new LlmJsonCallError("LLM JSON call failed", failures);
2644
+ }
2645
+ var DEFAULT_TOOL_LOOP_MAX_STEPS = 8;
2646
+ function resolveToolLoopContents(input) {
2647
+ return resolveTextContents(input);
2648
+ }
2649
+ function buildOpenAiFunctionTools(tools) {
2650
+ const toolEntries = Object.entries(tools);
2651
+ return toolEntries.map(([name, toolDef]) => ({
2652
+ type: "function",
2653
+ name,
2654
+ description: toolDef.description ?? void 0,
2655
+ parameters: buildOpenAiToolSchema(toolDef.inputSchema, name),
2656
+ strict: true
2657
+ }));
2658
+ }
2659
+ function buildOpenAiToolSchema(schema, name) {
2660
+ const rawSchema = (0, import_zod_to_json_schema.zodToJsonSchema)(schema, { name, target: "openAi" });
2661
+ const normalized = normalizeOpenAiSchema(resolveOpenAiSchemaRoot(rawSchema));
2662
+ if (!isJsonSchemaObject(normalized)) {
2663
+ throw new Error(`OpenAI tool schema for ${name} must be a JSON object at the root.`);
2664
+ }
2665
+ return normalized;
2666
+ }
2667
+ function buildGeminiFunctionDeclarations(tools) {
2668
+ const toolEntries = Object.entries(tools);
2669
+ const functionDeclarations = toolEntries.map(([name, toolDef]) => ({
2670
+ name,
2671
+ description: toolDef.description ?? "",
2672
+ parametersJsonSchema: buildGeminiToolSchema(toolDef.inputSchema, name)
2673
+ }));
2674
+ return [{ functionDeclarations }];
2675
+ }
2676
+ function buildGeminiToolSchema(schema, name) {
2677
+ const jsonSchema = toGeminiJsonSchema(schema, { name });
2678
+ if (!isJsonSchemaObject(jsonSchema)) {
2679
+ throw new Error(`Gemini tool schema for ${name} must be a JSON object at the root.`);
2680
+ }
2681
+ return jsonSchema;
2682
+ }
2683
+ function extractOpenAiReasoningSummary(response) {
2684
+ if (!response || typeof response !== "object") {
2685
+ return "";
2686
+ }
2687
+ const output = response.output;
2688
+ if (!Array.isArray(output)) {
2689
+ return "";
2690
+ }
2691
+ let summary = "";
2692
+ for (const item of output) {
2693
+ if (!item || typeof item !== "object") {
2694
+ continue;
2695
+ }
2696
+ if (item.type !== "reasoning") {
2697
+ continue;
2698
+ }
2699
+ const content = item.content;
2700
+ if (!Array.isArray(content)) {
2701
+ continue;
2702
+ }
2703
+ for (const entry of content) {
2704
+ if (!entry || typeof entry !== "object") {
2705
+ continue;
2706
+ }
2707
+ const entryType = entry.type;
2708
+ if (entryType === "reasoning_summary_text") {
2709
+ const text = entry.text;
2710
+ if (typeof text === "string") {
2711
+ summary += text;
2712
+ }
2713
+ }
2714
+ }
2715
+ }
2716
+ return summary;
2717
+ }
2718
+ async function runToolLoop(request) {
2719
+ const toolEntries = Object.entries(request.tools);
2720
+ if (toolEntries.length === 0) {
2721
+ throw new Error("Tool loop requires at least one tool definition.");
2722
+ }
2723
+ const contents = resolveToolLoopContents(request);
2724
+ if (contents.length === 0) {
2725
+ throw new Error("Tool loop prompt must not be empty.");
2726
+ }
2727
+ const maxSteps = Math.max(1, Math.floor(request.maxSteps ?? DEFAULT_TOOL_LOOP_MAX_STEPS));
2728
+ const providerInfo = resolveProvider(request.model);
2729
+ const steps = [];
2730
+ let totalCostUsd = 0;
2731
+ let finalText = "";
2732
+ let finalThoughts = "";
2733
+ if (providerInfo.provider === "openai") {
2734
+ const openAiFunctionTools = buildOpenAiFunctionTools(request.tools);
2735
+ const openAiNativeTools = toOpenAiTools(request.modelTools);
2736
+ const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiFunctionTools] : [...openAiFunctionTools];
2737
+ const reasoningEffort = resolveOpenAiReasoningEffort(
2738
+ providerInfo.model,
2739
+ request.openAiReasoningEffort
2740
+ );
2741
+ const textConfig = {
2742
+ format: { type: "text" },
2743
+ verbosity: resolveOpenAiVerbosity(providerInfo.model)
2744
+ };
2745
+ const reasoning = {
2746
+ effort: toOpenAiReasoningEffort(reasoningEffort),
2747
+ summary: "detailed"
2748
+ };
2749
+ let previousResponseId;
2750
+ let input = toOpenAiInput(contents);
2751
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
2752
+ const turn = stepIndex + 1;
2753
+ const abortController = new AbortController();
2754
+ if (request.signal) {
2755
+ if (request.signal.aborted) {
2756
+ abortController.abort(request.signal.reason);
2757
+ } else {
2758
+ request.signal.addEventListener(
2759
+ "abort",
2760
+ () => abortController.abort(request.signal?.reason),
2761
+ { once: true }
2762
+ );
2763
+ }
2764
+ }
2765
+ const onEvent = request.onEvent;
2766
+ let modelVersion = request.model;
2767
+ let usageTokens;
2768
+ const emitEvent = (ev) => {
2769
+ onEvent?.(ev);
2770
+ };
2771
+ const finalResponse = await runOpenAiCall(async (client) => {
2772
+ const stream = client.responses.stream(
2773
+ {
2774
+ model: providerInfo.model,
2775
+ input,
2776
+ ...previousResponseId ? { previous_response_id: previousResponseId } : {},
2777
+ ...openAiTools.length > 0 ? { tools: openAiTools } : {},
2778
+ ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
2779
+ reasoning,
2780
+ text: textConfig,
2781
+ include: ["reasoning.encrypted_content"]
2782
+ },
2783
+ { signal: abortController.signal }
2784
+ );
2785
+ for await (const event of stream) {
2786
+ switch (event.type) {
2787
+ case "response.output_text.delta":
2788
+ emitEvent({
2789
+ type: "delta",
2790
+ channel: "response",
2791
+ text: typeof event.delta === "string" ? event.delta : ""
2792
+ });
2793
+ break;
2794
+ case "response.reasoning_summary_text.delta":
2795
+ emitEvent({
2796
+ type: "delta",
2797
+ channel: "thought",
2798
+ text: typeof event.delta === "string" ? event.delta : ""
2799
+ });
2800
+ break;
2801
+ case "response.refusal.delta":
2802
+ emitEvent({ type: "blocked" });
2803
+ break;
2804
+ default:
2805
+ break;
2806
+ }
2807
+ }
2808
+ return await stream.finalResponse();
2809
+ });
2810
+ modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
2811
+ emitEvent({ type: "model", modelVersion });
2812
+ if (finalResponse.error) {
2813
+ const message = typeof finalResponse.error.message === "string" ? finalResponse.error.message : "OpenAI response failed";
2814
+ throw new Error(message);
2815
+ }
2816
+ usageTokens = extractOpenAiUsageTokens(finalResponse.usage);
2817
+ const responseText = extractOpenAiResponseParts(finalResponse).parts.filter((p) => p.type === "text" && p.thought !== true).map((p) => p.text).join("").trim();
2818
+ const reasoningSummary = extractOpenAiReasoningSummary(finalResponse).trim();
2819
+ const stepCostUsd = estimateCallCostUsd({
2820
+ modelId: modelVersion,
2821
+ tokens: usageTokens,
2822
+ responseImages: 0
2823
+ });
2824
+ totalCostUsd += stepCostUsd;
2825
+ if (usageTokens) {
2826
+ emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
2827
+ }
2828
+ const functionCalls = extractOpenAiFunctionCalls(finalResponse.output);
2829
+ const stepToolCalls = [];
2830
+ if (functionCalls.length === 0) {
2831
+ finalText = responseText;
2832
+ finalThoughts = reasoningSummary;
2833
+ steps.push({
2834
+ step: steps.length + 1,
2835
+ modelVersion,
2836
+ text: responseText || void 0,
2837
+ thoughts: reasoningSummary || void 0,
2838
+ toolCalls: [],
2839
+ usage: usageTokens,
2840
+ costUsd: stepCostUsd
2841
+ });
2842
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
2843
+ }
2844
+ const callInputs = functionCalls.map((call, index) => {
2845
+ const toolIndex = index + 1;
2846
+ const toolId = buildToolLogId(turn, toolIndex);
2847
+ const toolName = call.name;
2848
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
2849
+ return { call, toolName, value, parseError, toolId, turn, toolIndex };
2850
+ });
2851
+ const callResults = await Promise.all(
2852
+ callInputs.map(async (entry) => {
2853
+ return await toolCallContextStorage.run(
2854
+ {
2855
+ toolName: entry.toolName,
2856
+ toolId: entry.toolId,
2857
+ turn: entry.turn,
2858
+ toolIndex: entry.toolIndex
2859
+ },
2860
+ async () => {
2861
+ const { result, outputPayload } = await executeToolCall({
2862
+ toolName: entry.toolName,
2863
+ tool: request.tools[entry.toolName],
2864
+ rawInput: entry.value,
2865
+ parseError: entry.parseError
2866
+ });
2867
+ return { entry, result, outputPayload };
2868
+ }
2869
+ );
2870
+ })
2871
+ );
2872
+ const toolOutputs = [];
2873
+ for (const { entry, result, outputPayload } of callResults) {
2874
+ stepToolCalls.push({ ...result, callId: entry.call.call_id });
2875
+ toolOutputs.push({
2876
+ type: "function_call_output",
2877
+ call_id: entry.call.call_id,
2878
+ output: mergeToolOutput(outputPayload)
2879
+ });
2880
+ }
2881
+ steps.push({
2882
+ step: steps.length + 1,
2883
+ modelVersion,
2884
+ text: responseText || void 0,
2885
+ thoughts: reasoningSummary || void 0,
2886
+ toolCalls: stepToolCalls,
2887
+ usage: usageTokens,
2888
+ costUsd: stepCostUsd
2889
+ });
2890
+ previousResponseId = finalResponse.id;
2891
+ input = toolOutputs;
2892
+ }
2893
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
2894
+ }
2895
+ if (providerInfo.provider === "chatgpt") {
2896
+ const openAiFunctionTools = buildOpenAiFunctionTools(request.tools);
2897
+ const openAiNativeTools = toOpenAiTools(request.modelTools);
2898
+ const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiFunctionTools] : [...openAiFunctionTools];
2899
+ const reasoningEffort = resolveOpenAiReasoningEffort(
2900
+ request.model,
2901
+ request.openAiReasoningEffort
2902
+ );
2903
+ const toolLoopInput = toChatGptInput(contents);
2904
+ let input = [...toolLoopInput.input];
2905
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
2906
+ const turn = stepIndex + 1;
2907
+ const response = await collectChatGptCodexResponse({
2908
+ request: {
2909
+ model: providerInfo.model,
2910
+ store: false,
2911
+ stream: true,
2912
+ instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
2913
+ input,
2914
+ include: ["reasoning.encrypted_content"],
2915
+ tools: openAiTools,
2916
+ tool_choice: "auto",
2917
+ parallel_tool_calls: true,
2918
+ reasoning: {
2919
+ effort: toOpenAiReasoningEffort(reasoningEffort),
2920
+ summary: "detailed"
2921
+ },
2922
+ text: { verbosity: resolveOpenAiVerbosity(request.model) }
2923
+ },
2924
+ signal: request.signal,
2925
+ onDelta: (delta) => {
2926
+ if (delta.thoughtDelta) {
2927
+ request.onEvent?.({ type: "delta", channel: "thought", text: delta.thoughtDelta });
2928
+ }
2929
+ if (delta.textDelta) {
2930
+ request.onEvent?.({ type: "delta", channel: "response", text: delta.textDelta });
2931
+ }
2932
+ }
2933
+ });
2934
+ const modelVersion = response.model ? `chatgpt-${response.model}` : request.model;
2935
+ const usageTokens = extractChatGptUsageTokens(response.usage);
2936
+ const stepCostUsd = estimateCallCostUsd({
2937
+ modelId: modelVersion,
2938
+ tokens: usageTokens,
2939
+ responseImages: 0
2940
+ });
2941
+ totalCostUsd += stepCostUsd;
2942
+ const responseText = (response.text ?? "").trim();
2943
+ const reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
2944
+ const functionCalls = response.toolCalls ?? [];
2945
+ if (functionCalls.length === 0) {
2946
+ finalText = responseText;
2947
+ finalThoughts = reasoningSummaryText;
2948
+ steps.push({
2949
+ step: steps.length + 1,
2950
+ modelVersion,
2951
+ text: responseText || void 0,
2952
+ thoughts: reasoningSummaryText || void 0,
2953
+ toolCalls: [],
2954
+ usage: usageTokens,
2955
+ costUsd: stepCostUsd
2956
+ });
2957
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
2958
+ }
2959
+ const toolCalls = [];
2960
+ const toolOutputs = [];
2961
+ const callInputs = functionCalls.map((call, index) => {
2962
+ const toolIndex = index + 1;
2963
+ const toolId = buildToolLogId(turn, toolIndex);
2964
+ const toolName = call.name;
2965
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
2966
+ const ids = normalizeChatGptToolIds({ callId: call.callId, itemId: call.id });
2967
+ return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
2968
+ });
2969
+ const callResults = await Promise.all(
2970
+ callInputs.map(async (entry) => {
2971
+ return await toolCallContextStorage.run(
2972
+ {
2973
+ toolName: entry.toolName,
2974
+ toolId: entry.toolId,
2975
+ turn: entry.turn,
2976
+ toolIndex: entry.toolIndex
2977
+ },
2978
+ async () => {
2979
+ const { result, outputPayload } = await executeToolCall({
2980
+ toolName: entry.toolName,
2981
+ tool: request.tools[entry.toolName],
2982
+ rawInput: entry.value,
2983
+ parseError: entry.parseError
2984
+ });
2985
+ return { entry, result, outputPayload };
2986
+ }
2987
+ );
2988
+ })
2989
+ );
2990
+ for (const { entry, result, outputPayload } of callResults) {
2991
+ toolCalls.push({ ...result, callId: entry.ids.callId });
2992
+ toolOutputs.push({
2993
+ type: "function_call",
2994
+ id: entry.ids.itemId,
2995
+ call_id: entry.ids.callId,
2996
+ name: entry.toolName,
2997
+ arguments: entry.call.arguments,
2998
+ status: "completed"
2999
+ });
3000
+ toolOutputs.push({
3001
+ type: "function_call_output",
3002
+ call_id: entry.ids.callId,
3003
+ output: mergeToolOutput(outputPayload)
3004
+ });
3005
+ }
3006
+ steps.push({
3007
+ step: steps.length + 1,
3008
+ modelVersion,
3009
+ text: responseText || void 0,
3010
+ thoughts: reasoningSummaryText || void 0,
3011
+ toolCalls,
3012
+ usage: usageTokens,
3013
+ costUsd: stepCostUsd
3014
+ });
3015
+ input = input.concat(toolOutputs);
3016
+ }
3017
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
3018
+ }
3019
+ const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
3020
+ const geminiNativeTools = toGeminiTools(request.modelTools);
3021
+ const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
3022
+ const geminiContents = contents.map(convertLlmContentToGeminiContent);
3023
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
3024
+ const config = {
3025
+ maxOutputTokens: 32e3,
3026
+ tools: geminiTools,
3027
+ toolConfig: {
3028
+ functionCallingConfig: {
3029
+ mode: import_genai2.FunctionCallingConfigMode.VALIDATED
3030
+ }
3031
+ },
3032
+ thinkingConfig: resolveGeminiThinkingConfig(request.model)
3033
+ };
3034
+ const onEvent = request.onEvent;
3035
+ const response = await runGeminiCall(async (client) => {
3036
+ const stream = await client.models.generateContentStream({
3037
+ model: request.model,
3038
+ contents: geminiContents,
3039
+ config
3040
+ });
3041
+ let responseText = "";
3042
+ let thoughtsText = "";
3043
+ const modelParts = [];
3044
+ const functionCalls = [];
3045
+ const seenFunctionCallIds = /* @__PURE__ */ new Set();
3046
+ const seenFunctionCallKeys = /* @__PURE__ */ new Set();
3047
+ let latestUsageMetadata;
3048
+ let resolvedModelVersion;
3049
+ for await (const chunk of stream) {
3050
+ if (chunk.modelVersion) {
3051
+ resolvedModelVersion = chunk.modelVersion;
3052
+ onEvent?.({ type: "model", modelVersion: chunk.modelVersion });
3053
+ }
3054
+ if (chunk.usageMetadata) {
3055
+ latestUsageMetadata = chunk.usageMetadata;
3056
+ }
3057
+ const candidates = chunk.candidates;
3058
+ if (!candidates || candidates.length === 0) {
3059
+ continue;
3060
+ }
3061
+ const primary = candidates[0];
3062
+ const parts = primary?.content?.parts;
3063
+ if (!parts || parts.length === 0) {
3064
+ continue;
3065
+ }
3066
+ for (const part of parts) {
3067
+ modelParts.push(part);
3068
+ const call = part.functionCall;
3069
+ if (call) {
3070
+ const id = typeof call.id === "string" ? call.id : "";
3071
+ const shouldAdd = (() => {
3072
+ if (id.length > 0) {
3073
+ if (seenFunctionCallIds.has(id)) {
3074
+ return false;
3075
+ }
3076
+ seenFunctionCallIds.add(id);
3077
+ return true;
3078
+ }
3079
+ const key = JSON.stringify({ name: call.name ?? "", args: call.args ?? null });
3080
+ if (seenFunctionCallKeys.has(key)) {
3081
+ return false;
3082
+ }
3083
+ seenFunctionCallKeys.add(key);
3084
+ return true;
3085
+ })();
3086
+ if (shouldAdd) {
3087
+ functionCalls.push(call);
3088
+ }
3089
+ }
3090
+ if (typeof part.text === "string" && part.text.length > 0) {
3091
+ if (part.thought) {
3092
+ thoughtsText += part.text;
3093
+ onEvent?.({ type: "delta", channel: "thought", text: part.text });
3094
+ } else {
3095
+ responseText += part.text;
3096
+ onEvent?.({ type: "delta", channel: "response", text: part.text });
3097
+ }
3098
+ }
3099
+ }
3100
+ }
3101
+ return {
3102
+ responseText,
3103
+ thoughtsText,
3104
+ functionCalls,
3105
+ modelParts,
3106
+ usageMetadata: latestUsageMetadata,
3107
+ modelVersion: resolvedModelVersion ?? request.model
3108
+ };
3109
+ });
3110
+ const usageTokens = extractGeminiUsageTokens(response.usageMetadata);
3111
+ const modelVersion = response.modelVersion ?? request.model;
3112
+ const stepCostUsd = estimateCallCostUsd({
3113
+ modelId: modelVersion,
3114
+ tokens: usageTokens,
3115
+ responseImages: 0
3116
+ });
3117
+ totalCostUsd += stepCostUsd;
3118
+ if (response.functionCalls.length === 0) {
3119
+ finalText = response.responseText.trim();
3120
+ finalThoughts = response.thoughtsText.trim();
3121
+ steps.push({
3122
+ step: steps.length + 1,
3123
+ modelVersion,
3124
+ text: finalText || void 0,
3125
+ thoughts: finalThoughts || void 0,
3126
+ toolCalls: [],
3127
+ usage: usageTokens,
3128
+ costUsd: stepCostUsd
3129
+ });
3130
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
3131
+ }
3132
+ const toolCalls = [];
3133
+ const modelPartsForHistory = response.modelParts.filter(
3134
+ (part) => !(typeof part.text === "string" && part.thought === true)
3135
+ );
3136
+ if (modelPartsForHistory.length > 0) {
3137
+ geminiContents.push({ role: "model", parts: modelPartsForHistory });
3138
+ } else {
3139
+ const parts = [];
3140
+ if (response.responseText) {
3141
+ parts.push({ text: response.responseText });
3142
+ }
3143
+ for (const call of response.functionCalls) {
3144
+ parts.push({ functionCall: call });
3145
+ }
3146
+ geminiContents.push({ role: "model", parts });
3147
+ }
3148
+ const responseParts = [];
3149
+ const callInputs = response.functionCalls.map((call, index) => {
3150
+ const turn = stepIndex + 1;
3151
+ const toolIndex = index + 1;
3152
+ const toolId = buildToolLogId(turn, toolIndex);
3153
+ const toolName = call.name ?? "unknown";
3154
+ const rawInput = call.args ?? {};
3155
+ return { call, toolName, rawInput, toolId, turn, toolIndex };
3156
+ });
3157
+ const callResults = await Promise.all(
3158
+ callInputs.map(async (entry) => {
3159
+ return await toolCallContextStorage.run(
3160
+ {
3161
+ toolName: entry.toolName,
3162
+ toolId: entry.toolId,
3163
+ turn: entry.turn,
3164
+ toolIndex: entry.toolIndex
3165
+ },
3166
+ async () => {
3167
+ const { result, outputPayload } = await executeToolCall({
3168
+ toolName: entry.toolName,
3169
+ tool: request.tools[entry.toolName],
3170
+ rawInput: entry.rawInput
3171
+ });
3172
+ return { entry, result, outputPayload };
3173
+ }
3174
+ );
3175
+ })
3176
+ );
3177
+ for (const { entry, result, outputPayload } of callResults) {
3178
+ toolCalls.push({ ...result, callId: entry.call.id });
3179
+ const responsePayload = isPlainRecord(outputPayload) ? outputPayload : { output: outputPayload };
3180
+ responseParts.push({
3181
+ functionResponse: {
3182
+ name: entry.toolName,
3183
+ response: responsePayload,
3184
+ ...entry.call.id ? { id: entry.call.id } : {}
3185
+ }
3186
+ });
3187
+ }
3188
+ steps.push({
3189
+ step: steps.length + 1,
3190
+ modelVersion,
3191
+ text: response.responseText.trim() || void 0,
3192
+ thoughts: response.thoughtsText.trim() || void 0,
3193
+ toolCalls,
3194
+ usage: usageTokens,
3195
+ costUsd: stepCostUsd
3196
+ });
3197
+ geminiContents.push({ role: "user", parts: responseParts });
3198
+ }
3199
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
3200
+ }
3201
+ var IMAGE_GRADE_SCHEMA = import_zod3.z.enum(["pass", "fail"]);
3202
+ async function gradeGeneratedImage(params) {
3203
+ const parts = [
3204
+ {
3205
+ type: "text",
3206
+ text: [
3207
+ params.gradingPrompt,
3208
+ "",
3209
+ "Image prompt to grade:",
3210
+ params.imagePrompt,
3211
+ "",
3212
+ 'Respond with the JSON string "pass" or "fail".'
3213
+ ].join("\\n")
3214
+ },
3215
+ {
3216
+ type: "inlineData",
3217
+ data: params.image.data.toString("base64"),
3218
+ mimeType: params.image.mimeType ?? "image/png"
3219
+ }
3220
+ ];
3221
+ const { value } = await generateJson({
3222
+ model: params.model,
3223
+ contents: [{ role: "user", parts }],
3224
+ schema: IMAGE_GRADE_SCHEMA
3225
+ });
3226
+ return value;
3227
+ }
3228
+ async function generateImages(request) {
3229
+ const maxAttempts = Math.max(1, Math.floor(request.maxAttempts ?? 4));
3230
+ const promptList = Array.from(request.imagePrompts);
3231
+ if (promptList.length === 0) {
3232
+ return [];
3233
+ }
3234
+ const numImages = promptList.length;
3235
+ const promptEntries = promptList.map((rawPrompt, arrayIndex) => {
3236
+ const trimmedPrompt = rawPrompt.trim();
3237
+ if (!trimmedPrompt) {
3238
+ throw new Error(`imagePrompts[${arrayIndex}] must be a non-empty string`);
3239
+ }
3240
+ return { index: arrayIndex + 1, prompt: trimmedPrompt };
3241
+ });
3242
+ const gradingPrompt = request.imageGradingPrompt.trim();
3243
+ if (!gradingPrompt) {
3244
+ throw new Error("imageGradingPrompt must be a non-empty string");
3245
+ }
3246
+ const addText = (parts, text) => {
3247
+ const lastPart = parts[parts.length - 1];
3248
+ if (lastPart !== void 0 && lastPart.type === "text") {
3249
+ lastPart.text = `${lastPart.text}\\n${text}`;
3250
+ } else {
3251
+ parts.push({ type: "text", text });
3252
+ }
3253
+ };
3254
+ const buildInitialPromptParts = () => {
3255
+ const parts = [];
3256
+ addText(
3257
+ parts,
3258
+ [
3259
+ `Please make all ${numImages} requested images:`,
3260
+ "",
3261
+ "Follow the style:",
3262
+ request.stylePrompt
3263
+ ].join("\\n")
3264
+ );
3265
+ if (request.styleImages && request.styleImages.length > 0) {
3266
+ addText(
3267
+ parts,
3268
+ "\\nFollow the visual style, composition and the characters from these images:"
3269
+ );
3270
+ for (const styleImage of request.styleImages) {
3271
+ parts.push({
3272
+ type: "inlineData",
3273
+ data: styleImage.data.toString("base64"),
3274
+ mimeType: styleImage.mimeType
3275
+ });
3276
+ }
3277
+ }
3278
+ const lines = ["", "Image descriptions:"];
3279
+ for (const entry of promptEntries) {
3280
+ lines.push(`\\nImage ${entry.index}: ${entry.prompt}`);
3281
+ }
3282
+ lines.push("");
3283
+ lines.push(`Please make all ${numImages} images.`);
3284
+ addText(parts, lines.join("\\n"));
3285
+ return parts;
3286
+ };
3287
+ const buildContinuationPromptParts = (pending) => {
3288
+ const pendingIds = pending.map((entry) => entry.index).join(", ");
3289
+ const lines = [
3290
+ `Please continue generating the remaining images: ${pendingIds}.`,
3291
+ "",
3292
+ "Image descriptions:"
3293
+ ];
3294
+ for (const entry of pending) {
3295
+ lines.push(`\\nImage ${entry.index}: ${entry.prompt}`);
3296
+ }
3297
+ lines.push(`\\nPlease make all ${pending.length} remaining images.`);
3298
+ return [{ type: "text", text: lines.join("\\n") }];
3299
+ };
3300
+ const contents = [{ role: "user", parts: buildInitialPromptParts() }];
3301
+ const orderedEntries = [...promptEntries];
3302
+ const resolvedImages = /* @__PURE__ */ new Map();
3303
+ const removeResolvedEntries = (resolved) => {
3304
+ if (resolved.size === 0) {
3305
+ return;
3306
+ }
3307
+ for (let i = promptEntries.length - 1; i >= 0; i -= 1) {
3308
+ const entry = promptEntries[i];
3309
+ if (!entry) {
3310
+ continue;
3311
+ }
3312
+ if (resolved.has(entry.index)) {
3313
+ promptEntries.splice(i, 1);
3314
+ }
3315
+ }
3316
+ };
3317
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
3318
+ const result = await generateText({
3319
+ model: request.model,
3320
+ contents,
3321
+ responseModalities: ["IMAGE", "TEXT"],
3322
+ imageAspectRatio: request.imageAspectRatio,
3323
+ imageSize: request.imageSize ?? "2K"
3324
+ });
3325
+ if (result.blocked || !result.content) {
3326
+ continue;
3327
+ }
3328
+ const images = extractImages(result.content);
3329
+ if (images.length > 0 && promptEntries.length > 0) {
3330
+ const assignedCount = Math.min(images.length, promptEntries.length);
3331
+ const pendingAssignments = promptEntries.slice(0, assignedCount);
3332
+ const assignedImages = images.slice(0, assignedCount);
3333
+ const gradeResults = await Promise.all(
3334
+ pendingAssignments.map(
3335
+ (entry, index) => gradeGeneratedImage({
3336
+ gradingPrompt,
3337
+ imagePrompt: entry.prompt,
3338
+ image: (() => {
3339
+ const image = assignedImages[index];
3340
+ if (!image) {
3341
+ throw new Error("Image generation returned fewer images than expected.");
3342
+ }
3343
+ return image;
3344
+ })(),
3345
+ model: "gpt-5.2"
3346
+ })
3347
+ )
3348
+ );
3349
+ const passedEntries = /* @__PURE__ */ new Set();
3350
+ for (let i = 0; i < gradeResults.length; i += 1) {
3351
+ const grade = gradeResults[i];
3352
+ const entry = pendingAssignments[i];
3353
+ const image = assignedImages[i];
3354
+ if (!grade || !entry || !image) {
3355
+ continue;
3356
+ }
3357
+ if (grade === "pass") {
3358
+ resolvedImages.set(entry.index, image);
3359
+ passedEntries.add(entry.index);
3360
+ }
3361
+ }
3362
+ removeResolvedEntries(passedEntries);
3363
+ }
3364
+ if (promptEntries.length === 0) {
3365
+ break;
3366
+ }
3367
+ contents.push(result.content);
3368
+ contents.push({ role: "user", parts: buildContinuationPromptParts(promptEntries) });
3369
+ }
3370
+ const orderedImages = [];
3371
+ for (const entry of orderedEntries) {
3372
+ const image = resolvedImages.get(entry.index);
3373
+ if (image) {
3374
+ orderedImages.push(image);
3375
+ }
3376
+ }
3377
+ return orderedImages.slice(0, numImages);
3378
+ }
3379
+ async function generateImageInBatches(request) {
3380
+ const {
3381
+ batchSize,
3382
+ overlapSize,
3383
+ imagePrompts,
3384
+ styleImages: baseStyleImagesInput,
3385
+ ...rest
3386
+ } = request;
3387
+ if (batchSize <= 0) {
3388
+ throw new Error("batchSize must be greater than 0");
3389
+ }
3390
+ if (imagePrompts.length === 0) {
3391
+ return [];
3392
+ }
3393
+ const baseStyleImages = baseStyleImagesInput ? [...baseStyleImagesInput] : [];
3394
+ const generatedImages = [];
3395
+ const totalPrompts = imagePrompts.length;
3396
+ for (let startIndex = 0; startIndex < totalPrompts; startIndex += batchSize) {
3397
+ const endIndex = Math.min(startIndex + batchSize, totalPrompts);
3398
+ const batchPrompts = imagePrompts.slice(startIndex, endIndex);
3399
+ let styleImagesForBatch = baseStyleImages;
3400
+ if (overlapSize > 0 && generatedImages.length > 0) {
3401
+ const overlapImages = generatedImages.slice(
3402
+ Math.max(0, generatedImages.length - overlapSize)
3403
+ );
3404
+ if (overlapImages.length > 0) {
3405
+ styleImagesForBatch = [...baseStyleImages, ...overlapImages];
3406
+ }
3407
+ }
3408
+ const batchImages = await generateImages({
3409
+ ...rest,
3410
+ imagePrompts: batchPrompts,
3411
+ styleImages: styleImagesForBatch
3412
+ });
3413
+ generatedImages.push(...batchImages);
3414
+ }
3415
+ return generatedImages;
3416
+ }
3417
+ function stripCodexCitationMarkers(value) {
3418
+ const citationBlockPattern = /\uE200cite\uE202[^\uE201]*\uE201/gu;
3419
+ const leftoverMarkersPattern = /[\uE200\uE201\uE202]/gu;
3420
+ const withoutBlocks = value.replace(citationBlockPattern, "");
3421
+ const withoutMarkers = withoutBlocks.replace(leftoverMarkersPattern, "");
3422
+ const stripped = withoutMarkers !== value;
3423
+ return { text: withoutMarkers, stripped };
3424
+ }
3425
+ function hasMarkdownSourcesSection(value) {
3426
+ return /^##\s+Sources\s*$/gmu.test(value);
3427
+ }
3428
+ function appendMarkdownSourcesSection(value, sources) {
3429
+ const trimmed = value.trimEnd();
3430
+ if (sources.length === 0) {
3431
+ return trimmed;
3432
+ }
3433
+ if (hasMarkdownSourcesSection(trimmed)) {
3434
+ return trimmed;
3435
+ }
3436
+ const lines = sources.map((url) => `- <${url}>`).join("\n");
3437
+ return `${trimmed}
3438
+
3439
+ ## Sources
3440
+ ${lines}`;
3441
+ }
3442
+ // Annotate the CommonJS export names for ESM import in node:
3443
+ 0 && (module.exports = {
3444
+ LlmJsonCallError,
3445
+ appendMarkdownSourcesSection,
3446
+ configureGemini,
3447
+ convertGooglePartsToLlmParts,
3448
+ encodeChatGptAuthJson,
3449
+ encodeChatGptAuthJsonB64,
3450
+ estimateCallCostUsd,
3451
+ exchangeChatGptOauthCode,
3452
+ generateImageInBatches,
3453
+ generateImages,
3454
+ generateJson,
3455
+ generateText,
3456
+ getChatGptAuthProfile,
3457
+ getCurrentToolCallContext,
3458
+ isGeminiModelId,
3459
+ loadEnvFromFile,
3460
+ loadLocalEnv,
3461
+ parseJsonFromLlmText,
3462
+ refreshChatGptOauthToken,
3463
+ runToolLoop,
3464
+ sanitisePartForLogging,
3465
+ streamText,
3466
+ stripCodexCitationMarkers,
3467
+ toGeminiJsonSchema,
3468
+ tool
3469
+ });
3470
+ //# sourceMappingURL=index.cjs.map