@zaly/agent 0.0.12 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/{agent-Dp4LZDSo.mjs → agent-D5NrjvG5.mjs} +3 -3
  2. package/dist/{agent-Dp4LZDSo.mjs.map → agent-D5NrjvG5.mjs.map} +1 -1
  3. package/dist/{bash-COgq4W7U.mjs → bash-9x8PPg0A.mjs} +6 -6
  4. package/dist/bash-9x8PPg0A.mjs.map +1 -0
  5. package/dist/{compactions-CvQs64fS.mjs → compactions-D9XlwQGO.mjs} +2 -2
  6. package/dist/{compactions-CvQs64fS.mjs.map → compactions-D9XlwQGO.mjs.map} +1 -1
  7. package/dist/fetch-D0IeDN6H.mjs +159 -0
  8. package/dist/fetch-D0IeDN6H.mjs.map +1 -0
  9. package/dist/{find-CimUGn-L.mjs → find-D1Z2ac_b.mjs} +28 -10
  10. package/dist/find-D1Z2ac_b.mjs.map +1 -0
  11. package/dist/{grep-6O3mjSvV.mjs → grep-By4wLRDk.mjs} +17 -7
  12. package/dist/grep-By4wLRDk.mjs.map +1 -0
  13. package/dist/html-J629LNIm.mjs +33225 -0
  14. package/dist/html-J629LNIm.mjs.map +1 -0
  15. package/dist/index.d.mts +43 -3
  16. package/dist/index.d.mts.map +1 -1
  17. package/dist/index.mjs +2 -2
  18. package/dist/{manager-BqlbCGYV.mjs → manager-BJLjayBz.mjs} +2 -2
  19. package/dist/{manager-BqlbCGYV.mjs.map → manager-BJLjayBz.mjs.map} +1 -1
  20. package/dist/{masker-ZoP381jN.mjs → masker-jgSUXzjY.mjs} +2 -2
  21. package/dist/{masker-ZoP381jN.mjs.map → masker-jgSUXzjY.mjs.map} +1 -1
  22. package/dist/{registry-L9GDccS0.mjs → registry-Zo0QmKdR.mjs} +14 -16
  23. package/dist/registry-Zo0QmKdR.mjs.map +1 -0
  24. package/dist/rolldown-runtime-DktXgydw.mjs +35 -0
  25. package/dist/{search-CRw247ES.mjs → search-CH0D5CkV.mjs} +19 -15
  26. package/dist/search-CH0D5CkV.mjs.map +1 -0
  27. package/dist/{session-BSvUrr12.mjs → session-CACkHyDR.mjs} +2 -2
  28. package/dist/{session-BSvUrr12.mjs.map → session-CACkHyDR.mjs.map} +1 -1
  29. package/dist/session.mjs +1 -1
  30. package/dist/{src-C852Q6gy.mjs → src-DoXC_gDY.mjs} +14 -13
  31. package/dist/{src-C852Q6gy.mjs.map → src-DoXC_gDY.mjs.map} +1 -1
  32. package/dist/{subagent-C8XCEecM.mjs → subagent-BMQMbANL.mjs} +2 -2
  33. package/dist/{subagent-C8XCEecM.mjs.map → subagent-BMQMbANL.mjs.map} +1 -1
  34. package/dist/{tools-wvkkuXWH.mjs → tools-CJ2x45EZ.mjs} +3 -34
  35. package/dist/{tools-wvkkuXWH.mjs.map → tools-CJ2x45EZ.mjs.map} +1 -1
  36. package/package.json +46 -8
  37. package/dist/bash-COgq4W7U.mjs.map +0 -1
  38. package/dist/fetch-VKhSWdEh.mjs +0 -111
  39. package/dist/fetch-VKhSWdEh.mjs.map +0 -1
  40. package/dist/find-CimUGn-L.mjs.map +0 -1
  41. package/dist/grep-6O3mjSvV.mjs.map +0 -1
  42. package/dist/registry-L9GDccS0.mjs.map +0 -1
  43. package/dist/search-CRw247ES.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-D0IeDN6H.mjs","names":[],"sources":["../src/tools/fetch.ts"],"sourcesContent":["import type { Attachment, MetaPart, TextPart, ToolContext } from \"@zaly/ai\"\n\nimport { defineTool, toAttachment, zalyUserAgent } from \"@zaly/ai\"\nimport { randomHash } from \"@zaly/shared\"\nimport { mkdir, writeFile } from \"node:fs/promises\"\nimport { tmpdir } from \"node:os\"\nimport { join } from \"pathe\"\nimport { Type } from \"typebox\"\nimport { truncate } from \"../utils/truncate.ts\"\n\nexport type FetchTool = typeof fetchTool\nexport type FetchToolMeta = {\n kind: \"fetch\"\n url: string\n type: \"text\" | Attachment[\"type\"]\n format?: string\n title?: string\n status: number\n statusText: string\n ok: boolean\n contentType?: string\n durationMs: number\n warning?: string\n truncated?: { fullOutputPath: string; totalBytes: number; totalLines: number }\n}\n\n/**\n * Fetch APIs or static pages. HTML becomes readable Markdown; raw mode\n * preserves the response text. Conversion never runs page scripts.\n * Truncated text is saved in full alongside the session, like bash output.\n *\n * Permissions: routes through the `fetch` scope when the agent wires up\n * tool-side permission checks. Until then, callers should restrict via\n * a wrapping permission layer (rule-based or explicit allowlist).\n */\n// oxlint-disable-next-line sort-keys\nexport const fetchTool = defineTool({\n desc:\n `Fetch a URL and return the response. JSON responses are parsed; use \\`jsonpath\\` (e.g. \\`$.items[*].name\\`) to extract a subset and minimise tokens. ` +\n `HTML pages are converted to readable Markdown without executing JavaScript; use a browser for interactive pages. ` +\n `Images & PDFs are returned as attachments. Truncated text is saved in full; read \\`truncated.fullOutputPath\\` to inspect it.`,\n name: \"fetch\",\n parallel: true,\n\n // oxlint-disable-next-line sort-keys -- semantic param order: url, method, headers, query, body, jsonpath\n params: Type.Object({\n url: Type.String({ description: \"Absolute URL.\" }),\n method: Type.Optional(\n Type.Union(\n [\n Type.Literal(\"GET\"),\n Type.Literal(\"POST\"),\n Type.Literal(\"PUT\"),\n Type.Literal(\"PATCH\"),\n Type.Literal(\"DELETE\"),\n ],\n { default: \"GET\", description: \"HTTP method.\" }\n )\n ),\n headers: Type.Optional(\n Type.Record(Type.String(), Type.String(), {\n description: \"Request headers.\",\n })\n ),\n query: Type.Optional(\n Type.Record(Type.String(), Type.String(), {\n description: \"Query string params, appended to the URL.\",\n })\n ),\n body: Type.Optional(\n Type.String({\n description:\n \"Request body as a string. For JSON, stringify it yourself and \" +\n \"set `headers: { 'Content-Type': 'application/json' }`.\",\n })\n ),\n format: Type.Optional(\n Type.Union([Type.Literal(\"auto\"), Type.Literal(\"markdown\"), Type.Literal(\"raw\")], {\n default: \"auto\",\n description:\n \"auto converts HTML to Markdown and parses JSON. markdown also treats unrecognized text as HTML. raw returns original text without conversion or JSON parsing. Attachments are unchanged.\",\n })\n ),\n limit: Type.Optional(\n Type.Integer({\n default: 2000,\n description:\n \"Maximum lines to keep inline, split between head and tail. Full output is saved when truncated.\",\n minimum: 1,\n })\n ),\n jsonpath: Type.Optional(\n Type.String({\n description:\n \"JSONPath expression applied to a JSON response, e.g. \" +\n \"`$.items[*].name`. Returns the matched values as an array.\",\n })\n ),\n }),\n async call(args, ctx: ToolContext<FetchToolMeta>): Promise<(Attachment | MetaPart | TextPart)[]> {\n if (args.format === \"raw\" && args.jsonpath !== undefined)\n throw new Error(\"jsonpath cannot be combined with format: raw\")\n const t0 = Date.now()\n const url = new URL(args.url)\n if (args.query) {\n for (const [k, v] of Object.entries(args.query)) url.searchParams.set(k, v)\n }\n\n const headers = new Headers(args.headers)\n if (!headers.has(\"user-agent\")) headers.set(\"User-Agent\", zalyUserAgent)\n const res = await fetch(url, {\n body: args.body,\n headers,\n method: args.method,\n signal: ctx.signal,\n })\n\n const contentType = res.headers.get(\"content-type\") ?? \"\"\n const bytes = new Uint8Array(await res.arrayBuffer())\n\n const meta: FetchToolMeta = {\n contentType: contentType || undefined,\n durationMs: Date.now() - t0,\n kind: \"fetch\",\n ok: res.ok,\n status: res.status,\n statusText: res.statusText,\n type: \"text\",\n url: res.url || url.href,\n }\n ctx.meta = meta\n\n // Magic-byte detection on the response bytes — catches mislabeled\n // content (HTML served at /file.pdf, images served as\n // application/octet-stream, etc.). Inlines as base64 (same shape as\n // the `read` tool) so the model gets the bytes without the provider\n // re-fetching from its own IP. Only attach kinds the model accepts;\n // unsupported kinds fall through to the text branch (rendered via\n // `<image>` / `<pdf>` placeholders by the wire pipeline).\n const { fileDetect } = await import(\"@zaly/shared/detect\")\n const file = await fileDetect({\n data: bytes,\n mime: contentType || undefined,\n url: res.url || url.href,\n })\n if (file) {\n const att = await toAttachment(file)\n if (att) {\n meta.type = att.type\n meta.durationMs = Date.now() - t0\n return [{ data: meta, tag: \"fetch\", type: \"meta\" }, att]\n }\n }\n\n const text = new TextDecoder().decode(bytes)\n // `detectTextFormat` (inside fileDetect) already weighs MIME,\n // extension, and content sniff — reuse its verdict instead of a\n // parallel `looksLikeJson` heuristic.\n const isJson = file?.type === \"text\" && file.format === \"json\"\n\n let body: unknown = text\n let outputFormat: string | undefined\n if (isJson && args.format !== \"raw\") {\n try {\n body = JSON.parse(text)\n outputFormat = \"json\"\n } catch {\n // Server lied about content-type; keep as text.\n }\n }\n if (args.format !== \"raw\" && !isJson && file?.type === \"text\") {\n if (file.format === \"html\" || (args.format === \"markdown\" && file.format !== \"markdown\")) {\n const { htmlToMarkdown } = await import(\"../utils/html.ts\")\n const page = htmlToMarkdown(text, res.url || url.href)\n body = page.markdown\n outputFormat = \"markdown\"\n if (page.title) meta.title = page.title\n if (page.warning) meta.warning = page.warning\n } else if (file.format === \"markdown\") outputFormat = \"markdown\"\n }\n\n if (args.jsonpath !== undefined) {\n if (outputFormat !== \"json\") throw new Error(\"jsonpath requires a valid JSON response\")\n // Lazy-import — `jsonpath-plus` is only paid for when actually used,\n // and it bundles to a few hundred KB which we'd rather not load on\n // every fetch that doesn't filter.\n const { JSONPath } = await import(\"jsonpath-plus\")\n // oxlint-disable-next-line new-cap -- jsonpath-plus exports as TitleCase\n body = JSONPath({ json: body as object, path: args.jsonpath })\n }\n\n // Stringify the body for display. JSON pretty-prints; raw text is\n // used as-is.\n const bodyText = typeof body === \"string\" ? body : JSON.stringify(body, undefined, 2)\n\n meta.format = outputFormat ?? (file?.type === \"text\" ? file.format : \"plain\")\n const summary = truncate(bodyText, { maxLines: args.limit, strategy: \"head+tail\" })\n if (summary.truncated) {\n let extension = \"txt\"\n if (outputFormat === \"markdown\") extension = \"md\"\n else if (outputFormat === \"json\") extension = \"json\"\n const dir = ctx.sessionDir ?? join(tmpdir(), \"zaly-fetch\")\n const fullOutputPath = join(dir, `fetch-${randomHash()}.${extension}`)\n await mkdir(dir, { recursive: true })\n await writeFile(fullOutputPath, bodyText, \"utf8\")\n meta.truncated = {\n fullOutputPath,\n totalBytes: summary.origBytes,\n totalLines: summary.origLines,\n }\n }\n meta.durationMs = Date.now() - t0\n const parts: (MetaPart | TextPart)[] = [{ data: meta, tag: \"fetch\", type: \"meta\" }]\n if (summary.text !== \"\") parts.push({ format: outputFormat, text: summary.text, type: \"text\" })\n return parts\n },\n})\n"],"mappings":"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"}
