@ljoukov/llm 3.0.10 → 3.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -68,10 +68,10 @@ __export(index_exports, {
68
68
  createListDirectoryTool: () => createListDirectoryTool,
69
69
  createModelAgnosticFilesystemToolSet: () => createModelAgnosticFilesystemToolSet,
70
70
  createNodeAgentFilesystem: () => createNodeAgentFilesystem,
71
- createReadFilesTool: () => createReadFilesTool,
72
71
  createReplaceTool: () => createReplaceTool,
73
72
  createRgSearchTool: () => createRgSearchTool,
74
73
  createToolLoopSteeringChannel: () => createToolLoopSteeringChannel,
74
+ createViewImageTool: () => createViewImageTool,
75
75
  createWriteFileTool: () => createWriteFileTool,
76
76
  customTool: () => customTool,
77
77
  encodeChatGptAuthJson: () => encodeChatGptAuthJson,
@@ -234,6 +234,15 @@ var GEMINI_2_5_PRO_PRICING = {
234
234
  outputRateLow: 10 / 1e6,
235
235
  outputRateHigh: 15 / 1e6
236
236
  };
237
+ var GEMINI_2_5_FLASH_PRICING = {
238
+ threshold: 2e5,
239
+ inputRateLow: 0.3 / 1e6,
240
+ inputRateHigh: 0.3 / 1e6,
241
+ cachedRateLow: 0.03 / 1e6,
242
+ cachedRateHigh: 0.03 / 1e6,
243
+ outputRateLow: 2.5 / 1e6,
244
+ outputRateHigh: 2.5 / 1e6
245
+ };
237
246
  var GEMINI_IMAGE_PREVIEW_PRICING = {
238
247
  inputRate: 2 / 1e6,
239
248
  cachedRate: 0.2 / 1e6,
@@ -249,6 +258,9 @@ function getGeminiProPricing(modelId) {
249
258
  if (modelId.includes("gemini-2.5-pro")) {
250
259
  return GEMINI_2_5_PRO_PRICING;
251
260
  }
261
+ if (modelId.includes("gemini-2.5-flash") || modelId.includes("gemini-flash-latest")) {
262
+ return GEMINI_2_5_FLASH_PRICING;
263
+ }
252
264
  if (modelId.includes("gemini-3-pro") || modelId.includes("gemini-3.1-pro")) {
253
265
  return GEMINI_3_PRO_PREVIEW_PRICING;
254
266
  }
@@ -2206,7 +2218,6 @@ function getGoogleAuthOptions(scopes) {
2206
2218
 
2207
2219
  // src/google/client.ts
2208
2220
  var GEMINI_TEXT_MODEL_IDS = [
2209
- "gemini-3-pro-preview",
2210
2221
  "gemini-3.1-pro-preview",
2211
2222
  "gemini-3-flash-preview",
2212
2223
  "gemini-2.5-pro",
@@ -4010,6 +4021,38 @@ function mergeToolOutput(value) {
4010
4021
  return JSON.stringify({ error: "Failed to serialize tool output", detail: message });
4011
4022
  }
4012
4023
  }
4024
+ function isLlmToolOutputContentItem(value) {
4025
+ if (!isPlainRecord(value)) {
4026
+ return false;
4027
+ }
4028
+ const itemType = typeof value.type === "string" ? value.type : "";
4029
+ if (itemType === "input_text") {
4030
+ return typeof value.text === "string";
4031
+ }
4032
+ if (itemType === "input_image") {
4033
+ return typeof value.image_url === "string";
4034
+ }
4035
+ if (itemType === "input_file") {
4036
+ const keys = ["file_data", "file_id", "file_url", "filename"];
4037
+ for (const key of keys) {
4038
+ const part = value[key];
4039
+ if (part !== void 0 && part !== null && typeof part !== "string") {
4040
+ return false;
4041
+ }
4042
+ }
4043
+ return true;
4044
+ }
4045
+ return false;
4046
+ }
4047
+ function toOpenAiToolOutput(value) {
4048
+ if (isLlmToolOutputContentItem(value)) {
4049
+ return [value];
4050
+ }
4051
+ if (Array.isArray(value) && value.every((item) => isLlmToolOutputContentItem(item))) {
4052
+ return value;
4053
+ }
4054
+ return mergeToolOutput(value);
4055
+ }
4013
4056
  function parseOpenAiToolArguments(raw) {
4014
4057
  const trimmed = raw.trim();
4015
4058
  if (trimmed.length === 0) {
@@ -4365,7 +4408,6 @@ function resolveGeminiThinkingConfig(modelId) {
4365
4408
  return void 0;
4366
4409
  }
4367
4410
  switch (modelId) {
4368
- case "gemini-3-pro-preview":
4369
4411
  case "gemini-3.1-pro-preview":
4370
4412
  return { includeThoughts: true };
4371
4413
  case "gemini-3-flash-preview":
@@ -5367,13 +5409,13 @@ async function runToolLoop(request) {
5367
5409
  toolOutputs.push({
5368
5410
  type: "custom_tool_call_output",
5369
5411
  call_id: entry.call.call_id,
5370
- output: mergeToolOutput(outputPayload)
5412
+ output: toOpenAiToolOutput(outputPayload)
5371
5413
  });
5372
5414
  } else {
5373
5415
  toolOutputs.push({
5374
5416
  type: "function_call_output",
5375
5417
  call_id: entry.call.call_id,
5376
- output: mergeToolOutput(outputPayload)
5418
+ output: toOpenAiToolOutput(outputPayload)
5377
5419
  });
5378
5420
  }
5379
5421
  }
@@ -5588,7 +5630,7 @@ async function runToolLoop(request) {
5588
5630
  toolOutputs.push({
5589
5631
  type: "custom_tool_call_output",
5590
5632
  call_id: entry.ids.callId,
5591
- output: mergeToolOutput(outputPayload)
5633
+ output: toOpenAiToolOutput(outputPayload)
5592
5634
  });
5593
5635
  } else {
5594
5636
  toolOutputs.push({
@@ -5602,7 +5644,7 @@ async function runToolLoop(request) {
5602
5644
  toolOutputs.push({
5603
5645
  type: "function_call_output",
5604
5646
  call_id: entry.ids.callId,
5605
- output: mergeToolOutput(outputPayload)
5647
+ output: toOpenAiToolOutput(outputPayload)
5606
5648
  });
5607
5649
  }
5608
5650
  }
@@ -7233,6 +7275,7 @@ function sleep2(ms) {
7233
7275
 
7234
7276
  // src/tools/filesystemTools.ts
7235
7277
  var import_node_path5 = __toESM(require("path"), 1);
7278
+ var import_node_buffer3 = require("buffer");
7236
7279
  var import_zod6 = require("zod");
7237
7280
 
7238
7281
  // src/tools/applyPatch.ts
@@ -7266,6 +7309,10 @@ var InMemoryAgentFilesystem = class {
7266
7309
  }
7267
7310
  return file.content;
7268
7311
  }
7312
+ async readBinaryFile(filePath) {
7313
+ const content = await this.readTextFile(filePath);
7314
+ return Buffer.from(content, "utf8");
7315
+ }
7269
7316
  async writeTextFile(filePath, content) {
7270
7317
  const absolutePath = import_node_path3.default.resolve(filePath);
7271
7318
  const parentPath = import_node_path3.default.dirname(absolutePath);
@@ -7378,6 +7425,7 @@ var InMemoryAgentFilesystem = class {
7378
7425
  function createNodeAgentFilesystem() {
7379
7426
  return {
7380
7427
  readTextFile: async (filePath) => import_node_fs3.promises.readFile(filePath, "utf8"),
7428
+ readBinaryFile: async (filePath) => import_node_fs3.promises.readFile(filePath),
7381
7429
  writeTextFile: async (filePath, content) => import_node_fs3.promises.writeFile(filePath, content, "utf8"),
7382
7430
  deleteFile: async (filePath) => import_node_fs3.promises.unlink(filePath),
7383
7431
  ensureDir: async (directoryPath) => {
@@ -7933,30 +7981,28 @@ function formatSummary(added, modified, deleted) {
7933
7981
 
7934
7982
  // src/tools/filesystemTools.ts
7935
7983
  var DEFAULT_READ_FILE_LINE_LIMIT = 2e3;
7936
- var DEFAULT_READ_FILES_LINE_LIMIT = 200;
7937
- var DEFAULT_READ_FILES_CHAR_LIMIT = 4e3;
7938
7984
  var DEFAULT_LIST_DIR_LIMIT = 25;
7939
7985
  var DEFAULT_LIST_DIR_DEPTH = 2;
7940
7986
  var DEFAULT_GREP_LIMIT = 100;
7941
7987
  var MAX_GREP_LIMIT = 2e3;
7988
+ var MAX_VIEW_IMAGE_BYTES = 10 * 1024 * 1024;
7942
7989
  var DEFAULT_MAX_LINE_LENGTH = 500;
7943
7990
  var DEFAULT_GREP_MAX_SCANNED_FILES = 2e4;
7944
- var DEFAULT_TAB_WIDTH = 4;
7991
+ var SUPPORTED_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
7992
+ var IMAGE_MIME_BY_EXTENSION = {
7993
+ ".png": "image/png",
7994
+ ".jpg": "image/jpeg",
7995
+ ".jpeg": "image/jpeg",
7996
+ ".webp": "image/webp",
7997
+ ".gif": "image/gif"
7998
+ };
7945
7999
  var codexReadFileInputSchema = import_zod6.z.object({
7946
8000
  file_path: import_zod6.z.string().min(1).describe(
7947
8001
  "Path to the file (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
7948
8002
  ),
7949
8003
  offset: import_zod6.z.number().int().min(1).nullish().describe("The line number to start reading from. Must be 1 or greater."),
7950
- limit: import_zod6.z.number().int().min(1).nullish().describe("The maximum number of lines to return."),
7951
- mode: import_zod6.z.enum(["slice", "indentation"]).nullish().describe('Optional mode selector: "slice" (default) or "indentation".'),
7952
- indentation: import_zod6.z.object({
7953
- anchor_line: import_zod6.z.number().int().min(1).nullish(),
7954
- max_levels: import_zod6.z.number().int().min(0).nullish(),
7955
- include_siblings: import_zod6.z.boolean().nullish(),
7956
- include_header: import_zod6.z.boolean().nullish(),
7957
- max_lines: import_zod6.z.number().int().min(1).nullish()
7958
- }).nullish()
7959
- });
8004
+ limit: import_zod6.z.number().int().min(1).nullish().describe("The maximum number of lines to return.")
8005
+ }).strict();
7960
8006
  var codexListDirInputSchema = import_zod6.z.object({
7961
8007
  dir_path: import_zod6.z.string().min(1).describe(
7962
8008
  "Path to the directory to list (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
@@ -7971,6 +8017,9 @@ var codexGrepFilesInputSchema = import_zod6.z.object({
7971
8017
  path: import_zod6.z.string().nullish().describe("Directory or file path to search. Defaults to cwd."),
7972
8018
  limit: import_zod6.z.number().int().min(1).nullish().describe("Maximum number of file paths to return (defaults to 100).")
7973
8019
  });
8020
+ var codexViewImageInputSchema = import_zod6.z.object({
8021
+ path: import_zod6.z.string().min(1).describe("Local filesystem path to an image file")
8022
+ });
7974
8023
  var applyPatchInputSchema = import_zod6.z.object({
7975
8024
  input: import_zod6.z.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
7976
8025
  });
@@ -7978,24 +8027,7 @@ var geminiReadFileInputSchema = import_zod6.z.object({
7978
8027
  file_path: import_zod6.z.string().min(1),
7979
8028
  offset: import_zod6.z.number().int().min(0).nullish(),
7980
8029
  limit: import_zod6.z.number().int().min(1).nullish()
7981
- });
7982
- var geminiReadFilesInputSchema = import_zod6.z.object({
7983
- paths: import_zod6.z.array(import_zod6.z.string().min(1)).min(1),
7984
- line_offset: import_zod6.z.number().int().min(0).nullish(),
7985
- line_limit: import_zod6.z.number().int().min(1).nullish(),
7986
- char_offset: import_zod6.z.number().int().min(0).nullish(),
7987
- char_limit: import_zod6.z.number().int().min(1).nullish(),
7988
- include_line_numbers: import_zod6.z.boolean().nullish()
7989
- }).superRefine((value, context) => {
7990
- const hasLineWindow = value.line_offset !== void 0 || value.line_limit !== void 0;
7991
- const hasCharWindow = value.char_offset !== void 0 || value.char_limit !== void 0;
7992
- if (hasLineWindow && hasCharWindow) {
7993
- context.addIssue({
7994
- code: import_zod6.z.ZodIssueCode.custom,
7995
- message: "Use either line_* or char_* window arguments, not both."
7996
- });
7997
- }
7998
- });
8030
+ }).strict();
7999
8031
  var geminiWriteFileInputSchema = import_zod6.z.object({
8000
8032
  file_path: import_zod6.z.string().min(1),
8001
8033
  content: import_zod6.z.string()
@@ -8078,7 +8110,8 @@ function createCodexFilesystemToolSet(options = {}) {
8078
8110
  apply_patch: createCodexApplyPatchTool(options),
8079
8111
  read_file: createCodexReadFileTool(options),
8080
8112
  list_dir: createListDirTool(options),
8081
- grep_files: createGrepFilesTool(options)
8113
+ grep_files: createGrepFilesTool(options),
8114
+ view_image: createViewImageTool(options)
8082
8115
  };
8083
8116
  }
8084
8117
  function createGeminiFilesystemToolSet(options = {}) {
@@ -8127,7 +8160,7 @@ function createCodexApplyPatchTool(options = {}) {
8127
8160
  }
8128
8161
  function createCodexReadFileTool(options = {}) {
8129
8162
  return tool({
8130
- description: "Reads a local file with 1-indexed line numbers, supporting slice and indentation-aware block modes.",
8163
+ description: "Reads a local UTF-8 text file with 1-indexed line numbers.",
8131
8164
  inputSchema: codexReadFileInputSchema,
8132
8165
  execute: async (input) => readFileCodex(input, options)
8133
8166
  });
@@ -8146,6 +8179,13 @@ function createGrepFilesTool(options = {}) {
8146
8179
  execute: async (input) => grepFilesCodex(input, options)
8147
8180
  });
8148
8181
  }
8182
+ function createViewImageTool(options = {}) {
8183
+ return tool({
8184
+ description: "View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags).",
8185
+ inputSchema: codexViewImageInputSchema,
8186
+ execute: async (input) => viewImageCodex(input, options)
8187
+ });
8188
+ }
8149
8189
  function createGeminiReadFileTool(options = {}) {
8150
8190
  return tool({
8151
8191
  description: "Reads and returns the content of a specified file. Supports optional 0-based line offset and line limit.",
@@ -8153,13 +8193,6 @@ function createGeminiReadFileTool(options = {}) {
8153
8193
  execute: async (input) => readFileGemini(input, options)
8154
8194
  });
8155
8195
  }
8156
- function createReadFilesTool(options = {}) {
8157
- return tool({
8158
- description: "Reads one or more files with optional line-based or character-based slicing, similar to a controlled head/tail view.",
8159
- inputSchema: geminiReadFilesInputSchema,
8160
- execute: async (input) => readFilesGemini(input, options)
8161
- });
8162
- }
8163
8196
  function createWriteFileTool(options = {}) {
8164
8197
  return tool({
8165
8198
  description: "Writes content to a specified file in the local filesystem.",
@@ -8211,44 +8244,37 @@ async function readFileCodex(input, options) {
8211
8244
  action: "read",
8212
8245
  path: filePath
8213
8246
  });
8214
- const content = await runtime.filesystem.readTextFile(filePath);
8247
+ const fileBytes = await readBinaryFile(runtime.filesystem, filePath);
8248
+ const imageMimeType = detectImageMimeType(fileBytes, filePath);
8249
+ if (imageMimeType) {
8250
+ throw new Error(
8251
+ `read_file only supports text files; "${toDisplayPath2(filePath, runtime.cwd)}" is an image (${imageMimeType}). Use view_image instead.`
8252
+ );
8253
+ }
8254
+ if (isPdfFile(fileBytes, filePath)) {
8255
+ throw new Error(
8256
+ `read_file only supports text files; "${toDisplayPath2(filePath, runtime.cwd)}" is a PDF.`
8257
+ );
8258
+ }
8259
+ if (!isValidUtf8(fileBytes)) {
8260
+ throw new Error(
8261
+ `read_file only supports UTF-8 text files; "${toDisplayPath2(filePath, runtime.cwd)}" appears to be binary.`
8262
+ );
8263
+ }
8264
+ const content = fileBytes.toString("utf8");
8215
8265
  const lines = splitLines(content);
8216
8266
  const offset = input.offset ?? 1;
8217
8267
  const limit = input.limit ?? DEFAULT_READ_FILE_LINE_LIMIT;
8218
- const mode = input.mode ?? "slice";
8219
8268
  if (offset > lines.length) {
8220
8269
  throw new Error("offset exceeds file length");
8221
8270
  }
8222
- if (mode === "slice") {
8223
- const output = [];
8224
- const lastLine = Math.min(lines.length, offset + limit - 1);
8225
- for (let lineNumber = offset; lineNumber <= lastLine; lineNumber += 1) {
8226
- const line = lines[lineNumber - 1] ?? "";
8227
- output.push(`L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`);
8228
- }
8229
- return output.join("\n");
8230
- }
8231
- const indentation = input.indentation ?? {};
8232
- const anchorLine = indentation.anchor_line ?? offset;
8233
- if (anchorLine < 1 || anchorLine > lines.length) {
8234
- throw new Error("anchor_line exceeds file length");
8235
- }
8236
- const records = lines.map((line, index) => ({
8237
- number: index + 1,
8238
- raw: line,
8239
- display: truncateAtCodePointBoundary(line, runtime.maxLineLength),
8240
- indent: measureIndent(line, DEFAULT_TAB_WIDTH)
8241
- }));
8242
- const selected = readWithIndentationMode({
8243
- records,
8244
- anchorLine,
8245
- limit,
8246
- maxLevels: indentation.max_levels ?? 0,
8247
- includeSiblings: indentation.include_siblings ?? false,
8248
- includeHeader: indentation.include_header ?? true,
8249
- maxLines: indentation.max_lines ?? void 0
8250
- });
8251
- return selected.map((record) => `L${record.number}: ${record.display}`).join("\n");
8271
+ const output = [];
8272
+ const lastLine = Math.min(lines.length, offset + limit - 1);
8273
+ for (let lineNumber = offset; lineNumber <= lastLine; lineNumber += 1) {
8274
+ const line = lines[lineNumber - 1] ?? "";
8275
+ output.push(`L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`);
8276
+ }
8277
+ return output.join("\n");
8252
8278
  }
8253
8279
  async function listDirectoryCodex(input, options) {
8254
8280
  const runtime = resolveRuntime(options);
@@ -8336,6 +8362,85 @@ async function grepFilesCodex(input, options) {
8336
8362
  const limit = Math.min(input.limit ?? DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
8337
8363
  return matches.slice(0, limit).map((match) => match.filePath).join("\n");
8338
8364
  }
8365
+ async function viewImageCodex(input, options) {
8366
+ const runtime = resolveRuntime(options);
8367
+ const imagePath = resolvePathWithPolicy(input.path, runtime.cwd, runtime.allowOutsideCwd);
8368
+ await runAccessHook2(runtime, {
8369
+ cwd: runtime.cwd,
8370
+ tool: "view_image",
8371
+ action: "read",
8372
+ path: imagePath
8373
+ });
8374
+ const stats = await runtime.filesystem.stat(imagePath);
8375
+ if (stats.kind !== "file") {
8376
+ throw new Error(`image path \`${toDisplayPath2(imagePath, runtime.cwd)}\` is not a file`);
8377
+ }
8378
+ const bytes = await readBinaryFile(runtime.filesystem, imagePath);
8379
+ if (bytes.byteLength > MAX_VIEW_IMAGE_BYTES) {
8380
+ return [
8381
+ {
8382
+ type: "input_text",
8383
+ text: `Codex cannot attach image at \`${toDisplayPath2(imagePath, runtime.cwd)}\`: image exceeds ${MAX_VIEW_IMAGE_BYTES} bytes.`
8384
+ }
8385
+ ];
8386
+ }
8387
+ const mimeType = detectImageMimeType(bytes, imagePath);
8388
+ if (!mimeType) {
8389
+ return [
8390
+ {
8391
+ type: "input_text",
8392
+ text: `Codex cannot attach image at \`${toDisplayPath2(imagePath, runtime.cwd)}\`: unsupported image format.`
8393
+ }
8394
+ ];
8395
+ }
8396
+ return [
8397
+ {
8398
+ type: "input_image",
8399
+ image_url: `data:${mimeType};base64,${bytes.toString("base64")}`
8400
+ }
8401
+ ];
8402
+ }
8403
+ async function readBinaryFile(filesystem, filePath) {
8404
+ if (typeof filesystem.readBinaryFile === "function") {
8405
+ return await filesystem.readBinaryFile(filePath);
8406
+ }
8407
+ const text = await filesystem.readTextFile(filePath);
8408
+ return import_node_buffer3.Buffer.from(text, "utf8");
8409
+ }
8410
+ function detectImageMimeType(buffer, filePath) {
8411
+ if (buffer.length >= 8 && buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10) {
8412
+ return "image/png";
8413
+ }
8414
+ if (buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) {
8415
+ return "image/jpeg";
8416
+ }
8417
+ if (buffer.length >= 6) {
8418
+ const signature = buffer.subarray(0, 6).toString("ascii");
8419
+ if (signature === "GIF87a" || signature === "GIF89a") {
8420
+ return "image/gif";
8421
+ }
8422
+ }
8423
+ if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
8424
+ return "image/webp";
8425
+ }
8426
+ const fromExtension = IMAGE_MIME_BY_EXTENSION[import_node_path5.default.extname(filePath).toLowerCase()];
8427
+ if (fromExtension && SUPPORTED_IMAGE_MIME_TYPES.has(fromExtension)) {
8428
+ return fromExtension;
8429
+ }
8430
+ return void 0;
8431
+ }
8432
+ function isPdfFile(buffer, filePath) {
8433
+ if (buffer.length >= 5 && buffer.subarray(0, 5).toString("ascii") === "%PDF-") {
8434
+ return true;
8435
+ }
8436
+ return import_node_path5.default.extname(filePath).toLowerCase() === ".pdf";
8437
+ }
8438
+ function isValidUtf8(buffer) {
8439
+ if (buffer.length === 0) {
8440
+ return true;
8441
+ }
8442
+ return import_node_buffer3.Buffer.from(buffer.toString("utf8"), "utf8").equals(buffer);
8443
+ }
8339
8444
  async function readFileGemini(input, options) {
8340
8445
  const runtime = resolveRuntime(options);
8341
8446
  const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
@@ -8357,56 +8462,6 @@ async function readFileGemini(input, options) {
8357
8462
  (line, index) => `L${offset + index + 1}: ${truncateAtCodePointBoundary(line ?? "", runtime.maxLineLength)}`
8358
8463
  ).join("\n");
8359
8464
  }
8360
- async function readFilesGemini(input, options) {
8361
- const runtime = resolveRuntime(options);
8362
- const useCharWindow = input.char_offset !== void 0 || input.char_limit !== void 0;
8363
- const lineOffset = Math.max(0, input.line_offset ?? 0);
8364
- const lineLimit = input.line_limit ?? DEFAULT_READ_FILES_LINE_LIMIT;
8365
- const charOffset = Math.max(0, input.char_offset ?? 0);
8366
- const charLimit = input.char_limit ?? DEFAULT_READ_FILES_CHAR_LIMIT;
8367
- const includeLineNumbers = input.include_line_numbers !== false;
8368
- const sections = [];
8369
- for (const rawPath of input.paths) {
8370
- const filePath = resolvePathWithPolicy(rawPath, runtime.cwd, runtime.allowOutsideCwd);
8371
- await runAccessHook2(runtime, {
8372
- cwd: runtime.cwd,
8373
- tool: "read_files",
8374
- action: "read",
8375
- path: filePath
8376
- });
8377
- const content = await runtime.filesystem.readTextFile(filePath);
8378
- const displayPath = normalizeSlashes(toDisplayPath2(filePath, runtime.cwd));
8379
- sections.push(`==> ${displayPath} <==`);
8380
- if (useCharWindow) {
8381
- if (charOffset >= content.length) {
8382
- sections.push("");
8383
- continue;
8384
- }
8385
- const end2 = Math.min(content.length, charOffset + charLimit);
8386
- sections.push(content.slice(charOffset, end2));
8387
- continue;
8388
- }
8389
- const lines = splitLines(content);
8390
- if (lineOffset >= lines.length) {
8391
- sections.push("");
8392
- continue;
8393
- }
8394
- const end = Math.min(lines.length, lineOffset + lineLimit);
8395
- const selected = lines.slice(lineOffset, end);
8396
- if (includeLineNumbers) {
8397
- for (let index = 0; index < selected.length; index += 1) {
8398
- const lineNumber = lineOffset + index + 1;
8399
- const line = selected[index] ?? "";
8400
- sections.push(
8401
- `L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`
8402
- );
8403
- }
8404
- continue;
8405
- }
8406
- sections.push(selected.join("\n"));
8407
- }
8408
- return sections.join("\n");
8409
- }
8410
8465
  async function writeFileGemini(input, options) {
8411
8466
  const runtime = resolveRuntime(options);
8412
8467
  const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
@@ -8715,117 +8770,6 @@ function truncateAtCodePointBoundary(value, maxLength) {
8715
8770
  }
8716
8771
  return Array.from(value).slice(0, maxLength).join("");
8717
8772
  }
8718
- function measureIndent(line, tabWidth) {
8719
- let count = 0;
8720
- for (const char of line) {
8721
- if (char === " ") {
8722
- count += 1;
8723
- continue;
8724
- }
8725
- if (char === " ") {
8726
- count += tabWidth;
8727
- continue;
8728
- }
8729
- break;
8730
- }
8731
- return count;
8732
- }
8733
- function computeEffectiveIndents(records) {
8734
- const effective = [];
8735
- let previous = 0;
8736
- for (const record of records) {
8737
- if (record.raw.trim().length === 0) {
8738
- effective.push(previous);
8739
- } else {
8740
- previous = record.indent;
8741
- effective.push(previous);
8742
- }
8743
- }
8744
- return effective;
8745
- }
8746
- function trimBoundaryBlankLines(records) {
8747
- while (records.length > 0 && records[0]?.raw.trim().length === 0) {
8748
- records.shift();
8749
- }
8750
- while (records.length > 0 && records[records.length - 1]?.raw.trim().length === 0) {
8751
- records.pop();
8752
- }
8753
- }
8754
- function isCommentLine(line) {
8755
- const trimmed = line.trim();
8756
- return trimmed.startsWith("#") || trimmed.startsWith("//") || trimmed.startsWith("--");
8757
- }
8758
- function readWithIndentationMode(params) {
8759
- const { records, anchorLine, limit, maxLevels, includeSiblings, includeHeader, maxLines } = params;
8760
- const anchorIndex = anchorLine - 1;
8761
- const effectiveIndents = computeEffectiveIndents(records);
8762
- const anchorIndent = effectiveIndents[anchorIndex] ?? 0;
8763
- const minIndent = maxLevels === 0 ? 0 : Math.max(anchorIndent - maxLevels * DEFAULT_TAB_WIDTH, 0);
8764
- const guardLimit = maxLines ?? limit;
8765
- const finalLimit = Math.min(limit, guardLimit, records.length);
8766
- if (finalLimit <= 1) {
8767
- return [records[anchorIndex]].filter((entry) => Boolean(entry));
8768
- }
8769
- let upper = anchorIndex - 1;
8770
- let lower = anchorIndex + 1;
8771
- let upperMinIndentHits = 0;
8772
- let lowerMinIndentHits = 0;
8773
- const output = [records[anchorIndex]].filter(
8774
- (entry) => Boolean(entry)
8775
- );
8776
- while (output.length < finalLimit) {
8777
- let progressed = 0;
8778
- if (upper >= 0) {
8779
- const candidate = records[upper];
8780
- const candidateIndent = effectiveIndents[upper] ?? 0;
8781
- if (candidate && candidateIndent >= minIndent) {
8782
- output.unshift(candidate);
8783
- progressed += 1;
8784
- upper -= 1;
8785
- if (candidateIndent === minIndent && !includeSiblings) {
8786
- const allowHeaderComment = includeHeader && isCommentLine(candidate.raw);
8787
- const canTakeLine = allowHeaderComment || upperMinIndentHits === 0;
8788
- if (canTakeLine) {
8789
- upperMinIndentHits += 1;
8790
- } else {
8791
- output.shift();
8792
- progressed -= 1;
8793
- upper = -1;
8794
- }
8795
- }
8796
- if (output.length >= finalLimit) {
8797
- break;
8798
- }
8799
- } else {
8800
- upper = -1;
8801
- }
8802
- }
8803
- if (lower < records.length) {
8804
- const candidate = records[lower];
8805
- const candidateIndent = effectiveIndents[lower] ?? 0;
8806
- if (candidate && candidateIndent >= minIndent) {
8807
- output.push(candidate);
8808
- progressed += 1;
8809
- lower += 1;
8810
- if (candidateIndent === minIndent && !includeSiblings) {
8811
- if (lowerMinIndentHits > 0) {
8812
- output.pop();
8813
- progressed -= 1;
8814
- lower = records.length;
8815
- }
8816
- lowerMinIndentHits += 1;
8817
- }
8818
- } else {
8819
- lower = records.length;
8820
- }
8821
- }
8822
- if (progressed === 0) {
8823
- break;
8824
- }
8825
- }
8826
- trimBoundaryBlankLines(output);
8827
- return output;
8828
- }
8829
8773
  async function collectDirectoryEntries(filesystem, rootPath, depth, maxLineLength) {
8830
8774
  const queue = [
8831
8775
  { path: rootPath, relativePrefix: "", remainingDepth: depth }
@@ -10144,10 +10088,10 @@ async function runCandidateEvolution(options) {
10144
10088
  createListDirectoryTool,
10145
10089
  createModelAgnosticFilesystemToolSet,
10146
10090
  createNodeAgentFilesystem,
10147
- createReadFilesTool,
10148
10091
  createReplaceTool,
10149
10092
  createRgSearchTool,
10150
10093
  createToolLoopSteeringChannel,
10094
+ createViewImageTool,
10151
10095
  createWriteFileTool,
10152
10096
  customTool,
10153
10097
  encodeChatGptAuthJson,