@illuma-ai/agents 1.4.0-alpha.2 → 1.4.0-alpha.3

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 (42) hide show
  1. package/dist/cjs/main.cjs +37 -27
  2. package/dist/cjs/main.cjs.map +1 -1
  3. package/dist/cjs/tools/artifacts/schema.cjs +63 -0
  4. package/dist/cjs/tools/artifacts/schema.cjs.map +1 -0
  5. package/dist/cjs/tools/artifacts/tool.cjs +213 -0
  6. package/dist/cjs/tools/artifacts/tool.cjs.map +1 -0
  7. package/dist/cjs/tools/fileSearch/formatter.cjs +2 -4
  8. package/dist/cjs/tools/fileSearch/formatter.cjs.map +1 -1
  9. package/dist/cjs/tools/fileSearch/ragClient.cjs +4 -6
  10. package/dist/cjs/tools/fileSearch/ragClient.cjs.map +1 -1
  11. package/dist/cjs/tools/fileSearch/schema.cjs.map +1 -1
  12. package/dist/cjs/tools/fileSearch/tool.cjs.map +1 -1
  13. package/dist/esm/main.mjs +2 -0
  14. package/dist/esm/main.mjs.map +1 -1
  15. package/dist/esm/tools/artifacts/schema.mjs +56 -0
  16. package/dist/esm/tools/artifacts/schema.mjs.map +1 -0
  17. package/dist/esm/tools/artifacts/tool.mjs +207 -0
  18. package/dist/esm/tools/artifacts/tool.mjs.map +1 -0
  19. package/dist/esm/tools/fileSearch/formatter.mjs +2 -4
  20. package/dist/esm/tools/fileSearch/formatter.mjs.map +1 -1
  21. package/dist/esm/tools/fileSearch/ragClient.mjs +4 -6
  22. package/dist/esm/tools/fileSearch/ragClient.mjs.map +1 -1
  23. package/dist/esm/tools/fileSearch/schema.mjs.map +1 -1
  24. package/dist/esm/tools/fileSearch/tool.mjs.map +1 -1
  25. package/dist/types/index.d.ts +1 -0
  26. package/dist/types/tools/artifacts/index.d.ts +3 -0
  27. package/dist/types/tools/artifacts/schema.d.ts +63 -0
  28. package/dist/types/tools/artifacts/tool.d.ts +16 -0
  29. package/dist/types/tools/artifacts/types.d.ts +127 -0
  30. package/package.json +1 -1
  31. package/src/index.ts +1 -0
  32. package/src/tools/artifacts/__tests__/tool.test.ts +243 -0
  33. package/src/tools/artifacts/index.ts +33 -0
  34. package/src/tools/artifacts/schema.ts +76 -0
  35. package/src/tools/artifacts/tool.ts +277 -0
  36. package/src/tools/artifacts/types.ts +149 -0
  37. package/src/tools/fileSearch/__tests__/tool.test.ts +20 -10
  38. package/src/tools/fileSearch/formatter.ts +5 -7
  39. package/src/tools/fileSearch/ragClient.ts +6 -10
  40. package/src/tools/fileSearch/schema.ts +2 -2
  41. package/src/tools/fileSearch/tool.ts +6 -6
  42. package/src/tools/fileSearch/types.ts +4 -2