@@ -2,7 +2,8 @@ import { t as truncate } from "./truncate-BSMXgiXj.mjs";
2
2
  import { normPath } from "@zaly/shared";
3
3
  import { AiError, defineTool } from "@zaly/ai";
4
4
  import { Type } from "typebox";
5
- import { find } from "@zaly/shared/find";
5
+ import { cleanTextTui } from "@zaly/shared/text";
6
+ import { FindError, find } from "@zaly/shared/find";
6
7
  //#region src/tools/find.ts
7
8
  const DEFAULT_LIMIT = 200;
8
9
  const findTool = defineTool({
@@ -38,20 +39,23 @@ const findTool = defineTool({
38
39
  },
39
40
  async call(args, ctx) {
40
41
  const cwd = normPath(ctx.cwd, ".");
41
- let result = [];
42
+ const limit = args.limit ?? DEFAULT_LIMIT;
43
+ const result = [];
44
+ let warning;
42
45
  try {
43
- result = (await Array.fromAsync(find({
46
+ for await (const batch of find({
44
47
  ...args,
45
48
  cwd,
49
+ limit: limit + 1,
46
50
  pattern: args.glob,
47
51
  signal: ctx.signal
48
- }))).flat();
52
+ })) result.push(...batch);
49
53
  } catch (error) {
50
- throw AiError.from(error);
54
+ if (!(error instanceof FindError) || error.code !== "FIND_FAILED" || !result.length) throw AiError.from(error);
55
+ warning = error;
51
56
  }
52
- const limit = args.limit ?? DEFAULT_LIMIT;
53
57
  const summary = truncate(result.join("\n"), {
54
- maxLines: limit + 1,
58
+ maxLines: limit,
55
59
  strategy: "head"
56
60
  });
57
61
  const truncated = summary.truncated;
@@ -61,13 +65,27 @@ const findTool = defineTool({
61
65
  matches: summary.origLines,
62
66
  truncated
63
67
  };
64
- return [{
68
+ const parts = [];
69
+ if (warning) parts.push({
70
+ data: {
71
+ error: warning.data,
72
+ incomplete: true
73
+ },
74
+ tag: "find",
75
+ type: "meta"
76
+ });
77
+ parts.push({
65
78
  text: summary.text || "No files found.",
66
79
  type: "text"
67
- }];
80
+ });
81
+ if (warning) parts.push({
82
+ text: `Warning: Search incomplete.\n${cleanTextTui(warning.message)}`,
83
+ type: "text"
84
+ });
85
+ return parts;
68
86
  }
69
87
  });
70
88
  //#endregion
71
89
  export { findTool };
72
90
 
73
- //# sourceMappingURL=find-CimUGn-L.mjs.map
91
+ //# sourceMappingURL=find-D1Z2ac_b.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-D1Z2ac_b.mjs","names":[],"sources":["../src/tools/find.ts"],"sourcesContent":["import type { MetaPart, TextPart, ToolContext } from \"@zaly/ai\"\n\nimport { AiError, defineTool } from \"@zaly/ai\"\nimport { normPath } from \"@zaly/shared\"\nimport { find, FindError } from \"@zaly/shared/find\"\nimport { cleanTextTui } from \"@zaly/shared/text\"\nimport { Type } from \"typebox\"\nimport { truncate } from \"../utils/truncate.ts\"\n\nconst DEFAULT_LIMIT = 200\nconst MAX_LIMIT = 1000\n\nexport type FindTool = typeof findTool\nexport type FindToolMeta = {\n cwd: string\n glob: string | string[]\n matches: number\n truncated: boolean\n}\n\n// oxlint-disable-next-line sort-keys -- semantic field order: name, desc, params, call\nexport const findTool = defineTool({\n name: \"find\",\n desc:\n \"Find files using fd/fdfind or rg --files. `glob` is a filename/path glob, \" +\n \"array of globs, or plain substring; empty, `*`, or `**/*` lists all files. \" +\n \"Includes hidden files and respects .gitignore by default. Does not follow directory symlinks; \" +\n \"prefer this over bash find for file discovery.\",\n parallel: true,\n // oxlint-disable-next-line sort-keys -- semantic param order\n params: Type.Object({\n glob: Type.Optional(\n Type.Union([Type.String(), Type.Array(Type.String())], {\n default: \"\",\n description:\n \"Filename/path glob, array of globs, or plain substring. Empty, `*`, or `**/*` lists all files.\",\n })\n ),\n paths: Type.Optional(\n Type.Array(Type.String(), {\n description: \"Optional directories to search, relative to cwd or absolute.\",\n })\n ),\n hidden: Type.Optional(\n Type.Boolean({\n default: true,\n description: \"Include files in hidden directories and dotfiles.\",\n })\n ),\n ignore: Type.Optional(\n Type.Boolean({ default: true, description: \"Respect .gitignore/.ignore rules.\" })\n ),\n limit: Type.Optional(\n Type.Integer({\n default: DEFAULT_LIMIT,\n description: \"Maximum paths to keep inline.\",\n maximum: MAX_LIMIT,\n minimum: 1,\n })\n ),\n }),\n\n async preflight(args, ctx: ToolContext<FindToolMeta>) {\n const cwd = normPath(ctx.cwd, \".\")\n await ctx.need?.(\"read\", cwd)\n const paths = (args.paths?.length ? args.paths : [\".\"]).map((p) => normPath(cwd, p))\n await Promise.all(paths.map((path) => Promise.resolve(ctx.need?.(\"read\", path))))\n },\n\n async call(args, ctx: ToolContext<FindToolMeta>): Promise<(MetaPart | TextPart)[]> {\n const cwd = normPath(ctx.cwd, \".\")\n\n const limit = args.limit ?? DEFAULT_LIMIT\n const result: string[] = []\n let warning: FindError | undefined\n try {\n for await (const batch of find({\n ...args,\n cwd,\n limit: limit + 1,\n pattern: args.glob,\n signal: ctx.signal,\n })) {\n result.push(...batch)\n }\n } catch (error) {\n if (!(error instanceof FindError) || error.code !== \"FIND_FAILED\" || !result.length) {\n throw AiError.from(error)\n }\n warning = error\n }\n\n const summary = truncate(result.join(\"\\n\"), {\n maxLines: limit,\n strategy: \"head\",\n })\n\n const truncated = summary.truncated\n ctx.meta = {\n cwd,\n glob: args.glob ?? \"\",\n matches: summary.origLines,\n truncated,\n }\n\n const parts: (MetaPart | TextPart)[] = []\n if (warning) {\n parts.push({ data: { error: warning.data, incomplete: true }, tag: \"find\", type: \"meta\" })\n }\n parts.push({ text: summary.text || \"No files found.\", type: \"text\" })\n if (warning) {\n parts.push({\n text: `Warning: Search incomplete.\\n${cleanTextTui(warning.message)}`,\n type: \"text\",\n })\n }\n return parts\n },\n})\n"],"mappings":";;;;;;;AASA,MAAM,gBAAgB;AAYtB,MAAa,WAAW,WAAW;CACjC,MAAM;CACN,MACE;CAIF,UAAU;CAEV,QAAQ,KAAK,OAAO;EAClB,MAAM,KAAK,SACT,KAAK,MAAM,CAAC,KAAK,OAAO,GAAG,KAAK,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG;GACrD,SAAS;GACT,aACE;EACJ,CAAC,CACH;EACA,OAAO,KAAK,SACV,KAAK,MAAM,KAAK,OAAO,GAAG,EACxB,aAAa,+DACf,CAAC,CACH;EACA,QAAQ,KAAK,SACX,KAAK,QAAQ;GACX,SAAS;GACT,aAAa;EACf,CAAC,CACH;EACA,QAAQ,KAAK,SACX,KAAK,QAAQ;GAAE,SAAS;GAAM,aAAa;EAAoC,CAAC,CAClF;EACA,OAAO,KAAK,SACV,KAAK,QAAQ;GACX,SAAS;GACT,aAAa;GACb,SAAS;GACT,SAAS;EACX,CAAC,CACH;CACF,CAAC;CAED,MAAM,UAAU,MAAM,KAAgC;EACpD,MAAM,MAAM,SAAS,IAAI,KAAK,GAAG;EACjC,MAAM,IAAI,OAAO,QAAQ,GAAG;EAC5B,MAAM,SAAS,KAAK,OAAO,SAAS,KAAK,QAAQ,CAAC,GAAG,EAAA,CAAG,KAAK,MAAM,SAAS,KAAK,CAAC,CAAC;EACnF,MAAM,QAAQ,IAAI,MAAM,KAAK,SAAS,QAAQ,QAAQ,IAAI,OAAO,QAAQ,IAAI,CAAC,CAAC,CAAC;CAClF;CAEA,MAAM,KAAK,MAAM,KAAkE;EACjF,MAAM,MAAM,SAAS,IAAI,KAAK,GAAG;EAEjC,MAAM,QAAQ,KAAK,SAAS;EAC5B,MAAM,SAAmB,CAAC;EAC1B,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,SAAS,KAAK;IAC7B,GAAG;IACH;IACA,OAAO,QAAQ;IACf,SAAS,KAAK;IACd,QAAQ,IAAI;GACd,CAAC,GACC,OAAO,KAAK,GAAG,KAAK;EAExB,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,cAAc,MAAM,SAAS,iBAAiB,CAAC,OAAO,QAC3E,MAAM,QAAQ,KAAK,KAAK;GAE1B,UAAU;EACZ;EAEA,MAAM,UAAU,SAAS,OAAO,KAAK,IAAI,GAAG;GAC1C,UAAU;GACV,UAAU;EACZ,CAAC;EAED,MAAM,YAAY,QAAQ;EAC1B,IAAI,OAAO;GACT;GACA,MAAM,KAAK,QAAQ;GACnB,SAAS,QAAQ;GACjB;EACF;EAEA,MAAM,QAAiC,CAAC;EACxC,IAAI,SACF,MAAM,KAAK;GAAE,MAAM;IAAE,OAAO,QAAQ;IAAM,YAAY;GAAK;GAAG,KAAK;GAAQ,MAAM;EAAO,CAAC;EAE3F,MAAM,KAAK;GAAE,MAAM,QAAQ,QAAQ;GAAmB,MAAM;EAAO,CAAC;EACpE,IAAI,SACF,MAAM,KAAK;GACT,MAAM,gCAAgC,aAAa,QAAQ,OAAO;GAClE,MAAM;EACR,CAAC;EAEH,OAAO;CACT;AACF,CAAC"}
@@ -67,15 +67,17 @@ const grepTool = defineTool({
67
67
  message: String(error)
68
68
  });
69
69
  });
