@ljoukov/llm 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -73,6 +73,35 @@ function createAsyncQueue() {
73
73
  return { push, close, fail, iterable: iterator() };
74
74
  }
75
75
 
76
+ // src/fireworks/pricing.ts
77
+ var FIREWORKS_KIMI_K25_PRICING = {
78
+ inputRate: 0.6 / 1e6,
79
+ cachedRate: 0.1 / 1e6,
80
+ outputRate: 3 / 1e6
81
+ };
82
+ var FIREWORKS_GLM_5_PRICING = {
83
+ inputRate: 1 / 1e6,
84
+ cachedRate: 0.2 / 1e6,
85
+ outputRate: 3.2 / 1e6
86
+ };
87
+ var FIREWORKS_MINIMAX_M21_PRICING = {
88
+ inputRate: 0.3 / 1e6,
89
+ cachedRate: 0.15 / 1e6,
90
+ outputRate: 1.2 / 1e6
91
+ };
92
+ function getFireworksPricing(modelId) {
93
+ if (modelId.includes("kimi-k2.5") || modelId.includes("kimi-k2p5")) {
94
+ return FIREWORKS_KIMI_K25_PRICING;
95
+ }
96
+ if (modelId.includes("glm-5")) {
97
+ return FIREWORKS_GLM_5_PRICING;
98
+ }
99
+ if (modelId.includes("minimax-m2.1") || modelId.includes("minimax-m2p1")) {
100
+ return FIREWORKS_MINIMAX_M21_PRICING;
101
+ }
102
+ return void 0;
103
+ }
104
+
76
105
  // src/google/pricing.ts
