@ljoukov/llm 3.0.11 → 3.0.13

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
@@ -2105,7 +2105,6 @@ function getGoogleAuthOptions(scopes) {
2105
2105
 
2106
2106
  // src/google/client.ts
2107
2107
  var GEMINI_TEXT_MODEL_IDS = [
2108
- "gemini-3-pro-preview",
2109
2108
  "gemini-3.1-pro-preview",
2110
2109
  "gemini-3-flash-preview",
2111
2110
  "gemini-2.5-pro",
@@ -3909,6 +3908,38 @@ function mergeToolOutput(value) {
3909
3908
  return JSON.stringify({ error: "Failed to serialize tool output", detail: message });
3910
3909
  }
3911
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
+ }
3912
3943
  function parseOpenAiToolArguments(raw) {
3913
3944
  const trimmed = raw.trim();
3914
3945
  if (trimmed.length === 0) {
@@ -4264,7 +4295,6 @@ function resolveGeminiThinkingConfig(modelId) {
4264
4295
  return void 0;
4265
4296
  }
4266
4297
  switch (modelId) {
4267
- case "gemini-3-pro-preview":
4268
4298
  case "gemini-3.1-pro-preview":
4269
4299
  return { includeThoughts: true };
4270
4300
  case "gemini-3-flash-preview":
@@ -5266,13 +5296,13 @@ async function runToolLoop(request) {
5266
5296
  toolOutputs.push({
5267
5297
  type: "custom_tool_call_output",
5268
5298
  call_id: entry.call.call_id,
5269
- output: mergeToolOutput(outputPayload)
5299
+ output: toOpenAiToolOutput(outputPayload)
5270
5300
  });
5271
5301
  } else {
5272
5302
  toolOutputs.push({
5273
5303
  type: "function_call_output",
5274
5304
  call_id: entry.call.call_id,
5275
- output: mergeToolOutput(outputPayload)
5305
+ output: toOpenAiToolOutput(outputPayload)
5276
5306
  });
5277
5307
  }
5278
5308
  }
@@ -5487,7 +5517,7 @@ async function runToolLoop(request) {
5487
5517
  toolOutputs.push({
5488
5518
  type: "custom_tool_call_output",
5489
5519
  call_id: entry.ids.callId,
5490
- output: mergeToolOutput(outputPayload)
5520
+ output: toOpenAiToolOutput(outputPayload)
5491
5521
  });
5492
5522
  } else {
5493
5523
  toolOutputs.push({
@@ -5501,7 +5531,7 @@ async function runToolLoop(request) {
5501
5531
  toolOutputs.push({
5502
5532
  type: "function_call_output",
5503
5533
  call_id: entry.ids.callId,
5504
- output: mergeToolOutput(outputPayload)
5534
+ output: toOpenAiToolOutput(outputPayload)
5505
5535
  });
5506
5536
  }
5507
5537
  }
@@ -7132,6 +7162,7 @@ function sleep2(ms) {
7132
7162
 
7133
7163
  // src/tools/filesystemTools.ts
7134
7164
  import path5 from "path";
7165
+ import { Buffer as Buffer4 } from "buffer";
7135
7166
  import { z as z6 } from "zod";
7136
7167
 
7137
7168
  // src/tools/applyPatch.ts
@@ -7165,6 +7196,10 @@ var InMemoryAgentFilesystem = class {
7165
7196
  }
7166
7197
  return file.content;
7167
7198
  }
7199
+ async readBinaryFile(filePath) {
7200
+ const content = await this.readTextFile(filePath);
7201
+ return Buffer.from(content, "utf8");
7202
+ }
7168
7203
  async writeTextFile(filePath, content) {
7169
7204
  const absolutePath = path3.resolve(filePath);
7170
7205
  const parentPath = path3.dirname(absolutePath);
@@ -7277,6 +7312,7 @@ var InMemoryAgentFilesystem = class {
7277
7312
  function createNodeAgentFilesystem() {
7278
7313
  return {
7279
7314
  readTextFile: async (filePath) => fs3.readFile(filePath, "utf8"),
7315
+ readBinaryFile: async (filePath) => fs3.readFile(filePath),
7280
7316
  writeTextFile: async (filePath, content) => fs3.writeFile(filePath, content, "utf8"),
7281
7317
  deleteFile: async (filePath) => fs3.unlink(filePath),
7282
7318
  ensureDir: async (directoryPath) => {
@@ -7832,29 +7868,66 @@ function formatSummary(added, modified, deleted) {
7832
7868
 
7833
7869
  // src/tools/filesystemTools.ts
7834
7870
  var DEFAULT_READ_FILE_LINE_LIMIT = 2e3;
7835
- var DEFAULT_READ_FILES_LINE_LIMIT = 200;
7836
- var DEFAULT_READ_FILES_CHAR_LIMIT = 4e3;
7837
7871
  var DEFAULT_LIST_DIR_LIMIT = 25;
7838
7872
  var DEFAULT_LIST_DIR_DEPTH = 2;
7839
7873
  var DEFAULT_GREP_LIMIT = 100;
7840
7874
  var MAX_GREP_LIMIT = 2e3;
7875
+ var MAX_VIEW_IMAGE_BYTES = 10 * 1024 * 1024;
7841
7876
  var DEFAULT_MAX_LINE_LENGTH = 500;
7842
7877
  var DEFAULT_GREP_MAX_SCANNED_FILES = 2e4;
7843
- 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
+ };
7886
+ function parseOptionalString(value) {
7887
+ if (value === null || value === void 0) {
7888
+ return void 0;
7889
+ }
7890
+ if (typeof value !== "string") {
7891
+ return void 0;
7892
+ }
7893
+ const trimmed = value.trim();
7894
+ if (trimmed.length === 0) {
7895
+ return void 0;
7896
+ }
7897
+ return trimmed;
7898
+ }
7844
7899
  var codexReadFileInputSchema = z6.object({
7845
- file_path: z6.string().min(1).describe(
7846
- "Path to the file (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
7900
+ file_path: z6.preprocess(
7901
+ (value) => parseOptionalString(value),
7902
+ z6.string().min(1).optional().describe(
7903
+ "Path to the file (relative to cwd, or absolute. In sandbox mode, / maps to the sandbox root)."
7904
+ )
7905
+ ),
7906
+ path: z6.preprocess(
7907
+ (value) => parseOptionalString(value),
7908
+ z6.string().min(1).optional().describe(
7909
+ "Alias for file_path. If both file_path and path are provided they must be identical."
7910
+ )
7847
7911
  ),
7848
7912
  offset: z6.number().int().min(1).nullish().describe("The line number to start reading from. Must be 1 or greater."),
7849
- limit: z6.number().int().min(1).nullish().describe("The maximum number of lines to return."),
7850
- mode: z6.enum(["slice", "indentation"]).nullish().describe('Optional mode selector: "slice" (default) or "indentation".'),
7851
- indentation: z6.object({
7852
- anchor_line: z6.number().int().min(1).nullish(),
7853
- max_levels: z6.number().int().min(0).nullish(),
7854
- include_siblings: z6.boolean().nullish(),
7855
- include_header: z6.boolean().nullish(),
7856
- max_lines: z6.number().int().min(1).nullish()
7857
- }).nullish()
7913
+ limit: z6.number().int().min(1).nullish().describe("The maximum number of lines to return.")
7914
+ }).strict().superRefine((value, context) => {
7915
+ const filePath = value.file_path?.trim() ?? "";
7916
+ const aliasPath = value.path?.trim() ?? "";
7917
+ if (filePath.length === 0 && aliasPath.length === 0) {
7918
+ context.addIssue({
7919
+ code: z6.ZodIssueCode.custom,
7920
+ message: "read_file requires file_path (or path alias).",
7921
+ path: ["file_path"]
7922
+ });
7923
+ }
7924
+ if (filePath.length > 0 && aliasPath.length > 0 && filePath !== aliasPath) {
7925
+ context.addIssue({
7926
+ code: z6.ZodIssueCode.custom,
7927
+ message: "file_path and path must match when both are provided.",
7928
+ path: ["path"]
7929
+ });
7930
+ }
7858
7931
  });
7859
7932
  var codexListDirInputSchema = z6.object({
7860
7933
  dir_path: z6.string().min(1).describe(
@@ -7870,6 +7943,9 @@ var codexGrepFilesInputSchema = z6.object({
7870
7943
  path: z6.string().nullish().describe("Directory or file path to search. Defaults to cwd."),
7871
7944
  limit: z6.number().int().min(1).nullish().describe("Maximum number of file paths to return (defaults to 100).")
7872
7945
  });
7946
+ var codexViewImageInputSchema = z6.object({
7947
+ path: z6.string().min(1).describe("Local filesystem path to an image file")
7948
+ });
7873
7949
  var applyPatchInputSchema = z6.object({
7874
7950
  input: z6.string().min(1).describe(CODEX_APPLY_PATCH_INPUT_DESCRIPTION)
7875
7951
  });
@@ -7877,24 +7953,7 @@ var geminiReadFileInputSchema = z6.object({
7877
7953
  file_path: z6.string().min(1),
7878
7954
  offset: z6.number().int().min(0).nullish(),
7879
7955
  limit: z6.number().int().min(1).nullish()
7880
- });
7881
- var geminiReadFilesInputSchema = z6.object({
7882
- paths: z6.array(z6.string().min(1)).min(1),
7883
- line_offset: z6.number().int().min(0).nullish(),
7884
- line_limit: z6.number().int().min(1).nullish(),
7885
- char_offset: z6.number().int().min(0).nullish(),
7886
- char_limit: z6.number().int().min(1).nullish(),
7887
- include_line_numbers: z6.boolean().nullish()
7888
- }).superRefine((value, context) => {
7889
- const hasLineWindow = value.line_offset !== void 0 || value.line_limit !== void 0;
7890
- const hasCharWindow = value.char_offset !== void 0 || value.char_limit !== void 0;
7891
- if (hasLineWindow && hasCharWindow) {
7892
- context.addIssue({
7893
- code: z6.ZodIssueCode.custom,
7894
- message: "Use either line_* or char_* window arguments, not both."
7895
- });
7896
- }
7897
- });
7956
+ }).strict();
7898
7957
  var geminiWriteFileInputSchema = z6.object({
7899
7958
  file_path: z6.string().min(1),
7900
7959
  content: z6.string()
@@ -7977,7 +8036,8 @@ function createCodexFilesystemToolSet(options = {}) {
7977
8036
  apply_patch: createCodexApplyPatchTool(options),
7978
8037
  read_file: createCodexReadFileTool(options),
7979
8038
  list_dir: createListDirTool(options),
7980
- grep_files: createGrepFilesTool(options)
8039
+ grep_files: createGrepFilesTool(options),
8040
+ view_image: createViewImageTool(options)
7981
8041
  };
7982
8042
  }
7983
8043
  function createGeminiFilesystemToolSet(options = {}) {
@@ -8026,7 +8086,7 @@ function createCodexApplyPatchTool(options = {}) {
8026
8086
  }
8027
8087
  function createCodexReadFileTool(options = {}) {
8028
8088
  return tool({
8029
- description: "Reads a local file with 1-indexed line numbers, supporting slice and indentation-aware block modes.",
8089
+ description: "Reads a local UTF-8 text file with 1-indexed line numbers.",
8030
8090
  inputSchema: codexReadFileInputSchema,
8031
8091
  execute: async (input) => readFileCodex(input, options)
8032
8092
  });
@@ -8045,6 +8105,13 @@ function createGrepFilesTool(options = {}) {
8045
8105
  execute: async (input) => grepFilesCodex(input, options)
8046
8106
  });
8047
8107
  }
8108
+ function createViewImageTool(options = {}) {
8109
+ return tool({
8110
+ 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).",
8111
+ inputSchema: codexViewImageInputSchema,
8112
+ execute: async (input) => viewImageCodex(input, options)
8113
+ });
8114
+ }
8048
8115
  function createGeminiReadFileTool(options = {}) {
8049
8116
  return tool({
8050
8117
  description: "Reads and returns the content of a specified file. Supports optional 0-based line offset and line limit.",
@@ -8052,13 +8119,6 @@ function createGeminiReadFileTool(options = {}) {
8052
8119
  execute: async (input) => readFileGemini(input, options)
8053
8120
  });
8054
8121
  }
8055
- function createReadFilesTool(options = {}) {
8056
- return tool({
8057
- description: "Reads one or more files with optional line-based or character-based slicing, similar to a controlled head/tail view.",
8058
- inputSchema: geminiReadFilesInputSchema,
8059
- execute: async (input) => readFilesGemini(input, options)
8060
- });
8061
- }
8062
8122
  function createWriteFileTool(options = {}) {
8063
8123
  return tool({
8064
8124
  description: "Writes content to a specified file in the local filesystem.",
@@ -8101,53 +8161,61 @@ function createGlobTool(options = {}) {
8101
8161
  execute: async (input) => globFilesGemini(input, options)
8102
8162
  });
8103
8163
  }
8164
+ function resolveCodexReadFilePath(input) {
8165
+ const filePath = parseOptionalString(input.file_path);
8166
+ if (filePath) {
8167
+ return filePath;
8168
+ }
8169
+ const aliasPath = parseOptionalString(input.path);
8170
+ if (aliasPath) {
8171
+ return aliasPath;
8172
+ }
8173
+ throw new Error("read_file requires file_path");
8174
+ }
8104
8175
  async function readFileCodex(input, options) {
8105
8176
  const runtime = resolveRuntime(options);
8106
- const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
8177
+ const filePath = resolvePathWithPolicy(
8178
+ resolveCodexReadFilePath(input),
8179
+ runtime.cwd,
8180
+ runtime.allowOutsideCwd
8181
+ );
8107
8182
  await runAccessHook2(runtime, {
8108
8183
  cwd: runtime.cwd,
8109
8184
  tool: "read_file",
8110
8185
  action: "read",
8111
8186
  path: filePath
8112
8187
  });
8113
- const content = await runtime.filesystem.readTextFile(filePath);
8188
+ const fileBytes = await readBinaryFile(runtime.filesystem, filePath);
8189
+ const imageMimeType = detectImageMimeType(fileBytes, filePath);
8190
+ if (imageMimeType) {
8191
+ throw new Error(
8192
+ `read_file only supports text files; "${toDisplayPath2(filePath, runtime.cwd)}" is an image (${imageMimeType}). Use view_image instead.`
8193
+ );
8194
+ }
8195
+ if (isPdfFile(fileBytes, filePath)) {
8196
+ throw new Error(
8197
+ `read_file only supports text files; "${toDisplayPath2(filePath, runtime.cwd)}" is a PDF.`
8198
+ );
8199
+ }
8200
+ if (!isValidUtf8(fileBytes)) {
8201
+ throw new Error(
8202
+ `read_file only supports UTF-8 text files; "${toDisplayPath2(filePath, runtime.cwd)}" appears to be binary.`
8203
+ );
8204
+ }
8205
+ const content = fileBytes.toString("utf8");
8114
8206
  const lines = splitLines(content);
8115
8207
  const offset = input.offset ?? 1;
8116
8208
  const limit = input.limit ?? DEFAULT_READ_FILE_LINE_LIMIT;
8117
- const mode = input.mode ?? "slice";
8118
8209
  if (offset > lines.length) {
8119
8210
  throw new Error("offset exceeds file length");
8120
8211
  }
8121
- if (mode === "slice") {
8122
- const output = [];
8123
- const lastLine = Math.min(lines.length, offset + limit - 1);
8124
- for (let lineNumber = offset; lineNumber <= lastLine; lineNumber += 1) {
8125
- const line = lines[lineNumber - 1] ?? "";
8126
- output.push(`L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`);
8127
- }
8128
- return output.join("\n");
8129
- }
8130
- const indentation = input.indentation ?? {};
8131
- const anchorLine = indentation.anchor_line ?? offset;
8132
- if (anchorLine < 1 || anchorLine > lines.length) {
8133
- throw new Error("anchor_line exceeds file length");
8134
- }
8135
- const records = lines.map((line, index) => ({
8136
- number: index + 1,
8137
- raw: line,
8138
- display: truncateAtCodePointBoundary(line, runtime.maxLineLength),
8139
- indent: measureIndent(line, DEFAULT_TAB_WIDTH)
8140
- }));
8141
- const selected = readWithIndentationMode({
8142
- records,
8143
- anchorLine,
8144
- limit,
8145
- maxLevels: indentation.max_levels ?? 0,
8146
- includeSiblings: indentation.include_siblings ?? false,
8147
- includeHeader: indentation.include_header ?? true,
8148
- maxLines: indentation.max_lines ?? void 0
8149
- });
8150
- return selected.map((record) => `L${record.number}: ${record.display}`).join("\n");
8212
+ const output = [];
8213
+ const lastLine = Math.min(lines.length, offset + limit - 1);
8214
+ for (let lineNumber = offset; lineNumber <= lastLine; lineNumber += 1) {
8215
+ const line = lines[lineNumber - 1] ?? "";
8216
+ output.push(`L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`);
8217
+ }
8218
+ return output.join("\n");
8151
8219
  }
8152
8220
  async function listDirectoryCodex(input, options) {
8153
8221
  const runtime = resolveRuntime(options);
@@ -8235,6 +8303,85 @@ async function grepFilesCodex(input, options) {
8235
8303
  const limit = Math.min(input.limit ?? DEFAULT_GREP_LIMIT, MAX_GREP_LIMIT);
8236
8304
  return matches.slice(0, limit).map((match) => match.filePath).join("\n");
8237
8305
  }
8306
+ async function viewImageCodex(input, options) {
8307
+ const runtime = resolveRuntime(options);
8308
+ const imagePath = resolvePathWithPolicy(input.path, runtime.cwd, runtime.allowOutsideCwd);
8309
+ await runAccessHook2(runtime, {
8310
+ cwd: runtime.cwd,
8311
+ tool: "view_image",
8312
+ action: "read",
8313
+ path: imagePath
8314
+ });
8315
+ const stats = await runtime.filesystem.stat(imagePath);
8316
+ if (stats.kind !== "file") {
8317
+ throw new Error(`image path \`${toDisplayPath2(imagePath, runtime.cwd)}\` is not a file`);
8318
+ }
8319
+ const bytes = await readBinaryFile(runtime.filesystem, imagePath);
8320
+ if (bytes.byteLength > MAX_VIEW_IMAGE_BYTES) {
8321
+ return [
8322
+ {
8323
+ type: "input_text",
8324
+ text: `Codex cannot attach image at \`${toDisplayPath2(imagePath, runtime.cwd)}\`: image exceeds ${MAX_VIEW_IMAGE_BYTES} bytes.`
8325
+ }
8326
+ ];
8327
+ }
8328
+ const mimeType = detectImageMimeType(bytes, imagePath);
8329
+ if (!mimeType) {
8330
+ return [
8331
+ {
8332
+ type: "input_text",
8333
+ text: `Codex cannot attach image at \`${toDisplayPath2(imagePath, runtime.cwd)}\`: unsupported image format.`
8334
+ }
8335
+ ];
8336
+ }
8337
+ return [
8338
+ {
8339
+ type: "input_image",
8340
+ image_url: `data:${mimeType};base64,${bytes.toString("base64")}`
8341
+ }
8342
+ ];
8343
+ }
8344
+ async function readBinaryFile(filesystem, filePath) {
8345
+ if (typeof filesystem.readBinaryFile === "function") {
8346
+ return await filesystem.readBinaryFile(filePath);
8347
+ }
8348
+ const text = await filesystem.readTextFile(filePath);
8349
+ return Buffer4.from(text, "utf8");
8350
+ }
8351
+ function detectImageMimeType(buffer, filePath) {
8352
+ 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) {
8353
+ return "image/png";
8354
+ }
8355
+ if (buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) {
8356
+ return "image/jpeg";
8357
+ }
8358
+ if (buffer.length >= 6) {
8359
+ const signature = buffer.subarray(0, 6).toString("ascii");
8360
+ if (signature === "GIF87a" || signature === "GIF89a") {
8361
+ return "image/gif";
8362
+ }
8363
+ }
8364
+ if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
8365
+ return "image/webp";
8366
+ }
8367
+ const fromExtension = IMAGE_MIME_BY_EXTENSION[path5.extname(filePath).toLowerCase()];
8368
+ if (fromExtension && SUPPORTED_IMAGE_MIME_TYPES.has(fromExtension)) {
8369
+ return fromExtension;
8370
+ }
8371
+ return void 0;
8372
+ }
8373
+ function isPdfFile(buffer, filePath) {
8374
+ if (buffer.length >= 5 && buffer.subarray(0, 5).toString("ascii") === "%PDF-") {
8375
+ return true;
8376
+ }
8377
+ return path5.extname(filePath).toLowerCase() === ".pdf";
8378
+ }
8379
+ function isValidUtf8(buffer) {
8380
+ if (buffer.length === 0) {
8381
+ return true;
8382
+ }
8383
+ return Buffer4.from(buffer.toString("utf8"), "utf8").equals(buffer);
8384
+ }
8238
8385
  async function readFileGemini(input, options) {
8239
8386
  const runtime = resolveRuntime(options);
8240
8387
  const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
@@ -8256,56 +8403,6 @@ async function readFileGemini(input, options) {
8256
8403
  (line, index) => `L${offset + index + 1}: ${truncateAtCodePointBoundary(line ?? "", runtime.maxLineLength)}`
8257
8404
  ).join("\n");
8258
8405
  }
8259
- async function readFilesGemini(input, options) {
8260
- const runtime = resolveRuntime(options);
8261
- const useCharWindow = input.char_offset !== void 0 || input.char_limit !== void 0;
8262
- const lineOffset = Math.max(0, input.line_offset ?? 0);
8263
- const lineLimit = input.line_limit ?? DEFAULT_READ_FILES_LINE_LIMIT;
8264
- const charOffset = Math.max(0, input.char_offset ?? 0);
8265
- const charLimit = input.char_limit ?? DEFAULT_READ_FILES_CHAR_LIMIT;
8266
- const includeLineNumbers = input.include_line_numbers !== false;
8267
- const sections = [];
8268
- for (const rawPath of input.paths) {
8269
- const filePath = resolvePathWithPolicy(rawPath, runtime.cwd, runtime.allowOutsideCwd);
8270
- await runAccessHook2(runtime, {
8271
- cwd: runtime.cwd,
8272
- tool: "read_files",
8273
- action: "read",
8274
- path: filePath
8275
- });
8276
- const content = await runtime.filesystem.readTextFile(filePath);
8277
- const displayPath = normalizeSlashes(toDisplayPath2(filePath, runtime.cwd));
8278
- sections.push(`==> ${displayPath} <==`);
8279
- if (useCharWindow) {
8280
- if (charOffset >= content.length) {
8281
- sections.push("");
8282
- continue;
8283
- }
8284
- const end2 = Math.min(content.length, charOffset + charLimit);
8285
- sections.push(content.slice(charOffset, end2));
8286
- continue;
8287
- }
8288
- const lines = splitLines(content);
8289
- if (lineOffset >= lines.length) {
8290
- sections.push("");
8291
- continue;
8292
- }
8293
- const end = Math.min(lines.length, lineOffset + lineLimit);
8294
- const selected = lines.slice(lineOffset, end);
8295
- if (includeLineNumbers) {
8296
- for (let index = 0; index < selected.length; index += 1) {
8297
- const lineNumber = lineOffset + index + 1;
8298
- const line = selected[index] ?? "";
8299
- sections.push(
8300
- `L${lineNumber}: ${truncateAtCodePointBoundary(line, runtime.maxLineLength)}`
8301
- );
8302
- }
8303
- continue;
8304
- }
8305
- sections.push(selected.join("\n"));
8306
- }
8307
- return sections.join("\n");
8308
- }
8309
8406
  async function writeFileGemini(input, options) {
8310
8407
  const runtime = resolveRuntime(options);
8311
8408
  const filePath = resolvePathWithPolicy(input.file_path, runtime.cwd, runtime.allowOutsideCwd);
@@ -8614,117 +8711,6 @@ function truncateAtCodePointBoundary(value, maxLength) {
8614
8711
  }
8615
8712
  return Array.from(value).slice(0, maxLength).join("");
8616
8713
  }
8617
- function measureIndent(line, tabWidth) {
8618
- let count = 0;
8619
- for (const char of line) {
8620
- if (char === " ") {
8621
- count += 1;
8622
- continue;
8623
- }
8624
- if (char === " ") {
8625
- count += tabWidth;
8626
- continue;
8627
- }
8628
- break;
8629
- }
8630
- return count;
8631
- }
8632
- function computeEffectiveIndents(records) {
8633
- const effective = [];
8634
- let previous = 0;
8635
- for (const record of records) {
8636
- if (record.raw.trim().length === 0) {
8637
- effective.push(previous);
8638
- } else {
8639
- previous = record.indent;
8640
- effective.push(previous);
8641
- }
8642
- }
8643
- return effective;
8644
- }
8645
- function trimBoundaryBlankLines(records) {
8646
- while (records.length > 0 && records[0]?.raw.trim().length === 0) {
8647
- records.shift();
8648
- }
8649
- while (records.length > 0 && records[records.length - 1]?.raw.trim().length === 0) {
8650
- records.pop();
8651
- }
8652
- }
8653
- function isCommentLine(line) {
8654
- const trimmed = line.trim();
8655
- return trimmed.startsWith("#") || trimmed.startsWith("//") || trimmed.startsWith("--");
8656
- }
8657
- function readWithIndentationMode(params) {
8658
- const { records, anchorLine, limit, maxLevels, includeSiblings, includeHeader, maxLines } = params;
8659
- const anchorIndex = anchorLine - 1;
8660
- const effectiveIndents = computeEffectiveIndents(records);
8661
- const anchorIndent = effectiveIndents[anchorIndex] ?? 0;
8662
- const minIndent = maxLevels === 0 ? 0 : Math.max(anchorIndent - maxLevels * DEFAULT_TAB_WIDTH, 0);
8663
- const guardLimit = maxLines ?? limit;
8664
- const finalLimit = Math.min(limit, guardLimit, records.length);
8665
- if (finalLimit <= 1) {
8666
- return [records[anchorIndex]].filter((entry) => Boolean(entry));
8667
- }
8668
- let upper = anchorIndex - 1;
8669
- let lower = anchorIndex + 1;
8670
- let upperMinIndentHits = 0;
8671
- let lowerMinIndentHits = 0;
8672
- const output = [records[anchorIndex]].filter(
8673
- (entry) => Boolean(entry)
8674
- );
8675
- while (output.length < finalLimit) {
8676
- let progressed = 0;
8677
- if (upper >= 0) {
8678
- const candidate = records[upper];
8679
- const candidateIndent = effectiveIndents[upper] ?? 0;
8680
- if (candidate && candidateIndent >= minIndent) {
8681
- output.unshift(candidate);
8682
- progressed += 1;
8683
- upper -= 1;
8684
- if (candidateIndent === minIndent && !includeSiblings) {
8685
- const allowHeaderComment = includeHeader && isCommentLine(candidate.raw);
8686
- const canTakeLine = allowHeaderComment || upperMinIndentHits === 0;
8687
- if (canTakeLine) {
8688
- upperMinIndentHits += 1;
8689
- } else {
8690
- output.shift();
8691
- progressed -= 1;
8692
- upper = -1;
8693
- }
8694
- }
8695
- if (output.length >= finalLimit) {
8696
- break;
8697
- }
8698
- } else {
8699
- upper = -1;
8700
- }
8701
- }
8702
- if (lower < records.length) {
8703
- const candidate = records[lower];
8704
- const candidateIndent = effectiveIndents[lower] ?? 0;
8705
- if (candidate && candidateIndent >= minIndent) {
8706
- output.push(candidate);
8707
- progressed += 1;
8708
- lower += 1;
8709
- if (candidateIndent === minIndent && !includeSiblings) {
8710
- if (lowerMinIndentHits > 0) {
8711
- output.pop();
8712
- progressed -= 1;
8713
- lower = records.length;
8714
- }
8715
- lowerMinIndentHits += 1;
8716
- }
8717
- } else {
8718
- lower = records.length;
8719
- }
8720
- }
8721
- if (progressed === 0) {
8722
- break;
8723
- }
8724
- }
8725
- trimBoundaryBlankLines(output);
8726
- return output;
8727
- }
8728
8714
  async function collectDirectoryEntries(filesystem, rootPath, depth, maxLineLength) {
8729
8715
  const queue = [
8730
8716
  { path: rootPath, relativePrefix: "", remainingDepth: depth }
@@ -10042,10 +10028,10 @@ export {
10042
10028
  createListDirectoryTool,
10043
10029
  createModelAgnosticFilesystemToolSet,
10044
10030
  createNodeAgentFilesystem,
10045
- createReadFilesTool,
10046
10031
  createReplaceTool,
10047
10032
  createRgSearchTool,
10048
10033
  createToolLoopSteeringChannel,
10034
+ createViewImageTool,
10049
10035
  createWriteFileTool,
10050
10036
  customTool,
10051
10037
  encodeChatGptAuthJson,