70
- if (result.code > 1 && result.killReason !== "maxBuffer") throw new AiError({
70
+ const text = cleanTextTui(result.stdout);
71
+ const stderr = cleanTextTui(result.stderr).trim();
72
+ const failed = result.code > 1 && result.killReason !== "maxBuffer";
73
+ if (failed && !text.trim()) throw new AiError({
71
74
  code: "GREP_FAILED",
72
75
  data: {
73
76
  code: result.code,
74
- stderr: result.stderr
77
+ stderr
75
78
  },
76
- message: `${command.cmd} failed (${result.code}): ${result.stderr.slice(0, 500)}`
79
+ message: `${command.cmd} failed (${result.code}): ${stderr.slice(0, 500)}`
77
80
  });
78
- const text = cleanTextTui(result.stdout);
79
81
  const limit = args.limit ?? DEFAULT_LIMIT;
80
82
  const summary = truncate(text, {
81
83
  maxLineChars: command.kind === "rg" ? 500 : MAX_COLUMNS,
@@ -90,11 +92,12 @@ const grepTool = defineTool({
90
92
  truncated
91
93
  };
92
94
  const parts = [];
93
- const stderr = result.stderr.trim();
94
- if (stderr || result.killReason) {
95
+ if (stderr || result.killReason || failed) {
95
96
  const meta = {
97
+ code: result.code,
96
98
  command: command.cmd,
97
99
  cwd,
100
+ incomplete: failed || !!result.killReason,
98
101
  truncated
99
102
  };
100
103
  if (result.killReason) meta.killReason = result.killReason;
@@ -109,6 +112,13 @@ const grepTool = defineTool({
109
112
  text: summary.text || "No matches found.",
110
113
  type: "text"
111
114
  });
115
+ if (failed) {
116
+ const warning = `Warning: Search incomplete (${command.kind} exited with code ${result.code}).`;
117
+ parts.push({
118
+ text: stderr ? `${warning}\n${stderr.slice(0, 500)}` : warning,
119
+ type: "text"
120
+ });
121
+ }
112
122
  return parts;
113
123
  }
114
124
  });
@@ -173,4 +183,4 @@ function grepGlobArgs(glob) {
173
183
  //#endregion
174
184
  export { grepTool };
175
185
 
176
- //# sourceMappingURL=grep-6O3mjSvV.mjs.map
186
+ //# sourceMappingURL=grep-By4wLRDk.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grep-By4wLRDk.mjs","names":[],"sources":["../src/tools/grep.ts"],"sourcesContent":["import type { MetaPart, TextPart, ToolContext } from \"@zaly/ai\"\n\nimport { AiError, defineTool } from \"@zaly/ai\"\nimport { normPath } from \"@zaly/shared\"\nimport { defaultExcludes } from \"@zaly/shared/find\"\nimport { Spawn, TextStream, which } from \"@zaly/shared/process\"\nimport { cleanTextTui } from \"@zaly/shared/text\"\nimport { Type } from \"typebox\"\nimport { truncate } from \"../utils/truncate.ts\"\n\nconst MAX_BUFFER = 512 * 1024\nconst DEFAULT_LIMIT = 100\nconst MAX_LIMIT = 1000\nconst MAX_COLUMNS = 200\n\nexport type GrepTool = typeof grepTool\nexport type GrepToolMeta = {\n cwd: string\n pattern: string\n truncated: boolean\n cmd: string[]\n}\n\n// oxlint-disable-next-line sort-keys -- semantic field order: name, desc, params, call\nexport const grepTool = defineTool({\n name: \"grep\",\n desc:\n `Search file contents using rg/grep. Respects .gitignore by default when ripgrep is available; ` +\n \"prefer this over bash grep/find for code search. Returns human-readable grouped matches with line numbers.\",\n parallel: true,\n // oxlint-disable-next-line sort-keys -- semantic param order\n params: Type.Object({\n pattern: Type.String({\n description:\n \"Text or regex pattern. Uses smart case: case-insensitive unless uppercase is present.\",\n minLength: 1,\n }),\n paths: Type.Optional(\n Type.Array(Type.String(), {\n description: \"Optional files/directories to search, relative to cwd or absolute.\",\n })\n ),\n glob: Type.Optional(\n Type.Array(Type.String(), {\n description: \"Ripgrep glob filters, e.g. [`*.ts`, `!dist/**`].\",\n })\n ),\n fixed_strings: Type.Optional(\n Type.Boolean({\n default: false,\n description: \"Treat all patterns as literals instead of as regular expressions\",\n })\n ),\n context: Type.Optional(\n Type.Integer({\n default: 0,\n description: \"Context lines before/after each match. 0-10.\",\n maximum: 10,\n minimum: 0,\n })\n ),\n limit: Type.Optional(\n Type.Integer({\n default: DEFAULT_LIMIT,\n description: \"Maximum output lines to keep inline, including file headings and context.\",\n maximum: MAX_LIMIT,\n minimum: 1,\n })\n ),\n }),\n\n async preflight(args, ctx: ToolContext<GrepToolMeta>) {\n const cwd = normPath(ctx.cwd, \".\")\n await ctx.need?.(\"read\", cwd)\n\n const paths = (args.paths?.length ? args.paths : [\".\"]).map((p) => normPath(cwd, p))\n await Promise.all(paths.map((path) => Promise.resolve(ctx.need?.(\"read\", path))))\n },\n\n async call(args, ctx: ToolContext<GrepToolMeta>): Promise<(MetaPart | TextPart)[]> {\n const command = resolveGrep()\n if (!command) throw new AiError({ code: \"MISSING_TOOL\", message: \"grep requires rg or grep\" })\n\n const cwd = normPath(ctx.cwd, \".\")\n const paths = args.paths?.length ? args.paths : []\n\n const cmdArgs = buildArgs(command.kind, args, paths)\n const proc = new Spawn(command.cmd, cmdArgs, {\n cwd,\n maxBuffer: MAX_BUFFER,\n signal: ctx.signal,\n stderr: new TextStream(),\n stdout: new TextStream(),\n timeout: 60_000,\n })\n const result = await proc.result.catch((error: unknown) => {\n throw new AiError({ cause: error, code: \"GREP_FAILED\", message: String(error) })\n })\n\n const text = cleanTextTui(result.stdout)\n const stderr = cleanTextTui(result.stderr).trim()\n // rg/grep can report matches and still exit with an error for another search path.\n const failed = result.code > 1 && result.killReason !== \"maxBuffer\"\n if (failed && !text.trim()) {\n throw new AiError({\n code: \"GREP_FAILED\",\n data: { code: result.code, stderr },\n message: `${command.cmd} failed (${result.code}): ${stderr.slice(0, 500)}`,\n })\n }\n\n const limit = args.limit ?? DEFAULT_LIMIT\n const summary = truncate(text, {\n maxLineChars: command.kind === \"rg\" ? 500 : MAX_COLUMNS,\n maxLines: limit + 1,\n strategy: \"head\",\n })\n\n const truncated = summary.truncated || result.killReason === \"maxBuffer\"\n ctx.meta = {\n cmd: [command.cmd, ...cmdArgs],\n cwd,\n pattern: args.pattern,\n truncated,\n }\n\n const parts: (MetaPart | TextPart)[] = []\n\n if (stderr || result.killReason || failed) {\n const meta: Record<string, unknown> = {\n code: result.code,\n command: command.cmd,\n cwd,\n incomplete: failed || !!result.killReason,\n truncated,\n }\n if (result.killReason) meta.killReason = result.killReason\n if (stderr) meta.stderr = stderr.slice(0, 500)\n parts.push({ data: meta, tag: \"grep\", type: \"meta\" })\n }\n parts.push({ text: summary.text || \"No matches found.\", type: \"text\" })\n if (failed) {\n const warning = `Warning: Search incomplete (${command.kind} exited with code ${result.code}).`\n parts.push({ text: stderr ? `${warning}\\n${stderr.slice(0, 500)}` : warning, type: \"text\" })\n }\n return parts\n },\n})\n\ntype GrepArgs = Parameters<GrepTool[\"call\"]>[0]\ntype GrepBackend = { cmd: string; kind: \"rg\" | \"grep\" }\n\nfunction resolveGrep(): GrepBackend | undefined {\n const rg = which(\"rg\")\n if (rg) return { cmd: rg, kind: \"rg\" }\n const grep = which(\"grep\")\n return grep ? { cmd: grep, kind: \"grep\" } : undefined\n}\n\nfunction buildArgs(kind: GrepBackend[\"kind\"], args: GrepArgs, paths: string[]): string[] {\n return kind === \"rg\" ? buildRgArgs(args, paths) : buildGrepArgs(args, paths)\n}\n\nfunction buildRgArgs(args: GrepArgs, paths: string[]): string[] {\n const ret = [\n \"--color=always\",\n \"--heading\",\n \"--with-filename\",\n \"--line-number\",\n `--max-columns=${MAX_COLUMNS}`,\n \"--max-columns-preview\",\n \"--path-separator=/\",\n ]\n\n if (args.fixed_strings) ret.push(\"--fixed-strings\")\n ret.push(\"--smart-case\", \"--no-hidden\")\n const context = args.context ?? 0\n if (context > 0) ret.push(`--context=${context}`)\n\n for (const e of defaultExcludes(paths)) ret.push(\"--glob\", `!${e}`)\n for (const g of args.glob ?? []) ret.push(\"--glob\", g)\n\n ret.push(\"--\", args.pattern, ...paths)\n return ret\n}\n\nfunction buildGrepArgs(args: GrepArgs, paths: string[]): string[] {\n const ret = [\n \"--recursive\",\n \"--line-number\",\n \"--with-filename\",\n \"--binary-files=without-match\",\n \"--color=always\",\n ]\n if (args.fixed_strings) ret.push(\"--fixed-strings\")\n else ret.push(\"--extended-regexp\")\n const hasUpper = /[A-Z]/.test(args.pattern)\n if (!hasUpper) ret.push(\"--ignore-case\")\n const context = args.context ?? 0\n if (context > 0) ret.push(\"--context\", String(context))\n for (const e of defaultExcludes(paths)) ret.push(\"--exclude-dir\", e)\n for (const g of args.glob ?? []) ret.push(...grepGlobArgs(g))\n ret.push(\"--\", args.pattern, ...paths)\n return ret\n}\n\nfunction grepGlobArgs(glob: string): string[] {\n if (!glob.startsWith(\"!\")) return [\"--include\", glob]\n const exclude = glob.slice(1)\n if (exclude.endsWith(\"/**\") && !exclude.slice(0, -3).includes(\"*\")) {\n return [\"--exclude-dir\", exclude.slice(0, -3)]\n }\n return [\"--exclude\", exclude]\n}\n"],"mappings":"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"}