@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.js CHANGED
@@ -121,6 +121,15 @@ var GEMINI_2_5_PRO_PRICING = {
121
121
  outputRateLow: 10 / 1e6,
122
122
  outputRateHigh: 15 / 1e6
123
123
  };
124
+ var GEMINI_2_5_FLASH_PRICING = {
125
+ threshold: 2e5,
126
+ inputRateLow: 0.3 / 1e6,
127
+ inputRateHigh: 0.3 / 1e6,
128
+ cachedRateLow: 0.03 / 1e6,
129
+ cachedRateHigh: 0.03 / 1e6,
130
+ outputRateLow: 2.5 / 1e6,
131
+ outputRateHigh: 2.5 / 1e6
132
+ };
124
133
  var GEMINI_IMAGE_PREVIEW_PRICING = {
125
134
  inputRate: 2 / 1e6,
126
135
  cachedRate: 0.2 / 1e6,
@@ -136,6 +145,9 @@ function getGeminiProPricing(modelId) {
136
145
  if (modelId.includes("gemini-2.5-pro")) {
137
146
  return GEMINI_2_5_PRO_PRICING;
138
147
  }
148
+ if (modelId.includes("gemini-2.5-flash") || modelId.includes("gemini-flash-latest")) {
149
+ return GEMINI_2_5_FLASH_PRICING;
150
+ }
139
151
  if (modelId.includes("gemini-3-pro") || modelId.includes("gemini-3.1-pro")) {
140
152
  return GEMINI_3_PRO_PREVIEW_PRICING;
141
153
  }
@@ -2093,7 +2105,6 @@ function getGoogleAuthOptions(scopes) {
2093
2105
 
2094
2106
  // src/google/client.ts
2095
2107
  var GEMINI_TEXT_MODEL_IDS = [
2096
- "gemini-3-pro-preview",
2097
2108
  "gemini-3.1-pro-preview",
2098
2109
  "gemini-3-flash-preview",
2099
2110
  "gemini-2.5-pro",
@@ -3897,6 +3908,38 @@ function mergeToolOutput(value) {
3897
3908
  return JSON.stringify({ error: "Failed to serialize tool output", detail: message });
3898
3909
  }
3899
3910
  }
3911
+ function isLlmToolOutputContentItem(value) {
3912
+ if (!isPlainRecord(value)) {
3913
+ return false;
3914
+ }
3915
+ const itemType = typeof value.type === "string" ? value.type : "";
3916
+ if (itemType === "input_text") {
3917
+ return typeof value.text === "string";
3918
+ }
3919
+ if (itemType === "input_image") {
3920
+ return typeof value.image_url === "string";
3921
+ }
3922
+ if (itemType === "input_file") {
3923
+ const keys = ["file_data", "file_id", "file_url", "filename"];
3924
+ for (const key of keys) {
3925
+ const part = value[key];
3926
+ if (part !== void 0 && part !== null && typeof part !== "string") {
3927
+ return false;
3928
+ }
3929
+ }
3930
+ return true;
3931
+ }
3932
+ return false;
3933
+ }
3934
+ function toOpenAiToolOutput(value) {
3935
+ if (isLlmToolOutputContentItem(value)) {
3936
+ return [value];
3937
+ }
3938
+ if (Array.isArray(value) && value.every((item) => isLlmToolOutputContentItem(item))) {
3939
+ return value;
3940
+ }
3941
+ return mergeToolOutput(value);
3942
+ }
3900
3943
  function parseOpenAiToolArguments(raw) {
3901
3944
  const trimmed = raw.trim();
3902
3945
  if (trimmed.length === 0) {
@@ -4252,7 +4295,6 @@ function resolveGeminiThinkingConfig(modelId) {
4252
4295
  return void 0;
4253
4296
  }
4254
4297
  switch (modelId) {
4255
- case "gemini-3-pro-preview":
4256
4298
  case "gemini-3.1-pro-preview":
4257
4299
  return { includeThoughts: true };
4258
4300
  case "gemini-3-flash-preview":
@@ -5254,13 +5296,13 @@ async function runToolLoop(request) {
5254
5296
  toolOutputs.push({
5255
5297
  type: "custom_tool_call_output",
5256
5298
  call_id: entry.call.call_id,
5257
- output: mergeToolOutput(outputPayload)
5299
+ output: toOpenAiToolOutput(outputPayload)
5258
5300
  });
5259
5301
  } else {
5260
5302
  toolOutputs.push({
5261
5303
  type: "function_call_output",
5262
5304
  call_id: entry.call.call_id,
5263
- output: mergeToolOutput(outputPayload)
5305
+ output: toOpenAiToolOutput(outputPayload)
5264
5306
  });
5265
5307
  }
5266
5308
  }
@@ -5475,7 +5517,7 @@ async function runToolLoop(request) {
5475
5517
  toolOutputs.push({
5476
5518
  type: "custom_tool_call_output",
5477
5519
  call_id: entry.ids.callId,
5478
- output: mergeToolOutput(outputPayload)
5520
+ output: toOpenAiToolOutput(outputPayload)
5479
5521
  });
5480
5522
  } else {
5481
5523
  toolOutputs.push({
@@ -5489,7 +5531,7 @@ async function runToolLoop(request) {
5489
5531
  toolOutputs.push({
5490
5532
  type: "function_call_output",
5491
5533
  call_id: entry.ids.callId,
5492
- output: mergeToolOutput(outputPayload)
5534
+ output: toOpenAiToolOutput(outputPayload)
5493
5535
  });
5494
5536
  }
5495
5537
  }
@@ -7120,6 +7162,7 @@ function sleep2(ms) {
7120
7162
 
7121
7163
  // src/tools/filesystemTools.ts
7122
7164
  import path5 from "path";
7165
+ import { Buffer as Buffer4 } from "buffer";
7123
7166
  import { z as z6 } from "zod";
7124
7167
 
7125
7168
  // src/tools/applyPatch.ts
@@ -7153,6 +7196,10 @@ var InMemoryAgentFilesystem = class {
7153
7196
  }
7154
7197
  return file.content;
7155
7198
  }
7199
+ async readBinaryFile(filePath) {
7200
+ const content = await this.readTextFile(filePath);
7201
+ return Buffer.from(content, "utf8");
7202
+ }
7156
7203
  async writeTextFile(filePath, content) {
7157
7204
  const absolutePath = path3.resolve(filePath);
7158
7205
  const parentPath = path3.dirname(absolutePath);
@@ -7265,6 +7312,7 @@ var InMemoryAgentFilesystem = class {
7265
7312
  function createNodeAgentFilesystem() {
7266
7313
  return {
7267
7314
  readTextFile: async (filePath) => fs3.readFile(filePath, "utf8"),
7315
+ readBinaryFile: async (filePath) => fs3.readFile(filePath),
7268
7316
  writeTextFile: async (filePath, content) => fs3.writeFile(filePath, content, "utf8"),
7269
7317
  deleteFile: async (filePath) => fs3.unlink(filePath),
7270
7318
  ensureDir: async (directoryPath) => {
@@ -7820,30 +7868,28 @@ function formatSummary(added, modified, deleted) {
7820
7868
 
7821
7869
  // src/tools/filesystemTools.ts
7822
7870
  var DEFAULT_READ_FILE_LINE_LIMIT = 2e3;
7823
- var DEFAULT_READ_FILES_LINE_LIMIT = 200;
7824
- var DEFAULT_READ_FILES_CHAR_LIMIT = 4e3;
7825
7871
  var DEFAULT_LIST_DIR_LIMIT = 25;
7826
7872
  var DEFAULT_LIST_DIR_DEPTH = 2;
7827
7873
  var DEFAULT_GREP_LIMIT = 100;
7828
7874
  var MAX_GREP_LIMIT = 2e3;
7875
+ var MAX_VIEW_IMAGE_BYTES = 10 * 1024 * 1024;
7829
7876
  var DEFAULT_MAX_LINE_LENGTH = 500;
7830
7877
  var DEFAULT_GREP_MAX_SCANNED_FILES = 2e4;
7831
- var DEFAULT_TAB_WIDTH = 4;
7878
+ var SUPPORTED_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
7879
+ var IMAGE_MIME_BY_EXTENSION = {
7880
+ ".png": "image/png",
7881
+ ".jpg": "image/jpeg",
7882
+ ".jpeg": "image/jpeg",
7883
+ ".webp": "image/webp",
7884
+ ".gif": "image/gif"
7885
+ };
7832
7886
  var codexReadFileInputSchema = z6.object({
7833
7887
  file_path: z6.string().min(1).describe(
7834
7888
  "Path to the file (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
7835
7889
  ),
7836
7890
  offset: z6.number().int().min(1).nullish().describe("The line number to start reading from. Must be 1 or greater."),
7837
- limit: z6.number().int().min(1).nullish().describe("The maximum number of lines to return."),
7838
- mode: z6.enum(["slice", "indentation"]).nullish().describe('Optional mode selector: "slice" (default) or "indentation".'),
7839
- indentation: z6.object({
7840
- anchor_line: z6.number().int().min(1).nullish(),
7841
- max_levels: z6.number().int().min(0).nullish(),
7842
- include_siblings: z6.boolean().nullish(),
7843
- include_header: z6.boolean().nullish(),
7844
- max_lines: z6.number().int().min(1).nullish()
7845
- }).nullish()
7846
- });
7891
+ limit: z6.number().int().min(1).nullish().describe("The maximum number of lines to return.")
7892
+ }).strict();
7847
7893
  var codexListDirInputSchema = z6.object({
7848
7894
  dir_path: z6.string().min(1).describe(
7849
7895
  "Path to the directory to list (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
@@ -7858,6 +7904,9 @@ var codexGrepFilesInputSchema = z6.object({
7858
7904
  path: z6.string().nullish().describe("Directory or file path to search. Defaults to cwd."),
7859
7905
  limit: z6.number().int().min(1).nullish().describe("Maximum number of file paths to return (defaults to 100).")
7860
7906
  });
7907
+ var codexViewImageInputSchema = z6.object({
7908
+ path: z6.string().min(1).describe("Local filesystem path to an image file")
7909
+ });
7861
7910
  var applyPatchInputSchema = z6.object({
7862
7911
  input: z6.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
7863
7912
  });
@@ -7865,24 +7914,7 @@ var geminiReadFileInputSchema = z6.object({
7865
7914
  file_path: z6.string().min(1),
7866
7915
  offset: z6.number().int().min(0).nullish(),
7867
7916
  limit: z6.number().int().min(1).nullish()
7868
- });
7869
- var geminiReadFilesInputSchema = z6.object({
7870
- paths: z6.array(z6.string().min(1)).min(1),
7871
- line_offset: z6.number().int().min(0).nullish(),
7872
- line_limit: z6.number().int().min(1).nullish(),
7873
- char_offset: z6.number().int().min(0).nullish(),
7874
- char_limit: z6.number().int().min(1).nullish(),
7875
- include_line_numbers: z6.boolean().nullish()
7876
- }).superRefine((value, context) => {
7877
- const hasLineWindow = value.line_offset !== void 0 || value.line_limit !== void 0;
7878
- const hasCharWindow = value.char_offset !== void 0 || value.char_limit !== void 0;
7879
- if (hasLineWindow && hasCharWindow) {
7880
- context.addIssue({
7881
- code: z6.ZodIssueCode.custom,
7882
- message: "Use either line_* or char_* window arguments, not both."
7883
- });
7884
- }
7885
- });
7917
+ }).strict();
7886
7918
  var geminiWriteFileInputSchema = z6.object({
7887
7919
  file_path: z6.string().min(1),
7888
7920
  content: z6.string()
@@ -7965,7 +7997,8 @@ function createCodexFilesystemToolSet(options = {}) {
7965
7997
  apply_patch: createCodexApplyPatchTool(options),
7966
7998
  read_file: createCodexReadFileTool(options),
7967
7999
  list_dir: createListDirTool(options),
7968
- grep_files: createGrepFilesTool(options)
8000
+ grep_files: createGrepFilesTool(options),
8001
+ view_image: createViewImageTool(options)
7969
8002
  };
7970
8003
  }
7971
8004
  function createGeminiFilesystemToolSet(options = {}) {
@@ -8014,7 +8047,7 @@ function createCodexApplyPatchTool(options = {}) {
8014
8047
  }
8015
8048
  function createCodexReadFileTool(options = {}) {
8016
8049
  return tool({
8017
- description: "Reads a local file with 1-indexed line numbers, supporting slice and indentation-aware block modes.",
8050
+ description: "Reads a local UTF-8 text file with 1-indexed line numbers.",
8018
8051
  inputSchema: codexReadFileInputSchema,
8019
8052
  execute: async (input) => readFileCodex(input, options)
8020
8053
  });
@@ -8033,6 +8066,13 @@ function createGrepFilesTool(options = {}) {
8033
8066
  execute: async (input) => grepFilesCodex(input, options)
8034
8067
  });
8035
8068
  }
8069
+ function createViewImageTool(options = {}) {
8070
+ return tool({
8071
+ 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).",
8072
+ inputSchema: codexViewImageInputSchema,
8073
+ execute: async (input) => viewImageCodex(input, options)
8074
+ });
8075
+ }
8036
8076
  function createGeminiReadFileTool(options = {}) {
8037
8077
  return tool({
8038
8078
  description: "Reads and returns the content of a specified file. Supports optional 0-based line offset and line limit.",
@@ -8040,13 +8080,6 @@ function createGeminiReadFileTool(options = {}) {
8040
8080
  execute: async (input) => readFileGemini(input, options)
8041
8081
  });
8042
8082
  }
8043
- function createReadFilesTool(options = {}) {
8044
- return tool({
8045
- description: "Reads one or more files with optional line-based or character-based slicing, similar to a controlled head/tail view.",
8046
- inputSchema: geminiReadFilesInputSchema,
8047
- execute: async (input) => readFilesGemini(input, options)
8048
- });
8049
- }
8050
8083
  function createWriteFileTool(options = {}) {
8051
8084
  return tool({
8052
8085
  description: "Writes content to a specified file in the local filesystem.",
@@ -8098,44 +8131,37 @@ async function readFileCodex(input, options) {
8098
8131
  action: "read",
8099
8132
  path: filePath
8100
8133
  });
8101
- const content = await runtime.filesystem.readTextFile(filePath);
8134
+ const fileBytes = await readBinaryFile(runtime.filesystem, filePath);
8135
+ const imageMimeType = detectImageMimeType(fileBytes, filePath);
8136
+ if (imageMimeType) {
8137
+ throw new Error(
8138
+ `read_file only supports text files; "${toDisplayPath2(filePath, runtime.cwd)}" is an image (${imageMimeType}). Use view_image instead.`
8139
+ );
8140
+ }
8141
+ if (isPdfFile(fileBytes, filePath)) {
8142
+ throw new Error(
8143
+ `read_file only supports text files; "${toDisplayPath2(filePath, runtime.cwd)}" is a PDF.`
8144
+ );
8145
+ }
8146
+ if (!isValidUtf8(fileBytes)) {
8147
+ throw new Error(
8148
+ `read_file only supports UTF-8 text files; "${toDisplayPath2(filePath, runtime.cwd)}" appears to be binary.`
8149
+ );
8150
+ }
8151
+ const content = fileBytes.toString("utf8");
8102
8152
  const lines = splitLines(content);
8103
8153
  const offset = input.offset ?? 1;
8104
8154
  const limit = input.limit ?? DEFAULT_READ_FILE_LINE_LIMIT;
8105
- const mode = input.mode ?? "slice";
8106
8155
  if (offset > lines.length) {
8107
8156
  throw new Error("offset exceeds file length");
8108
8157
  }
8109
- if (mode === "slice") {
8110
- const output = [];
8111
- const lastLine = Math.min(lines.length, offset + limit - 1);
8112
- for (let lineNumber = offset; lineNumber <= lastLine; lineNumber += 1) {
8113
- const line = lines[lineNumber - 1] ?? "";
8114
- output.push(`L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`);
8115
- }
8116
- return output.join("\n");
8117
- }
8118
- const indentation = input.indentation ?? {};
8119
- const anchorLine = indentation.anchor_line ?? offset;
8120
- if (anchorLine < 1 || anchorLine > lines.length) {
8121
- throw new Error("anchor_line exceeds file length");
8122
- }
8123
- const records = lines.map((line, index) => ({
8124
- number: index + 1,
8125
- raw: line,
8126
- display: truncateAtCodePointBoundary(line, runtime.maxLineLength),
8127
- indent: measureIndent(line, DEFAULT_TAB_WIDTH)
8128
- }));
8129
- const selected = readWithIndentationMode({
8130
- records,
8131
- anchorLine,
8132
- limit,
8133
- maxLevels: indentation.max_levels ?? 0,
8134
- includeSiblings: indentation.include_siblings ?? false,
8135
- includeHeader: indentation.include_header ?? true,
8136
- maxLines: indentation.max_lines ?? void 0
8137
- });
8138
- return selected.map((record) => `L${record.number}: ${record.display}`).join("\n");
8158
+ const output = [];
8159
+ const lastLine = Math.min(lines.length, offset + limit - 1);
8160
+ for (let lineNumber = offset; lineNumber <= lastLine; lineNumber += 1) {
8161
+ const line = lines[lineNumber - 1] ?? "";
8162
+ output.push(`L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`);
8163
+ }
8164
+ return output.join("\n");
8139
8165
  }
8140
8166
  async function listDirectoryCodex(input, options) {
8141
8167
  const runtime = resolveRuntime(options);
@@ -8223,6 +8249,85 @@ async function grepFilesCodex(input, options) {
8223
8249
  const limit = Math.min(input.limit ?? DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
8224
8250
  return matches.slice(0, limit).map((match) => match.filePath).join("\n");
8225
8251
  }
8252
+ async function viewImageCodex(input, options) {
8253
+ const runtime = resolveRuntime(options);
8254
+ const imagePath = resolvePathWithPolicy(input.path, runtime.cwd, runtime.allowOutsideCwd);
8255
+ await runAccessHook2(runtime, {
8256
+ cwd: runtime.cwd,
8257
+ tool: "view_image",
8258
+ action: "read",
8259
+ path: imagePath
8260
+ });
8261
+ const stats = await runtime.filesystem.stat(imagePath);
8262
+ if (stats.kind !== "file") {
8263
+ throw new Error(`image path \`${toDisplayPath2(imagePath, runtime.cwd)}\` is not a file`);
8264
+ }
8265
+ const bytes = await readBinaryFile(runtime.filesystem, imagePath);
8266
+ if (bytes.byteLength > MAX_VIEW_IMAGE_BYTES) {
8267
+ return [
8268
+ {
8269
+ type: "input_text",
8270
+ text: `Codex cannot attach image at \`${toDisplayPath2(imagePath, runtime.cwd)}\`: image exceeds ${MAX_VIEW_IMAGE_BYTES} bytes.`
8271
+ }
8272
+ ];
8273
+ }
8274
+ const mimeType = detectImageMimeType(bytes, imagePath);
8275
+ if (!mimeType) {
8276
+ return [
8277
+ {
8278
+ type: "input_text",
8279
+ text: `Codex cannot attach image at \`${toDisplayPath2(imagePath, runtime.cwd)}\`: unsupported image format.`
8280
+ }
8281
+ ];
8282
+ }
8283
+ return [
8284
+ {
8285
+ type: "input_image",
8286
+ image_url: `data:${mimeType};base64,${bytes.toString("base64")}`
8287
+ }
8288
+ ];
8289
+ }
8290
+ async function readBinaryFile(filesystem, filePath) {
8291
+ if (typeof filesystem.readBinaryFile === "function") {
8292
+ return await filesystem.readBinaryFile(filePath);
8293
+ }
8294
+ const text = await filesystem.readTextFile(filePath);
8295
+ return Buffer4.from(text, "utf8");
8296
+ }
8297
+ function detectImageMimeType(buffer, filePath) {
8298
+ 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) {
8299
+ return "image/png";
8300
+ }
8301
+ if (buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) {
8302
+ return "image/jpeg";
8303
+ }
8304
+ if (buffer.length >= 6) {
8305
+ const signature = buffer.subarray(0, 6).toString("ascii");
8306
+ if (signature === "GIF87a" || signature === "GIF89a") {
8307
+ return "image/gif";
8308
+ }
8309
+ }
8310
+ if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
8311
+ return "image/webp";
8312
+ }
8313
+ const fromExtension = IMAGE_MIME_BY_EXTENSION[path5.extname(filePath).toLowerCase()];
8314
+ if (fromExtension && SUPPORTED_IMAGE_MIME_TYPES.has(fromExtension)) {
8315
+ return fromExtension;
8316
+ }
8317
+ return void 0;
8318
+ }
8319
+ function isPdfFile(buffer, filePath) {
8320
+ if (buffer.length >= 5 && buffer.subarray(0, 5).toString("ascii") === "%PDF-") {
8321
+ return true;
8322
+ }
8323
+ return path5.extname(filePath).toLowerCase() === ".pdf";
8324
+ }
8325
+ function isValidUtf8(buffer) {
8326
+ if (buffer.length === 0) {
8327
+ return true;
8328
+ }
8329
+ return Buffer4.from(buffer.toString("utf8"), "utf8").equals(buffer);
8330
+ }
8226
8331
  async function readFileGemini(input, options) {
8227
8332
  const runtime = resolveRuntime(options);
8228
8333
  const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
@@ -8244,56 +8349,6 @@ async function readFileGemini(input, options) {
8244
8349
  (line, index) => `L${offset + index + 1}: ${truncateAtCodePointBoundary(line ?? "", runtime.maxLineLength)}`
8245
8350
  ).join("\n");
8246
8351
  }
8247
- async function readFilesGemini(input, options) {
8248
- const runtime = resolveRuntime(options);
8249
- const useCharWindow = input.char_offset !== void 0 || input.char_limit !== void 0;
8250
- const lineOffset = Math.max(0, input.line_offset ?? 0);
8251
- const lineLimit = input.line_limit ?? DEFAULT_READ_FILES_LINE_LIMIT;
8252
- const charOffset = Math.max(0, input.char_offset ?? 0);
8253
- const charLimit = input.char_limit ?? DEFAULT_READ_FILES_CHAR_LIMIT;
8254
- const includeLineNumbers = input.include_line_numbers !== false;
8255
- const sections = [];
8256
- for (const rawPath of input.paths) {
8257
- const filePath = resolvePathWithPolicy(rawPath, runtime.cwd, runtime.allowOutsideCwd);
8258
- await runAccessHook2(runtime, {
8259
- cwd: runtime.cwd,
8260
- tool: "read_files",
8261
- action: "read",
8262
- path: filePath
8263
- });
8264
- const content = await runtime.filesystem.readTextFile(filePath);
8265
- const displayPath = normalizeSlashes(toDisplayPath2(filePath, runtime.cwd));
8266
- sections.push(`==> ${displayPath} <==`);
8267
- if (useCharWindow) {
8268
- if (charOffset >= content.length) {
8269
- sections.push("");
8270
- continue;
8271
- }
8272
- const end2 = Math.min(content.length, charOffset + charLimit);
8273
- sections.push(content.slice(charOffset, end2));
8274
- continue;
8275
- }
8276
- const lines = splitLines(content);
8277
- if (lineOffset >= lines.length) {
8278
- sections.push("");
8279
- continue;
8280
- }
8281
- const end = Math.min(lines.length, lineOffset + lineLimit);
8282
- const selected = lines.slice(lineOffset, end);
8283
- if (includeLineNumbers) {
8284
- for (let index = 0; index < selected.length; index += 1) {
8285
- const lineNumber = lineOffset + index + 1;
8286
- const line = selected[index] ?? "";
8287
- sections.push(
8288
- `L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`
8289
- );
8290
- }
8291
- continue;
8292
- }
8293
- sections.push(selected.join("\n"));
8294
- }
8295
- return sections.join("\n");
8296
- }
8297
8352
  async function writeFileGemini(input, options) {
8298
8353
  const runtime = resolveRuntime(options);
8299
8354
  const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
@@ -8602,117 +8657,6 @@ function truncateAtCodePointBoundary(value, maxLength) {
8602
8657
  }
8603
8658
  return Array.from(value).slice(0, maxLength).join("");
8604
8659
  }
8605
- function measureIndent(line, tabWidth) {
8606
- let count = 0;
8607
- for (const char of line) {
8608
- if (char === " ") {
8609
- count += 1;
8610
- continue;
8611
- }
8612
- if (char === " ") {
8613
- count += tabWidth;
8614
- continue;
8615
- }
8616
- break;
8617
- }
8618
- return count;
8619
- }
8620
- function computeEffectiveIndents(records) {
8621
- const effective = [];
8622
- let previous = 0;
8623
- for (const record of records) {
8624
- if (record.raw.trim().length === 0) {
8625
- effective.push(previous);
8626
- } else {
8627
- previous = record.indent;
8628
- effective.push(previous);
8629
- }
8630
- }
8631
- return effective;
8632
- }
8633
- function trimBoundaryBlankLines(records) {
8634
- while (records.length > 0 && records[0]?.raw.trim().length === 0) {
8635
- records.shift();
8636
- }
8637
- while (records.length > 0 && records[records.length - 1]?.raw.trim().length === 0) {
8638
- records.pop();
8639
- }
8640
- }
8641
- function isCommentLine(line) {
8642
- const trimmed = line.trim();
8643
- return trimmed.startsWith("#") || trimmed.startsWith("//") || trimmed.startsWith("--");
8644
- }
8645
- function readWithIndentationMode(params) {
8646
- const { records, anchorLine, limit, maxLevels, includeSiblings, includeHeader, maxLines } = params;
8647
- const anchorIndex = anchorLine - 1;
8648
- const effectiveIndents = computeEffectiveIndents(records);
8649
- const anchorIndent = effectiveIndents[anchorIndex] ?? 0;
8650
- const minIndent = maxLevels === 0 ? 0 : Math.max(anchorIndent - maxLevels * DEFAULT_TAB_WIDTH, 0);
8651
- const guardLimit = maxLines ?? limit;
8652
- const finalLimit = Math.min(limit, guardLimit, records.length);
8653
- if (finalLimit <= 1) {
8654
- return [records[anchorIndex]].filter((entry) => Boolean(entry));
8655
- }
8656
- let upper = anchorIndex - 1;
8657
- let lower = anchorIndex + 1;
8658
- let upperMinIndentHits = 0;
8659
- let lowerMinIndentHits = 0;
8660
- const output = [records[anchorIndex]].filter(
8661
- (entry) => Boolean(entry)
8662
- );
8663
- while (output.length < finalLimit) {
8664
- let progressed = 0;
8665
- if (upper >= 0) {
8666
- const candidate = records[upper];
8667
- const candidateIndent = effectiveIndents[upper] ?? 0;
8668
- if (candidate && candidateIndent >= minIndent) {
8669
- output.unshift(candidate);
8670
- progressed += 1;
8671
- upper -= 1;
8672
- if (candidateIndent === minIndent && !includeSiblings) {
8673
- const allowHeaderComment = includeHeader && isCommentLine(candidate.raw);
8674
- const canTakeLine = allowHeaderComment || upperMinIndentHits === 0;
8675
- if (canTakeLine) {
8676
- upperMinIndentHits += 1;
8677
- } else {
8678
- output.shift();
8679
- progressed -= 1;
8680
- upper = -1;
8681
- }
8682
- }
8683
- if (output.length >= finalLimit) {
8684
- break;
8685
- }
8686
- } else {
8687
- upper = -1;
8688
- }
8689
- }
8690
- if (lower < records.length) {
8691
- const candidate = records[lower];
8692
- const candidateIndent = effectiveIndents[lower] ?? 0;
8693
- if (candidate && candidateIndent >= minIndent) {
8694
- output.push(candidate);
8695
- progressed += 1;
8696
- lower += 1;
8697
- if (candidateIndent === minIndent && !includeSiblings) {
8698
- if (lowerMinIndentHits > 0) {
8699
- output.pop();
8700
- progressed -= 1;
8701
- lower = records.length;
8702
- }
8703
- lowerMinIndentHits += 1;
8704
- }
8705
- } else {
8706
- lower = records.length;
8707
- }
8708
- }
8709
- if (progressed === 0) {
8710
- break;
8711
- }
8712
- }
8713
- trimBoundaryBlankLines(output);
8714
- return output;
8715
- }
8716
8660
  async function collectDirectoryEntries(filesystem, rootPath, depth, maxLineLength) {
8717
8661
  const queue = [
8718
8662
  { path: rootPath, relativePrefix: "", remainingDepth: depth }
@@ -10030,10 +9974,10 @@ export {
10030
9974
  createListDirectoryTool,
10031
9975
  createModelAgnosticFilesystemToolSet,
10032
9976
  createNodeAgentFilesystem,
10033
- createReadFilesTool,
10034
9977
  createReplaceTool,
10035
9978
  createRgSearchTool,
10036
9979
  createToolLoopSteeringChannel,
9980
+ createViewImageTool,
10037
9981
  createWriteFileTool,
10038
9982
  customTool,
10039
9983
  encodeChatGptAuthJson,