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

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 (30) hide show
  1. package/dist/cjs/providers/tools-server/ToolsServerCapabilityProvider.cjs +8 -1
  2. package/dist/cjs/providers/tools-server/ToolsServerCapabilityProvider.cjs.map +1 -1
  3. package/dist/cjs/providers/types.cjs.map +1 -1
  4. package/dist/cjs/tools/artifacts/schema.cjs +30 -7
  5. package/dist/cjs/tools/artifacts/schema.cjs.map +1 -1
  6. package/dist/cjs/tools/artifacts/tool.cjs +8 -2
  7. package/dist/cjs/tools/artifacts/tool.cjs.map +1 -1
  8. package/dist/cjs/tools/proxyTool.cjs +7 -5
  9. package/dist/cjs/tools/proxyTool.cjs.map +1 -1
  10. package/dist/esm/providers/tools-server/ToolsServerCapabilityProvider.mjs +8 -1
  11. package/dist/esm/providers/tools-server/ToolsServerCapabilityProvider.mjs.map +1 -1
  12. package/dist/esm/providers/types.mjs.map +1 -1
  13. package/dist/esm/tools/artifacts/schema.mjs +30 -7
  14. package/dist/esm/tools/artifacts/schema.mjs.map +1 -1
  15. package/dist/esm/tools/artifacts/tool.mjs +8 -2
  16. package/dist/esm/tools/artifacts/tool.mjs.map +1 -1
  17. package/dist/esm/tools/proxyTool.mjs +7 -5
  18. package/dist/esm/tools/proxyTool.mjs.map +1 -1
  19. package/dist/types/providers/tools-server/ToolsServerCapabilityProvider.d.ts +14 -0
  20. package/dist/types/providers/types.d.ts +14 -0
  21. package/dist/types/tools/proxyTool.d.ts +7 -0
  22. package/package.json +1 -1
  23. package/src/providers/__tests__/ToolsServerCapabilityProvider.test.ts +63 -0
  24. package/src/providers/tools-server/ToolsServerCapabilityProvider.ts +28 -0
  25. package/src/providers/types.ts +17 -0
  26. package/src/tools/artifacts/__tests__/tool.test.ts +31 -15
  27. package/src/tools/artifacts/schema.ts +36 -13
  28. package/src/tools/artifacts/tool.ts +28 -16
  29. package/src/tools/artifacts/types.ts +18 -5
  30. package/src/tools/proxyTool.ts +25 -5
@@ -1 +1 @@
1
- {"version":3,"file":"schema.mjs","sources":["../../../../src/tools/artifacts/schema.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const ARTIFACT_WRITE_ACTIONS = ['write', 'edit', 'verify', 'delete'] as const;\nexport const CONTENT_READ_ACTIONS = ['read', 'search', 'list', 'info'] as const;\n\nexport const artifactToolSchema = z.object({\n action: z\n .enum(ARTIFACT_WRITE_ACTIONS)\n .describe(\n 'Authoring action: write (create/overwrite), edit (str_replace), verify (syntax check), delete (remove).',\n ),\n content_id: z\n .string()\n .optional()\n .describe(\n 'ID of the artifact entry. Required for edit/verify/delete; optional for write (supply to overwrite an existing entry).',\n ),\n\n // write\n content: z\n .string()\n .optional()\n .describe('Full file content (required for write action).'),\n name: z\n .string()\n .optional()\n .describe(\n 'Filename for the new entry (required when creating). MUST include the correct file extension — the extension drives the preview template.',\n ),\n\n // edit (str_replace)\n old_str: z\n .string()\n .optional()\n .describe('Exact string to find and replace (required for edit).'),\n new_str: z.string().optional().describe('Replacement string (required for edit).'),\n replace_all: z\n .boolean()\n .optional()\n .describe(\n 'edit: when true, replaces every occurrence of old_str. When false (default) and old_str matches more than one location, the edit is refused.',\n ),\n});\n\nexport const contentReaderSchema = z.object({\n action: z\n .enum(CONTENT_READ_ACTIONS)\n .describe(\n 'Read-only action: read (lines), search (regex), list (all entries), info (metadata).',\n ),\n content_id: z\n .string()\n .optional()\n .describe(\n 'ID of the content entry. Required for read/search/info. Omit for list.',\n ),\n\n // read pagination\n start_line: z.number().optional().describe('1-based start line for reading.'),\n end_line: z.number().optional().describe('1-based end line (inclusive).'),\n\n // search\n pattern: z.string().optional().describe('Regex pattern (required for search).'),\n flags: z.string().optional().describe('Regex flags (e.g., \"i\" for case-insensitive).'),\n context: z.number().optional().describe('Lines of context around each match.'),\n\n // shared pagination\n offset: z.number().optional().describe('Offset for read or search pagination.'),\n limit: z.number().optional().describe('Max lines (read) or matches (search).'),\n});\n\nexport const ARTIFACT_TOOL_NAME = 'artifact_tool';\nexport const CONTENT_READER_NAME = 'content_reader';\n\nexport type ArtifactToolInput = z.infer<typeof artifactToolSchema>;\nexport type ContentReaderInput = z.infer<typeof contentReaderSchema>;\n"],"names":[],"mappings":";;AAEO,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ;AACnE,MAAM,oBAAoB,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM;AAE9D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;AACzC,IAAA,MAAM,EAAE;SACL,IAAI,CAAC,sBAAsB;SAC3B,QAAQ,CACP,yGAAyG,CAC1G;AACH,IAAA,UAAU,EAAE;AACT,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CACP,wHAAwH,CACzH;;AAGH,IAAA,OAAO,EAAE;AACN,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CAAC,gDAAgD,CAAC;AAC7D,IAAA,IAAI,EAAE;AACH,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CACP,2IAA2I,CAC5I;;AAGH,IAAA,OAAO,EAAE;AACN,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CAAC,uDAAuD,CAAC;AACpE,IAAA,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;AAClF,IAAA,WAAW,EAAE;AACV,SAAA,OAAO;AACP,SAAA,QAAQ;SACR,QAAQ,CACP,8IAA8I,CAC/I;AACJ,CAAA;AAEM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;AAC1C,IAAA,MAAM,EAAE;SACL,IAAI,CAAC,oBAAoB;SACzB,QAAQ,CACP,sFAAsF,CACvF;AACH,IAAA,UAAU,EAAE;AACT,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CACP,wEAAwE,CACzE;;AAGH,IAAA,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;AAC7E,IAAA,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;;AAGzE,IAAA,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;AAC/E,IAAA,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;AACtF,IAAA,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;;AAG9E,IAAA,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;AAC/E,IAAA,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;AAC/E,CAAA;AAEM,MAAM,kBAAkB,GAAG;AAC3B,MAAM,mBAAmB,GAAG;;;;"}
1
+ {"version":3,"file":"schema.mjs","sources":["../../../../src/tools/artifacts/schema.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const ARTIFACT_WRITE_ACTIONS = [\n 'write',\n 'edit',\n 'verify',\n 'delete',\n] as const;\nexport const CONTENT_READ_ACTIONS = ['read', 'search', 'list', 'info'] as const;\n\nexport const artifactToolSchema = z.object({\n action: z\n .enum(ARTIFACT_WRITE_ACTIONS)\n .describe(\n 'Authoring action: write (create/overwrite), edit (str_replace), verify (syntax check), delete (remove).'\n ),\n content_id: z\n .string()\n .optional()\n .describe(\n 'ID of the artifact entry. Required for edit/verify/delete; optional for write (supply to overwrite an existing entry).'\n ),\n\n // write\n content: z\n .string()\n .optional()\n .describe('Full file content (required for write action).'),\n name: z\n .string()\n .optional()\n .describe(\n 'Filename for the new entry (required when creating). MUST include the correct file extension — the extension drives the preview template.'\n ),\n\n // edit (str_replace)\n old_str: z\n .string()\n .optional()\n .describe('Exact string to find and replace (required for edit).'),\n new_str: z\n .string()\n .optional()\n .describe('Replacement string (required for edit).'),\n replace_all: z\n .boolean()\n .optional()\n .describe(\n 'edit: when true, replaces every occurrence of old_str. When false (default) and old_str matches more than one location, the edit is refused.'\n ),\n});\n\nexport const contentReaderSchema = z.object({\n action: z\n .enum(CONTENT_READ_ACTIONS)\n .describe(\n 'Read-only action: read (lines), search (regex), list (all entries), info (metadata).'\n ),\n content_id: z\n .string()\n .optional()\n .describe(\n 'ID of the content entry. Required for read/search/info. Omit for list.'\n ),\n\n // read pagination\n start_line: z.number().optional().describe('1-based start line for reading.'),\n end_line: z.number().optional().describe('1-based end line (inclusive).'),\n\n // search\n pattern: z\n .string()\n .optional()\n .describe('Regex pattern (required for search).'),\n flags: z\n .string()\n .optional()\n .describe('Regex flags (e.g., \"i\" for case-insensitive).'),\n context: z\n .number()\n .optional()\n .describe('Lines of context around each match.'),\n\n // shared pagination\n offset: z\n .number()\n .optional()\n .describe('Offset for read or search pagination.'),\n limit: z\n .number()\n .optional()\n .describe('Max lines (read) or matches (search).'),\n});\n\nexport const ARTIFACT_TOOL_NAME = 'artifact_tool';\nexport const CONTENT_READER_NAME = 'content_reader';\n\nexport type ArtifactToolInput = z.infer<typeof artifactToolSchema>;\nexport type ContentReaderInput = z.infer<typeof contentReaderSchema>;\n"],"names":[],"mappings":";;AAEO,MAAM,sBAAsB,GAAG;IACpC,OAAO;IACP,MAAM;IACN,QAAQ;IACR,QAAQ;;AAEH,MAAM,oBAAoB,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM;AAE9D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;AACzC,IAAA,MAAM,EAAE;SACL,IAAI,CAAC,sBAAsB;SAC3B,QAAQ,CACP,yGAAyG,CAC1G;AACH,IAAA,UAAU,EAAE;AACT,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CACP,wHAAwH,CACzH;;AAGH,IAAA,OAAO,EAAE;AACN,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CAAC,gDAAgD,CAAC;AAC7D,IAAA,IAAI,EAAE;AACH,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CACP,2IAA2I,CAC5I;;AAGH,IAAA,OAAO,EAAE;AACN,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CAAC,uDAAuD,CAAC;AACpE,IAAA,OAAO,EAAE;AACN,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CAAC,yCAAyC,CAAC;AACtD,IAAA,WAAW,EAAE;AACV,SAAA,OAAO;AACP,SAAA,QAAQ;SACR,QAAQ,CACP,8IAA8I,CAC/I;AACJ,CAAA;AAEM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;AAC1C,IAAA,MAAM,EAAE;SACL,IAAI,CAAC,oBAAoB;SACzB,QAAQ,CACP,sFAAsF,CACvF;AACH,IAAA,UAAU,EAAE;AACT,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CACP,wEAAwE,CACzE;;AAGH,IAAA,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;AAC7E,IAAA,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;;AAGzE,IAAA,OAAO,EAAE;AACN,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CAAC,sCAAsC,CAAC;AACnD,IAAA,KAAK,EAAE;AACJ,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CAAC,+CAA+C,CAAC;AAC5D,IAAA,OAAO,EAAE;AACN,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CAAC,qCAAqC,CAAC;;AAGlD,IAAA,MAAM,EAAE;AACL,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CAAC,uCAAuC,CAAC;AACpD,IAAA,KAAK,EAAE;AACJ,SAAA,MAAM;AACN,SAAA,QAAQ;SACR,QAAQ,CAAC,uCAAuC,CAAC;AACrD,CAAA;AAEM,MAAM,kBAAkB,GAAG;AAC3B,MAAM,mBAAmB,GAAG;;;;"}
@@ -105,7 +105,10 @@ function createArtifactTool(config) {
105
105
  return await handlers.delete(args, scope);
106
106
  }
