@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/README.md +134 -11
- package/dist/index.cjs +2871 -179
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +263 -6
- package/dist/index.d.ts +263 -6
- package/dist/index.js +2844 -185
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/llm.ts
|
|
2
|
-
import { Buffer as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
281
|
-
var
|
|
282
|
-
var
|
|
283
|
-
var
|
|
284
|
-
var
|
|
285
|
-
var
|
|
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
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
|
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 ??
|
|
389
|
-
const profile = isExpired(baseProfile) ? await
|
|
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
|
|
399
|
-
const
|
|
400
|
-
|
|
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(
|
|
560
|
+
throw new Error(`Codex auth store at ${authPath} is missing chatgpt_account_id/account_id.`);
|
|
403
561
|
}
|
|
404
562
|
return {
|
|
405
|
-
access
|
|
406
|
-
refresh
|
|
563
|
+
access,
|
|
564
|
+
refresh,
|
|
407
565
|
expires,
|
|
408
566
|
accountId,
|
|
409
|
-
idToken:
|
|
567
|
+
idToken: idToken ?? void 0
|
|
410
568
|
};
|
|
411
569
|
}
|
|
412
|
-
function
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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("
|
|
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:
|
|
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 =
|
|
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
|
|
515
|
-
|
|
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
|
-
|
|
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 =
|
|
665
|
-
const 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
|
|
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
|
|
1459
|
+
return scheduler2.run(async () => fn(await getGeminiClient()));
|
|
1137
1460
|
}
|
|
1138
1461
|
|
|
1139
1462
|
// src/openai/client.ts
|
|
1140
|
-
import
|
|
1141
|
-
import { Agent, fetch as
|
|
1142
|
-
var
|
|
1143
|
-
var
|
|
1144
|
-
var
|
|
1145
|
-
var
|
|
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 (
|
|
1149
|
-
return
|
|
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
|
-
|
|
1154
|
-
return
|
|
1476
|
+
cachedTimeoutMs2 = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_OPENAI_TIMEOUT_MS;
|
|
1477
|
+
return cachedTimeoutMs2;
|
|
1155
1478
|
}
|
|
1156
1479
|
function getOpenAiFetch() {
|
|
1157
|
-
if (
|
|
1158
|
-
return
|
|
1480
|
+
if (cachedFetch2) {
|
|
1481
|
+
return cachedFetch2;
|
|
1159
1482
|
}
|
|
1160
1483
|
const timeoutMs = resolveOpenAiTimeoutMs();
|
|
1161
|
-
const dispatcher = new
|
|
1484
|
+
const dispatcher = new Agent2({
|
|
1162
1485
|
bodyTimeout: timeoutMs,
|
|
1163
1486
|
headersTimeout: timeoutMs
|
|
1164
1487
|
});
|
|
1165
|
-
|
|
1166
|
-
return
|
|
1488
|
+
cachedFetch2 = ((input, init) => {
|
|
1489
|
+
return undiciFetch2(input, {
|
|
1167
1490
|
...init ?? {},
|
|
1168
1491
|
dispatcher
|
|
1169
1492
|
});
|
|
1170
1493
|
});
|
|
1171
|
-
return
|
|
1494
|
+
return cachedFetch2;
|
|
1172
1495
|
}
|
|
1173
1496
|
function getOpenAiApiKey() {
|
|
1174
|
-
if (
|
|
1175
|
-
return
|
|
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
|
-
|
|
1184
|
-
return
|
|
1506
|
+
cachedApiKey2 = value;
|
|
1507
|
+
return cachedApiKey2;
|
|
1185
1508
|
}
|
|
1186
1509
|
function getOpenAiClient() {
|
|
1187
|
-
if (
|
|
1188
|
-
return
|
|
1510
|
+
if (cachedClient2) {
|
|
1511
|
+
return cachedClient2;
|
|
1189
1512
|
}
|
|
1190
1513
|
const apiKey = getOpenAiApiKey();
|
|
1191
1514
|
const timeoutMs = resolveOpenAiTimeoutMs();
|
|
1192
|
-
|
|
1515
|
+
cachedClient2 = new OpenAI2({
|
|
1193
1516
|
apiKey,
|
|
1194
1517
|
fetch: getOpenAiFetch(),
|
|
1195
1518
|
timeout: timeoutMs
|
|
1196
1519
|
});
|
|
1197
|
-
return
|
|
1520
|
+
return cachedClient2;
|
|
1198
1521
|
}
|
|
1199
1522
|
|
|
1200
1523
|
// src/openai/calls.ts
|
|
1201
1524
|
var DEFAULT_OPENAI_REASONING_EFFORT = "medium";
|
|
1202
|
-
var
|
|
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
|
|
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
|
|
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 =
|
|
1810
|
+
omittedBytes = Buffer3.from(part.data, "base64").byteLength;
|
|
1479
1811
|
} catch {
|
|
1480
|
-
omittedBytes =
|
|
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
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
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
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
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
|
|
2393
|
-
messages.push(`${
|
|
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 ||
|
|
2474
|
-
if (
|
|
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
|
|
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
|
-
|
|
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
|
|
3107
|
+
return Buffer3.from(base64, "base64");
|
|
2576
3108
|
} catch {
|
|
2577
|
-
return
|
|
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
|
|
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
|
|
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
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
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
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
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
|
|
3762
|
+
const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
|
|
3151
3763
|
const openAiNativeTools = toOpenAiTools(request.modelTools);
|
|
3152
|
-
const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...
|
|
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
|
|
3856
|
+
const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
|
|
3245
3857
|
const stepToolCalls = [];
|
|
3246
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
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
|
|
3944
|
+
const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
|
|
3313
3945
|
const openAiNativeTools = toOpenAiTools(request.modelTools);
|
|
3314
|
-
const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...
|
|
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
|
|
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
|
|
3361
|
-
if (
|
|
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 =
|
|
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({
|
|
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
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
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,
|