@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.cjs
CHANGED
|
@@ -30,10 +30,39 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION: () => CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION,
|
|
34
|
+
CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION: () => CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION,
|
|
35
|
+
CODEX_APPLY_PATCH_LARK_GRAMMAR: () => CODEX_APPLY_PATCH_LARK_GRAMMAR,
|
|
36
|
+
FIREWORKS_DEFAULT_GLM_MODEL: () => FIREWORKS_DEFAULT_GLM_MODEL,
|
|
37
|
+
FIREWORKS_DEFAULT_KIMI_MODEL: () => FIREWORKS_DEFAULT_KIMI_MODEL,
|
|
38
|
+
FIREWORKS_DEFAULT_MINIMAX_MODEL: () => FIREWORKS_DEFAULT_MINIMAX_MODEL,
|
|
39
|
+
FIREWORKS_MODEL_IDS: () => FIREWORKS_MODEL_IDS,
|
|
40
|
+
InMemoryAgentFilesystem: () => InMemoryAgentFilesystem,
|
|
33
41
|
LlmJsonCallError: () => LlmJsonCallError,
|
|
34
42
|
appendMarkdownSourcesSection: () => appendMarkdownSourcesSection,
|
|
43
|
+
applyPatch: () => applyPatch,
|
|
35
44
|
configureGemini: () => configureGemini,
|
|
36
45
|
convertGooglePartsToLlmParts: () => convertGooglePartsToLlmParts,
|
|
46
|
+
createApplyPatchTool: () => createApplyPatchTool,
|
|
47
|
+
createCodexApplyPatchTool: () => createCodexApplyPatchTool,
|
|
48
|
+
createCodexFilesystemToolSet: () => createCodexFilesystemToolSet,
|
|
49
|
+
createCodexReadFileTool: () => createCodexReadFileTool,
|
|
50
|
+
createFilesystemToolSetForModel: () => createFilesystemToolSetForModel,
|
|
51
|
+
createGeminiFilesystemToolSet: () => createGeminiFilesystemToolSet,
|
|
52
|
+
createGeminiReadFileTool: () => createGeminiReadFileTool,
|
|
53
|
+
createGlobTool: () => createGlobTool,
|
|
54
|
+
createGrepFilesTool: () => createGrepFilesTool,
|
|
55
|
+
createGrepSearchTool: () => createGrepSearchTool,
|
|
56
|
+
createInMemoryAgentFilesystem: () => createInMemoryAgentFilesystem,
|
|
57
|
+
createListDirTool: () => createListDirTool,
|
|
58
|
+
createListDirectoryTool: () => createListDirectoryTool,
|
|
59
|
+
createModelAgnosticFilesystemToolSet: () => createModelAgnosticFilesystemToolSet,
|
|
60
|
+
createNodeAgentFilesystem: () => createNodeAgentFilesystem,
|
|
61
|
+
createReadFilesTool: () => createReadFilesTool,
|
|
62
|
+
createReplaceTool: () => createReplaceTool,
|
|
63
|
+
createRgSearchTool: () => createRgSearchTool,
|
|
64
|
+
createWriteFileTool: () => createWriteFileTool,
|
|
65
|
+
customTool: () => customTool,
|
|
37
66
|
encodeChatGptAuthJson: () => encodeChatGptAuthJson,
|
|
38
67
|
encodeChatGptAuthJsonB64: () => encodeChatGptAuthJsonB64,
|
|
39
68
|
estimateCallCostUsd: () => estimateCallCostUsd,
|
|
@@ -44,11 +73,15 @@ __export(index_exports, {
|
|
|
44
73
|
generateText: () => generateText,
|
|
45
74
|
getChatGptAuthProfile: () => getChatGptAuthProfile,
|
|
46
75
|
getCurrentToolCallContext: () => getCurrentToolCallContext,
|
|
76
|
+
isFireworksModelId: () => isFireworksModelId,
|
|
47
77
|
isGeminiModelId: () => isGeminiModelId,
|
|
48
78
|
loadEnvFromFile: () => loadEnvFromFile,
|
|
49
79
|
loadLocalEnv: () => loadLocalEnv,
|
|
50
80
|
parseJsonFromLlmText: () => parseJsonFromLlmText,
|
|
51
81
|
refreshChatGptOauthToken: () => refreshChatGptOauthToken,
|
|
82
|
+
resolveFilesystemToolProfile: () => resolveFilesystemToolProfile,
|
|
83
|
+
resolveFireworksModelId: () => resolveFireworksModelId,
|
|
84
|
+
runAgentLoop: () => runAgentLoop,
|
|
52
85
|
runToolLoop: () => runToolLoop,
|
|
53
86
|
sanitisePartForLogging: () => sanitisePartForLogging,
|
|
54
87
|
streamJson: () => streamJson,
|
|
@@ -131,6 +164,35 @@ function createAsyncQueue() {
|
|
|
131
164
|
return { push, close, fail, iterable: iterator() };
|
|
132
165
|
}
|
|
133
166
|
|
|
167
|
+
// src/fireworks/pricing.ts
|
|
168
|
+
var FIREWORKS_KIMI_K25_PRICING = {
|
|
169
|
+
inputRate: 0.6 / 1e6,
|
|
170
|
+
cachedRate: 0.1 / 1e6,
|
|
171
|
+
outputRate: 3 / 1e6
|
|
172
|
+
};
|
|
173
|
+
var FIREWORKS_GLM_5_PRICING = {
|
|
174
|
+
inputRate: 1 / 1e6,
|
|
175
|
+
cachedRate: 0.2 / 1e6,
|
|
176
|
+
outputRate: 3.2 / 1e6
|
|
177
|
+
};
|
|
178
|
+
var FIREWORKS_MINIMAX_M21_PRICING = {
|
|
179
|
+
inputRate: 0.3 / 1e6,
|
|
180
|
+
cachedRate: 0.15 / 1e6,
|
|
181
|
+
outputRate: 1.2 / 1e6
|
|
182
|
+
};
|
|
183
|
+
function getFireworksPricing(modelId) {
|
|
184
|
+
if (modelId.includes("kimi-k2.5") || modelId.includes("kimi-k2p5")) {
|
|
185
|
+
return FIREWORKS_KIMI_K25_PRICING;
|
|
186
|
+
}
|
|
187
|
+
if (modelId.includes("glm-5")) {
|
|
188
|
+
return FIREWORKS_GLM_5_PRICING;
|
|
189
|
+
}
|
|
190
|
+
if (modelId.includes("minimax-m2.1") || modelId.includes("minimax-m2p1")) {
|
|
191
|
+
return FIREWORKS_MINIMAX_M21_PRICING;
|
|
192
|
+
}
|
|
193
|
+
return void 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
134
196
|
// src/google/pricing.ts
|
|
135
197
|
var GEMINI_3_PRO_PREVIEW_PRICING = {
|
|
136
198
|
threshold: 2e5,
|
|
@@ -183,17 +245,34 @@ var OPENAI_GPT_52_PRICING = {
|
|
|
183
245
|
cachedRate: 0.175 / 1e6,
|
|
184
246
|
outputRate: 14 / 1e6
|
|
185
247
|
};
|
|
186
|
-
var
|
|
248
|
+
var OPENAI_GPT_53_CODEX_PRICING = {
|
|
249
|
+
inputRate: 1.25 / 1e6,
|
|
250
|
+
cachedRate: 0.125 / 1e6,
|
|
251
|
+
outputRate: 10 / 1e6
|
|
252
|
+
};
|
|
253
|
+
var OPENAI_GPT_5_MINI_PRICING = {
|
|
187
254
|
inputRate: 0.25 / 1e6,
|
|
188
255
|
cachedRate: 0.025 / 1e6,
|
|
189
256
|
outputRate: 2 / 1e6
|
|
190
257
|
};
|
|
191
258
|
function getOpenAiPricing(modelId) {
|
|
259
|
+
if (modelId.includes("gpt-5.3-codex-spark")) {
|
|
260
|
+
return OPENAI_GPT_5_MINI_PRICING;
|
|
261
|
+
}
|
|
262
|
+
if (modelId.includes("gpt-5.3-codex")) {
|
|
263
|
+
return OPENAI_GPT_53_CODEX_PRICING;
|
|
264
|
+
}
|
|
265
|
+
if (modelId.includes("gpt-5-codex")) {
|
|
266
|
+
return OPENAI_GPT_53_CODEX_PRICING;
|
|
267
|
+
}
|
|
192
268
|
if (modelId.includes("gpt-5.2")) {
|
|
193
269
|
return OPENAI_GPT_52_PRICING;
|
|
194
270
|
}
|
|
271
|
+
if (modelId.includes("gpt-5-mini")) {
|
|
272
|
+
return OPENAI_GPT_5_MINI_PRICING;
|
|
273
|
+
}
|
|
195
274
|
if (modelId.includes("gpt-5.1-codex-mini")) {
|
|
196
|
-
return
|
|
275
|
+
return OPENAI_GPT_5_MINI_PRICING;
|
|
197
276
|
}
|
|
198
277
|
return void 0;
|
|
199
278
|
}
|
|
@@ -254,6 +333,14 @@ function estimateCallCostUsd({
|
|
|
254
333
|
const outputCost = outputTokens * outputRate;
|
|
255
334
|
return inputCost + cachedCost + outputCost;
|
|
256
335
|
}
|
|
336
|
+
const fireworksPricing = getFireworksPricing(modelId);
|
|
337
|
+
if (fireworksPricing) {
|
|
338
|
+
const inputCost = nonCachedPrompt * fireworksPricing.inputRate;
|
|
339
|
+
const cachedCost = cachedTokens * fireworksPricing.cachedRate;
|
|
340
|
+
const outputTokens = responseTokens + thinkingTokens;
|
|
341
|
+
const outputCost = outputTokens * fireworksPricing.outputRate;
|
|
342
|
+
return inputCost + cachedCost + outputCost;
|
|
343
|
+
}
|
|
257
344
|
const openAiPricing = getOpenAiPricing(modelId);
|
|
258
345
|
if (openAiPricing) {
|
|
259
346
|
const inputCost = nonCachedPrompt * openAiPricing.inputRate;
|
|
@@ -266,11 +353,14 @@ function estimateCallCostUsd({
|
|
|
266
353
|
}
|
|
267
354
|
|
|
268
355
|
// src/openai/chatgpt-codex.ts
|
|
269
|
-
var
|
|
356
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
270
357
|
var import_node_util = require("util");
|
|
271
358
|
|
|
272
359
|
// src/openai/chatgpt-auth.ts
|
|
273
360
|
var import_node_buffer = require("buffer");
|
|
361
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
362
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
363
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
274
364
|
var import_zod = require("zod");
|
|
275
365
|
|
|
276
366
|
// src/utils/env.ts
|
|
@@ -335,34 +425,30 @@ function parseEnvLine(line) {
|
|
|
335
425
|
}
|
|
336
426
|
|
|
337
427
|
// src/openai/chatgpt-auth.ts
|
|
338
|
-
var
|
|
339
|
-
var
|
|
340
|
-
var
|
|
341
|
-
var
|
|
342
|
-
var
|
|
343
|
-
var
|
|
344
|
-
var CHATGPT_ID_TOKEN_ENV = "CHATGPT_ID_TOKEN";
|
|
345
|
-
var CHATGPT_ACCESS_TOKEN_ENV = "CHATGPT_ACCESS_TOKEN";
|
|
346
|
-
var CHATGPT_REFRESH_TOKEN_ENV = "CHATGPT_REFRESH_TOKEN";
|
|
347
|
-
var CHATGPT_EXPIRES_AT_ENV = "CHATGPT_EXPIRES_AT";
|
|
428
|
+
var CHATGPT_AUTH_TOKEN_PROVIDER_URL_ENV = "CHATGPT_AUTH_TOKEN_PROVIDER_URL";
|
|
429
|
+
var CHATGPT_AUTH_TOKEN_PROVIDER_STORE_ENV = "CHATGPT_AUTH_TOKEN_PROVIDER_STORE";
|
|
430
|
+
var CHATGPT_AUTH_SERVER_URL_ENV = "CHATGPT_AUTH_SERVER_URL";
|
|
431
|
+
var CHATGPT_AUTH_SERVER_STORE_ENV = "CHATGPT_AUTH_SERVER_STORE";
|
|
432
|
+
var CHATGPT_AUTH_API_KEY_ENV = "CHATGPT_AUTH_API_KEY";
|
|
433
|
+
var CHATGPT_AUTH_TOKEN_PROVIDER_API_KEY_ENV = "CHATGPT_AUTH_TOKEN_PROVIDER_API_KEY";
|
|
348
434
|
var CHATGPT_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
349
435
|
var CHATGPT_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
350
436
|
var CHATGPT_OAUTH_REDIRECT_URI = "http://localhost:1455/auth/callback";
|
|
351
437
|
var TOKEN_EXPIRY_BUFFER_MS = 3e4;
|
|
352
|
-
var
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
438
|
+
var CodexAuthFileSchema = import_zod.z.object({
|
|
439
|
+
OPENAI_API_KEY: import_zod.z.string().nullable().optional(),
|
|
440
|
+
last_refresh: import_zod.z.string().optional(),
|
|
441
|
+
tokens: import_zod.z.object({
|
|
442
|
+
access_token: import_zod.z.string().min(1).optional(),
|
|
443
|
+
refresh_token: import_zod.z.string().min(1).optional(),
|
|
444
|
+
id_token: import_zod.z.string().min(1).optional(),
|
|
445
|
+
account_id: import_zod.z.string().min(1).optional(),
|
|
446
|
+
// Allow a bit of flexibility if the file format changes.
|
|
447
|
+
accessToken: import_zod.z.string().min(1).optional(),
|
|
448
|
+
refreshToken: import_zod.z.string().min(1).optional(),
|
|
449
|
+
idToken: import_zod.z.string().min(1).optional(),
|
|
450
|
+
accountId: import_zod.z.string().min(1).optional()
|
|
451
|
+
}).optional()
|
|
366
452
|
}).loose();
|
|
367
453
|
var RefreshResponseSchema = import_zod.z.object({
|
|
368
454
|
access_token: import_zod.z.string().min(1),
|
|
@@ -377,6 +463,44 @@ var ExchangeResponseSchema = import_zod.z.object({
|
|
|
377
463
|
});
|
|
378
464
|
var cachedProfile = null;
|
|
379
465
|
var refreshPromise = null;
|
|
466
|
+
async function fetchChatGptAuthProfileFromTokenProvider(options) {
|
|
467
|
+
const base = options.baseUrl.replace(/\/+$/u, "");
|
|
468
|
+
const store = options.store?.trim() ? options.store.trim() : "kv";
|
|
469
|
+
const url = new URL(`${base}/v1/token`);
|
|
470
|
+
url.searchParams.set("store", store);
|
|
471
|
+
const response = await fetch(url.toString(), {
|
|
472
|
+
method: "GET",
|
|
473
|
+
headers: {
|
|
474
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
475
|
+
"x-chatgpt-auth": options.apiKey,
|
|
476
|
+
Accept: "application/json"
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
if (!response.ok) {
|
|
480
|
+
const body = await response.text();
|
|
481
|
+
throw new Error(`ChatGPT token provider request failed (${response.status}): ${body}`);
|
|
482
|
+
}
|
|
483
|
+
const payload = await response.json();
|
|
484
|
+
if (!payload || typeof payload !== "object") {
|
|
485
|
+
throw new Error("ChatGPT token provider returned invalid JSON.");
|
|
486
|
+
}
|
|
487
|
+
const accessToken = payload.accessToken ?? payload.access_token;
|
|
488
|
+
const accountId = payload.accountId ?? payload.account_id;
|
|
489
|
+
const expiresAt = payload.expiresAt ?? payload.expires_at;
|
|
490
|
+
if (typeof accessToken !== "string" || accessToken.trim().length === 0) {
|
|
491
|
+
throw new Error("ChatGPT token provider response missing accessToken.");
|
|
492
|
+
}
|
|
493
|
+
if (typeof accountId !== "string" || accountId.trim().length === 0) {
|
|
494
|
+
throw new Error("ChatGPT token provider response missing accountId.");
|
|
495
|
+
}
|
|
496
|
+
const expires = normalizeEpochMillis(expiresAt) ?? Date.now() + 5 * 6e4;
|
|
497
|
+
return {
|
|
498
|
+
access: accessToken,
|
|
499
|
+
refresh: "token_provider",
|
|
500
|
+
expires,
|
|
501
|
+
accountId
|
|
502
|
+
};
|
|
503
|
+
}
|
|
380
504
|
function encodeChatGptAuthJson(profile) {
|
|
381
505
|
const payload = {
|
|
382
506
|
access: profile.access,
|
|
@@ -415,7 +539,7 @@ async function exchangeChatGptOauthCode({
|
|
|
415
539
|
const payload = ExchangeResponseSchema.parse(await response.json());
|
|
416
540
|
return profileFromTokenResponse(payload);
|
|
417
541
|
}
|
|
418
|
-
async function refreshChatGptOauthToken(refreshToken) {
|
|
542
|
+
async function refreshChatGptOauthToken(refreshToken, fallback) {
|
|
419
543
|
const params = new URLSearchParams();
|
|
420
544
|
params.set("grant_type", "refresh_token");
|
|
421
545
|
params.set("client_id", CHATGPT_OAUTH_CLIENT_ID);
|
|
@@ -432,9 +556,35 @@ async function refreshChatGptOauthToken(refreshToken) {
|
|
|
432
556
|
throw new Error(`ChatGPT OAuth refresh failed (${response.status}): ${body}`);
|
|
433
557
|
}
|
|
434
558
|
const payload = RefreshResponseSchema.parse(await response.json());
|
|
435
|
-
return profileFromTokenResponse(payload);
|
|
559
|
+
return profileFromTokenResponse(payload, fallback);
|
|
436
560
|
}
|
|
437
561
|
async function getChatGptAuthProfile() {
|
|
562
|
+
loadLocalEnv();
|
|
563
|
+
const tokenProviderUrl = process.env[CHATGPT_AUTH_TOKEN_PROVIDER_URL_ENV] ?? process.env[CHATGPT_AUTH_SERVER_URL_ENV];
|
|
564
|
+
const tokenProviderKey = process.env[CHATGPT_AUTH_TOKEN_PROVIDER_API_KEY_ENV] ?? process.env[CHATGPT_AUTH_API_KEY_ENV];
|
|
565
|
+
if (tokenProviderUrl && tokenProviderUrl.trim().length > 0 && tokenProviderKey && tokenProviderKey.trim().length > 0) {
|
|
566
|
+
if (cachedProfile && !isExpired(cachedProfile)) {
|
|
567
|
+
return cachedProfile;
|
|
568
|
+
}
|
|
569
|
+
if (refreshPromise) {
|
|
570
|
+
return refreshPromise;
|
|
571
|
+
}
|
|
572
|
+
refreshPromise = (async () => {
|
|
573
|
+
try {
|
|
574
|
+
const store = process.env[CHATGPT_AUTH_TOKEN_PROVIDER_STORE_ENV] ?? process.env[CHATGPT_AUTH_SERVER_STORE_ENV];
|
|
575
|
+
const profile = await fetchChatGptAuthProfileFromTokenProvider({
|
|
576
|
+
baseUrl: tokenProviderUrl,
|
|
577
|
+
apiKey: tokenProviderKey,
|
|
578
|
+
store: store ?? void 0
|
|
579
|
+
});
|
|
580
|
+
cachedProfile = profile;
|
|
581
|
+
return profile;
|
|
582
|
+
} finally {
|
|
583
|
+
refreshPromise = null;
|
|
584
|
+
}
|
|
585
|
+
})();
|
|
586
|
+
return refreshPromise;
|
|
587
|
+
}
|
|
438
588
|
if (cachedProfile && !isExpired(cachedProfile)) {
|
|
439
589
|
return cachedProfile;
|
|
440
590
|
}
|
|
@@ -443,8 +593,8 @@ async function getChatGptAuthProfile() {
|
|
|
443
593
|
}
|
|
444
594
|
refreshPromise = (async () => {
|
|
445
595
|
try {
|
|
446
|
-
const baseProfile = cachedProfile ??
|
|
447
|
-
const profile = isExpired(baseProfile) ? await
|
|
596
|
+
const baseProfile = cachedProfile ?? loadAuthProfileFromCodexStore();
|
|
597
|
+
const profile = isExpired(baseProfile) ? await refreshAndPersistCodexProfile(baseProfile) : baseProfile;
|
|
448
598
|
cachedProfile = profile;
|
|
449
599
|
return profile;
|
|
450
600
|
} finally {
|
|
@@ -453,39 +603,111 @@ async function getChatGptAuthProfile() {
|
|
|
453
603
|
})();
|
|
454
604
|
return refreshPromise;
|
|
455
605
|
}
|
|
456
|
-
function
|
|
457
|
-
const
|
|
458
|
-
|
|
606
|
+
function resolveCodexHome() {
|
|
607
|
+
const codexHome = process.env.CODEX_HOME;
|
|
608
|
+
if (codexHome && codexHome.trim().length > 0) {
|
|
609
|
+
return codexHome.trim();
|
|
610
|
+
}
|
|
611
|
+
return import_node_path2.default.join(import_node_os.default.homedir(), ".codex");
|
|
612
|
+
}
|
|
613
|
+
function resolveCodexAuthJsonPath() {
|
|
614
|
+
return import_node_path2.default.join(resolveCodexHome(), "auth.json");
|
|
615
|
+
}
|
|
616
|
+
function loadAuthProfileFromCodexStore() {
|
|
617
|
+
const authPath = resolveCodexAuthJsonPath();
|
|
618
|
+
let raw;
|
|
619
|
+
try {
|
|
620
|
+
raw = import_node_fs2.default.readFileSync(authPath, "utf8");
|
|
621
|
+
} catch {
|
|
622
|
+
throw new Error(
|
|
623
|
+
`ChatGPT auth not configured. Set ${CHATGPT_AUTH_TOKEN_PROVIDER_URL_ENV}+${CHATGPT_AUTH_API_KEY_ENV} or login via Codex to create ${authPath}.`
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
let parsed;
|
|
627
|
+
try {
|
|
628
|
+
parsed = CodexAuthFileSchema.parse(JSON.parse(raw));
|
|
629
|
+
} catch (e) {
|
|
630
|
+
throw new Error(
|
|
631
|
+
`Failed to parse Codex auth store at ${authPath}. (${e?.message ?? e})`
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
const tokens = parsed.tokens;
|
|
635
|
+
if (!tokens) {
|
|
636
|
+
throw new Error(
|
|
637
|
+
`Codex auth store at ${authPath} is missing tokens. Re-login via Codex, or configure ${CHATGPT_AUTH_TOKEN_PROVIDER_URL_ENV}.`
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
const access = tokens.access_token ?? tokens.accessToken ?? void 0;
|
|
641
|
+
const refresh = tokens.refresh_token ?? tokens.refreshToken ?? void 0;
|
|
642
|
+
const idToken = tokens.id_token ?? tokens.idToken ?? void 0;
|
|
643
|
+
if (!access || !refresh) {
|
|
644
|
+
throw new Error(
|
|
645
|
+
`Codex auth store at ${authPath} is missing access_token/refresh_token. Re-login via Codex, or configure ${CHATGPT_AUTH_TOKEN_PROVIDER_URL_ENV}.`
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
const expires = extractJwtExpiry(access) ?? extractJwtExpiry(idToken ?? "") ?? Date.now() + 5 * 6e4;
|
|
649
|
+
const accountId = tokens.account_id ?? tokens.accountId ?? extractChatGptAccountId(idToken ?? "") ?? extractChatGptAccountId(access);
|
|
459
650
|
if (!accountId) {
|
|
460
|
-
throw new Error(
|
|
651
|
+
throw new Error(`Codex auth store at ${authPath} is missing chatgpt_account_id/account_id.`);
|
|
461
652
|
}
|
|
462
653
|
return {
|
|
463
|
-
access
|
|
464
|
-
refresh
|
|
654
|
+
access,
|
|
655
|
+
refresh,
|
|
465
656
|
expires,
|
|
466
657
|
accountId,
|
|
467
|
-
idToken:
|
|
658
|
+
idToken: idToken ?? void 0
|
|
468
659
|
};
|
|
469
660
|
}
|
|
470
|
-
function
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
661
|
+
async function refreshAndPersistCodexProfile(baseProfile) {
|
|
662
|
+
const refreshed = await refreshChatGptOauthToken(baseProfile.refresh, {
|
|
663
|
+
accountId: baseProfile.accountId,
|
|
664
|
+
idToken: baseProfile.idToken
|
|
665
|
+
});
|
|
666
|
+
persistCodexTokens(refreshed);
|
|
667
|
+
return refreshed;
|
|
668
|
+
}
|
|
669
|
+
function persistCodexTokens(profile) {
|
|
670
|
+
const authPath = resolveCodexAuthJsonPath();
|
|
671
|
+
const codexHome = import_node_path2.default.dirname(authPath);
|
|
672
|
+
let doc = {};
|
|
673
|
+
try {
|
|
674
|
+
doc = JSON.parse(import_node_fs2.default.readFileSync(authPath, "utf8"));
|
|
675
|
+
} catch {
|
|
676
|
+
doc = {};
|
|
475
677
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
678
|
+
if (!doc || typeof doc !== "object") {
|
|
679
|
+
doc = {};
|
|
680
|
+
}
|
|
681
|
+
if (!doc.tokens || typeof doc.tokens !== "object") {
|
|
682
|
+
doc.tokens = {};
|
|
683
|
+
}
|
|
684
|
+
doc.tokens.access_token = profile.access;
|
|
685
|
+
doc.tokens.refresh_token = profile.refresh;
|
|
686
|
+
doc.tokens.account_id = profile.accountId;
|
|
687
|
+
if (profile.idToken) {
|
|
688
|
+
doc.tokens.id_token = profile.idToken;
|
|
689
|
+
}
|
|
690
|
+
doc.last_refresh = (/* @__PURE__ */ new Date()).toISOString();
|
|
691
|
+
import_node_fs2.default.mkdirSync(codexHome, { recursive: true, mode: 448 });
|
|
692
|
+
const tmpPath = `${authPath}.tmp.${process.pid}.${Math.random().toString(16).slice(2)}`;
|
|
693
|
+
import_node_fs2.default.writeFileSync(tmpPath, `${JSON.stringify(doc, null, 2)}
|
|
694
|
+
`, { mode: 384 });
|
|
695
|
+
import_node_fs2.default.renameSync(tmpPath, authPath);
|
|
696
|
+
}
|
|
697
|
+
function profileFromTokenResponse(payload, fallback) {
|
|
698
|
+
const expires = Date.now() + normalizeNumber(payload.expires_in) * 1e3;
|
|
699
|
+
const fallbackAccountId = fallback?.accountId;
|
|
700
|
+
const fallbackIdToken = fallback?.idToken;
|
|
701
|
+
const accountId = extractChatGptAccountId(payload.id_token ?? "") ?? extractChatGptAccountId(payload.access_token) ?? fallbackAccountId;
|
|
480
702
|
if (!accountId) {
|
|
481
|
-
throw new Error("
|
|
703
|
+
throw new Error("Failed to extract chatgpt_account_id from access token.");
|
|
482
704
|
}
|
|
483
705
|
return {
|
|
484
|
-
access,
|
|
485
|
-
refresh,
|
|
706
|
+
access: payload.access_token,
|
|
707
|
+
refresh: payload.refresh_token,
|
|
486
708
|
expires,
|
|
487
709
|
accountId,
|
|
488
|
-
idToken:
|
|
710
|
+
idToken: payload.id_token ?? fallbackIdToken
|
|
489
711
|
};
|
|
490
712
|
}
|
|
491
713
|
function normalizeEpochMillis(value) {
|
|
@@ -514,31 +736,6 @@ function isExpired(profile) {
|
|
|
514
736
|
}
|
|
515
737
|
return Date.now() + TOKEN_EXPIRY_BUFFER_MS >= expires;
|
|
516
738
|
}
|
|
517
|
-
function loadAuthProfileFromEnv() {
|
|
518
|
-
loadLocalEnv();
|
|
519
|
-
const rawJson = process.env[CHATGPT_AUTH_JSON_ENV];
|
|
520
|
-
if (rawJson && rawJson.trim().length > 0) {
|
|
521
|
-
return normalizeAuthProfile(AuthInputSchema.parse(JSON.parse(rawJson)));
|
|
522
|
-
}
|
|
523
|
-
const rawB64 = process.env[CHATGPT_AUTH_JSON_B64_ENV];
|
|
524
|
-
if (rawB64 && rawB64.trim().length > 0) {
|
|
525
|
-
const decoded = import_node_buffer.Buffer.from(rawB64.trim(), "base64url").toString("utf8");
|
|
526
|
-
return normalizeAuthProfile(AuthInputSchema.parse(JSON.parse(decoded)));
|
|
527
|
-
}
|
|
528
|
-
const access = process.env[CHATGPT_ACCESS_ENV] ?? process.env[CHATGPT_ACCESS_TOKEN_ENV] ?? void 0;
|
|
529
|
-
const refresh = process.env[CHATGPT_REFRESH_ENV] ?? process.env[CHATGPT_REFRESH_TOKEN_ENV] ?? void 0;
|
|
530
|
-
const expires = process.env[CHATGPT_EXPIRES_ENV] ?? process.env[CHATGPT_EXPIRES_AT_ENV] ?? void 0;
|
|
531
|
-
const accountId = process.env[CHATGPT_ACCOUNT_ID_ENV] ?? void 0;
|
|
532
|
-
const idToken = process.env[CHATGPT_ID_TOKEN_ENV] ?? void 0;
|
|
533
|
-
const parsed = AuthInputSchema.parse({
|
|
534
|
-
access,
|
|
535
|
-
refresh,
|
|
536
|
-
expires,
|
|
537
|
-
accountId,
|
|
538
|
-
idToken
|
|
539
|
-
});
|
|
540
|
-
return normalizeAuthProfile(parsed);
|
|
541
|
-
}
|
|
542
739
|
function decodeJwtPayload(token) {
|
|
543
740
|
const segments = token.split(".");
|
|
544
741
|
if (segments.length < 2) {
|
|
@@ -569,8 +766,12 @@ function extractChatGptAccountId(token) {
|
|
|
569
766
|
if (!payload || typeof payload !== "object") {
|
|
570
767
|
return void 0;
|
|
571
768
|
}
|
|
572
|
-
const
|
|
573
|
-
|
|
769
|
+
const direct = payload.chatgpt_account_id;
|
|
770
|
+
if (typeof direct === "string" && direct.length > 0) {
|
|
771
|
+
return direct;
|
|
772
|
+
}
|
|
773
|
+
const namespaced = payload["https://api.openai.com/auth"]?.chatgpt_account_id;
|
|
774
|
+
return typeof namespaced === "string" && namespaced.length > 0 ? namespaced : void 0;
|
|
574
775
|
}
|
|
575
776
|
|
|
576
777
|
// src/openai/chatgpt-codex.ts
|
|
@@ -606,7 +807,19 @@ async function streamChatGptCodexResponse(options) {
|
|
|
606
807
|
return parseEventStream(body);
|
|
607
808
|
}
|
|
608
809
|
async function collectChatGptCodexResponse(options) {
|
|
609
|
-
|
|
810
|
+
let stream;
|
|
811
|
+
try {
|
|
812
|
+
stream = await streamChatGptCodexResponse(options);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
if (shouldRetryWithoutReasoningSummary(options.request, error)) {
|
|
815
|
+
stream = await streamChatGptCodexResponse({
|
|
816
|
+
...options,
|
|
817
|
+
request: removeReasoningSummary(options.request)
|
|
818
|
+
});
|
|
819
|
+
} else {
|
|
820
|
+
throw error;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
610
823
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
611
824
|
const toolCallOrder = [];
|
|
612
825
|
const webSearchCalls = /* @__PURE__ */ new Map();
|
|
@@ -615,6 +828,7 @@ async function collectChatGptCodexResponse(options) {
|
|
|
615
828
|
const reasoningText = "";
|
|
616
829
|
let reasoningSummaryText = "";
|
|
617
830
|
let usage;
|
|
831
|
+
let responseId;
|
|
618
832
|
let model;
|
|
619
833
|
let status;
|
|
620
834
|
let blocked = false;
|
|
@@ -655,7 +869,18 @@ async function collectChatGptCodexResponse(options) {
|
|
|
655
869
|
if (!toolCalls.has(callId)) {
|
|
656
870
|
toolCallOrder.push(callId);
|
|
657
871
|
}
|
|
658
|
-
toolCalls.set(callId, { id, callId, name, arguments: args });
|
|
872
|
+
toolCalls.set(callId, { kind: "function", id, callId, name, arguments: args });
|
|
873
|
+
}
|
|
874
|
+
} else if (item.type === "custom_tool_call") {
|
|
875
|
+
const id = typeof item.id === "string" ? item.id : "";
|
|
876
|
+
const callId = typeof item.call_id === "string" ? item.call_id : id;
|
|
877
|
+
const name = typeof item.name === "string" ? item.name : "";
|
|
878
|
+
const input = typeof item.input === "string" ? item.input : "";
|
|
879
|
+
if (callId) {
|
|
880
|
+
if (!toolCalls.has(callId)) {
|
|
881
|
+
toolCallOrder.push(callId);
|
|
882
|
+
}
|
|
883
|
+
toolCalls.set(callId, { kind: "custom", id, callId, name, input });
|
|
659
884
|
}
|
|
660
885
|
} else if (item.type === "web_search_call") {
|
|
661
886
|
const id = typeof item.id === "string" ? item.id : "";
|
|
@@ -677,6 +902,7 @@ async function collectChatGptCodexResponse(options) {
|
|
|
677
902
|
const response = event.response;
|
|
678
903
|
if (response) {
|
|
679
904
|
usage = response.usage;
|
|
905
|
+
responseId = typeof response.id === "string" ? response.id : responseId;
|
|
680
906
|
model = typeof response.model === "string" ? response.model : void 0;
|
|
681
907
|
status = typeof response.status === "string" ? response.status : void 0;
|
|
682
908
|
}
|
|
@@ -686,6 +912,7 @@ async function collectChatGptCodexResponse(options) {
|
|
|
686
912
|
const response = event.response;
|
|
687
913
|
if (response) {
|
|
688
914
|
usage = response.usage;
|
|
915
|
+
responseId = typeof response.id === "string" ? response.id : responseId;
|
|
689
916
|
model = typeof response.model === "string" ? response.model : void 0;
|
|
690
917
|
status = typeof response.status === "string" ? response.status : void 0;
|
|
691
918
|
}
|
|
@@ -695,6 +922,7 @@ async function collectChatGptCodexResponse(options) {
|
|
|
695
922
|
const response = event.response;
|
|
696
923
|
if (response) {
|
|
697
924
|
usage = response.usage;
|
|
925
|
+
responseId = typeof response.id === "string" ? response.id : responseId;
|
|
698
926
|
model = typeof response.model === "string" ? response.model : void 0;
|
|
699
927
|
status = typeof response.status === "string" ? response.status : void 0;
|
|
700
928
|
}
|
|
@@ -712,15 +940,38 @@ async function collectChatGptCodexResponse(options) {
|
|
|
712
940
|
toolCalls: orderedToolCalls,
|
|
713
941
|
webSearchCalls: orderedWebSearchCalls,
|
|
714
942
|
usage,
|
|
943
|
+
id: responseId,
|
|
715
944
|
model,
|
|
716
945
|
status,
|
|
717
946
|
blocked
|
|
718
947
|
};
|
|
719
948
|
}
|
|
949
|
+
function shouldRetryWithoutReasoningSummary(request, error) {
|
|
950
|
+
if (!request.reasoning?.summary) {
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
if (!(error instanceof Error)) {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
const message = error.message.toLowerCase();
|
|
957
|
+
return message.includes("unsupported parameter") && message.includes("reasoning.summary");
|
|
958
|
+
}
|
|
959
|
+
function removeReasoningSummary(request) {
|
|
960
|
+
const reasoning = request.reasoning;
|
|
961
|
+
if (!reasoning?.summary) {
|
|
962
|
+
return request;
|
|
963
|
+
}
|
|
964
|
+
return {
|
|
965
|
+
...request,
|
|
966
|
+
reasoning: {
|
|
967
|
+
effort: reasoning.effort
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
}
|
|
720
971
|
function buildUserAgent() {
|
|
721
972
|
const node = process.version;
|
|
722
|
-
const platform =
|
|
723
|
-
const release =
|
|
973
|
+
const platform = import_node_os2.default.platform();
|
|
974
|
+
const release = import_node_os2.default.release();
|
|
724
975
|
return `@ljoukov/llm (node ${node}; ${platform} ${release})`;
|
|
725
976
|
}
|
|
726
977
|
async function* parseEventStream(stream) {
|
|
@@ -868,6 +1119,110 @@ function createCallScheduler(options = {}) {
|
|
|
868
1119
|
return { run };
|
|
869
1120
|
}
|
|
870
1121
|
|
|
1122
|
+
// src/fireworks/client.ts
|
|
1123
|
+
var import_openai = __toESM(require("openai"), 1);
|
|
1124
|
+
var import_undici = require("undici");
|
|
1125
|
+
var DEFAULT_FIREWORKS_BASE_URL = "https://api.fireworks.ai/inference/v1";
|
|
1126
|
+
var DEFAULT_FIREWORKS_TIMEOUT_MS = 15 * 6e4;
|
|
1127
|
+
var cachedClient = null;
|
|
1128
|
+
var cachedFetch = null;
|
|
1129
|
+
var cachedBaseUrl = null;
|
|
1130
|
+
var cachedApiKey = null;
|
|
1131
|
+
var cachedTimeoutMs = null;
|
|
1132
|
+
function resolveTimeoutMs() {
|
|
1133
|
+
if (cachedTimeoutMs !== null) {
|
|
1134
|
+
return cachedTimeoutMs;
|
|
1135
|
+
}
|
|
1136
|
+
const raw = process.env.FIREWORKS_TIMEOUT_MS;
|
|
1137
|
+
const parsed = raw ? Number(raw) : Number.NaN;
|
|
1138
|
+
cachedTimeoutMs = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_FIREWORKS_TIMEOUT_MS;
|
|
1139
|
+
return cachedTimeoutMs;
|
|
1140
|
+
}
|
|
1141
|
+
function resolveBaseUrl() {
|
|
1142
|
+
if (cachedBaseUrl !== null) {
|
|
1143
|
+
return cachedBaseUrl;
|
|
1144
|
+
}
|
|
1145
|
+
loadLocalEnv();
|
|
1146
|
+
const raw = process.env.FIREWORKS_BASE_URL?.trim();
|
|
1147
|
+
cachedBaseUrl = raw && raw.length > 0 ? raw : DEFAULT_FIREWORKS_BASE_URL;
|
|
1148
|
+
return cachedBaseUrl;
|
|
1149
|
+
}
|
|
1150
|
+
function resolveApiKey() {
|
|
1151
|
+
if (cachedApiKey !== null) {
|
|
1152
|
+
return cachedApiKey;
|
|
1153
|
+
}
|
|
1154
|
+
loadLocalEnv();
|
|
1155
|
+
const raw = process.env.FIREWORKS_TOKEN ?? process.env.FIREWORKS_API_KEY;
|
|
1156
|
+
const token = raw?.trim();
|
|
1157
|
+
if (!token) {
|
|
1158
|
+
throw new Error(
|
|
1159
|
+
"FIREWORKS_TOKEN (or FIREWORKS_API_KEY) must be provided to access Fireworks APIs."
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
cachedApiKey = token;
|
|
1163
|
+
return cachedApiKey;
|
|
1164
|
+
}
|
|
1165
|
+
function getFireworksFetch() {
|
|
1166
|
+
if (cachedFetch) {
|
|
1167
|
+
return cachedFetch;
|
|
1168
|
+
}
|
|
1169
|
+
const timeoutMs = resolveTimeoutMs();
|
|
1170
|
+
const dispatcher = new import_undici.Agent({
|
|
1171
|
+
bodyTimeout: timeoutMs,
|
|
1172
|
+
headersTimeout: timeoutMs
|
|
1173
|
+
});
|
|
1174
|
+
cachedFetch = ((input, init) => {
|
|
1175
|
+
return (0, import_undici.fetch)(input, {
|
|
1176
|
+
...init ?? {},
|
|
1177
|
+
dispatcher
|
|
1178
|
+
});
|
|
1179
|
+
});
|
|
1180
|
+
return cachedFetch;
|
|
1181
|
+
}
|
|
1182
|
+
function getFireworksClient() {
|
|
1183
|
+
if (cachedClient) {
|
|
1184
|
+
return cachedClient;
|
|
1185
|
+
}
|
|
1186
|
+
cachedClient = new import_openai.default({
|
|
1187
|
+
apiKey: resolveApiKey(),
|
|
1188
|
+
baseURL: resolveBaseUrl(),
|
|
1189
|
+
timeout: resolveTimeoutMs(),
|
|
1190
|
+
fetch: getFireworksFetch()
|
|
1191
|
+
});
|
|
1192
|
+
return cachedClient;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// src/fireworks/calls.ts
|
|
1196
|
+
var scheduler = createCallScheduler({
|
|
1197
|
+
maxParallelRequests: 3,
|
|
1198
|
+
minIntervalBetweenStartMs: 200,
|
|
1199
|
+
startJitterMs: 200
|
|
1200
|
+
});
|
|
1201
|
+
async function runFireworksCall(fn) {
|
|
1202
|
+
return scheduler.run(async () => fn(getFireworksClient()));
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// src/fireworks/models.ts
|
|
1206
|
+
var FIREWORKS_MODEL_IDS = ["kimi-k2.5", "glm-5", "minimax-m2.1"];
|
|
1207
|
+
var FIREWORKS_DEFAULT_KIMI_MODEL = "kimi-k2.5";
|
|
1208
|
+
var FIREWORKS_DEFAULT_GLM_MODEL = "glm-5";
|
|
1209
|
+
var FIREWORKS_DEFAULT_MINIMAX_MODEL = "minimax-m2.1";
|
|
1210
|
+
var FIREWORKS_CANONICAL_MODEL_IDS = {
|
|
1211
|
+
"kimi-k2.5": "accounts/fireworks/models/kimi-k2p5",
|
|
1212
|
+
"glm-5": "accounts/fireworks/models/glm-5",
|
|
1213
|
+
"minimax-m2.1": "accounts/fireworks/models/minimax-m2p1"
|
|
1214
|
+
};
|
|
1215
|
+
function isFireworksModelId(value) {
|
|
1216
|
+
return FIREWORKS_MODEL_IDS.includes(value.trim());
|
|
1217
|
+
}
|
|
1218
|
+
function resolveFireworksModelId(model) {
|
|
1219
|
+
const trimmed = model.trim();
|
|
1220
|
+
if (!isFireworksModelId(trimmed)) {
|
|
1221
|
+
return void 0;
|
|
1222
|
+
}
|
|
1223
|
+
return FIREWORKS_CANONICAL_MODEL_IDS[trimmed];
|
|
1224
|
+
}
|
|
1225
|
+
|
|
871
1226
|
// src/google/client.ts
|
|
872
1227
|
var import_genai = require("@google/genai");
|
|
873
1228
|
|
|
@@ -936,6 +1291,7 @@ function getGoogleAuthOptions(scopes) {
|
|
|
936
1291
|
// src/google/client.ts
|
|
937
1292
|
var GEMINI_MODEL_IDS = [
|
|
938
1293
|
"gemini-3-pro-preview",
|
|
1294
|
+
"gemini-3-flash-preview",
|
|
939
1295
|
"gemini-2.5-pro",
|
|
940
1296
|
"gemini-flash-latest",
|
|
941
1297
|
"gemini-flash-lite-latest"
|
|
@@ -1175,7 +1531,7 @@ function retryDelayMs(attempt) {
|
|
|
1175
1531
|
const jitter = Math.floor(Math.random() * 200);
|
|
1176
1532
|
return base + jitter;
|
|
1177
1533
|
}
|
|
1178
|
-
var
|
|
1534
|
+
var scheduler2 = createCallScheduler({
|
|
1179
1535
|
maxParallelRequests: 3,
|
|
1180
1536
|
minIntervalBetweenStartMs: 200,
|
|
1181
1537
|
startJitterMs: 200,
|
|
@@ -1191,46 +1547,46 @@ var scheduler = createCallScheduler({
|
|
|
1191
1547
|
}
|
|
1192
1548
|
});
|
|
1193
1549
|
async function runGeminiCall(fn) {
|
|
1194
|
-
return
|
|
1550
|
+
return scheduler2.run(async () => fn(await getGeminiClient()));
|
|
1195
1551
|
}
|
|
1196
1552
|
|
|
1197
1553
|
// src/openai/client.ts
|
|
1198
|
-
var
|
|
1199
|
-
var
|
|
1200
|
-
var
|
|
1201
|
-
var
|
|
1202
|
-
var
|
|
1203
|
-
var
|
|
1554
|
+
var import_openai2 = __toESM(require("openai"), 1);
|
|
1555
|
+
var import_undici2 = require("undici");
|
|
1556
|
+
var cachedApiKey2 = null;
|
|
1557
|
+
var cachedClient2 = null;
|
|
1558
|
+
var cachedFetch2 = null;
|
|
1559
|
+
var cachedTimeoutMs2 = null;
|
|
1204
1560
|
var DEFAULT_OPENAI_TIMEOUT_MS = 15 * 6e4;
|
|
1205
1561
|
function resolveOpenAiTimeoutMs() {
|
|
1206
|
-
if (
|
|
1207
|
-
return
|
|
1562
|
+
if (cachedTimeoutMs2 !== null) {
|
|
1563
|
+
return cachedTimeoutMs2;
|
|
1208
1564
|
}
|
|
1209
1565
|
const raw = process.env.OPENAI_STREAM_TIMEOUT_MS ?? process.env.OPENAI_TIMEOUT_MS;
|
|
1210
1566
|
const parsed = raw ? Number(raw) : Number.NaN;
|
|
1211
|
-
|
|
1212
|
-
return
|
|
1567
|
+
cachedTimeoutMs2 = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_OPENAI_TIMEOUT_MS;
|
|
1568
|
+
return cachedTimeoutMs2;
|
|
1213
1569
|
}
|
|
1214
1570
|
function getOpenAiFetch() {
|
|
1215
|
-
if (
|
|
1216
|
-
return
|
|
1571
|
+
if (cachedFetch2) {
|
|
1572
|
+
return cachedFetch2;
|
|
1217
1573
|
}
|
|
1218
1574
|
const timeoutMs = resolveOpenAiTimeoutMs();
|
|
1219
|
-
const dispatcher = new
|
|
1575
|
+
const dispatcher = new import_undici2.Agent({
|
|
1220
1576
|
bodyTimeout: timeoutMs,
|
|
1221
1577
|
headersTimeout: timeoutMs
|
|
1222
1578
|
});
|
|
1223
|
-
|
|
1224
|
-
return (0,
|
|
1579
|
+
cachedFetch2 = ((input, init) => {
|
|
1580
|
+
return (0, import_undici2.fetch)(input, {
|
|
1225
1581
|
...init ?? {},
|
|
1226
1582
|
dispatcher
|
|
1227
1583
|
});
|
|
1228
1584
|
});
|
|
1229
|
-
return
|
|
1585
|
+
return cachedFetch2;
|
|
1230
1586
|
}
|
|
1231
1587
|
function getOpenAiApiKey() {
|
|
1232
|
-
if (
|
|
1233
|
-
return
|
|
1588
|
+
if (cachedApiKey2 !== null) {
|
|
1589
|
+
return cachedApiKey2;
|
|
1234
1590
|
}
|
|
1235
1591
|
loadLocalEnv();
|
|
1236
1592
|
const raw = process.env.OPENAI_API_KEY;
|
|
@@ -1238,32 +1594,32 @@ function getOpenAiApiKey() {
|
|
|
1238
1594
|
if (!value) {
|
|
1239
1595
|
throw new Error("OPENAI_API_KEY must be provided to access OpenAI APIs.");
|
|
1240
1596
|
}
|
|
1241
|
-
|
|
1242
|
-
return
|
|
1597
|
+
cachedApiKey2 = value;
|
|
1598
|
+
return cachedApiKey2;
|
|
1243
1599
|
}
|
|
1244
1600
|
function getOpenAiClient() {
|
|
1245
|
-
if (
|
|
1246
|
-
return
|
|
1601
|
+
if (cachedClient2) {
|
|
1602
|
+
return cachedClient2;
|
|
1247
1603
|
}
|
|
1248
1604
|
const apiKey = getOpenAiApiKey();
|
|
1249
1605
|
const timeoutMs = resolveOpenAiTimeoutMs();
|
|
1250
|
-
|
|
1606
|
+
cachedClient2 = new import_openai2.default({
|
|
1251
1607
|
apiKey,
|
|
1252
1608
|
fetch: getOpenAiFetch(),
|
|
1253
1609
|
timeout: timeoutMs
|
|
1254
1610
|
});
|
|
1255
|
-
return
|
|
1611
|
+
return cachedClient2;
|
|
1256
1612
|
}
|
|
1257
1613
|
|
|
1258
1614
|
// src/openai/calls.ts
|
|
1259
1615
|
var DEFAULT_OPENAI_REASONING_EFFORT = "medium";
|
|
1260
|
-
var
|
|
1616
|
+
var scheduler3 = createCallScheduler({
|
|
1261
1617
|
maxParallelRequests: 3,
|
|
1262
1618
|
minIntervalBetweenStartMs: 200,
|
|
1263
1619
|
startJitterMs: 200
|
|
1264
1620
|
});
|
|
1265
1621
|
async function runOpenAiCall(fn) {
|
|
1266
|
-
return
|
|
1622
|
+
return scheduler3.run(async () => fn(getOpenAiClient()));
|
|
1267
1623
|
}
|
|
1268
1624
|
|
|
1269
1625
|
// src/llm.ts
|
|
@@ -1279,7 +1635,16 @@ var LlmJsonCallError = class extends Error {
|
|
|
1279
1635
|
}
|
|
1280
1636
|
};
|
|
1281
1637
|
function tool(options) {
|
|
1282
|
-
return
|
|
1638
|
+
return {
|
|
1639
|
+
type: "function",
|
|
1640
|
+
...options
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
function customTool(options) {
|
|
1644
|
+
return {
|
|
1645
|
+
type: "custom",
|
|
1646
|
+
...options
|
|
1647
|
+
};
|
|
1283
1648
|
}
|
|
1284
1649
|
function isPlainRecord(value) {
|
|
1285
1650
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -1625,6 +1990,10 @@ function resolveProvider(model) {
|
|
|
1625
1990
|
if (model.startsWith("gemini-")) {
|
|
1626
1991
|
return { provider: "gemini", model };
|
|
1627
1992
|
}
|
|
1993
|
+
const fireworksModel = resolveFireworksModelId(model);
|
|
1994
|
+
if (fireworksModel) {
|
|
1995
|
+
return { provider: "fireworks", model: fireworksModel };
|
|
1996
|
+
}
|
|
1628
1997
|
return { provider: "openai", model };
|
|
1629
1998
|
}
|
|
1630
1999
|
function isOpenAiCodexModel(modelId) {
|
|
@@ -1654,6 +2023,27 @@ function toOpenAiReasoningEffort(effort) {
|
|
|
1654
2023
|
function resolveOpenAiVerbosity(modelId) {
|
|
1655
2024
|
return isOpenAiCodexModel(modelId) ? "medium" : "high";
|
|
1656
2025
|
}
|
|
2026
|
+
function isRetryableChatGptTransportError(error) {
|
|
2027
|
+
if (!(error instanceof Error)) {
|
|
2028
|
+
return false;
|
|
2029
|
+
}
|
|
2030
|
+
const message = error.message.toLowerCase();
|
|
2031
|
+
return message === "terminated" || message.includes("socket hang up") || message.includes("fetch failed") || message.includes("network");
|
|
2032
|
+
}
|
|
2033
|
+
async function collectChatGptCodexResponseWithRetry(options, maxAttempts = 2) {
|
|
2034
|
+
let attempt = 1;
|
|
2035
|
+
while (true) {
|
|
2036
|
+
try {
|
|
2037
|
+
return await collectChatGptCodexResponse(options);
|
|
2038
|
+
} catch (error) {
|
|
2039
|
+
if (attempt >= maxAttempts || !isRetryableChatGptTransportError(error)) {
|
|
2040
|
+
throw error;
|
|
2041
|
+
}
|
|
2042
|
+
await new Promise((resolve) => setTimeout(resolve, 250 * attempt));
|
|
2043
|
+
attempt += 1;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
1657
2047
|
function isInlineImageMime(mimeType) {
|
|
1658
2048
|
if (!mimeType) {
|
|
1659
2049
|
return false;
|
|
@@ -2236,6 +2626,53 @@ function toChatGptInput(contents) {
|
|
|
2236
2626
|
input
|
|
2237
2627
|
};
|
|
2238
2628
|
}
|
|
2629
|
+
function toFireworksMessages(contents, options) {
|
|
2630
|
+
const systemMessages = [];
|
|
2631
|
+
const messages = [];
|
|
2632
|
+
if (options?.responseMimeType === "application/json") {
|
|
2633
|
+
systemMessages.push("Return valid JSON only. Do not include markdown or prose outside JSON.");
|
|
2634
|
+
}
|
|
2635
|
+
if (options?.responseJsonSchema) {
|
|
2636
|
+
systemMessages.push(`Target JSON schema:
|
|
2637
|
+
${JSON.stringify(options.responseJsonSchema)}`);
|
|
2638
|
+
}
|
|
2639
|
+
for (const content of contents) {
|
|
2640
|
+
const text = content.parts.map((part) => {
|
|
2641
|
+
if (part.type === "text") {
|
|
2642
|
+
return part.text;
|
|
2643
|
+
}
|
|
2644
|
+
const mimeType = part.mimeType ?? "application/octet-stream";
|
|
2645
|
+
if (isInlineImageMime(mimeType)) {
|
|
2646
|
+
return `[image:${mimeType}]`;
|
|
2647
|
+
}
|
|
2648
|
+
return `[file:${mimeType}]`;
|
|
2649
|
+
}).join("\n").trim();
|
|
2650
|
+
if (content.role === "system" || content.role === "developer") {
|
|
2651
|
+
if (text.length > 0) {
|
|
2652
|
+
systemMessages.push(text);
|
|
2653
|
+
}
|
|
2654
|
+
continue;
|
|
2655
|
+
}
|
|
2656
|
+
if (content.role === "tool" || content.role === "assistant") {
|
|
2657
|
+
messages.push({
|
|
2658
|
+
role: "assistant",
|
|
2659
|
+
content: text.length > 0 ? text : "(empty content)"
|
|
2660
|
+
});
|
|
2661
|
+
continue;
|
|
2662
|
+
}
|
|
2663
|
+
messages.push({
|
|
2664
|
+
role: "user",
|
|
2665
|
+
content: text.length > 0 ? text : "(empty content)"
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
if (systemMessages.length > 0) {
|
|
2669
|
+
messages.unshift({
|
|
2670
|
+
role: "system",
|
|
2671
|
+
content: systemMessages.join("\n\n")
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
return messages;
|
|
2675
|
+
}
|
|
2239
2676
|
function toGeminiTools(tools) {
|
|
2240
2677
|
if (!tools || tools.length === 0) {
|
|
2241
2678
|
return void 0;
|
|
@@ -2409,21 +2846,56 @@ function extractChatGptUsageTokens(usage) {
|
|
|
2409
2846
|
totalTokens
|
|
2410
2847
|
};
|
|
2411
2848
|
}
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
import_genai2.FinishReason.PROHIBITED_CONTENT,
|
|
2416
|
-
import_genai2.FinishReason.SPII
|
|
2417
|
-
]);
|
|
2418
|
-
function isModerationFinish(reason) {
|
|
2419
|
-
if (!reason) {
|
|
2420
|
-
return false;
|
|
2849
|
+
function extractFireworksUsageTokens(usage) {
|
|
2850
|
+
if (!usage || typeof usage !== "object") {
|
|
2851
|
+
return void 0;
|
|
2421
2852
|
}
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2853
|
+
const promptTokens = toMaybeNumber(
|
|
2854
|
+
usage.prompt_tokens ?? usage.input_tokens
|
|
2855
|
+
);
|
|
2856
|
+
const cachedTokens = toMaybeNumber(
|
|
2857
|
+
usage.prompt_tokens_details?.cached_tokens ?? usage.input_tokens_details?.cached_tokens
|
|
2858
|
+
);
|
|
2859
|
+
const outputTokensRaw = toMaybeNumber(
|
|
2860
|
+
usage.completion_tokens ?? usage.output_tokens
|
|
2861
|
+
);
|
|
2862
|
+
const reasoningTokens = toMaybeNumber(
|
|
2863
|
+
usage.completion_tokens_details?.reasoning_tokens ?? usage.output_tokens_details?.reasoning_tokens
|
|
2864
|
+
);
|
|
2865
|
+
const totalTokens = toMaybeNumber(
|
|
2866
|
+
usage.total_tokens ?? usage.totalTokenCount
|
|
2867
|
+
);
|
|
2868
|
+
let responseTokens;
|
|
2869
|
+
if (outputTokensRaw !== void 0) {
|
|
2870
|
+
const adjusted = outputTokensRaw - (reasoningTokens ?? 0);
|
|
2871
|
+
responseTokens = adjusted >= 0 ? adjusted : 0;
|
|
2872
|
+
}
|
|
2873
|
+
if (promptTokens === void 0 && cachedTokens === void 0 && responseTokens === void 0 && reasoningTokens === void 0 && totalTokens === void 0) {
|
|
2874
|
+
return void 0;
|
|
2875
|
+
}
|
|
2876
|
+
return {
|
|
2877
|
+
promptTokens,
|
|
2878
|
+
cachedTokens,
|
|
2879
|
+
responseTokens,
|
|
2880
|
+
thinkingTokens: reasoningTokens,
|
|
2881
|
+
totalTokens
|
|
2882
|
+
};
|
|
2883
|
+
}
|
|
2884
|
+
var MODERATION_FINISH_REASONS = /* @__PURE__ */ new Set([
|
|
2885
|
+
import_genai2.FinishReason.SAFETY,
|
|
2886
|
+
import_genai2.FinishReason.BLOCKLIST,
|
|
2887
|
+
import_genai2.FinishReason.PROHIBITED_CONTENT,
|
|
2888
|
+
import_genai2.FinishReason.SPII
|
|
2889
|
+
]);
|
|
2890
|
+
function isModerationFinish(reason) {
|
|
2891
|
+
if (!reason) {
|
|
2892
|
+
return false;
|
|
2893
|
+
}
|
|
2894
|
+
return MODERATION_FINISH_REASONS.has(reason);
|
|
2895
|
+
}
|
|
2896
|
+
function mergeToolOutput(value) {
|
|
2897
|
+
if (typeof value === "string") {
|
|
2898
|
+
return value;
|
|
2427
2899
|
}
|
|
2428
2900
|
try {
|
|
2429
2901
|
return JSON.stringify(value);
|
|
@@ -2447,8 +2919,8 @@ function parseOpenAiToolArguments(raw) {
|
|
|
2447
2919
|
function formatZodIssues(issues) {
|
|
2448
2920
|
const messages = [];
|
|
2449
2921
|
for (const issue of issues) {
|
|
2450
|
-
const
|
|
2451
|
-
messages.push(`${
|
|
2922
|
+
const path6 = issue.path.length > 0 ? issue.path.map(String).join(".") : "input";
|
|
2923
|
+
messages.push(`${path6}: ${issue.message}`);
|
|
2452
2924
|
}
|
|
2453
2925
|
return messages.join("; ");
|
|
2454
2926
|
}
|
|
@@ -2464,7 +2936,7 @@ function buildToolErrorOutput(message, issues) {
|
|
|
2464
2936
|
return output;
|
|
2465
2937
|
}
|
|
2466
2938
|
async function executeToolCall(params) {
|
|
2467
|
-
const { toolName, tool: tool2, rawInput, parseError } = params;
|
|
2939
|
+
const { callKind, toolName, tool: tool2, rawInput, parseError } = params;
|
|
2468
2940
|
if (!tool2) {
|
|
2469
2941
|
const message = `Unknown tool: ${toolName}`;
|
|
2470
2942
|
return {
|
|
@@ -2472,6 +2944,39 @@ async function executeToolCall(params) {
|
|
|
2472
2944
|
outputPayload: buildToolErrorOutput(message)
|
|
2473
2945
|
};
|
|
2474
2946
|
}
|
|
2947
|
+
if (callKind === "custom") {
|
|
2948
|
+
if (!isCustomTool(tool2)) {
|
|
2949
|
+
const message = `Tool ${toolName} was called as custom_tool_call but is declared as function.`;
|
|
2950
|
+
const outputPayload = buildToolErrorOutput(message);
|
|
2951
|
+
return {
|
|
2952
|
+
result: { toolName, input: rawInput, output: outputPayload, error: message },
|
|
2953
|
+
outputPayload
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
const input = typeof rawInput === "string" ? rawInput : String(rawInput ?? "");
|
|
2957
|
+
try {
|
|
2958
|
+
const output = await tool2.execute(input);
|
|
2959
|
+
return {
|
|
2960
|
+
result: { toolName, input, output },
|
|
2961
|
+
outputPayload: output
|
|
2962
|
+
};
|
|
2963
|
+
} catch (error) {
|
|
2964
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2965
|
+
const outputPayload = buildToolErrorOutput(`Tool ${toolName} failed: ${message}`);
|
|
2966
|
+
return {
|
|
2967
|
+
result: { toolName, input, output: outputPayload, error: message },
|
|
2968
|
+
outputPayload
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
if (isCustomTool(tool2)) {
|
|
2973
|
+
const message = `Tool ${toolName} was called as function_call but is declared as custom.`;
|
|
2974
|
+
const outputPayload = buildToolErrorOutput(message);
|
|
2975
|
+
return {
|
|
2976
|
+
result: { toolName, input: rawInput, output: outputPayload, error: message },
|
|
2977
|
+
outputPayload
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2475
2980
|
if (parseError) {
|
|
2476
2981
|
const message = `Invalid JSON for tool ${toolName}: ${parseError}`;
|
|
2477
2982
|
return {
|
|
@@ -2528,8 +3033,12 @@ function normalizeChatGptToolIds(params) {
|
|
|
2528
3033
|
rawItemId = nextItemId ?? rawItemId;
|
|
2529
3034
|
}
|
|
2530
3035
|
const callValue = sanitizeChatGptToolId(rawCallId || rawItemId || (0, import_node_crypto.randomBytes)(8).toString("hex"));
|
|
2531
|
-
let itemValue = sanitizeChatGptToolId(rawItemId ||
|
|
2532
|
-
if (
|
|
3036
|
+
let itemValue = sanitizeChatGptToolId(rawItemId || callValue);
|
|
3037
|
+
if (params.callKind === "custom") {
|
|
3038
|
+
if (!itemValue.startsWith("ctc")) {
|
|
3039
|
+
itemValue = `ctc_${itemValue}`;
|
|
3040
|
+
}
|
|
3041
|
+
} else if (!itemValue.startsWith("fc")) {
|
|
2533
3042
|
itemValue = `fc-${itemValue}`;
|
|
2534
3043
|
}
|
|
2535
3044
|
return { callId: callValue, itemId: itemValue };
|
|
@@ -2594,7 +3103,7 @@ function extractOpenAiResponseParts(response) {
|
|
|
2594
3103
|
}
|
|
2595
3104
|
return { parts, blocked };
|
|
2596
3105
|
}
|
|
2597
|
-
function
|
|
3106
|
+
function extractOpenAiToolCalls(output) {
|
|
2598
3107
|
const calls = [];
|
|
2599
3108
|
if (!Array.isArray(output)) {
|
|
2600
3109
|
return calls;
|
|
@@ -2603,22 +3112,78 @@ function extractOpenAiFunctionCalls(output) {
|
|
|
2603
3112
|
if (!item || typeof item !== "object") {
|
|
2604
3113
|
continue;
|
|
2605
3114
|
}
|
|
2606
|
-
|
|
3115
|
+
const itemType = item.type;
|
|
3116
|
+
if (itemType === "function_call") {
|
|
2607
3117
|
const name = typeof item.name === "string" ? item.name : "";
|
|
2608
3118
|
const args = typeof item.arguments === "string" ? item.arguments : "";
|
|
2609
3119
|
const call_id = typeof item.call_id === "string" ? item.call_id : "";
|
|
2610
3120
|
const id = typeof item.id === "string" ? item.id : void 0;
|
|
2611
3121
|
if (name && call_id) {
|
|
2612
|
-
calls.push({ name, arguments: args, call_id, id });
|
|
3122
|
+
calls.push({ kind: "function", name, arguments: args, call_id, id });
|
|
3123
|
+
}
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
if (itemType === "custom_tool_call") {
|
|
3127
|
+
const name = typeof item.name === "string" ? item.name : "";
|
|
3128
|
+
const input = typeof item.input === "string" ? item.input : "";
|
|
3129
|
+
const call_id = typeof item.call_id === "string" ? item.call_id : "";
|
|
3130
|
+
const id = typeof item.id === "string" ? item.id : void 0;
|
|
3131
|
+
if (name && call_id) {
|
|
3132
|
+
calls.push({ kind: "custom", name, input, call_id, id });
|
|
2613
3133
|
}
|
|
2614
3134
|
}
|
|
2615
3135
|
}
|
|
2616
3136
|
return calls;
|
|
2617
3137
|
}
|
|
3138
|
+
function extractFireworksMessageText(message) {
|
|
3139
|
+
if (!message || typeof message !== "object") {
|
|
3140
|
+
return "";
|
|
3141
|
+
}
|
|
3142
|
+
const content = message.content;
|
|
3143
|
+
if (typeof content === "string") {
|
|
3144
|
+
return content;
|
|
3145
|
+
}
|
|
3146
|
+
if (!Array.isArray(content)) {
|
|
3147
|
+
return "";
|
|
3148
|
+
}
|
|
3149
|
+
let text = "";
|
|
3150
|
+
for (const part of content) {
|
|
3151
|
+
const textPart = part.text;
|
|
3152
|
+
if (typeof textPart === "string") {
|
|
3153
|
+
text += textPart;
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
return text;
|
|
3157
|
+
}
|
|
3158
|
+
function extractFireworksToolCalls(message) {
|
|
3159
|
+
if (!message || typeof message !== "object") {
|
|
3160
|
+
return [];
|
|
3161
|
+
}
|
|
3162
|
+
const toolCalls = message.tool_calls;
|
|
3163
|
+
if (!Array.isArray(toolCalls)) {
|
|
3164
|
+
return [];
|
|
3165
|
+
}
|
|
3166
|
+
const calls = [];
|
|
3167
|
+
for (const call of toolCalls) {
|
|
3168
|
+
if (!call || typeof call !== "object") {
|
|
3169
|
+
continue;
|
|
3170
|
+
}
|
|
3171
|
+
const id = typeof call.id === "string" ? call.id : "";
|
|
3172
|
+
const fn = call.function;
|
|
3173
|
+
const name = fn && typeof fn === "object" && typeof fn.name === "string" ? fn.name ?? "" : "";
|
|
3174
|
+
const args = fn && typeof fn === "object" && typeof fn.arguments === "string" ? fn.arguments ?? "" : "";
|
|
3175
|
+
if (id && name) {
|
|
3176
|
+
calls.push({ id, name, arguments: args });
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
return calls;
|
|
3180
|
+
}
|
|
2618
3181
|
function resolveGeminiThinkingConfig(modelId) {
|
|
2619
3182
|
switch (modelId) {
|
|
2620
3183
|
case "gemini-3-pro-preview":
|
|
2621
3184
|
return { includeThoughts: true };
|
|
3185
|
+
case "gemini-3-flash-preview":
|
|
3186
|
+
return { includeThoughts: true, thinkingBudget: 16384 };
|
|
2622
3187
|
case "gemini-2.5-pro":
|
|
2623
3188
|
return { includeThoughts: true, thinkingBudget: 32768 };
|
|
2624
3189
|
case "gemini-flash-latest":
|
|
@@ -2794,7 +3359,7 @@ async function runTextCall(params) {
|
|
|
2794
3359
|
};
|
|
2795
3360
|
let sawResponseDelta = false;
|
|
2796
3361
|
let sawThoughtDelta = false;
|
|
2797
|
-
const result = await
|
|
3362
|
+
const result = await collectChatGptCodexResponseWithRetry({
|
|
2798
3363
|
request: requestPayload,
|
|
2799
3364
|
signal,
|
|
2800
3365
|
onDelta: (delta) => {
|
|
@@ -2825,6 +3390,47 @@ async function runTextCall(params) {
|
|
|
2825
3390
|
if (!sawResponseDelta && fallbackText.length > 0) {
|
|
2826
3391
|
pushDelta("response", fallbackText);
|
|
2827
3392
|
}
|
|
3393
|
+
} else if (provider === "fireworks") {
|
|
3394
|
+
if (request.tools && request.tools.length > 0) {
|
|
3395
|
+
throw new Error(
|
|
3396
|
+
"Fireworks provider does not support provider-native tools in generateText; use runToolLoop for function tools."
|
|
3397
|
+
);
|
|
3398
|
+
}
|
|
3399
|
+
const fireworksMessages = toFireworksMessages(contents, {
|
|
3400
|
+
responseMimeType: request.responseMimeType,
|
|
3401
|
+
responseJsonSchema: request.responseJsonSchema
|
|
3402
|
+
});
|
|
3403
|
+
await runFireworksCall(async (client) => {
|
|
3404
|
+
const responseFormat = request.responseJsonSchema ? {
|
|
3405
|
+
type: "json_schema",
|
|
3406
|
+
json_schema: {
|
|
3407
|
+
name: "llm-response",
|
|
3408
|
+
schema: request.responseJsonSchema
|
|
3409
|
+
}
|
|
3410
|
+
} : request.responseMimeType === "application/json" ? { type: "json_object" } : void 0;
|
|
3411
|
+
const response = await client.chat.completions.create(
|
|
3412
|
+
{
|
|
3413
|
+
model: modelForProvider,
|
|
3414
|
+
messages: fireworksMessages,
|
|
3415
|
+
...responseFormat ? { response_format: responseFormat } : {}
|
|
3416
|
+
},
|
|
3417
|
+
{ signal }
|
|
3418
|
+
);
|
|
3419
|
+
modelVersion = typeof response.model === "string" ? response.model : request.model;
|
|
3420
|
+
queue.push({ type: "model", modelVersion });
|
|
3421
|
+
const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
|
|
3422
|
+
if (choice?.finish_reason === "content_filter") {
|
|
3423
|
+
blocked = true;
|
|
3424
|
+
queue.push({ type: "blocked" });
|
|
3425
|
+
}
|
|
3426
|
+
const textOutput = extractFireworksMessageText(
|
|
3427
|
+
choice?.message
|
|
3428
|
+
);
|
|
3429
|
+
if (textOutput.length > 0) {
|
|
3430
|
+
pushDelta("response", textOutput);
|
|
3431
|
+
}
|
|
3432
|
+
latestUsage = extractFireworksUsageTokens(response.usage);
|
|
3433
|
+
});
|
|
2828
3434
|
} else {
|
|
2829
3435
|
const geminiContents = contents.map(convertLlmContentToGeminiContent);
|
|
2830
3436
|
const config = {
|
|
@@ -2949,11 +3555,12 @@ function buildJsonSchemaConfig(request) {
|
|
|
2949
3555
|
const schemaName = (request.openAiSchemaName ?? "llm-response").trim() || "llm-response";
|
|
2950
3556
|
const providerInfo = resolveProvider(request.model);
|
|
2951
3557
|
const isOpenAiVariant = providerInfo.provider === "openai" || providerInfo.provider === "chatgpt";
|
|
3558
|
+
const isGeminiVariant = providerInfo.provider === "gemini";
|
|
2952
3559
|
const baseJsonSchema = (0, import_zod_to_json_schema.zodToJsonSchema)(request.schema, {
|
|
2953
3560
|
name: schemaName,
|
|
2954
3561
|
target: isOpenAiVariant ? "openAi" : "jsonSchema7"
|
|
2955
3562
|
});
|
|
2956
|
-
const responseJsonSchema = isOpenAiVariant ? resolveOpenAiSchemaRoot(baseJsonSchema) : addGeminiPropertyOrdering(baseJsonSchema);
|
|
3563
|
+
const responseJsonSchema = isOpenAiVariant ? resolveOpenAiSchemaRoot(baseJsonSchema) : isGeminiVariant ? addGeminiPropertyOrdering(baseJsonSchema) : resolveOpenAiSchemaRoot(baseJsonSchema);
|
|
2957
3564
|
if (isOpenAiVariant && !isJsonSchemaObject(responseJsonSchema)) {
|
|
2958
3565
|
throw new Error("OpenAI structured outputs require a JSON object schema at the root.");
|
|
2959
3566
|
}
|
|
@@ -3120,15 +3727,46 @@ var DEFAULT_TOOL_LOOP_MAX_STEPS = 8;
|
|
|
3120
3727
|
function resolveToolLoopContents(input) {
|
|
3121
3728
|
return resolveTextContents(input);
|
|
3122
3729
|
}
|
|
3123
|
-
function
|
|
3730
|
+
function isCustomTool(toolDef) {
|
|
3731
|
+
return toolDef.type === "custom";
|
|
3732
|
+
}
|
|
3733
|
+
function buildOpenAiToolsFromToolSet(tools) {
|
|
3124
3734
|
const toolEntries = Object.entries(tools);
|
|
3125
|
-
return toolEntries.map(([name, toolDef]) =>
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3735
|
+
return toolEntries.map(([name, toolDef]) => {
|
|
3736
|
+
if (isCustomTool(toolDef)) {
|
|
3737
|
+
return {
|
|
3738
|
+
type: "custom",
|
|
3739
|
+
name,
|
|
3740
|
+
description: toolDef.description ?? void 0,
|
|
3741
|
+
...toolDef.format ? { format: toolDef.format } : {}
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
return {
|
|
3745
|
+
type: "function",
|
|
3746
|
+
name,
|
|
3747
|
+
description: toolDef.description ?? void 0,
|
|
3748
|
+
parameters: buildOpenAiToolSchema(toolDef.inputSchema, name),
|
|
3749
|
+
strict: true
|
|
3750
|
+
};
|
|
3751
|
+
});
|
|
3752
|
+
}
|
|
3753
|
+
function buildFireworksToolsFromToolSet(tools) {
|
|
3754
|
+
const toolEntries = Object.entries(tools);
|
|
3755
|
+
return toolEntries.map(([name, toolDef]) => {
|
|
3756
|
+
if (isCustomTool(toolDef)) {
|
|
3757
|
+
throw new Error(
|
|
3758
|
+
`Fireworks provider does not support custom/freeform tools (${name}). Use JSON function tools instead.`
|
|
3759
|
+
);
|
|
3760
|
+
}
|
|
3761
|
+
return {
|
|
3762
|
+
type: "function",
|
|
3763
|
+
function: {
|
|
3764
|
+
name,
|
|
3765
|
+
description: toolDef.description ?? void 0,
|
|
3766
|
+
parameters: buildOpenAiToolSchema(toolDef.inputSchema, name)
|
|
3767
|
+
}
|
|
3768
|
+
};
|
|
3769
|
+
});
|
|
3132
3770
|
}
|
|
3133
3771
|
function buildOpenAiToolSchema(schema, name) {
|
|
3134
3772
|
const rawSchema = (0, import_zod_to_json_schema.zodToJsonSchema)(schema, { name, target: "openAi" });
|
|
@@ -3140,11 +3778,18 @@ function buildOpenAiToolSchema(schema, name) {
|
|
|
3140
3778
|
}
|
|
3141
3779
|
function buildGeminiFunctionDeclarations(tools) {
|
|
3142
3780
|
const toolEntries = Object.entries(tools);
|
|
3143
|
-
const functionDeclarations = toolEntries.map(([name, toolDef]) =>
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3781
|
+
const functionDeclarations = toolEntries.map(([name, toolDef]) => {
|
|
3782
|
+
if (isCustomTool(toolDef)) {
|
|
3783
|
+
throw new Error(
|
|
3784
|
+
`Gemini provider does not support custom/freeform tools (${name}). Use JSON function tools instead.`
|
|
3785
|
+
);
|
|
3786
|
+
}
|
|
3787
|
+
return {
|
|
3788
|
+
name,
|
|
3789
|
+
description: toolDef.description ?? "",
|
|
3790
|
+
parametersJsonSchema: buildGeminiToolSchema(toolDef.inputSchema, name)
|
|
3791
|
+
};
|
|
3792
|
+
});
|
|
3148
3793
|
return [{ functionDeclarations }];
|
|
3149
3794
|
}
|
|
3150
3795
|
function buildGeminiToolSchema(schema, name) {
|
|
@@ -3205,9 +3850,9 @@ async function runToolLoop(request) {
|
|
|
3205
3850
|
let finalText = "";
|
|
3206
3851
|
let finalThoughts = "";
|
|
3207
3852
|
if (providerInfo.provider === "openai") {
|
|
3208
|
-
const
|
|
3853
|
+
const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
|
|
3209
3854
|
const openAiNativeTools = toOpenAiTools(request.modelTools);
|
|
3210
|
-
const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...
|
|
3855
|
+
const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
|
|
3211
3856
|
const reasoningEffort = resolveOpenAiReasoningEffort(
|
|
3212
3857
|
providerInfo.model,
|
|
3213
3858
|
request.openAiReasoningEffort
|
|
@@ -3299,9 +3944,9 @@ async function runToolLoop(request) {
|
|
|
3299
3944
|
if (usageTokens) {
|
|
3300
3945
|
emitEvent({ type: "usage", usage: usageTokens, costUsd: stepCostUsd, modelVersion });
|
|
3301
3946
|
}
|
|
3302
|
-
const
|
|
3947
|
+
const responseToolCalls = extractOpenAiToolCalls(finalResponse.output);
|
|
3303
3948
|
const stepToolCalls = [];
|
|
3304
|
-
if (
|
|
3949
|
+
if (responseToolCalls.length === 0) {
|
|
3305
3950
|
finalText = responseText;
|
|
3306
3951
|
finalThoughts = reasoningSummary;
|
|
3307
3952
|
steps.push({
|
|
@@ -3315,10 +3960,21 @@ async function runToolLoop(request) {
|
|
|
3315
3960
|
});
|
|
3316
3961
|
return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
|
|
3317
3962
|
}
|
|
3318
|
-
const callInputs =
|
|
3963
|
+
const callInputs = responseToolCalls.map((call, index) => {
|
|
3319
3964
|
const toolIndex = index + 1;
|
|
3320
3965
|
const toolId = buildToolLogId(turn, toolIndex);
|
|
3321
3966
|
const toolName = call.name;
|
|
3967
|
+
if (call.kind === "custom") {
|
|
3968
|
+
return {
|
|
3969
|
+
call,
|
|
3970
|
+
toolName,
|
|
3971
|
+
value: call.input,
|
|
3972
|
+
parseError: void 0,
|
|
3973
|
+
toolId,
|
|
3974
|
+
turn,
|
|
3975
|
+
toolIndex
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3322
3978
|
const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
|
|
3323
3979
|
return { call, toolName, value, parseError, toolId, turn, toolIndex };
|
|
3324
3980
|
});
|
|
@@ -3333,6 +3989,7 @@ async function runToolLoop(request) {
|
|
|
3333
3989
|
},
|
|
3334
3990
|
async () => {
|
|
3335
3991
|
const { result, outputPayload } = await executeToolCall({
|
|
3992
|
+
callKind: entry.call.kind,
|
|
3336
3993
|
toolName: entry.toolName,
|
|
3337
3994
|
tool: request.tools[entry.toolName],
|
|
3338
3995
|
rawInput: entry.value,
|
|
@@ -3346,11 +4003,19 @@ async function runToolLoop(request) {
|
|
|
3346
4003
|
const toolOutputs = [];
|
|
3347
4004
|
for (const { entry, result, outputPayload } of callResults) {
|
|
3348
4005
|
stepToolCalls.push({ ...result, callId: entry.call.call_id });
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
4006
|
+
if (entry.call.kind === "custom") {
|
|
4007
|
+
toolOutputs.push({
|
|
4008
|
+
type: "custom_tool_call_output",
|
|
4009
|
+
call_id: entry.call.call_id,
|
|
4010
|
+
output: mergeToolOutput(outputPayload)
|
|
4011
|
+
});
|
|
4012
|
+
} else {
|
|
4013
|
+
toolOutputs.push({
|
|
4014
|
+
type: "function_call_output",
|
|
4015
|
+
call_id: entry.call.call_id,
|
|
4016
|
+
output: mergeToolOutput(outputPayload)
|
|
4017
|
+
});
|
|
4018
|
+
}
|
|
3354
4019
|
}
|
|
3355
4020
|
steps.push({
|
|
3356
4021
|
step: steps.length + 1,
|
|
@@ -3367,24 +4032,28 @@ async function runToolLoop(request) {
|
|
|
3367
4032
|
throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
|
|
3368
4033
|
}
|
|
3369
4034
|
if (providerInfo.provider === "chatgpt") {
|
|
3370
|
-
const
|
|
4035
|
+
const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
|
|
3371
4036
|
const openAiNativeTools = toOpenAiTools(request.modelTools);
|
|
3372
|
-
const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...
|
|
4037
|
+
const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
|
|
3373
4038
|
const reasoningEffort = resolveOpenAiReasoningEffort(
|
|
3374
4039
|
request.model,
|
|
3375
4040
|
request.openAiReasoningEffort
|
|
3376
4041
|
);
|
|
3377
4042
|
const toolLoopInput = toChatGptInput(contents);
|
|
4043
|
+
const conversationId = `tool-loop-${(0, import_node_crypto.randomBytes)(8).toString("hex")}`;
|
|
4044
|
+
const promptCacheKey = conversationId;
|
|
3378
4045
|
let input = [...toolLoopInput.input];
|
|
3379
4046
|
for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
|
|
3380
4047
|
const turn = stepIndex + 1;
|
|
3381
|
-
const response = await
|
|
4048
|
+
const response = await collectChatGptCodexResponseWithRetry({
|
|
4049
|
+
sessionId: conversationId,
|
|
3382
4050
|
request: {
|
|
3383
4051
|
model: providerInfo.model,
|
|
3384
4052
|
store: false,
|
|
3385
4053
|
stream: true,
|
|
3386
4054
|
instructions: toolLoopInput.instructions ?? "You are a helpful assistant.",
|
|
3387
4055
|
input,
|
|
4056
|
+
prompt_cache_key: promptCacheKey,
|
|
3388
4057
|
include: ["reasoning.encrypted_content"],
|
|
3389
4058
|
tools: openAiTools,
|
|
3390
4059
|
tool_choice: "auto",
|
|
@@ -3415,8 +4084,8 @@ async function runToolLoop(request) {
|
|
|
3415
4084
|
totalCostUsd += stepCostUsd;
|
|
3416
4085
|
const responseText = (response.text ?? "").trim();
|
|
3417
4086
|
const reasoningSummaryText = (response.reasoningSummaryText ?? "").trim();
|
|
3418
|
-
const
|
|
3419
|
-
if (
|
|
4087
|
+
const responseToolCalls = response.toolCalls ?? [];
|
|
4088
|
+
if (responseToolCalls.length === 0) {
|
|
3420
4089
|
finalText = responseText;
|
|
3421
4090
|
finalThoughts = reasoningSummaryText;
|
|
3422
4091
|
steps.push({
|
|
@@ -3432,12 +4101,16 @@ async function runToolLoop(request) {
|
|
|
3432
4101
|
}
|
|
3433
4102
|
const toolCalls = [];
|
|
3434
4103
|
const toolOutputs = [];
|
|
3435
|
-
const callInputs =
|
|
4104
|
+
const callInputs = responseToolCalls.map((call, index) => {
|
|
3436
4105
|
const toolIndex = index + 1;
|
|
3437
4106
|
const toolId = buildToolLogId(turn, toolIndex);
|
|
3438
4107
|
const toolName = call.name;
|
|
3439
|
-
const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
|
|
3440
|
-
const ids = normalizeChatGptToolIds({
|
|
4108
|
+
const { value, error: parseError } = call.kind === "custom" ? { value: call.input, error: void 0 } : parseOpenAiToolArguments(call.arguments);
|
|
4109
|
+
const ids = normalizeChatGptToolIds({
|
|
4110
|
+
callKind: call.kind,
|
|
4111
|
+
callId: call.callId,
|
|
4112
|
+
itemId: call.id
|
|
4113
|
+
});
|
|
3441
4114
|
return { call, toolName, value, parseError, ids, toolId, turn, toolIndex };
|
|
3442
4115
|
});
|
|
3443
4116
|
const callResults = await Promise.all(
|
|
@@ -3451,6 +4124,7 @@ async function runToolLoop(request) {
|
|
|
3451
4124
|
},
|
|
3452
4125
|
async () => {
|
|
3453
4126
|
const { result, outputPayload } = await executeToolCall({
|
|
4127
|
+
callKind: entry.call.kind,
|
|
3454
4128
|
toolName: entry.toolName,
|
|
3455
4129
|
tool: request.tools[entry.toolName],
|
|
3456
4130
|
rawInput: entry.value,
|
|
@@ -3463,19 +4137,35 @@ async function runToolLoop(request) {
|
|
|
3463
4137
|
);
|
|
3464
4138
|
for (const { entry, result, outputPayload } of callResults) {
|
|
3465
4139
|
toolCalls.push({ ...result, callId: entry.ids.callId });
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
4140
|
+
if (entry.call.kind === "custom") {
|
|
4141
|
+
toolOutputs.push({
|
|
4142
|
+
type: "custom_tool_call",
|
|
4143
|
+
id: entry.ids.itemId,
|
|
4144
|
+
call_id: entry.ids.callId,
|
|
4145
|
+
name: entry.toolName,
|
|
4146
|
+
input: entry.call.input,
|
|
4147
|
+
status: "completed"
|
|
4148
|
+
});
|
|
4149
|
+
toolOutputs.push({
|
|
4150
|
+
type: "custom_tool_call_output",
|
|
4151
|
+
call_id: entry.ids.callId,
|
|
4152
|
+
output: mergeToolOutput(outputPayload)
|
|
4153
|
+
});
|
|
4154
|
+
} else {
|
|
4155
|
+
toolOutputs.push({
|
|
4156
|
+
type: "function_call",
|
|
4157
|
+
id: entry.ids.itemId,
|
|
4158
|
+
call_id: entry.ids.callId,
|
|
4159
|
+
name: entry.toolName,
|
|
4160
|
+
arguments: entry.call.arguments,
|
|
4161
|
+
status: "completed"
|
|
4162
|
+
});
|
|
4163
|
+
toolOutputs.push({
|
|
4164
|
+
type: "function_call_output",
|
|
4165
|
+
call_id: entry.ids.callId,
|
|
4166
|
+
output: mergeToolOutput(outputPayload)
|
|
4167
|
+
});
|
|
4168
|
+
}
|
|
3479
4169
|
}
|
|
3480
4170
|
steps.push({
|
|
3481
4171
|
step: steps.length + 1,
|
|
@@ -3490,6 +4180,134 @@ async function runToolLoop(request) {
|
|
|
3490
4180
|
}
|
|
3491
4181
|
throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
|
|
3492
4182
|
}
|
|
4183
|
+
if (providerInfo.provider === "fireworks") {
|
|
4184
|
+
if (request.modelTools && request.modelTools.length > 0) {
|
|
4185
|
+
throw new Error(
|
|
4186
|
+
"Fireworks provider does not support provider-native modelTools in runToolLoop."
|
|
4187
|
+
);
|
|
4188
|
+
}
|
|
4189
|
+
const fireworksTools = buildFireworksToolsFromToolSet(request.tools);
|
|
4190
|
+
const messages = toFireworksMessages(contents);
|
|
4191
|
+
for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
|
|
4192
|
+
const turn = stepIndex + 1;
|
|
4193
|
+
const response = await runFireworksCall(async (client) => {
|
|
4194
|
+
return await client.chat.completions.create(
|
|
4195
|
+
{
|
|
4196
|
+
model: providerInfo.model,
|
|
4197
|
+
messages,
|
|
4198
|
+
tools: fireworksTools,
|
|
4199
|
+
tool_choice: "auto",
|
|
4200
|
+
parallel_tool_calls: true
|
|
4201
|
+
},
|
|
4202
|
+
{ signal: request.signal }
|
|
4203
|
+
);
|
|
4204
|
+
});
|
|
4205
|
+
const modelVersion = typeof response.model === "string" ? response.model : request.model;
|
|
4206
|
+
request.onEvent?.({ type: "model", modelVersion });
|
|
4207
|
+
const choice = Array.isArray(response.choices) ? response.choices[0] : void 0;
|
|
4208
|
+
if (choice?.finish_reason === "content_filter") {
|
|
4209
|
+
request.onEvent?.({ type: "blocked" });
|
|
4210
|
+
}
|
|
4211
|
+
const message = choice?.message;
|
|
4212
|
+
const responseText = extractFireworksMessageText(message).trim();
|
|
4213
|
+
if (responseText.length > 0) {
|
|
4214
|
+
request.onEvent?.({ type: "delta", channel: "response", text: responseText });
|
|
4215
|
+
}
|
|
4216
|
+
const usageTokens = extractFireworksUsageTokens(response.usage);
|
|
4217
|
+
const stepCostUsd = estimateCallCostUsd({
|
|
4218
|
+
modelId: modelVersion,
|
|
4219
|
+
tokens: usageTokens,
|
|
4220
|
+
responseImages: 0
|
|
4221
|
+
});
|
|
4222
|
+
totalCostUsd += stepCostUsd;
|
|
4223
|
+
if (usageTokens) {
|
|
4224
|
+
request.onEvent?.({
|
|
4225
|
+
type: "usage",
|
|
4226
|
+
usage: usageTokens,
|
|
4227
|
+
costUsd: stepCostUsd,
|
|
4228
|
+
modelVersion
|
|
4229
|
+
});
|
|
4230
|
+
}
|
|
4231
|
+
const responseToolCalls = extractFireworksToolCalls(message);
|
|
4232
|
+
if (responseToolCalls.length === 0) {
|
|
4233
|
+
finalText = responseText;
|
|
4234
|
+
finalThoughts = "";
|
|
4235
|
+
steps.push({
|
|
4236
|
+
step: steps.length + 1,
|
|
4237
|
+
modelVersion,
|
|
4238
|
+
text: responseText || void 0,
|
|
4239
|
+
thoughts: void 0,
|
|
4240
|
+
toolCalls: [],
|
|
4241
|
+
usage: usageTokens,
|
|
4242
|
+
costUsd: stepCostUsd
|
|
4243
|
+
});
|
|
4244
|
+
return { text: finalText, thoughts: finalThoughts, steps, totalCostUsd };
|
|
4245
|
+
}
|
|
4246
|
+
const stepToolCalls = [];
|
|
4247
|
+
const callInputs = responseToolCalls.map((call, index) => {
|
|
4248
|
+
const toolIndex = index + 1;
|
|
4249
|
+
const toolId = buildToolLogId(turn, toolIndex);
|
|
4250
|
+
const { value, error: parseError } = parseOpenAiToolArguments(call.arguments);
|
|
4251
|
+
return { call, toolName: call.name, value, parseError, toolId, turn, toolIndex };
|
|
4252
|
+
});
|
|
4253
|
+
const callResults = await Promise.all(
|
|
4254
|
+
callInputs.map(async (entry) => {
|
|
4255
|
+
return await toolCallContextStorage.run(
|
|
4256
|
+
{
|
|
4257
|
+
toolName: entry.toolName,
|
|
4258
|
+
toolId: entry.toolId,
|
|
4259
|
+
turn: entry.turn,
|
|
4260
|
+
toolIndex: entry.toolIndex
|
|
4261
|
+
},
|
|
4262
|
+
async () => {
|
|
4263
|
+
const { result, outputPayload } = await executeToolCall({
|
|
4264
|
+
callKind: "function",
|
|
4265
|
+
toolName: entry.toolName,
|
|
4266
|
+
tool: request.tools[entry.toolName],
|
|
4267
|
+
rawInput: entry.value,
|
|
4268
|
+
parseError: entry.parseError
|
|
4269
|
+
});
|
|
4270
|
+
return { entry, result, outputPayload };
|
|
4271
|
+
}
|
|
4272
|
+
);
|
|
4273
|
+
})
|
|
4274
|
+
);
|
|
4275
|
+
const assistantToolCalls = [];
|
|
4276
|
+
const toolMessages = [];
|
|
4277
|
+
for (const { entry, result, outputPayload } of callResults) {
|
|
4278
|
+
stepToolCalls.push({ ...result, callId: entry.call.id });
|
|
4279
|
+
assistantToolCalls.push({
|
|
4280
|
+
id: entry.call.id,
|
|
4281
|
+
type: "function",
|
|
4282
|
+
function: {
|
|
4283
|
+
name: entry.toolName,
|
|
4284
|
+
arguments: entry.call.arguments
|
|
4285
|
+
}
|
|
4286
|
+
});
|
|
4287
|
+
toolMessages.push({
|
|
4288
|
+
role: "tool",
|
|
4289
|
+
tool_call_id: entry.call.id,
|
|
4290
|
+
content: mergeToolOutput(outputPayload)
|
|
4291
|
+
});
|
|
4292
|
+
}
|
|
4293
|
+
steps.push({
|
|
4294
|
+
step: steps.length + 1,
|
|
4295
|
+
modelVersion,
|
|
4296
|
+
text: responseText || void 0,
|
|
4297
|
+
thoughts: void 0,
|
|
4298
|
+
toolCalls: stepToolCalls,
|
|
4299
|
+
usage: usageTokens,
|
|
4300
|
+
costUsd: stepCostUsd
|
|
4301
|
+
});
|
|
4302
|
+
messages.push({
|
|
4303
|
+
role: "assistant",
|
|
4304
|
+
...responseText.length > 0 ? { content: responseText } : {},
|
|
4305
|
+
tool_calls: assistantToolCalls
|
|
4306
|
+
});
|
|
4307
|
+
messages.push(...toolMessages);
|
|
4308
|
+
}
|
|
4309
|
+
throw new Error(`Tool loop exceeded max steps (${maxSteps}) without final response.`);
|
|
4310
|
+
}
|
|
3493
4311
|
const geminiFunctionTools = buildGeminiFunctionDeclarations(request.tools);
|
|
3494
4312
|
const geminiNativeTools = toGeminiTools(request.modelTools);
|
|
3495
4313
|
const geminiTools = geminiNativeTools ? geminiNativeTools.concat(geminiFunctionTools) : geminiFunctionTools;
|
|
@@ -3639,6 +4457,7 @@ async function runToolLoop(request) {
|
|
|
3639
4457
|
},
|
|
3640
4458
|
async () => {
|
|
3641
4459
|
const { result, outputPayload } = await executeToolCall({
|
|
4460
|
+
callKind: "function",
|
|
3642
4461
|
toolName: entry.toolName,
|
|
3643
4462
|
tool: request.tools[entry.toolName],
|
|
3644
4463
|
rawInput: entry.rawInput
|
|
@@ -3916,12 +4735,1881 @@ function appendMarkdownSourcesSection(value, sources) {
|
|
|
3916
4735
|
## Sources
|
|
3917
4736
|
${lines}`;
|
|
3918
4737
|
}
|
|
4738
|
+
|
|
4739
|
+
// src/tools/filesystemTools.ts
|
|
4740
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
4741
|
+
var import_zod5 = require("zod");
|
|
4742
|
+
|
|
4743
|
+
// src/tools/applyPatch.ts
|
|
4744
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
4745
|
+
var import_zod4 = require("zod");
|
|
4746
|
+
|
|
4747
|
+
// src/tools/filesystem.ts
|
|
4748
|
+
var import_node_fs3 = require("fs");
|
|
4749
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
4750
|
+
var InMemoryAgentFilesystem = class {
|
|
4751
|
+
#files = /* @__PURE__ */ new Map();
|
|
4752
|
+
#dirs = /* @__PURE__ */ new Map();
|
|
4753
|
+
#clock = 0;
|
|
4754
|
+
constructor(initialFiles = {}) {
|
|
4755
|
+
const root = import_node_path3.default.resolve("/");
|
|
4756
|
+
this.#dirs.set(root, { mtimeMs: this.#nextMtime() });
|
|
4757
|
+
for (const [filePath, content] of Object.entries(initialFiles)) {
|
|
4758
|
+
const absolutePath = import_node_path3.default.resolve(filePath);
|
|
4759
|
+
this.#ensureDirSync(import_node_path3.default.dirname(absolutePath));
|
|
4760
|
+
this.#files.set(absolutePath, {
|
|
4761
|
+
content,
|
|
4762
|
+
mtimeMs: this.#nextMtime()
|
|
4763
|
+
});
|
|
4764
|
+
}
|
|
4765
|
+
}
|
|
4766
|
+
async readTextFile(filePath) {
|
|
4767
|
+
const absolutePath = import_node_path3.default.resolve(filePath);
|
|
4768
|
+
const file = this.#files.get(absolutePath);
|
|
4769
|
+
if (!file) {
|
|
4770
|
+
throw createNoSuchFileError("open", absolutePath);
|
|
4771
|
+
}
|
|
4772
|
+
return file.content;
|
|
4773
|
+
}
|
|
4774
|
+
async writeTextFile(filePath, content) {
|
|
4775
|
+
const absolutePath = import_node_path3.default.resolve(filePath);
|
|
4776
|
+
const parentPath = import_node_path3.default.dirname(absolutePath);
|
|
4777
|
+
if (!this.#dirs.has(parentPath)) {
|
|
4778
|
+
throw createNoSuchFileError("open", parentPath);
|
|
4779
|
+
}
|
|
4780
|
+
this.#files.set(absolutePath, { content, mtimeMs: this.#nextMtime() });
|
|
4781
|
+
}
|
|
4782
|
+
async deleteFile(filePath) {
|
|
4783
|
+
const absolutePath = import_node_path3.default.resolve(filePath);
|
|
4784
|
+
if (!this.#files.delete(absolutePath)) {
|
|
4785
|
+
throw createNoSuchFileError("unlink", absolutePath);
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
async ensureDir(directoryPath) {
|
|
4789
|
+
this.#ensureDirSync(import_node_path3.default.resolve(directoryPath));
|
|
4790
|
+
}
|
|
4791
|
+
async readDir(directoryPath) {
|
|
4792
|
+
const absolutePath = import_node_path3.default.resolve(directoryPath);
|
|
4793
|
+
const directory = this.#dirs.get(absolutePath);
|
|
4794
|
+
if (!directory) {
|
|
4795
|
+
throw createNoSuchFileError("scandir", absolutePath);
|
|
4796
|
+
}
|
|
4797
|
+
const entries = [];
|
|
4798
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
4799
|
+
for (const [dirPath, dirRecord] of this.#dirs.entries()) {
|
|
4800
|
+
if (dirPath === absolutePath) {
|
|
4801
|
+
continue;
|
|
4802
|
+
}
|
|
4803
|
+
if (import_node_path3.default.dirname(dirPath) !== absolutePath) {
|
|
4804
|
+
continue;
|
|
4805
|
+
}
|
|
4806
|
+
const name = import_node_path3.default.basename(dirPath);
|
|
4807
|
+
if (seenNames.has(name)) {
|
|
4808
|
+
continue;
|
|
4809
|
+
}
|
|
4810
|
+
seenNames.add(name);
|
|
4811
|
+
entries.push({
|
|
4812
|
+
name,
|
|
4813
|
+
path: dirPath,
|
|
4814
|
+
kind: "directory",
|
|
4815
|
+
mtimeMs: dirRecord.mtimeMs
|
|
4816
|
+
});
|
|
4817
|
+
}
|
|
4818
|
+
for (const [filePath, fileRecord] of this.#files.entries()) {
|
|
4819
|
+
if (import_node_path3.default.dirname(filePath) !== absolutePath) {
|
|
4820
|
+
continue;
|
|
4821
|
+
}
|
|
4822
|
+
const name = import_node_path3.default.basename(filePath);
|
|
4823
|
+
if (seenNames.has(name)) {
|
|
4824
|
+
continue;
|
|
4825
|
+
}
|
|
4826
|
+
seenNames.add(name);
|
|
4827
|
+
entries.push({
|
|
4828
|
+
name,
|
|
4829
|
+
path: filePath,
|
|
4830
|
+
kind: "file",
|
|
4831
|
+
mtimeMs: fileRecord.mtimeMs
|
|
4832
|
+
});
|
|
4833
|
+
}
|
|
4834
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
4835
|
+
return entries;
|
|
4836
|
+
}
|
|
4837
|
+
async stat(entryPath) {
|
|
4838
|
+
const absolutePath = import_node_path3.default.resolve(entryPath);
|
|
4839
|
+
const file = this.#files.get(absolutePath);
|
|
4840
|
+
if (file) {
|
|
4841
|
+
return { kind: "file", mtimeMs: file.mtimeMs };
|
|
4842
|
+
}
|
|
4843
|
+
const directory = this.#dirs.get(absolutePath);
|
|
4844
|
+
if (directory) {
|
|
4845
|
+
return { kind: "directory", mtimeMs: directory.mtimeMs };
|
|
4846
|
+
}
|
|
4847
|
+
throw createNoSuchFileError("stat", absolutePath);
|
|
4848
|
+
}
|
|
4849
|
+
snapshot() {
|
|
4850
|
+
const entries = [...this.#files.entries()].sort(([left], [right]) => left.localeCompare(right));
|
|
4851
|
+
return Object.fromEntries(entries.map(([filePath, record]) => [filePath, record.content]));
|
|
4852
|
+
}
|
|
4853
|
+
#ensureDirSync(directoryPath) {
|
|
4854
|
+
const absolutePath = import_node_path3.default.resolve(directoryPath);
|
|
4855
|
+
const parts = [];
|
|
4856
|
+
let cursor = absolutePath;
|
|
4857
|
+
for (; ; ) {
|
|
4858
|
+
if (this.#dirs.has(cursor)) {
|
|
4859
|
+
break;
|
|
4860
|
+
}
|
|
4861
|
+
parts.push(cursor);
|
|
4862
|
+
const parent = import_node_path3.default.dirname(cursor);
|
|
4863
|
+
if (parent === cursor) {
|
|
4864
|
+
break;
|
|
4865
|
+
}
|
|
4866
|
+
cursor = parent;
|
|
4867
|
+
}
|
|
4868
|
+
for (let index = parts.length - 1; index >= 0; index -= 1) {
|
|
4869
|
+
const nextDir = parts[index];
|
|
4870
|
+
if (nextDir === void 0) {
|
|
4871
|
+
continue;
|
|
4872
|
+
}
|
|
4873
|
+
if (!this.#dirs.has(nextDir)) {
|
|
4874
|
+
this.#dirs.set(nextDir, { mtimeMs: this.#nextMtime() });
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
#nextMtime() {
|
|
4879
|
+
this.#clock += 1;
|
|
4880
|
+
return this.#clock;
|
|
4881
|
+
}
|
|
4882
|
+
};
|
|
4883
|
+
function createNodeAgentFilesystem() {
|
|
4884
|
+
return {
|
|
4885
|
+
readTextFile: async (filePath) => import_node_fs3.promises.readFile(filePath, "utf8"),
|
|
4886
|
+
writeTextFile: async (filePath, content) => import_node_fs3.promises.writeFile(filePath, content, "utf8"),
|
|
4887
|
+
deleteFile: async (filePath) => import_node_fs3.promises.unlink(filePath),
|
|
4888
|
+
ensureDir: async (directoryPath) => {
|
|
4889
|
+
await import_node_fs3.promises.mkdir(directoryPath, { recursive: true });
|
|
4890
|
+
},
|
|
4891
|
+
readDir: async (directoryPath) => {
|
|
4892
|
+
const entries = await import_node_fs3.promises.readdir(directoryPath, { withFileTypes: true });
|
|
4893
|
+
const result = [];
|
|
4894
|
+
for (const entry of entries) {
|
|
4895
|
+
const entryPath = import_node_path3.default.resolve(directoryPath, entry.name);
|
|
4896
|
+
const stats = await import_node_fs3.promises.lstat(entryPath);
|
|
4897
|
+
result.push({
|
|
4898
|
+
name: entry.name,
|
|
4899
|
+
path: entryPath,
|
|
4900
|
+
kind: statsToKind(stats),
|
|
4901
|
+
mtimeMs: stats.mtimeMs
|
|
4902
|
+
});
|
|
4903
|
+
}
|
|
4904
|
+
return result;
|
|
4905
|
+
},
|
|
4906
|
+
stat: async (entryPath) => {
|
|
4907
|
+
const stats = await import_node_fs3.promises.lstat(entryPath);
|
|
4908
|
+
return {
|
|
4909
|
+
kind: statsToKind(stats),
|
|
4910
|
+
mtimeMs: stats.mtimeMs
|
|
4911
|
+
};
|
|
4912
|
+
}
|
|
4913
|
+
};
|
|
4914
|
+
}
|
|
4915
|
+
function createInMemoryAgentFilesystem(initialFiles = {}) {
|
|
4916
|
+
return new InMemoryAgentFilesystem(initialFiles);
|
|
4917
|
+
}
|
|
4918
|
+
function statsToKind(stats) {
|
|
4919
|
+
if (stats.isSymbolicLink()) {
|
|
4920
|
+
return "symlink";
|
|
4921
|
+
}
|
|
4922
|
+
if (stats.isDirectory()) {
|
|
4923
|
+
return "directory";
|
|
4924
|
+
}
|
|
4925
|
+
if (stats.isFile()) {
|
|
4926
|
+
return "file";
|
|
4927
|
+
}
|
|
4928
|
+
return "other";
|
|
4929
|
+
}
|
|
4930
|
+
function createNoSuchFileError(syscall, filePath) {
|
|
4931
|
+
const error = new Error(
|
|
4932
|
+
`ENOENT: no such file or directory, ${syscall} '${filePath}'`
|
|
4933
|
+
);
|
|
4934
|
+
error.code = "ENOENT";
|
|
4935
|
+
error.syscall = syscall;
|
|
4936
|
+
error.path = filePath;
|
|
4937
|
+
return error;
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4940
|
+
// src/tools/applyPatch.ts
|
|
4941
|
+
var BEGIN_PATCH_LINE = "*** Begin Patch";
|
|
4942
|
+
var END_PATCH_LINE = "*** End Patch";
|
|
4943
|
+
var ADD_FILE_PREFIX = "*** Add File: ";
|
|
4944
|
+
var DELETE_FILE_PREFIX = "*** Delete File: ";
|
|
4945
|
+
var UPDATE_FILE_PREFIX = "*** Update File: ";
|
|
4946
|
+
var MOVE_TO_PREFIX = "*** Move to: ";
|
|
4947
|
+
var END_OF_FILE_LINE = "*** End of File";
|
|
4948
|
+
var DEFAULT_MAX_PATCH_BYTES = 1024 * 1024;
|
|
4949
|
+
var CODEX_APPLY_PATCH_INPUT_DESCRIPTION = "The entire contents of the apply_patch command";
|
|
4950
|
+
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.";
|
|
4951
|
+
var CODEX_APPLY_PATCH_LARK_GRAMMAR = [
|
|
4952
|
+
"start: begin_patch hunk+ end_patch",
|
|
4953
|
+
'begin_patch: "*** Begin Patch" LF',
|
|
4954
|
+
'end_patch: "*** End Patch" LF?',
|
|
4955
|
+
"",
|
|
4956
|
+
"hunk: add_hunk | delete_hunk | update_hunk",
|
|
4957
|
+
'add_hunk: "*** Add File: " filename LF add_line+',
|
|
4958
|
+
'delete_hunk: "*** Delete File: " filename LF',
|
|
4959
|
+
'update_hunk: "*** Update File: " filename LF change_move? change?',
|
|
4960
|
+
"",
|
|
4961
|
+
"filename: /(.+)/",
|
|
4962
|
+
'add_line: "+" /(.*)/ LF -> line',
|
|
4963
|
+
"",
|
|
4964
|
+
'change_move: "*** Move to: " filename LF',
|
|
4965
|
+
"change: (change_context | change_line)+ eof_line?",
|
|
4966
|
+
'change_context: ("@@" | "@@ " /(.+)/) LF',
|
|
4967
|
+
'change_line: ("+" | "-" | " ") /(.*)/ LF',
|
|
4968
|
+
'eof_line: "*** End of File" LF',
|
|
4969
|
+
"",
|
|
4970
|
+
"%import common.LF"
|
|
4971
|
+
].join("\n");
|
|
4972
|
+
var CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION = [
|
|
4973
|
+
"Use the `apply_patch` tool to edit files.",
|
|
4974
|
+
"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:",
|
|
4975
|
+
"",
|
|
4976
|
+
"*** Begin Patch",
|
|
4977
|
+
"[ one or more file sections ]",
|
|
4978
|
+
"*** End Patch",
|
|
4979
|
+
"",
|
|
4980
|
+
"Within that envelope, you get a sequence of file operations.",
|
|
4981
|
+
"You MUST include a header to specify the action you are taking.",
|
|
4982
|
+
"Each operation starts with one of three headers:",
|
|
4983
|
+
"",
|
|
4984
|
+
"*** Add File: <path> - create a new file. Every following line is a + line (the initial contents).",
|
|
4985
|
+
"*** Delete File: <path> - remove an existing file. Nothing follows.",
|
|
4986
|
+
"*** Update File: <path> - patch an existing file in place (optionally with a rename).",
|
|
4987
|
+
"",
|
|
4988
|
+
"May be immediately followed by *** Move to: <new path> if you want to rename the file.",
|
|
4989
|
+
"Then one or more \u201Chunks\u201D, each introduced by @@ (optionally followed by a hunk header).",
|
|
4990
|
+
"Within a hunk each line starts with:",
|
|
4991
|
+
"",
|
|
4992
|
+
"For instructions on [context_before] and [context_after]:",
|
|
4993
|
+
"- 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.",
|
|
4994
|
+
"- 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:",
|
|
4995
|
+
"@@ class BaseClass",
|
|
4996
|
+
"[3 lines of pre-context]",
|
|
4997
|
+
"- [old_code]",
|
|
4998
|
+
"+ [new_code]",
|
|
4999
|
+
"[3 lines of post-context]",
|
|
5000
|
+
"",
|
|
5001
|
+
"- 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:",
|
|
5002
|
+
"",
|
|
5003
|
+
"@@ class BaseClass",
|
|
5004
|
+
"@@ def method():",
|
|
5005
|
+
"[3 lines of pre-context]",
|
|
5006
|
+
"- [old_code]",
|
|
5007
|
+
"+ [new_code]",
|
|
5008
|
+
"[3 lines of post-context]",
|
|
5009
|
+
"",
|
|
5010
|
+
"The full grammar definition is below:",
|
|
5011
|
+
"Patch := Begin { FileOp } End",
|
|
5012
|
+
'Begin := "*** Begin Patch" NEWLINE',
|
|
5013
|
+
'End := "*** End Patch" NEWLINE',
|
|
5014
|
+
"FileOp := AddFile | DeleteFile | UpdateFile",
|
|
5015
|
+
'AddFile := "*** Add File: " path NEWLINE { "+" line NEWLINE }',
|
|
5016
|
+
'DeleteFile := "*** Delete File: " path NEWLINE',
|
|
5017
|
+
'UpdateFile := "*** Update File: " path NEWLINE [ MoveTo ] { Hunk }',
|
|
5018
|
+
'MoveTo := "*** Move to: " newPath NEWLINE',
|
|
5019
|
+
'Hunk := "@@" [ header ] NEWLINE { HunkLine } [ "*** End of File" NEWLINE ]',
|
|
5020
|
+
'HunkLine := (" " | "-" | "+") text NEWLINE',
|
|
5021
|
+
"",
|
|
5022
|
+
"A full patch can combine several operations:",
|
|
5023
|
+
"",
|
|
5024
|
+
"*** Begin Patch",
|
|
5025
|
+
"*** Add File: hello.txt",
|
|
5026
|
+
"+Hello world",
|
|
5027
|
+
"*** Update File: src/app.py",
|
|
5028
|
+
"*** Move to: src/main.py",
|
|
5029
|
+
"@@ def greet():",
|
|
5030
|
+
'-print("Hi")',
|
|
5031
|
+
'+print("Hello, world!")',
|
|
5032
|
+
"*** Delete File: obsolete.txt",
|
|
5033
|
+
"*** End Patch",
|
|
5034
|
+
"",
|
|
5035
|
+
"It is important to remember:",
|
|
5036
|
+
"",
|
|
5037
|
+
"- You must include a header with your intended action (Add/Delete/Update)",
|
|
5038
|
+
"- You must prefix new lines with `+` even when creating a new file",
|
|
5039
|
+
"- File references can only be relative, NEVER ABSOLUTE."
|
|
5040
|
+
].join("\n");
|
|
5041
|
+
var applyPatchToolInputSchema = import_zod4.z.object({
|
|
5042
|
+
input: import_zod4.z.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
|
|
5043
|
+
});
|
|
5044
|
+
function createApplyPatchTool(options = {}) {
|
|
5045
|
+
return tool({
|
|
5046
|
+
description: options.description ?? CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION,
|
|
5047
|
+
inputSchema: applyPatchToolInputSchema,
|
|
5048
|
+
execute: async ({ input }) => applyPatch({
|
|
5049
|
+
patch: input,
|
|
5050
|
+
cwd: options.cwd,
|
|
5051
|
+
fs: options.fs,
|
|
5052
|
+
allowOutsideCwd: options.allowOutsideCwd,
|
|
5053
|
+
checkAccess: options.checkAccess,
|
|
5054
|
+
maxPatchBytes: options.maxPatchBytes
|
|
5055
|
+
})
|
|
5056
|
+
});
|
|
5057
|
+
}
|
|
5058
|
+
async function applyPatch(request) {
|
|
5059
|
+
const cwd = import_node_path4.default.resolve(request.cwd ?? process.cwd());
|
|
5060
|
+
const adapter = request.fs ?? createNodeAgentFilesystem();
|
|
5061
|
+
const allowOutsideCwd = request.allowOutsideCwd === true;
|
|
5062
|
+
const patchBytes = Buffer.byteLength(request.patch, "utf8");
|
|
5063
|
+
const maxPatchBytes = request.maxPatchBytes ?? DEFAULT_MAX_PATCH_BYTES;
|
|
5064
|
+
if (patchBytes > maxPatchBytes) {
|
|
5065
|
+
throw new Error(
|
|
5066
|
+
`apply_patch failed: patch too large (${patchBytes} bytes > ${maxPatchBytes} bytes)`
|
|
5067
|
+
);
|
|
5068
|
+
}
|
|
5069
|
+
const parsed = parsePatchDocument(normalizePatchText(request.patch));
|
|
5070
|
+
const added = [];
|
|
5071
|
+
const modified = [];
|
|
5072
|
+
const deleted = [];
|
|
5073
|
+
for (const operation of parsed.operations) {
|
|
5074
|
+
if (operation.type === "add") {
|
|
5075
|
+
const absolutePath2 = resolvePatchPath(operation.path, cwd, allowOutsideCwd);
|
|
5076
|
+
await runAccessHook(request.checkAccess, {
|
|
5077
|
+
cwd,
|
|
5078
|
+
kind: "add",
|
|
5079
|
+
path: absolutePath2
|
|
5080
|
+
});
|
|
5081
|
+
await adapter.ensureDir(import_node_path4.default.dirname(absolutePath2));
|
|
5082
|
+
await adapter.writeTextFile(absolutePath2, operation.content);
|
|
5083
|
+
added.push(toDisplayPath(absolutePath2, cwd));
|
|
5084
|
+
continue;
|
|
5085
|
+
}
|
|
5086
|
+
if (operation.type === "delete") {
|
|
5087
|
+
const absolutePath2 = resolvePatchPath(operation.path, cwd, allowOutsideCwd);
|
|
5088
|
+
await runAccessHook(request.checkAccess, {
|
|
5089
|
+
cwd,
|
|
5090
|
+
kind: "delete",
|
|
5091
|
+
path: absolutePath2
|
|
5092
|
+
});
|
|
5093
|
+
await adapter.readTextFile(absolutePath2);
|
|
5094
|
+
await adapter.deleteFile(absolutePath2);
|
|
5095
|
+
deleted.push(toDisplayPath(absolutePath2, cwd));
|
|
5096
|
+
continue;
|
|
5097
|
+
}
|
|
5098
|
+
const absolutePath = resolvePatchPath(operation.path, cwd, allowOutsideCwd);
|
|
5099
|
+
await runAccessHook(request.checkAccess, {
|
|
5100
|
+
cwd,
|
|
5101
|
+
kind: "update",
|
|
5102
|
+
path: absolutePath
|
|
5103
|
+
});
|
|
5104
|
+
const current = await adapter.readTextFile(absolutePath);
|
|
5105
|
+
const next = deriveUpdatedContent(current, operation.chunks, toDisplayPath(absolutePath, cwd));
|
|
5106
|
+
if (operation.movePath) {
|
|
5107
|
+
const destinationPath = resolvePatchPath(operation.movePath, cwd, allowOutsideCwd);
|
|
5108
|
+
await runAccessHook(request.checkAccess, {
|
|
5109
|
+
cwd,
|
|
5110
|
+
kind: "move",
|
|
5111
|
+
path: destinationPath,
|
|
5112
|
+
fromPath: absolutePath,
|
|
5113
|
+
toPath: destinationPath
|
|
5114
|
+
});
|
|
5115
|
+
await adapter.ensureDir(import_node_path4.default.dirname(destinationPath));
|
|
5116
|
+
await adapter.writeTextFile(destinationPath, next);
|
|
5117
|
+
await adapter.deleteFile(absolutePath);
|
|
5118
|
+
modified.push(toDisplayPath(destinationPath, cwd));
|
|
5119
|
+
continue;
|
|
5120
|
+
}
|
|
5121
|
+
await adapter.writeTextFile(absolutePath, next);
|
|
5122
|
+
modified.push(toDisplayPath(absolutePath, cwd));
|
|
5123
|
+
}
|
|
5124
|
+
return {
|
|
5125
|
+
success: true,
|
|
5126
|
+
summary: formatSummary(added, modified, deleted),
|
|
5127
|
+
added,
|
|
5128
|
+
modified,
|
|
5129
|
+
deleted
|
|
5130
|
+
};
|
|
5131
|
+
}
|
|
5132
|
+
async function runAccessHook(hook, context) {
|
|
5133
|
+
if (!hook) {
|
|
5134
|
+
return;
|
|
5135
|
+
}
|
|
5136
|
+
await hook(context);
|
|
5137
|
+
}
|
|
5138
|
+
function normalizePatchText(raw) {
|
|
5139
|
+
return raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
5140
|
+
}
|
|
5141
|
+
function resolvePatchPath(rawPath, cwd, allowOutsideCwd) {
|
|
5142
|
+
const trimmed = rawPath.trim();
|
|
5143
|
+
if (trimmed.length === 0) {
|
|
5144
|
+
throw new Error("apply_patch failed: empty file path");
|
|
5145
|
+
}
|
|
5146
|
+
const absolutePath = import_node_path4.default.isAbsolute(trimmed) ? import_node_path4.default.resolve(trimmed) : import_node_path4.default.resolve(cwd, trimmed);
|
|
5147
|
+
if (!allowOutsideCwd && !isPathInsideCwd(absolutePath, cwd)) {
|
|
5148
|
+
throw new Error(`apply_patch failed: path "${trimmed}" resolves outside cwd "${cwd}"`);
|
|
5149
|
+
}
|
|
5150
|
+
return absolutePath;
|
|
5151
|
+
}
|
|
5152
|
+
function isPathInsideCwd(candidatePath, cwd) {
|
|
5153
|
+
const relative = import_node_path4.default.relative(cwd, candidatePath);
|
|
5154
|
+
return relative === "" || !relative.startsWith("..") && !import_node_path4.default.isAbsolute(relative);
|
|
5155
|
+
}
|
|
5156
|
+
function toDisplayPath(absolutePath, cwd) {
|
|
5157
|
+
const relative = import_node_path4.default.relative(cwd, absolutePath);
|
|
5158
|
+
if (relative === "") {
|
|
5159
|
+
return ".";
|
|
5160
|
+
}
|
|
5161
|
+
if (!relative.startsWith("..") && !import_node_path4.default.isAbsolute(relative)) {
|
|
5162
|
+
return relative;
|
|
5163
|
+
}
|
|
5164
|
+
return absolutePath;
|
|
5165
|
+
}
|
|
5166
|
+
function parsePatchDocument(patch) {
|
|
5167
|
+
const lines = patch.split("\n");
|
|
5168
|
+
if (lines.at(-1) === "") {
|
|
5169
|
+
lines.pop();
|
|
5170
|
+
}
|
|
5171
|
+
if (lines.length < 2) {
|
|
5172
|
+
throw new Error("apply_patch failed: patch must contain Begin/End markers");
|
|
5173
|
+
}
|
|
5174
|
+
if (lines[0] !== BEGIN_PATCH_LINE) {
|
|
5175
|
+
throw new Error(`apply_patch failed: missing "${BEGIN_PATCH_LINE}" header`);
|
|
5176
|
+
}
|
|
5177
|
+
if (lines[lines.length - 1] !== END_PATCH_LINE) {
|
|
5178
|
+
throw new Error(`apply_patch failed: missing "${END_PATCH_LINE}" footer`);
|
|
5179
|
+
}
|
|
5180
|
+
const body = lines.slice(1, -1);
|
|
5181
|
+
if (body.length === 0) {
|
|
5182
|
+
throw new Error("apply_patch failed: patch body is empty");
|
|
5183
|
+
}
|
|
5184
|
+
const operations = [];
|
|
5185
|
+
let index = 0;
|
|
5186
|
+
while (index < body.length) {
|
|
5187
|
+
const line = body[index];
|
|
5188
|
+
if (!line) {
|
|
5189
|
+
throw new Error("apply_patch failed: unexpected empty line between file sections");
|
|
5190
|
+
}
|
|
5191
|
+
if (line.startsWith(ADD_FILE_PREFIX)) {
|
|
5192
|
+
const filePath = extractPatchPath(line, ADD_FILE_PREFIX);
|
|
5193
|
+
index += 1;
|
|
5194
|
+
const contentLines = [];
|
|
5195
|
+
while (index < body.length) {
|
|
5196
|
+
const contentLine = body[index];
|
|
5197
|
+
if (contentLine === void 0 || isPatchSectionHeader(contentLine)) {
|
|
5198
|
+
break;
|
|
5199
|
+
}
|
|
5200
|
+
if (!contentLine.startsWith("+")) {
|
|
5201
|
+
throw new Error(`apply_patch failed: invalid add-file line "${contentLine}"`);
|
|
5202
|
+
}
|
|
5203
|
+
contentLines.push(contentLine.slice(1));
|
|
5204
|
+
index += 1;
|
|
5205
|
+
}
|
|
5206
|
+
if (contentLines.length === 0) {
|
|
5207
|
+
throw new Error(`apply_patch failed: add-file section for "${filePath}" is empty`);
|
|
5208
|
+
}
|
|
5209
|
+
operations.push({
|
|
5210
|
+
type: "add",
|
|
5211
|
+
path: filePath,
|
|
5212
|
+
content: `${contentLines.join("\n")}
|
|
5213
|
+
`
|
|
5214
|
+
});
|
|
5215
|
+
continue;
|
|
5216
|
+
}
|
|
5217
|
+
if (line.startsWith(DELETE_FILE_PREFIX)) {
|
|
5218
|
+
operations.push({
|
|
5219
|
+
type: "delete",
|
|
5220
|
+
path: extractPatchPath(line, DELETE_FILE_PREFIX)
|
|
5221
|
+
});
|
|
5222
|
+
index += 1;
|
|
5223
|
+
continue;
|
|
5224
|
+
}
|
|
5225
|
+
if (line.startsWith(UPDATE_FILE_PREFIX)) {
|
|
5226
|
+
const filePath = extractPatchPath(line, UPDATE_FILE_PREFIX);
|
|
5227
|
+
index += 1;
|
|
5228
|
+
let movePath;
|
|
5229
|
+
const moveHeader = body[index];
|
|
5230
|
+
if (moveHeader?.startsWith(MOVE_TO_PREFIX)) {
|
|
5231
|
+
movePath = extractPatchPath(moveHeader, MOVE_TO_PREFIX);
|
|
5232
|
+
index += 1;
|
|
5233
|
+
}
|
|
5234
|
+
const chunks = [];
|
|
5235
|
+
while (index < body.length) {
|
|
5236
|
+
const hunkHeader = body[index];
|
|
5237
|
+
if (hunkHeader === void 0 || isPatchSectionHeader(hunkHeader)) {
|
|
5238
|
+
break;
|
|
5239
|
+
}
|
|
5240
|
+
if (!(hunkHeader === "@@" || hunkHeader.startsWith("@@ "))) {
|
|
5241
|
+
throw new Error(
|
|
5242
|
+
`apply_patch failed: expected hunk marker in "${filePath}", got "${hunkHeader}"`
|
|
5243
|
+
);
|
|
5244
|
+
}
|
|
5245
|
+
const contextSelector = hunkHeader.length > 2 ? hunkHeader.slice(3) : void 0;
|
|
5246
|
+
index += 1;
|
|
5247
|
+
const oldLines = [];
|
|
5248
|
+
const newLines = [];
|
|
5249
|
+
let sawBodyLine = false;
|
|
5250
|
+
let sawChangeLine = false;
|
|
5251
|
+
let isEndOfFile = false;
|
|
5252
|
+
while (index < body.length) {
|
|
5253
|
+
const chunkLine = body[index];
|
|
5254
|
+
if (chunkLine === void 0) {
|
|
5255
|
+
break;
|
|
5256
|
+
}
|
|
5257
|
+
if (chunkLine === "@@" || chunkLine.startsWith("@@ ") || isPatchSectionHeader(chunkLine)) {
|
|
5258
|
+
break;
|
|
5259
|
+
}
|
|
5260
|
+
if (chunkLine === END_OF_FILE_LINE) {
|
|
5261
|
+
isEndOfFile = true;
|
|
5262
|
+
index += 1;
|
|
5263
|
+
break;
|
|
5264
|
+
}
|
|
5265
|
+
if (chunkLine.length === 0) {
|
|
5266
|
+
throw new Error(`apply_patch failed: invalid empty hunk line in "${filePath}"`);
|
|
5267
|
+
}
|
|
5268
|
+
const prefix = chunkLine[0];
|
|
5269
|
+
const content = chunkLine.slice(1);
|
|
5270
|
+
if (prefix === " ") {
|
|
5271
|
+
oldLines.push(content);
|
|
5272
|
+
newLines.push(content);
|
|
5273
|
+
} else if (prefix === "-") {
|
|
5274
|
+
oldLines.push(content);
|
|
5275
|
+
sawChangeLine = true;
|
|
5276
|
+
} else if (prefix === "+") {
|
|
5277
|
+
newLines.push(content);
|
|
5278
|
+
sawChangeLine = true;
|
|
5279
|
+
} else {
|
|
5280
|
+
throw new Error(
|
|
5281
|
+
`apply_patch failed: unsupported hunk prefix "${prefix}" in "${chunkLine}"`
|
|
5282
|
+
);
|
|
5283
|
+
}
|
|
5284
|
+
sawBodyLine = true;
|
|
5285
|
+
index += 1;
|
|
5286
|
+
}
|
|
5287
|
+
if (!sawBodyLine) {
|
|
5288
|
+
throw new Error(`apply_patch failed: empty hunk body in "${filePath}"`);
|
|
5289
|
+
}
|
|
5290
|
+
if (!sawChangeLine) {
|
|
5291
|
+
throw new Error(
|
|
5292
|
+
`apply_patch failed: hunk in "${filePath}" must include '+' or '-' lines`
|
|
5293
|
+
);
|
|
5294
|
+
}
|
|
5295
|
+
chunks.push({
|
|
5296
|
+
contextSelector,
|
|
5297
|
+
oldLines,
|
|
5298
|
+
newLines,
|
|
5299
|
+
isEndOfFile
|
|
5300
|
+
});
|
|
5301
|
+
}
|
|
5302
|
+
if (chunks.length === 0) {
|
|
5303
|
+
throw new Error(`apply_patch failed: update section for "${filePath}" has no hunks`);
|
|
5304
|
+
}
|
|
5305
|
+
operations.push({
|
|
5306
|
+
type: "update",
|
|
5307
|
+
path: filePath,
|
|
5308
|
+
movePath,
|
|
5309
|
+
chunks
|
|
5310
|
+
});
|
|
5311
|
+
continue;
|
|
5312
|
+
}
|
|
5313
|
+
throw new Error(`apply_patch failed: unrecognized section header "${line}"`);
|
|
5314
|
+
}
|
|
5315
|
+
return { operations };
|
|
5316
|
+
}
|
|
5317
|
+
function extractPatchPath(line, prefix) {
|
|
5318
|
+
const value = line.slice(prefix.length).trim();
|
|
5319
|
+
if (value.length === 0) {
|
|
5320
|
+
throw new Error(`apply_patch failed: missing file path in "${line}"`);
|
|
5321
|
+
}
|
|
5322
|
+
return value;
|
|
5323
|
+
}
|
|
5324
|
+
function isPatchSectionHeader(line) {
|
|
5325
|
+
return line.startsWith(ADD_FILE_PREFIX) || line.startsWith(DELETE_FILE_PREFIX) || line.startsWith(UPDATE_FILE_PREFIX);
|
|
5326
|
+
}
|
|
5327
|
+
function deriveUpdatedContent(originalContent, chunks, displayPath) {
|
|
5328
|
+
const originalLines = splitFileContentIntoLines(originalContent);
|
|
5329
|
+
const replacements = [];
|
|
5330
|
+
let lineIndex = 0;
|
|
5331
|
+
for (const chunk of chunks) {
|
|
5332
|
+
if (chunk.contextSelector !== void 0) {
|
|
5333
|
+
const contextIndex = seekSequence(originalLines, [chunk.contextSelector], lineIndex, false);
|
|
5334
|
+
if (contextIndex === null) {
|
|
5335
|
+
throw new Error(
|
|
5336
|
+
`apply_patch failed: unable to locate context "${chunk.contextSelector}" in ${displayPath}`
|
|
5337
|
+
);
|
|
5338
|
+
}
|
|
5339
|
+
lineIndex = contextIndex + 1;
|
|
5340
|
+
}
|
|
5341
|
+
if (chunk.oldLines.length === 0) {
|
|
5342
|
+
replacements.push({
|
|
5343
|
+
startIndex: originalLines.length,
|
|
5344
|
+
oldLength: 0,
|
|
5345
|
+
newLines: [...chunk.newLines]
|
|
5346
|
+
});
|
|
5347
|
+
continue;
|
|
5348
|
+
}
|
|
5349
|
+
let oldLines = [...chunk.oldLines];
|
|
5350
|
+
let newLines = [...chunk.newLines];
|
|
5351
|
+
let startIndex = seekSequence(originalLines, oldLines, lineIndex, chunk.isEndOfFile);
|
|
5352
|
+
if (startIndex === null && oldLines.at(-1) === "") {
|
|
5353
|
+
oldLines = oldLines.slice(0, -1);
|
|
5354
|
+
if (newLines.at(-1) === "") {
|
|
5355
|
+
newLines = newLines.slice(0, -1);
|
|
5356
|
+
}
|
|
5357
|
+
startIndex = seekSequence(originalLines, oldLines, lineIndex, chunk.isEndOfFile);
|
|
5358
|
+
}
|
|
5359
|
+
if (startIndex === null) {
|
|
5360
|
+
throw new Error(
|
|
5361
|
+
`apply_patch failed: failed to match hunk in ${displayPath}:
|
|
5362
|
+
${chunk.oldLines.join("\n")}`
|
|
5363
|
+
);
|
|
5364
|
+
}
|
|
5365
|
+
replacements.push({
|
|
5366
|
+
startIndex,
|
|
5367
|
+
oldLength: oldLines.length,
|
|
5368
|
+
newLines
|
|
5369
|
+
});
|
|
5370
|
+
lineIndex = startIndex + oldLines.length;
|
|
5371
|
+
}
|
|
5372
|
+
replacements.sort((left, right) => left.startIndex - right.startIndex);
|
|
5373
|
+
const nextLines = applyReplacements(originalLines, replacements);
|
|
5374
|
+
if (nextLines.length > 0 && nextLines[nextLines.length - 1] !== "") {
|
|
5375
|
+
nextLines.push("");
|
|
5376
|
+
}
|
|
5377
|
+
return nextLines.join("\n");
|
|
5378
|
+
}
|
|
5379
|
+
function splitFileContentIntoLines(content) {
|
|
5380
|
+
const lines = content.split("\n");
|
|
5381
|
+
if (lines.at(-1) === "") {
|
|
5382
|
+
lines.pop();
|
|
5383
|
+
}
|
|
5384
|
+
return lines;
|
|
5385
|
+
}
|
|
5386
|
+
function seekSequence(sourceLines, targetLines, startIndex, isEndOfFile) {
|
|
5387
|
+
if (targetLines.length === 0) {
|
|
5388
|
+
return Math.min(Math.max(startIndex, 0), sourceLines.length);
|
|
5389
|
+
}
|
|
5390
|
+
const from = Math.max(startIndex, 0);
|
|
5391
|
+
const maxStart = sourceLines.length - targetLines.length;
|
|
5392
|
+
if (maxStart < from) {
|
|
5393
|
+
return null;
|
|
5394
|
+
}
|
|
5395
|
+
const matchesAt = (candidateIndex) => {
|
|
5396
|
+
for (let offset = 0; offset < targetLines.length; offset += 1) {
|
|
5397
|
+
if (sourceLines[candidateIndex + offset] !== targetLines[offset]) {
|
|
5398
|
+
return false;
|
|
5399
|
+
}
|
|
5400
|
+
}
|
|
5401
|
+
return true;
|
|
5402
|
+
};
|
|
5403
|
+
if (isEndOfFile) {
|
|
5404
|
+
return matchesAt(maxStart) ? maxStart : null;
|
|
5405
|
+
}
|
|
5406
|
+
for (let candidate = from; candidate <= maxStart; candidate += 1) {
|
|
5407
|
+
if (matchesAt(candidate)) {
|
|
5408
|
+
return candidate;
|
|
5409
|
+
}
|
|
5410
|
+
}
|
|
5411
|
+
return null;
|
|
5412
|
+
}
|
|
5413
|
+
function applyReplacements(lines, replacements) {
|
|
5414
|
+
const result = [...lines];
|
|
5415
|
+
for (let index = replacements.length - 1; index >= 0; index -= 1) {
|
|
5416
|
+
const replacement = replacements[index];
|
|
5417
|
+
if (replacement === void 0) {
|
|
5418
|
+
continue;
|
|
5419
|
+
}
|
|
5420
|
+
result.splice(replacement.startIndex, replacement.oldLength, ...replacement.newLines);
|
|
5421
|
+
}
|
|
5422
|
+
return result;
|
|
5423
|
+
}
|
|
5424
|
+
function formatSummary(added, modified, deleted) {
|
|
5425
|
+
const lines = ["Success. Updated the following files:"];
|
|
5426
|
+
for (const filePath of added) {
|
|
5427
|
+
lines.push(`A ${filePath}`);
|
|
5428
|
+
}
|
|
5429
|
+
for (const filePath of modified) {
|
|
5430
|
+
lines.push(`M ${filePath}`);
|
|
5431
|
+
}
|
|
5432
|
+
for (const filePath of deleted) {
|
|
5433
|
+
lines.push(`D ${filePath}`);
|
|
5434
|
+
}
|
|
5435
|
+
return `${lines.join("\n")}
|
|
5436
|
+
`;
|
|
5437
|
+
}
|
|
5438
|
+
|
|
5439
|
+
// src/tools/filesystemTools.ts
|
|
5440
|
+
var DEFAULT_READ_FILE_LINE_LIMIT = 2e3;
|
|
5441
|
+
var DEFAULT_READ_FILES_LINE_LIMIT = 200;
|
|
5442
|
+
var DEFAULT_READ_FILES_CHAR_LIMIT = 4e3;
|
|
5443
|
+
var DEFAULT_LIST_DIR_LIMIT = 25;
|
|
5444
|
+
var DEFAULT_LIST_DIR_DEPTH = 2;
|
|
5445
|
+
var DEFAULT_GREP_LIMIT = 100;
|
|
5446
|
+
var MAX_GREP_LIMIT = 2e3;
|
|
5447
|
+
var DEFAULT_MAX_LINE_LENGTH = 500;
|
|
5448
|
+
var DEFAULT_GREP_MAX_SCANNED_FILES = 2e4;
|
|
5449
|
+
var DEFAULT_TAB_WIDTH = 4;
|
|
5450
|
+
var codexReadFileInputSchema = import_zod5.z.object({
|
|
5451
|
+
file_path: import_zod5.z.string().min(1).describe("Absolute path to the file"),
|
|
5452
|
+
offset: import_zod5.z.number().int().min(1).optional().describe("The line number to start reading from. Must be 1 or greater."),
|
|
5453
|
+
limit: import_zod5.z.number().int().min(1).optional().describe("The maximum number of lines to return."),
|
|
5454
|
+
mode: import_zod5.z.enum(["slice", "indentation"]).optional().describe('Optional mode selector: "slice" (default) or "indentation".'),
|
|
5455
|
+
indentation: import_zod5.z.object({
|
|
5456
|
+
anchor_line: import_zod5.z.number().int().min(1).optional(),
|
|
5457
|
+
max_levels: import_zod5.z.number().int().min(0).optional(),
|
|
5458
|
+
include_siblings: import_zod5.z.boolean().optional(),
|
|
5459
|
+
include_header: import_zod5.z.boolean().optional(),
|
|
5460
|
+
max_lines: import_zod5.z.number().int().min(1).optional()
|
|
5461
|
+
}).optional()
|
|
5462
|
+
});
|
|
5463
|
+
var codexListDirInputSchema = import_zod5.z.object({
|
|
5464
|
+
dir_path: import_zod5.z.string().min(1).describe("Absolute path to the directory to list."),
|
|
5465
|
+
offset: import_zod5.z.number().int().min(1).optional().describe("The entry number to start listing from. Must be 1 or greater."),
|
|
5466
|
+
limit: import_zod5.z.number().int().min(1).optional().describe("The maximum number of entries to return."),
|
|
5467
|
+
depth: import_zod5.z.number().int().min(1).optional().describe("The maximum directory depth to traverse. Must be 1 or greater.")
|
|
5468
|
+
});
|
|
5469
|
+
var codexGrepFilesInputSchema = import_zod5.z.object({
|
|
5470
|
+
pattern: import_zod5.z.string().min(1).describe("Regular expression pattern to search for."),
|
|
5471
|
+
include: import_zod5.z.string().optional().describe('Optional glob limiting searched files (for example "*.rs").'),
|
|
5472
|
+
path: import_zod5.z.string().optional().describe("Directory or file path to search. Defaults to cwd."),
|
|
5473
|
+
limit: import_zod5.z.number().int().min(1).optional().describe("Maximum number of file paths to return (defaults to 100).")
|
|
5474
|
+
});
|
|
5475
|
+
var applyPatchInputSchema = import_zod5.z.object({
|
|
5476
|
+
input: import_zod5.z.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
|
|
5477
|
+
});
|
|
5478
|
+
var geminiReadFileInputSchema = import_zod5.z.object({
|
|
5479
|
+
file_path: import_zod5.z.string().min(1),
|
|
5480
|
+
offset: import_zod5.z.number().int().min(0).nullish(),
|
|
5481
|
+
limit: import_zod5.z.number().int().min(1).nullish()
|
|
5482
|
+
});
|
|
5483
|
+
var geminiReadFilesInputSchema = import_zod5.z.object({
|
|
5484
|
+
paths: import_zod5.z.array(import_zod5.z.string().min(1)).min(1),
|
|
5485
|
+
line_offset: import_zod5.z.number().int().min(0).nullish(),
|
|
5486
|
+
line_limit: import_zod5.z.number().int().min(1).nullish(),
|
|
5487
|
+
char_offset: import_zod5.z.number().int().min(0).nullish(),
|
|
5488
|
+
char_limit: import_zod5.z.number().int().min(1).nullish(),
|
|
5489
|
+
include_line_numbers: import_zod5.z.boolean().nullish()
|
|
5490
|
+
}).superRefine((value, context) => {
|
|
5491
|
+
const hasLineWindow = value.line_offset !== void 0 || value.line_limit !== void 0;
|
|
5492
|
+
const hasCharWindow = value.char_offset !== void 0 || value.char_limit !== void 0;
|
|
5493
|
+
if (hasLineWindow && hasCharWindow) {
|
|
5494
|
+
context.addIssue({
|
|
5495
|
+
code: import_zod5.z.ZodIssueCode.custom,
|
|
5496
|
+
message: "Use either line_* or char_* window arguments, not both."
|
|
5497
|
+
});
|
|
5498
|
+
}
|
|
5499
|
+
});
|
|
5500
|
+
var geminiWriteFileInputSchema = import_zod5.z.object({
|
|
5501
|
+
file_path: import_zod5.z.string().min(1),
|
|
5502
|
+
content: import_zod5.z.string()
|
|
5503
|
+
});
|
|
5504
|
+
var geminiReplaceInputSchema = import_zod5.z.object({
|
|
5505
|
+
file_path: import_zod5.z.string().min(1),
|
|
5506
|
+
instruction: import_zod5.z.string().min(1),
|
|
5507
|
+
old_string: import_zod5.z.string(),
|
|
5508
|
+
new_string: import_zod5.z.string(),
|
|
5509
|
+
expected_replacements: import_zod5.z.number().int().min(1).nullish()
|
|
5510
|
+
});
|
|
5511
|
+
var geminiListDirectoryInputSchema = import_zod5.z.object({
|
|
5512
|
+
dir_path: import_zod5.z.string().min(1),
|
|
5513
|
+
ignore: import_zod5.z.array(import_zod5.z.string()).nullish(),
|
|
5514
|
+
file_filtering_options: import_zod5.z.object({
|
|
5515
|
+
respect_git_ignore: import_zod5.z.boolean().nullish(),
|
|
5516
|
+
respect_gemini_ignore: import_zod5.z.boolean().nullish()
|
|
5517
|
+
}).nullish()
|
|
5518
|
+
});
|
|
5519
|
+
var geminiRgSearchInputSchema = import_zod5.z.object({
|
|
5520
|
+
pattern: import_zod5.z.string().min(1),
|
|
5521
|
+
path: import_zod5.z.string().nullish(),
|
|
5522
|
+
glob: import_zod5.z.string().nullish(),
|
|
5523
|
+
case_sensitive: import_zod5.z.boolean().nullish(),
|
|
5524
|
+
exclude_pattern: import_zod5.z.string().nullish(),
|
|
5525
|
+
names_only: import_zod5.z.boolean().nullish(),
|
|
5526
|
+
max_matches_per_file: import_zod5.z.number().int().min(1).nullish(),
|
|
5527
|
+
max_results: import_zod5.z.number().int().min(1).nullish()
|
|
5528
|
+
});
|
|
5529
|
+
var geminiGrepSearchInputSchema = import_zod5.z.object({
|
|
5530
|
+
pattern: import_zod5.z.string().min(1),
|
|
5531
|
+
dir_path: import_zod5.z.string().nullish(),
|
|
5532
|
+
include: import_zod5.z.string().nullish(),
|
|
5533
|
+
exclude_pattern: import_zod5.z.string().nullish(),
|
|
5534
|
+
names_only: import_zod5.z.boolean().nullish(),
|
|
5535
|
+
max_matches_per_file: import_zod5.z.number().int().min(1).nullish(),
|
|
5536
|
+
total_max_matches: import_zod5.z.number().int().min(1).nullish()
|
|
5537
|
+
});
|
|
5538
|
+
var geminiGlobInputSchema = import_zod5.z.object({
|
|
5539
|
+
pattern: import_zod5.z.string().min(1),
|
|
5540
|
+
dir_path: import_zod5.z.string().nullish(),
|
|
5541
|
+
case_sensitive: import_zod5.z.boolean().nullish(),
|
|
5542
|
+
respect_git_ignore: import_zod5.z.boolean().nullish(),
|
|
5543
|
+
respect_gemini_ignore: import_zod5.z.boolean().nullish()
|
|
5544
|
+
});
|
|
5545
|
+
function resolveFilesystemToolProfile(model, profile = "auto") {
|
|
5546
|
+
if (profile !== "auto") {
|
|
5547
|
+
return profile;
|
|
5548
|
+
}
|
|
5549
|
+
if (isCodexModel(model)) {
|
|
5550
|
+
return "codex";
|
|
5551
|
+
}
|
|
5552
|
+
if (isGeminiModel(model)) {
|
|
5553
|
+
return "gemini";
|
|
5554
|
+
}
|
|
5555
|
+
return "model-agnostic";
|
|
5556
|
+
}
|
|
5557
|
+
function createFilesystemToolSetForModel(model, profileOrOptions = "auto", maybeOptions) {
|
|
5558
|
+
if (typeof profileOrOptions === "string") {
|
|
5559
|
+
const resolvedProfile2 = resolveFilesystemToolProfile(model, profileOrOptions);
|
|
5560
|
+
if (resolvedProfile2 === "codex") {
|
|
5561
|
+
return createCodexFilesystemToolSet(maybeOptions);
|
|
5562
|
+
}
|
|
5563
|
+
if (resolvedProfile2 === "gemini") {
|
|
5564
|
+
return createGeminiFilesystemToolSet(maybeOptions);
|
|
5565
|
+
}
|
|
5566
|
+
return createModelAgnosticFilesystemToolSet(maybeOptions);
|
|
5567
|
+
}
|
|
5568
|
+
const resolvedProfile = resolveFilesystemToolProfile(model, "auto");
|
|
5569
|
+
if (resolvedProfile === "codex") {
|
|
5570
|
+
return createCodexFilesystemToolSet(profileOrOptions);
|
|
5571
|
+
}
|
|
5572
|
+
if (resolvedProfile === "gemini") {
|
|
5573
|
+
return createGeminiFilesystemToolSet(profileOrOptions);
|
|
5574
|
+
}
|
|
5575
|
+
return createModelAgnosticFilesystemToolSet(profileOrOptions);
|
|
5576
|
+
}
|
|
5577
|
+
function createCodexFilesystemToolSet(options = {}) {
|
|
5578
|
+
return {
|
|
5579
|
+
apply_patch: createCodexApplyPatchTool(options),
|
|
5580
|
+
read_file: createCodexReadFileTool(options),
|
|
5581
|
+
list_dir: createListDirTool(options),
|
|
5582
|
+
grep_files: createGrepFilesTool(options)
|
|
5583
|
+
};
|
|
5584
|
+
}
|
|
5585
|
+
function createGeminiFilesystemToolSet(options = {}) {
|
|
5586
|
+
return {
|
|
5587
|
+
read_file: createGeminiReadFileTool(options),
|
|
5588
|
+
write_file: createWriteFileTool(options),
|
|
5589
|
+
replace: createReplaceTool(options),
|
|
5590
|
+
list_directory: createListDirectoryTool(options),
|
|
5591
|
+
grep_search: createGrepSearchTool(options),
|
|
5592
|
+
glob: createGlobTool(options)
|
|
5593
|
+
};
|
|
5594
|
+
}
|
|
5595
|
+
function createModelAgnosticFilesystemToolSet(options = {}) {
|
|
5596
|
+
return createGeminiFilesystemToolSet(options);
|
|
5597
|
+
}
|
|
5598
|
+
function createCodexApplyPatchTool(options = {}) {
|
|
5599
|
+
return customTool({
|
|
5600
|
+
description: CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION,
|
|
5601
|
+
format: {
|
|
5602
|
+
type: "grammar",
|
|
5603
|
+
syntax: "lark",
|
|
5604
|
+
definition: CODEX_APPLY_PATCH_LARK_GRAMMAR
|
|
5605
|
+
},
|
|
5606
|
+
execute: async (input) => {
|
|
5607
|
+
const runtime = resolveRuntime(options);
|
|
5608
|
+
const result = await applyPatch({
|
|
5609
|
+
patch: input,
|
|
5610
|
+
cwd: runtime.cwd,
|
|
5611
|
+
fs: runtime.filesystem,
|
|
5612
|
+
allowOutsideCwd: runtime.allowOutsideCwd,
|
|
5613
|
+
checkAccess: runtime.checkAccess ? async (context) => {
|
|
5614
|
+
await runtime.checkAccess?.({
|
|
5615
|
+
cwd: runtime.cwd,
|
|
5616
|
+
tool: "apply_patch",
|
|
5617
|
+
action: mapApplyPatchAction(context.kind),
|
|
5618
|
+
path: context.path,
|
|
5619
|
+
fromPath: context.fromPath,
|
|
5620
|
+
toPath: context.toPath
|
|
5621
|
+
});
|
|
5622
|
+
} : void 0,
|
|
5623
|
+
maxPatchBytes: options.applyPatch?.maxPatchBytes
|
|
5624
|
+
});
|
|
5625
|
+
return result.summary;
|
|
5626
|
+
}
|
|
5627
|
+
});
|
|
5628
|
+
}
|
|
5629
|
+
function createCodexReadFileTool(options = {}) {
|
|
5630
|
+
return tool({
|
|
5631
|
+
description: "Reads a local file with 1-indexed line numbers, supporting slice and indentation-aware block modes.",
|
|
5632
|
+
inputSchema: codexReadFileInputSchema,
|
|
5633
|
+
execute: async (input) => readFileCodex(input, options)
|
|
5634
|
+
});
|
|
5635
|
+
}
|
|
5636
|
+
function createListDirTool(options = {}) {
|
|
5637
|
+
return tool({
|
|
5638
|
+
description: "Lists entries in a local directory with 1-indexed entry numbers and simple type labels.",
|
|
5639
|
+
inputSchema: codexListDirInputSchema,
|
|
5640
|
+
execute: async (input) => listDirectoryCodex(input, options)
|
|
5641
|
+
});
|
|
5642
|
+
}
|
|
5643
|
+
function createGrepFilesTool(options = {}) {
|
|
5644
|
+
return tool({
|
|
5645
|
+
description: "Finds files whose contents match the pattern and lists them by modification time.",
|
|
5646
|
+
inputSchema: codexGrepFilesInputSchema,
|
|
5647
|
+
execute: async (input) => grepFilesCodex(input, options)
|
|
5648
|
+
});
|
|
5649
|
+
}
|
|
5650
|
+
function createGeminiReadFileTool(options = {}) {
|
|
5651
|
+
return tool({
|
|
5652
|
+
description: "Reads and returns the content of a specified file. Supports optional 0-based line offset and line limit.",
|
|
5653
|
+
inputSchema: geminiReadFileInputSchema,
|
|
5654
|
+
execute: async (input) => readFileGemini(input, options)
|
|
5655
|
+
});
|
|
5656
|
+
}
|
|
5657
|
+
function createReadFilesTool(options = {}) {
|
|
5658
|
+
return tool({
|
|
5659
|
+
description: "Reads one or more files with optional line-based or character-based slicing, similar to a controlled head/tail view.",
|
|
5660
|
+
inputSchema: geminiReadFilesInputSchema,
|
|
5661
|
+
execute: async (input) => readFilesGemini(input, options)
|
|
5662
|
+
});
|
|
5663
|
+
}
|
|
5664
|
+
function createWriteFileTool(options = {}) {
|
|
5665
|
+
return tool({
|
|
5666
|
+
description: "Writes content to a specified file in the local filesystem.",
|
|
5667
|
+
inputSchema: geminiWriteFileInputSchema,
|
|
5668
|
+
execute: async (input) => writeFileGemini(input, options)
|
|
5669
|
+
});
|
|
5670
|
+
}
|
|
5671
|
+
function createReplaceTool(options = {}) {
|
|
5672
|
+
return tool({
|
|
5673
|
+
description: "Replaces exact literal text within a file.",
|
|
5674
|
+
inputSchema: geminiReplaceInputSchema,
|
|
5675
|
+
execute: async (input) => replaceFileContentGemini(input, options)
|
|
5676
|
+
});
|
|
5677
|
+
}
|
|
5678
|
+
function createListDirectoryTool(options = {}) {
|
|
5679
|
+
return tool({
|
|
5680
|
+
description: "Lists files and subdirectories directly within a specified directory path.",
|
|
5681
|
+
inputSchema: geminiListDirectoryInputSchema,
|
|
5682
|
+
execute: async (input) => listDirectoryGemini(input, options)
|
|
5683
|
+
});
|
|
5684
|
+
}
|
|
5685
|
+
function createGrepSearchTool(options = {}) {
|
|
5686
|
+
return tool({
|
|
5687
|
+
description: "Searches for a regex pattern within file contents.",
|
|
5688
|
+
inputSchema: geminiGrepSearchInputSchema,
|
|
5689
|
+
execute: async (input) => grepSearchGemini(input, options)
|
|
5690
|
+
});
|
|
5691
|
+
}
|
|
5692
|
+
function createRgSearchTool(options = {}) {
|
|
5693
|
+
return tool({
|
|
5694
|
+
description: "Searches for a regex pattern within file contents.",
|
|
5695
|
+
inputSchema: geminiRgSearchInputSchema,
|
|
5696
|
+
execute: async (input) => rgSearchGemini(input, options)
|
|
5697
|
+
});
|
|
5698
|
+
}
|
|
5699
|
+
function createGlobTool(options = {}) {
|
|
5700
|
+
return tool({
|
|
5701
|
+
description: "Finds files matching glob patterns, sorted by modification time (newest first).",
|
|
5702
|
+
inputSchema: geminiGlobInputSchema,
|
|
5703
|
+
execute: async (input) => globFilesGemini(input, options)
|
|
5704
|
+
});
|
|
5705
|
+
}
|
|
5706
|
+
async function readFileCodex(input, options) {
|
|
5707
|
+
const runtime = resolveRuntime(options);
|
|
5708
|
+
if (!import_node_path5.default.isAbsolute(input.file_path)) {
|
|
5709
|
+
throw new Error("file_path must be an absolute path");
|
|
5710
|
+
}
|
|
5711
|
+
const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
5712
|
+
await runAccessHook2(runtime, {
|
|
5713
|
+
cwd: runtime.cwd,
|
|
5714
|
+
tool: "read_file",
|
|
5715
|
+
action: "read",
|
|
5716
|
+
path: filePath
|
|
5717
|
+
});
|
|
5718
|
+
const content = await runtime.filesystem.readTextFile(filePath);
|
|
5719
|
+
const lines = splitLines(content);
|
|
5720
|
+
const offset = input.offset ?? 1;
|
|
5721
|
+
const limit = input.limit ?? DEFAULT_READ_FILE_LINE_LIMIT;
|
|
5722
|
+
const mode = input.mode ?? "slice";
|
|
5723
|
+
if (offset > lines.length) {
|
|
5724
|
+
throw new Error("offset exceeds file length");
|
|
5725
|
+
}
|
|
5726
|
+
if (mode === "slice") {
|
|
5727
|
+
const output = [];
|
|
5728
|
+
const lastLine = Math.min(lines.length, offset + limit - 1);
|
|
5729
|
+
for (let lineNumber = offset; lineNumber <= lastLine; lineNumber += 1) {
|
|
5730
|
+
const line = lines[lineNumber - 1] ?? "";
|
|
5731
|
+
output.push(`L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`);
|
|
5732
|
+
}
|
|
5733
|
+
return output.join("\n");
|
|
5734
|
+
}
|
|
5735
|
+
const indentation = input.indentation ?? {};
|
|
5736
|
+
const anchorLine = indentation.anchor_line ?? offset;
|
|
5737
|
+
if (anchorLine < 1 || anchorLine > lines.length) {
|
|
5738
|
+
throw new Error("anchor_line exceeds file length");
|
|
5739
|
+
}
|
|
5740
|
+
const records = lines.map((line, index) => ({
|
|
5741
|
+
number: index + 1,
|
|
5742
|
+
raw: line,
|
|
5743
|
+
display: truncateAtCodePointBoundary(line, runtime.maxLineLength),
|
|
5744
|
+
indent: measureIndent(line, DEFAULT_TAB_WIDTH)
|
|
5745
|
+
}));
|
|
5746
|
+
const selected = readWithIndentationMode({
|
|
5747
|
+
records,
|
|
5748
|
+
anchorLine,
|
|
5749
|
+
limit,
|
|
5750
|
+
maxLevels: indentation.max_levels ?? 0,
|
|
5751
|
+
includeSiblings: indentation.include_siblings ?? false,
|
|
5752
|
+
includeHeader: indentation.include_header ?? true,
|
|
5753
|
+
maxLines: indentation.max_lines
|
|
5754
|
+
});
|
|
5755
|
+
return selected.map((record) => `L${record.number}: ${record.display}`).join("\n");
|
|
5756
|
+
}
|
|
5757
|
+
async function listDirectoryCodex(input, options) {
|
|
5758
|
+
const runtime = resolveRuntime(options);
|
|
5759
|
+
if (!import_node_path5.default.isAbsolute(input.dir_path)) {
|
|
5760
|
+
throw new Error("dir_path must be an absolute path");
|
|
5761
|
+
}
|
|
5762
|
+
const dirPath = resolvePathWithPolicy(input.dir_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
5763
|
+
await runAccessHook2(runtime, {
|
|
5764
|
+
cwd: runtime.cwd,
|
|
5765
|
+
tool: "list_dir",
|
|
5766
|
+
action: "list",
|
|
5767
|
+
path: dirPath
|
|
5768
|
+
});
|
|
5769
|
+
const stats = await runtime.filesystem.stat(dirPath);
|
|
5770
|
+
if (stats.kind !== "directory") {
|
|
5771
|
+
throw new Error(`failed to read directory: "${dirPath}" is not a directory`);
|
|
5772
|
+
}
|
|
5773
|
+
const offset = input.offset ?? 1;
|
|
5774
|
+
const limit = input.limit ?? DEFAULT_LIST_DIR_LIMIT;
|
|
5775
|
+
const depth = input.depth ?? DEFAULT_LIST_DIR_DEPTH;
|
|
5776
|
+
const entries = await collectDirectoryEntries(
|
|
5777
|
+
runtime.filesystem,
|
|
5778
|
+
dirPath,
|
|
5779
|
+
depth,
|
|
5780
|
+
runtime.maxLineLength
|
|
5781
|
+
);
|
|
5782
|
+
if (offset > entries.length) {
|
|
5783
|
+
throw new Error("offset exceeds directory entry count");
|
|
5784
|
+
}
|
|
5785
|
+
const startIndex = offset - 1;
|
|
5786
|
+
const remaining = entries.length - startIndex;
|
|
5787
|
+
const cappedLimit = Math.min(limit, remaining);
|
|
5788
|
+
const selected = entries.slice(startIndex, startIndex + cappedLimit);
|
|
5789
|
+
const output = [`Absolute path: ${dirPath}`];
|
|
5790
|
+
for (const entry of selected) {
|
|
5791
|
+
output.push(formatListEntry(entry));
|
|
5792
|
+
}
|
|
5793
|
+
if (startIndex + cappedLimit < entries.length) {
|
|
5794
|
+
output.push(`More than ${cappedLimit} entries found`);
|
|
5795
|
+
}
|
|
5796
|
+
return output.join("\n");
|
|
5797
|
+
}
|
|
5798
|
+
async function grepFilesCodex(input, options) {
|
|
5799
|
+
const runtime = resolveRuntime(options);
|
|
5800
|
+
const pattern = input.pattern.trim();
|
|
5801
|
+
if (pattern.length === 0) {
|
|
5802
|
+
throw new Error("pattern must not be empty");
|
|
5803
|
+
}
|
|
5804
|
+
const regex = compileRegex(pattern);
|
|
5805
|
+
const searchPath = resolvePathWithPolicy(
|
|
5806
|
+
input.path ?? runtime.cwd,
|
|
5807
|
+
runtime.cwd,
|
|
5808
|
+
runtime.allowOutsideCwd
|
|
5809
|
+
);
|
|
5810
|
+
await runAccessHook2(runtime, {
|
|
5811
|
+
cwd: runtime.cwd,
|
|
5812
|
+
tool: "grep_files",
|
|
5813
|
+
action: "search",
|
|
5814
|
+
path: searchPath,
|
|
5815
|
+
pattern,
|
|
5816
|
+
include: input.include?.trim()
|
|
5817
|
+
});
|
|
5818
|
+
const searchPathInfo = await runtime.filesystem.stat(searchPath);
|
|
5819
|
+
const filesToScan = await collectSearchFiles({
|
|
5820
|
+
filesystem: runtime.filesystem,
|
|
5821
|
+
searchPath,
|
|
5822
|
+
rootKind: searchPathInfo.kind,
|
|
5823
|
+
maxScannedFiles: runtime.grepMaxScannedFiles
|
|
5824
|
+
});
|
|
5825
|
+
const includeMatcher = input.include ? createGlobMatcher(input.include) : null;
|
|
5826
|
+
const matches = [];
|
|
5827
|
+
for (const filePath of filesToScan) {
|
|
5828
|
+
const relativePath = toDisplayPath2(filePath, runtime.cwd);
|
|
5829
|
+
if (includeMatcher && !includeMatcher(relativePath)) {
|
|
5830
|
+
continue;
|
|
5831
|
+
}
|
|
5832
|
+
const fileContent = await runtime.filesystem.readTextFile(filePath);
|
|
5833
|
+
if (!regex.test(fileContent)) {
|
|
5834
|
+
continue;
|
|
5835
|
+
}
|
|
5836
|
+
const stats = await runtime.filesystem.stat(filePath);
|
|
5837
|
+
matches.push({ filePath: normalizeSlashes(relativePath), mtimeMs: stats.mtimeMs });
|
|
5838
|
+
}
|
|
5839
|
+
if (matches.length === 0) {
|
|
5840
|
+
return "No matches found.";
|
|
5841
|
+
}
|
|
5842
|
+
matches.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
5843
|
+
const limit = Math.min(input.limit ?? DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
|
|
5844
|
+
return matches.slice(0, limit).map((match) => match.filePath).join("\n");
|
|
5845
|
+
}
|
|
5846
|
+
async function readFileGemini(input, options) {
|
|
5847
|
+
const runtime = resolveRuntime(options);
|
|
5848
|
+
const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
5849
|
+
await runAccessHook2(runtime, {
|
|
5850
|
+
cwd: runtime.cwd,
|
|
5851
|
+
tool: "read_file",
|
|
5852
|
+
action: "read",
|
|
5853
|
+
path: filePath
|
|
5854
|
+
});
|
|
5855
|
+
const content = await runtime.filesystem.readTextFile(filePath);
|
|
5856
|
+
const lines = splitLines(content);
|
|
5857
|
+
const offset = Math.max(0, input.offset ?? 0);
|
|
5858
|
+
const limit = input.limit ?? DEFAULT_READ_FILE_LINE_LIMIT;
|
|
5859
|
+
if (offset >= lines.length) {
|
|
5860
|
+
return "";
|
|
5861
|
+
}
|
|
5862
|
+
const end = Math.min(lines.length, offset + limit);
|
|
5863
|
+
return lines.slice(offset, end).map(
|
|
5864
|
+
(line, index) => `L${offset + index + 1}: ${truncateAtCodePointBoundary(line ?? "", runtime.maxLineLength)}`
|
|
5865
|
+
).join("\n");
|
|
5866
|
+
}
|
|
5867
|
+
async function readFilesGemini(input, options) {
|
|
5868
|
+
const runtime = resolveRuntime(options);
|
|
5869
|
+
const useCharWindow = input.char_offset !== void 0 || input.char_limit !== void 0;
|
|
5870
|
+
const lineOffset = Math.max(0, input.line_offset ?? 0);
|
|
5871
|
+
const lineLimit = input.line_limit ?? DEFAULT_READ_FILES_LINE_LIMIT;
|
|
5872
|
+
const charOffset = Math.max(0, input.char_offset ?? 0);
|
|
5873
|
+
const charLimit = input.char_limit ?? DEFAULT_READ_FILES_CHAR_LIMIT;
|
|
5874
|
+
const includeLineNumbers = input.include_line_numbers !== false;
|
|
5875
|
+
const sections = [];
|
|
5876
|
+
for (const rawPath of input.paths) {
|
|
5877
|
+
const filePath = resolvePathWithPolicy(rawPath, runtime.cwd, runtime.allowOutsideCwd);
|
|
5878
|
+
await runAccessHook2(runtime, {
|
|
5879
|
+
cwd: runtime.cwd,
|
|
5880
|
+
tool: "read_files",
|
|
5881
|
+
action: "read",
|
|
5882
|
+
path: filePath
|
|
5883
|
+
});
|
|
5884
|
+
const content = await runtime.filesystem.readTextFile(filePath);
|
|
5885
|
+
const displayPath = normalizeSlashes(toDisplayPath2(filePath, runtime.cwd));
|
|
5886
|
+
sections.push(`==> ${displayPath} <==`);
|
|
5887
|
+
if (useCharWindow) {
|
|
5888
|
+
if (charOffset >= content.length) {
|
|
5889
|
+
sections.push("");
|
|
5890
|
+
continue;
|
|
5891
|
+
}
|
|
5892
|
+
const end2 = Math.min(content.length, charOffset + charLimit);
|
|
5893
|
+
sections.push(content.slice(charOffset, end2));
|
|
5894
|
+
continue;
|
|
5895
|
+
}
|
|
5896
|
+
const lines = splitLines(content);
|
|
5897
|
+
if (lineOffset >= lines.length) {
|
|
5898
|
+
sections.push("");
|
|
5899
|
+
continue;
|
|
5900
|
+
}
|
|
5901
|
+
const end = Math.min(lines.length, lineOffset + lineLimit);
|
|
5902
|
+
const selected = lines.slice(lineOffset, end);
|
|
5903
|
+
if (includeLineNumbers) {
|
|
5904
|
+
for (let index = 0; index < selected.length; index += 1) {
|
|
5905
|
+
const lineNumber = lineOffset + index + 1;
|
|
5906
|
+
const line = selected[index] ?? "";
|
|
5907
|
+
sections.push(
|
|
5908
|
+
`L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`
|
|
5909
|
+
);
|
|
5910
|
+
}
|
|
5911
|
+
continue;
|
|
5912
|
+
}
|
|
5913
|
+
sections.push(selected.join("\n"));
|
|
5914
|
+
}
|
|
5915
|
+
return sections.join("\n");
|
|
5916
|
+
}
|
|
5917
|
+
async function writeFileGemini(input, options) {
|
|
5918
|
+
const runtime = resolveRuntime(options);
|
|
5919
|
+
const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
5920
|
+
await runAccessHook2(runtime, {
|
|
5921
|
+
cwd: runtime.cwd,
|
|
5922
|
+
tool: "write_file",
|
|
5923
|
+
action: "write",
|
|
5924
|
+
path: filePath
|
|
5925
|
+
});
|
|
5926
|
+
await runtime.filesystem.ensureDir(import_node_path5.default.dirname(filePath));
|
|
5927
|
+
await runtime.filesystem.writeTextFile(filePath, input.content);
|
|
5928
|
+
return `Successfully wrote file: ${toDisplayPath2(filePath, runtime.cwd)}`;
|
|
5929
|
+
}
|
|
5930
|
+
async function replaceFileContentGemini(input, options) {
|
|
5931
|
+
const runtime = resolveRuntime(options);
|
|
5932
|
+
const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
5933
|
+
await runAccessHook2(runtime, {
|
|
5934
|
+
cwd: runtime.cwd,
|
|
5935
|
+
tool: "replace",
|
|
5936
|
+
action: "write",
|
|
5937
|
+
path: filePath
|
|
5938
|
+
});
|
|
5939
|
+
const expectedReplacements = input.expected_replacements ?? 1;
|
|
5940
|
+
const oldValue = input.old_string;
|
|
5941
|
+
const newValue = input.new_string;
|
|
5942
|
+
let originalContent = "";
|
|
5943
|
+
try {
|
|
5944
|
+
originalContent = await runtime.filesystem.readTextFile(filePath);
|
|
5945
|
+
} catch (error) {
|
|
5946
|
+
if (isNoEntError(error) && oldValue.length === 0) {
|
|
5947
|
+
await runtime.filesystem.ensureDir(import_node_path5.default.dirname(filePath));
|
|
5948
|
+
await runtime.filesystem.writeTextFile(filePath, newValue);
|
|
5949
|
+
return `Successfully wrote new file: ${toDisplayPath2(filePath, runtime.cwd)}`;
|
|
5950
|
+
}
|
|
5951
|
+
throw error;
|
|
5952
|
+
}
|
|
5953
|
+
if (oldValue === newValue) {
|
|
5954
|
+
throw new Error("No changes to apply. old_string and new_string are identical.");
|
|
5955
|
+
}
|
|
5956
|
+
const occurrences = countOccurrences(originalContent, oldValue);
|
|
5957
|
+
if (occurrences === 0) {
|
|
5958
|
+
throw new Error("Failed to edit, could not find old_string in file.");
|
|
5959
|
+
}
|
|
5960
|
+
if (occurrences !== expectedReplacements) {
|
|
5961
|
+
throw new Error(
|
|
5962
|
+
`Failed to edit, expected ${expectedReplacements} occurrence(s) but found ${occurrences}.`
|
|
5963
|
+
);
|
|
5964
|
+
}
|
|
5965
|
+
const updatedContent = safeReplaceAll(originalContent, oldValue, newValue);
|
|
5966
|
+
await runtime.filesystem.writeTextFile(filePath, updatedContent);
|
|
5967
|
+
return `Successfully replaced ${occurrences} occurrence(s) in ${toDisplayPath2(filePath, runtime.cwd)}.`;
|
|
5968
|
+
}
|
|
5969
|
+
async function listDirectoryGemini(input, options) {
|
|
5970
|
+
const runtime = resolveRuntime(options);
|
|
5971
|
+
const dirPath = resolvePathWithPolicy(input.dir_path, runtime.cwd, runtime.allowOutsideCwd);
|
|
5972
|
+
await runAccessHook2(runtime, {
|
|
5973
|
+
cwd: runtime.cwd,
|
|
5974
|
+
tool: "list_directory",
|
|
5975
|
+
action: "list",
|
|
5976
|
+
path: dirPath
|
|
5977
|
+
});
|
|
5978
|
+
const stats = await runtime.filesystem.stat(dirPath);
|
|
5979
|
+
if (stats.kind !== "directory") {
|
|
5980
|
+
throw new Error(`Path is not a directory: ${dirPath}`);
|
|
5981
|
+
}
|
|
5982
|
+
const entries = await runtime.filesystem.readDir(dirPath);
|
|
5983
|
+
const ignoreMatchers = (input.ignore ?? []).map((pattern) => createGlobMatcher(pattern));
|
|
5984
|
+
const filtered = entries.filter((entry) => {
|
|
5985
|
+
if (ignoreMatchers.length === 0) {
|
|
5986
|
+
return true;
|
|
5987
|
+
}
|
|
5988
|
+
return !ignoreMatchers.some((matches) => matches(entry.name));
|
|
5989
|
+
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
5990
|
+
if (filtered.length === 0) {
|
|
5991
|
+
return `Directory ${toDisplayPath2(dirPath, runtime.cwd)} is empty.`;
|
|
5992
|
+
}
|
|
5993
|
+
return filtered.map((entry) => {
|
|
5994
|
+
const label = entry.kind === "directory" ? `${entry.name}/` : entry.name;
|
|
5995
|
+
return label;
|
|
5996
|
+
}).join("\n");
|
|
5997
|
+
}
|
|
5998
|
+
async function rgSearchGemini(input, options, toolName = "rg_search") {
|
|
5999
|
+
const runtime = resolveRuntime(options);
|
|
6000
|
+
const pattern = input.pattern.trim();
|
|
6001
|
+
if (pattern.length === 0) {
|
|
6002
|
+
throw new Error("pattern must not be empty");
|
|
6003
|
+
}
|
|
6004
|
+
const glob = input.glob?.trim();
|
|
6005
|
+
const searchPath = resolvePathWithPolicy(
|
|
6006
|
+
input.path ?? runtime.cwd,
|
|
6007
|
+
runtime.cwd,
|
|
6008
|
+
runtime.allowOutsideCwd
|
|
6009
|
+
);
|
|
6010
|
+
await runAccessHook2(runtime, {
|
|
6011
|
+
cwd: runtime.cwd,
|
|
6012
|
+
tool: toolName,
|
|
6013
|
+
action: "search",
|
|
6014
|
+
path: searchPath,
|
|
6015
|
+
pattern,
|
|
6016
|
+
include: glob
|
|
6017
|
+
});
|
|
6018
|
+
const searchPathInfo = await runtime.filesystem.stat(searchPath);
|
|
6019
|
+
const filesToScan = await collectSearchFiles({
|
|
6020
|
+
filesystem: runtime.filesystem,
|
|
6021
|
+
searchPath,
|
|
6022
|
+
rootKind: searchPathInfo.kind,
|
|
6023
|
+
maxScannedFiles: runtime.grepMaxScannedFiles
|
|
6024
|
+
});
|
|
6025
|
+
const matcher = glob ? createGlobMatcher(glob) : null;
|
|
6026
|
+
const patternRegex = compileRegex(pattern, input.case_sensitive === true ? "m" : "im");
|
|
6027
|
+
const excludeRegex = input.exclude_pattern ? compileRegex(input.exclude_pattern) : null;
|
|
6028
|
+
const totalMaxMatches = input.max_results ?? DEFAULT_GREP_LIMIT;
|
|
6029
|
+
const perFileMaxMatches = input.max_matches_per_file ?? Number.POSITIVE_INFINITY;
|
|
6030
|
+
const matches = [];
|
|
6031
|
+
const fileMatches = /* @__PURE__ */ new Set();
|
|
6032
|
+
for (const filePath of filesToScan) {
|
|
6033
|
+
const relativePath = normalizeSlashes(toDisplayPath2(filePath, runtime.cwd));
|
|
6034
|
+
if (matcher && !matcher(relativePath)) {
|
|
6035
|
+
continue;
|
|
6036
|
+
}
|
|
6037
|
+
const content = await runtime.filesystem.readTextFile(filePath);
|
|
6038
|
+
const lines = splitLines(content);
|
|
6039
|
+
let fileMatchCount = 0;
|
|
6040
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
6041
|
+
const line = lines[index] ?? "";
|
|
6042
|
+
if (!patternRegex.test(line)) {
|
|
6043
|
+
continue;
|
|
6044
|
+
}
|
|
6045
|
+
if (excludeRegex?.test(line)) {
|
|
6046
|
+
continue;
|
|
6047
|
+
}
|
|
6048
|
+
if (fileMatches.has(relativePath) === false) {
|
|
6049
|
+
fileMatches.add(relativePath);
|
|
6050
|
+
}
|
|
6051
|
+
if (input.names_only) {
|
|
6052
|
+
continue;
|
|
6053
|
+
}
|
|
6054
|
+
matches.push({
|
|
6055
|
+
filePath: relativePath,
|
|
6056
|
+
mtimeMs: 0,
|
|
6057
|
+
lineNumber: index + 1,
|
|
6058
|
+
line
|
|
6059
|
+
});
|
|
6060
|
+
fileMatchCount += 1;
|
|
6061
|
+
if (fileMatchCount >= perFileMaxMatches || matches.length >= totalMaxMatches) {
|
|
6062
|
+
break;
|
|
6063
|
+
}
|
|
6064
|
+
}
|
|
6065
|
+
if (input.names_only && fileMatches.size >= totalMaxMatches) {
|
|
6066
|
+
break;
|
|
6067
|
+
}
|
|
6068
|
+
if (!input.names_only && matches.length >= totalMaxMatches) {
|
|
6069
|
+
break;
|
|
6070
|
+
}
|
|
6071
|
+
}
|
|
6072
|
+
if (input.names_only) {
|
|
6073
|
+
if (fileMatches.size === 0) {
|
|
6074
|
+
return "No matches found.";
|
|
6075
|
+
}
|
|
6076
|
+
return [...fileMatches].slice(0, totalMaxMatches).join("\n");
|
|
6077
|
+
}
|
|
6078
|
+
if (matches.length === 0) {
|
|
6079
|
+
return "No matches found.";
|
|
6080
|
+
}
|
|
6081
|
+
return matches.slice(0, totalMaxMatches).map((match) => `${match.filePath}:${match.lineNumber}:${match.line ?? ""}`).join("\n");
|
|
6082
|
+
}
|
|
6083
|
+
async function grepSearchGemini(input, options) {
|
|
6084
|
+
return rgSearchGemini(
|
|
6085
|
+
{
|
|
6086
|
+
pattern: input.pattern,
|
|
6087
|
+
path: input.dir_path,
|
|
6088
|
+
glob: input.include,
|
|
6089
|
+
exclude_pattern: input.exclude_pattern,
|
|
6090
|
+
names_only: input.names_only,
|
|
6091
|
+
max_matches_per_file: input.max_matches_per_file,
|
|
6092
|
+
max_results: input.total_max_matches
|
|
6093
|
+
},
|
|
6094
|
+
options,
|
|
6095
|
+
"grep_search"
|
|
6096
|
+
);
|
|
6097
|
+
}
|
|
6098
|
+
async function globFilesGemini(input, options) {
|
|
6099
|
+
const runtime = resolveRuntime(options);
|
|
6100
|
+
const dirPath = resolvePathWithPolicy(
|
|
6101
|
+
input.dir_path ?? runtime.cwd,
|
|
6102
|
+
runtime.cwd,
|
|
6103
|
+
runtime.allowOutsideCwd
|
|
6104
|
+
);
|
|
6105
|
+
await runAccessHook2(runtime, {
|
|
6106
|
+
cwd: runtime.cwd,
|
|
6107
|
+
tool: "glob",
|
|
6108
|
+
action: "search",
|
|
6109
|
+
path: dirPath,
|
|
6110
|
+
pattern: input.pattern
|
|
6111
|
+
});
|
|
6112
|
+
const dirStats = await runtime.filesystem.stat(dirPath);
|
|
6113
|
+
if (dirStats.kind !== "directory") {
|
|
6114
|
+
throw new Error(`Path is not a directory: ${dirPath}`);
|
|
6115
|
+
}
|
|
6116
|
+
const matcher = createGlobMatcher(input.pattern, input.case_sensitive === true);
|
|
6117
|
+
const files = await collectSearchFiles({
|
|
6118
|
+
filesystem: runtime.filesystem,
|
|
6119
|
+
searchPath: dirPath,
|
|
6120
|
+
rootKind: "directory",
|
|
6121
|
+
maxScannedFiles: runtime.grepMaxScannedFiles
|
|
6122
|
+
});
|
|
6123
|
+
const matched = [];
|
|
6124
|
+
for (const filePath of files) {
|
|
6125
|
+
const relativePath = normalizeSlashes(import_node_path5.default.relative(dirPath, filePath));
|
|
6126
|
+
if (!matcher(relativePath)) {
|
|
6127
|
+
continue;
|
|
6128
|
+
}
|
|
6129
|
+
const fileStats = await runtime.filesystem.stat(filePath);
|
|
6130
|
+
matched.push({
|
|
6131
|
+
filePath,
|
|
6132
|
+
mtimeMs: fileStats.mtimeMs
|
|
6133
|
+
});
|
|
6134
|
+
}
|
|
6135
|
+
if (matched.length === 0) {
|
|
6136
|
+
return "No files found.";
|
|
6137
|
+
}
|
|
6138
|
+
matched.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
6139
|
+
return matched.map((entry) => normalizeSlashes(toDisplayPath2(entry.filePath, runtime.cwd))).join("\n");
|
|
6140
|
+
}
|
|
6141
|
+
function resolveRuntime(options) {
|
|
6142
|
+
return {
|
|
6143
|
+
cwd: import_node_path5.default.resolve(options.cwd ?? process.cwd()),
|
|
6144
|
+
filesystem: options.fs ?? createNodeAgentFilesystem(),
|
|
6145
|
+
allowOutsideCwd: options.allowOutsideCwd === true,
|
|
6146
|
+
checkAccess: options.checkAccess,
|
|
6147
|
+
maxLineLength: options.maxLineLength ?? DEFAULT_MAX_LINE_LENGTH,
|
|
6148
|
+
grepMaxScannedFiles: options.grepMaxScannedFiles ?? DEFAULT_GREP_MAX_SCANNED_FILES
|
|
6149
|
+
};
|
|
6150
|
+
}
|
|
6151
|
+
async function runAccessHook2(runtime, context) {
|
|
6152
|
+
if (!runtime.checkAccess) {
|
|
6153
|
+
return;
|
|
6154
|
+
}
|
|
6155
|
+
await runtime.checkAccess(context);
|
|
6156
|
+
}
|
|
6157
|
+
function isCodexModel(model) {
|
|
6158
|
+
const normalized = model.startsWith("chatgpt-") ? model.slice("chatgpt-".length) : model;
|
|
6159
|
+
return normalized.includes("codex");
|
|
6160
|
+
}
|
|
6161
|
+
function isGeminiModel(model) {
|
|
6162
|
+
return model.startsWith("gemini-");
|
|
6163
|
+
}
|
|
6164
|
+
function mapApplyPatchAction(action) {
|
|
6165
|
+
if (action === "add" || action === "update") {
|
|
6166
|
+
return "write";
|
|
6167
|
+
}
|
|
6168
|
+
if (action === "delete") {
|
|
6169
|
+
return "delete";
|
|
6170
|
+
}
|
|
6171
|
+
return "move";
|
|
6172
|
+
}
|
|
6173
|
+
function resolvePathWithPolicy(inputPath, cwd, allowOutsideCwd) {
|
|
6174
|
+
const absolutePath = import_node_path5.default.isAbsolute(inputPath) ? import_node_path5.default.resolve(inputPath) : import_node_path5.default.resolve(cwd, inputPath);
|
|
6175
|
+
if (!allowOutsideCwd && !isPathInsideCwd2(absolutePath, cwd)) {
|
|
6176
|
+
throw new Error(`path "${inputPath}" resolves outside cwd "${cwd}"`);
|
|
6177
|
+
}
|
|
6178
|
+
return absolutePath;
|
|
6179
|
+
}
|
|
6180
|
+
function isPathInsideCwd2(candidatePath, cwd) {
|
|
6181
|
+
const relative = import_node_path5.default.relative(cwd, candidatePath);
|
|
6182
|
+
return relative === "" || !relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative);
|
|
6183
|
+
}
|
|
6184
|
+
function toDisplayPath2(absolutePath, cwd) {
|
|
6185
|
+
const relative = import_node_path5.default.relative(cwd, absolutePath);
|
|
6186
|
+
if (relative === "") {
|
|
6187
|
+
return ".";
|
|
6188
|
+
}
|
|
6189
|
+
if (!relative.startsWith("..") && !import_node_path5.default.isAbsolute(relative)) {
|
|
6190
|
+
return relative;
|
|
6191
|
+
}
|
|
6192
|
+
return absolutePath;
|
|
6193
|
+
}
|
|
6194
|
+
function splitLines(content) {
|
|
6195
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
6196
|
+
const lines = normalized.split("\n");
|
|
6197
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
6198
|
+
lines.pop();
|
|
6199
|
+
}
|
|
6200
|
+
return lines;
|
|
6201
|
+
}
|
|
6202
|
+
function truncateAtCodePointBoundary(value, maxLength) {
|
|
6203
|
+
if (value.length <= maxLength) {
|
|
6204
|
+
return value;
|
|
6205
|
+
}
|
|
6206
|
+
return Array.from(value).slice(0, maxLength).join("");
|
|
6207
|
+
}
|
|
6208
|
+
function measureIndent(line, tabWidth) {
|
|
6209
|
+
let count = 0;
|
|
6210
|
+
for (const char of line) {
|
|
6211
|
+
if (char === " ") {
|
|
6212
|
+
count += 1;
|
|
6213
|
+
continue;
|
|
6214
|
+
}
|
|
6215
|
+
if (char === " ") {
|
|
6216
|
+
count += tabWidth;
|
|
6217
|
+
continue;
|
|
6218
|
+
}
|
|
6219
|
+
break;
|
|
6220
|
+
}
|
|
6221
|
+
return count;
|
|
6222
|
+
}
|
|
6223
|
+
function computeEffectiveIndents(records) {
|
|
6224
|
+
const effective = [];
|
|
6225
|
+
let previous = 0;
|
|
6226
|
+
for (const record of records) {
|
|
6227
|
+
if (record.raw.trim().length === 0) {
|
|
6228
|
+
effective.push(previous);
|
|
6229
|
+
} else {
|
|
6230
|
+
previous = record.indent;
|
|
6231
|
+
effective.push(previous);
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
return effective;
|
|
6235
|
+
}
|
|
6236
|
+
function trimBoundaryBlankLines(records) {
|
|
6237
|
+
while (records.length > 0 && records[0]?.raw.trim().length === 0) {
|
|
6238
|
+
records.shift();
|
|
6239
|
+
}
|
|
6240
|
+
while (records.length > 0 && records[records.length - 1]?.raw.trim().length === 0) {
|
|
6241
|
+
records.pop();
|
|
6242
|
+
}
|
|
6243
|
+
}
|
|
6244
|
+
function isCommentLine(line) {
|
|
6245
|
+
const trimmed = line.trim();
|
|
6246
|
+
return trimmed.startsWith("#") || trimmed.startsWith("//") || trimmed.startsWith("--");
|
|
6247
|
+
}
|
|
6248
|
+
function readWithIndentationMode(params) {
|
|
6249
|
+
const { records, anchorLine, limit, maxLevels, includeSiblings, includeHeader, maxLines } = params;
|
|
6250
|
+
const anchorIndex = anchorLine - 1;
|
|
6251
|
+
const effectiveIndents = computeEffectiveIndents(records);
|
|
6252
|
+
const anchorIndent = effectiveIndents[anchorIndex] ?? 0;
|
|
6253
|
+
const minIndent = maxLevels === 0 ? 0 : Math.max(anchorIndent - maxLevels * DEFAULT_TAB_WIDTH, 0);
|
|
6254
|
+
const guardLimit = maxLines ?? limit;
|
|
6255
|
+
const finalLimit = Math.min(limit, guardLimit, records.length);
|
|
6256
|
+
if (finalLimit <= 1) {
|
|
6257
|
+
return [records[anchorIndex]].filter((entry) => Boolean(entry));
|
|
6258
|
+
}
|
|
6259
|
+
let upper = anchorIndex - 1;
|
|
6260
|
+
let lower = anchorIndex + 1;
|
|
6261
|
+
let upperMinIndentHits = 0;
|
|
6262
|
+
let lowerMinIndentHits = 0;
|
|
6263
|
+
const output = [records[anchorIndex]].filter(
|
|
6264
|
+
(entry) => Boolean(entry)
|
|
6265
|
+
);
|
|
6266
|
+
while (output.length < finalLimit) {
|
|
6267
|
+
let progressed = 0;
|
|
6268
|
+
if (upper >= 0) {
|
|
6269
|
+
const candidate = records[upper];
|
|
6270
|
+
const candidateIndent = effectiveIndents[upper] ?? 0;
|
|
6271
|
+
if (candidate && candidateIndent >= minIndent) {
|
|
6272
|
+
output.unshift(candidate);
|
|
6273
|
+
progressed += 1;
|
|
6274
|
+
upper -= 1;
|
|
6275
|
+
if (candidateIndent === minIndent && !includeSiblings) {
|
|
6276
|
+
const allowHeaderComment = includeHeader && isCommentLine(candidate.raw);
|
|
6277
|
+
const canTakeLine = allowHeaderComment || upperMinIndentHits === 0;
|
|
6278
|
+
if (canTakeLine) {
|
|
6279
|
+
upperMinIndentHits += 1;
|
|
6280
|
+
} else {
|
|
6281
|
+
output.shift();
|
|
6282
|
+
progressed -= 1;
|
|
6283
|
+
upper = -1;
|
|
6284
|
+
}
|
|
6285
|
+
}
|
|
6286
|
+
if (output.length >= finalLimit) {
|
|
6287
|
+
break;
|
|
6288
|
+
}
|
|
6289
|
+
} else {
|
|
6290
|
+
upper = -1;
|
|
6291
|
+
}
|
|
6292
|
+
}
|
|
6293
|
+
if (lower < records.length) {
|
|
6294
|
+
const candidate = records[lower];
|
|
6295
|
+
const candidateIndent = effectiveIndents[lower] ?? 0;
|
|
6296
|
+
if (candidate && candidateIndent >= minIndent) {
|
|
6297
|
+
output.push(candidate);
|
|
6298
|
+
progressed += 1;
|
|
6299
|
+
lower += 1;
|
|
6300
|
+
if (candidateIndent === minIndent && !includeSiblings) {
|
|
6301
|
+
if (lowerMinIndentHits > 0) {
|
|
6302
|
+
output.pop();
|
|
6303
|
+
progressed -= 1;
|
|
6304
|
+
lower = records.length;
|
|
6305
|
+
}
|
|
6306
|
+
lowerMinIndentHits += 1;
|
|
6307
|
+
}
|
|
6308
|
+
} else {
|
|
6309
|
+
lower = records.length;
|
|
6310
|
+
}
|
|
6311
|
+
}
|
|
6312
|
+
if (progressed === 0) {
|
|
6313
|
+
break;
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
trimBoundaryBlankLines(output);
|
|
6317
|
+
return output;
|
|
6318
|
+
}
|
|
6319
|
+
async function collectDirectoryEntries(filesystem, rootPath, depth, maxLineLength) {
|
|
6320
|
+
const queue = [
|
|
6321
|
+
{ path: rootPath, relativePrefix: "", remainingDepth: depth }
|
|
6322
|
+
];
|
|
6323
|
+
const records = [];
|
|
6324
|
+
while (queue.length > 0) {
|
|
6325
|
+
const next = queue.shift();
|
|
6326
|
+
if (!next) {
|
|
6327
|
+
break;
|
|
6328
|
+
}
|
|
6329
|
+
const entries = await filesystem.readDir(next.path);
|
|
6330
|
+
const nextEntries = [...entries].map((entry) => {
|
|
6331
|
+
const relativePath = next.relativePrefix ? `${next.relativePrefix}/${entry.name}` : entry.name;
|
|
6332
|
+
return {
|
|
6333
|
+
entry,
|
|
6334
|
+
relativePath,
|
|
6335
|
+
depth: next.relativePrefix.length === 0 ? 0 : next.relativePrefix.split("/").length,
|
|
6336
|
+
sortName: normalizeSlashes(relativePath)
|
|
6337
|
+
};
|
|
6338
|
+
}).sort((left, right) => left.sortName.localeCompare(right.sortName));
|
|
6339
|
+
for (const item of nextEntries) {
|
|
6340
|
+
if (item.entry.kind === "directory" && next.remainingDepth > 1) {
|
|
6341
|
+
queue.push({
|
|
6342
|
+
path: item.entry.path,
|
|
6343
|
+
relativePrefix: item.relativePath,
|
|
6344
|
+
remainingDepth: next.remainingDepth - 1
|
|
6345
|
+
});
|
|
6346
|
+
}
|
|
6347
|
+
records.push({
|
|
6348
|
+
name: item.sortName,
|
|
6349
|
+
displayName: truncateAtCodePointBoundary(item.entry.name, maxLineLength),
|
|
6350
|
+
depth: item.depth,
|
|
6351
|
+
kind: item.entry.kind
|
|
6352
|
+
});
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
records.sort((left, right) => left.name.localeCompare(right.name));
|
|
6356
|
+
return records;
|
|
6357
|
+
}
|
|
6358
|
+
function formatListEntry(entry) {
|
|
6359
|
+
const indent = " ".repeat(entry.depth * 2);
|
|
6360
|
+
let name = entry.displayName;
|
|
6361
|
+
if (entry.kind === "directory") {
|
|
6362
|
+
name += "/";
|
|
6363
|
+
} else if (entry.kind === "symlink") {
|
|
6364
|
+
name += "@";
|
|
6365
|
+
} else if (entry.kind === "other") {
|
|
6366
|
+
name += "?";
|
|
6367
|
+
}
|
|
6368
|
+
return `${indent}${name}`;
|
|
6369
|
+
}
|
|
6370
|
+
async function collectSearchFiles(params) {
|
|
6371
|
+
const { filesystem, searchPath, rootKind, maxScannedFiles } = params;
|
|
6372
|
+
if (rootKind === "file") {
|
|
6373
|
+
return [searchPath];
|
|
6374
|
+
}
|
|
6375
|
+
const queue = [searchPath];
|
|
6376
|
+
const files = [];
|
|
6377
|
+
while (queue.length > 0) {
|
|
6378
|
+
const current = queue.shift();
|
|
6379
|
+
if (!current) {
|
|
6380
|
+
break;
|
|
6381
|
+
}
|
|
6382
|
+
const entries = await filesystem.readDir(current);
|
|
6383
|
+
for (const entry of entries) {
|
|
6384
|
+
if (entry.kind === "directory") {
|
|
6385
|
+
queue.push(entry.path);
|
|
6386
|
+
continue;
|
|
6387
|
+
}
|
|
6388
|
+
if (entry.kind !== "file") {
|
|
6389
|
+
continue;
|
|
6390
|
+
}
|
|
6391
|
+
files.push(entry.path);
|
|
6392
|
+
if (files.length >= maxScannedFiles) {
|
|
6393
|
+
return files;
|
|
6394
|
+
}
|
|
6395
|
+
}
|
|
6396
|
+
}
|
|
6397
|
+
return files;
|
|
6398
|
+
}
|
|
6399
|
+
function compileRegex(pattern, flags = "m") {
|
|
6400
|
+
try {
|
|
6401
|
+
return new RegExp(pattern, flags);
|
|
6402
|
+
} catch (error) {
|
|
6403
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6404
|
+
throw new Error(`invalid regex pattern: ${message}`);
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6407
|
+
function createGlobMatcher(pattern, caseSensitive = false) {
|
|
6408
|
+
const expanded = expandBracePatterns(normalizeSlashes(pattern.trim()));
|
|
6409
|
+
const flags = caseSensitive ? "" : "i";
|
|
6410
|
+
const compiled = expanded.map((entry) => ({
|
|
6411
|
+
regex: globToRegex(entry, flags),
|
|
6412
|
+
applyToBasename: !entry.includes("/")
|
|
6413
|
+
}));
|
|
6414
|
+
return (candidatePath) => {
|
|
6415
|
+
const normalizedPath = normalizeSlashes(candidatePath);
|
|
6416
|
+
const basename = import_node_path5.default.posix.basename(normalizedPath);
|
|
6417
|
+
return compiled.some(
|
|
6418
|
+
(entry) => entry.regex.test(entry.applyToBasename ? basename : normalizedPath)
|
|
6419
|
+
);
|
|
6420
|
+
};
|
|
6421
|
+
}
|
|
6422
|
+
function globToRegex(globPattern, flags) {
|
|
6423
|
+
let source = "^";
|
|
6424
|
+
for (let index = 0; index < globPattern.length; index += 1) {
|
|
6425
|
+
const char = globPattern[index];
|
|
6426
|
+
const nextChar = globPattern[index + 1];
|
|
6427
|
+
if (char === void 0) {
|
|
6428
|
+
continue;
|
|
6429
|
+
}
|
|
6430
|
+
if (char === "*" && nextChar === "*") {
|
|
6431
|
+
source += ".*";
|
|
6432
|
+
index += 1;
|
|
6433
|
+
continue;
|
|
6434
|
+
}
|
|
6435
|
+
if (char === "*") {
|
|
6436
|
+
source += "[^/]*";
|
|
6437
|
+
continue;
|
|
6438
|
+
}
|
|
6439
|
+
if (char === "?") {
|
|
6440
|
+
source += "[^/]";
|
|
6441
|
+
continue;
|
|
6442
|
+
}
|
|
6443
|
+
source += escapeRegexCharacter(char);
|
|
6444
|
+
}
|
|
6445
|
+
source += "$";
|
|
6446
|
+
return new RegExp(source, flags);
|
|
6447
|
+
}
|
|
6448
|
+
function expandBracePatterns(pattern) {
|
|
6449
|
+
const start = pattern.indexOf("{");
|
|
6450
|
+
if (start === -1) {
|
|
6451
|
+
return [pattern];
|
|
6452
|
+
}
|
|
6453
|
+
let depth = 0;
|
|
6454
|
+
let end = -1;
|
|
6455
|
+
for (let index = start; index < pattern.length; index += 1) {
|
|
6456
|
+
const char = pattern[index];
|
|
6457
|
+
if (char === "{") {
|
|
6458
|
+
depth += 1;
|
|
6459
|
+
continue;
|
|
6460
|
+
}
|
|
6461
|
+
if (char === "}") {
|
|
6462
|
+
depth -= 1;
|
|
6463
|
+
if (depth === 0) {
|
|
6464
|
+
end = index;
|
|
6465
|
+
break;
|
|
6466
|
+
}
|
|
6467
|
+
}
|
|
6468
|
+
}
|
|
6469
|
+
if (end === -1) {
|
|
6470
|
+
return [pattern];
|
|
6471
|
+
}
|
|
6472
|
+
const prefix = pattern.slice(0, start);
|
|
6473
|
+
const suffix = pattern.slice(end + 1);
|
|
6474
|
+
const body = pattern.slice(start + 1, end);
|
|
6475
|
+
const variants = splitTopLevel(body, ",");
|
|
6476
|
+
const expanded = [];
|
|
6477
|
+
for (const variant of variants) {
|
|
6478
|
+
expanded.push(...expandBracePatterns(`${prefix}${variant}${suffix}`));
|
|
6479
|
+
}
|
|
6480
|
+
return expanded;
|
|
6481
|
+
}
|
|
6482
|
+
function splitTopLevel(value, separator) {
|
|
6483
|
+
const parts = [];
|
|
6484
|
+
let depth = 0;
|
|
6485
|
+
let current = "";
|
|
6486
|
+
for (const char of value) {
|
|
6487
|
+
if (char === "{") {
|
|
6488
|
+
depth += 1;
|
|
6489
|
+
current += char;
|
|
6490
|
+
continue;
|
|
6491
|
+
}
|
|
6492
|
+
if (char === "}") {
|
|
6493
|
+
depth = Math.max(0, depth - 1);
|
|
6494
|
+
current += char;
|
|
6495
|
+
continue;
|
|
6496
|
+
}
|
|
6497
|
+
if (char === separator && depth === 0) {
|
|
6498
|
+
parts.push(current);
|
|
6499
|
+
current = "";
|
|
6500
|
+
continue;
|
|
6501
|
+
}
|
|
6502
|
+
current += char;
|
|
6503
|
+
}
|
|
6504
|
+
parts.push(current);
|
|
6505
|
+
return parts;
|
|
6506
|
+
}
|
|
6507
|
+
function escapeRegexCharacter(char) {
|
|
6508
|
+
return /[.*+?^${}()|[\]\\]/u.test(char) ? `\\${char}` : char;
|
|
6509
|
+
}
|
|
6510
|
+
function normalizeSlashes(value) {
|
|
6511
|
+
return value.replaceAll("\\", "/");
|
|
6512
|
+
}
|
|
6513
|
+
function countOccurrences(text, search) {
|
|
6514
|
+
if (search.length === 0) {
|
|
6515
|
+
return 0;
|
|
6516
|
+
}
|
|
6517
|
+
return text.split(search).length - 1;
|
|
6518
|
+
}
|
|
6519
|
+
function safeReplaceAll(text, search, replacement) {
|
|
6520
|
+
if (search.length === 0) {
|
|
6521
|
+
return text;
|
|
6522
|
+
}
|
|
6523
|
+
return text.split(search).join(replacement);
|
|
6524
|
+
}
|
|
6525
|
+
function isNoEntError(error) {
|
|
6526
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
6527
|
+
}
|
|
6528
|
+
|
|
6529
|
+
// src/agent.ts
|
|
6530
|
+
async function runAgentLoop(request) {
|
|
6531
|
+
const { tools: customTools, filesystemTool, filesystem_tool, ...toolLoopRequest } = request;
|
|
6532
|
+
const filesystemSelection = filesystemTool ?? filesystem_tool;
|
|
6533
|
+
const filesystemTools = resolveFilesystemTools(request.model, filesystemSelection);
|
|
6534
|
+
const mergedTools = mergeToolSets(filesystemTools, customTools ?? {});
|
|
6535
|
+
if (Object.keys(mergedTools).length === 0) {
|
|
6536
|
+
throw new Error(
|
|
6537
|
+
"runAgentLoop requires at least one tool. Provide `tools` or enable `filesystemTool`."
|
|
6538
|
+
);
|
|
6539
|
+
}
|
|
6540
|
+
return runToolLoop({
|
|
6541
|
+
...toolLoopRequest,
|
|
6542
|
+
tools: mergedTools
|
|
6543
|
+
});
|
|
6544
|
+
}
|
|
6545
|
+
function resolveFilesystemTools(model, selection) {
|
|
6546
|
+
if (selection === void 0 || selection === false) {
|
|
6547
|
+
return {};
|
|
6548
|
+
}
|
|
6549
|
+
if (selection === true) {
|
|
6550
|
+
return createFilesystemToolSetForModel(model, "auto");
|
|
6551
|
+
}
|
|
6552
|
+
if (typeof selection === "string") {
|
|
6553
|
+
return createFilesystemToolSetForModel(model, selection);
|
|
6554
|
+
}
|
|
6555
|
+
if (selection.enabled === false) {
|
|
6556
|
+
return {};
|
|
6557
|
+
}
|
|
6558
|
+
if (selection.options && selection.profile !== void 0) {
|
|
6559
|
+
return createFilesystemToolSetForModel(model, selection.profile, selection.options);
|
|
6560
|
+
}
|
|
6561
|
+
if (selection.options) {
|
|
6562
|
+
return createFilesystemToolSetForModel(model, selection.options);
|
|
6563
|
+
}
|
|
6564
|
+
return createFilesystemToolSetForModel(model, selection.profile ?? "auto");
|
|
6565
|
+
}
|
|
6566
|
+
function mergeToolSets(base, extra) {
|
|
6567
|
+
const merged = { ...base };
|
|
6568
|
+
for (const [toolName, toolSpec] of Object.entries(extra)) {
|
|
6569
|
+
if (Object.hasOwn(merged, toolName)) {
|
|
6570
|
+
throw new Error(
|
|
6571
|
+
`Duplicate tool name "${toolName}" in runAgentLoop. Rename the custom tool or disable that filesystem tool.`
|
|
6572
|
+
);
|
|
6573
|
+
}
|
|
6574
|
+
merged[toolName] = toolSpec;
|
|
6575
|
+
}
|
|
6576
|
+
return merged;
|
|
6577
|
+
}
|
|
3919
6578
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3920
6579
|
0 && (module.exports = {
|
|
6580
|
+
CODEX_APPLY_PATCH_FREEFORM_TOOL_DESCRIPTION,
|
|
6581
|
+
CODEX_APPLY_PATCH_JSON_TOOL_DESCRIPTION,
|
|
6582
|
+
CODEX_APPLY_PATCH_LARK_GRAMMAR,
|
|
6583
|
+
FIREWORKS_DEFAULT_GLM_MODEL,
|
|
6584
|
+
FIREWORKS_DEFAULT_KIMI_MODEL,
|
|
6585
|
+
FIREWORKS_DEFAULT_MINIMAX_MODEL,
|
|
6586
|
+
FIREWORKS_MODEL_IDS,
|
|
6587
|
+
InMemoryAgentFilesystem,
|
|
3921
6588
|
LlmJsonCallError,
|
|
3922
6589
|
appendMarkdownSourcesSection,
|
|
6590
|
+
applyPatch,
|
|
3923
6591
|
configureGemini,
|
|
3924
6592
|
convertGooglePartsToLlmParts,
|
|
6593
|
+
createApplyPatchTool,
|
|
6594
|
+
createCodexApplyPatchTool,
|
|
6595
|
+
createCodexFilesystemToolSet,
|
|
6596
|
+
createCodexReadFileTool,
|
|
6597
|
+
createFilesystemToolSetForModel,
|
|
6598
|
+
createGeminiFilesystemToolSet,
|
|
6599
|
+
createGeminiReadFileTool,
|
|
6600
|
+
createGlobTool,
|
|
6601
|
+
createGrepFilesTool,
|
|
6602
|
+
createGrepSearchTool,
|
|
6603
|
+
createInMemoryAgentFilesystem,
|
|
6604
|
+
createListDirTool,
|
|
6605
|
+
createListDirectoryTool,
|
|
6606
|
+
createModelAgnosticFilesystemToolSet,
|
|
6607
|
+
createNodeAgentFilesystem,
|
|
6608
|
+
createReadFilesTool,
|
|
6609
|
+
createReplaceTool,
|
|
6610
|
+
createRgSearchTool,
|
|
6611
|
+
createWriteFileTool,
|
|
6612
|
+
customTool,
|
|
3925
6613
|
encodeChatGptAuthJson,
|
|
3926
6614
|
encodeChatGptAuthJsonB64,
|
|
3927
6615
|
estimateCallCostUsd,
|
|
@@ -3932,11 +6620,15 @@ ${lines}`;
|
|
|
3932
6620
|
generateText,
|
|
3933
6621
|
getChatGptAuthProfile,
|
|
3934
6622
|
getCurrentToolCallContext,
|
|
6623
|
+
isFireworksModelId,
|
|
3935
6624
|
isGeminiModelId,
|
|
3936
6625
|
loadEnvFromFile,
|
|
3937
6626
|
loadLocalEnv,
|
|
3938
6627
|
parseJsonFromLlmText,
|
|
3939
6628
|
refreshChatGptOauthToken,
|
|
6629
|
+
resolveFilesystemToolProfile,
|
|
6630
|
+
resolveFireworksModelId,
|
|
6631
|
+
runAgentLoop,
|
|
3940
6632
|
runToolLoop,
|
|
3941
6633
|
sanitisePartForLogging,
|
|
3942
6634
|
streamJson,
|