@ljoukov/llm 3.0.11 → 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,
@@ -2218,7 +2218,6 @@ function getGoogleAuthOptions(scopes) {
2218
2218
 
2219
2219
  // src/google/client.ts
2220
2220
  var GEMINI_TEXT_MODEL_IDS = [
2221
- "gemini-3-pro-preview",
2222
2221
  "gemini-3.1-pro-preview",
2223
2222
  "gemini-3-flash-preview",
2224
2223
  "gemini-2.5-pro",
@@ -4022,6 +4021,38 @@ function mergeToolOutput(value) {
4022
4021
  return JSON.stringify({ error: "Failed to serialize tool output", detail: message });
4023
4022
  }
4024
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
+ }
4025
4056
  function parseOpenAiToolArguments(raw) {
4026
4057
  const trimmed = raw.trim();
4027
4058
  if (trimmed.length === 0) {
@@ -4377,7 +4408,6 @@ function resolveGeminiThinkingConfig(modelId) {
4377
4408
  return void 0;
4378
4409
  }
4379
4410
  switch (modelId) {
4380
- case "gemini-3-pro-preview":
4381
4411
  case "gemini-3.1-pro-preview":
4382
4412
  return { includeThoughts: true };
4383
4413
  case "gemini-3-flash-preview":
@@ -5379,13 +5409,13 @@ async function runToolLoop(request) {
5379
5409
  toolOutputs.push({
5380
5410
  type: "custom_tool_call_output",
5381
5411
  call_id: entry.call.call_id,
5382
- output: mergeToolOutput(outputPayload)
5412
+ output: toOpenAiToolOutput(outputPayload)
5383
5413
  });
5384
5414
  } else {
5385
5415
  toolOutputs.push({
5386
5416
  type: "function_call_output",
5387
5417
  call_id: entry.call.call_id,
5388
- output: mergeToolOutput(outputPayload)
5418
+ output: toOpenAiToolOutput(outputPayload)
5389
5419
  });
5390
5420
  }
5391
5421
  }
@@ -5600,7 +5630,7 @@ async function runToolLoop(request) {
5600
5630
  toolOutputs.push({
5601
5631
  type: "custom_tool_call_output",
5602
5632
  call_id: entry.ids.callId,
5603
- output: mergeToolOutput(outputPayload)
5633
+ output: toOpenAiToolOutput(outputPayload)
5604
5634
  });
5605
5635
  } else {
5606
5636
  toolOutputs.push({
@@ -5614,7 +5644,7 @@ async function runToolLoop(request) {
5614
5644
  toolOutputs.push({
5615
5645
  type: "function_call_output",
5616
5646
  call_id: entry.ids.callId,
5617
- output: mergeToolOutput(outputPayload)
5647
+ output: toOpenAiToolOutput(outputPayload)
5618
5648
  });
5619
5649
  }
5620
5650
  }
@@ -7245,6 +7275,7 @@ function sleep2(ms) {
7245
7275
 
7246
7276
  // src/tools/filesystemTools.ts
7247
7277
  var import_node_path5 = __toESM(require("path"), 1);
7278
+ var import_node_buffer3 = require("buffer");
7248
7279
  var import_zod6 = require("zod");
7249
7280
 
7250
7281
  // src/tools/applyPatch.ts
@@ -7278,6 +7309,10 @@ var InMemoryAgentFilesystem = class {
7278
7309
  }
7279
7310
  return file.content;
7280
7311
  }
7312
+ async readBinaryFile(filePath) {
7313
+ const content = await this.readTextFile(filePath);
7314
+ return Buffer.from(content, "utf8");
7315
+ }
7281
7316
  async writeTextFile(filePath, content) {
7282
7317
  const absolutePath = import_node_path3.default.resolve(filePath);
7283
7318
  const parentPath = import_node_path3.default.dirname(absolutePath);
@@ -7390,6 +7425,7 @@ var InMemoryAgentFilesystem = class {
7390
7425
  function createNodeAgentFilesystem() {
7391
7426
  return {
7392
7427
  readTextFile: async (filePath) => import_node_fs3.promises.readFile(filePath, "utf8"),
7428
+ readBinaryFile: async (filePath) => import_node_fs3.promises.readFile(filePath),
7393
7429
  writeTextFile: async (filePath, content) => import_node_fs3.promises.writeFile(filePath, content, "utf8"),
7394
7430
  deleteFile: async (filePath) => import_node_fs3.promises.unlink(filePath),
7395
7431
  ensureDir: async (directoryPath) => {
@@ -7945,30 +7981,28 @@ function formatSummary(added, modified, deleted) {
7945
7981
 
7946
7982
  // src/tools/filesystemTools.ts
7947
7983
  var DEFAULT_READ_FILE_LINE_LIMIT = 2e3;
7948
- var DEFAULT_READ_FILES_LINE_LIMIT = 200;
7949
- var DEFAULT_READ_FILES_CHAR_LIMIT = 4e3;
7950
7984
  var DEFAULT_LIST_DIR_LIMIT = 25;
7951
7985
  var DEFAULT_LIST_DIR_DEPTH = 2;
7952
7986
  var DEFAULT_GREP_LIMIT = 100;
7953
7987
  var MAX_GREP_LIMIT = 2e3;
7988
+ var MAX_VIEW_IMAGE_BYTES = 10 * 1024 * 1024;
7954
7989
  var DEFAULT_MAX_LINE_LENGTH = 500;
7955
7990
  var DEFAULT_GREP_MAX_SCANNED_FILES = 2e4;
7956
- 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
+ };
7957
7999
  var codexReadFileInputSchema = import_zod6.z.object({
7958
8000
  file_path: import_zod6.z.string().min(1).describe(
7959
8001
  "Path to the file (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
7960
8002
  ),
7961
8003
  offset: import_zod6.z.number().int().min(1).nullish().describe("The line number to start reading from. Must be 1 or greater."),
7962
- limit: import_zod6.z.number().int().min(1).nullish().describe("The maximum number of lines to return."),
7963
- mode: import_zod6.z.enum(["slice", "indentation"]).nullish().describe('Optional mode selector: "slice" (default) or "indentation".'),
7964
- indentation: import_zod6.z.object({
7965
- anchor_line: import_zod6.z.number().int().min(1).nullish(),
7966
- max_levels: import_zod6.z.number().int().min(0).nullish(),
7967
- include_siblings: import_zod6.z.boolean().nullish(),
7968
- include_header: import_zod6.z.boolean().nullish(),
7969
- max_lines: import_zod6.z.number().int().min(1).nullish()
7970
- }).nullish()
7971
- });
8004
+ limit: import_zod6.z.number().int().min(1).nullish().describe("The maximum number of lines to return.")
8005
+ }).strict();
7972
8006
  var codexListDirInputSchema = import_zod6.z.object({
7973
8007
  dir_path: import_zod6.z.string().min(1).describe(
7974
8008
  "Path to the directory to list (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
@@ -7983,6 +8017,9 @@ var codexGrepFilesInputSchema = import_zod6.z.object({
7983
8017
  path: import_zod6.z.string().nullish().describe("Directory or file path to search. Defaults to cwd."),
7984
8018
  limit: import_zod6.z.number().int().min(1).nullish().describe("Maximum number of file paths to return (defaults to 100).")
7985
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
+ });
7986
8023
  var applyPatchInputSchema = import_zod6.z.object({
7987
8024
  input: import_zod6.z.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
7988
8025
  });
@@ -7990,24 +8027,7 @@ var geminiReadFileInputSchema = import_zod6.z.object({
7990
8027
  file_path: import_zod6.z.string().min(1),
7991
8028
  offset: import_zod6.z.number().int().min(0).nullish(),
7992
8029
  limit: import_zod6.z.number().int().min(1).nullish()
7993
- });
7994
- var geminiReadFilesInputSchema = import_zod6.z.object({
7995
- paths: import_zod6.z.array(import_zod6.z.string().min(1)).min(1),
7996
- line_offset: import_zod6.z.number().int().min(0).nullish(),
7997
- line_limit: import_zod6.z.number().int().min(1).nullish(),
7998
- char_offset: import_zod6.z.number().int().min(0).nullish(),
7999
- char_limit: import_zod6.z.number().int().min(1).nullish(),
8000
- include_line_numbers: import_zod6.z.boolean().nullish()
8001
- }).superRefine((value, context) => {
8002
- const hasLineWindow = value.line_offset !== void 0 || value.line_limit !== void 0;
8003
- const hasCharWindow = value.char_offset !== void 0 || value.char_limit !== void 0;
8004
- if (hasLineWindow && hasCharWindow) {
8005
- context.addIssue({
8006
- code: import_zod6.z.ZodIssueCode.custom,
8007
- message: "Use either line_* or char_* window arguments, not both."
8008
- });
8009
- }
8010
- });
8030
+ }).strict();
8011
8031
  var geminiWriteFileInputSchema = import_zod6.z.object({
8012
8032
  file_path: import_zod6.z.string().min(1),
8013
8033
  content: import_zod6.z.string()
@@ -8090,7 +8110,8 @@ function createCodexFilesystemToolSet(options = {}) {
8090
8110
  apply_patch: createCodexApplyPatchTool(options),
8091
8111
  read_file: createCodexReadFileTool(options),
8092
8112
  list_dir: createListDirTool(options),
8093
- grep_files: createGrepFilesTool(options)
8113
+ grep_files: createGrepFilesTool(options),
8114
+ view_image: createViewImageTool(options)
8094
8115
  };
8095
8116
  }
8096
8117
  function createGeminiFilesystemToolSet(options = {}) {
@@ -8139,7 +8160,7 @@ function createCodexApplyPatchTool(options = {}) {
8139
8160
  }
8140
8161
  function createCodexReadFileTool(options = {}) {
8141
8162
  return tool({
8142
- 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.",
8143
8164
  inputSchema: codexReadFileInputSchema,
8144
8165
  execute: async (input) => readFileCodex(input, options)
8145
8166
  });
@@ -8158,6 +8179,13 @@ function createGrepFilesTool(options = {}) {
8158
8179
  execute: async (input) => grepFilesCodex(input, options)
8159
8180
  });
8160
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
+ }
8161
8189
  function createGeminiReadFileTool(options = {}) {
8162
8190
  return tool({
8163
8191
  description: "Reads and returns the content of a specified file. Supports optional 0-based line offset and line limit.",
@@ -8165,13 +8193,6 @@ function createGeminiReadFileTool(options = {}) {
8165
8193
  execute: async (input) => readFileGemini(input, options)
8166
8194
  });
8167
8195
  }
8168
- function createReadFilesTool(options = {}) {
8169
- return tool({
8170
- description: "Reads one or more files with optional line-based or character-based slicing, similar to a controlled head/tail view.",
8171
- inputSchema: geminiReadFilesInputSchema,
8172
- execute: async (input) => readFilesGemini(input, options)
8173
- });
8174
- }
8175
8196
  function createWriteFileTool(options = {}) {
8176
8197
  return tool({
8177
8198
  description: "Writes content to a specified file in the local filesystem.",
@@ -8223,44 +8244,37 @@ async function readFileCodex(input, options) {
8223
8244
  action: "read",
8224
8245
  path: filePath
8225
8246
  });
8226
- 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");
8227
8265
  const lines = splitLines(content);
8228
8266
  const offset = input.offset ?? 1;
8229
8267
  const limit = input.limit ?? DEFAULT_READ_FILE_LINE_LIMIT;
8230
- const mode = input.mode ?? "slice";
8231
8268
  if (offset > lines.length) {
8232
8269
  throw new Error("offset exceeds file length");
8233
8270
  }
8234
- if (mode === "slice") {
8235
- const output = [];
8236
- const lastLine = Math.min(lines.length, offset + limit - 1);
8237
- for (let lineNumber = offset; lineNumber <= lastLine; lineNumber += 1) {
8238
- const line = lines[lineNumber - 1] ?? "";
8239
- output.push(`L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`);
8240
- }
8241
- return output.join("\n");
8242
- }
8243
- const indentation = input.indentation ?? {};
8244
- const anchorLine = indentation.anchor_line ?? offset;
8245
- if (anchorLine < 1 || anchorLine > lines.length) {
8246
- throw new Error("anchor_line exceeds file length");
8247
- }
8248
- const records = lines.map((line, index) => ({
8249
- number: index + 1,
8250
- raw: line,
8251
- display: truncateAtCodePointBoundary(line, runtime.maxLineLength),
8252
- indent: measureIndent(line, DEFAULT_TAB_WIDTH)
8253
- }));
8254
- const selected = readWithIndentationMode({
8255
- records,
8256
- anchorLine,
8257
- limit,
8258
- maxLevels: indentation.max_levels ?? 0,
8259
- includeSiblings: indentation.include_siblings ?? false,
8260
- includeHeader: indentation.include_header ?? true,
8261
- maxLines: indentation.max_lines ?? void 0
8262
- });
8263
- 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");
8264
8278
  }
8265
8279
  async function listDirectoryCodex(input, options) {
8266
8280
  const runtime = resolveRuntime(options);
@@ -8348,6 +8362,85 @@ async function grepFilesCodex(input, options) {
8348
8362
  const limit = Math.min(input.limit ?? DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
8349
8363
  return matches.slice(0, limit).map((match) => match.filePath).join("\n");
8350
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
+ }
8351
8444
  async function readFileGemini(input, options) {
8352
8445
  const runtime = resolveRuntime(options);
8353
8446
  const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
@@ -8369,56 +8462,6 @@ async function readFileGemini(input, options) {
8369
8462
  (line, index) => `L${offset + index + 1}: ${truncateAtCodePointBoundary(line ?? "", runtime.maxLineLength)}`
8370
8463
  ).join("\n");
8371
8464
  }
8372
- async function readFilesGemini(input, options) {
8373
- const runtime = resolveRuntime(options);
8374
- const useCharWindow = input.char_offset !== void 0 || input.char_limit !== void 0;
8375
- const lineOffset = Math.max(0, input.line_offset ?? 0);
8376
- const lineLimit = input.line_limit ?? DEFAULT_READ_FILES_LINE_LIMIT;
8377
- const charOffset = Math.max(0, input.char_offset ?? 0);
8378
- const charLimit = input.char_limit ?? DEFAULT_READ_FILES_CHAR_LIMIT;
8379
- const includeLineNumbers = input.include_line_numbers !== false;
8380
- const sections = [];
8381
- for (const rawPath of input.paths) {
8382
- const filePath = resolvePathWithPolicy(rawPath, runtime.cwd, runtime.allowOutsideCwd);
8383
- await runAccessHook2(runtime, {
8384
- cwd: runtime.cwd,
8385
- tool: "read_files",
8386
- action: "read",
8387
- path: filePath
8388
- });
8389
- const content = await runtime.filesystem.readTextFile(filePath);
8390
- const displayPath = normalizeSlashes(toDisplayPath2(filePath, runtime.cwd));
8391
- sections.push(`==> ${displayPath} <==`);
8392
- if (useCharWindow) {
8393
- if (charOffset >= content.length) {
8394
- sections.push("");
8395
- continue;
8396
- }
8397
- const end2 = Math.min(content.length, charOffset + charLimit);
8398
- sections.push(content.slice(charOffset, end2));
8399
- continue;
8400
- }
8401
- const lines = splitLines(content);
8402
- if (lineOffset >= lines.length) {
8403
- sections.push("");
8404
- continue;
8405
- }
8406
- const end = Math.min(lines.length, lineOffset + lineLimit);
8407
- const selected = lines.slice(lineOffset, end);
8408
- if (includeLineNumbers) {
8409
- for (let index = 0; index < selected.length; index += 1) {
8410
- const lineNumber = lineOffset + index + 1;
8411
- const line = selected[index] ?? "";
8412
- sections.push(
8413
- `L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`
8414
- );
8415
- }
8416
- continue;
8417
- }
8418
- sections.push(selected.join("\n"));
8419
- }
8420
- return sections.join("\n");
8421
- }
8422
8465
  async function writeFileGemini(input, options) {
8423
8466
  const runtime = resolveRuntime(options);
8424
8467
  const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
@@ -8727,117 +8770,6 @@ function truncateAtCodePointBoundary(value, maxLength) {
8727
8770
  }
8728
8771
  return Array.from(value).slice(0, maxLength).join("");
8729
8772
  }
8730
- function measureIndent(line, tabWidth) {
8731
- let count = 0;
8732
- for (const char of line) {
8733
- if (char === " ") {
8734
- count += 1;
8735
- continue;
8736
- }
8737
- if (char === " ") {
8738
- count += tabWidth;
8739
- continue;
8740
- }
8741
- break;
8742
- }
8743
- return count;
8744
- }
8745
- function computeEffectiveIndents(records) {
8746
- const effective = [];
8747
- let previous = 0;
8748
- for (const record of records) {
8749
- if (record.raw.trim().length === 0) {
8750
- effective.push(previous);
8751
- } else {
8752
- previous = record.indent;
8753
- effective.push(previous);
8754
- }
8755
- }
8756
- return effective;
8757
- }
8758
- function trimBoundaryBlankLines(records) {
8759
- while (records.length > 0 && records[0]?.raw.trim().length === 0) {
8760
- records.shift();
8761
- }
8762
- while (records.length > 0 && records[records.length - 1]?.raw.trim().length === 0) {
8763
- records.pop();
8764
- }
8765
- }
8766
- function isCommentLine(line) {
8767
- const trimmed = line.trim();
8768
- return trimmed.startsWith("#") || trimmed.startsWith("//") || trimmed.startsWith("--");
8769
- }
8770
- function readWithIndentationMode(params) {
8771
- const { records, anchorLine, limit, maxLevels, includeSiblings, includeHeader, maxLines } = params;
8772
- const anchorIndex = anchorLine - 1;
8773
- const effectiveIndents = computeEffectiveIndents(records);
8774
- const anchorIndent = effectiveIndents[anchorIndex] ?? 0;
8775
- const minIndent = maxLevels === 0 ? 0 : Math.max(anchorIndent - maxLevels * DEFAULT_TAB_WIDTH, 0);
8776
- const guardLimit = maxLines ?? limit;
8777
- const finalLimit = Math.min(limit, guardLimit, records.length);
8778
- if (finalLimit <= 1) {
8779
- return [records[anchorIndex]].filter((entry) => Boolean(entry));
8780
- }
8781
- let upper = anchorIndex - 1;
8782
- let lower = anchorIndex + 1;
8783
- let upperMinIndentHits = 0;
8784
- let lowerMinIndentHits = 0;
8785
- const output = [records[anchorIndex]].filter(
8786
- (entry) => Boolean(entry)
8787
- );
8788
- while (output.length < finalLimit) {
8789
- let progressed = 0;
8790
- if (upper >= 0) {
8791
- const candidate = records[upper];
8792
- const candidateIndent = effectiveIndents[upper] ?? 0;
8793
- if (candidate && candidateIndent >= minIndent) {
8794
- output.unshift(candidate);
8795
- progressed += 1;
8796
- upper -= 1;
8797
- if (candidateIndent === minIndent && !includeSiblings) {
8798
- const allowHeaderComment = includeHeader && isCommentLine(candidate.raw);
8799
- const canTakeLine = allowHeaderComment || upperMinIndentHits === 0;
8800
- if (canTakeLine) {
8801
- upperMinIndentHits += 1;
8802
- } else {
8803
- output.shift();
8804
- progressed -= 1;
8805
- upper = -1;
8806
- }
8807
- }
8808
- if (output.length >= finalLimit) {
8809
- break;
8810
- }
8811
- } else {
8812
- upper = -1;
8813
- }
8814
- }
8815
- if (lower < records.length) {
8816
- const candidate = records[lower];
8817
- const candidateIndent = effectiveIndents[lower] ?? 0;
8818
- if (candidate && candidateIndent >= minIndent) {
8819
- output.push(candidate);
8820
- progressed += 1;
8821
- lower += 1;
8822
- if (candidateIndent === minIndent && !includeSiblings) {
8823
- if (lowerMinIndentHits > 0) {
8824
- output.pop();
8825
- progressed -= 1;
8826
- lower = records.length;
8827
- }
8828
- lowerMinIndentHits += 1;
8829
- }
8830
- } else {
8831
- lower = records.length;
8832
- }
8833
- }
8834
- if (progressed === 0) {
8835
- break;
8836
- }
8837
- }
8838
- trimBoundaryBlankLines(output);
8839
- return output;
8840
- }
8841
8773
  async function collectDirectoryEntries(filesystem, rootPath, depth, maxLineLength) {
8842
8774
  const queue = [
8843
8775
  { path: rootPath, relativePrefix: "", remainingDepth: depth }
@@ -10156,10 +10088,10 @@ async function runCandidateEvolution(options) {
10156
10088
  createListDirectoryTool,
10157
10089
  createModelAgnosticFilesystemToolSet,
10158
10090
  createNodeAgentFilesystem,
10159
- createReadFilesTool,
10160
10091
  createReplaceTool,
10161
10092
  createRgSearchTool,
10162
10093
  createToolLoopSteeringChannel,
10094
+ createViewImageTool,
10163
10095
  createWriteFileTool,
10164
10096
  customTool,
10165
10097
  encodeChatGptAuthJson,