@@ -0,0 +1,277 @@
1
+ /**
2
+ * artifact_tool + content_reader library factories.
3
+ *
4
+ * The library owns the LangChain wiring (schema, description, response
5
+ * shape) and the action dispatch; the runtime supplies a handler bundle
6
+ * matching the `ArtifactWriteHandlers` / `ContentReadHandlers` interface.
7
+ *
8
+ * This keeps 800+ LOC of host-specific handler logic (S3 adapters, file
9
+ * model CRUD, syntax checkers, line utils) out of the library while
10
+ * still centralizing the tool surface every runtime shares.
11
+ */
12
+
13
+ import { tool, DynamicStructuredTool } from '@langchain/core/tools';
14
+ import {
15
+ artifactToolSchema,
16
+ contentReaderSchema,
17
+ ARTIFACT_TOOL_NAME,
18
+ CONTENT_READER_NAME,
19
+ } from './schema';
20
+ import type {
21
+ ArtifactToolConfig,
22
+ ContentReaderToolConfig,
23
+ ArtifactToolScope,
24
+ ArtifactToolResult,
25
+ WriteArgs,
26
+ EditArgs,
27
+ VerifyArgs,
28
+ DeleteArgs,
29
+ ReadArgs,
30
+ SearchArgs,
31
+ ListArgs,
32
+ InfoArgs,
33
+ ContentIdResolver,
34
+ } from './types';
35
+
36
+ const DEFAULT_ARTIFACT_DESCRIPTION = `Author content artifacts that render live in the host's preview panel — this tool does NOT produce downloadable files (use execute_code for those).
37
+
38
+ Actions:
39
+ - write: Create a new artifact. Write a COMPLETE file in one call. The \`name\` field MUST include the file extension — it routes the preview (.tsx, .html, .mmd, .svg, .csv, .json, .dot, .md, .drawio).
40
+ - verify: Check an artifact for syntax errors. REQUIRED as the next step after every write/edit on code files — do not render until verify passes.
41
+ - edit: Surgical string replacement — provide old_str (exact match) and new_str. Works on all file types.
42
+ - delete: Remove an artifact and its backing file.
43
+
44
+ Artifacts are persisted by the host. No manual save needed.`;
45
+
46
+ const DEFAULT_CONTENT_READER_DESCRIPTION = `Read and navigate stored content — artifacts authored by artifact_tool, large tool results auto-cached by the host, uploaded file attachments, and code blocks.
47
+
48
+ Read-only surface. Use write/edit tools (artifact_tool, execute_code) to mutate.
49
+
50
+ Actions:
51
+ - read: Return line ranges from a specific content_id.
52
+ - search: Regex search across a specific content_id with paginated matches.
53
+ - list: Enumerate every content entry currently stored.
54
+ - info: Metadata (size, kind, creation time) for a specific content_id.`;
55
+
56
+ /**
57
+ * Optional content_id self-healing — if the runtime supplies a resolver,
58
+ * we pre-resolve the ID on every action that takes one so nicknames
59
+ * (e.g., "Dashboard") map to canonical IDs.
60
+ */
61
+ async function resolveContentIdIfPresent(
62
+ resolver: ContentIdResolver | undefined,
63
+ id: string | undefined,
64
+ scope: ArtifactToolScope,
65
+ logger?: { debug: (msg: string) => void },
66
+ ): Promise<string | undefined> {
67
+ if (!resolver || !id) return id;
68
+ const out = await resolver.resolve(id, scope);
69
+ if (!out) return id;
70
+ if (out.resolvedId !== id) {
71
+ logger?.debug(
72
+ `[artifact] resolved "${id}" → "${out.resolvedId}"${out.resolvedName ? ` ("${out.resolvedName}")` : ''}`,
73
+ );
74
+ }
75
+ return out.resolvedId;
76
+ }
77
+
78
+ // ─── Writer tool (artifact_tool) ─────────────────────────────────────────
79
+
80
+ export function createArtifactTool(
81
+ config: ArtifactToolConfig,
82
+ ): DynamicStructuredTool {
83
+ const { handlers, getScope, resolver, logger, descriptionOverride } = config;
84
+
85
+ return tool(
86
+ async (rawInput, runnableConfig): Promise<ArtifactToolResult> => {
87
+ const scope = getScope(runnableConfig);
88
+ if (!scope) {
89
+ logger?.warn('[artifact_tool] no scope resolved from runnableConfig');
90
+ return ['Error: No conversation context available', {}];
91
+ }
92
+
93
+ const input = rawInput as {
94
+ action: 'write' | 'edit' | 'verify' | 'delete';
95
+ content_id?: string;
96
+ content?: string;
97
+ name?: string;
98
+ old_str?: string;
99
+ new_str?: string;
100
+ replace_all?: boolean;
101
+ };
102
+
103
+ const resolvedContentId = await resolveContentIdIfPresent(
104
+ resolver,
105
+ input.content_id,
106
+ scope,
107
+ logger,
108
+ );
109
+
110
+ const started = Date.now();
111
+ try {
112
+ switch (input.action) {
113
+ case 'write': {
114
+ const args: WriteArgs = {
115
+ action: 'write',
116
+ content_id: resolvedContentId,
117
+ content: input.content ?? '',
118
+ name: input.name,
119
+ };
120
+ if (!args.content) return ['Error: write requires content', {}];
121
+ return await handlers.write(args, scope);
122
+ }
123
+ case 'edit': {
124
+ if (!resolvedContentId) return ['Error: edit requires content_id', {}];
125
+ const args: EditArgs = {
126
+ action: 'edit',
127
+ content_id: resolvedContentId,
128
+ old_str: input.old_str ?? '',
129
+ new_str: input.new_str ?? '',
130
+ replace_all: input.replace_all,
131
+ };
132
+ if (!args.old_str) return ['Error: edit requires old_str', {}];
133
+ return await handlers.edit(args, scope);
134
+ }
135
+ case 'verify': {
136
+ if (!resolvedContentId) return ['Error: verify requires content_id', {}];
137
+ const args: VerifyArgs = {
138
+ action: 'verify',
139
+ content_id: resolvedContentId,
140
+ };
141
+ return await handlers.verify(args, scope);
142
+ }
143
+ case 'delete': {
144
+ if (!resolvedContentId) return ['Error: delete requires content_id', {}];
145
+ const args: DeleteArgs = {
146
+ action: 'delete',
147
+ content_id: resolvedContentId,
148
+ };
149
+ return await handlers.delete(args, scope);
150
+ }
151
+ default:
152
+ return [`Unknown action: ${(input as { action: string }).action}`, {}];
153
+ }
154
+ } catch (err) {
155
+ const e = err instanceof Error ? err : new Error(String(err));
156
+ logger?.error('[artifact_tool] handler threw', {
157
+ action: input.action,
158
+ contentId: resolvedContentId,
159
+ error: e.message,
160
+ elapsed: `${Date.now() - started}ms`,
161
+ });
162
+ return [`Error: ${e.message}`, {}];
163
+ }
164
+ },
165
+ {
166
+ name: ARTIFACT_TOOL_NAME,
167
+ responseFormat: 'content_and_artifact',
168
+ description: descriptionOverride ?? DEFAULT_ARTIFACT_DESCRIPTION,
169
+ schema: artifactToolSchema,
170
+ },
171
+ );
172
+ }
173
+
174
+ // ─── Reader tool (content_reader) ────────────────────────────────────────
175
+
176
+ export function createContentReaderTool(
177
+ config: ContentReaderToolConfig,
178
+ ): DynamicStructuredTool {
179
+ const { handlers, getScope, resolver, logger, descriptionOverride } = config;
180
+
181
+ return tool(
182
+ async (rawInput, runnableConfig): Promise<ArtifactToolResult> => {
183
+ const scope = getScope(runnableConfig);
184
+ if (!scope) {
185
+ logger?.warn('[content_reader] no scope resolved from runnableConfig');
186
+ return ['Error: No conversation context available', {}];
187
+ }
188
+
189
+ const input = rawInput as {
190
+ action: 'read' | 'search' | 'list' | 'info';
191
+ content_id?: string;
192
+ start_line?: number;
193
+ end_line?: number;
194
+ pattern?: string;
195
+ flags?: string;
196
+ context?: number;
197
+ offset?: number;
198
+ limit?: number;
199
+ };
200
+
201
+ const resolvedContentId = await resolveContentIdIfPresent(
202
+ resolver,
203
+ input.content_id,
204
+ scope,
205
+ logger,
206
+ );
207
+
208
+ const started = Date.now();
209
+ try {
210
+ switch (input.action) {
211
+ case 'read': {
212
+ if (!resolvedContentId) return ['Error: read requires content_id', {}];
213
+ const args: ReadArgs = {
214
+ action: 'read',
215
+ content_id: resolvedContentId,
216
+ start_line: input.start_line,
217
+ end_line: input.end_line,
218
+ offset: input.offset,
219
+ limit: input.limit,
220
+ };
221
+ return await handlers.read(args, scope);
222
+ }
223
+ case 'search': {
224
+ if (!resolvedContentId) return ['Error: search requires content_id', {}];
225
+ if (!input.pattern) return ['Error: search requires pattern', {}];
226
+ const args: SearchArgs = {
227
+ action: 'search',
228
+ content_id: resolvedContentId,
229
+ pattern: input.pattern,
230
+ flags: input.flags,
231
+ context: input.context,
232
+ offset: input.offset,
233
+ limit: input.limit,
234
+ };
235
+ return await handlers.search(args, scope);
236
+ }
237
+ case 'list': {
238
+ const args: ListArgs = { action: 'list' };
239
+ return await handlers.list(args, scope);
240
+ }
241
+ case 'info': {
242
+ if (!resolvedContentId) return ['Error: info requires content_id', {}];
243
+ const args: InfoArgs = {
244
+ action: 'info',
245
+ content_id: resolvedContentId,
246
+ };
247
+ return await handlers.info(args, scope);
248
+ }
249
+ default:
250
+ return [`Unknown action: ${(input as { action: string }).action}`, {}];
251
+ }
252
+ } catch (err) {
253
+ const e = err instanceof Error ? err : new Error(String(err));
254
+ logger?.error('[content_reader] handler threw', {
255
+ action: input.action,
256
+ contentId: resolvedContentId,
257
+ error: e.message,
258
+ elapsed: `${Date.now() - started}ms`,
259
+ });
260
+ return [`Error: ${e.message}`, {}];
261
+ }
262
+ },
263
+ {
264
+ name: CONTENT_READER_NAME,
265
+ responseFormat: 'content_and_artifact',
266
+ description: descriptionOverride ?? DEFAULT_CONTENT_READER_DESCRIPTION,
267
+ schema: contentReaderSchema,
268
+ },
269
+ );
270
+ }
271
+
272
+ export {
273
+ ARTIFACT_TOOL_NAME,
274
+ CONTENT_READER_NAME,
275
+ ARTIFACT_WRITE_ACTIONS,
276
+ CONTENT_READ_ACTIONS,
277
+ } from './schema';
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Artifact-tool types. The library owns the LangChain wiring, schema, and
3
+ * action dispatch. Runtime supplies an `ArtifactHandlers` bundle — one
4
+ * function per action — so ranger reuses its existing handlers, CLI
5
+ * supplies disk-backed ones, and A2A server supplies buffer-backed ones.
6
+ *
7
+ * Each handler receives:
8
+ * - `args`: the parsed input (typed per action)
9
+ * - `scope`: runtime-resolved scope identity (conversationId + userId
10
+ * for ranger, agent-run-id for CLI, a2a-task-id for A2A)
11
+ *
12
+ * Each handler returns a `[llmText, toolArtifact]` tuple matching
13
+ * LangChain's `content_and_artifact` response format.
14
+ */
15
+
16
+ export interface ArtifactToolScope {
17
+ /**
18
+ * Primary scope — whatever the runtime uses to silo artifacts per
19
+ * session/run/conversation. Ranger uses `conversationId`; CLI uses
20
+ * `agent-run-id` or `agent-id`; A2A uses `a2a-task-id`.
21
+ */
22
+ conversationId: string;
23
+ /** Optional — present when the runtime has an authenticated user. */
24
+ userId?: string;
25
+ /** Free-form extension — runtimes can add their own fields. */
26
+ [key: string]: unknown;
27
+ }
28
+
29
+ export type ArtifactToolResult = [llmText: string, artifact?: unknown];
30
+
31
+ // ─── Action arg shapes ────────────────────────────────────────────────────
32
+
33
+ export interface WriteArgs {
34
+ action: 'write';
35
+ content_id?: string;
36
+ content: string;
37
+ name?: string;
38
+ }
39
+
40
+ export interface EditArgs {
41
+ action: 'edit';
42
+ content_id: string;
43
+ old_str: string;
44
+ new_str: string;
45
+ replace_all?: boolean;
46
+ }
47
+
48
+ export interface VerifyArgs {
49
+ action: 'verify';
50
+ content_id: string;
51
+ }
52
+
53
+ export interface DeleteArgs {
54
+ action: 'delete';
55
+ content_id: string;
56
+ }
57
+
58
+ export interface ReadArgs {
59
+ action: 'read';
60
+ content_id: string;
61
+ start_line?: number;
62
+ end_line?: number;
63
+ offset?: number;
64
+ limit?: number;
65
+ }
66
+
67
+ export interface SearchArgs {
68
+ action: 'search';
69
+ content_id: string;
70
+ pattern: string;
71
+ flags?: string;
72
+ context?: number;
73
+ offset?: number;
74
+ limit?: number;
75
+ }
76
+
77
+ export interface ListArgs {
78
+ action: 'list';
79
+ }
80
+
81
+ export interface InfoArgs {
82
+ action: 'info';
83
+ content_id: string;
84
+ }
85
+
86
+ export type ArtifactWriteAction = WriteArgs | EditArgs | VerifyArgs | DeleteArgs;
87
+ export type ArtifactReadAction = ReadArgs | SearchArgs | ListArgs | InfoArgs;
88
+
89
+ // ─── Handler bundles ──────────────────────────────────────────────────────
90
+
91
+ /** Writer-surface handlers — invoked by `artifact_tool`. */
92
+ export interface ArtifactWriteHandlers {
93
+ write(args: WriteArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
94
+ edit(args: EditArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
95
+ verify(args: VerifyArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
96
+ delete(args: DeleteArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
97
+ }
98
+
99
+ /** Reader-surface handlers — invoked by `content_reader`. */
100
+ export interface ContentReadHandlers {
101
+ read(args: ReadArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
102
+ search(args: SearchArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
103
+ list(args: ListArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
104
+ info(args: InfoArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
105
+ }
106
+
107
+ /**
108
+ * Optional content_id self-healing: runtimes that want to let the LLM
109
+ * pass nicknames (e.g., "Dashboard") instead of canonical IDs implement
110
+ * this. Library falls through when unset.
111
+ */
112
+ export interface ContentIdResolver {
113
+ resolve(
114
+ id: string,
115
+ scope: ArtifactToolScope,
116
+ ): Promise<{ resolvedId: string; resolvedName?: string } | null>;
117
+ }
118
+
119
+ export interface ArtifactToolLogger {
120
+ debug: (msg: string, ...args: unknown[]) => void;
121
+ info: (msg: string, ...args: unknown[]) => void;
122
+ warn: (msg: string, ...args: unknown[]) => void;
123
+ error: (msg: string, ...args: unknown[]) => void;
124
+ }
125
+
126
+ export interface ArtifactToolBaseConfig {
127
+ /**
128
+ * Resolves the runtime scope from the LangChain `config` object on each
129
+ * invocation. Ranger pulls `conversationId` + `userId` from request
130
+ * metadata; CLI pulls `agent-run-id` from its runner context.
131
+ */
132
+ getScope: (config: unknown) => ArtifactToolScope | null;
133
+ resolver?: ContentIdResolver;
134
+ logger?: ArtifactToolLogger;
135
+ /**
136
+ * Description override. Host can inject brand-specific guidance
137
+ * (e.g., ranger's HTML branding mandate, TSX style rules). Defaults
138
+ * to a generic description appropriate for any runtime.
139
+ */
140
+ descriptionOverride?: string;
141
+ }
142
+
143
+ export interface ArtifactToolConfig extends ArtifactToolBaseConfig {
144
+ handlers: ArtifactWriteHandlers;
145
+ }
146
+
147
+ export interface ContentReaderToolConfig extends ArtifactToolBaseConfig {
148
+ handlers: ContentReadHandlers;
149
+ }
@@ -8,16 +8,26 @@
8
8
  */
9
9
 
10
10
  import { createFileSearchTool } from '../tool';
11
- import { plainTextFormatter, createCitationAnchorFormatter } from '../formatter';
12
- import type { RagClient, RagQueryParams, RagChunk, FileSearchFile } from '../types';
11
+ import {
12
+ plainTextFormatter,
13
+ createCitationAnchorFormatter,
14
+ } from '../formatter';
15
+ import type {
16
+ RagClient,
17
+ RagQueryParams,
18
+ RagChunk,
19
+ FileSearchFile,
20
+ } from '../types';
13
21
 
14
22
  // Build a mock RagClient that records every query it receives and returns
15
23
  // a deterministic chunk set per file.
16
- function makeRagClient(opts: {
17
- chunksByFile?: Record<string, RagChunk[]>;
18
- failFileIds?: Set<string>;
19
- hang?: boolean;
20
- } = {}) {
24
+ function makeRagClient(
25
+ opts: {
26
+ chunksByFile?: Record<string, RagChunk[]>;
27
+ failFileIds?: Set<string>;
28
+ hang?: boolean;
29
+ } = {}
30
+ ) {
21
31
  const calls: RagQueryParams[] = [];
22
32
  const client: RagClient = {
23
33
  async query(params) {
@@ -119,7 +129,7 @@ describe('createFileSearchTool', () => {
119
129
  expect(calls.length).toBe(3);
120
130
  expect(onFileError).toHaveBeenCalledWith(
121
131
  expect.objectContaining({ file_id: 'f-beta' }),
122
- expect.any(Error),
132
+ expect.any(Error)
123
133
  );
124
134
  });
125
135
 
@@ -142,7 +152,7 @@ describe('createFileSearchTool', () => {
142
152
  entity_id: 'tenant-42',
143
153
  scope: 'user:alice',
144
154
  authHeaders: { Authorization: 'Bearer TOKEN' },
145
- }),
155
+ })
146
156
  );
147
157
  });
148
158
 
@@ -236,7 +246,7 @@ function chunk(
236
246
  file_id: string,
237
247
  text: string,
238
248
  distance: number,
239
- metadata?: Record<string, unknown>,
249
+ metadata?: Record<string, unknown>
240
250
  ): RagChunk {
241
251
  return { file_id, page_content: text, distance, metadata };
242
252
  }
@@ -63,14 +63,14 @@ export interface CitationAnchorFormatterOptions {
63
63
  }
64
64
 
65
65
  export function createCitationAnchorFormatter(
66
- opts: CitationAnchorFormatterOptions = {},
66
+ opts: CitationAnchorFormatterOptions = {}
67
67
  ): FileSearchResultFormatter {
68
68
  const toolName = opts.toolName ?? 'file_search';
69
- const getOffset = opts.getSourceOffset ?? (() => 0);
70
- const advance = opts.advanceSourceOffset ?? (() => {});
69
+ const getOffset = opts.getSourceOffset ?? ((): number => 0);
70
+ const advance = opts.advanceSourceOffset ?? ((_by: number): void => {});
71
71
 
72
72
  return {
73
- format(chunks) {
73
+ format(chunks): { message: string; artifact?: unknown } {
74
74
  if (chunks.length === 0) {
75
75
  return {
76
76
  message:
@@ -100,9 +100,7 @@ export function createCitationAnchorFormatter(
100
100
  relevance: 1 - c.distance,
101
101
  pages: getPage(c) != null ? [getPage(c) as number] : [],
102
102
  pageRelevance:
103
- getPage(c) != null
104
- ? { [getPage(c) as number]: 1 - c.distance }
105
- : {},
103
+ getPage(c) != null ? { [getPage(c) as number]: 1 - c.distance } : {},
106
104
  }));
107
105
 
108
106
  advance(chunks.length);
@@ -21,14 +21,11 @@ export const RAG_API_URL_ENV = 'RAG_API_URL';
21
21
 
22
22
  /** Resolve base URL at call time so env-var changes propagate. */
23
23
  export function getRagBaseUrl(override?: string): string {
24
- const url =
25
- override ??
26
- getEnvironmentVariable(RAG_API_URL_ENV) ??
27
- '';
24
+ const url = override ?? getEnvironmentVariable(RAG_API_URL_ENV) ?? '';
28
25
  if (!url) {
29
26
  throw new Error(
30
27
  `file_search: ${RAG_API_URL_ENV} is not configured. ` +
31
- `Set the env var or pass baseUrl to HttpRagClient.`,
28
+ `Set the env var or pass baseUrl to HttpRagClient.`
32
29
  );
33
30
  }
34
31
  return url.replace(/\/$/, '');
@@ -112,7 +109,7 @@ export class HttpRagClient implements RagClient {
112
109
  if (!res.ok) {
113
110
  const text = await res.text().catch(() => '');
114
111
  throw new Error(
115
- `RAG query failed: ${res.status} ${res.statusText} — ${text.slice(0, 200)}`,
112
+ `RAG query failed: ${res.status} ${res.statusText} — ${text.slice(0, 200)}`
116
113
  );
117
114
  }
118
115
  const json = (await res.json()) as RagApiResponse;
@@ -131,11 +128,10 @@ export class HttpRagClient implements RagClient {
131
128
  return resp
132
129
  .filter((row) => Array.isArray(row) && row.length === 2)
133
130
  .map(([doc, distance]) => ({
134
- file_id:
135
- (doc?.metadata?.file_id as string | undefined) ?? file_id,
136
- page_content: doc?.page_content ?? '',
131
+ file_id: (doc.metadata?.file_id as string | undefined) ?? file_id,
132
+ page_content: doc.page_content ?? '',
137
133
  distance: typeof distance === 'number' ? distance : 1,
138
- metadata: doc?.metadata,
134
+ metadata: doc.metadata,
139
135
  }));
140
136
  }
141
137
  }
@@ -4,13 +4,13 @@ export const fileSearchInputSchema = z.object({
4
4
  query: z
5
5
  .string()
6
6
  .describe(
7
- "A natural language query to search for relevant information in the files. Be SPECIFIC and TARGETED — use keywords for the specific section or topic you need. For comprehensive tasks (summaries, overviews), call this tool multiple times with different targeted queries (e.g., 'introduction', 'methodology', 'results', 'conclusions') rather than one broad query.",
7
+ "A natural language query to search for relevant information in the files. Be SPECIFIC and TARGETED — use keywords for the specific section or topic you need. For comprehensive tasks (summaries, overviews), call this tool multiple times with different targeted queries (e.g., 'introduction', 'methodology', 'results', 'conclusions') rather than one broad query."
8
8
  ),
9
9
  target_files: z
10
10
  .array(z.string())
11
11
  .optional()
12
12
  .describe(
13
- 'Optional list of filenames (or partial names) to limit the search to. When provided, only files whose name contains one of these strings will be searched. Use this to avoid searching irrelevant files. Omit to search all available files.',
13
+ 'Optional list of filenames (or partial names) to limit the search to. When provided, only files whose name contains one of these strings will be searched. Use this to avoid searching irrelevant files. Omit to search all available files.'
14
14
  ),
15
15
  });
16
16
 
@@ -54,7 +54,7 @@ Cite EVERY statement derived from file content. Place the citation anchor IMMEDI
54
54
  }
55
55
 
56
56
  export function createFileSearchTool(
57
- config: FileSearchToolConfig,
57
+ config: FileSearchToolConfig
58
58
  ): DynamicStructuredTool {
59
59
  const {
60
60
  ragClient,
@@ -97,16 +97,16 @@ export function createFileSearchTool(
97
97
  if (target_files && target_files.length > 0) {
98
98
  const lowerTargets = target_files.map((t) => t.toLowerCase());
99
99
  const matched = files.filter((f) =>
100
- lowerTargets.some((t) => f.filename.toLowerCase().includes(t)),
100
+ lowerTargets.some((t) => f.filename.toLowerCase().includes(t))
101
101
  );
102
102
  if (matched.length === 0) {
103
103
  logger?.warn(
104
- `[file_search] No files matched target_files ${target_files.join(', ')}; falling back to all files`,
104
+ `[file_search] No files matched target_files ${target_files.join(', ')}; falling back to all files`
105
105
  );
106
106
  filesToQuery = files;
107
107
  } else {
108
108
  logger?.info(
109
- `[file_search] Filtered to ${matched.length}/${files.length} via target_files`,
109
+ `[file_search] Filtered to ${matched.length}/${files.length} via target_files`
110
110
  );
111
111
  filesToQuery = matched;
112
112
  }
@@ -131,7 +131,7 @@ export function createFileSearchTool(
131
131
  } catch (err) {
132
132
  const e = err instanceof Error ? err : new Error(String(err));
133
133
  logger?.error(
134
- `[file_search] Query failed for ${file.filename}: ${e.message}`,
134
+ `[file_search] Query failed for ${file.filename}: ${e.message}`
135
135
  );
136
136
  callbacks?.onFileError?.(file, e);
137
137
  return [];
@@ -200,7 +200,7 @@ export function createFileSearchTool(
200
200
  responseFormat: 'content_and_artifact',
201
201
  description: buildDescription({ fileCitations }),
202
202
  schema: fileSearchInputSchema,
203
- },
203
+ }
204
204
  );
205
205
  }
206
206
 
@@ -79,7 +79,7 @@ export interface FileSearchResultFormatter {
79
79
  callIndex: number;
80
80
  /** Same files list the factory was seeded with (for lookups). */
81
81
  files: FileSearchFile[];
82
- },
82
+ }
83
83
  ): {
84
84
  /** Message returned to the LLM (content). */
85
85
  message: string;
@@ -122,7 +122,9 @@ export interface FileSearchToolConfig {
122
122
  * the host can mint fresh short-lived tokens (ranger's JWT pattern).
123
123
  * When omitted, no auth headers are sent.
124
124
  */
125
- getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
125
+ getAuthHeaders?: () =>
126
+ | Record<string, string>
127
+ | Promise<Record<string, string>>;
126
128
  /**
127
129
  * Result formatter. When omitted, the default plain-text formatter is
128
130
  * used (suitable for CLI/A2A runtimes that don't need citation anchors).