107
107
  default:
108
- return [`Unknown action: ${input.action}`, {}];
108
+ return [
109
+ `Unknown action: ${input.action}`,
110
+ {},
111
+ ];
109
112
  }
110
113
  }
111
114
  catch (err) {
@@ -182,7 +185,10 @@ function createContentReaderTool(config) {
182
185
  return await handlers.info(args, scope);
183
186
  }
184
187
  default:
185
- return [`Unknown action: ${input.action}`, {}];
188
+ return [
189
+ `Unknown action: ${input.action}`,
190
+ {},
191
+ ];
186
192
  }
187
193
  }
188
194
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"tool.mjs","sources":["../../../../src/tools/artifacts/tool.ts"],"sourcesContent":["/**\n * artifact_tool + content_reader library factories.\n *\n * The library owns the LangChain wiring (schema, description, response\n * shape) and the action dispatch; the runtime supplies a handler bundle\n * matching the `ArtifactWriteHandlers` / `ContentReadHandlers` interface.\n *\n * This keeps 800+ LOC of host-specific handler logic (S3 adapters, file\n * model CRUD, syntax checkers, line utils) out of the library while\n * still centralizing the tool surface every runtime shares.\n */\n\nimport { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport {\n artifactToolSchema,\n contentReaderSchema,\n ARTIFACT_TOOL_NAME,\n CONTENT_READER_NAME,\n} from './schema';\nimport type {\n ArtifactToolConfig,\n ContentReaderToolConfig,\n ArtifactToolScope,\n ArtifactToolResult,\n WriteArgs,\n EditArgs,\n VerifyArgs,\n DeleteArgs,\n ReadArgs,\n SearchArgs,\n ListArgs,\n InfoArgs,\n ContentIdResolver,\n} from './types';\n\nconst 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).\n\nActions:\n- 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).\n- 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.\n- edit: Surgical string replacement — provide old_str (exact match) and new_str. Works on all file types.\n- delete: Remove an artifact and its backing file.\n\nArtifacts are persisted by the host. No manual save needed.`;\n\nconst 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.\n\nRead-only surface. Use write/edit tools (artifact_tool, execute_code) to mutate.\n\nActions:\n- read: Return line ranges from a specific content_id.\n- search: Regex search across a specific content_id with paginated matches.\n- list: Enumerate every content entry currently stored.\n- info: Metadata (size, kind, creation time) for a specific content_id.`;\n\n/**\n * Optional content_id self-healing — if the runtime supplies a resolver,\n * we pre-resolve the ID on every action that takes one so nicknames\n * (e.g., \"Dashboard\") map to canonical IDs.\n */\nasync function resolveContentIdIfPresent(\n resolver: ContentIdResolver | undefined,\n id: string | undefined,\n scope: ArtifactToolScope,\n logger?: { debug: (msg: string) => void },\n): Promise<string | undefined> {\n if (!resolver || !id) return id;\n const out = await resolver.resolve(id, scope);\n if (!out) return id;\n if (out.resolvedId !== id) {\n logger?.debug(\n `[artifact] resolved \"${id}\" → \"${out.resolvedId}\"${out.resolvedName ? ` (\"${out.resolvedName}\")` : ''}`,\n );\n }\n return out.resolvedId;\n}\n\n// ─── Writer tool (artifact_tool) ─────────────────────────────────────────\n\nexport function createArtifactTool(\n config: ArtifactToolConfig,\n): DynamicStructuredTool {\n const { handlers, getScope, resolver, logger, descriptionOverride } = config;\n\n return tool(\n async (rawInput, runnableConfig): Promise<ArtifactToolResult> => {\n const scope = getScope(runnableConfig);\n if (!scope) {\n logger?.warn('[artifact_tool] no scope resolved from runnableConfig');\n return ['Error: No conversation context available', {}];\n }\n\n const input = rawInput as {\n action: 'write' | 'edit' | 'verify' | 'delete';\n content_id?: string;\n content?: string;\n name?: string;\n old_str?: string;\n new_str?: string;\n replace_all?: boolean;\n };\n\n const resolvedContentId = await resolveContentIdIfPresent(\n resolver,\n input.content_id,\n scope,\n logger,\n );\n\n const started = Date.now();\n try {\n switch (input.action) {\n case 'write': {\n const args: WriteArgs = {\n action: 'write',\n content_id: resolvedContentId,\n content: input.content ?? '',\n name: input.name,\n };\n if (!args.content) return ['Error: write requires content', {}];\n return await handlers.write(args, scope);\n }\n case 'edit': {\n if (!resolvedContentId) return ['Error: edit requires content_id', {}];\n const args: EditArgs = {\n action: 'edit',\n content_id: resolvedContentId,\n old_str: input.old_str ?? '',\n new_str: input.new_str ?? '',\n replace_all: input.replace_all,\n };\n if (!args.old_str) return ['Error: edit requires old_str', {}];\n return await handlers.edit(args, scope);\n }\n case 'verify': {\n if (!resolvedContentId) return ['Error: verify requires content_id', {}];\n const args: VerifyArgs = {\n action: 'verify',\n content_id: resolvedContentId,\n };\n return await handlers.verify(args, scope);\n }\n case 'delete': {\n if (!resolvedContentId) return ['Error: delete requires content_id', {}];\n const args: DeleteArgs = {\n action: 'delete',\n content_id: resolvedContentId,\n };\n return await handlers.delete(args, scope);\n }\n default:\n return [`Unknown action: ${(input as { action: string }).action}`, {}];\n }\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n logger?.error('[artifact_tool] handler threw', {\n action: input.action,\n contentId: resolvedContentId,\n error: e.message,\n elapsed: `${Date.now() - started}ms`,\n });\n return [`Error: ${e.message}`, {}];\n }\n },\n {\n name: ARTIFACT_TOOL_NAME,\n responseFormat: 'content_and_artifact',\n description: descriptionOverride ?? DEFAULT_ARTIFACT_DESCRIPTION,\n schema: artifactToolSchema,\n },\n );\n}\n\n// ─── Reader tool (content_reader) ────────────────────────────────────────\n\nexport function createContentReaderTool(\n config: ContentReaderToolConfig,\n): DynamicStructuredTool {\n const { handlers, getScope, resolver, logger, descriptionOverride } = config;\n\n return tool(\n async (rawInput, runnableConfig): Promise<ArtifactToolResult> => {\n const scope = getScope(runnableConfig);\n if (!scope) {\n logger?.warn('[content_reader] no scope resolved from runnableConfig');\n return ['Error: No conversation context available', {}];\n }\n\n const input = rawInput as {\n action: 'read' | 'search' | 'list' | 'info';\n content_id?: string;\n start_line?: number;\n end_line?: number;\n pattern?: string;\n flags?: string;\n context?: number;\n offset?: number;\n limit?: number;\n };\n\n const resolvedContentId = await resolveContentIdIfPresent(\n resolver,\n input.content_id,\n scope,\n logger,\n );\n\n const started = Date.now();\n try {\n switch (input.action) {\n case 'read': {\n if (!resolvedContentId) return ['Error: read requires content_id', {}];\n const args: ReadArgs = {\n action: 'read',\n content_id: resolvedContentId,\n start_line: input.start_line,\n end_line: input.end_line,\n offset: input.offset,\n limit: input.limit,\n };\n return await handlers.read(args, scope);\n }\n case 'search': {\n if (!resolvedContentId) return ['Error: search requires content_id', {}];\n if (!input.pattern) return ['Error: search requires pattern', {}];\n const args: SearchArgs = {\n action: 'search',\n content_id: resolvedContentId,\n pattern: input.pattern,\n flags: input.flags,\n context: input.context,\n offset: input.offset,\n limit: input.limit,\n };\n return await handlers.search(args, scope);\n }\n case 'list': {\n const args: ListArgs = { action: 'list' };\n return await handlers.list(args, scope);\n }\n case 'info': {\n if (!resolvedContentId) return ['Error: info requires content_id', {}];\n const args: InfoArgs = {\n action: 'info',\n content_id: resolvedContentId,\n };\n return await handlers.info(args, scope);\n }\n default:\n return [`Unknown action: ${(input as { action: string }).action}`, {}];\n }\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n logger?.error('[content_reader] handler threw', {\n action: input.action,\n contentId: resolvedContentId,\n error: e.message,\n elapsed: `${Date.now() - started}ms`,\n });\n return [`Error: ${e.message}`, {}];\n }\n },\n {\n name: CONTENT_READER_NAME,\n responseFormat: 'content_and_artifact',\n description: descriptionOverride ?? DEFAULT_CONTENT_READER_DESCRIPTION,\n schema: contentReaderSchema,\n },\n );\n}\n\nexport {\n ARTIFACT_TOOL_NAME,\n CONTENT_READER_NAME,\n ARTIFACT_WRITE_ACTIONS,\n CONTENT_READ_ACTIONS,\n} from './schema';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tool.mjs","sources":["../../../../src/tools/artifacts/tool.ts"],"sourcesContent":["/**\n * artifact_tool + content_reader library factories.\n *\n * The library owns the LangChain wiring (schema, description, response\n * shape) and the action dispatch; the runtime supplies a handler bundle\n * matching the `ArtifactWriteHandlers` / `ContentReadHandlers` interface.\n *\n * This keeps 800+ LOC of host-specific handler logic (S3 adapters, file\n * model CRUD, syntax checkers, line utils) out of the library while\n * still centralizing the tool surface every runtime shares.\n */\n\nimport { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport {\n artifactToolSchema,\n contentReaderSchema,\n ARTIFACT_TOOL_NAME,\n CONTENT_READER_NAME,\n} from './schema';\nimport type {\n ArtifactToolConfig,\n ContentReaderToolConfig,\n ArtifactToolScope,\n ArtifactToolResult,\n WriteArgs,\n EditArgs,\n VerifyArgs,\n DeleteArgs,\n ReadArgs,\n SearchArgs,\n ListArgs,\n InfoArgs,\n ContentIdResolver,\n} from './types';\n\nconst 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).\n\nActions:\n- 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).\n- 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.\n- edit: Surgical string replacement — provide old_str (exact match) and new_str. Works on all file types.\n- delete: Remove an artifact and its backing file.\n\nArtifacts are persisted by the host. No manual save needed.`;\n\nconst 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.\n\nRead-only surface. Use write/edit tools (artifact_tool, execute_code) to mutate.\n\nActions:\n- read: Return line ranges from a specific content_id.\n- search: Regex search across a specific content_id with paginated matches.\n- list: Enumerate every content entry currently stored.\n- info: Metadata (size, kind, creation time) for a specific content_id.`;\n\n/**\n * Optional content_id self-healing — if the runtime supplies a resolver,\n * we pre-resolve the ID on every action that takes one so nicknames\n * (e.g., \"Dashboard\") map to canonical IDs.\n */\nasync function resolveContentIdIfPresent(\n resolver: ContentIdResolver | undefined,\n id: string | undefined,\n scope: ArtifactToolScope,\n logger?: { debug: (msg: string) => void }\n): Promise<string | undefined> {\n if (!resolver || !id) return id;\n const out = await resolver.resolve(id, scope);\n if (!out) return id;\n if (out.resolvedId !== id) {\n logger?.debug(\n `[artifact] resolved \"${id}\" → \"${out.resolvedId}\"${out.resolvedName ? ` (\"${out.resolvedName}\")` : ''}`\n );\n }\n return out.resolvedId;\n}\n\n// ─── Writer tool (artifact_tool) ─────────────────────────────────────────\n\nexport function createArtifactTool(\n config: ArtifactToolConfig\n): DynamicStructuredTool {\n const { handlers, getScope, resolver, logger, descriptionOverride } = config;\n\n return tool(\n async (rawInput, runnableConfig): Promise<ArtifactToolResult> => {\n const scope = getScope(runnableConfig);\n if (!scope) {\n logger?.warn('[artifact_tool] no scope resolved from runnableConfig');\n return ['Error: No conversation context available', {}];\n }\n\n const input = rawInput as {\n action: 'write' | 'edit' | 'verify' | 'delete';\n content_id?: string;\n content?: string;\n name?: string;\n old_str?: string;\n new_str?: string;\n replace_all?: boolean;\n };\n\n const resolvedContentId = await resolveContentIdIfPresent(\n resolver,\n input.content_id,\n scope,\n logger\n );\n\n const started = Date.now();\n try {\n switch (input.action) {\n case 'write': {\n const args: WriteArgs = {\n action: 'write',\n content_id: resolvedContentId,\n content: input.content ?? '',\n name: input.name,\n };\n if (!args.content) return ['Error: write requires content', {}];\n return await handlers.write(args, scope);\n }\n case 'edit': {\n if (!resolvedContentId)\n return ['Error: edit requires content_id', {}];\n const args: EditArgs = {\n action: 'edit',\n content_id: resolvedContentId,\n old_str: input.old_str ?? '',\n new_str: input.new_str ?? '',\n replace_all: input.replace_all,\n };\n if (!args.old_str) return ['Error: edit requires old_str', {}];\n return await handlers.edit(args, scope);\n }\n case 'verify': {\n if (!resolvedContentId)\n return ['Error: verify requires content_id', {}];\n const args: VerifyArgs = {\n action: 'verify',\n content_id: resolvedContentId,\n };\n return await handlers.verify(args, scope);\n }\n case 'delete': {\n if (!resolvedContentId)\n return ['Error: delete requires content_id', {}];\n const args: DeleteArgs = {\n action: 'delete',\n content_id: resolvedContentId,\n };\n return await handlers.delete(args, scope);\n }\n default:\n return [\n `Unknown action: ${(input as { action: string }).action}`,\n {},\n ];\n }\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n logger?.error('[artifact_tool] handler threw', {\n action: input.action,\n contentId: resolvedContentId,\n error: e.message,\n elapsed: `${Date.now() - started}ms`,\n });\n return [`Error: ${e.message}`, {}];\n }\n },\n {\n name: ARTIFACT_TOOL_NAME,\n responseFormat: 'content_and_artifact',\n description: descriptionOverride ?? DEFAULT_ARTIFACT_DESCRIPTION,\n schema: artifactToolSchema,\n }\n );\n}\n\n// ─── Reader tool (content_reader) ────────────────────────────────────────\n\nexport function createContentReaderTool(\n config: ContentReaderToolConfig\n): DynamicStructuredTool {\n const { handlers, getScope, resolver, logger, descriptionOverride } = config;\n\n return tool(\n async (rawInput, runnableConfig): Promise<ArtifactToolResult> => {\n const scope = getScope(runnableConfig);\n if (!scope) {\n logger?.warn('[content_reader] no scope resolved from runnableConfig');\n return ['Error: No conversation context available', {}];\n }\n\n const input = rawInput as {\n action: 'read' | 'search' | 'list' | 'info';\n content_id?: string;\n start_line?: number;\n end_line?: number;\n pattern?: string;\n flags?: string;\n context?: number;\n offset?: number;\n limit?: number;\n };\n\n const resolvedContentId = await resolveContentIdIfPresent(\n resolver,\n input.content_id,\n scope,\n logger\n );\n\n const started = Date.now();\n try {\n switch (input.action) {\n case 'read': {\n if (!resolvedContentId)\n return ['Error: read requires content_id', {}];\n const args: ReadArgs = {\n action: 'read',\n content_id: resolvedContentId,\n start_line: input.start_line,\n end_line: input.end_line,\n offset: input.offset,\n limit: input.limit,\n };\n return await handlers.read(args, scope);\n }\n case 'search': {\n if (!resolvedContentId)\n return ['Error: search requires content_id', {}];\n if (!input.pattern) return ['Error: search requires pattern', {}];\n const args: SearchArgs = {\n action: 'search',\n content_id: resolvedContentId,\n pattern: input.pattern,\n flags: input.flags,\n context: input.context,\n offset: input.offset,\n limit: input.limit,\n };\n return await handlers.search(args, scope);\n }\n case 'list': {\n const args: ListArgs = { action: 'list' };\n return await handlers.list(args, scope);\n }\n case 'info': {\n if (!resolvedContentId)\n return ['Error: info requires content_id', {}];\n const args: InfoArgs = {\n action: 'info',\n content_id: resolvedContentId,\n };\n return await handlers.info(args, scope);\n }\n default:\n return [\n `Unknown action: ${(input as { action: string }).action}`,\n {},\n ];\n }\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n logger?.error('[content_reader] handler threw', {\n action: input.action,\n contentId: resolvedContentId,\n error: e.message,\n elapsed: `${Date.now() - started}ms`,\n });\n return [`Error: ${e.message}`, {}];\n }\n },\n {\n name: CONTENT_READER_NAME,\n responseFormat: 'content_and_artifact',\n description: descriptionOverride ?? DEFAULT_CONTENT_READER_DESCRIPTION,\n schema: contentReaderSchema,\n }\n );\n}\n\nexport {\n ARTIFACT_TOOL_NAME,\n CONTENT_READER_NAME,\n ARTIFACT_WRITE_ACTIONS,\n CONTENT_READ_ACTIONS,\n} from './schema';\n"],"names":[],"mappings":"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"}
@@ -22,7 +22,7 @@ import { HttpError } from '../utils/httpClient.mjs';
22
22
  * per-invocation credential rotation should rebuild the tool.