77
106
  var GEMINI_3_PRO_PREVIEW_PRICING = {
78
107
  threshold: 2e5,
@@ -130,12 +159,15 @@ var OPENAI_GPT_53_CODEX_PRICING = {
130
159
  cachedRate: 0.125 / 1e6,
131
160
  outputRate: 10 / 1e6
132
161
  };
133
- var OPENAI_GPT_51_CODEX_MINI_PRICING = {
162
+ var OPENAI_GPT_5_MINI_PRICING = {
134
163
  inputRate: 0.25 / 1e6,
135
164
  cachedRate: 0.025 / 1e6,
136
165
  outputRate: 2 / 1e6
137
166
  };
138
167
  function getOpenAiPricing(modelId) {
168
+ if (modelId.includes("gpt-5.3-codex-spark")) {
169
+ return OPENAI_GPT_5_MINI_PRICING;
170
+ }
139
171
  if (modelId.includes("gpt-5.3-codex")) {
140
172
  return OPENAI_GPT_53_CODEX_PRICING;
141
173
  }
@@ -145,8 +177,11 @@ function getOpenAiPricing(modelId) {
145
177
  if (modelId.includes("gpt-5.2")) {
146
178
  return OPENAI_GPT_52_PRICING;
147
179
  }
180
+ if (modelId.includes("gpt-5-mini")) {
181
+ return OPENAI_GPT_5_MINI_PRICING;
182
+ }
148
183
  if (modelId.includes("gpt-5.1-codex-mini")) {
149
- return OPENAI_GPT_51_CODEX_MINI_PRICING;
184
+ return OPENAI_GPT_5_MINI_PRICING;
150
185
  }
151
186
  return void 0;
152
187
  }
@@ -207,6 +242,14 @@ function estimateCallCostUsd({
207
242
  const outputCost = outputTokens * outputRate;
208
243
  return inputCost + cachedCost + outputCost;
209
244
  }
245
+ const fireworksPricing = getFireworksPricing(modelId);
246
+ if (fireworksPricing) {
247
+ const inputCost = nonCachedPrompt * fireworksPricing.inputRate;
248
+ const cachedCost = cachedTokens * fireworksPricing.cachedRate;
249
+ const outputTokens = responseTokens + thinkingTokens;
250
+ const outputCost = outputTokens * fireworksPricing.outputRate;
251
+ return inputCost + cachedCost + outputCost;
252
+ }
210
253
  const openAiPricing = getOpenAiPricing(modelId);
211
254
  if (openAiPricing) {
212
255
  const inputCost = nonCachedPrompt * openAiPricing.inputRate;
@@ -219,11 +262,14 @@ function estimateCallCostUsd({
219
262
  }
220
263
 
221
264
  // src/openai/chatgpt-codex.ts
222
- import os from "os";
265
+ import os2 from "os";
223
266
  import { TextDecoder } from "util";
224
267
 
225
268
  // src/openai/chatgpt-auth.ts
226
269
  import { Buffer as Buffer2 } from "buffer";
270
+ import fs2 from "fs";
271
+ import os from "os";
272
+ import path2 from "path";
227
273
  import { z } from "zod";
228
274
 
229
275
  // src/utils/env.ts
@@ -288,34 +334,30 @@ function parseEnvLine(line) {
288
334
  }
289
335
 
290
336
  // src/openai/chatgpt-auth.ts
291
- var CHATGPT_AUTH_JSON_ENV = "CHATGPT_AUTH_JSON";
292
- var CHATGPT_AUTH_JSON_B64_ENV = "CHATGPT_AUTH_JSON_B64";
293
- var CHATGPT_ACCESS_ENV = "CHATGPT_ACCESS";
294
- var CHATGPT_REFRESH_ENV = "CHATGPT_REFRESH";
295
- var CHATGPT_EXPIRES_ENV = "CHATGPT_EXPIRES";
296
- var CHATGPT_ACCOUNT_ID_ENV = "CHATGPT_ACCOUNT_ID";
297
- var CHATGPT_ID_TOKEN_ENV = "CHATGPT_ID_TOKEN";
298
- var CHATGPT_ACCESS_TOKEN_ENV = "CHATGPT_ACCESS_TOKEN";
299
- var CHATGPT_REFRESH_TOKEN_ENV = "CHATGPT_REFRESH_TOKEN";
300
- var CHATGPT_EXPIRES_AT_ENV = "CHATGPT_EXPIRES_AT";
337
+ var CHATGPT_AUTH_TOKEN_PROVIDER_URL_ENV = "CHATGPT_AUTH_TOKEN_PROVIDER_URL";
338
+ var CHATGPT_AUTH_TOKEN_PROVIDER_STORE_ENV = "CHATGPT_AUTH_TOKEN_PROVIDER_STORE";
339
+ var CHATGPT_AUTH_SERVER_URL_ENV = "CHATGPT_AUTH_SERVER_URL";
340
+ var CHATGPT_AUTH_SERVER_STORE_ENV = "CHATGPT_AUTH_SERVER_STORE";
341
+ var CHATGPT_AUTH_API_KEY_ENV = "CHATGPT_AUTH_API_KEY";
342
+ var CHATGPT_AUTH_TOKEN_PROVIDER_API_KEY_ENV = "CHATGPT_AUTH_TOKEN_PROVIDER_API_KEY";
301
343
  var CHATGPT_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
302
344
  var CHATGPT_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
303
345
  var CHATGPT_OAUTH_REDIRECT_URI = "http://localhost:1455/auth/callback";
304
346
  var TOKEN_EXPIRY_BUFFER_MS = 3e4;
305
- var AuthInputSchema = z.object({
306
- access: z.string().min(1).optional(),
307
- access_token: z.string().min(1).optional(),
308
- accessToken: z.string().min(1).optional(),
309
- refresh: z.string().min(1).optional(),
310
- refresh_token: z.string().min(1).optional(),
311
- refreshToken: z.string().min(1).optional(),
312
- expires: z.union([z.number(), z.string()]).optional(),
313
- expires_at: z.union([z.number(), z.string()]).optional(),
314
- expiresAt: z.union([z.number(), z.string()]).optional(),
315
- accountId: z.string().min(1).optional(),
316
- account_id: z.string().min(1).optional(),
317
- id_token: z.string().optional(),
318
- idToken: z.string().optional()
347
+ var CodexAuthFileSchema = z.object({
348
+ OPENAI_API_KEY: z.string().nullable().optional(),
349
+ last_refresh: z.string().optional(),
350
+ tokens: z.object({
351
+ access_token: z.string().min(1).optional(),
352
+ refresh_token: z.string().min(1).optional(),
353
+ id_token: z.string().min(1).optional(),
354
+ account_id: z.string().min(1).optional(),
355
+ // Allow a bit of flexibility if the file format changes.
356
+ accessToken: z.string().min(1).optional(),
357
+ refreshToken: z.string().min(1).optional(),
358
+ idToken: z.string().min(1).optional(),
359
+ accountId: z.string().min(1).optional()
360
+ }).optional()
319
361
  }).loose();
320
362
  var RefreshResponseSchema = z.object({
321
363
  access_token: z.string().min(1),
@@ -330,6 +372,44 @@ var ExchangeResponseSchema = z.object({
330
372
  });
331
373
  var cachedProfile = null;
332
374
  var refreshPromise = null;
375
+ async function fetchChatGptAuthProfileFromTokenProvider(options) {
376
+ const base = options.baseUrl.replace(/\/+$/u, "");
377
+ const store = options.store?.trim() ? options.store.trim() : "kv";
378
+ const url = new URL(`${base}/v1/token`);
379
+ url.searchParams.set("store", store);
380
+ const response = await fetch(url.toString(), {
381
+ method: "GET",
382
+ headers: {
383
+ Authorization: `Bearer ${options.apiKey}`,
384
+ "x-chatgpt-auth": options.apiKey,
385
+ Accept: "application/json"
386
+ }
387
+ });
388
+ if (!response.ok) {
389
+ const body = await response.text();
390
+ throw new Error(`ChatGPT token provider request failed (${response.status}): ${body}`);
391
+ }
392
+ const payload = await response.json();
393
+ if (!payload || typeof payload !== "object") {
394
+ throw new Error("ChatGPT token provider returned invalid JSON.");
395
+ }
396
+ const accessToken = payload.accessToken ?? payload.access_token;
397
+ const accountId = payload.accountId ?? payload.account_id;
398
+ const expiresAt = payload.expiresAt ?? payload.expires_at;
399
+ if (typeof accessToken !== "string" || accessToken.trim().length === 0) {
400
+ throw new Error("ChatGPT token provider response missing accessToken.");
401
+ }
402
+ if (typeof accountId !== "string" || accountId.trim().length === 0) {
403
+ throw new Error("ChatGPT token provider response missing accountId.");
404
+ }
405
+ const expires = normalizeEpochMillis(expiresAt) ?? Date.now() + 5 * 6e4;
406
+ return {
407
+ access: accessToken,
408
+ refresh: "token_provider",
409
+ expires,
410
+ accountId
411
+ };
412
+ }
333
413
  function encodeChatGptAuthJson(profile) {
334
414
  const payload = {
335
415
  access: profile.access,
@@ -368,7 +448,7 @@ async function exchangeChatGptOauthCode({
368
448
  const payload = ExchangeResponseSchema.parse(await response.json());
369
449
  return profileFromTokenResponse(payload);
370
450
  }
371
- async function refreshChatGptOauthToken(refreshToken) {
451
+ async function refreshChatGptOauthToken(refreshToken, fallback) {
372
452
  const params = new URLSearchParams();
373
453
  params.set("grant_type", "refresh_token");
374
454
  params.set("client_id", CHATGPT_OAUTH_CLIENT_ID);
@@ -385,9 +465,35 @@ async function refreshChatGptOauthToken(refreshToken) {
385
465
  throw new Error(`ChatGPT OAuth refresh failed (${response.status}): ${body}`);
386
466
  }
387
467
  const payload = RefreshResponseSchema.parse(await response.json());
388
- return profileFromTokenResponse(payload);
468
+ return profileFromTokenResponse(payload, fallback);
389
469
  }
390
470
  async function getChatGptAuthProfile() {
471
+ loadLocalEnv();
472
+ const tokenProviderUrl = process.env[CHATGPT_AUTH_TOKEN_PROVIDER_URL_ENV] ?? process.env[CHATGPT_AUTH_SERVER_URL_ENV];
473
+ const tokenProviderKey = process.env[CHATGPT_AUTH_TOKEN_PROVIDER_API_KEY_ENV] ?? process.env[CHATGPT_AUTH_API_KEY_ENV];
474
+ if (tokenProviderUrl && tokenProviderUrl.trim().length > 0 && tokenProviderKey && tokenProviderKey.trim().length > 0) {
475
+ if (cachedProfile && !isExpired(cachedProfile)) {
476
+ return cachedProfile;
477
+ }
478
+ if (refreshPromise) {
479
+ return refreshPromise;
480
+ }
481
+ refreshPromise = (async () => {
482
+ try {
483
+ const store = process.env[CHATGPT_AUTH_TOKEN_PROVIDER_STORE_ENV] ?? process.env[CHATGPT_AUTH_SERVER_STORE_ENV];
484
+ const profile = await fetchChatGptAuthProfileFromTokenProvider({
485
+ baseUrl: tokenProviderUrl,
486
+ apiKey: tokenProviderKey,
487
+ store: store ?? void 0
488
+ });
489
+ cachedProfile = profile;
490
+ return profile;
491
+ } finally {
492
+ refreshPromise = null;
493
+ }
494
+ })();
495
+ return refreshPromise;
496
+ }
391
497
  if (cachedProfile && !isExpired(cachedProfile)) {
392
498
  return cachedProfile;
393
499
  }
@@ -396,8 +502,8 @@ async function getChatGptAuthProfile() {
396
502
  }
397
503
  refreshPromise = (async () => {
398
504
  try {
399
- const baseProfile = cachedProfile ?? loadAuthProfileFromEnv();
400
- const profile = isExpired(baseProfile) ? await refreshChatGptOauthToken(baseProfile.refresh) : baseProfile;
505
+ const baseProfile = cachedProfile ?? loadAuthProfileFromCodexStore();
506
+ const profile = isExpired(baseProfile) ? await refreshAndPersistCodexProfile(baseProfile) : baseProfile;
401
507
  cachedProfile = profile;
402
508
  return profile;
403
509
  } finally {
@@ -406,39 +512,111 @@ async function getChatGptAuthProfile() {
406
512
  })();
407
513
  return refreshPromise;
408
514
  }
409
- function profileFromTokenResponse(payload) {
410
- const expires = Date.now() + normalizeNumber(payload.expires_in) * 1e3;
411
- const accountId = extractChatGptAccountId(payload.id_token ?? "") ?? extractChatGptAccountId(payload.access_token);
515
+ function resolveCodexHome() {
516
+ const codexHome = process.env.CODEX_HOME;
517
+ if (codexHome && codexHome.trim().length > 0) {
518
+ return codexHome.trim();
519
+ }
520
+ return path2.join(os.homedir(), ".codex");
521
+ }
522
+ function resolveCodexAuthJsonPath() {
523
+ return path2.join(resolveCodexHome(), "auth.json");
524
+ }
525
+ function loadAuthProfileFromCodexStore() {
526
+ const authPath = resolveCodexAuthJsonPath();
527
+ let raw;
528
+ try {
529
+ raw = fs2.readFileSync(authPath, "utf8");
530
+ } catch {
531
+ throw new Error(
532
+ `ChatGPT auth not configured. Set ${CHATGPT_AUTH_TOKEN_PROVIDER_URL_ENV}+${CHATGPT_AUTH_API_KEY_ENV} or login via Codex to create ${authPath}.`
533
+ );
534
+ }
535
+ let parsed;
536
+ try {
537
+ parsed = CodexAuthFileSchema.parse(JSON.parse(raw));
538
+ } catch (e) {
539
+ throw new Error(
540
+ `Failed to parse Codex auth store at ${authPath}. (${e?.message ?? e})`
541
+ );
542
+ }
543
+ const tokens = parsed.tokens;
544
+ if (!tokens) {
545
+ throw new Error(
546
+ `Codex auth store at ${authPath} is missing tokens. Re-login via Codex, or configure ${CHATGPT_AUTH_TOKEN_PROVIDER_URL_ENV}.`
547
+ );
548
+ }
549
+ const access = tokens.access_token ?? tokens.accessToken ?? void 0;
550
+ const refresh = tokens.refresh_token ?? tokens.refreshToken ?? void 0;
551
+ const idToken = tokens.id_token ?? tokens.idToken ?? void 0;
552
+ if (!access || !refresh) {
553
+ throw new Error(
554
+ `Codex auth store at ${authPath} is missing access_token/refresh_token. Re-login via Codex, or configure ${CHATGPT_AUTH_TOKEN_PROVIDER_URL_ENV}.`
555
+ );
556
+ }
557
+ const expires = extractJwtExpiry(access) ?? extractJwtExpiry(idToken ?? "") ?? Date.now() + 5 * 6e4;
558
+ const accountId = tokens.account_id ?? tokens.accountId ?? extractChatGptAccountId(idToken ?? "") ?? extractChatGptAccountId(access);
412
559
  if (!accountId) {
413
- throw new Error("Failed to extract chatgpt_account_id from access token.");
560
+ throw new Error(`Codex auth store at ${authPath} is missing chatgpt_account_id/account_id.`);
414
561
  }
415
562
  return {
416
- access: payload.access_token,
417
- refresh: payload.refresh_token,
563
+ access,
564
+ refresh,
418
565
  expires,
419
566
  accountId,
420
- idToken: payload.id_token
567
+ idToken: idToken ?? void 0
421
568
  };
422
569
  }
423
- function normalizeAuthProfile(data) {
424
- const access = data.access ?? data.access_token ?? data.accessToken ?? void 0;
425
- const refresh = data.refresh ?? data.refresh_token ?? data.refreshToken ?? void 0;
426
- if (!access || !refresh) {
427
- throw new Error("ChatGPT credentials must include access and refresh.");
570
+ async function refreshAndPersistCodexProfile(baseProfile) {
571
+ const refreshed = await refreshChatGptOauthToken(baseProfile.refresh, {
572
+ accountId: baseProfile.accountId,
573
+ idToken: baseProfile.idToken
574
+ });
575
+ persistCodexTokens(refreshed);
576
+ return refreshed;
577
+ }
578
+ function persistCodexTokens(profile) {
579
+ const authPath = resolveCodexAuthJsonPath();
580
+ const codexHome = path2.dirname(authPath);
581
+ let doc = {};
582
+ try {
583
+ doc = JSON.parse(fs2.readFileSync(authPath, "utf8"));
584
+ } catch {
585
+ doc = {};
586
+ }
587
+ if (!doc || typeof doc !== "object") {
588
+ doc = {};
428
589
  }
429
- const expiresRaw = data.expires ?? data.expires_at ?? data.expiresAt;
430
- const idToken = data.idToken ?? data.id_token ?? void 0;
431
- const expires = normalizeEpochMillis(expiresRaw) ?? extractJwtExpiry(idToken ?? access) ?? Date.now() + 5 * 6e4;
432
- const accountId = data.accountId ?? data.account_id ?? extractChatGptAccountId(idToken ?? "") ?? extractChatGptAccountId(access);
590
+ if (!doc.tokens || typeof doc.tokens !== "object") {
591
+ doc.tokens = {};
592
+ }
593
+ doc.tokens.access_token = profile.access;
594
+ doc.tokens.refresh_token = profile.refresh;
595
+ doc.tokens.account_id = profile.accountId;
596
+ if (profile.idToken) {
597
+ doc.tokens.id_token = profile.idToken;
598
+ }
599
+ doc.last_refresh = (/* @__PURE__ */ new Date()).toISOString();
600
+ fs2.mkdirSync(codexHome, { recursive: true, mode: 448 });
601
+ const tmpPath = `${authPath}.tmp.${process.pid}.${Math.random().toString(16).slice(2)}`;
602
+ fs2.writeFileSync(tmpPath, `${JSON.stringify(doc, null, 2)}
603
+ `, { mode: 384 });
604
+ fs2.renameSync(tmpPath, authPath);
605
+ }
606
+ function profileFromTokenResponse(payload, fallback) {
607
+ const expires = Date.now() + normalizeNumber(payload.expires_in) * 1e3;
608
+ const fallbackAccountId = fallback?.accountId;
609
+ const fallbackIdToken = fallback?.idToken;
610
+ const accountId = extractChatGptAccountId(payload.id_token ?? "") ?? extractChatGptAccountId(payload.access_token) ?? fallbackAccountId;
433
611
  if (!accountId) {
434
- throw new Error("ChatGPT credentials missing chatgpt_account_id.");
612
+ throw new Error("Failed to extract chatgpt_account_id from access token.");
435
613
  }
436
614
  return {
437
- access,
438
- refresh,
615
+ access: payload.access_token,
616
+ refresh: payload.refresh_token,
439
617
  expires,
440
618
  accountId,
441
- idToken: idToken ?? void 0
619
+ idToken: payload.id_token ?? fallbackIdToken
442
620
  };
443
621
  }
444
622
  function normalizeEpochMillis(value) {
@@ -467,31 +645,6 @@ function isExpired(profile) {
467
645
  }
468
646
  return Date.now() + TOKEN_EXPIRY_BUFFER_MS >= expires;
469
647
  }
470
- function loadAuthProfileFromEnv() {
471
- loadLocalEnv();
472
- const rawJson = process.env[CHATGPT_AUTH_JSON_ENV];
473
- if (rawJson && rawJson.trim().length > 0) {
474
- return normalizeAuthProfile(AuthInputSchema.parse(JSON.parse(rawJson)));
475
- }
476
- const rawB64 = process.env[CHATGPT_AUTH_JSON_B64_ENV];
477
- if (rawB64 && rawB64.trim().length > 0) {
478
- const decoded = Buffer2.from(rawB64.trim(), "base64url").toString("utf8");
479
- return normalizeAuthProfile(AuthInputSchema.parse(JSON.parse(decoded)));
480
- }
481
- const access = process.env[CHATGPT_ACCESS_ENV] ?? process.env[CHATGPT_ACCESS_TOKEN_ENV] ?? void 0;
482
- const refresh = process.env[CHATGPT_REFRESH_ENV] ?? process.env[CHATGPT_REFRESH_TOKEN_ENV] ?? void 0;
483
- const expires = process.env[CHATGPT_EXPIRES_ENV] ?? process.env[CHATGPT_EXPIRES_AT_ENV] ?? void 0;
484
- const accountId = process.env[CHATGPT_ACCOUNT_ID_ENV] ?? void 0;
485
- const idToken = process.env[CHATGPT_ID_TOKEN_ENV] ?? void 0;
486
- const parsed = AuthInputSchema.parse({
487
- access,
488
- refresh,
489
- expires,
490
- accountId,
491
- idToken
492
- });
493
- return normalizeAuthProfile(parsed);
494
- }
495
648
  function decodeJwtPayload(token) {
496
649
  const segments = token.split(".");
497
650
  if (segments.length < 2) {
@@ -522,8 +675,12 @@ function extractChatGptAccountId(token) {
522
675
  if (!payload || typeof payload !== "object") {
523
676
  return void 0;
524
677
  }
525
- const accountId = payload.chatgpt_account_id;
526
- return typeof accountId === "string" && accountId.length > 0 ? accountId : void 0;
678
+ const direct = payload.chatgpt_account_id;
679
+ if (typeof direct === "string" && direct.length > 0) {
680
+ return direct;
681
+ }
682
+ const namespaced = payload["https://api.openai.com/auth"]?.chatgpt_account_id;
683
+ return typeof namespaced === "string" && namespaced.length > 0 ? namespaced : void 0;
527
684
  }
528
685
 
529
686
  // src/openai/chatgpt-codex.ts
@@ -559,7 +716,19 @@ async function streamChatGptCodexResponse(options) {
559
716
  return parseEventStream(body);
560
717
  }
561
718
  async function collectChatGptCodexResponse(options) {
562
- const stream = await streamChatGptCodexResponse(options);
719
+ let stream;
720
+ try {
721
+ stream = await streamChatGptCodexResponse(options);
722
+ } catch (error) {
723
+ if (shouldRetryWithoutReasoningSummary(options.request, error)) {
724
+ stream = await streamChatGptCodexResponse({
725
+ ...options,
726
+ request: removeReasoningSummary(options.request)
727
+ });
728
+ } else {
729
+ throw error;
730
+ }
731
+ }
563
732
  const toolCalls = /* @__PURE__ */ new Map();
564
733
  const toolCallOrder = [];
565
734
  const webSearchCalls = /* @__PURE__ */ new Map();
@@ -568,6 +737,7 @@ async function collectChatGptCodexResponse(options) {
568
737
  const reasoningText = "";
569
738
  let reasoningSummaryText = "";
570
739
  let usage;
740
+ let responseId;
571
741
  let model;
572
742
  let status;
573
743
  let blocked = false;
@@ -608,7 +778,18 @@ async function collectChatGptCodexResponse(options) {
608
778
  if (!toolCalls.has(callId)) {
609
779
  toolCallOrder.push(callId);
610
780
  }
611
- toolCalls.set(callId, { id, callId, name, arguments: args });
781
+ toolCalls.set(callId, { kind: "function", id, callId, name, arguments: args });
782
+ }
783
+ } else if (item.type === "custom_tool_call") {
784
+ const id = typeof item.id === "string" ? item.id : "";
785
+ const callId = typeof item.call_id === "string" ? item.call_id : id;
786
+ const name = typeof item.name === "string" ? item.name : "";
787
+ const input = typeof item.input === "string" ? item.input : "";
788
+ if (callId) {
789
+ if (!toolCalls.has(callId)) {
790
+ toolCallOrder.push(callId);
791
+ }
792
+ toolCalls.set(callId, { kind: "custom", id, callId, name, input });
612
793
  }
613
794
  } else if (item.type === "web_search_call") {
614
795
  const id = typeof item.id === "string" ? item.id : "";
@@ -630,6 +811,7 @@ async function collectChatGptCodexResponse(options) {
630
811
  const response = event.response;
631
812
  if (response) {
632
813
  usage = response.usage;
814
+ responseId = typeof response.id === "string" ? response.id : responseId;
633
815
  model = typeof response.model === "string" ? response.model : void 0;
634
816
  status = typeof response.status === "string" ? response.status : void 0;
635
817
  }
@@ -639,6 +821,7 @@ async function collectChatGptCodexResponse(options) {
639
821
  const response = event.response;
640
822
  if (response) {
641
823
  usage = response.usage;
824
+ responseId = typeof response.id === "string" ? response.id : responseId;
642
825
  model = typeof response.model === "string" ? response.model : void 0;
643
826
  status = typeof response.status === "string" ? response.status : void 0;
644
827
  }
@@ -648,6 +831,7 @@ async function collectChatGptCodexResponse(options) {
648
831
  const response = event.response;
649
832
  if (response) {
650
833
  usage = response.usage;
834
+ responseId = typeof response.id === "string" ? response.id : responseId;
651
835
  model = typeof response.model === "string" ? response.model : void 0;
652
836
  status = typeof response.status === "string" ? response.status : void 0;
653
837
  }
@@ -665,15 +849,38 @@ async function collectChatGptCodexResponse(options) {
665
849
  toolCalls: orderedToolCalls,
666
850
  webSearchCalls: orderedWebSearchCalls,
667
851
  usage,
852
+ id: responseId,
668
853
  model,
669
854
  status,
670
855
  blocked
671
856
  };
672
857
  }
858
+ function shouldRetryWithoutReasoningSummary(request, error) {
859
+ if (!request.reasoning?.summary) {
860
+ return false;
861
+ }
862
+ if (!(error instanceof Error)) {
863
+ return false;
864
+ }
865
+ const message = error.message.toLowerCase();
866
+ return message.includes("unsupported parameter") && message.includes("reasoning.summary");
867
+ }
868
+ function removeReasoningSummary(request) {
869
+ const reasoning = request.reasoning;
870
+ if (!reasoning?.summary) {
871
+ return request;
872
+ }
873
+ return {
874
+ ...request,
875
+ reasoning: {
876
+ effort: reasoning.effort
877
+ }
878
+ };
879
+ }
673
880
  function buildUserAgent() {
674
881
  const node = process.version;
675
- const platform = os.platform();
676
- const release = os.release();
882
+ const platform = os2.platform();
883
+ const release = os2.release();
677
884
  return `@ljoukov/llm (node ${node}; ${platform} ${release})`;
678
885
  }
679
886
  async function* parseEventStream(stream) {
@@ -821,6 +1028,110 @@ function createCallScheduler(options = {}) {
821
1028
  return { run };
822
1029
  }
823
1030
 
1031
+ // src/fireworks/client.ts
1032
+ import OpenAI from "openai";
1033
+ import { Agent, fetch as undiciFetch } from "undici";
1034
+ var DEFAULT_FIREWORKS_BASE_URL = "https://api.fireworks.ai/inference/v1";
1035
+ var DEFAULT_FIREWORKS_TIMEOUT_MS = 15 * 6e4;
1036
+ var cachedClient = null;
1037
+ var cachedFetch = null;
1038
+ var cachedBaseUrl = null;
1039
+ var cachedApiKey = null;
1040
+ var cachedTimeoutMs = null;
1041
+ function resolveTimeoutMs() {
1042
+ if (cachedTimeoutMs !== null) {
1043
+ return cachedTimeoutMs;
1044
+ }
1045
+ const raw = process.env.FIREWORKS_TIMEOUT_MS;
1046
+ const parsed = raw ? Number(raw) : Number.NaN;
1047
+ cachedTimeoutMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_FIREWORKS_TIMEOUT_MS;
1048
+ return cachedTimeoutMs;
1049
+ }
1050
+ function resolveBaseUrl() {
1051
+ if (cachedBaseUrl !== null) {
1052
+ return cachedBaseUrl;
1053
+ }
1054
+ loadLocalEnv();
1055
+ const raw = process.env.FIREWORKS_BASE_URL?.trim();
1056
+ cachedBaseUrl = raw && raw.length > 0 ? raw : DEFAULT_FIREWORKS_BASE_URL;
1057
+ return cachedBaseUrl;
1058
+ }
1059
+ function resolveApiKey() {
1060
+ if (cachedApiKey !== null) {
1061
+ return cachedApiKey;
1062
+ }
1063
+ loadLocalEnv();
1064
+ const raw = process.env.FIREWORKS_TOKEN ?? process.env.FIREWORKS_API_KEY;
1065
+ const token = raw?.trim();
1066
+ if (!token) {
1067
+ throw new Error(
1068
+ "FIREWORKS_TOKEN (or FIREWORKS_API_KEY) must be provided to access Fireworks APIs."
1069
+ );
1070
+ }
1071
+ cachedApiKey = token;
1072
+ return cachedApiKey;
1073
+ }
1074
+ function getFireworksFetch() {
1075
+ if (cachedFetch) {
1076
+ return cachedFetch;
1077
+ }
1078
+ const timeoutMs = resolveTimeoutMs();
1079
+ const dispatcher = new Agent({
1080
+ bodyTimeout: timeoutMs,
1081
+ headersTimeout: timeoutMs
1082
+ });
1083
+ cachedFetch = ((input, init) => {
1084
+ return undiciFetch(input, {
1085
+ ...init ?? {},
1086
+ dispatcher
1087
+ });
1088
+ });
1089
+ return cachedFetch;
1090
+ }
1091
+ function getFireworksClient() {
1092
+ if (cachedClient) {
1093
+ return cachedClient;
1094
+ }
1095
+ cachedClient = new OpenAI({
1096
+ apiKey: resolveApiKey(),
1097
+ baseURL: resolveBaseUrl(),
1098
+ timeout: resolveTimeoutMs(),
1099
+ fetch: getFireworksFetch()
1100
+ });
1101
+ return cachedClient;
1102
+ }
1103
+
1104
+ // src/fireworks/calls.ts
1105
+ var scheduler = createCallScheduler({
1106
+ maxParallelRequests: 3,
1107
+ minIntervalBetweenStartMs: 200,
1108
+ startJitterMs: 200
1109
+ });
1110
+ async function runFireworksCall(fn) {
1111
+ return scheduler.run(async () => fn(getFireworksClient()));
1112
+ }
1113
+
1114
+ // src/fireworks/models.ts
1115
+ var FIREWORKS_MODEL_IDS = ["kimi-k2.5", "glm-5", "minimax-m2.1"];
1116
+ var FIREWORKS_DEFAULT_KIMI_MODEL = "kimi-k2.5";
1117
+ var FIREWORKS_DEFAULT_GLM_MODEL = "glm-5";
1118
+ var FIREWORKS_DEFAULT_MINIMAX_MODEL = "minimax-m2.1";
1119
+ var FIREWORKS_CANONICAL_MODEL_IDS = {
1120
+ "kimi-k2.5": "accounts/fireworks/models/kimi-k2p5",
1121
+ "glm-5": "accounts/fireworks/models/glm-5",
1122
+ "minimax-m2.1": "accounts/fireworks/models/minimax-m2p1"
1123
+ };
1124
+ function isFireworksModelId(value) {
1125
+ return FIREWORKS_MODEL_IDS.includes(value.trim());
1126
+ }
1127
+ function resolveFireworksModelId(model) {
1128
+ const trimmed = model.trim();
1129
+ if (!isFireworksModelId(trimmed)) {
1130
+ return void 0;
1131
+ }
1132
+ return FIREWORKS_CANONICAL_MODEL_IDS[trimmed];
1133
+ }
1134
+
824
1135
  // src/google/client.ts
825
1136
  import { GoogleGenAI } from "@google/genai";
826
1137
 
@@ -889,6 +1200,7 @@ function getGoogleAuthOptions(scopes) {
889
1200
  // src/google/client.ts
890
1201
  var GEMINI_MODEL_IDS = [
891
1202
  "gemini-3-pro-preview",
1203
+ "gemini-3-flash-preview",
892
1204
  "gemini-2.5-pro",
893
1205
  "gemini-flash-latest",
894
1206
  "gemini-flash-lite-latest"
@@ -1128,7 +1440,7 @@ function retryDelayMs(attempt) {
1128
1440
  const jitter = Math.floor(Math.random() * 200);
1129
1441
  return base + jitter;
1130
1442
  }
1131
- var scheduler = createCallScheduler({
1443
+ var scheduler2 = createCallScheduler({
1132
1444
  maxParallelRequests: 3,
1133
1445
  minIntervalBetweenStartMs: 200,
1134
1446
  startJitterMs: 200,
@@ -1144,46 +1456,46 @@ var scheduler = createCallScheduler({
1144
1456
  }
1145
1457
  });
1146
1458
  async function runGeminiCall(fn) {
1147
- return scheduler.run(async () => fn(await getGeminiClient()));
1459
+ return scheduler2.run(async () => fn(await getGeminiClient()));
1148
1460
  }
1149
1461
 
1150
1462
  // src/openai/client.ts
1151
- import OpenAI from "openai";
1152
- import { Agent, fetch as undiciFetch } from "undici";
1153
- var cachedApiKey = null;
1154
- var cachedClient = null;
1155
- var cachedFetch = null;
1156
- var cachedTimeoutMs = null;
1463
+ import OpenAI2 from "openai";
1464
+ import { Agent as Agent2, fetch as undiciFetch2 } from "undici";
1465
+ var cachedApiKey2 = null;
1466
+ var cachedClient2 = null;
1467
+ var cachedFetch2 = null;
1468
+ var cachedTimeoutMs2 = null;
1157
1469
  var DEFAULT_OPENAI_TIMEOUT_MS = 15 * 6e4;
1158
1470
  function resolveOpenAiTimeoutMs() {
1159
- if (cachedTimeoutMs !== null) {
1160
- return cachedTimeoutMs;
1471
+ if (cachedTimeoutMs2 !== null) {
1472
+ return cachedTimeoutMs2;
1161
1473
  }
1162
1474
  const raw = process.env.OPENAI_STREAM_TIMEOUT_MS ?? process.env.OPENAI_TIMEOUT_MS;
1163
1475
  const parsed = raw ? Number(raw) : Number.NaN;
1164
- cachedTimeoutMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_OPENAI_TIMEOUT_MS;
1165
- return cachedTimeoutMs;
1476
+ cachedTimeoutMs2 = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_OPENAI_TIMEOUT_MS;
1477
+ return cachedTimeoutMs2;
1166
1478
  }
1167
1479
  function getOpenAiFetch() {
1168
- if (cachedFetch) {
1169
- return cachedFetch;
1480
+ if (cachedFetch2) {
1481
+ return cachedFetch2;
1170
1482
  }
1171
1483
  const timeoutMs = resolveOpenAiTimeoutMs();
1172
- const dispatcher = new Agent({
1484
+ const dispatcher = new Agent2({
1173
1485
  bodyTimeout: timeoutMs,
1174
1486
  headersTimeout: timeoutMs
1175
1487
  });
1176
- cachedFetch = ((input, init) => {
1177
- return undiciFetch(input, {
1488
+ cachedFetch2 = ((input, init) => {
1489
+ return undiciFetch2(input, {
1178
1490
  ...init ?? {},
1179
1491
  dispatcher
1180
1492
  });
1181
1493
  });
1182
- return cachedFetch;
1494
+ return cachedFetch2;
1183
1495
  }
1184
1496
  function getOpenAiApiKey() {
1185
- if (cachedApiKey !== null) {
1186
- return cachedApiKey;
1497
+ if (cachedApiKey2 !== null) {
1498
+ return cachedApiKey2;
1187
1499
  }
1188
1500
  loadLocalEnv();
1189
1501
  const raw = process.env.OPENAI_API_KEY;
@@ -1191,32 +1503,32 @@ function getOpenAiApiKey() {
1191
1503
  if (!value) {
1192
1504
  throw new Error("OPENAI_API_KEY must be provided to access OpenAI APIs.");
1193
1505
  }
1194
- cachedApiKey = value;
1195
- return cachedApiKey;
1506
+ cachedApiKey2 = value;
1507
+ return cachedApiKey2;
1196
1508
  }
1197
1509
  function getOpenAiClient() {
1198
- if (cachedClient) {
1199
- return cachedClient;
1510
+ if (cachedClient2) {
1511
+ return cachedClient2;
1200
1512
  }
1201
1513
  const apiKey = getOpenAiApiKey();
1202
1514
  const timeoutMs = resolveOpenAiTimeoutMs();
1203
- cachedClient = new OpenAI({
1515
+ cachedClient2 = new OpenAI2({
1204
1516
  apiKey,
1205
1517
  fetch: getOpenAiFetch(),
1206
1518
  timeout: timeoutMs
1207
1519
  });
1208
- return cachedClient;
1520
+ return cachedClient2;
1209
1521
  }
1210
1522
 
1211
1523
  // src/openai/calls.ts
1212
1524
  var DEFAULT_OPENAI_REASONING_EFFORT = "medium";
1213
- var scheduler2 = createCallScheduler({
1525
+ var scheduler3 = createCallScheduler({
1214
1526
  maxParallelRequests: 3,
1215
1527
  minIntervalBetweenStartMs: 200,
1216
1528
  startJitterMs: 200
1217
1529
  });
1218
1530
  async function runOpenAiCall(fn) {
1219
- return scheduler2.run(async () => fn(getOpenAiClient()));
1531
+ return scheduler3.run(async () => fn(getOpenAiClient()));
1220
1532
  }
1221
1533
 
1222
1534
  // src/llm.ts
@@ -1232,7 +1544,16 @@ var LlmJsonCallError = class extends Error {
1232
1544
  }
1233
1545
  };
1234
1546
  function tool(options) {
1235
- return options;
1547
+ return {
1548
+ type: "function",
1549
+ ...options
1550
+ };
1551
+ }
1552
+ function customTool(options) {
1553
+ return {
1554
+ type: "custom",
1555
+ ...options
1556
+ };
1236
1557
  }
1237
1558
  function isPlainRecord(value) {
1238
1559
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -1578,6 +1899,10 @@ function resolveProvider(model) {
1578
1899
  if (model.startsWith("gemini-")) {
1579
1900
  return { provider: "gemini", model };
1580
1901
  }
1902
+ const fireworksModel = resolveFireworksModelId(model);
1903
+ if (fireworksModel) {
1904
+ return { provider: "fireworks", model: fireworksModel };
1905
+ }
1581
1906
  return { provider: "openai", model };
1582
1907
  }
1583
1908
  function isOpenAiCodexModel(modelId) {
@@ -1607,6 +1932,27 @@ function toOpenAiReasoningEffort(effort) {
1607
1932
  function resolveOpenAiVerbosity(modelId) {
1608
1933
  return isOpenAiCodexModel(modelId) ? "medium" : "high";
1609
1934
  }
1935
+ function isRetryableChatGptTransportError(error) {
1936
+ if (!(error instanceof Error)) {
1937
+ return false;
1938
+ }
1939
+ const message = error.message.toLowerCase();
1940
+ return message === "terminated" || message.includes("socket hang up") || message.includes("fetch failed") || message.includes("network");
1941
+ }
1942
+ async function collectChatGptCodexResponseWithRetry(options, maxAttempts = 2) {
1943
+ let attempt = 1;
1944
+ while (true) {
1945
+ try {
1946
+ return await collectChatGptCodexResponse(options);
1947
+ } catch (error) {
1948
+ if (attempt >= maxAttempts || !isRetryableChatGptTransportError(error)) {
1949
+ throw error;
1950
+ }
1951
+ await new Promise((resolve) => setTimeout(resolve, 250 * attempt));
1952
+ attempt += 1;
1953
+ }
1954
+ }
1955
+ }
1610
1956
  function isInlineImageMime(mimeType) {
1611
1957
  if (!mimeType) {
1612
1958
  return false;
@@ -2189,6 +2535,53 @@ function toChatGptInput(contents) {
2189
2535
  input
2190
2536
  };
2191
2537
  }
2538
+ function toFireworksMessages(contents, options) {
2539
+ const systemMessages = [];
2540
+ const messages = [];
2541
+ if (options?.responseMimeType === "application/json") {
2542
+ systemMessages.push("Return valid JSON only. Do not include markdown or prose outside JSON.");
2543
+ }
2544
+ if (options?.responseJsonSchema) {
2545
+ systemMessages.push(`Target JSON schema:
2546
+ ${JSON.stringify(options.responseJsonSchema)}`);
2547
+ }
2548
+ for (const content of contents) {
2549
+ const text = content.parts.map((part) => {
2550
+ if (part.type === "text") {
2551
+ return part.text;
2552
+ }
2553
+ const mimeType = part.mimeType ?? "application/octet-stream";
2554
+ if (isInlineImageMime(mimeType)) {
2555
+ return `[image:${mimeType}]`;
2556
+ }
2557
+ return `[file:${mimeType}]`;
2558
+ }).join("\n").trim();
2559
+ if (content.role === "system" || content.role === "developer") {
2560
+ if (text.length > 0) {
2561
+ systemMessages.push(text);
2562
+ }
2563
+ continue;
2564
+ }
2565
+ if (content.role === "tool" || content.role === "assistant") {
2566
+ messages.push({
2567
+ role: "assistant",
2568
+ content: text.length > 0 ? text : "(empty content)"
2569
+ });
2570
+ continue;
2571
+ }
2572
+ messages.push({
2573
+ role: "user",
2574
+ content: text.length > 0 ? text : "(empty content)"
2575
+ });
2576
+ }
2577
+ if (systemMessages.length > 0) {
2578
+ messages.unshift({
2579
+ role: "system",
2580
+ content: systemMessages.join("\n\n")
2581
+ });
2582
+ }
2583
+ return messages;
2584
+ }
2192
2585
  function toGeminiTools(tools) {
2193
2586
  if (!tools || tools.length === 0) {
2194
2587
  return void 0;
@@ -2362,6 +2755,41 @@ function extractChatGptUsageTokens(usage) {
2362
2755
  totalTokens
2363
2756
  };
2364
2757
  }
2758
+ function extractFireworksUsageTokens(usage) {
2759
+ if (!usage || typeof usage !== "object") {
2760
+ return void 0;
2761
+ }
2762
+ const promptTokens = toMaybeNumber(
2763
+ usage.prompt_tokens ?? usage.input_tokens
2764
+ );
2765
+ const cachedTokens = toMaybeNumber(
2766
+ usage.prompt_tokens_details?.cached_tokens ?? usage.input_tokens_details?.cached_tokens
2767
+ );
2768
+ const outputTokensRaw = toMaybeNumber(
2769
+ usage.completion_tokens ?? usage.output_tokens
2770
+ );
2771
+ const reasoningTokens = toMaybeNumber(
2772
+ usage.completion_tokens_details?.reasoning_tokens ?? usage.output_tokens_details?.reasoning_tokens
2773
+ );
2774
+ const totalTokens = toMaybeNumber(
2775
+ usage.total_tokens ?? usage.totalTokenCount
2776
+ );
2777
+ let responseTokens;
2778
+ if (outputTokensRaw !== void 0) {
2779
+ const adjusted = outputTokensRaw - (reasoningTokens ?? 0);
2780
+ responseTokens = adjusted >= 0 ? adjusted : 0;
2781
+ }
2782
+ if (promptTokens === void 0 && cachedTokens === void 0 && responseTokens === void 0 && reasoningTokens === void 0 && totalTokens === void 0) {
2783
+ return void 0;
2784
+ }
2785
+ return {
2786
+ promptTokens,
2787
+ cachedTokens,
2788
+ responseTokens,
2789
+ thinkingTokens: reasoningTokens,
2790
+ totalTokens
2791
+ };
2792
+ }
2365
2793
  var MODERATION_FINISH_REASONS = /* @__PURE__ */ new Set([
2366
2794
  FinishReason.SAFETY,
2367
2795
  FinishReason.BLOCKLIST,
@@ -2400,8 +2828,8 @@ function parseOpenAiToolArguments(raw) {
2400
2828
  function formatZodIssues(issues) {
2401
2829
  const messages = [];
2402
2830
  for (const issue of issues) {
2403
- const path5 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
2404
- messages.push(`${path5}: ${issue.message}`);
2831
+ const path6 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
2832
+ messages.push(`${path6}: ${issue.message}`);
2405
2833
  }
2406
2834
  return messages.join("; ");
2407
2835
  }
@@ -2417,7 +2845,7 @@ function buildToolErrorOutput(message, issues) {
2417
2845
  return output;
2418
2846
  }
2419
2847
  async function executeToolCall(params) {
2420
- const { toolName, tool: tool2, rawInput, parseError } = params;
2848
+ const { callKind, toolName, tool: tool2, rawInput, parseError } = params;
2421
2849
  if (!tool2) {
2422
2850
  const message = `Unknown tool: ${toolName}`;
2423
2851
  return {
@@ -2425,6 +2853,39 @@ async function executeToolCall(params) {
2425
2853
  outputPayload: buildToolErrorOutput(message)
2426
2854
  };
2427
2855
  }
2856
+ if (callKind === "custom") {
2857
+ if (!isCustomTool(tool2)) {
2858
+ const message = `Tool ${toolName} was called as custom_tool_call but is declared as function.`;
2859
+ const outputPayload = buildToolErrorOutput(message);
2860
+ return {
2861
+ result: { toolName, input: rawInput, output: outputPayload, error: message },
2862
+ outputPayload
2863
+ };
2864
+ }
2865
+ const input = typeof rawInput === "string" ? rawInput : String(rawInput ?? "");
2866
+ try {
2867
+ const output = await tool2.execute(input);
2868
+ return {
2869
+ result: { toolName, input, output },
2870
+ outputPayload: output
2871
+ };
2872
+ } catch (error) {
2873
+ const message = error instanceof Error ? error.message : String(error);
2874
+ const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
2875
+ return {
2876
+ result: { toolName, input, output: outputPayload, error: message },
2877
+ outputPayload
2878
+ };
2879
+ }
2880
+ }
2881
+ if (isCustomTool(tool2)) {
2882
+ const message = `Tool ${toolName} was called as function_call but is declared as custom.`;
2883
+ const outputPayload = buildToolErrorOutput(message);
2884
+ return {
2885
+ result: { toolName, input: rawInput, output: outputPayload, error: message },
2886
+ outputPayload
2887
+ };
2888
+ }
2428
2889
  if (parseError) {
2429
2890
  const message = `Invalid JSON for tool ${toolName}: ${parseError}`;
2430
2891
  return {
@@ -2481,8 +2942,12 @@ function normalizeChatGptToolIds(params) {
2481
2942
  rawItemId = nextItemId ?? rawItemId;
2482
2943
  }
2483
2944
  const callValue = sanitizeChatGptToolId(rawCallId || rawItemId || randomBytes(8).toString("hex"));
2484
- let itemValue = sanitizeChatGptToolId(rawItemId || `fc-${callValue}`);
2485
- if (!itemValue.startsWith("fc")) {
2945
+ let itemValue = sanitizeChatGptToolId(rawItemId || callValue);
2946
+ if (params.callKind === "custom") {
2947
+ if (!itemValue.startsWith("ctc")) {
2948
+ itemValue = `ctc_${itemValue}`;
2949
+ }
2950
+ } else if (!itemValue.startsWith("fc")) {
2486
2951
  itemValue = `fc-${itemValue}`;
2487
2952
  }
2488
2953
  return { callId: callValue, itemId: itemValue };
@@ -2547,7 +3012,7 @@ function extractOpenAiResponseParts(response) {
2547
3012
  }
2548
3013
  return { parts, blocked };
2549
3014
  }
2550
- function extractOpenAiFunctionCalls(output) {
3015
+ function extractOpenAiToolCalls(output) {
2551
3016
  const calls = [];
2552
3017
  if (!Array.isArray(output)) {
2553
3018
  return calls;
@@ -2556,22 +3021,78 @@ function extractOpenAiFunctionCalls(output) {
2556
3021
  if (!item || typeof item !== "object") {
2557
3022
  continue;
2558
3023
  }
2559
- if (item.type === "function_call") {
3024
+ const itemType = item.type;
3025
+ if (itemType === "function_call") {
2560
3026
  const name = typeof item.name === "string" ? item.name : "";
2561
3027
  const args = typeof item.arguments === "string" ? item.arguments : "";
2562
3028
  const call_id = typeof item.call_id === "string" ? item.call_id : "";
2563
3029
  const id = typeof item.id === "string" ? item.id : void 0;
2564
3030
  if (name && call_id) {
2565
- calls.push({ name, arguments: args, call_id, id });
3031
+ calls.push({ kind: "function", name, arguments: args, call_id, id });
3032
+ }
3033
+ continue;
3034
+ }
3035
+ if (itemType === "custom_tool_call") {
3036
+ const name = typeof item.name === "string" ? item.name : "";
3037
+ const input = typeof item.input === "string" ? item.input : "";
3038
+ const call_id = typeof item.call_id === "string" ? item.call_id : "";
3039
+ const id = typeof item.id === "string" ? item.id : void 0;
3040
+ if (name && call_id) {
3041
+ calls.push({ kind: "custom", name, input, call_id, id });
2566
3042
  }
2567
3043
  }
2568
3044
  }
2569
3045
  return calls;
2570
3046
  }
3047
+ function extractFireworksMessageText(message) {
3048
+ if (!message || typeof message !== "object") {
3049
+ return "";
3050
+ }
3051
+ const content = message.content;
3052
+ if (typeof content === "string") {
3053
+ return content;
3054
+ }
3055
+ if (!Array.isArray(content)) {
3056
+ return "";
3057
+ }
3058
+ let text = "";
3059
+ for (const part of content) {
3060
+ const textPart = part.text;
3061
+ if (typeof textPart === "string") {
3062
+ text += textPart;
3063
+ }
3064
+ }
3065
+ return text;
3066
+ }
3067
+ function extractFireworksToolCalls(message) {
3068
+ if (!message || typeof message !== "object") {
3069
+ return [];
3070
+ }
3071
+ const toolCalls = message.tool_calls;
3072
+ if (!Array.isArray(toolCalls)) {
3073
+ return [];
3074
+ }
3075
+ const calls = [];
3076
+ for (const call of toolCalls) {
3077
+ if (!call || typeof call !== "object") {
3078
+ continue;
3079
+ }
3080
+ const id = typeof call.id === "string" ? call.id : "";
3081
+ const fn = call.function;
3082
+ const name = fn && typeof fn === "object" && typeof fn.name === "string" ? fn.name ?? "" : "";
3083
+ const args = fn && typeof fn === "object" && typeof fn.arguments === "string" ? fn.arguments ?? "" : "";
3084
+ if (id && name) {
3085
+ calls.push({ id, name, arguments: args });
3086
+ }
3087
+ }
3088
+ return calls;
3089
+ }
2571
3090
  function resolveGeminiThinkingConfig(modelId) {
2572
3091
  switch (modelId) {
2573
3092
  case "gemini-3-pro-preview":
2574
3093
  return { includeThoughts: true };
3094
+ case "gemini-3-flash-preview":
3095
+ return { includeThoughts: true, thinkingBudget: 16384 };
2575
3096
  case "gemini-2.5-pro":
2576
3097
  return { includeThoughts: true, thinkingBudget: 32768 };
2577
3098
  case "gemini-flash-latest":
@@ -2747,7 +3268,7 @@ async function runTextCall(params) {
2747
3268
  };
2748
3269
  let sawResponseDelta = false;
2749
3270
  let sawThoughtDelta = false;
2750
- const result = await collectChatGptCodexResponse({
3271
+ const result = await collectChatGptCodexResponseWithRetry({
2751
3272
  request: requestPayload,
2752
3273
  signal,
2753
3274
  onDelta: (delta) => {
@@ -2778,6 +3299,47 @@ async function runTextCall(params) {
2778
3299
  if (!sawResponseDelta && fallbackText.length > 0) {
2779
3300
  pushDelta("response", fallbackText);
2780
3301
  }
3302
+ } else if (provider === "fireworks") {
3303
+ if (request.tools && request.tools.length > 0) {
3304
+ throw new Error(
3305
+ "Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
3306
+ );
3307
+ }
3308
+ const fireworksMessages = toFireworksMessages(contents, {
3309
+ responseMimeType: request.responseMimeType,
3310
+ responseJsonSchema: request.responseJsonSchema
3311
+ });
3312
+ await runFireworksCall(async (client) => {
3313
+ const responseFormat = request.responseJsonSchema ? {
3314
+ type: "json_schema",
3315
+ json_schema: {
3316
+ name: "llm-response",
3317
+ schema: request.responseJsonSchema
3318
+ }
3319
+ } : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
3320
+ const response = await client.chat.completions.create(
3321
+ {
3322
+ model: modelForProvider,
3323
+ messages: fireworksMessages,
3324
+ ...responseFormat ? { response_format: responseFormat } : {}
3325
+ },
3326
+ { signal }
3327
+ );
3328
+ modelVersion = typeof response.model === "string" ? response.model : request.model;
3329
+ queue.push({ type: "model", modelVersion });
3330
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
3331
+ if (choice?.finish_reason === "content_filter") {
3332
+ blocked = true;
3333
+ queue.push({ type: "blocked" });
3334
+ }
3335
+ const textOutput = extractFireworksMessageText(
3336
+ choice?.message
3337
+ );
3338
+ if (textOutput.length > 0) {
3339
+ pushDelta("response", textOutput);
3340
+ }
3341
+ latestUsage = extractFireworksUsageTokens(response.usage);
3342
+ });
2781
3343
  } else {
2782
3344
  const geminiContents = contents.map(convertLlmContentToGeminiContent);
2783
3345
  const config = {
@@ -2902,11 +3464,12 @@ function buildJsonSchemaConfig(request) {
2902
3464
  const schemaName = (request.openAiSchemaName ?? "llm-response").trim() || "llm-response";
2903
3465
  const providerInfo = resolveProvider(request.model);
2904
3466
  const isOpenAiVariant = providerInfo.provider === "openai" || providerInfo.provider === "chatgpt";
3467
+ const isGeminiVariant = providerInfo.provider === "gemini";
2905
3468
  const baseJsonSchema = zodToJsonSchema(request.schema, {
2906
3469
  name: schemaName,
2907
3470
  target: isOpenAiVariant ? "openAi" : "jsonSchema7"
2908
3471
  });
2909
- const responseJsonSchema = isOpenAiVariant ? resolveOpenAiSchemaRoot(baseJsonSchema) : addGeminiPropertyOrdering(baseJsonSchema);
3472
+ const responseJsonSchema = isOpenAiVariant ? resolveOpenAiSchemaRoot(baseJsonSchema) : isGeminiVariant ? addGeminiPropertyOrdering(baseJsonSchema) : resolveOpenAiSchemaRoot(baseJsonSchema);
2910
3473
  if (isOpenAiVariant && !isJsonSchemaObject(responseJsonSchema)) {
2911
3474
  throw new Error("OpenAI structured outputs require a JSON object schema at the root.");
2912
3475
  }
@@ -3073,15 +3636,46 @@ var DEFAULT_TOOL_LOOP_MAX_STEPS = 8;
3073
3636
  function resolveToolLoopContents(input) {
3074
3637
  return resolveTextContents(input);
3075
3638
  }
3076
- function buildOpenAiFunctionTools(tools) {
3639
+ function isCustomTool(toolDef) {
3640
+ return toolDef.type === "custom";
3641
+ }
3642
+ function buildOpenAiToolsFromToolSet(tools) {
3077
3643
  const toolEntries = Object.entries(tools);
3078
- return toolEntries.map(([name, toolDef]) => ({
3079
- type: "function",
3080
- name,
3081
- description: toolDef.description ?? void 0,
3082
- parameters: buildOpenAiToolSchema(toolDef.inputSchema, name),
3083
- strict: true
3084
- }));
3644
+ return toolEntries.map(([name, toolDef]) => {
3645
+ if (isCustomTool(toolDef)) {
3646
+ return {
3647
+ type: "custom",
3648
+ name,
3649
+ description: toolDef.description ?? void 0,
3650
+ ...toolDef.format ? { format: toolDef.format } : {}
3651
+ };
3652
+ }
3653
+ return {
3654
+ type: "function",
3655
+ name,
3656
+ description: toolDef.description ?? void 0,
3657
+ parameters: buildOpenAiToolSchema(toolDef.inputSchema, name),
3658
+ strict: true
3659
+ };
3660
+ });
3661
+ }
3662
+ function buildFireworksToolsFromToolSet(tools) {
3663
+ const toolEntries = Object.entries(tools);
3664
+ return toolEntries.map(([name, toolDef]) => {
3665
+ if (isCustomTool(toolDef)) {
3666
+ throw new Error(
3667
+ `Fireworks provider does not support custom/freeform tools (${name}). Use JSON function tools instead.`
3668
+ );
3669
+ }
3670
+ return {
3671
+ type: "function",
3672
+ function: {
3673
+ name,
3674
+ description: toolDef.description ?? void 0,
3675
+ parameters: buildOpenAiToolSchema(toolDef.inputSchema, name)
3676
+ }
3677
+ };
3678
+ });
3085
3679
  }
3086
3680
  function buildOpenAiToolSchema(schema, name) {
3087
3681
  const rawSchema = zodToJsonSchema(schema, { name, target: "openAi" });
@@ -3093,11 +3687,18 @@ function buildOpenAiToolSchema(schema, name) {
3093
3687
  }
3094
3688
  function buildGeminiFunctionDeclarations(tools) {
3095
3689
  const toolEntries = Object.entries(tools);
3096
- const functionDeclarations = toolEntries.map(([name, toolDef]) => ({
3097
- name,
3098
- description: toolDef.description ?? "",
3099
- parametersJsonSchema: buildGeminiToolSchema(toolDef.inputSchema, name)
3100
- }));
3690
+ const functionDeclarations = toolEntries.map(([name, toolDef]) => {
3691
+ if (isCustomTool(toolDef)) {
3692
+ throw new Error(
3693
+ `Gemini provider does not support custom/freeform tools (${name}). Use JSON function tools instead.`
3694
+ );
3695
+ }
3696
+ return {
3697
+ name,
3698
+ description: toolDef.description ?? "",
3699
+ parametersJsonSchema: buildGeminiToolSchema(toolDef.inputSchema, name)
3700
+ };
3701
+ });
3101
3702
  return [{ functionDeclarations }];
3102
3703
  }
3103
3704
  function buildGeminiToolSchema(schema, name) {
@@ -3158,9 +3759,9 @@ async function runToolLoop(request) {
3158
3759
  let finalText = "";
3159
3760
  let finalThoughts = "";
3160
3761
  if (providerInfo.provider === "openai") {
3161
- const openAiFunctionTools = buildOpenAiFunctionTools(request.tools);
3762
+ const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
3162
3763
  const openAiNativeTools = toOpenAiTools(request.modelTools);
3163
- const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiFunctionTools] : [...openAiFunctionTools];
3764
+ const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
3164
3765
  const reasoningEffort = resolveOpenAiReasoningEffort(
3165
3766
  providerInfo.model,
3166
3767
  request.openAiReasoningEffort
@@ -3252,9 +3853,9 @@ async function runToolLoop(request) {
3252
3853
  if (usageTokens) {
3253
3854
  emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
3254
3855
  }
3255
- const functionCalls = extractOpenAiFunctionCalls(finalResponse.output);
3856
+ const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
3256
3857
  const stepToolCalls = [];
3257
- if (functionCalls.length === 0) {
3858
+ if (responseToolCalls.length === 0) {
3258
3859
  finalText = responseText;
3259
3860
  finalThoughts = reasoningSummary;
3260
3861
  steps.push({
@@ -3268,10 +3869,21 @@ async function runToolLoop(request) {
3268
3869
  });
3269
3870
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
3270
3871
  }
3271
- const callInputs = functionCalls.map((call, index) => {
3872
+ const callInputs = responseToolCalls.map((call, index) => {
3272
3873
  const toolIndex = index + 1;
3273
3874
  const toolId = buildToolLogId(turn, toolIndex);
3274
3875
  const toolName = call.name;
3876
+ if (call.kind === "custom") {
3877
+ return {
3878
+ call,
3879
+ toolName,
3880
+ value: call.input,
3881
+ parseError: void 0,
3882
+ toolId,
3883
+ turn,
3884
+ toolIndex
3885
+ };
3886
+ }
3275
3887
  const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
3276
3888
  return { call, toolName, value, parseError, toolId, turn, toolIndex };
3277
3889
  });
@@ -3286,6 +3898,7 @@ async function runToolLoop(request) {
3286
3898
  },
3287
3899
  async () => {
3288
3900
  const { result, outputPayload } = await executeToolCall({
3901
+ callKind: entry.call.kind,
3289
3902
  toolName: entry.toolName,
3290
3903
  tool: request.tools[entry.toolName],
3291
3904
  rawInput: entry.value,
@@ -3299,11 +3912,19 @@ async function runToolLoop(request) {
3299
3912
  const toolOutputs = [];
3300
3913
  for (const { entry, result, outputPayload } of callResults) {
3301
3914
  stepToolCalls.push({ ...result, callId: entry.call.call_id });
3302
- toolOutputs.push({
3303
- type: "function_call_output",
3304
- call_id: entry.call.call_id,
3305
- output: mergeToolOutput(outputPayload)
3306
- });
3915
+ if (entry.call.kind === "custom") {
3916
+ toolOutputs.push({
3917
+ type: "custom_tool_call_output",
3918
+ call_id: entry.call.call_id,
3919
+ output: mergeToolOutput(outputPayload)
3920
+ });
3921
+ } else {
3922
+ toolOutputs.push({
3923
+ type: "function_call_output",
3924
+ call_id: entry.call.call_id,
3925
+ output: mergeToolOutput(outputPayload)
3926
+ });
3927
+ }
3307
3928
  }
3308
3929
  steps.push({
3309
3930
  step: steps.length + 1,
@@ -3320,24 +3941,28 @@ async function runToolLoop(request) {
3320
3941
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
3321
3942
  }
3322
3943
  if (providerInfo.provider === "chatgpt") {
3323
- const openAiFunctionTools = buildOpenAiFunctionTools(request.tools);
3944
+ const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
3324
3945
  const openAiNativeTools = toOpenAiTools(request.modelTools);
3325
- const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiFunctionTools] : [...openAiFunctionTools];
3946
+ const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
3326
3947
  const reasoningEffort = resolveOpenAiReasoningEffort(
3327
3948
  request.model,
3328
3949
  request.openAiReasoningEffort
3329
3950
  );
3330
3951
  const toolLoopInput = toChatGptInput(contents);
3952
+ const conversationId = `tool-loop-${randomBytes(8).toString("hex")}`;
3953
+ const promptCacheKey = conversationId;
3331
3954
  let input = [...toolLoopInput.input];
3332
3955
  for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
3333
3956
  const turn = stepIndex + 1;
3334
- const response = await collectChatGptCodexResponse({
3957
+ const response = await collectChatGptCodexResponseWithRetry({
3958
+ sessionId: conversationId,
3335
3959
  request: {
3336
3960
  model: providerInfo.model,
3337
3961
  store: false,
3338
3962
  stream: true,
3339
3963
  instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
3340
3964
  input,
3965
+ prompt_cache_key: promptCacheKey,
3341
3966
  include: ["reasoning.encrypted_content"],
3342
3967
  tools: openAiTools,
3343
3968
  tool_choice: "auto",
@@ -3368,8 +3993,8 @@ async function runToolLoop(request) {
3368
3993
  totalCostUsd += stepCostUsd;
3369
3994
  const responseText = (response.text ?? "").trim();
3370
3995
  const reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
3371
- const functionCalls = response.toolCalls ?? [];
3372
- if (functionCalls.length === 0) {
3996
+ const responseToolCalls = response.toolCalls ?? [];
3997
+ if (responseToolCalls.length === 0) {
3373
3998
  finalText = responseText;
3374
3999
  finalThoughts = reasoningSummaryText;
3375
4000
  steps.push({
@@ -3385,12 +4010,16 @@ async function runToolLoop(request) {
3385
4010
  }
3386
4011
  const toolCalls = [];
3387
4012
  const toolOutputs = [];
3388
- const callInputs = functionCalls.map((call, index) => {
4013
+ const callInputs = responseToolCalls.map((call, index) => {
3389
4014
  const toolIndex = index + 1;
3390
4015
  const toolId = buildToolLogId(turn, toolIndex);
3391
4016
  const toolName = call.name;
3392
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
3393
- const ids = normalizeChatGptToolIds({ callId: call.callId, itemId: call.id });
4017
+ const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
4018
+ const ids = normalizeChatGptToolIds({
4019
+ callKind: call.kind,
4020
+ callId: call.callId,
4021
+ itemId: call.id
4022
+ });
3394
4023
  return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
3395
4024
  });
3396
4025
  const callResults = await Promise.all(
@@ -3404,6 +4033,7 @@ async function runToolLoop(request) {
3404
4033
  },
3405
4034
  async () => {
3406
4035
  const { result, outputPayload } = await executeToolCall({
4036
+ callKind: entry.call.kind,
3407
4037
  toolName: entry.toolName,
3408
4038
  tool: request.tools[entry.toolName],
3409
4039
  rawInput: entry.value,
@@ -3416,19 +4046,35 @@ async function runToolLoop(request) {
3416
4046
  );
3417
4047
  for (const { entry, result, outputPayload } of callResults) {
3418
4048
  toolCalls.push({ ...result, callId: entry.ids.callId });
3419
- toolOutputs.push({
3420
- type: "function_call",
3421
- id: entry.ids.itemId,
3422
- call_id: entry.ids.callId,
3423
- name: entry.toolName,
3424
- arguments: entry.call.arguments,
3425
- status: "completed"
3426
- });
3427
- toolOutputs.push({
3428
- type: "function_call_output",
3429
- call_id: entry.ids.callId,
3430
- output: mergeToolOutput(outputPayload)
3431
- });
4049
+ if (entry.call.kind === "custom") {
4050
+ toolOutputs.push({
4051
+ type: "custom_tool_call",
4052
+ id: entry.ids.itemId,
4053
+ call_id: entry.ids.callId,
4054
+ name: entry.toolName,
4055
+ input: entry.call.input,
4056
+ status: "completed"
4057
+ });
4058
+ toolOutputs.push({
4059
+ type: "custom_tool_call_output",
4060
+ call_id: entry.ids.callId,
4061
+ output: mergeToolOutput(outputPayload)
4062
+ });
4063
+ } else {
4064
+ toolOutputs.push({
4065
+ type: "function_call",
4066
+ id: entry.ids.itemId,
4067
+ call_id: entry.ids.callId,
4068
+ name: entry.toolName,
4069
+ arguments: entry.call.arguments,
4070
+ status: "completed"
4071
+ });
4072
+ toolOutputs.push({
4073
+ type: "function_call_output",
4074
+ call_id: entry.ids.callId,
4075
+ output: mergeToolOutput(outputPayload)
4076
+ });
4077
+ }
3432
4078
  }
3433
4079
  steps.push({
3434
4080
  step: steps.length + 1,
@@ -3443,6 +4089,134 @@ async function runToolLoop(request) {
3443
4089
  }
3444
4090
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
3445
4091
  }
4092
+ if (providerInfo.provider === "fireworks") {
4093
+ if (request.modelTools && request.modelTools.length > 0) {
4094
+ throw new Error(
4095
+ "Fireworks provider does not support provider-native modelTools in runToolLoop."
4096
+ );
4097
+ }
4098
+ const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
4099
+ const messages = toFireworksMessages(contents);
4100
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
4101
+ const turn = stepIndex + 1;
4102
+ const response = await runFireworksCall(async (client) => {
4103
+ return await client.chat.completions.create(
4104
+ {
4105
+ model: providerInfo.model,
4106
+ messages,
4107
+ tools: fireworksTools,
4108
+ tool_choice: "auto",
4109
+ parallel_tool_calls: true
4110
+ },
4111
+ { signal: request.signal }
4112
+ );
4113
+ });
4114
+ const modelVersion = typeof response.model === "string" ? response.model : request.model;
4115
+ request.onEvent?.({ type: "model", modelVersion });
4116
+ const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
4117
+ if (choice?.finish_reason === "content_filter") {
4118
+ request.onEvent?.({ type: "blocked" });
4119
+ }
4120
+ const message = choice?.message;
4121
+ const responseText = extractFireworksMessageText(message).trim();
4122
+ if (responseText.length > 0) {
4123
+ request.onEvent?.({ type: "delta", channel: "response", text: responseText });
4124
+ }
4125
+ const usageTokens = extractFireworksUsageTokens(response.usage);
4126
+ const stepCostUsd = estimateCallCostUsd({
4127
+ modelId: modelVersion,
4128
+ tokens: usageTokens,
4129
+ responseImages: 0
4130
+ });
4131
+ totalCostUsd += stepCostUsd;
4132
+ if (usageTokens) {
4133
+ request.onEvent?.({
4134
+ type: "usage",
4135
+ usage: usageTokens,
4136
+ costUsd: stepCostUsd,
4137
+ modelVersion
4138
+ });
4139
+ }
4140
+ const responseToolCalls = extractFireworksToolCalls(message);
4141
+ if (responseToolCalls.length === 0) {
4142
+ finalText = responseText;
4143
+ finalThoughts = "";
4144
+ steps.push({
4145
+ step: steps.length + 1,
4146
+ modelVersion,
4147
+ text: responseText || void 0,
4148
+ thoughts: void 0,
4149
+ toolCalls: [],
4150
+ usage: usageTokens,
4151
+ costUsd: stepCostUsd
4152
+ });
4153
+ return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
4154
+ }
4155
+ const stepToolCalls = [];
4156
+ const callInputs = responseToolCalls.map((call, index) => {
4157
+ const toolIndex = index + 1;
4158
+ const toolId = buildToolLogId(turn, toolIndex);
4159
+ const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
4160
+ return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
4161
+ });
4162
+ const callResults = await Promise.all(
4163
+ callInputs.map(async (entry) => {
4164
+ return await toolCallContextStorage.run(
4165
+ {
4166
+ toolName: entry.toolName,
4167
+ toolId: entry.toolId,
4168
+ turn: entry.turn,
4169
+ toolIndex: entry.toolIndex
4170
+ },
4171
+ async () => {
4172
+ const { result, outputPayload } = await executeToolCall({
4173
+ callKind: "function",
4174
+ toolName: entry.toolName,
4175
+ tool: request.tools[entry.toolName],
4176
+ rawInput: entry.value,
4177
+ parseError: entry.parseError
4178
+ });
4179
+ return { entry, result, outputPayload };
4180
+ }
4181
+ );
4182
+ })
4183
+ );
4184
+ const assistantToolCalls = [];
4185
+ const toolMessages = [];
4186
+ for (const { entry, result, outputPayload } of callResults) {
4187
+ stepToolCalls.push({ ...result, callId: entry.call.id });
4188
+ assistantToolCalls.push({
4189
+ id: entry.call.id,
4190
+ type: "function",
4191
+ function: {
4192
+ name: entry.toolName,
4193
+ arguments: entry.call.arguments
4194
+ }
4195
+ });
4196
+ toolMessages.push({
4197
+ role: "tool",
4198
+ tool_call_id: entry.call.id,
4199
+ content: mergeToolOutput(outputPayload)
4200
+ });
4201
+ }
4202
+ steps.push({
4203
+ step: steps.length + 1,
4204
+ modelVersion,
4205
+ text: responseText || void 0,
4206
+ thoughts: void 0,
4207
+ toolCalls: stepToolCalls,
4208
+ usage: usageTokens,
4209
+ costUsd: stepCostUsd
4210
+ });
4211
+ messages.push({
4212
+ role: "assistant",
4213
+ ...responseText.length > 0 ? { content: responseText } : {},
4214
+ tool_calls: assistantToolCalls
4215
+ });
4216
+ messages.push(...toolMessages);
4217
+ }
4218
+ throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
4219
+ }
3446
4220
  const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
3447
4221
  const geminiNativeTools = toGeminiTools(request.modelTools);
3448
4222
  const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
@@ -3592,6 +4366,7 @@ async function runToolLoop(request) {
3592
4366
  },
3593
4367
  async () => {
3594
4368
  const { result, outputPayload } = await executeToolCall({
4369
+ callKind: "function",
3595
4370
  toolName: entry.toolName,
3596
4371
  tool: request.tools[entry.toolName],
3597
4372
  rawInput: entry.rawInput
@@ -3871,26 +4646,26 @@ ${lines}`;
3871
4646
  }
3872
4647
 
3873
4648
  // src/tools/filesystemTools.ts
3874
- import path4 from "path";
4649
+ import path5 from "path";
3875
4650
  import { z as z5 } from "zod";
3876
4651
 
3877
4652
  // src/tools/applyPatch.ts
3878
- import path3 from "path";
4653
+ import path4 from "path";
3879
4654
  import { z as z4 } from "zod";
3880
4655
 
3881
4656
  // src/tools/filesystem.ts
3882
- import { promises as fs2 } from "fs";
3883
- import path2 from "path";
4657
+ import { promises as fs3 } from "fs";
4658
+ import path3 from "path";
3884
4659
  var InMemoryAgentFilesystem = class {
3885
4660
  #files = /* @__PURE__ */ new Map();
3886
4661
  #dirs = /* @__PURE__ */ new Map();
3887
4662
  #clock = 0;
3888
4663
  constructor(initialFiles = {}) {
3889
- const root = path2.resolve("/");
4664
+ const root = path3.resolve("/");
3890
4665
  this.#dirs.set(root, { mtimeMs: this.#nextMtime() });
3891
4666
  for (const [filePath, content] of Object.entries(initialFiles)) {
3892
- const absolutePath = path2.resolve(filePath);
3893
- this.#ensureDirSync(path2.dirname(absolutePath));
4667
+ const absolutePath = path3.resolve(filePath);
4668
+ this.#ensureDirSync(path3.dirname(absolutePath));
3894
4669
  this.#files.set(absolutePath, {
3895
4670
  content,
3896
4671
  mtimeMs: this.#nextMtime()
@@ -3898,7 +4673,7 @@ var InMemoryAgentFilesystem = class {
3898
4673
  }
3899
4674
  }
3900
4675
  async readTextFile(filePath) {
3901
- const absolutePath = path2.resolve(filePath);
4676
+ const absolutePath = path3.resolve(filePath);
3902
4677
  const file = this.#files.get(absolutePath);
3903
4678
  if (!file) {
3904
4679
  throw createNoSuchFileError("open", absolutePath);
@@ -3906,24 +4681,24 @@ var InMemoryAgentFilesystem = class {
3906
4681
  return file.content;
3907
4682
  }
3908
4683
  async writeTextFile(filePath, content) {
3909
- const absolutePath = path2.resolve(filePath);
3910
- const parentPath = path2.dirname(absolutePath);
4684
+ const absolutePath = path3.resolve(filePath);
4685
+ const parentPath = path3.dirname(absolutePath);
3911
4686
  if (!this.#dirs.has(parentPath)) {
3912
4687
  throw createNoSuchFileError("open", parentPath);
3913
4688
  }
3914
4689
  this.#files.set(absolutePath, { content, mtimeMs: this.#nextMtime() });
3915
4690
  }
3916
4691
  async deleteFile(filePath) {
3917
- const absolutePath = path2.resolve(filePath);
4692
+ const absolutePath = path3.resolve(filePath);
3918
4693
  if (!this.#files.delete(absolutePath)) {
3919
4694
  throw createNoSuchFileError("unlink", absolutePath);
3920
4695
  }
3921
4696
  }
3922
4697
  async ensureDir(directoryPath) {
3923
- this.#ensureDirSync(path2.resolve(directoryPath));
4698
+ this.#ensureDirSync(path3.resolve(directoryPath));
3924
4699
  }
3925
4700
  async readDir(directoryPath) {
3926
- const absolutePath = path2.resolve(directoryPath);
4701
+ const absolutePath = path3.resolve(directoryPath);
3927
4702
  const directory = this.#dirs.get(absolutePath);
3928
4703
  if (!directory) {
3929
4704
  throw createNoSuchFileError("scandir", absolutePath);
@@ -3934,10 +4709,10 @@ var InMemoryAgentFilesystem = class {
3934
4709
  if (dirPath === absolutePath) {
3935
4710
  continue;
3936
4711
  }
3937
- if (path2.dirname(dirPath) !== absolutePath) {
4712
+ if (path3.dirname(dirPath) !== absolutePath) {
3938
4713
  continue;
3939
4714
  }
3940
- const name = path2.basename(dirPath);
4715
+ const name = path3.basename(dirPath);
3941
4716
  if (seenNames.has(name)) {
3942
4717
  continue;
3943
4718
  }
@@ -3950,10 +4725,10 @@ var InMemoryAgentFilesystem = class {
3950
4725
  });
3951
4726
  }
3952
4727
  for (const [filePath, fileRecord] of this.#files.entries()) {
3953
- if (path2.dirname(filePath) !== absolutePath) {
4728
+ if (path3.dirname(filePath) !== absolutePath) {
3954
4729
  continue;
3955
4730
  }
3956
- const name = path2.basename(filePath);
4731
+ const name = path3.basename(filePath);
3957
4732
  if (seenNames.has(name)) {
3958
4733
  continue;
3959
4734
  }
@@ -3969,7 +4744,7 @@ var InMemoryAgentFilesystem = class {
3969
4744
  return entries;
3970
4745
  }
3971
4746
  async stat(entryPath) {
3972
- const absolutePath = path2.resolve(entryPath);
4747
+ const absolutePath = path3.resolve(entryPath);
3973
4748
  const file = this.#files.get(absolutePath);
3974
4749
  if (file) {
3975
4750
  return { kind: "file", mtimeMs: file.mtimeMs };
@@ -3985,7 +4760,7 @@ var InMemoryAgentFilesystem = class {
3985
4760
  return Object.fromEntries(entries.map(([filePath, record]) => [filePath, record.content]));
3986
4761
  }
3987
4762
  #ensureDirSync(directoryPath) {
3988
- const absolutePath = path2.resolve(directoryPath);
4763
+ const absolutePath = path3.resolve(directoryPath);
3989
4764
  const parts = [];
3990
4765
  let cursor = absolutePath;
3991
4766
  for (; ; ) {
@@ -3993,7 +4768,7 @@ var InMemoryAgentFilesystem = class {
3993
4768
  break;
3994
4769
  }
3995
4770
  parts.push(cursor);
3996
- const parent = path2.dirname(cursor);
4771
+ const parent = path3.dirname(cursor);
3997
4772
  if (parent === cursor) {
3998
4773
  break;
3999
4774
  }
@@ -4016,18 +4791,18 @@ var InMemoryAgentFilesystem = class {
4016
4791
  };
4017
4792
  function createNodeAgentFilesystem() {
4018
4793
  return {
4019
- readTextFile: async (filePath) => fs2.readFile(filePath, "utf8"),
4020
- writeTextFile: async (filePath, content) => fs2.writeFile(filePath, content, "utf8"),
4021
- deleteFile: async (filePath) => fs2.unlink(filePath),
4794
+ readTextFile: async (filePath) => fs3.readFile(filePath, "utf8"),
4795
+ writeTextFile: async (filePath, content) => fs3.writeFile(filePath, content, "utf8"),
4796
+ deleteFile: async (filePath) => fs3.unlink(filePath),
4022
4797
  ensureDir: async (directoryPath) => {
4023
- await fs2.mkdir(directoryPath, { recursive: true });
4798
+ await fs3.mkdir(directoryPath, { recursive: true });
4024
4799
  },
4025
4800
  readDir: async (directoryPath) => {
4026
- const entries = await fs2.readdir(directoryPath, { withFileTypes: true });
4801
+ const entries = await fs3.readdir(directoryPath, { withFileTypes: true });
4027
4802
  const result = [];
4028
4803
  for (const entry of entries) {
4029
- const entryPath = path2.resolve(directoryPath, entry.name);
4030
- const stats = await fs2.lstat(entryPath);
4804
+ const entryPath = path3.resolve(directoryPath, entry.name);
4805
+ const stats = await fs3.lstat(entryPath);
4031
4806
  result.push({
4032
4807
  name: entry.name,
4033
4808
  path: entryPath,
@@ -4038,7 +4813,7 @@ function createNodeAgentFilesystem() {
4038
4813
  return result;
4039
4814
  },
4040
4815
  stat: async (entryPath) => {
4041
- const stats = await fs2.lstat(entryPath);
4816
+ const stats = await fs3.lstat(entryPath);
4042
4817
  return {
4043
4818
  kind: statsToKind(stats),
4044
4819
  mtimeMs: stats.mtimeMs
@@ -4080,12 +4855,104 @@ var UPDATE_FILE_PREFIX = "*** Update File: ";
4080
4855
  var MOVE_TO_PREFIX = "*** Move to: ";
4081
4856
  var END_OF_FILE_LINE = "*** End of File";
4082
4857
  var DEFAULT_MAX_PATCH_BYTES = 1024 * 1024;
4858
+ var CODEX_APPLY_PATCH_INPUT_DESCRIPTION = "The entire contents of the apply_patch command";
4859
+ var CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION = "Use the `apply_patch` tool to edit files. This is a FREEFORM tool, so do not wrap the patch in JSON.";
4860
+ var CODEX_APPLY_PATCH_LARK_GRAMMAR = [
4861
+ "start: begin_patch hunk+ end_patch",
4862
+ 'begin_patch: "*** Begin Patch" LF',
4863
+ 'end_patch: "*** End Patch" LF?',
4864
+ "",
4865
+ "hunk: add_hunk | delete_hunk | update_hunk",
4866
+ 'add_hunk: "*** Add File: " filename LF add_line+',
4867
+ 'delete_hunk: "*** Delete File: " filename LF',
4868
+ 'update_hunk: "*** Update File: " filename LF change_move? change?',
4869
+ "",
4870
+ "filename: /(.+)/",
4871
+ 'add_line: "+" /(.*)/ LF -> line',
4872
+ "",
4873
+ 'change_move: "*** Move to: " filename LF',
4874
+ "change: (change_context | change_line)+ eof_line?",
4875
+ 'change_context: ("@@" | "@@ " /(.+)/) LF',
4876
+ 'change_line: ("+" | "-" | " ") /(.*)/ LF',
4877
+ 'eof_line: "*** End of File" LF',
4878
+ "",
4879
+ "%import common.LF"
4880
+ ].join("\n");
4881
+ var CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION = [
4882
+ "Use the `apply_patch` tool to edit files.",
4883
+ "Your patch language is a stripped\u2011down, file\u2011oriented diff format designed to be easy to parse and safe to apply. You can think of it as a high\u2011level envelope:",
4884
+ "",
4885
+ "*** Begin Patch",
4886
+ "[ one or more file sections ]",
4887
+ "*** End Patch",
4888
+ "",
4889
+ "Within that envelope, you get a sequence of file operations.",
4890
+ "You MUST include a header to specify the action you are taking.",
4891
+ "Each operation starts with one of three headers:",
4892
+ "",
4893
+ "*** Add File: <path> - create a new file. Every following line is a + line (the initial contents).",
4894
+ "*** Delete File: <path> - remove an existing file. Nothing follows.",
4895
+ "*** Update File: <path> - patch an existing file in place (optionally with a rename).",
4896
+ "",
4897
+ "May be immediately followed by *** Move to: <new path> if you want to rename the file.",
4898
+ "Then one or more \u201Chunks\u201D, each introduced by @@ (optionally followed by a hunk header).",
4899
+ "Within a hunk each line starts with:",
4900
+ "",
4901
+ "For instructions on [context_before] and [context_after]:",
4902
+ "- By default, show 3 lines of code immediately above and 3 lines immediately below each change. If a change is within 3 lines of a previous change, do NOT duplicate the first change\u2019s [context_after] lines in the second change\u2019s [context_before] lines.",
4903
+ "- If 3 lines of context is insufficient to uniquely identify the snippet of code within the file, use the @@ operator to indicate the class or function to which the snippet belongs. For instance, we might have:",
4904
+ "@@ class BaseClass",
4905
+ "[3 lines of pre-context]",
4906
+ "- [old_code]",
4907
+ "+ [new_code]",
4908
+ "[3 lines of post-context]",
4909
+ "",
4910
+ "- If a code block is repeated so many times in a class or function such that even a single `@@` statement and 3 lines of context cannot uniquely identify the snippet of code, you can use multiple `@@` statements to jump to the right context. For instance:",
4911
+ "",
4912
+ "@@ class BaseClass",
4913
+ "@@ def method():",
4914
+ "[3 lines of pre-context]",
4915
+ "- [old_code]",
4916
+ "+ [new_code]",
4917
+ "[3 lines of post-context]",
4918
+ "",
4919
+ "The full grammar definition is below:",
4920
+ "Patch := Begin { FileOp } End",
4921
+ 'Begin := "*** Begin Patch" NEWLINE',
4922
+ 'End := "*** End Patch" NEWLINE',
4923
+ "FileOp := AddFile | DeleteFile | UpdateFile",
4924
+ 'AddFile := "*** Add File: " path NEWLINE { "+" line NEWLINE }',
4925
+ 'DeleteFile := "*** Delete File: " path NEWLINE',
4926
+ 'UpdateFile := "*** Update File: " path NEWLINE [ MoveTo ] { Hunk }',
4927
+ 'MoveTo := "*** Move to: " newPath NEWLINE',
4928
+ 'Hunk := "@@" [ header ] NEWLINE { HunkLine } [ "*** End of File" NEWLINE ]',
4929
+ 'HunkLine := (" " | "-" | "+") text NEWLINE',
4930
+ "",
4931
+ "A full patch can combine several operations:",
4932
+ "",
4933
+ "*** Begin Patch",
4934
+ "*** Add File: hello.txt",
4935
+ "+Hello world",
4936
+ "*** Update File: src/app.py",
4937
+ "*** Move to: src/main.py",
4938
+ "@@ def greet():",
4939
+ '-print("Hi")',
4940
+ '+print("Hello, world!")',
4941
+ "*** Delete File: obsolete.txt",
4942
+ "*** End Patch",
4943
+ "",
4944
+ "It is important to remember:",
4945
+ "",
4946
+ "- You must include a header with your intended action (Add/Delete/Update)",
4947
+ "- You must prefix new lines with `+` even when creating a new file",
4948
+ "- File references can only be relative, NEVER ABSOLUTE."
4949
+ ].join("\n");
4083
4950
  var applyPatchToolInputSchema = z4.object({
4084
- input: z4.string().min(1).describe("The entire apply_patch payload, including Begin/End markers.")
4951
+ input: z4.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
4085
4952
  });
4086
4953
  function createApplyPatchTool(options = {}) {
4087
4954
  return tool({
4088
- description: options.description ?? "Apply edits using a Codex-style apply_patch payload with Begin/End markers.",
4955
+ description: options.description ?? CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION,
4089
4956
  inputSchema: applyPatchToolInputSchema,
4090
4957
  execute: async ({ input }) => applyPatch({
4091
4958
  patch: input,
@@ -4098,7 +4965,7 @@ function createApplyPatchTool(options = {}) {
4098
4965
  });
4099
4966
  }
4100
4967
  async function applyPatch(request) {
4101
- const cwd = path3.resolve(request.cwd ?? process.cwd());
4968
+ const cwd = path4.resolve(request.cwd ?? process.cwd());
4102
4969
  const adapter = request.fs ?? createNodeAgentFilesystem();
4103
4970
  const allowOutsideCwd = request.allowOutsideCwd === true;
4104
4971
  const patchBytes = Buffer.byteLength(request.patch, "utf8");
@@ -4120,7 +4987,7 @@ async function applyPatch(request) {
4120
4987
  kind: "add",
4121
4988
  path: absolutePath2
4122
4989
  });
4123
- await adapter.ensureDir(path3.dirname(absolutePath2));
4990
+ await adapter.ensureDir(path4.dirname(absolutePath2));
4124
4991
  await adapter.writeTextFile(absolutePath2, operation.content);
4125
4992
  added.push(toDisplayPath(absolutePath2, cwd));
4126
4993
  continue;
@@ -4154,7 +5021,7 @@ async function applyPatch(request) {
4154
5021
  fromPath: absolutePath,
4155
5022
  toPath: destinationPath
4156
5023
  });
4157
- await adapter.ensureDir(path3.dirname(destinationPath));
5024
+ await adapter.ensureDir(path4.dirname(destinationPath));
4158
5025
  await adapter.writeTextFile(destinationPath, next);
4159
5026
  await adapter.deleteFile(absolutePath);
4160
5027
  modified.push(toDisplayPath(destinationPath, cwd));
@@ -4185,22 +5052,22 @@ function resolvePatchPath(rawPath, cwd, allowOutsideCwd) {
4185
5052
  if (trimmed.length === 0) {
4186
5053
  throw new Error("apply_patch failed: empty file path");
4187
5054
  }
4188
- const absolutePath = path3.isAbsolute(trimmed) ? path3.resolve(trimmed) : path3.resolve(cwd, trimmed);
5055
+ const absolutePath = path4.isAbsolute(trimmed) ? path4.resolve(trimmed) : path4.resolve(cwd, trimmed);
4189
5056
  if (!allowOutsideCwd && !isPathInsideCwd(absolutePath, cwd)) {
4190
5057
  throw new Error(`apply_patch failed: path "${trimmed}" resolves outside cwd "${cwd}"`);
4191
5058
  }
4192
5059
  return absolutePath;
4193
5060
  }
4194
5061
  function isPathInsideCwd(candidatePath, cwd) {
4195
- const relative = path3.relative(cwd, candidatePath);
4196
- return relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative);
5062
+ const relative = path4.relative(cwd, candidatePath);
5063
+ return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
4197
5064
  }
4198
5065
  function toDisplayPath(absolutePath, cwd) {
4199
- const relative = path3.relative(cwd, absolutePath);
5066
+ const relative = path4.relative(cwd, absolutePath);
4200
5067
  if (relative === "") {
4201
5068
  return ".";
4202
5069
  }
4203
- if (!relative.startsWith("..") && !path3.isAbsolute(relative)) {
5070
+ if (!relative.startsWith("..") && !path4.isAbsolute(relative)) {
4204
5071
  return relative;
4205
5072
  }
4206
5073
  return absolutePath;
@@ -4480,6 +5347,8 @@ function formatSummary(added, modified, deleted) {
4480
5347
 
4481
5348
  // src/tools/filesystemTools.ts
4482
5349
  var DEFAULT_READ_FILE_LINE_LIMIT = 2e3;
5350
+ var DEFAULT_READ_FILES_LINE_LIMIT = 200;
5351
+ var DEFAULT_READ_FILES_CHAR_LIMIT = 4e3;
4483
5352
  var DEFAULT_LIST_DIR_LIMIT = 25;
4484
5353
  var DEFAULT_LIST_DIR_DEPTH = 2;
4485
5354
  var DEFAULT_GREP_LIMIT = 100;
@@ -4513,12 +5382,29 @@ var codexGrepFilesInputSchema = z5.object({
4513
5382
  limit: z5.number().int().min(1).optional().describe("Maximum number of file paths to return (defaults to 100).")
4514
5383
  });
4515
5384
  var applyPatchInputSchema = z5.object({
4516
- input: z5.string().min(1)
5385
+ input: z5.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
4517
5386
  });
4518
5387
  var geminiReadFileInputSchema = z5.object({
4519
5388
  file_path: z5.string().min(1),
4520
- offset: z5.number().int().min(0).optional(),
4521
- limit: z5.number().int().min(1).optional()
5389
+ offset: z5.number().int().min(0).nullish(),
5390
+ limit: z5.number().int().min(1).nullish()
5391
+ });
5392
+ var geminiReadFilesInputSchema = z5.object({
5393
+ paths: z5.array(z5.string().min(1)).min(1),
5394
+ line_offset: z5.number().int().min(0).nullish(),
5395
+ line_limit: z5.number().int().min(1).nullish(),
5396
+ char_offset: z5.number().int().min(0).nullish(),
5397
+ char_limit: z5.number().int().min(1).nullish(),
5398
+ include_line_numbers: z5.boolean().nullish()
5399
+ }).superRefine((value, context) => {
5400
+ const hasLineWindow = value.line_offset !== void 0 || value.line_limit !== void 0;
5401
+ const hasCharWindow = value.char_offset !== void 0 || value.char_limit !== void 0;
5402
+ if (hasLineWindow && hasCharWindow) {
5403
+ context.addIssue({
5404
+ code: z5.ZodIssueCode.custom,
5405
+ message: "Use either line_* or char_* window arguments, not both."
5406
+ });
5407
+ }
4522
5408
  });
4523
5409
  var geminiWriteFileInputSchema = z5.object({
4524
5410
  file_path: z5.string().min(1),
@@ -4529,31 +5415,41 @@ var geminiReplaceInputSchema = z5.object({
4529
5415
  instruction: z5.string().min(1),
4530
5416
  old_string: z5.string(),
4531
5417
  new_string: z5.string(),
4532
- expected_replacements: z5.number().int().min(1).optional()
5418
+ expected_replacements: z5.number().int().min(1).nullish()
4533
5419
  });
4534
5420
  var geminiListDirectoryInputSchema = z5.object({
4535
5421
  dir_path: z5.string().min(1),
4536
- ignore: z5.array(z5.string()).optional(),
5422
+ ignore: z5.array(z5.string()).nullish(),
4537
5423
  file_filtering_options: z5.object({
4538
- respect_git_ignore: z5.boolean().optional(),
4539
- respect_gemini_ignore: z5.boolean().optional()
4540
- }).optional()
5424
+ respect_git_ignore: z5.boolean().nullish(),
5425
+ respect_gemini_ignore: z5.boolean().nullish()
5426
+ }).nullish()
5427
+ });
5428
+ var geminiRgSearchInputSchema = z5.object({
5429
+ pattern: z5.string().min(1),
5430
+ path: z5.string().nullish(),
5431
+ glob: z5.string().nullish(),
5432
+ case_sensitive: z5.boolean().nullish(),
5433
+ exclude_pattern: z5.string().nullish(),
5434
+ names_only: z5.boolean().nullish(),
5435
+ max_matches_per_file: z5.number().int().min(1).nullish(),
5436
+ max_results: z5.number().int().min(1).nullish()
4541
5437
  });
4542
5438
  var geminiGrepSearchInputSchema = z5.object({
4543
5439
  pattern: z5.string().min(1),
4544
- dir_path: z5.string().optional(),
4545
- include: z5.string().optional(),
4546
- exclude_pattern: z5.string().optional(),
4547
- names_only: z5.boolean().optional(),
4548
- max_matches_per_file: z5.number().int().min(1).optional(),
4549
- total_max_matches: z5.number().int().min(1).optional()
5440
+ dir_path: z5.string().nullish(),
5441
+ include: z5.string().nullish(),
5442
+ exclude_pattern: z5.string().nullish(),
5443
+ names_only: z5.boolean().nullish(),
5444
+ max_matches_per_file: z5.number().int().min(1).nullish(),
5445
+ total_max_matches: z5.number().int().min(1).nullish()
4550
5446
  });
4551
5447
  var geminiGlobInputSchema = z5.object({
4552
5448
  pattern: z5.string().min(1),
4553
- dir_path: z5.string().optional(),
4554
- case_sensitive: z5.boolean().optional(),
4555
- respect_git_ignore: z5.boolean().optional(),
4556
- respect_gemini_ignore: z5.boolean().optional()
5449
+ dir_path: z5.string().nullish(),
5450
+ case_sensitive: z5.boolean().nullish(),
5451
+ respect_git_ignore: z5.boolean().nullish(),
5452
+ respect_gemini_ignore: z5.boolean().nullish()
4557
5453
  });
4558
5454
  function resolveFilesystemToolProfile(model, profile = "auto") {
4559
5455
  if (profile !== "auto") {
@@ -4597,7 +5493,7 @@ function createCodexFilesystemToolSet(options = {}) {
4597
5493
  }
4598
5494
  function createGeminiFilesystemToolSet(options = {}) {
4599
5495
  return {
4600
- read_file: createReadFileTool(options),
5496
+ read_file: createGeminiReadFileTool(options),
4601
5497
  write_file: createWriteFileTool(options),
4602
5498
  replace: createReplaceTool(options),
4603
5499
  list_directory: createListDirectoryTool(options),
@@ -4609,10 +5505,14 @@ function createModelAgnosticFilesystemToolSet(options = {}) {
4609
5505
  return createGeminiFilesystemToolSet(options);
4610
5506
  }
4611
5507
  function createCodexApplyPatchTool(options = {}) {
4612
- return tool({
4613
- description: "Use the `apply_patch` tool to edit files. This is a FREEFORM tool, so do not wrap the patch in JSON.",
4614
- inputSchema: applyPatchInputSchema,
4615
- execute: async ({ input }) => {
5508
+ return customTool({
5509
+ description: CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION,
5510
+ format: {
5511
+ type: "grammar",
5512
+ syntax: "lark",
5513
+ definition: CODEX_APPLY_PATCH_LARK_GRAMMAR
5514
+ },
5515
+ execute: async (input) => {
4616
5516
  const runtime = resolveRuntime(options);
4617
5517
  const result = await applyPatch({
4618
5518
  patch: input,
@@ -4656,13 +5556,20 @@ function createGrepFilesTool(options = {}) {
4656
5556
  execute: async (input) => grepFilesCodex(input, options)
4657
5557
  });
4658
5558
  }
4659
- function createReadFileTool(options = {}) {
5559
+ function createGeminiReadFileTool(options = {}) {
4660
5560
  return tool({
4661
- description: "Reads and returns content of a specified file.",
5561
+ description: "Reads and returns the content of a specified file. Supports optional 0-based line offset and line limit.",
4662
5562
  inputSchema: geminiReadFileInputSchema,
4663
5563
  execute: async (input) => readFileGemini(input, options)
4664
5564
  });
4665
5565
  }
5566
+ function createReadFilesTool(options = {}) {
5567
+ return tool({
5568
+ description: "Reads one or more files with optional line-based or character-based slicing, similar to a controlled head/tail view.",
5569
+ inputSchema: geminiReadFilesInputSchema,
5570
+ execute: async (input) => readFilesGemini(input, options)
5571
+ });
5572
+ }
4666
5573
  function createWriteFileTool(options = {}) {
4667
5574
  return tool({
4668
5575
  description: "Writes content to a specified file in the local filesystem.",
@@ -4691,6 +5598,13 @@ function createGrepSearchTool(options = {}) {
4691
5598
  execute: async (input) => grepSearchGemini(input, options)
4692
5599
  });
4693
5600
  }
5601
+ function createRgSearchTool(options = {}) {
5602
+ return tool({
5603
+ description: "Searches for a regex pattern within file contents.",
5604
+ inputSchema: geminiRgSearchInputSchema,
5605
+ execute: async (input) => rgSearchGemini(input, options)
5606
+ });
5607
+ }
4694
5608
  function createGlobTool(options = {}) {
4695
5609
  return tool({
4696
5610
  description: "Finds files matching glob patterns, sorted by modification time (newest first).",
@@ -4700,7 +5614,7 @@ function createGlobTool(options = {}) {
4700
5614
  }
4701
5615
  async function readFileCodex(input, options) {
4702
5616
  const runtime = resolveRuntime(options);
4703
- if (!path4.isAbsolute(input.file_path)) {
5617
+ if (!path5.isAbsolute(input.file_path)) {
4704
5618
  throw new Error("file_path must be an absolute path");
4705
5619
  }
4706
5620
  const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
@@ -4751,7 +5665,7 @@ async function readFileCodex(input, options) {
4751
5665
  }
4752
5666
  async function listDirectoryCodex(input, options) {
4753
5667
  const runtime = resolveRuntime(options);
4754
- if (!path4.isAbsolute(input.dir_path)) {
5668
+ if (!path5.isAbsolute(input.dir_path)) {
4755
5669
  throw new Error("dir_path must be an absolute path");
4756
5670
  }
4757
5671
  const dirPath = resolvePathWithPolicy(input.dir_path, runtime.cwd, runtime.allowOutsideCwd);
@@ -4848,9 +5762,6 @@ async function readFileGemini(input, options) {
4848
5762
  path: filePath
4849
5763
  });
4850
5764
  const content = await runtime.filesystem.readTextFile(filePath);
4851
- if (input.offset === void 0 && input.limit === void 0) {
4852
- return content;
4853
- }
4854
5765
  const lines = splitLines(content);
4855
5766
  const offset = Math.max(0, input.offset ?? 0);
4856
5767
  const limit = input.limit ?? DEFAULT_READ_FILE_LINE_LIMIT;
@@ -4858,7 +5769,59 @@ async function readFileGemini(input, options) {
4858
5769
  return "";
4859
5770
  }
4860
5771
  const end = Math.min(lines.length, offset + limit);
4861
- return lines.slice(offset, end).join("\n");
5772
+ return lines.slice(offset, end).map(
5773
+ (line, index) => `L${offset + index + 1}: ${truncateAtCodePointBoundary(line ?? "", runtime.maxLineLength)}`
5774
+ ).join("\n");
5775
+ }
5776
+ async function readFilesGemini(input, options) {
5777
+ const runtime = resolveRuntime(options);
5778
+ const useCharWindow = input.char_offset !== void 0 || input.char_limit !== void 0;
5779
+ const lineOffset = Math.max(0, input.line_offset ?? 0);
5780
+ const lineLimit = input.line_limit ?? DEFAULT_READ_FILES_LINE_LIMIT;
5781
+ const charOffset = Math.max(0, input.char_offset ?? 0);
5782
+ const charLimit = input.char_limit ?? DEFAULT_READ_FILES_CHAR_LIMIT;
5783
+ const includeLineNumbers = input.include_line_numbers !== false;
5784
+ const sections = [];
5785
+ for (const rawPath of input.paths) {
5786
+ const filePath = resolvePathWithPolicy(rawPath, runtime.cwd, runtime.allowOutsideCwd);
5787
+ await runAccessHook2(runtime, {
5788
+ cwd: runtime.cwd,
5789
+ tool: "read_files",
5790
+ action: "read",
5791
+ path: filePath
5792
+ });
5793
+ const content = await runtime.filesystem.readTextFile(filePath);
5794
+ const displayPath = normalizeSlashes(toDisplayPath2(filePath, runtime.cwd));
5795
+ sections.push(`==> ${displayPath} <==`);
5796
+ if (useCharWindow) {
5797
+ if (charOffset >= content.length) {
5798
+ sections.push("");
5799
+ continue;
5800
+ }
5801
+ const end2 = Math.min(content.length, charOffset + charLimit);
5802
+ sections.push(content.slice(charOffset, end2));
5803
+ continue;
5804
+ }
5805
+ const lines = splitLines(content);
5806
+ if (lineOffset >= lines.length) {
5807
+ sections.push("");
5808
+ continue;
5809
+ }
5810
+ const end = Math.min(lines.length, lineOffset + lineLimit);
5811
+ const selected = lines.slice(lineOffset, end);
5812
+ if (includeLineNumbers) {
5813
+ for (let index = 0; index < selected.length; index += 1) {
5814
+ const lineNumber = lineOffset + index + 1;
5815
+ const line = selected[index] ?? "";
5816
+ sections.push(
5817
+ `L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`
5818
+ );
5819
+ }
5820
+ continue;
5821
+ }
5822
+ sections.push(selected.join("\n"));
5823
+ }
5824
+ return sections.join("\n");
4862
5825
  }
4863
5826
  async function writeFileGemini(input, options) {
4864
5827
  const runtime = resolveRuntime(options);
@@ -4869,7 +5832,7 @@ async function writeFileGemini(input, options) {
4869
5832
  action: "write",
4870
5833
  path: filePath
4871
5834
  });
4872
- await runtime.filesystem.ensureDir(path4.dirname(filePath));
5835
+ await runtime.filesystem.ensureDir(path5.dirname(filePath));
4873
5836
  await runtime.filesystem.writeTextFile(filePath, input.content);
4874
5837
  return `Successfully wrote file: ${toDisplayPath2(filePath, runtime.cwd)}`;
4875
5838
  }
@@ -4890,7 +5853,7 @@ async function replaceFileContentGemini(input, options) {
4890
5853
  originalContent = await runtime.filesystem.readTextFile(filePath);
4891
5854
  } catch (error) {
4892
5855
  if (isNoEntError(error) && oldValue.length === 0) {
4893
- await runtime.filesystem.ensureDir(path4.dirname(filePath));
5856
+ await runtime.filesystem.ensureDir(path5.dirname(filePath));
4894
5857
  await runtime.filesystem.writeTextFile(filePath, newValue);
4895
5858
  return `Successfully wrote new file: ${toDisplayPath2(filePath, runtime.cwd)}`;
4896
5859
  }
@@ -4941,25 +5904,25 @@ async function listDirectoryGemini(input, options) {
4941
5904
  return label;
4942
5905
  }).join("\n");
4943
5906
  }
4944
- async function grepSearchGemini(input, options) {
5907
+ async function rgSearchGemini(input, options, toolName = "rg_search") {
4945
5908
  const runtime = resolveRuntime(options);
4946
5909
  const pattern = input.pattern.trim();
4947
5910
  if (pattern.length === 0) {
4948
5911
  throw new Error("pattern must not be empty");
4949
5912
  }
4950
- const include = input.include?.trim();
5913
+ const glob = input.glob?.trim();
4951
5914
  const searchPath = resolvePathWithPolicy(
4952
- input.dir_path ?? runtime.cwd,
5915
+ input.path ?? runtime.cwd,
4953
5916
  runtime.cwd,
4954
5917
  runtime.allowOutsideCwd
4955
5918
  );
4956
5919
  await runAccessHook2(runtime, {
4957
5920
  cwd: runtime.cwd,
4958
- tool: "grep_search",
5921
+ tool: toolName,
4959
5922
  action: "search",
4960
5923
  path: searchPath,
4961
5924
  pattern,
4962
- include
5925
+ include: glob
4963
5926
  });
4964
5927
  const searchPathInfo = await runtime.filesystem.stat(searchPath);
4965
5928
  const filesToScan = await collectSearchFiles({
@@ -4968,10 +5931,10 @@ async function grepSearchGemini(input, options) {
4968
5931
  rootKind: searchPathInfo.kind,
4969
5932
  maxScannedFiles: runtime.grepMaxScannedFiles
4970
5933
  });
4971
- const matcher = include ? createGlobMatcher(include) : null;
4972
- const patternRegex = compileRegex(pattern);
5934
+ const matcher = glob ? createGlobMatcher(glob) : null;
5935
+ const patternRegex = compileRegex(pattern, input.case_sensitive === true ? "m" : "im");
4973
5936
  const excludeRegex = input.exclude_pattern ? compileRegex(input.exclude_pattern) : null;
4974
- const totalMaxMatches = input.total_max_matches ?? DEFAULT_GREP_LIMIT;
5937
+ const totalMaxMatches = input.max_results ?? DEFAULT_GREP_LIMIT;
4975
5938
  const perFileMaxMatches = input.max_matches_per_file ?? Number.POSITIVE_INFINITY;
4976
5939
  const matches = [];
4977
5940
  const fileMatches = /* @__PURE__ */ new Set();
@@ -5026,6 +5989,21 @@ async function grepSearchGemini(input, options) {
5026
5989
  }
5027
5990
  return matches.slice(0, totalMaxMatches).map((match) => `${match.filePath}:${match.lineNumber}:${match.line ?? ""}`).join("\n");
5028
5991
  }
5992
+ async function grepSearchGemini(input, options) {
5993
+ return rgSearchGemini(
5994
+ {
5995
+ pattern: input.pattern,
5996
+ path: input.dir_path,
5997
+ glob: input.include,
5998
+ exclude_pattern: input.exclude_pattern,
5999
+ names_only: input.names_only,
6000
+ max_matches_per_file: input.max_matches_per_file,
6001
+ max_results: input.total_max_matches
6002
+ },
6003
+ options,
6004
+ "grep_search"
6005
+ );
6006
+ }
5029
6007
  async function globFilesGemini(input, options) {
5030
6008
  const runtime = resolveRuntime(options);
5031
6009
  const dirPath = resolvePathWithPolicy(
@@ -5053,7 +6031,7 @@ async function globFilesGemini(input, options) {
5053
6031
  });
5054
6032
  const matched = [];
5055
6033
  for (const filePath of files) {
5056
- const relativePath = normalizeSlashes(path4.relative(dirPath, filePath));
6034
+ const relativePath = normalizeSlashes(path5.relative(dirPath, filePath));
5057
6035
  if (!matcher(relativePath)) {
5058
6036
  continue;
5059
6037
  }
@@ -5067,11 +6045,11 @@ async function globFilesGemini(input, options) {
5067
6045
  return "No files found.";
5068
6046
  }
5069
6047
  matched.sort((left, right) => right.mtimeMs - left.mtimeMs);
5070
- return matched.map((entry) => normalizeSlashes(path4.resolve(entry.filePath))).join("\n");
6048
+ return matched.map((entry) => normalizeSlashes(toDisplayPath2(entry.filePath, runtime.cwd))).join("\n");
5071
6049
  }
5072
6050
  function resolveRuntime(options) {
5073
6051
  return {
5074
- cwd: path4.resolve(options.cwd ?? process.cwd()),
6052
+ cwd: path5.resolve(options.cwd ?? process.cwd()),
5075
6053
  filesystem: options.fs ?? createNodeAgentFilesystem(),
5076
6054
  allowOutsideCwd: options.allowOutsideCwd === true,
5077
6055
  checkAccess: options.checkAccess,
@@ -5102,22 +6080,22 @@ function mapApplyPatchAction(action) {
5102
6080
  return "move";
5103
6081
  }
5104
6082
  function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
5105
- const absolutePath = path4.isAbsolute(inputPath) ? path4.resolve(inputPath) : path4.resolve(cwd, inputPath);
6083
+ const absolutePath = path5.isAbsolute(inputPath) ? path5.resolve(inputPath) : path5.resolve(cwd, inputPath);
5106
6084
  if (!allowOutsideCwd && !isPathInsideCwd2(absolutePath, cwd)) {
5107
6085
  throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
5108
6086
  }
5109
6087
  return absolutePath;
5110
6088
  }
5111
6089
  function isPathInsideCwd2(candidatePath, cwd) {
5112
- const relative = path4.relative(cwd, candidatePath);
5113
- return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
6090
+ const relative = path5.relative(cwd, candidatePath);
6091
+ return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
5114
6092
  }
5115
6093
  function toDisplayPath2(absolutePath, cwd) {
5116
- const relative = path4.relative(cwd, absolutePath);
6094
+ const relative = path5.relative(cwd, absolutePath);
5117
6095
  if (relative === "") {
5118
6096
  return ".";
5119
6097
  }
5120
- if (!relative.startsWith("..") && !path4.isAbsolute(relative)) {
6098
+ if (!relative.startsWith("..") && !path5.isAbsolute(relative)) {
5121
6099
  return relative;
5122
6100
  }
5123
6101
  return absolutePath;
@@ -5327,9 +6305,9 @@ async function collectSearchFiles(params) {
5327
6305
  }
5328
6306
  return files;
5329
6307
  }
5330
- function compileRegex(pattern) {
6308
+ function compileRegex(pattern, flags = "m") {
5331
6309
  try {
5332
- return new RegExp(pattern, "m");
6310
+ return new RegExp(pattern, flags);
5333
6311
  } catch (error) {
5334
6312
  const message = error instanceof Error ? error.message : String(error);
5335
6313
  throw new Error(`invalid regex pattern: ${message}`);
@@ -5344,7 +6322,7 @@ function createGlobMatcher(pattern, caseSensitive = false) {
5344
6322
  }));
5345
6323
  return (candidatePath) => {
5346
6324
  const normalizedPath = normalizeSlashes(candidatePath);
5347
- const basename = path4.posix.basename(normalizedPath);
6325
+ const basename = path5.posix.basename(normalizedPath);
5348
6326
  return compiled.some(
5349
6327
  (entry) => entry.regex.test(entry.applyToBasename ? basename : normalizedPath)
5350
6328
  );
@@ -5507,6 +6485,13 @@ function mergeToolSets(base, extra) {
5507
6485
  return merged;
5508
6486
  }
5509
6487
  export {
6488
+ CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION,
6489
+ CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION,
6490
+ CODEX_APPLY_PATCH_LARK_GRAMMAR,
6491
+ FIREWORKS_DEFAULT_GLM_MODEL,
6492
+ FIREWORKS_DEFAULT_KIMI_MODEL,
6493
+ FIREWORKS_DEFAULT_MINIMAX_MODEL,
6494
+ FIREWORKS_MODEL_IDS,
5510
6495
  InMemoryAgentFilesystem,
5511
6496
  LlmJsonCallError,
5512
6497
  appendMarkdownSourcesSection,
@@ -5519,6 +6504,7 @@ export {
5519
6504
  createCodexReadFileTool,
5520
6505
  createFilesystemToolSetForModel,
5521
6506
  createGeminiFilesystemToolSet,
6507
+ createGeminiReadFileTool,
5522
6508
  createGlobTool,
5523
6509
  createGrepFilesTool,
5524
6510
  createGrepSearchTool,
@@ -5527,9 +6513,11 @@ export {
5527
6513
  createListDirectoryTool,
5528
6514
  createModelAgnosticFilesystemToolSet,
5529
6515
  createNodeAgentFilesystem,
5530
- createReadFileTool,
6516
+ createReadFilesTool,
5531
6517
  createReplaceTool,
6518
+ createRgSearchTool,
5532
6519
  createWriteFileTool,
6520
+ customTool,
5533
6521
  encodeChatGptAuthJson,
5534
6522
  encodeChatGptAuthJsonB64,
5535
6523
  estimateCallCostUsd,
@@ -5540,12 +6528,14 @@ export {
5540
6528
  generateText,
5541
6529
  getChatGptAuthProfile,
5542
6530
  getCurrentToolCallContext,
6531
+ isFireworksModelId,
5543
6532
  isGeminiModelId,
5544
6533
  loadEnvFromFile,
5545
6534
  loadLocalEnv,
5546
6535
  parseJsonFromLlmText,
5547
6536
  refreshChatGptOauthToken,
5548
6537
  resolveFilesystemToolProfile,
6538
+ resolveFireworksModelId,
5549
6539
  runAgentLoop,
5550
6540
  runToolLoop,
5551
6541
  sanitisePartForLogging,