@ljoukov/llm 2.0.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
@@ -1,5 +1,5 @@
1
1
  // src/llm.ts
2
- import { Buffer as Buffer2 } from "buffer";
2
+ import { Buffer as Buffer3 } from "buffer";
3
3
  import { AsyncLocalStorage } from "async_hooks";
4
4
  import { randomBytes } from "crypto";
5
5
  import {
@@ -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,
@@ -125,17 +154,34 @@ var OPENAI_GPT_52_PRICING = {
125
154
  cachedRate: 0.175 / 1e6,
126
155
  outputRate: 14 / 1e6
127
156
  };
128
- var OPENAI_GPT_51_CODEX_MINI_PRICING = {
157
+ var OPENAI_GPT_53_CODEX_PRICING = {
158
+ inputRate: 1.25 / 1e6,
159
+ cachedRate: 0.125 / 1e6,
160
+ outputRate: 10 / 1e6
161
+ };
162
+ var OPENAI_GPT_5_MINI_PRICING = {
129
163
  inputRate: 0.25 / 1e6,
130
164
  cachedRate: 0.025 / 1e6,
131
165
  outputRate: 2 / 1e6
132
166
  };
133
167
  function getOpenAiPricing(modelId) {
168
+ if (modelId.includes("gpt-5.3-codex-spark")) {
169
+ return OPENAI_GPT_5_MINI_PRICING;
170
+ }
171
+ if (modelId.includes("gpt-5.3-codex")) {
172
+ return OPENAI_GPT_53_CODEX_PRICING;
173
+ }
174
+ if (modelId.includes("gpt-5-codex")) {
175
+ return OPENAI_GPT_53_CODEX_PRICING;
176
+ }
134
177
  if (modelId.includes("gpt-5.2")) {
135
178
  return OPENAI_GPT_52_PRICING;
136
179
  }
180
+ if (modelId.includes("gpt-5-mini")) {
181
+ return OPENAI_GPT_5_MINI_PRICING;
182
+ }
137
183
  if (modelId.includes("gpt-5.1-codex-mini")) {
138
- return OPENAI_GPT_51_CODEX_MINI_PRICING;
184
+ return OPENAI_GPT_5_MINI_PRICING;
139
185
  }
140
186
  return void 0;
141
187
  }
@@ -196,6 +242,14 @@ function estimateCallCostUsd({
196
242
  const outputCost = outputTokens * outputRate;
197
243
  return inputCost + cachedCost + outputCost;
198
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
+ }
199
253
  const openAiPricing = getOpenAiPricing(modelId);
200
254
  if (openAiPricing) {
201
255
  const inputCost = nonCachedPrompt * openAiPricing.inputRate;
@@ -208,11 +262,14 @@ function estimateCallCostUsd({
208
262
  }
209
263
 
210
264
  // src/openai/chatgpt-codex.ts
211
- import os from "os";
265
+ import os2 from "os";
212
266
  import { TextDecoder } from "util";
213
267
 
214
268
  // src/openai/chatgpt-auth.ts
215
- import { Buffer } from "buffer";
269
+ import { Buffer as Buffer2 } from "buffer";
270
+ import fs2 from "fs";
271
+ import os from "os";
272
+ import path2 from "path";
216
273
  import { z } from "zod";
217
274
 
218
275
  // src/utils/env.ts
@@ -277,34 +334,30 @@ function parseEnvLine(line) {
277
334
  }
278
335
 
279
336
  // src/openai/chatgpt-auth.ts
280
- var CHATGPT_AUTH_JSON_ENV = "CHATGPT_AUTH_JSON";
281
- var CHATGPT_AUTH_JSON_B64_ENV = "CHATGPT_AUTH_JSON_B64";
282
- var CHATGPT_ACCESS_ENV = "CHATGPT_ACCESS";
283
- var CHATGPT_REFRESH_ENV = "CHATGPT_REFRESH";
284
- var CHATGPT_EXPIRES_ENV = "CHATGPT_EXPIRES";
285
- var CHATGPT_ACCOUNT_ID_ENV = "CHATGPT_ACCOUNT_ID";
286
- var CHATGPT_ID_TOKEN_ENV = "CHATGPT_ID_TOKEN";
287
- var CHATGPT_ACCESS_TOKEN_ENV = "CHATGPT_ACCESS_TOKEN";
288
- var CHATGPT_REFRESH_TOKEN_ENV = "CHATGPT_REFRESH_TOKEN";
289
- 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";
290
343
  var CHATGPT_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
291
344
  var CHATGPT_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
292
345
  var CHATGPT_OAUTH_REDIRECT_URI = "http://localhost:1455/auth/callback";
293
346
  var TOKEN_EXPIRY_BUFFER_MS = 3e4;
294
- var AuthInputSchema = z.object({
295
- access: z.string().min(1).optional(),
296
- access_token: z.string().min(1).optional(),
297
- accessToken: z.string().min(1).optional(),
298
- refresh: z.string().min(1).optional(),
299
- refresh_token: z.string().min(1).optional(),
300
- refreshToken: z.string().min(1).optional(),
301
- expires: z.union([z.number(), z.string()]).optional(),
302
- expires_at: z.union([z.number(), z.string()]).optional(),
303
- expiresAt: z.union([z.number(), z.string()]).optional(),
304
- accountId: z.string().min(1).optional(),
305
- account_id: z.string().min(1).optional(),
306
- id_token: z.string().optional(),
307
- 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()
308
361
  }).loose();
309
362
  var RefreshResponseSchema = z.object({
310
363
  access_token: z.string().min(1),
@@ -319,6 +372,44 @@ var ExchangeResponseSchema = z.object({
319
372
  });
320
373
  var cachedProfile = null;
321
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
+ }
322
413
  function encodeChatGptAuthJson(profile) {
323
414
  const payload = {
324
415
  access: profile.access,
@@ -330,7 +421,7 @@ function encodeChatGptAuthJson(profile) {
330
421
  return JSON.stringify(payload);
331
422
  }
332
423
  function encodeChatGptAuthJsonB64(profile) {
333
- return Buffer.from(encodeChatGptAuthJson(profile)).toString("base64url");
424
+ return Buffer2.from(encodeChatGptAuthJson(profile)).toString("base64url");
334
425
  }
335
426
  async function exchangeChatGptOauthCode({
336
427
  code,
@@ -357,7 +448,7 @@ async function exchangeChatGptOauthCode({
357
448
  const payload = ExchangeResponseSchema.parse(await response.json());
358
449
  return profileFromTokenResponse(payload);
359
450
  }
360
- async function refreshChatGptOauthToken(refreshToken) {
451
+ async function refreshChatGptOauthToken(refreshToken, fallback) {
361
452
  const params = new URLSearchParams();
362
453
  params.set("grant_type", "refresh_token");
363
454
  params.set("client_id", CHATGPT_OAUTH_CLIENT_ID);
@@ -374,9 +465,35 @@ async function refreshChatGptOauthToken(refreshToken) {
374
465
  throw new Error(`ChatGPT OAuth refresh failed (${response.status}): ${body}`);
375
466
  }
376
467
  const payload = RefreshResponseSchema.parse(await response.json());
377
- return profileFromTokenResponse(payload);
468
+ return profileFromTokenResponse(payload, fallback);
378
469
  }
379
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
+ }
380
497
  if (cachedProfile && !isExpired(cachedProfile)) {
381
498
  return cachedProfile;
382
499
  }
@@ -385,8 +502,8 @@ async function getChatGptAuthProfile() {
385
502
  }
386
503
  refreshPromise = (async () => {
387
504
  try {
388
- const baseProfile = cachedProfile ?? loadAuthProfileFromEnv();
389
- const profile = isExpired(baseProfile) ? await refreshChatGptOauthToken(baseProfile.refresh) : baseProfile;
505
+ const baseProfile = cachedProfile ?? loadAuthProfileFromCodexStore();
506
+ const profile = isExpired(baseProfile) ? await refreshAndPersistCodexProfile(baseProfile) : baseProfile;
390
507
  cachedProfile = profile;
391
508
  return profile;
392
509
  } finally {
@@ -395,39 +512,111 @@ async function getChatGptAuthProfile() {
395
512
  })();
396
513
  return refreshPromise;
397
514
  }
398
- function profileFromTokenResponse(payload) {
399
- const expires = Date.now() + normalizeNumber(payload.expires_in) * 1e3;
400
- 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);
401
559
  if (!accountId) {
402
- 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.`);
403
561
  }
404
562
  return {
405
- access: payload.access_token,
406
- refresh: payload.refresh_token,
563
+ access,
564
+ refresh,
407
565
  expires,
408
566
  accountId,
409
- idToken: payload.id_token
567
+ idToken: idToken ?? void 0
410
568
  };
411
569
  }
412
- function normalizeAuthProfile(data) {
413
- const access = data.access ?? data.access_token ?? data.accessToken ?? void 0;
414
- const refresh = data.refresh ?? data.refresh_token ?? data.refreshToken ?? void 0;
415
- if (!access || !refresh) {
416
- 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 = {};
589
+ }
590
+ if (!doc.tokens || typeof doc.tokens !== "object") {
591
+ doc.tokens = {};
417
592
  }
418
- const expiresRaw = data.expires ?? data.expires_at ?? data.expiresAt;
419
- const idToken = data.idToken ?? data.id_token ?? void 0;
420
- const expires = normalizeEpochMillis(expiresRaw) ?? extractJwtExpiry(idToken ?? access) ?? Date.now() + 5 * 6e4;
421
- const accountId = data.accountId ?? data.account_id ?? extractChatGptAccountId(idToken ?? "") ?? extractChatGptAccountId(access);
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;
422
611
  if (!accountId) {
423
- throw new Error("ChatGPT credentials missing chatgpt_account_id.");
612
+ throw new Error("Failed to extract chatgpt_account_id from access token.");
424
613
  }
425
614
  return {
426
- access,
427
- refresh,
615
+ access: payload.access_token,
616
+ refresh: payload.refresh_token,
428
617
  expires,
429
618
  accountId,
430
- idToken: idToken ?? void 0
619
+ idToken: payload.id_token ?? fallbackIdToken
431
620
  };
432
621
  }
433
622
  function normalizeEpochMillis(value) {
@@ -456,31 +645,6 @@ function isExpired(profile) {
456
645
  }
457
646
  return Date.now() + TOKEN_EXPIRY_BUFFER_MS >= expires;
458
647
  }
459
- function loadAuthProfileFromEnv() {
460
- loadLocalEnv();
461
- const rawJson = process.env[CHATGPT_AUTH_JSON_ENV];
462
- if (rawJson && rawJson.trim().length > 0) {
463
- return normalizeAuthProfile(AuthInputSchema.parse(JSON.parse(rawJson)));
464
- }
465
- const rawB64 = process.env[CHATGPT_AUTH_JSON_B64_ENV];
466
- if (rawB64 && rawB64.trim().length > 0) {
467
- const decoded = Buffer.from(rawB64.trim(), "base64url").toString("utf8");
468
- return normalizeAuthProfile(AuthInputSchema.parse(JSON.parse(decoded)));
469
- }
470
- const access = process.env[CHATGPT_ACCESS_ENV] ?? process.env[CHATGPT_ACCESS_TOKEN_ENV] ?? void 0;
471
- const refresh = process.env[CHATGPT_REFRESH_ENV] ?? process.env[CHATGPT_REFRESH_TOKEN_ENV] ?? void 0;
472
- const expires = process.env[CHATGPT_EXPIRES_ENV] ?? process.env[CHATGPT_EXPIRES_AT_ENV] ?? void 0;
473
- const accountId = process.env[CHATGPT_ACCOUNT_ID_ENV] ?? void 0;
474
- const idToken = process.env[CHATGPT_ID_TOKEN_ENV] ?? void 0;
475
- const parsed = AuthInputSchema.parse({
476
- access,
477
- refresh,
478
- expires,
479
- accountId,
480
- idToken
481
- });
482
- return normalizeAuthProfile(parsed);
483
- }
484
648
  function decodeJwtPayload(token) {
485
649
  const segments = token.split(".");
486
650
  if (segments.length < 2) {
@@ -488,7 +652,7 @@ function decodeJwtPayload(token) {
488
652
  }
489
653
  const payloadB64 = segments[1] ?? "";
490
654
  try {
491
- const decoded = Buffer.from(payloadB64, "base64url").toString("utf8");
655
+ const decoded = Buffer2.from(payloadB64, "base64url").toString("utf8");
492
656
  return JSON.parse(decoded);
493
657
  } catch {
494
658
  return null;
@@ -511,8 +675,12 @@ function extractChatGptAccountId(token) {
511
675
  if (!payload || typeof payload !== "object") {
512
676
  return void 0;
513
677
  }
514
- const accountId = payload.chatgpt_account_id;
515
- 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;
516
684
  }
517
685
 
518
686
  // src/openai/chatgpt-codex.ts
@@ -548,7 +716,19 @@ async function streamChatGptCodexResponse(options) {
548
716
  return parseEventStream(body);
549
717
  }
550
718
  async function collectChatGptCodexResponse(options) {
551
- 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
+ }
552
732
  const toolCalls = /* @__PURE__ */ new Map();
553
733
  const toolCallOrder = [];
554
734
  const webSearchCalls = /* @__PURE__ */ new Map();
@@ -557,6 +737,7 @@ async function collectChatGptCodexResponse(options) {
557
737
  const reasoningText = "";
558
738
  let reasoningSummaryText = "";
559
739
  let usage;
740
+ let responseId;
560
741
  let model;
561
742
  let status;
562
743
  let blocked = false;
@@ -597,7 +778,18 @@ async function collectChatGptCodexResponse(options) {
597
778
  if (!toolCalls.has(callId)) {
598
779
  toolCallOrder.push(callId);
599
780
  }
600
- 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 });
601
793
  }
602
794
  } else if (item.type === "web_search_call") {
603
795
  const id = typeof item.id === "string" ? item.id : "";
@@ -619,6 +811,7 @@ async function collectChatGptCodexResponse(options) {
619
811
  const response = event.response;
620
812
  if (response) {
621
813
  usage = response.usage;
814
+ responseId = typeof response.id === "string" ? response.id : responseId;
622
815
  model = typeof response.model === "string" ? response.model : void 0;
623
816
  status = typeof response.status === "string" ? response.status : void 0;
624
817
  }
@@ -628,6 +821,7 @@ async function collectChatGptCodexResponse(options) {
628
821
  const response = event.response;
629
822
  if (response) {
630
823
  usage = response.usage;
824
+ responseId = typeof response.id === "string" ? response.id : responseId;
631
825
  model = typeof response.model === "string" ? response.model : void 0;
632
826
  status = typeof response.status === "string" ? response.status : void 0;
633
827
  }
@@ -637,6 +831,7 @@ async function collectChatGptCodexResponse(options) {
637
831
  const response = event.response;
638
832
  if (response) {
639
833
  usage = response.usage;
834
+ responseId = typeof response.id === "string" ? response.id : responseId;
640
835
  model = typeof response.model === "string" ? response.model : void 0;
641
836
  status = typeof response.status === "string" ? response.status : void 0;
642
837
  }
@@ -654,15 +849,38 @@ async function collectChatGptCodexResponse(options) {
654
849
  toolCalls: orderedToolCalls,
655
850
  webSearchCalls: orderedWebSearchCalls,
656
851
  usage,
852
+ id: responseId,
657
853
  model,
658
854
  status,
659
855
  blocked
660
856
  };
661
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
+ }
662
880
  function buildUserAgent() {
663
881
  const node = process.version;
664
- const platform = os.platform();
665
- const release = os.release();
882
+ const platform = os2.platform();
883
+ const release = os2.release();
666
884
  return `@ljoukov/llm (node ${node}; ${platform} ${release})`;
667
885
  }
668
886
  async function* parseEventStream(stream) {
@@ -810,6 +1028,110 @@ function createCallScheduler(options = {}) {
810
1028
  return { run };
811
1029
  }
812
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
+
813
1135
  // src/google/client.ts
814
1136
  import { GoogleGenAI } from "@google/genai";
815
1137
 
@@ -878,6 +1200,7 @@ function getGoogleAuthOptions(scopes) {
878
1200
  // src/google/client.ts
879
1201
  var GEMINI_MODEL_IDS = [
880
1202
  "gemini-3-pro-preview",
1203
+ "gemini-3-flash-preview",
881
1204
  "gemini-2.5-pro",
882
1205
  "gemini-flash-latest",
883
1206
  "gemini-flash-lite-latest"
@@ -1117,7 +1440,7 @@ function retryDelayMs(attempt) {
1117
1440
  const jitter = Math.floor(Math.random() * 200);
1118
1441
  return base + jitter;
1119
1442
  }
1120
- var scheduler = createCallScheduler({
1443
+ var scheduler2 = createCallScheduler({
1121
1444
  maxParallelRequests: 3,
1122
1445
  minIntervalBetweenStartMs: 200,
1123
1446
  startJitterMs: 200,
@@ -1133,46 +1456,46 @@ var scheduler = createCallScheduler({
1133
1456
  }
1134
1457
  });
1135
1458
  async function runGeminiCall(fn) {
1136
- return scheduler.run(async () => fn(await getGeminiClient()));
1459
+ return scheduler2.run(async () => fn(await getGeminiClient()));
1137
1460
  }
1138
1461
 
1139
1462
  // src/openai/client.ts
1140
- import OpenAI from "openai";
1141
- import { Agent, fetch as undiciFetch } from "undici";
1142
- var cachedApiKey = null;
1143
- var cachedClient = null;
1144
- var cachedFetch = null;
1145
- 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;
1146
1469
  var DEFAULT_OPENAI_TIMEOUT_MS = 15 * 6e4;
1147
1470
  function resolveOpenAiTimeoutMs() {
1148
- if (cachedTimeoutMs !== null) {
1149
- return cachedTimeoutMs;
1471
+ if (cachedTimeoutMs2 !== null) {
1472
+ return cachedTimeoutMs2;
1150
1473
  }
1151
1474
  const raw = process.env.OPENAI_STREAM_TIMEOUT_MS ?? process.env.OPENAI_TIMEOUT_MS;
1152
1475
  const parsed = raw ? Number(raw) : Number.NaN;
1153
- cachedTimeoutMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_OPENAI_TIMEOUT_MS;
1154
- return cachedTimeoutMs;
1476
+ cachedTimeoutMs2 = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_OPENAI_TIMEOUT_MS;
1477
+ return cachedTimeoutMs2;
1155
1478
  }
1156
1479
  function getOpenAiFetch() {
1157
- if (cachedFetch) {
1158
- return cachedFetch;
1480
+ if (cachedFetch2) {
1481
+ return cachedFetch2;
1159
1482
  }
1160
1483
  const timeoutMs = resolveOpenAiTimeoutMs();
1161
- const dispatcher = new Agent({
1484
+ const dispatcher = new Agent2({
1162
1485
  bodyTimeout: timeoutMs,
1163
1486
  headersTimeout: timeoutMs
1164
1487
  });
1165
- cachedFetch = ((input, init) => {
1166
- return undiciFetch(input, {
1488
+ cachedFetch2 = ((input, init) => {
1489
+ return undiciFetch2(input, {
1167
1490
  ...init ?? {},
1168
1491
  dispatcher
1169
1492
  });
1170
1493
  });
1171
- return cachedFetch;
1494
+ return cachedFetch2;
1172
1495
  }
1173
1496
  function getOpenAiApiKey() {
1174
- if (cachedApiKey !== null) {
1175
- return cachedApiKey;
1497
+ if (cachedApiKey2 !== null) {
1498
+ return cachedApiKey2;
1176
1499
  }
1177
1500
  loadLocalEnv();
1178
1501
  const raw = process.env.OPENAI_API_KEY;
@@ -1180,32 +1503,32 @@ function getOpenAiApiKey() {
1180
1503
  if (!value) {
1181
1504
  throw new Error("OPENAI_API_KEY must be provided to access OpenAI APIs.");
1182
1505
  }
1183
- cachedApiKey = value;
1184
- return cachedApiKey;
1506
+ cachedApiKey2 = value;
1507
+ return cachedApiKey2;
1185
1508
  }
1186
1509
  function getOpenAiClient() {
1187
- if (cachedClient) {
1188
- return cachedClient;
1510
+ if (cachedClient2) {
1511
+ return cachedClient2;
1189
1512
  }
1190
1513
  const apiKey = getOpenAiApiKey();
1191
1514
  const timeoutMs = resolveOpenAiTimeoutMs();
1192
- cachedClient = new OpenAI({
1515
+ cachedClient2 = new OpenAI2({
1193
1516
  apiKey,
1194
1517
  fetch: getOpenAiFetch(),
1195
1518
  timeout: timeoutMs
1196
1519
  });
1197
- return cachedClient;
1520
+ return cachedClient2;
1198
1521
  }
1199
1522
 
1200
1523
  // src/openai/calls.ts
1201
1524
  var DEFAULT_OPENAI_REASONING_EFFORT = "medium";
1202
- var scheduler2 = createCallScheduler({
1525
+ var scheduler3 = createCallScheduler({
1203
1526
  maxParallelRequests: 3,
1204
1527
  minIntervalBetweenStartMs: 200,
1205
1528
  startJitterMs: 200
1206
1529
  });
1207
1530
  async function runOpenAiCall(fn) {
1208
- return scheduler2.run(async () => fn(getOpenAiClient()));
1531
+ return scheduler3.run(async () => fn(getOpenAiClient()));
1209
1532
  }
1210
1533
 
1211
1534
  // src/llm.ts
@@ -1221,7 +1544,16 @@ var LlmJsonCallError = class extends Error {
1221
1544
  }
1222
1545
  };
1223
1546
  function tool(options) {
1224
- return options;
1547
+ return {
1548
+ type: "function",
1549
+ ...options
1550
+ };
1551
+ }
1552
+ function customTool(options) {
1553
+ return {
1554
+ type: "custom",
1555
+ ...options
1556
+ };
1225
1557
  }
1226
1558
  function isPlainRecord(value) {
1227
1559
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -1475,9 +1807,9 @@ function sanitisePartForLogging(part) {
1475
1807
  case "inlineData": {
1476
1808
  let omittedBytes;
1477
1809
  try {
1478
- omittedBytes = Buffer2.from(part.data, "base64").byteLength;
1810
+ omittedBytes = Buffer3.from(part.data, "base64").byteLength;
1479
1811
  } catch {
1480
- omittedBytes = Buffer2.byteLength(part.data, "utf8");
1812
+ omittedBytes = Buffer3.byteLength(part.data, "utf8");
1481
1813
  }
1482
1814
  return {
1483
1815
  type: "inlineData",
@@ -1567,6 +1899,10 @@ function resolveProvider(model) {
1567
1899
  if (model.startsWith("gemini-")) {
1568
1900
  return { provider: "gemini", model };
1569
1901
  }
1902
+ const fireworksModel = resolveFireworksModelId(model);
1903
+ if (fireworksModel) {
1904
+ return { provider: "fireworks", model: fireworksModel };
1905
+ }
1570
1906
  return { provider: "openai", model };
1571
1907
  }
1572
1908
  function isOpenAiCodexModel(modelId) {
@@ -1596,6 +1932,27 @@ function toOpenAiReasoningEffort(effort) {
1596
1932
  function resolveOpenAiVerbosity(modelId) {
1597
1933
  return isOpenAiCodexModel(modelId) ? "medium" : "high";
1598
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
+ }
1599
1956
  function isInlineImageMime(mimeType) {
1600
1957
  if (!mimeType) {
1601
1958
  return false;
@@ -2178,6 +2535,53 @@ function toChatGptInput(contents) {
2178
2535
  input
2179
2536
  };
2180
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
+ }
2181
2585
  function toGeminiTools(tools) {
2182
2586
  if (!tools || tools.length === 0) {
2183
2587
  return void 0;
@@ -2351,19 +2755,54 @@ function extractChatGptUsageTokens(usage) {
2351
2755
  totalTokens
2352
2756
  };
2353
2757
  }
2354
- var MODERATION_FINISH_REASONS = /* @__PURE__ */ new Set([
2355
- FinishReason.SAFETY,
2356
- FinishReason.BLOCKLIST,
2357
- FinishReason.PROHIBITED_CONTENT,
2358
- FinishReason.SPII
2359
- ]);
2360
- function isModerationFinish(reason) {
2361
- if (!reason) {
2362
- return false;
2758
+ function extractFireworksUsageTokens(usage) {
2759
+ if (!usage || typeof usage !== "object") {
2760
+ return void 0;
2363
2761
  }
2364
- return MODERATION_FINISH_REASONS.has(reason);
2365
- }
2366
- function mergeToolOutput(value) {
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
+ }
2793
+ var MODERATION_FINISH_REASONS = /* @__PURE__ */ new Set([
2794
+ FinishReason.SAFETY,
2795
+ FinishReason.BLOCKLIST,
2796
+ FinishReason.PROHIBITED_CONTENT,
2797
+ FinishReason.SPII
2798
+ ]);
2799
+ function isModerationFinish(reason) {
2800
+ if (!reason) {
2801
+ return false;
2802
+ }
2803
+ return MODERATION_FINISH_REASONS.has(reason);
2804
+ }
2805
+ function mergeToolOutput(value) {
2367
2806
  if (typeof value === "string") {
2368
2807
  return value;
2369
2808
  }
@@ -2389,8 +2828,8 @@ function parseOpenAiToolArguments(raw) {
2389
2828
  function formatZodIssues(issues) {
2390
2829
  const messages = [];
2391
2830
  for (const issue of issues) {
2392
- const path2 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
2393
- messages.push(`${path2}: ${issue.message}`);
2831
+ const path6 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
2832
+ messages.push(`${path6}: ${issue.message}`);
2394
2833
  }
2395
2834
  return messages.join("; ");
2396
2835
  }
@@ -2406,7 +2845,7 @@ function buildToolErrorOutput(message, issues) {
2406
2845
  return output;
2407
2846
  }
2408
2847
  async function executeToolCall(params) {
2409
- const { toolName, tool: tool2, rawInput, parseError } = params;
2848
+ const { callKind, toolName, tool: tool2, rawInput, parseError } = params;
2410
2849
  if (!tool2) {
2411
2850
  const message = `Unknown tool: ${toolName}`;
2412
2851
  return {
@@ -2414,6 +2853,39 @@ async function executeToolCall(params) {
2414
2853
  outputPayload: buildToolErrorOutput(message)
2415
2854
  };
2416
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
+ }
2417
2889
  if (parseError) {
2418
2890
  const message = `Invalid JSON for tool ${toolName}: ${parseError}`;
2419
2891
  return {
@@ -2470,8 +2942,12 @@ function normalizeChatGptToolIds(params) {
2470
2942
  rawItemId = nextItemId ?? rawItemId;
2471
2943
  }
2472
2944
  const callValue = sanitizeChatGptToolId(rawCallId || rawItemId || randomBytes(8).toString("hex"));
2473
- let itemValue = sanitizeChatGptToolId(rawItemId || `fc-${callValue}`);
2474
- 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")) {
2475
2951
  itemValue = `fc-${itemValue}`;
2476
2952
  }
2477
2953
  return { callId: callValue, itemId: itemValue };
@@ -2536,7 +3012,7 @@ function extractOpenAiResponseParts(response) {
2536
3012
  }
2537
3013
  return { parts, blocked };
2538
3014
  }
2539
- function extractOpenAiFunctionCalls(output) {
3015
+ function extractOpenAiToolCalls(output) {
2540
3016
  const calls = [];
2541
3017
  if (!Array.isArray(output)) {
2542
3018
  return calls;
@@ -2545,22 +3021,78 @@ function extractOpenAiFunctionCalls(output) {
2545
3021
  if (!item || typeof item !== "object") {
2546
3022
  continue;
2547
3023
  }
2548
- if (item.type === "function_call") {
3024
+ const itemType = item.type;
3025
+ if (itemType === "function_call") {
2549
3026
  const name = typeof item.name === "string" ? item.name : "";
2550
3027
  const args = typeof item.arguments === "string" ? item.arguments : "";
2551
3028
  const call_id = typeof item.call_id === "string" ? item.call_id : "";
2552
3029
  const id = typeof item.id === "string" ? item.id : void 0;
2553
3030
  if (name && call_id) {
2554
- 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 });
2555
3042
  }
2556
3043
  }
2557
3044
  }
2558
3045
  return calls;
2559
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
+ }
2560
3090
  function resolveGeminiThinkingConfig(modelId) {
2561
3091
  switch (modelId) {
2562
3092
  case "gemini-3-pro-preview":
2563
3093
  return { includeThoughts: true };
3094
+ case "gemini-3-flash-preview":
3095
+ return { includeThoughts: true, thinkingBudget: 16384 };
2564
3096
  case "gemini-2.5-pro":
2565
3097
  return { includeThoughts: true, thinkingBudget: 32768 };
2566
3098
  case "gemini-flash-latest":
@@ -2572,9 +3104,9 @@ function resolveGeminiThinkingConfig(modelId) {
2572
3104
  }
2573
3105
  function decodeInlineDataBuffer(base64) {
2574
3106
  try {
2575
- return Buffer2.from(base64, "base64");
3107
+ return Buffer3.from(base64, "base64");
2576
3108
  } catch {
2577
- return Buffer2.from(base64, "base64url");
3109
+ return Buffer3.from(base64, "base64url");
2578
3110
  }
2579
3111
  }
2580
3112
  function extractImages(content) {
@@ -2736,7 +3268,7 @@ async function runTextCall(params) {
2736
3268
  };
2737
3269
  let sawResponseDelta = false;
2738
3270
  let sawThoughtDelta = false;
2739
- const result = await collectChatGptCodexResponse({
3271
+ const result = await collectChatGptCodexResponseWithRetry({
2740
3272
  request: requestPayload,
2741
3273
  signal,
2742
3274
  onDelta: (delta) => {
@@ -2767,6 +3299,47 @@ async function runTextCall(params) {
2767
3299
  if (!sawResponseDelta && fallbackText.length > 0) {
2768
3300
  pushDelta("response", fallbackText);
2769
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
+ });
2770
3343
  } else {
2771
3344
  const geminiContents = contents.map(convertLlmContentToGeminiContent);
2772
3345
  const config = {
@@ -2891,11 +3464,12 @@ function buildJsonSchemaConfig(request) {
2891
3464
  const schemaName = (request.openAiSchemaName ?? "llm-response").trim() || "llm-response";
2892
3465
  const providerInfo = resolveProvider(request.model);
2893
3466
  const isOpenAiVariant = providerInfo.provider === "openai" || providerInfo.provider === "chatgpt";
3467
+ const isGeminiVariant = providerInfo.provider === "gemini";
2894
3468
  const baseJsonSchema = zodToJsonSchema(request.schema, {
2895
3469
  name: schemaName,
2896
3470
  target: isOpenAiVariant ? "openAi" : "jsonSchema7"
2897
3471
  });
2898
- const responseJsonSchema = isOpenAiVariant ? resolveOpenAiSchemaRoot(baseJsonSchema) : addGeminiPropertyOrdering(baseJsonSchema);
3472
+ const responseJsonSchema = isOpenAiVariant ? resolveOpenAiSchemaRoot(baseJsonSchema) : isGeminiVariant ? addGeminiPropertyOrdering(baseJsonSchema) : resolveOpenAiSchemaRoot(baseJsonSchema);
2899
3473
  if (isOpenAiVariant && !isJsonSchemaObject(responseJsonSchema)) {
2900
3474
  throw new Error("OpenAI structured outputs require a JSON object schema at the root.");
2901
3475
  }
@@ -3062,15 +3636,46 @@ var DEFAULT_TOOL_LOOP_MAX_STEPS = 8;
3062
3636
  function resolveToolLoopContents(input) {
3063
3637
  return resolveTextContents(input);
3064
3638
  }
3065
- function buildOpenAiFunctionTools(tools) {
3639
+ function isCustomTool(toolDef) {
3640
+ return toolDef.type === "custom";
3641
+ }
3642
+ function buildOpenAiToolsFromToolSet(tools) {
3066
3643
  const toolEntries = Object.entries(tools);
3067
- return toolEntries.map(([name, toolDef]) => ({
3068
- type: "function",
3069
- name,
3070
- description: toolDef.description ?? void 0,
3071
- parameters: buildOpenAiToolSchema(toolDef.inputSchema, name),
3072
- strict: true
3073
- }));
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
+ });
3074
3679
  }
3075
3680
  function buildOpenAiToolSchema(schema, name) {
3076
3681
  const rawSchema = zodToJsonSchema(schema, { name, target: "openAi" });
@@ -3082,11 +3687,18 @@ function buildOpenAiToolSchema(schema, name) {
3082
3687
  }
3083
3688
  function buildGeminiFunctionDeclarations(tools) {
3084
3689
  const toolEntries = Object.entries(tools);
3085
- const functionDeclarations = toolEntries.map(([name, toolDef]) => ({
3086
- name,
3087
- description: toolDef.description ?? "",
3088
- parametersJsonSchema: buildGeminiToolSchema(toolDef.inputSchema, name)
3089
- }));
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
+ });
3090
3702
  return [{ functionDeclarations }];
3091
3703
  }
3092
3704
  function buildGeminiToolSchema(schema, name) {
@@ -3147,9 +3759,9 @@ async function runToolLoop(request) {
3147
3759
  let finalText = "";
3148
3760
  let finalThoughts = "";
3149
3761
  if (providerInfo.provider === "openai") {
3150
- const openAiFunctionTools = buildOpenAiFunctionTools(request.tools);
3762
+ const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
3151
3763
  const openAiNativeTools = toOpenAiTools(request.modelTools);
3152
- const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiFunctionTools] : [...openAiFunctionTools];
3764
+ const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
3153
3765
  const reasoningEffort = resolveOpenAiReasoningEffort(
3154
3766
  providerInfo.model,
3155
3767
  request.openAiReasoningEffort
@@ -3241,9 +3853,9 @@ async function runToolLoop(request) {
3241
3853
  if (usageTokens) {
3242
3854
  emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
3243
3855
  }
3244
- const functionCalls = extractOpenAiFunctionCalls(finalResponse.output);
3856
+ const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
3245
3857
  const stepToolCalls = [];
3246
- if (functionCalls.length === 0) {
3858
+ if (responseToolCalls.length === 0) {
3247
3859
  finalText = responseText;
3248
3860
  finalThoughts = reasoningSummary;
3249
3861
  steps.push({
@@ -3257,10 +3869,21 @@ async function runToolLoop(request) {
3257
3869
  });
3258
3870
  return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
3259
3871
  }
3260
- const callInputs = functionCalls.map((call, index) => {
3872
+ const callInputs = responseToolCalls.map((call, index) => {
3261
3873
  const toolIndex = index + 1;
3262
3874
  const toolId = buildToolLogId(turn, toolIndex);
3263
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
+ }
3264
3887
  const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
3265
3888
  return { call, toolName, value, parseError, toolId, turn, toolIndex };
3266
3889
  });
@@ -3275,6 +3898,7 @@ async function runToolLoop(request) {
3275
3898
  },
3276
3899
  async () => {
3277
3900
  const { result, outputPayload } = await executeToolCall({
3901
+ callKind: entry.call.kind,
3278
3902
  toolName: entry.toolName,
3279
3903
  tool: request.tools[entry.toolName],
3280
3904
  rawInput: entry.value,
@@ -3288,11 +3912,19 @@ async function runToolLoop(request) {
3288
3912
  const toolOutputs = [];
3289
3913
  for (const { entry, result, outputPayload } of callResults) {
3290
3914
  stepToolCalls.push({ ...result, callId: entry.call.call_id });
3291
- toolOutputs.push({
3292
- type: "function_call_output",
3293
- call_id: entry.call.call_id,
3294
- output: mergeToolOutput(outputPayload)
3295
- });
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
+ }
3296
3928
  }
3297
3929
  steps.push({
3298
3930
  step: steps.length + 1,
@@ -3309,24 +3941,28 @@ async function runToolLoop(request) {
3309
3941
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
3310
3942
  }
3311
3943
  if (providerInfo.provider === "chatgpt") {
3312
- const openAiFunctionTools = buildOpenAiFunctionTools(request.tools);
3944
+ const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
3313
3945
  const openAiNativeTools = toOpenAiTools(request.modelTools);
3314
- const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiFunctionTools] : [...openAiFunctionTools];
3946
+ const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
3315
3947
  const reasoningEffort = resolveOpenAiReasoningEffort(
3316
3948
  request.model,
3317
3949
  request.openAiReasoningEffort
3318
3950
  );
3319
3951
  const toolLoopInput = toChatGptInput(contents);
3952
+ const conversationId = `tool-loop-${randomBytes(8).toString("hex")}`;
3953
+ const promptCacheKey = conversationId;
3320
3954
  let input = [...toolLoopInput.input];
3321
3955
  for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
3322
3956
  const turn = stepIndex + 1;
3323
- const response = await collectChatGptCodexResponse({
3957
+ const response = await collectChatGptCodexResponseWithRetry({
3958
+ sessionId: conversationId,
3324
3959
  request: {
3325
3960
  model: providerInfo.model,
3326
3961
  store: false,
3327
3962
  stream: true,
3328
3963
  instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
3329
3964
  input,
3965
+ prompt_cache_key: promptCacheKey,
3330
3966
  include: ["reasoning.encrypted_content"],
3331
3967
  tools: openAiTools,
3332
3968
  tool_choice: "auto",
@@ -3357,8 +3993,8 @@ async function runToolLoop(request) {
3357
3993
  totalCostUsd += stepCostUsd;
3358
3994
  const responseText = (response.text ?? "").trim();
3359
3995
  const reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
3360
- const functionCalls = response.toolCalls ?? [];
3361
- if (functionCalls.length === 0) {
3996
+ const responseToolCalls = response.toolCalls ?? [];
3997
+ if (responseToolCalls.length === 0) {
3362
3998
  finalText = responseText;
3363
3999
  finalThoughts = reasoningSummaryText;
3364
4000
  steps.push({
@@ -3374,12 +4010,16 @@ async function runToolLoop(request) {
3374
4010
  }
3375
4011
  const toolCalls = [];
3376
4012
  const toolOutputs = [];
3377
- const callInputs = functionCalls.map((call, index) => {
4013
+ const callInputs = responseToolCalls.map((call, index) => {
3378
4014
  const toolIndex = index + 1;
3379
4015
  const toolId = buildToolLogId(turn, toolIndex);
3380
4016
  const toolName = call.name;
3381
- const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
3382
- 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
+ });
3383
4023
  return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
3384
4024
  });
3385
4025
  const callResults = await Promise.all(
@@ -3393,6 +4033,7 @@ async function runToolLoop(request) {
3393
4033
  },
3394
4034
  async () => {
3395
4035
  const { result, outputPayload } = await executeToolCall({
4036
+ callKind: entry.call.kind,
3396
4037
  toolName: entry.toolName,
3397
4038
  tool: request.tools[entry.toolName],
3398
4039
  rawInput: entry.value,
@@ -3405,19 +4046,35 @@ async function runToolLoop(request) {
3405
4046
  );
3406
4047
  for (const { entry, result, outputPayload } of callResults) {
3407
4048
  toolCalls.push({ ...result, callId: entry.ids.callId });
3408
- toolOutputs.push({
3409
- type: "function_call",
3410
- id: entry.ids.itemId,
3411
- call_id: entry.ids.callId,
3412
- name: entry.toolName,
3413
- arguments: entry.call.arguments,
3414
- status: "completed"
3415
- });
3416
- toolOutputs.push({
3417
- type: "function_call_output",
3418
- call_id: entry.ids.callId,
3419
- output: mergeToolOutput(outputPayload)
3420
- });
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
+ }
3421
4078
  }
3422
4079
  steps.push({
3423
4080
  step: steps.length + 1,
@@ -3432,6 +4089,134 @@ async function runToolLoop(request) {
3432
4089
  }
3433
4090
  throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
3434
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
+ }
3435
4220
  const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
3436
4221
  const geminiNativeTools = toGeminiTools(request.modelTools);
3437
4222
  const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
@@ -3581,6 +4366,7 @@ async function runToolLoop(request) {
3581
4366
  },
3582
4367
  async () => {
3583
4368
  const { result, outputPayload } = await executeToolCall({
4369
+ callKind: "function",
3584
4370
  toolName: entry.toolName,
3585
4371
  tool: request.tools[entry.toolName],
3586
4372
  rawInput: entry.rawInput
@@ -3858,11 +4644,1880 @@ function appendMarkdownSourcesSection(value, sources) {
3858
4644
  ## Sources
3859
4645
  ${lines}`;
3860
4646
  }
4647
+
4648
+ // src/tools/filesystemTools.ts
4649
+ import path5 from "path";
4650
+ import { z as z5 } from "zod";
4651
+
4652
+ // src/tools/applyPatch.ts
4653
+ import path4 from "path";
4654
+ import { z as z4 } from "zod";
4655
+
4656
+ // src/tools/filesystem.ts
4657
+ import { promises as fs3 } from "fs";
4658
+ import path3 from "path";
4659
+ var InMemoryAgentFilesystem = class {
4660
+ #files = /* @__PURE__ */ new Map();
4661
+ #dirs = /* @__PURE__ */ new Map();
4662
+ #clock = 0;
4663
+ constructor(initialFiles = {}) {
4664
+ const root = path3.resolve("/");
4665
+ this.#dirs.set(root, { mtimeMs: this.#nextMtime() });
4666
+ for (const [filePath, content] of Object.entries(initialFiles)) {
4667
+ const absolutePath = path3.resolve(filePath);
4668
+ this.#ensureDirSync(path3.dirname(absolutePath));
4669
+ this.#files.set(absolutePath, {
4670
+ content,
4671
+ mtimeMs: this.#nextMtime()
4672
+ });
4673
+ }
4674
+ }
4675
+ async readTextFile(filePath) {
4676
+ const absolutePath = path3.resolve(filePath);
4677
+ const file = this.#files.get(absolutePath);
4678
+ if (!file) {
4679
+ throw createNoSuchFileError("open", absolutePath);
4680
+ }
4681
+ return file.content;
4682
+ }
4683
+ async writeTextFile(filePath, content) {
4684
+ const absolutePath = path3.resolve(filePath);
4685
+ const parentPath = path3.dirname(absolutePath);
4686
+ if (!this.#dirs.has(parentPath)) {
4687
+ throw createNoSuchFileError("open", parentPath);
4688
+ }
4689
+ this.#files.set(absolutePath, { content, mtimeMs: this.#nextMtime() });
4690
+ }
4691
+ async deleteFile(filePath) {
4692
+ const absolutePath = path3.resolve(filePath);
4693
+ if (!this.#files.delete(absolutePath)) {
4694
+ throw createNoSuchFileError("unlink", absolutePath);
4695
+ }
4696
+ }
4697
+ async ensureDir(directoryPath) {
4698
+ this.#ensureDirSync(path3.resolve(directoryPath));
4699
+ }
4700
+ async readDir(directoryPath) {
4701
+ const absolutePath = path3.resolve(directoryPath);
4702
+ const directory = this.#dirs.get(absolutePath);
4703
+ if (!directory) {
4704
+ throw createNoSuchFileError("scandir", absolutePath);
4705
+ }
4706
+ const entries = [];
4707
+ const seenNames = /* @__PURE__ */ new Set();
4708
+ for (const [dirPath, dirRecord] of this.#dirs.entries()) {
4709
+ if (dirPath === absolutePath) {
4710
+ continue;
4711
+ }
4712
+ if (path3.dirname(dirPath) !== absolutePath) {
4713
+ continue;
4714
+ }
4715
+ const name = path3.basename(dirPath);
4716
+ if (seenNames.has(name)) {
4717
+ continue;
4718
+ }
4719
+ seenNames.add(name);
4720
+ entries.push({
4721
+ name,
4722
+ path: dirPath,
4723
+ kind: "directory",
4724
+ mtimeMs: dirRecord.mtimeMs
4725
+ });
4726
+ }
4727
+ for (const [filePath, fileRecord] of this.#files.entries()) {
4728
+ if (path3.dirname(filePath) !== absolutePath) {
4729
+ continue;
4730
+ }
4731
+ const name = path3.basename(filePath);
4732
+ if (seenNames.has(name)) {
4733
+ continue;
4734
+ }
4735
+ seenNames.add(name);
4736
+ entries.push({
4737
+ name,
4738
+ path: filePath,
4739
+ kind: "file",
4740
+ mtimeMs: fileRecord.mtimeMs
4741
+ });
4742
+ }
4743
+ entries.sort((left, right) => left.name.localeCompare(right.name));
4744
+ return entries;
4745
+ }
4746
+ async stat(entryPath) {
4747
+ const absolutePath = path3.resolve(entryPath);
4748
+ const file = this.#files.get(absolutePath);
4749
+ if (file) {
4750
+ return { kind: "file", mtimeMs: file.mtimeMs };
4751
+ }
4752
+ const directory = this.#dirs.get(absolutePath);
4753
+ if (directory) {
4754
+ return { kind: "directory", mtimeMs: directory.mtimeMs };
4755
+ }
4756
+ throw createNoSuchFileError("stat", absolutePath);
4757
+ }
4758
+ snapshot() {
4759
+ const entries = [...this.#files.entries()].sort(([left], [right]) => left.localeCompare(right));
4760
+ return Object.fromEntries(entries.map(([filePath, record]) => [filePath, record.content]));
4761
+ }
4762
+ #ensureDirSync(directoryPath) {
4763
+ const absolutePath = path3.resolve(directoryPath);
4764
+ const parts = [];
4765
+ let cursor = absolutePath;
4766
+ for (; ; ) {
4767
+ if (this.#dirs.has(cursor)) {
4768
+ break;
4769
+ }
4770
+ parts.push(cursor);
4771
+ const parent = path3.dirname(cursor);
4772
+ if (parent === cursor) {
4773
+ break;
4774
+ }
4775
+ cursor = parent;
4776
+ }
4777
+ for (let index = parts.length - 1; index >= 0; index -= 1) {
4778
+ const nextDir = parts[index];
4779
+ if (nextDir === void 0) {
4780
+ continue;
4781
+ }
4782
+ if (!this.#dirs.has(nextDir)) {
4783
+ this.#dirs.set(nextDir, { mtimeMs: this.#nextMtime() });
4784
+ }
4785
+ }
4786
+ }
4787
+ #nextMtime() {
4788
+ this.#clock += 1;
4789
+ return this.#clock;
4790
+ }
4791
+ };
4792
+ function createNodeAgentFilesystem() {
4793
+ return {
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),
4797
+ ensureDir: async (directoryPath) => {
4798
+ await fs3.mkdir(directoryPath, { recursive: true });
4799
+ },
4800
+ readDir: async (directoryPath) => {
4801
+ const entries = await fs3.readdir(directoryPath, { withFileTypes: true });
4802
+ const result = [];
4803
+ for (const entry of entries) {
4804
+ const entryPath = path3.resolve(directoryPath, entry.name);
4805
+ const stats = await fs3.lstat(entryPath);
4806
+ result.push({
4807
+ name: entry.name,
4808
+ path: entryPath,
4809
+ kind: statsToKind(stats),
4810
+ mtimeMs: stats.mtimeMs
4811
+ });
4812
+ }
4813
+ return result;
4814
+ },
4815
+ stat: async (entryPath) => {
4816
+ const stats = await fs3.lstat(entryPath);
4817
+ return {
4818
+ kind: statsToKind(stats),
4819
+ mtimeMs: stats.mtimeMs
4820
+ };
4821
+ }
4822
+ };
4823
+ }
4824
+ function createInMemoryAgentFilesystem(initialFiles = {}) {
4825
+ return new InMemoryAgentFilesystem(initialFiles);
4826
+ }
4827
+ function statsToKind(stats) {
4828
+ if (stats.isSymbolicLink()) {
4829
+ return "symlink";
4830
+ }
4831
+ if (stats.isDirectory()) {
4832
+ return "directory";
4833
+ }
4834
+ if (stats.isFile()) {
4835
+ return "file";
4836
+ }
4837
+ return "other";
4838
+ }
4839
+ function createNoSuchFileError(syscall, filePath) {
4840
+ const error = new Error(
4841
+ `ENOENT: no such file or directory, ${syscall} '${filePath}'`
4842
+ );
4843
+ error.code = "ENOENT";
4844
+ error.syscall = syscall;
4845
+ error.path = filePath;
4846
+ return error;
4847
+ }
4848
+
4849
+ // src/tools/applyPatch.ts
4850
+ var BEGIN_PATCH_LINE = "*** Begin Patch";
4851
+ var END_PATCH_LINE = "*** End Patch";
4852
+ var ADD_FILE_PREFIX = "*** Add File: ";
4853
+ var DELETE_FILE_PREFIX = "*** Delete File: ";
4854
+ var UPDATE_FILE_PREFIX = "*** Update File: ";
4855
+ var MOVE_TO_PREFIX = "*** Move to: ";
4856
+ var END_OF_FILE_LINE = "*** End of File";
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");
4950
+ var applyPatchToolInputSchema = z4.object({
4951
+ input: z4.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
4952
+ });
4953
+ function createApplyPatchTool(options = {}) {
4954
+ return tool({
4955
+ description: options.description ?? CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION,
4956
+ inputSchema: applyPatchToolInputSchema,
4957
+ execute: async ({ input }) => applyPatch({
4958
+ patch: input,
4959
+ cwd: options.cwd,
4960
+ fs: options.fs,
4961
+ allowOutsideCwd: options.allowOutsideCwd,
4962
+ checkAccess: options.checkAccess,
4963
+ maxPatchBytes: options.maxPatchBytes
4964
+ })
4965
+ });
4966
+ }
4967
+ async function applyPatch(request) {
4968
+ const cwd = path4.resolve(request.cwd ?? process.cwd());
4969
+ const adapter = request.fs ?? createNodeAgentFilesystem();
4970
+ const allowOutsideCwd = request.allowOutsideCwd === true;
4971
+ const patchBytes = Buffer.byteLength(request.patch, "utf8");
4972
+ const maxPatchBytes = request.maxPatchBytes ?? DEFAULT_MAX_PATCH_BYTES;
4973
+ if (patchBytes > maxPatchBytes) {
4974
+ throw new Error(
4975
+ `apply_patch failed: patch too large (${patchBytes} bytes > ${maxPatchBytes} bytes)`
4976
+ );
4977
+ }
4978
+ const parsed = parsePatchDocument(normalizePatchText(request.patch));
4979
+ const added = [];
4980
+ const modified = [];
4981
+ const deleted = [];
4982
+ for (const operation of parsed.operations) {
4983
+ if (operation.type === "add") {
4984
+ const absolutePath2 = resolvePatchPath(operation.path, cwd, allowOutsideCwd);
4985
+ await runAccessHook(request.checkAccess, {
4986
+ cwd,
4987
+ kind: "add",
4988
+ path: absolutePath2
4989
+ });
4990
+ await adapter.ensureDir(path4.dirname(absolutePath2));
4991
+ await adapter.writeTextFile(absolutePath2, operation.content);
4992
+ added.push(toDisplayPath(absolutePath2, cwd));
4993
+ continue;
4994
+ }
4995
+ if (operation.type === "delete") {
4996
+ const absolutePath2 = resolvePatchPath(operation.path, cwd, allowOutsideCwd);
4997
+ await runAccessHook(request.checkAccess, {
4998
+ cwd,
4999
+ kind: "delete",
5000
+ path: absolutePath2
5001
+ });
5002
+ await adapter.readTextFile(absolutePath2);
5003
+ await adapter.deleteFile(absolutePath2);
5004
+ deleted.push(toDisplayPath(absolutePath2, cwd));
5005
+ continue;
5006
+ }
5007
+ const absolutePath = resolvePatchPath(operation.path, cwd, allowOutsideCwd);
5008
+ await runAccessHook(request.checkAccess, {
5009
+ cwd,
5010
+ kind: "update",
5011
+ path: absolutePath
5012
+ });
5013
+ const current = await adapter.readTextFile(absolutePath);
5014
+ const next = deriveUpdatedContent(current, operation.chunks, toDisplayPath(absolutePath, cwd));
5015
+ if (operation.movePath) {
5016
+ const destinationPath = resolvePatchPath(operation.movePath, cwd, allowOutsideCwd);
5017
+ await runAccessHook(request.checkAccess, {
5018
+ cwd,
5019
+ kind: "move",
5020
+ path: destinationPath,
5021
+ fromPath: absolutePath,
5022
+ toPath: destinationPath
5023
+ });
5024
+ await adapter.ensureDir(path4.dirname(destinationPath));
5025
+ await adapter.writeTextFile(destinationPath, next);
5026
+ await adapter.deleteFile(absolutePath);
5027
+ modified.push(toDisplayPath(destinationPath, cwd));
5028
+ continue;
5029
+ }
5030
+ await adapter.writeTextFile(absolutePath, next);
5031
+ modified.push(toDisplayPath(absolutePath, cwd));
5032
+ }
5033
+ return {
5034
+ success: true,
5035
+ summary: formatSummary(added, modified, deleted),
5036
+ added,
5037
+ modified,
5038
+ deleted
5039
+ };
5040
+ }
5041
+ async function runAccessHook(hook, context) {
5042
+ if (!hook) {
5043
+ return;
5044
+ }
5045
+ await hook(context);
5046
+ }
5047
+ function normalizePatchText(raw) {
5048
+ return raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
5049
+ }
5050
+ function resolvePatchPath(rawPath, cwd, allowOutsideCwd) {
5051
+ const trimmed = rawPath.trim();
5052
+ if (trimmed.length === 0) {
5053
+ throw new Error("apply_patch failed: empty file path");
5054
+ }
5055
+ const absolutePath = path4.isAbsolute(trimmed) ? path4.resolve(trimmed) : path4.resolve(cwd, trimmed);
5056
+ if (!allowOutsideCwd && !isPathInsideCwd(absolutePath, cwd)) {
5057
+ throw new Error(`apply_patch failed: path "${trimmed}" resolves outside cwd "${cwd}"`);
5058
+ }
5059
+ return absolutePath;
5060
+ }
5061
+ function isPathInsideCwd(candidatePath, cwd) {
5062
+ const relative = path4.relative(cwd, candidatePath);
5063
+ return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
5064
+ }
5065
+ function toDisplayPath(absolutePath, cwd) {
5066
+ const relative = path4.relative(cwd, absolutePath);
5067
+ if (relative === "") {
5068
+ return ".";
5069
+ }
5070
+ if (!relative.startsWith("..") && !path4.isAbsolute(relative)) {
5071
+ return relative;
5072
+ }
5073
+ return absolutePath;
5074
+ }
5075
+ function parsePatchDocument(patch) {
5076
+ const lines = patch.split("\n");
5077
+ if (lines.at(-1) === "") {
5078
+ lines.pop();
5079
+ }
5080
+ if (lines.length < 2) {
5081
+ throw new Error("apply_patch failed: patch must contain Begin/End markers");
5082
+ }
5083
+ if (lines[0] !== BEGIN_PATCH_LINE) {
5084
+ throw new Error(`apply_patch failed: missing "${BEGIN_PATCH_LINE}" header`);
5085
+ }
5086
+ if (lines[lines.length - 1] !== END_PATCH_LINE) {
5087
+ throw new Error(`apply_patch failed: missing "${END_PATCH_LINE}" footer`);
5088
+ }
5089
+ const body = lines.slice(1, -1);
5090
+ if (body.length === 0) {
5091
+ throw new Error("apply_patch failed: patch body is empty");
5092
+ }
5093
+ const operations = [];
5094
+ let index = 0;
5095
+ while (index < body.length) {
5096
+ const line = body[index];
5097
+ if (!line) {
5098
+ throw new Error("apply_patch failed: unexpected empty line between file sections");
5099
+ }
5100
+ if (line.startsWith(ADD_FILE_PREFIX)) {
5101
+ const filePath = extractPatchPath(line, ADD_FILE_PREFIX);
5102
+ index += 1;
5103
+ const contentLines = [];
5104
+ while (index < body.length) {
5105
+ const contentLine = body[index];
5106
+ if (contentLine === void 0 || isPatchSectionHeader(contentLine)) {
5107
+ break;
5108
+ }
5109
+ if (!contentLine.startsWith("+")) {
5110
+ throw new Error(`apply_patch failed: invalid add-file line "${contentLine}"`);
5111
+ }
5112
+ contentLines.push(contentLine.slice(1));
5113
+ index += 1;
5114
+ }
5115
+ if (contentLines.length === 0) {
5116
+ throw new Error(`apply_patch failed: add-file section for "${filePath}" is empty`);
5117
+ }
5118
+ operations.push({
5119
+ type: "add",
5120
+ path: filePath,
5121
+ content: `${contentLines.join("\n")}
5122
+ `
5123
+ });
5124
+ continue;
5125
+ }
5126
+ if (line.startsWith(DELETE_FILE_PREFIX)) {
5127
+ operations.push({
5128
+ type: "delete",
5129
+ path: extractPatchPath(line, DELETE_FILE_PREFIX)
5130
+ });
5131
+ index += 1;
5132
+ continue;
5133
+ }
5134
+ if (line.startsWith(UPDATE_FILE_PREFIX)) {
5135
+ const filePath = extractPatchPath(line, UPDATE_FILE_PREFIX);
5136
+ index += 1;
5137
+ let movePath;
5138
+ const moveHeader = body[index];
5139
+ if (moveHeader?.startsWith(MOVE_TO_PREFIX)) {
5140
+ movePath = extractPatchPath(moveHeader, MOVE_TO_PREFIX);
5141
+ index += 1;
5142
+ }
5143
+ const chunks = [];
5144
+ while (index < body.length) {
5145
+ const hunkHeader = body[index];
5146
+ if (hunkHeader === void 0 || isPatchSectionHeader(hunkHeader)) {
5147
+ break;
5148
+ }
5149
+ if (!(hunkHeader === "@@" || hunkHeader.startsWith("@@ "))) {
5150
+ throw new Error(
5151
+ `apply_patch failed: expected hunk marker in "${filePath}", got "${hunkHeader}"`
5152
+ );
5153
+ }
5154
+ const contextSelector = hunkHeader.length > 2 ? hunkHeader.slice(3) : void 0;
5155
+ index += 1;
5156
+ const oldLines = [];
5157
+ const newLines = [];
5158
+ let sawBodyLine = false;
5159
+ let sawChangeLine = false;
5160
+ let isEndOfFile = false;
5161
+ while (index < body.length) {
5162
+ const chunkLine = body[index];
5163
+ if (chunkLine === void 0) {
5164
+ break;
5165
+ }
5166
+ if (chunkLine === "@@" || chunkLine.startsWith("@@ ") || isPatchSectionHeader(chunkLine)) {
5167
+ break;
5168
+ }
5169
+ if (chunkLine === END_OF_FILE_LINE) {
5170
+ isEndOfFile = true;
5171
+ index += 1;
5172
+ break;
5173
+ }
5174
+ if (chunkLine.length === 0) {
5175
+ throw new Error(`apply_patch failed: invalid empty hunk line in "${filePath}"`);
5176
+ }
5177
+ const prefix = chunkLine[0];
5178
+ const content = chunkLine.slice(1);
5179
+ if (prefix === " ") {
5180
+ oldLines.push(content);
5181
+ newLines.push(content);
5182
+ } else if (prefix === "-") {
5183
+ oldLines.push(content);
5184
+ sawChangeLine = true;
5185
+ } else if (prefix === "+") {
5186
+ newLines.push(content);
5187
+ sawChangeLine = true;
5188
+ } else {
5189
+ throw new Error(
5190
+ `apply_patch failed: unsupported hunk prefix "${prefix}" in "${chunkLine}"`
5191
+ );
5192
+ }
5193
+ sawBodyLine = true;
5194
+ index += 1;
5195
+ }
5196
+ if (!sawBodyLine) {
5197
+ throw new Error(`apply_patch failed: empty hunk body in "${filePath}"`);
5198
+ }
5199
+ if (!sawChangeLine) {
5200
+ throw new Error(
5201
+ `apply_patch failed: hunk in "${filePath}" must include '+' or '-' lines`
5202
+ );
5203
+ }
5204
+ chunks.push({
5205
+ contextSelector,
5206
+ oldLines,
5207
+ newLines,
5208
+ isEndOfFile
5209
+ });
5210
+ }
5211
+ if (chunks.length === 0) {
5212
+ throw new Error(`apply_patch failed: update section for "${filePath}" has no hunks`);
5213
+ }
5214
+ operations.push({
5215
+ type: "update",
5216
+ path: filePath,
5217
+ movePath,
5218
+ chunks
5219
+ });
5220
+ continue;
5221
+ }
5222
+ throw new Error(`apply_patch failed: unrecognized section header "${line}"`);
5223
+ }
5224
+ return { operations };
5225
+ }
5226
+ function extractPatchPath(line, prefix) {
5227
+ const value = line.slice(prefix.length).trim();
5228
+ if (value.length === 0) {
5229
+ throw new Error(`apply_patch failed: missing file path in "${line}"`);
5230
+ }
5231
+ return value;
5232
+ }
5233
+ function isPatchSectionHeader(line) {
5234
+ return line.startsWith(ADD_FILE_PREFIX) || line.startsWith(DELETE_FILE_PREFIX) || line.startsWith(UPDATE_FILE_PREFIX);
5235
+ }
5236
+ function deriveUpdatedContent(originalContent, chunks, displayPath) {
5237
+ const originalLines = splitFileContentIntoLines(originalContent);
5238
+ const replacements = [];
5239
+ let lineIndex = 0;
5240
+ for (const chunk of chunks) {
5241
+ if (chunk.contextSelector !== void 0) {
5242
+ const contextIndex = seekSequence(originalLines, [chunk.contextSelector], lineIndex, false);
5243
+ if (contextIndex === null) {
5244
+ throw new Error(
5245
+ `apply_patch failed: unable to locate context "${chunk.contextSelector}" in ${displayPath}`
5246
+ );
5247
+ }
5248
+ lineIndex = contextIndex + 1;
5249
+ }
5250
+ if (chunk.oldLines.length === 0) {
5251
+ replacements.push({
5252
+ startIndex: originalLines.length,
5253
+ oldLength: 0,
5254
+ newLines: [...chunk.newLines]
5255
+ });
5256
+ continue;
5257
+ }
5258
+ let oldLines = [...chunk.oldLines];
5259
+ let newLines = [...chunk.newLines];
5260
+ let startIndex = seekSequence(originalLines, oldLines, lineIndex, chunk.isEndOfFile);
5261
+ if (startIndex === null && oldLines.at(-1) === "") {
5262
+ oldLines = oldLines.slice(0, -1);
5263
+ if (newLines.at(-1) === "") {
5264
+ newLines = newLines.slice(0, -1);
5265
+ }
5266
+ startIndex = seekSequence(originalLines, oldLines, lineIndex, chunk.isEndOfFile);
5267
+ }
5268
+ if (startIndex === null) {
5269
+ throw new Error(
5270
+ `apply_patch failed: failed to match hunk in ${displayPath}:
5271
+ ${chunk.oldLines.join("\n")}`
5272
+ );
5273
+ }
5274
+ replacements.push({
5275
+ startIndex,
5276
+ oldLength: oldLines.length,
5277
+ newLines
5278
+ });
5279
+ lineIndex = startIndex + oldLines.length;
5280
+ }
5281
+ replacements.sort((left, right) => left.startIndex - right.startIndex);
5282
+ const nextLines = applyReplacements(originalLines, replacements);
5283
+ if (nextLines.length > 0 && nextLines[nextLines.length - 1] !== "") {
5284
+ nextLines.push("");
5285
+ }
5286
+ return nextLines.join("\n");
5287
+ }
5288
+ function splitFileContentIntoLines(content) {
5289
+ const lines = content.split("\n");
5290
+ if (lines.at(-1) === "") {
5291
+ lines.pop();
5292
+ }
5293
+ return lines;
5294
+ }
5295
+ function seekSequence(sourceLines, targetLines, startIndex, isEndOfFile) {
5296
+ if (targetLines.length === 0) {
5297
+ return Math.min(Math.max(startIndex, 0), sourceLines.length);
5298
+ }
5299
+ const from = Math.max(startIndex, 0);
5300
+ const maxStart = sourceLines.length - targetLines.length;
5301
+ if (maxStart < from) {
5302
+ return null;
5303
+ }
5304
+ const matchesAt = (candidateIndex) => {
5305
+ for (let offset = 0; offset < targetLines.length; offset += 1) {
5306
+ if (sourceLines[candidateIndex + offset] !== targetLines[offset]) {
5307
+ return false;
5308
+ }
5309
+ }
5310
+ return true;
5311
+ };
5312
+ if (isEndOfFile) {
5313
+ return matchesAt(maxStart) ? maxStart : null;
5314
+ }
5315
+ for (let candidate = from; candidate <= maxStart; candidate += 1) {
5316
+ if (matchesAt(candidate)) {
5317
+ return candidate;
5318
+ }
5319
+ }
5320
+ return null;
5321
+ }
5322
+ function applyReplacements(lines, replacements) {
5323
+ const result = [...lines];
5324
+ for (let index = replacements.length - 1; index >= 0; index -= 1) {
5325
+ const replacement = replacements[index];
5326
+ if (replacement === void 0) {
5327
+ continue;
5328
+ }
5329
+ result.splice(replacement.startIndex, replacement.oldLength, ...replacement.newLines);
5330
+ }
5331
+ return result;
5332
+ }
5333
+ function formatSummary(added, modified, deleted) {
5334
+ const lines = ["Success. Updated the following files:"];
5335
+ for (const filePath of added) {
5336
+ lines.push(`A ${filePath}`);
5337
+ }
5338
+ for (const filePath of modified) {
5339
+ lines.push(`M ${filePath}`);
5340
+ }
5341
+ for (const filePath of deleted) {
5342
+ lines.push(`D ${filePath}`);
5343
+ }
5344
+ return `${lines.join("\n")}
5345
+ `;
5346
+ }
5347
+
5348
+ // src/tools/filesystemTools.ts
5349
+ var DEFAULT_READ_FILE_LINE_LIMIT = 2e3;
5350
+ var DEFAULT_READ_FILES_LINE_LIMIT = 200;
5351
+ var DEFAULT_READ_FILES_CHAR_LIMIT = 4e3;
5352
+ var DEFAULT_LIST_DIR_LIMIT = 25;
5353
+ var DEFAULT_LIST_DIR_DEPTH = 2;
5354
+ var DEFAULT_GREP_LIMIT = 100;
5355
+ var MAX_GREP_LIMIT = 2e3;
5356
+ var DEFAULT_MAX_LINE_LENGTH = 500;
5357
+ var DEFAULT_GREP_MAX_SCANNED_FILES = 2e4;
5358
+ var DEFAULT_TAB_WIDTH = 4;
5359
+ var codexReadFileInputSchema = z5.object({
5360
+ file_path: z5.string().min(1).describe("Absolute path to the file"),
5361
+ offset: z5.number().int().min(1).optional().describe("The line number to start reading from. Must be 1 or greater."),
5362
+ limit: z5.number().int().min(1).optional().describe("The maximum number of lines to return."),
5363
+ mode: z5.enum(["slice", "indentation"]).optional().describe('Optional mode selector: "slice" (default) or "indentation".'),
5364
+ indentation: z5.object({
5365
+ anchor_line: z5.number().int().min(1).optional(),
5366
+ max_levels: z5.number().int().min(0).optional(),
5367
+ include_siblings: z5.boolean().optional(),
5368
+ include_header: z5.boolean().optional(),
5369
+ max_lines: z5.number().int().min(1).optional()
5370
+ }).optional()
5371
+ });
5372
+ var codexListDirInputSchema = z5.object({
5373
+ dir_path: z5.string().min(1).describe("Absolute path to the directory to list."),
5374
+ offset: z5.number().int().min(1).optional().describe("The entry number to start listing from. Must be 1 or greater."),
5375
+ limit: z5.number().int().min(1).optional().describe("The maximum number of entries to return."),
5376
+ depth: z5.number().int().min(1).optional().describe("The maximum directory depth to traverse. Must be 1 or greater.")
5377
+ });
5378
+ var codexGrepFilesInputSchema = z5.object({
5379
+ pattern: z5.string().min(1).describe("Regular expression pattern to search for."),
5380
+ include: z5.string().optional().describe('Optional glob limiting searched files (for example "*.rs").'),
5381
+ path: z5.string().optional().describe("Directory or file path to search. Defaults to cwd."),
5382
+ limit: z5.number().int().min(1).optional().describe("Maximum number of file paths to return (defaults to 100).")
5383
+ });
5384
+ var applyPatchInputSchema = z5.object({
5385
+ input: z5.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
5386
+ });
5387
+ var geminiReadFileInputSchema = z5.object({
5388
+ file_path: z5.string().min(1),
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
+ }
5408
+ });
5409
+ var geminiWriteFileInputSchema = z5.object({
5410
+ file_path: z5.string().min(1),
5411
+ content: z5.string()
5412
+ });
5413
+ var geminiReplaceInputSchema = z5.object({
5414
+ file_path: z5.string().min(1),
5415
+ instruction: z5.string().min(1),
5416
+ old_string: z5.string(),
5417
+ new_string: z5.string(),
5418
+ expected_replacements: z5.number().int().min(1).nullish()
5419
+ });
5420
+ var geminiListDirectoryInputSchema = z5.object({
5421
+ dir_path: z5.string().min(1),
5422
+ ignore: z5.array(z5.string()).nullish(),
5423
+ file_filtering_options: z5.object({
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()
5437
+ });
5438
+ var geminiGrepSearchInputSchema = z5.object({
5439
+ pattern: z5.string().min(1),
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()
5446
+ });
5447
+ var geminiGlobInputSchema = z5.object({
5448
+ pattern: z5.string().min(1),
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()
5453
+ });
5454
+ function resolveFilesystemToolProfile(model, profile = "auto") {
5455
+ if (profile !== "auto") {
5456
+ return profile;
5457
+ }
5458
+ if (isCodexModel(model)) {
5459
+ return "codex";
5460
+ }
5461
+ if (isGeminiModel(model)) {
5462
+ return "gemini";
5463
+ }
5464
+ return "model-agnostic";
5465
+ }
5466
+ function createFilesystemToolSetForModel(model, profileOrOptions = "auto", maybeOptions) {
5467
+ if (typeof profileOrOptions === "string") {
5468
+ const resolvedProfile2 = resolveFilesystemToolProfile(model, profileOrOptions);
5469
+ if (resolvedProfile2 === "codex") {
5470
+ return createCodexFilesystemToolSet(maybeOptions);
5471
+ }
5472
+ if (resolvedProfile2 === "gemini") {
5473
+ return createGeminiFilesystemToolSet(maybeOptions);
5474
+ }
5475
+ return createModelAgnosticFilesystemToolSet(maybeOptions);
5476
+ }
5477
+ const resolvedProfile = resolveFilesystemToolProfile(model, "auto");
5478
+ if (resolvedProfile === "codex") {
5479
+ return createCodexFilesystemToolSet(profileOrOptions);
5480
+ }
5481
+ if (resolvedProfile === "gemini") {
5482
+ return createGeminiFilesystemToolSet(profileOrOptions);
5483
+ }
5484
+ return createModelAgnosticFilesystemToolSet(profileOrOptions);
5485
+ }
5486
+ function createCodexFilesystemToolSet(options = {}) {
5487
+ return {
5488
+ apply_patch: createCodexApplyPatchTool(options),
5489
+ read_file: createCodexReadFileTool(options),
5490
+ list_dir: createListDirTool(options),
5491
+ grep_files: createGrepFilesTool(options)
5492
+ };
5493
+ }
5494
+ function createGeminiFilesystemToolSet(options = {}) {
5495
+ return {
5496
+ read_file: createGeminiReadFileTool(options),
5497
+ write_file: createWriteFileTool(options),
5498
+ replace: createReplaceTool(options),
5499
+ list_directory: createListDirectoryTool(options),
5500
+ grep_search: createGrepSearchTool(options),
5501
+ glob: createGlobTool(options)
5502
+ };
5503
+ }
5504
+ function createModelAgnosticFilesystemToolSet(options = {}) {
5505
+ return createGeminiFilesystemToolSet(options);
5506
+ }
5507
+ function createCodexApplyPatchTool(options = {}) {
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) => {
5516
+ const runtime = resolveRuntime(options);
5517
+ const result = await applyPatch({
5518
+ patch: input,
5519
+ cwd: runtime.cwd,
5520
+ fs: runtime.filesystem,
5521
+ allowOutsideCwd: runtime.allowOutsideCwd,
5522
+ checkAccess: runtime.checkAccess ? async (context) => {
5523
+ await runtime.checkAccess?.({
5524
+ cwd: runtime.cwd,
5525
+ tool: "apply_patch",
5526
+ action: mapApplyPatchAction(context.kind),
5527
+ path: context.path,
5528
+ fromPath: context.fromPath,
5529
+ toPath: context.toPath
5530
+ });
5531
+ } : void 0,
5532
+ maxPatchBytes: options.applyPatch?.maxPatchBytes
5533
+ });
5534
+ return result.summary;
5535
+ }
5536
+ });
5537
+ }
5538
+ function createCodexReadFileTool(options = {}) {
5539
+ return tool({
5540
+ description: "Reads a local file with 1-indexed line numbers, supporting slice and indentation-aware block modes.",
5541
+ inputSchema: codexReadFileInputSchema,
5542
+ execute: async (input) => readFileCodex(input, options)
5543
+ });
5544
+ }
5545
+ function createListDirTool(options = {}) {
5546
+ return tool({
5547
+ description: "Lists entries in a local directory with 1-indexed entry numbers and simple type labels.",
5548
+ inputSchema: codexListDirInputSchema,
5549
+ execute: async (input) => listDirectoryCodex(input, options)
5550
+ });
5551
+ }
5552
+ function createGrepFilesTool(options = {}) {
5553
+ return tool({
5554
+ description: "Finds files whose contents match the pattern and lists them by modification time.",
5555
+ inputSchema: codexGrepFilesInputSchema,
5556
+ execute: async (input) => grepFilesCodex(input, options)
5557
+ });
5558
+ }
5559
+ function createGeminiReadFileTool(options = {}) {
5560
+ return tool({
5561
+ description: "Reads and returns the content of a specified file. Supports optional 0-based line offset and line limit.",
5562
+ inputSchema: geminiReadFileInputSchema,
5563
+ execute: async (input) => readFileGemini(input, options)
5564
+ });
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
+ }
5573
+ function createWriteFileTool(options = {}) {
5574
+ return tool({
5575
+ description: "Writes content to a specified file in the local filesystem.",
5576
+ inputSchema: geminiWriteFileInputSchema,
5577
+ execute: async (input) => writeFileGemini(input, options)
5578
+ });
5579
+ }
5580
+ function createReplaceTool(options = {}) {
5581
+ return tool({
5582
+ description: "Replaces exact literal text within a file.",
5583
+ inputSchema: geminiReplaceInputSchema,
5584
+ execute: async (input) => replaceFileContentGemini(input, options)
5585
+ });
5586
+ }
5587
+ function createListDirectoryTool(options = {}) {
5588
+ return tool({
5589
+ description: "Lists files and subdirectories directly within a specified directory path.",
5590
+ inputSchema: geminiListDirectoryInputSchema,
5591
+ execute: async (input) => listDirectoryGemini(input, options)
5592
+ });
5593
+ }
5594
+ function createGrepSearchTool(options = {}) {
5595
+ return tool({
5596
+ description: "Searches for a regex pattern within file contents.",
5597
+ inputSchema: geminiGrepSearchInputSchema,
5598
+ execute: async (input) => grepSearchGemini(input, options)
5599
+ });
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
+ }
5608
+ function createGlobTool(options = {}) {
5609
+ return tool({
5610
+ description: "Finds files matching glob patterns, sorted by modification time (newest first).",
5611
+ inputSchema: geminiGlobInputSchema,
5612
+ execute: async (input) => globFilesGemini(input, options)
5613
+ });
5614
+ }
5615
+ async function readFileCodex(input, options) {
5616
+ const runtime = resolveRuntime(options);
5617
+ if (!path5.isAbsolute(input.file_path)) {
5618
+ throw new Error("file_path must be an absolute path");
5619
+ }
5620
+ const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
5621
+ await runAccessHook2(runtime, {
5622
+ cwd: runtime.cwd,
5623
+ tool: "read_file",
5624
+ action: "read",
5625
+ path: filePath
5626
+ });
5627
+ const content = await runtime.filesystem.readTextFile(filePath);
5628
+ const lines = splitLines(content);
5629
+ const offset = input.offset ?? 1;
5630
+ const limit = input.limit ?? DEFAULT_READ_FILE_LINE_LIMIT;
5631
+ const mode = input.mode ?? "slice";
5632
+ if (offset > lines.length) {
5633
+ throw new Error("offset exceeds file length");
5634
+ }
5635
+ if (mode === "slice") {
5636
+ const output = [];
5637
+ const lastLine = Math.min(lines.length, offset + limit - 1);
5638
+ for (let lineNumber = offset; lineNumber <= lastLine; lineNumber += 1) {
5639
+ const line = lines[lineNumber - 1] ?? "";
5640
+ output.push(`L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`);
5641
+ }
5642
+ return output.join("\n");
5643
+ }
5644
+ const indentation = input.indentation ?? {};
5645
+ const anchorLine = indentation.anchor_line ?? offset;
5646
+ if (anchorLine < 1 || anchorLine > lines.length) {
5647
+ throw new Error("anchor_line exceeds file length");
5648
+ }
5649
+ const records = lines.map((line, index) => ({
5650
+ number: index + 1,
5651
+ raw: line,
5652
+ display: truncateAtCodePointBoundary(line, runtime.maxLineLength),
5653
+ indent: measureIndent(line, DEFAULT_TAB_WIDTH)
5654
+ }));
5655
+ const selected = readWithIndentationMode({
5656
+ records,
5657
+ anchorLine,
5658
+ limit,
5659
+ maxLevels: indentation.max_levels ?? 0,
5660
+ includeSiblings: indentation.include_siblings ?? false,
5661
+ includeHeader: indentation.include_header ?? true,
5662
+ maxLines: indentation.max_lines
5663
+ });
5664
+ return selected.map((record) => `L${record.number}: ${record.display}`).join("\n");
5665
+ }
5666
+ async function listDirectoryCodex(input, options) {
5667
+ const runtime = resolveRuntime(options);
5668
+ if (!path5.isAbsolute(input.dir_path)) {
5669
+ throw new Error("dir_path must be an absolute path");
5670
+ }
5671
+ const dirPath = resolvePathWithPolicy(input.dir_path, runtime.cwd, runtime.allowOutsideCwd);
5672
+ await runAccessHook2(runtime, {
5673
+ cwd: runtime.cwd,
5674
+ tool: "list_dir",
5675
+ action: "list",
5676
+ path: dirPath
5677
+ });
5678
+ const stats = await runtime.filesystem.stat(dirPath);
5679
+ if (stats.kind !== "directory") {
5680
+ throw new Error(`failed to read directory: "${dirPath}" is not a directory`);
5681
+ }
5682
+ const offset = input.offset ?? 1;
5683
+ const limit = input.limit ?? DEFAULT_LIST_DIR_LIMIT;
5684
+ const depth = input.depth ?? DEFAULT_LIST_DIR_DEPTH;
5685
+ const entries = await collectDirectoryEntries(
5686
+ runtime.filesystem,
5687
+ dirPath,
5688
+ depth,
5689
+ runtime.maxLineLength
5690
+ );
5691
+ if (offset > entries.length) {
5692
+ throw new Error("offset exceeds directory entry count");
5693
+ }
5694
+ const startIndex = offset - 1;
5695
+ const remaining = entries.length - startIndex;
5696
+ const cappedLimit = Math.min(limit, remaining);
5697
+ const selected = entries.slice(startIndex, startIndex + cappedLimit);
5698
+ const output = [`Absolute path: ${dirPath}`];
5699
+ for (const entry of selected) {
5700
+ output.push(formatListEntry(entry));
5701
+ }
5702
+ if (startIndex + cappedLimit < entries.length) {
5703
+ output.push(`More than ${cappedLimit} entries found`);
5704
+ }
5705
+ return output.join("\n");
5706
+ }
5707
+ async function grepFilesCodex(input, options) {
5708
+ const runtime = resolveRuntime(options);
5709
+ const pattern = input.pattern.trim();
5710
+ if (pattern.length === 0) {
5711
+ throw new Error("pattern must not be empty");
5712
+ }
5713
+ const regex = compileRegex(pattern);
5714
+ const searchPath = resolvePathWithPolicy(
5715
+ input.path ?? runtime.cwd,
5716
+ runtime.cwd,
5717
+ runtime.allowOutsideCwd
5718
+ );
5719
+ await runAccessHook2(runtime, {
5720
+ cwd: runtime.cwd,
5721
+ tool: "grep_files",
5722
+ action: "search",
5723
+ path: searchPath,
5724
+ pattern,
5725
+ include: input.include?.trim()
5726
+ });
5727
+ const searchPathInfo = await runtime.filesystem.stat(searchPath);
5728
+ const filesToScan = await collectSearchFiles({
5729
+ filesystem: runtime.filesystem,
5730
+ searchPath,
5731
+ rootKind: searchPathInfo.kind,
5732
+ maxScannedFiles: runtime.grepMaxScannedFiles
5733
+ });
5734
+ const includeMatcher = input.include ? createGlobMatcher(input.include) : null;
5735
+ const matches = [];
5736
+ for (const filePath of filesToScan) {
5737
+ const relativePath = toDisplayPath2(filePath, runtime.cwd);
5738
+ if (includeMatcher && !includeMatcher(relativePath)) {
5739
+ continue;
5740
+ }
5741
+ const fileContent = await runtime.filesystem.readTextFile(filePath);
5742
+ if (!regex.test(fileContent)) {
5743
+ continue;
5744
+ }
5745
+ const stats = await runtime.filesystem.stat(filePath);
5746
+ matches.push({ filePath: normalizeSlashes(relativePath), mtimeMs: stats.mtimeMs });
5747
+ }
5748
+ if (matches.length === 0) {
5749
+ return "No matches found.";
5750
+ }
5751
+ matches.sort((left, right) => right.mtimeMs - left.mtimeMs);
5752
+ const limit = Math.min(input.limit ?? DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
5753
+ return matches.slice(0, limit).map((match) => match.filePath).join("\n");
5754
+ }
5755
+ async function readFileGemini(input, options) {
5756
+ const runtime = resolveRuntime(options);
5757
+ const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
5758
+ await runAccessHook2(runtime, {
5759
+ cwd: runtime.cwd,
5760
+ tool: "read_file",
5761
+ action: "read",
5762
+ path: filePath
5763
+ });
5764
+ const content = await runtime.filesystem.readTextFile(filePath);
5765
+ const lines = splitLines(content);
5766
+ const offset = Math.max(0, input.offset ?? 0);
5767
+ const limit = input.limit ?? DEFAULT_READ_FILE_LINE_LIMIT;
5768
+ if (offset >= lines.length) {
5769
+ return "";
5770
+ }
5771
+ const end = Math.min(lines.length, offset + limit);
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");
5825
+ }
5826
+ async function writeFileGemini(input, options) {
5827
+ const runtime = resolveRuntime(options);
5828
+ const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
5829
+ await runAccessHook2(runtime, {
5830
+ cwd: runtime.cwd,
5831
+ tool: "write_file",
5832
+ action: "write",
5833
+ path: filePath
5834
+ });
5835
+ await runtime.filesystem.ensureDir(path5.dirname(filePath));
5836
+ await runtime.filesystem.writeTextFile(filePath, input.content);
5837
+ return `Successfully wrote file: ${toDisplayPath2(filePath, runtime.cwd)}`;
5838
+ }
5839
+ async function replaceFileContentGemini(input, options) {
5840
+ const runtime = resolveRuntime(options);
5841
+ const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
5842
+ await runAccessHook2(runtime, {
5843
+ cwd: runtime.cwd,
5844
+ tool: "replace",
5845
+ action: "write",
5846
+ path: filePath
5847
+ });
5848
+ const expectedReplacements = input.expected_replacements ?? 1;
5849
+ const oldValue = input.old_string;
5850
+ const newValue = input.new_string;
5851
+ let originalContent = "";
5852
+ try {
5853
+ originalContent = await runtime.filesystem.readTextFile(filePath);
5854
+ } catch (error) {
5855
+ if (isNoEntError(error) && oldValue.length === 0) {
5856
+ await runtime.filesystem.ensureDir(path5.dirname(filePath));
5857
+ await runtime.filesystem.writeTextFile(filePath, newValue);
5858
+ return `Successfully wrote new file: ${toDisplayPath2(filePath, runtime.cwd)}`;
5859
+ }
5860
+ throw error;
5861
+ }
5862
+ if (oldValue === newValue) {
5863
+ throw new Error("No changes to apply. old_string and new_string are identical.");
5864
+ }
5865
+ const occurrences = countOccurrences(originalContent, oldValue);
5866
+ if (occurrences === 0) {
5867
+ throw new Error("Failed to edit, could not find old_string in file.");
5868
+ }
5869
+ if (occurrences !== expectedReplacements) {
5870
+ throw new Error(
5871
+ `Failed to edit, expected ${expectedReplacements} occurrence(s) but found ${occurrences}.`
5872
+ );
5873
+ }
5874
+ const updatedContent = safeReplaceAll(originalContent, oldValue, newValue);
5875
+ await runtime.filesystem.writeTextFile(filePath, updatedContent);
5876
+ return `Successfully replaced ${occurrences} occurrence(s) in ${toDisplayPath2(filePath, runtime.cwd)}.`;
5877
+ }
5878
+ async function listDirectoryGemini(input, options) {
5879
+ const runtime = resolveRuntime(options);
5880
+ const dirPath = resolvePathWithPolicy(input.dir_path, runtime.cwd, runtime.allowOutsideCwd);
5881
+ await runAccessHook2(runtime, {
5882
+ cwd: runtime.cwd,
5883
+ tool: "list_directory",
5884
+ action: "list",
5885
+ path: dirPath
5886
+ });
5887
+ const stats = await runtime.filesystem.stat(dirPath);
5888
+ if (stats.kind !== "directory") {
5889
+ throw new Error(`Path is not a directory: ${dirPath}`);
5890
+ }
5891
+ const entries = await runtime.filesystem.readDir(dirPath);
5892
+ const ignoreMatchers = (input.ignore ?? []).map((pattern) => createGlobMatcher(pattern));
5893
+ const filtered = entries.filter((entry) => {
5894
+ if (ignoreMatchers.length === 0) {
5895
+ return true;
5896
+ }
5897
+ return !ignoreMatchers.some((matches) => matches(entry.name));
5898
+ }).sort((left, right) => left.name.localeCompare(right.name));
5899
+ if (filtered.length === 0) {
5900
+ return `Directory ${toDisplayPath2(dirPath, runtime.cwd)} is empty.`;
5901
+ }
5902
+ return filtered.map((entry) => {
5903
+ const label = entry.kind === "directory" ? `${entry.name}/` : entry.name;
5904
+ return label;
5905
+ }).join("\n");
5906
+ }
5907
+ async function rgSearchGemini(input, options, toolName = "rg_search") {
5908
+ const runtime = resolveRuntime(options);
5909
+ const pattern = input.pattern.trim();
5910
+ if (pattern.length === 0) {
5911
+ throw new Error("pattern must not be empty");
5912
+ }
5913
+ const glob = input.glob?.trim();
5914
+ const searchPath = resolvePathWithPolicy(
5915
+ input.path ?? runtime.cwd,
5916
+ runtime.cwd,
5917
+ runtime.allowOutsideCwd
5918
+ );
5919
+ await runAccessHook2(runtime, {
5920
+ cwd: runtime.cwd,
5921
+ tool: toolName,
5922
+ action: "search",
5923
+ path: searchPath,
5924
+ pattern,
5925
+ include: glob
5926
+ });
5927
+ const searchPathInfo = await runtime.filesystem.stat(searchPath);
5928
+ const filesToScan = await collectSearchFiles({
5929
+ filesystem: runtime.filesystem,
5930
+ searchPath,
5931
+ rootKind: searchPathInfo.kind,
5932
+ maxScannedFiles: runtime.grepMaxScannedFiles
5933
+ });
5934
+ const matcher = glob ? createGlobMatcher(glob) : null;
5935
+ const patternRegex = compileRegex(pattern, input.case_sensitive === true ? "m" : "im");
5936
+ const excludeRegex = input.exclude_pattern ? compileRegex(input.exclude_pattern) : null;
5937
+ const totalMaxMatches = input.max_results ?? DEFAULT_GREP_LIMIT;
5938
+ const perFileMaxMatches = input.max_matches_per_file ?? Number.POSITIVE_INFINITY;
5939
+ const matches = [];
5940
+ const fileMatches = /* @__PURE__ */ new Set();
5941
+ for (const filePath of filesToScan) {
5942
+ const relativePath = normalizeSlashes(toDisplayPath2(filePath, runtime.cwd));
5943
+ if (matcher && !matcher(relativePath)) {
5944
+ continue;
5945
+ }
5946
+ const content = await runtime.filesystem.readTextFile(filePath);
5947
+ const lines = splitLines(content);
5948
+ let fileMatchCount = 0;
5949
+ for (let index = 0; index < lines.length; index += 1) {
5950
+ const line = lines[index] ?? "";
5951
+ if (!patternRegex.test(line)) {
5952
+ continue;
5953
+ }
5954
+ if (excludeRegex?.test(line)) {
5955
+ continue;
5956
+ }
5957
+ if (fileMatches.has(relativePath) === false) {
5958
+ fileMatches.add(relativePath);
5959
+ }
5960
+ if (input.names_only) {
5961
+ continue;
5962
+ }
5963
+ matches.push({
5964
+ filePath: relativePath,
5965
+ mtimeMs: 0,
5966
+ lineNumber: index + 1,
5967
+ line
5968
+ });
5969
+ fileMatchCount += 1;
5970
+ if (fileMatchCount >= perFileMaxMatches || matches.length >= totalMaxMatches) {
5971
+ break;
5972
+ }
5973
+ }
5974
+ if (input.names_only && fileMatches.size >= totalMaxMatches) {
5975
+ break;
5976
+ }
5977
+ if (!input.names_only && matches.length >= totalMaxMatches) {
5978
+ break;
5979
+ }
5980
+ }
5981
+ if (input.names_only) {
5982
+ if (fileMatches.size === 0) {
5983
+ return "No matches found.";
5984
+ }
5985
+ return [...fileMatches].slice(0, totalMaxMatches).join("\n");
5986
+ }
5987
+ if (matches.length === 0) {
5988
+ return "No matches found.";
5989
+ }
5990
+ return matches.slice(0, totalMaxMatches).map((match) => `${match.filePath}:${match.lineNumber}:${match.line ?? ""}`).join("\n");
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
+ }
6007
+ async function globFilesGemini(input, options) {
6008
+ const runtime = resolveRuntime(options);
6009
+ const dirPath = resolvePathWithPolicy(
6010
+ input.dir_path ?? runtime.cwd,
6011
+ runtime.cwd,
6012
+ runtime.allowOutsideCwd
6013
+ );
6014
+ await runAccessHook2(runtime, {
6015
+ cwd: runtime.cwd,
6016
+ tool: "glob",
6017
+ action: "search",
6018
+ path: dirPath,
6019
+ pattern: input.pattern
6020
+ });
6021
+ const dirStats = await runtime.filesystem.stat(dirPath);
6022
+ if (dirStats.kind !== "directory") {
6023
+ throw new Error(`Path is not a directory: ${dirPath}`);
6024
+ }
6025
+ const matcher = createGlobMatcher(input.pattern, input.case_sensitive === true);
6026
+ const files = await collectSearchFiles({
6027
+ filesystem: runtime.filesystem,
6028
+ searchPath: dirPath,
6029
+ rootKind: "directory",
6030
+ maxScannedFiles: runtime.grepMaxScannedFiles
6031
+ });
6032
+ const matched = [];
6033
+ for (const filePath of files) {
6034
+ const relativePath = normalizeSlashes(path5.relative(dirPath, filePath));
6035
+ if (!matcher(relativePath)) {
6036
+ continue;
6037
+ }
6038
+ const fileStats = await runtime.filesystem.stat(filePath);
6039
+ matched.push({
6040
+ filePath,
6041
+ mtimeMs: fileStats.mtimeMs
6042
+ });
6043
+ }
6044
+ if (matched.length === 0) {
6045
+ return "No files found.";
6046
+ }
6047
+ matched.sort((left, right) => right.mtimeMs - left.mtimeMs);
6048
+ return matched.map((entry) => normalizeSlashes(toDisplayPath2(entry.filePath, runtime.cwd))).join("\n");
6049
+ }
6050
+ function resolveRuntime(options) {
6051
+ return {
6052
+ cwd: path5.resolve(options.cwd ?? process.cwd()),
6053
+ filesystem: options.fs ?? createNodeAgentFilesystem(),
6054
+ allowOutsideCwd: options.allowOutsideCwd === true,
6055
+ checkAccess: options.checkAccess,
6056
+ maxLineLength: options.maxLineLength ?? DEFAULT_MAX_LINE_LENGTH,
6057
+ grepMaxScannedFiles: options.grepMaxScannedFiles ?? DEFAULT_GREP_MAX_SCANNED_FILES
6058
+ };
6059
+ }
6060
+ async function runAccessHook2(runtime, context) {
6061
+ if (!runtime.checkAccess) {
6062
+ return;
6063
+ }
6064
+ await runtime.checkAccess(context);
6065
+ }
6066
+ function isCodexModel(model) {
6067
+ const normalized = model.startsWith("chatgpt-") ? model.slice("chatgpt-".length) : model;
6068
+ return normalized.includes("codex");
6069
+ }
6070
+ function isGeminiModel(model) {
6071
+ return model.startsWith("gemini-");
6072
+ }
6073
+ function mapApplyPatchAction(action) {
6074
+ if (action === "add" || action === "update") {
6075
+ return "write";
6076
+ }
6077
+ if (action === "delete") {
6078
+ return "delete";
6079
+ }
6080
+ return "move";
6081
+ }
6082
+ function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
6083
+ const absolutePath = path5.isAbsolute(inputPath) ? path5.resolve(inputPath) : path5.resolve(cwd, inputPath);
6084
+ if (!allowOutsideCwd && !isPathInsideCwd2(absolutePath, cwd)) {
6085
+ throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
6086
+ }
6087
+ return absolutePath;
6088
+ }
6089
+ function isPathInsideCwd2(candidatePath, cwd) {
6090
+ const relative = path5.relative(cwd, candidatePath);
6091
+ return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
6092
+ }
6093
+ function toDisplayPath2(absolutePath, cwd) {
6094
+ const relative = path5.relative(cwd, absolutePath);
6095
+ if (relative === "") {
6096
+ return ".";
6097
+ }
6098
+ if (!relative.startsWith("..") && !path5.isAbsolute(relative)) {
6099
+ return relative;
6100
+ }
6101
+ return absolutePath;
6102
+ }
6103
+ function splitLines(content) {
6104
+ const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
6105
+ const lines = normalized.split("\n");
6106
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
6107
+ lines.pop();
6108
+ }
6109
+ return lines;
6110
+ }
6111
+ function truncateAtCodePointBoundary(value, maxLength) {
6112
+ if (value.length <= maxLength) {
6113
+ return value;
6114
+ }
6115
+ return Array.from(value).slice(0, maxLength).join("");
6116
+ }
6117
+ function measureIndent(line, tabWidth) {
6118
+ let count = 0;
6119
+ for (const char of line) {
6120
+ if (char === " ") {
6121
+ count += 1;
6122
+ continue;
6123
+ }
6124
+ if (char === " ") {
6125
+ count += tabWidth;
6126
+ continue;
6127
+ }
6128
+ break;
6129
+ }
6130
+ return count;
6131
+ }
6132
+ function computeEffectiveIndents(records) {
6133
+ const effective = [];
6134
+ let previous = 0;
6135
+ for (const record of records) {
6136
+ if (record.raw.trim().length === 0) {
6137
+ effective.push(previous);
6138
+ } else {
6139
+ previous = record.indent;
6140
+ effective.push(previous);
6141
+ }
6142
+ }
6143
+ return effective;
6144
+ }
6145
+ function trimBoundaryBlankLines(records) {
6146
+ while (records.length > 0 && records[0]?.raw.trim().length === 0) {
6147
+ records.shift();
6148
+ }
6149
+ while (records.length > 0 && records[records.length - 1]?.raw.trim().length === 0) {
6150
+ records.pop();
6151
+ }
6152
+ }
6153
+ function isCommentLine(line) {
6154
+ const trimmed = line.trim();
6155
+ return trimmed.startsWith("#") || trimmed.startsWith("//") || trimmed.startsWith("--");
6156
+ }
6157
+ function readWithIndentationMode(params) {
6158
+ const { records, anchorLine, limit, maxLevels, includeSiblings, includeHeader, maxLines } = params;
6159
+ const anchorIndex = anchorLine - 1;
6160
+ const effectiveIndents = computeEffectiveIndents(records);
6161
+ const anchorIndent = effectiveIndents[anchorIndex] ?? 0;
6162
+ const minIndent = maxLevels === 0 ? 0 : Math.max(anchorIndent - maxLevels * DEFAULT_TAB_WIDTH, 0);
6163
+ const guardLimit = maxLines ?? limit;
6164
+ const finalLimit = Math.min(limit, guardLimit, records.length);
6165
+ if (finalLimit <= 1) {
6166
+ return [records[anchorIndex]].filter((entry) => Boolean(entry));
6167
+ }
6168
+ let upper = anchorIndex - 1;
6169
+ let lower = anchorIndex + 1;
6170
+ let upperMinIndentHits = 0;
6171
+ let lowerMinIndentHits = 0;
6172
+ const output = [records[anchorIndex]].filter(
6173
+ (entry) => Boolean(entry)
6174
+ );
6175
+ while (output.length < finalLimit) {
6176
+ let progressed = 0;
6177
+ if (upper >= 0) {
6178
+ const candidate = records[upper];
6179
+ const candidateIndent = effectiveIndents[upper] ?? 0;
6180
+ if (candidate && candidateIndent >= minIndent) {
6181
+ output.unshift(candidate);
6182
+ progressed += 1;
6183
+ upper -= 1;
6184
+ if (candidateIndent === minIndent && !includeSiblings) {
6185
+ const allowHeaderComment = includeHeader && isCommentLine(candidate.raw);
6186
+ const canTakeLine = allowHeaderComment || upperMinIndentHits === 0;
6187
+ if (canTakeLine) {
6188
+ upperMinIndentHits += 1;
6189
+ } else {
6190
+ output.shift();
6191
+ progressed -= 1;
6192
+ upper = -1;
6193
+ }
6194
+ }
6195
+ if (output.length >= finalLimit) {
6196
+ break;
6197
+ }
6198
+ } else {
6199
+ upper = -1;
6200
+ }
6201
+ }
6202
+ if (lower < records.length) {
6203
+ const candidate = records[lower];
6204
+ const candidateIndent = effectiveIndents[lower] ?? 0;
6205
+ if (candidate && candidateIndent >= minIndent) {
6206
+ output.push(candidate);
6207
+ progressed += 1;
6208
+ lower += 1;
6209
+ if (candidateIndent === minIndent && !includeSiblings) {
6210
+ if (lowerMinIndentHits > 0) {
6211
+ output.pop();
6212
+ progressed -= 1;
6213
+ lower = records.length;
6214
+ }
6215
+ lowerMinIndentHits += 1;
6216
+ }
6217
+ } else {
6218
+ lower = records.length;
6219
+ }
6220
+ }
6221
+ if (progressed === 0) {
6222
+ break;
6223
+ }
6224
+ }
6225
+ trimBoundaryBlankLines(output);
6226
+ return output;
6227
+ }
6228
+ async function collectDirectoryEntries(filesystem, rootPath, depth, maxLineLength) {
6229
+ const queue = [
6230
+ { path: rootPath, relativePrefix: "", remainingDepth: depth }
6231
+ ];
6232
+ const records = [];
6233
+ while (queue.length > 0) {
6234
+ const next = queue.shift();
6235
+ if (!next) {
6236
+ break;
6237
+ }
6238
+ const entries = await filesystem.readDir(next.path);
6239
+ const nextEntries = [...entries].map((entry) => {
6240
+ const relativePath = next.relativePrefix ? `${next.relativePrefix}/${entry.name}` : entry.name;
6241
+ return {
6242
+ entry,
6243
+ relativePath,
6244
+ depth: next.relativePrefix.length === 0 ? 0 : next.relativePrefix.split("/").length,
6245
+ sortName: normalizeSlashes(relativePath)
6246
+ };
6247
+ }).sort((left, right) => left.sortName.localeCompare(right.sortName));
6248
+ for (const item of nextEntries) {
6249
+ if (item.entry.kind === "directory" && next.remainingDepth > 1) {
6250
+ queue.push({
6251
+ path: item.entry.path,
6252
+ relativePrefix: item.relativePath,
6253
+ remainingDepth: next.remainingDepth - 1
6254
+ });
6255
+ }
6256
+ records.push({
6257
+ name: item.sortName,
6258
+ displayName: truncateAtCodePointBoundary(item.entry.name, maxLineLength),
6259
+ depth: item.depth,
6260
+ kind: item.entry.kind
6261
+ });
6262
+ }
6263
+ }
6264
+ records.sort((left, right) => left.name.localeCompare(right.name));
6265
+ return records;
6266
+ }
6267
+ function formatListEntry(entry) {
6268
+ const indent = " ".repeat(entry.depth * 2);
6269
+ let name = entry.displayName;
6270
+ if (entry.kind === "directory") {
6271
+ name += "/";
6272
+ } else if (entry.kind === "symlink") {
6273
+ name += "@";
6274
+ } else if (entry.kind === "other") {
6275
+ name += "?";
6276
+ }
6277
+ return `${indent}${name}`;
6278
+ }
6279
+ async function collectSearchFiles(params) {
6280
+ const { filesystem, searchPath, rootKind, maxScannedFiles } = params;
6281
+ if (rootKind === "file") {
6282
+ return [searchPath];
6283
+ }
6284
+ const queue = [searchPath];
6285
+ const files = [];
6286
+ while (queue.length > 0) {
6287
+ const current = queue.shift();
6288
+ if (!current) {
6289
+ break;
6290
+ }
6291
+ const entries = await filesystem.readDir(current);
6292
+ for (const entry of entries) {
6293
+ if (entry.kind === "directory") {
6294
+ queue.push(entry.path);
6295
+ continue;
6296
+ }
6297
+ if (entry.kind !== "file") {
6298
+ continue;
6299
+ }
6300
+ files.push(entry.path);
6301
+ if (files.length >= maxScannedFiles) {
6302
+ return files;
6303
+ }
6304
+ }
6305
+ }
6306
+ return files;
6307
+ }
6308
+ function compileRegex(pattern, flags = "m") {
6309
+ try {
6310
+ return new RegExp(pattern, flags);
6311
+ } catch (error) {
6312
+ const message = error instanceof Error ? error.message : String(error);
6313
+ throw new Error(`invalid regex pattern: ${message}`);
6314
+ }
6315
+ }
6316
+ function createGlobMatcher(pattern, caseSensitive = false) {
6317
+ const expanded = expandBracePatterns(normalizeSlashes(pattern.trim()));
6318
+ const flags = caseSensitive ? "" : "i";
6319
+ const compiled = expanded.map((entry) => ({
6320
+ regex: globToRegex(entry, flags),
6321
+ applyToBasename: !entry.includes("/")
6322
+ }));
6323
+ return (candidatePath) => {
6324
+ const normalizedPath = normalizeSlashes(candidatePath);
6325
+ const basename = path5.posix.basename(normalizedPath);
6326
+ return compiled.some(
6327
+ (entry) => entry.regex.test(entry.applyToBasename ? basename : normalizedPath)
6328
+ );
6329
+ };
6330
+ }
6331
+ function globToRegex(globPattern, flags) {
6332
+ let source = "^";
6333
+ for (let index = 0; index < globPattern.length; index += 1) {
6334
+ const char = globPattern[index];
6335
+ const nextChar = globPattern[index + 1];
6336
+ if (char === void 0) {
6337
+ continue;
6338
+ }
6339
+ if (char === "*" && nextChar === "*") {
6340
+ source += ".*";
6341
+ index += 1;
6342
+ continue;
6343
+ }
6344
+ if (char === "*") {
6345
+ source += "[^/]*";
6346
+ continue;
6347
+ }
6348
+ if (char === "?") {
6349
+ source += "[^/]";
6350
+ continue;
6351
+ }
6352
+ source += escapeRegexCharacter(char);
6353
+ }
6354
+ source += "$";
6355
+ return new RegExp(source, flags);
6356
+ }
6357
+ function expandBracePatterns(pattern) {
6358
+ const start = pattern.indexOf("{");
6359
+ if (start === -1) {
6360
+ return [pattern];
6361
+ }
6362
+ let depth = 0;
6363
+ let end = -1;
6364
+ for (let index = start; index < pattern.length; index += 1) {
6365
+ const char = pattern[index];
6366
+ if (char === "{") {
6367
+ depth += 1;
6368
+ continue;
6369
+ }
6370
+ if (char === "}") {
6371
+ depth -= 1;
6372
+ if (depth === 0) {
6373
+ end = index;
6374
+ break;
6375
+ }
6376
+ }
6377
+ }
6378
+ if (end === -1) {
6379
+ return [pattern];
6380
+ }
6381
+ const prefix = pattern.slice(0, start);
6382
+ const suffix = pattern.slice(end + 1);
6383
+ const body = pattern.slice(start + 1, end);
6384
+ const variants = splitTopLevel(body, ",");
6385
+ const expanded = [];
6386
+ for (const variant of variants) {
6387
+ expanded.push(...expandBracePatterns(`${prefix}${variant}${suffix}`));
6388
+ }
6389
+ return expanded;
6390
+ }
6391
+ function splitTopLevel(value, separator) {
6392
+ const parts = [];
6393
+ let depth = 0;
6394
+ let current = "";
6395
+ for (const char of value) {
6396
+ if (char === "{") {
6397
+ depth += 1;
6398
+ current += char;
6399
+ continue;
6400
+ }
6401
+ if (char === "}") {
6402
+ depth = Math.max(0, depth - 1);
6403
+ current += char;
6404
+ continue;
6405
+ }
6406
+ if (char === separator && depth === 0) {
6407
+ parts.push(current);
6408
+ current = "";
6409
+ continue;
6410
+ }
6411
+ current += char;
6412
+ }
6413
+ parts.push(current);
6414
+ return parts;
6415
+ }
6416
+ function escapeRegexCharacter(char) {
6417
+ return /[.*+?^${}()|[\]\\]/u.test(char) ? `\\${char}` : char;
6418
+ }
6419
+ function normalizeSlashes(value) {
6420
+ return value.replaceAll("\\", "/");
6421
+ }
6422
+ function countOccurrences(text, search) {
6423
+ if (search.length === 0) {
6424
+ return 0;
6425
+ }
6426
+ return text.split(search).length - 1;
6427
+ }
6428
+ function safeReplaceAll(text, search, replacement) {
6429
+ if (search.length === 0) {
6430
+ return text;
6431
+ }
6432
+ return text.split(search).join(replacement);
6433
+ }
6434
+ function isNoEntError(error) {
6435
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
6436
+ }
6437
+
6438
+ // src/agent.ts
6439
+ async function runAgentLoop(request) {
6440
+ const { tools: customTools, filesystemTool, filesystem_tool, ...toolLoopRequest } = request;
6441
+ const filesystemSelection = filesystemTool ?? filesystem_tool;
6442
+ const filesystemTools = resolveFilesystemTools(request.model, filesystemSelection);
6443
+ const mergedTools = mergeToolSets(filesystemTools, customTools ?? {});
6444
+ if (Object.keys(mergedTools).length === 0) {
6445
+ throw new Error(
6446
+ "runAgentLoop requires at least one tool. Provide `tools` or enable `filesystemTool`."
6447
+ );
6448
+ }
6449
+ return runToolLoop({
6450
+ ...toolLoopRequest,
6451
+ tools: mergedTools
6452
+ });
6453
+ }
6454
+ function resolveFilesystemTools(model, selection) {
6455
+ if (selection === void 0 || selection === false) {
6456
+ return {};
6457
+ }
6458
+ if (selection === true) {
6459
+ return createFilesystemToolSetForModel(model, "auto");
6460
+ }
6461
+ if (typeof selection === "string") {
6462
+ return createFilesystemToolSetForModel(model, selection);
6463
+ }
6464
+ if (selection.enabled === false) {
6465
+ return {};
6466
+ }
6467
+ if (selection.options && selection.profile !== void 0) {
6468
+ return createFilesystemToolSetForModel(model, selection.profile, selection.options);
6469
+ }
6470
+ if (selection.options) {
6471
+ return createFilesystemToolSetForModel(model, selection.options);
6472
+ }
6473
+ return createFilesystemToolSetForModel(model, selection.profile ?? "auto");
6474
+ }
6475
+ function mergeToolSets(base, extra) {
6476
+ const merged = { ...base };
6477
+ for (const [toolName, toolSpec] of Object.entries(extra)) {
6478
+ if (Object.hasOwn(merged, toolName)) {
6479
+ throw new Error(
6480
+ `Duplicate tool name "${toolName}" in runAgentLoop. Rename the custom tool or disable that filesystem tool.`
6481
+ );
6482
+ }
6483
+ merged[toolName] = toolSpec;
6484
+ }
6485
+ return merged;
6486
+ }
3861
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,
6495
+ InMemoryAgentFilesystem,
3862
6496
  LlmJsonCallError,
3863
6497
  appendMarkdownSourcesSection,
6498
+ applyPatch,
3864
6499
  configureGemini,
3865
6500
  convertGooglePartsToLlmParts,
6501
+ createApplyPatchTool,
6502
+ createCodexApplyPatchTool,
6503
+ createCodexFilesystemToolSet,
6504
+ createCodexReadFileTool,
6505
+ createFilesystemToolSetForModel,
6506
+ createGeminiFilesystemToolSet,
6507
+ createGeminiReadFileTool,
6508
+ createGlobTool,
6509
+ createGrepFilesTool,
6510
+ createGrepSearchTool,
6511
+ createInMemoryAgentFilesystem,
6512
+ createListDirTool,
6513
+ createListDirectoryTool,
6514
+ createModelAgnosticFilesystemToolSet,
6515
+ createNodeAgentFilesystem,
6516
+ createReadFilesTool,
6517
+ createReplaceTool,
6518
+ createRgSearchTool,
6519
+ createWriteFileTool,
6520
+ customTool,
3866
6521
  encodeChatGptAuthJson,
3867
6522
  encodeChatGptAuthJsonB64,
3868
6523
  estimateCallCostUsd,
@@ -3873,11 +6528,15 @@ export {
3873
6528
  generateText,
3874
6529
  getChatGptAuthProfile,
3875
6530
  getCurrentToolCallContext,
6531
+ isFireworksModelId,
3876
6532
  isGeminiModelId,
3877
6533
  loadEnvFromFile,
3878
6534
  loadLocalEnv,
3879
6535
  parseJsonFromLlmText,
3880
6536
  refreshChatGptOauthToken,
6537
+ resolveFilesystemToolProfile,
6538
+ resolveFireworksModelId,
6539
+ runAgentLoop,
3881
6540
  runToolLoop,
3882
6541
  sanitisePartForLogging,
3883
6542
  streamJson,