23
23
  */
24
24
  function buildProxyTool(capability, credentials, options) {
25
- const { client, executePath = '/execute/:name', onExecute } = options;
25
+ const { client, executePath = '/execute/:name', onExecute, getAuthHeaders, } = options;
26
26
  const url = executePath.replace(':name', encodeURIComponent(capability.name));
27
27
  return tool(async (input) => {
28
28
  const startMs = Date.now();
@@ -31,10 +31,12 @@ function buildProxyTool(capability, credentials, options) {
31
31
  // DEBUG: log (remove after POC stabilizes)
32
32
  // eslint-disable-next-line no-console
33
33
  console.debug(`${debugPrefix} invoking — inputKeys=${input && typeof input === 'object' ? Object.keys(input).length : 0}`);
34
- const res = await client.post(url, {
35
- input,
36
- credentials,
37
- });
34
+ const extraHeaders = getAuthHeaders
35
+ ? await getAuthHeaders()
36
+ : undefined;
37
+ const res = await client.post(url, { input, credentials }, extraHeaders && Object.keys(extraHeaders).length > 0
38
+ ? { headers: extraHeaders }
39
+ : undefined);
38
40
  const durationMs = Date.now() - startMs;
39
41
  if (res.status < 200 || res.status >= 300) {
40
42
  throw new HttpError(res.status, url, res.data);
@@ -1 +1 @@
1
- {"version":3,"file":"proxyTool.mjs","sources":["../../../src/tools/proxyTool.ts"],"sourcesContent":["/**\n * proxyTool — wraps a Capability into a LangChain StructuredTool that\n * dispatches execution to a remote backend (e.g., tools-server) over HTTP.\n *\n * The LLM sees this tool identically to a locally-implemented tool:\n * - same name\n * - same description\n * - same input schema (JSON Schema)\n * - same invoke(input) contract\n *\n * Under the hood, invoke() POSTs to the backend's execute endpoint with\n * the input and a caller-supplied credential map. The backend returns\n * { success, result, error, timing } which this wrapper unpacks.\n */\n\nimport type { AxiosInstance } from 'axios';\nimport { tool, type StructuredToolInterface } from '@langchain/core/tools';\nimport type { Capability, CredentialMap } from '@/providers/types';\nimport { HttpError } from '@/utils/httpClient';\n\n/** Shape of the backend's execute response. Matches tools-server. */\nexport interface ExecuteResponse {\n success: boolean;\n result?: unknown;\n error?: string;\n timing?: { durationMs: number };\n}\n\n/** Options passed to buildProxyTool. */\nexport interface ProxyToolOptions {\n /** HTTP client configured with backend base URL + auth. */\n client: AxiosInstance;\n /**\n * Path template for execution. The literal `:name` is replaced with the\n * capability's name. Defaults to `/execute/:name` (tools-server convention).\n */\n executePath?: string;\n /**\n * Optional callback invoked on every execution — used for metrics,\n * telemetry, debug logging. Errors in the hook are swallowed.\n */\n onExecute?: (ctx: ExecuteCallbackContext) => void;\n}\n\nexport interface ExecuteCallbackContext {\n capabilityName: string;\n input: unknown;\n response?: ExecuteResponse;\n error?: Error;\n durationMs: number;\n}\n\n/**\n * Build a StructuredTool that proxies to a remote backend.\n *\n * The credentialMap is baked into the closure — callers that need\n * per-invocation credential rotation should rebuild the tool.\n */\nexport function buildProxyTool(\n capability: Capability,\n credentials: CredentialMap,\n options: ProxyToolOptions\n): StructuredToolInterface {\n const { client, executePath = '/execute/:name', onExecute } = options;\n const url = executePath.replace(':name', encodeURIComponent(capability.name));\n\n return tool(\n async (input: unknown): Promise<string> => {\n const startMs = Date.now();\n const debugPrefix = `[proxyTool:${capability.name}]`;\n\n try {\n // DEBUG: log (remove after POC stabilizes)\n // eslint-disable-next-line no-console\n console.debug(\n `${debugPrefix} invoking — inputKeys=${input && typeof input === 'object' ? Object.keys(input as object).length : 0}`\n );\n\n const res = await client.post<ExecuteResponse>(url, {\n input,\n credentials,\n });\n\n const durationMs = Date.now() - startMs;\n\n if (res.status < 200 || res.status >= 300) {\n throw new HttpError(res.status, url, res.data);\n }\n\n const body = res.data;\n if (onExecute) {\n try {\n onExecute({\n capabilityName: capability.name,\n input,\n response: body,\n durationMs,\n });\n } catch {\n // hook errors are non-fatal\n }\n }\n\n if (!body.success) {\n // DEBUG\n // eslint-disable-next-line no-console\n console.debug(\n `${debugPrefix} backend reported failure — ${body.error}`\n );\n throw new Error(body.error ?? 'Tool execution failed');\n }\n\n // LangChain tools typically return strings; stringify non-string results\n if (typeof body.result === 'string') {\n return body.result;\n }\n return JSON.stringify(body.result);\n } catch (err) {\n const durationMs = Date.now() - startMs;\n const error = err instanceof Error ? err : new Error(String(err));\n if (onExecute) {\n try {\n onExecute({\n capabilityName: capability.name,\n input,\n error,\n durationMs,\n });\n } catch {\n // hook errors are non-fatal\n }\n }\n throw error;\n }\n },\n {\n name: capability.name,\n description: capability.description,\n schema: (capability.schema as object) ?? {\n type: 'object',\n properties: {},\n },\n responseFormat: 'content',\n }\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"proxyTool.mjs","sources":["../../../src/tools/proxyTool.ts"],"sourcesContent":["/**\n * proxyTool — wraps a Capability into a LangChain StructuredTool that\n * dispatches execution to a remote backend (e.g., tools-server) over HTTP.\n *\n * The LLM sees this tool identically to a locally-implemented tool:\n * - same name\n * - same description\n * - same input schema (JSON Schema)\n * - same invoke(input) contract\n *\n * Under the hood, invoke() POSTs to the backend's execute endpoint with\n * the input and a caller-supplied credential map. The backend returns\n * { success, result, error, timing } which this wrapper unpacks.\n */\n\nimport type { AxiosInstance } from 'axios';\nimport { tool, type StructuredToolInterface } from '@langchain/core/tools';\nimport type { Capability, CredentialMap } from '@/providers/types';\nimport { HttpError } from '@/utils/httpClient';\n\n/** Shape of the backend's execute response. Matches tools-server. */\nexport interface ExecuteResponse {\n success: boolean;\n result?: unknown;\n error?: string;\n timing?: { durationMs: number };\n}\n\n/** Options passed to buildProxyTool. */\nexport interface ProxyToolOptions {\n /** HTTP client configured with backend base URL + auth. */\n client: AxiosInstance;\n /**\n * Path template for execution. The literal `:name` is replaced with the\n * capability's name. Defaults to `/execute/:name` (tools-server convention).\n */\n executePath?: string;\n /**\n * Optional callback invoked on every execution — used for metrics,\n * telemetry, debug logging. Errors in the hook are swallowed.\n */\n onExecute?: (ctx: ExecuteCallbackContext) => void;\n /**\n * Optional per-invocation auth header builder. Called on every tool\n * invocation before POSTing; returned headers are merged into the\n * request alongside the base client's headers. Typical use: pass a\n * freshly minted per-user JWT for admin-gated tools.\n */\n getAuthHeaders?: () =>\n | Record<string, string>\n | Promise<Record<string, string>>;\n}\n\nexport interface ExecuteCallbackContext {\n capabilityName: string;\n input: unknown;\n response?: ExecuteResponse;\n error?: Error;\n durationMs: number;\n}\n\n/**\n * Build a StructuredTool that proxies to a remote backend.\n *\n * The credentialMap is baked into the closure — callers that need\n * per-invocation credential rotation should rebuild the tool.\n */\nexport function buildProxyTool(\n capability: Capability,\n credentials: CredentialMap,\n options: ProxyToolOptions\n): StructuredToolInterface {\n const {\n client,\n executePath = '/execute/:name',\n onExecute,\n getAuthHeaders,\n } = options;\n const url = executePath.replace(':name', encodeURIComponent(capability.name));\n\n return tool(\n async (input: unknown): Promise<string> => {\n const startMs = Date.now();\n const debugPrefix = `[proxyTool:${capability.name}]`;\n\n try {\n // DEBUG: log (remove after POC stabilizes)\n // eslint-disable-next-line no-console\n console.debug(\n `${debugPrefix} invoking — inputKeys=${input && typeof input === 'object' ? Object.keys(input as object).length : 0}`\n );\n\n const extraHeaders = getAuthHeaders\n ? await getAuthHeaders()\n : undefined;\n const res = await client.post<ExecuteResponse>(\n url,\n { input, credentials },\n extraHeaders && Object.keys(extraHeaders).length > 0\n ? { headers: extraHeaders }\n : undefined,\n );\n\n const durationMs = Date.now() - startMs;\n\n if (res.status < 200 || res.status >= 300) {\n throw new HttpError(res.status, url, res.data);\n }\n\n const body = res.data;\n if (onExecute) {\n try {\n onExecute({\n capabilityName: capability.name,\n input,\n response: body,\n durationMs,\n });\n } catch {\n // hook errors are non-fatal\n }\n }\n\n if (!body.success) {\n // DEBUG\n // eslint-disable-next-line no-console\n console.debug(\n `${debugPrefix} backend reported failure — ${body.error}`\n );\n throw new Error(body.error ?? 'Tool execution failed');\n }\n\n // LangChain tools typically return strings; stringify non-string results\n if (typeof body.result === 'string') {\n return body.result;\n }\n return JSON.stringify(body.result);\n } catch (err) {\n const durationMs = Date.now() - startMs;\n const error = err instanceof Error ? err : new Error(String(err));\n if (onExecute) {\n try {\n onExecute({\n capabilityName: capability.name,\n input,\n error,\n durationMs,\n });\n } catch {\n // hook errors are non-fatal\n }\n }\n throw error;\n }\n },\n {\n name: capability.name,\n description: capability.description,\n schema: (capability.schema as object) ?? {\n type: 'object',\n properties: {},\n },\n responseFormat: 'content',\n }\n );\n}\n"],"names":[],"mappings":"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"}
@@ -29,6 +29,19 @@ export interface ToolsServerConfig {
29
29
  client?: AxiosInstance;
30
30
  /** Optional proxy override (defaults to process.env.PROXY). */
31
31
  proxy?: string | null;
32
+ /**
33
+ * Optional per-request auth header builder — invoked on every tool
34
+ * invocation (NOT on the manifest fetch, which is service-to-service).
35
+ * When provided, the returned headers are merged into the `/execute`
36
+ * request so the host can pass user-scoped identity (e.g.,
37
+ * `Authorization: Bearer <jwt>`) that tools-server verifies for
38
+ * admin-gated tools.
39
+ *
40
+ * Typical host wiring: mint a short-lived JWT per call carrying the
41
+ * authenticated user's `{ userId, role }` claims; tools-server's
42
+ * `TOOLS_SERVER_JWT_SECRET` validates.
43
+ */
44
+ getExecuteAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
32
45
  }
33
46
  export declare class ToolsServerCapabilityProvider implements CapabilityProvider {
34
47
  private readonly config;
@@ -37,6 +50,7 @@ export declare class ToolsServerCapabilityProvider implements CapabilityProvider
37
50
  private readonly manifestPath;
38
51
  private readonly executePath;
39
52
  private readonly cache;
53
+ private readonly getExecuteAuthHeaders?;
40
54
  constructor(config: ToolsServerConfig);
41
55
  fetchManifest(filter?: CapabilityFilter): Promise<Capability[]>;
42
56
  createRunnables(capabilities: Capability[], credentials: CredentialMap): Promise<StructuredToolInterface[]>;
@@ -82,6 +82,20 @@ export interface CapabilityMetadata {
82
82
  category?: string;
83
83
  /** Free-form tags for filtering / search. */
84
84
  tags?: string[];
85
+ /**
86
+ * When true, hosts omit this capability from user-facing pickers
87
+ * (Agent Builder, tool dropdown). Still invokable when a saved agent
88
+ * references it by name.
89
+ */
90
+ hidden?: boolean;
91
+ /** When true, only admin-role users can enable or invoke this capability. */
92
+ admin?: boolean;
93
+ /**
94
+ * Deployment-stage restriction. When set, hosts whose deployment stage
95
+ * isn't in the array should hide the capability (e.g., a prod host
96
+ * shouldn't surface a tool with `env: ['development']`).
97
+ */
98
+ env?: Array<'development' | 'production' | 'test' | 'staging'>;
85
99
  /** Reserved for upstream `fix/auth-events` — auth mode declared by tool. */
86
100
  auth?: string;
87
101
  /** Reserved for upstream `fix/auth-events` — Unix timestamp for token expiration. */
@@ -38,6 +38,13 @@ export interface ProxyToolOptions {
38
38
  * telemetry, debug logging. Errors in the hook are swallowed.
39
39
  */
40
40
  onExecute?: (ctx: ExecuteCallbackContext) => void;
41
+ /**
42
+ * Optional per-invocation auth header builder. Called on every tool
43
+ * invocation before POSTing; returned headers are merged into the
44
+ * request alongside the base client's headers. Typical use: pass a
45
+ * freshly minted per-user JWT for admin-gated tools.
46
+ */
47
+ getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
41
48
  }
42
49
  export interface ExecuteCallbackContext {
43
50
  capabilityName: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@illuma-ai/agents",
3
- "version": "1.4.0-alpha.3",
3
+ "version": "1.4.0-alpha.5",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -203,4 +203,67 @@ describe('ToolsServerCapabilityProvider.createRunnables', () => {
203
203
  /missing API key/
204
204
  );
205
205
  });
206
+
207
+ it('getExecuteAuthHeaders is invoked per call and forwarded as HTTP headers', async () => {
208
+ const client = {
209
+ get: jest.fn().mockResolvedValue({ status: 200, data: manifestFixture }),
210
+ post: jest.fn().mockResolvedValue({
211
+ status: 200,
212
+ data: {
213
+ success: true,
214
+ result: 'ok',
215
+ timing: { durationMs: 1 },
216
+ },
217
+ }),
218
+ defaults: { baseURL: 'http://stub', headers: {} },
219
+ };
220
+
221
+ let mintCount = 0;
222
+ const p = new ToolsServerCapabilityProvider({
223
+ baseUrl: 'http://x',
224
+ apiKey: 'k',
225
+ client: client as unknown as ReturnType<typeof axios.create>,
226
+ getExecuteAuthHeaders: async () => {
227
+ mintCount += 1;
228
+ return { Authorization: `Bearer TOKEN-${mintCount}` };
229
+ },
230
+ });
231
+ const caps = await p.fetchManifest();
232
+ const [wikipedia] = await p.createRunnables(
233
+ caps.filter((c) => c.name === 'wikipedia'),
234
+ {}
235
+ );
236
+
237
+ await wikipedia.invoke({ query: 'a' });
238
+ await wikipedia.invoke({ query: 'b' });
239
+
240
+ // Two invocations → two mints (fresh token each call)
241
+ expect(mintCount).toBe(2);
242
+
243
+ // Each POST includes the Authorization header from the mint
244
+ const firstCall = (client.post as jest.Mock).mock.calls[0];
245
+ const secondCall = (client.post as jest.Mock).mock.calls[1];
246
+ expect(firstCall[2]?.headers).toEqual({ Authorization: 'Bearer TOKEN-1' });
247
+ expect(secondCall[2]?.headers).toEqual({ Authorization: 'Bearer TOKEN-2' });
248
+ });
249
+
250
+ it('getExecuteAuthHeaders is NOT called during manifest fetch (service-to-service)', async () => {
251
+ const client = {
252
+ get: jest.fn().mockResolvedValue({ status: 200, data: manifestFixture }),
253
+ post: jest.fn(),
254
+ defaults: { baseURL: 'http://stub', headers: {} },
255
+ };
256
+
257
+ const authBuilder = jest.fn().mockReturnValue({ Authorization: 'Bearer X' });
258
+ const p = new ToolsServerCapabilityProvider({
259
+ baseUrl: 'http://x',
260
+ apiKey: 'k',
261
+ client: client as unknown as ReturnType<typeof axios.create>,
262
+ getExecuteAuthHeaders: authBuilder,
263
+ });
264
+
265
+ await p.fetchManifest();
266
+ // Manifest fetch is service-to-service — no per-user auth headers
267
+ expect(authBuilder).not.toHaveBeenCalled();
268
+ });
206
269
  });
@@ -48,6 +48,21 @@ export interface ToolsServerConfig {
48
48
  client?: AxiosInstance;
49
49
  /** Optional proxy override (defaults to process.env.PROXY). */
50
50
  proxy?: string | null;
51
+ /**
52
+ * Optional per-request auth header builder — invoked on every tool
53
+ * invocation (NOT on the manifest fetch, which is service-to-service).
54
+ * When provided, the returned headers are merged into the `/execute`
55
+ * request so the host can pass user-scoped identity (e.g.,
56
+ * `Authorization: Bearer <jwt>`) that tools-server verifies for
57
+ * admin-gated tools.
58
+ *
59
+ * Typical host wiring: mint a short-lived JWT per call carrying the
60
+ * authenticated user's `{ userId, role }` claims; tools-server's
61
+ * `TOOLS_SERVER_JWT_SECRET` validates.
62
+ */
63
+ getExecuteAuthHeaders?: () =>
64
+ | Record<string, string>
65
+ | Promise<Record<string, string>>;
51
66
  }
52
67
 
53
68
  /**
@@ -79,6 +94,10 @@ interface ToolsServerManifestEntry {
79
94
  tags?: string[];
80
95
  toolkit?: string;
81
96
  endpoint?: string;
97
+ /** Governance flags forwarded by tools-server getManifest(). */
98
+ hidden?: boolean;
99
+ admin?: boolean;
100
+ env?: Array<'development' | 'production' | 'test' | 'staging'>;
82
101
  }
83
102
 
84
103
  export class ToolsServerCapabilityProvider implements CapabilityProvider {
@@ -87,6 +106,8 @@ export class ToolsServerCapabilityProvider implements CapabilityProvider {
87
106
  private readonly manifestPath: string;
88
107
  private readonly executePath: string;
89
108
  private readonly cache: ManifestCache;
109
+ private readonly getExecuteAuthHeaders?:
110
+ | (() => Record<string, string> | Promise<Record<string, string>>);
90
111
 
91
112
  constructor(private readonly config: ToolsServerConfig) {
92
113
  const {
@@ -98,6 +119,7 @@ export class ToolsServerCapabilityProvider implements CapabilityProvider {
98
119
  manifestTtlMs = 60_000,
99
120
  client,
100
121
  proxy,
122
+ getExecuteAuthHeaders,
101
123
  } = config;
102
124
 
103
125
  if (!baseUrl) {
@@ -111,6 +133,7 @@ export class ToolsServerCapabilityProvider implements CapabilityProvider {
111
133
  this.manifestPath = manifestPath;
112
134
  this.executePath = executePath;
113
135
  this.cache = new ManifestCache({ ttlMs: manifestTtlMs });
136
+ this.getExecuteAuthHeaders = getExecuteAuthHeaders;
114
137
 
115
138
  if (client) {
116
139
  this.client = client;
@@ -180,6 +203,7 @@ export class ToolsServerCapabilityProvider implements CapabilityProvider {
180
203
  buildProxyTool(cap, credentials, {
181
204
  client: this.client,
182
205
  executePath: this.executePath,
206
+ getAuthHeaders: this.getExecuteAuthHeaders,
183
207
  })
184
208
  );
185
209
  }
@@ -215,6 +239,10 @@ function normalizeEntry(entry: ToolsServerManifestEntry): Capability {
215
239
  icon: entry.icon,
216
240
  category: entry.category,
217
241
  tags: entry.tags,
242
+ // Governance — hosts use these to gate UI/role/environment.
243
+ hidden: entry.hidden,
244
+ admin: entry.admin,
245
+ env: entry.env,
218
246
  },
219
247
  };
220
248
  }
@@ -88,6 +88,23 @@ export interface CapabilityMetadata {
88
88
  /** Free-form tags for filtering / search. */
89
89
  tags?: string[];
90
90
 
91
+ // --- Governance flags ------------------------------------------------
92
+
93
+ /**
94
+ * When true, hosts omit this capability from user-facing pickers
95
+ * (Agent Builder, tool dropdown). Still invokable when a saved agent
96
+ * references it by name.
97
+ */
98
+ hidden?: boolean;
99
+ /** When true, only admin-role users can enable or invoke this capability. */
100
+ admin?: boolean;
101
+ /**
102
+ * Deployment-stage restriction. When set, hosts whose deployment stage
103
+ * isn't in the array should hide the capability (e.g., a prod host
104
+ * shouldn't surface a tool with `env: ['development']`).
105
+ */
106
+ env?: Array<'development' | 'production' | 'test' | 'staging'>;
107
+
91
108
  // --- Reserved for upstream patterns (not populated today) ---
92
109
 
93
110
  /** Reserved for upstream `fix/auth-events` — auth mode declared by tool. */
@@ -10,10 +10,7 @@
10
10
  * We verify each of those paths here with mock handlers.
11
11
  */
12
12
 
13
- import {
14
- createArtifactTool,
15
- createContentReaderTool,
16
- } from '../tool';
13
+ import { createArtifactTool, createContentReaderTool } from '../tool';
17
14
  import type {
18
15
  ArtifactWriteHandlers,
19
16
  ContentReadHandlers,
@@ -105,10 +102,14 @@ describe('createArtifactTool', () => {
105
102
  await t.invoke({ action: 'write', content: 'hello', name: 'doc.md' });
106
103
  expect(handlers.write).toHaveBeenCalledTimes(1);
107
104
  expect(handlers._calls[0].args).toEqual(
108
- expect.objectContaining({ action: 'write', content: 'hello', name: 'doc.md' }),
105
+ expect.objectContaining({
106
+ action: 'write',
107
+ content: 'hello',
108
+ name: 'doc.md',
109
+ })
109
110
  );
110
111
  expect(handlers._calls[0].scope).toEqual(
111
- expect.objectContaining({ conversationId: 'conv-1' }),
112
+ expect.objectContaining({ conversationId: 'conv-1' })
112
113
  );
113
114
  });
114
115
 
@@ -131,17 +132,26 @@ describe('createArtifactTool', () => {
131
132
  getScope: makeScope,
132
133
  });
133
134
  const e = await t.invoke({ action: 'edit', old_str: 'a', new_str: 'b' });
134
- expect(String(Array.isArray(e) ? e[0] : e)).toMatch(/edit requires content_id/i);
135
+ expect(String(Array.isArray(e) ? e[0] : e)).toMatch(
136
+ /edit requires content_id/i
137
+ );
135
138
  const v = await t.invoke({ action: 'verify' });
136
- expect(String(Array.isArray(v) ? v[0] : v)).toMatch(/verify requires content_id/i);
139
+ expect(String(Array.isArray(v) ? v[0] : v)).toMatch(
140
+ /verify requires content_id/i
141
+ );
137
142
  const d = await t.invoke({ action: 'delete' });
138
- expect(String(Array.isArray(d) ? d[0] : d)).toMatch(/delete requires content_id/i);
143
+ expect(String(Array.isArray(d) ? d[0] : d)).toMatch(
144
+ /delete requires content_id/i
145
+ );
139
146
  });
140
147
 
141
148
  it('applies the resolver before dispatching to handler', async () => {
142
149
  const handlers = makeWriteHandlers();
143
150
  const resolver: ContentIdResolver = {
144
- resolve: jest.fn(async (id) => ({ resolvedId: `canonical:${id}`, resolvedName: 'X' })),
151
+ resolve: jest.fn(async (id) => ({
152
+ resolvedId: `canonical:${id}`,
153
+ resolvedName: 'X',
154
+ })),
145
155
  };
146
156
  const t = createArtifactTool({
147
157
  handlers,
@@ -155,7 +165,7 @@ describe('createArtifactTool', () => {
155
165
  new_str: 'b',
156
166
  });
157
167
  expect(handlers._calls[0].args).toEqual(
158
- expect.objectContaining({ content_id: 'canonical:nickname' }),
168
+ expect.objectContaining({ content_id: 'canonical:nickname' })
159
169
  );
160
170
  });
161
171
 
@@ -202,11 +212,17 @@ describe('createContentReaderTool', () => {
202
212
  getScope: makeScope,
203
213
  });
204
214
  const r = await t.invoke({ action: 'read' });
205
- expect(String(Array.isArray(r) ? r[0] : r)).toMatch(/read requires content_id/i);
215
+ expect(String(Array.isArray(r) ? r[0] : r)).toMatch(
216
+ /read requires content_id/i
217
+ );
206
218
  const i = await t.invoke({ action: 'info' });
207
- expect(String(Array.isArray(i) ? i[0] : i)).toMatch(/info requires content_id/i);
219
+ expect(String(Array.isArray(i) ? i[0] : i)).toMatch(
220
+ /info requires content_id/i
221
+ );
208
222
  const s = await t.invoke({ action: 'search', pattern: 'p' });
209
- expect(String(Array.isArray(s) ? s[0] : s)).toMatch(/search requires content_id/i);
223
+ expect(String(Array.isArray(s) ? s[0] : s)).toMatch(
224
+ /search requires content_id/i
225
+ );
210
226
  });
211
227
 
212
228
  it('rejects search without pattern even when content_id is given', async () => {
@@ -237,7 +253,7 @@ describe('createContentReaderTool', () => {
237
253
  content_id: 'c-1',
238
254
  start_line: 10,
239
255
  end_line: 50,
240
- }),
256
+ })
241
257
  );
242
258
  });
243
259
  });
@@ -1,19 +1,24 @@
1
1
  import { z } from 'zod';
2
2
 
3
- export const ARTIFACT_WRITE_ACTIONS = ['write', 'edit', 'verify', 'delete'] as const;
3
+ export const ARTIFACT_WRITE_ACTIONS = [
4
+ 'write',
5
+ 'edit',
6
+ 'verify',
7
+ 'delete',
8
+ ] as const;
4
9
  export const CONTENT_READ_ACTIONS = ['read', 'search', 'list', 'info'] as const;
5
10
 
6
11
  export const artifactToolSchema = z.object({
7
12
  action: z
8
13
  .enum(ARTIFACT_WRITE_ACTIONS)
9
14
  .describe(
10
- 'Authoring action: write (create/overwrite), edit (str_replace), verify (syntax check), delete (remove).',
15
+ 'Authoring action: write (create/overwrite), edit (str_replace), verify (syntax check), delete (remove).'
11
16
  ),
12
17
  content_id: z
13
18
  .string()
14
19
  .optional()
15
20
  .describe(
16
- 'ID of the artifact entry. Required for edit/verify/delete; optional for write (supply to overwrite an existing entry).',
21
+ 'ID of the artifact entry. Required for edit/verify/delete; optional for write (supply to overwrite an existing entry).'
17
22
  ),
18
23
 
19
24
  // write
@@ -25,7 +30,7 @@ export const artifactToolSchema = z.object({
25
30
  .string()
26
31
  .optional()
27
32
  .describe(
28
- 'Filename for the new entry (required when creating). MUST include the correct file extension — the extension drives the preview template.',
33
+ 'Filename for the new entry (required when creating). MUST include the correct file extension — the extension drives the preview template.'
29
34
  ),
30
35
 
31
36
  // edit (str_replace)
@@ -33,12 +38,15 @@ export const artifactToolSchema = z.object({
33
38
  .string()
34
39
  .optional()
35
40
  .describe('Exact string to find and replace (required for edit).'),
36
- new_str: z.string().optional().describe('Replacement string (required for edit).'),
41
+ new_str: z
42
+ .string()
43
+ .optional()
44
+ .describe('Replacement string (required for edit).'),
37
45
  replace_all: z
38
46
  .boolean()
39
47
  .optional()
40
48
  .describe(
41
- 'edit: when true, replaces every occurrence of old_str. When false (default) and old_str matches more than one location, the edit is refused.',
49
+ 'edit: when true, replaces every occurrence of old_str. When false (default) and old_str matches more than one location, the edit is refused.'
42
50
  ),
43
51
  });
44
52
 
@@ -46,13 +54,13 @@ export const contentReaderSchema = z.object({
46
54
  action: z
47
55
  .enum(CONTENT_READ_ACTIONS)
48
56
  .describe(
49
- 'Read-only action: read (lines), search (regex), list (all entries), info (metadata).',
57
+ 'Read-only action: read (lines), search (regex), list (all entries), info (metadata).'
50
58
  ),
51
59
  content_id: z
52
60
  .string()
53
61
  .optional()
54
62
  .describe(
55
- 'ID of the content entry. Required for read/search/info. Omit for list.',
63
+ 'ID of the content entry. Required for read/search/info. Omit for list.'
56
64
  ),
57
65
 
58
66
  // read pagination
@@ -60,13 +68,28 @@ export const contentReaderSchema = z.object({
60
68
  end_line: z.number().optional().describe('1-based end line (inclusive).'),
61
69
 
62
70
  // search
63
- pattern: z.string().optional().describe('Regex pattern (required for search).'),
64
- flags: z.string().optional().describe('Regex flags (e.g., "i" for case-insensitive).'),
65
- context: z.number().optional().describe('Lines of context around each match.'),
71
+ pattern: z
72
+ .string()
73
+ .optional()
74
+ .describe('Regex pattern (required for search).'),
75
+ flags: z
76
+ .string()
77
+ .optional()
78
+ .describe('Regex flags (e.g., "i" for case-insensitive).'),
79
+ context: z
80
+ .number()
81
+ .optional()
82
+ .describe('Lines of context around each match.'),
66
83
 
67
84
  // shared pagination
68
- offset: z.number().optional().describe('Offset for read or search pagination.'),
69
- limit: z.number().optional().describe('Max lines (read) or matches (search).'),
85
+ offset: z
86
+ .number()
87
+ .optional()
88
+ .describe('Offset for read or search pagination.'),
89
+ limit: z
90
+ .number()
91
+ .optional()
92
+ .describe('Max lines (read) or matches (search).'),
70
93
  });
71
94
 
72
95
  export const ARTIFACT_TOOL_NAME = 'artifact_tool';