@illuma-ai/agents 1.4.0-alpha.4 → 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.
@@ -22,9 +22,10 @@ class ToolsServerCapabilityProvider {
22
22
  manifestPath;
23
23
  executePath;
24
24
  cache;
25
+ getExecuteAuthHeaders;
25
26
  constructor(config) {
26
27
  this.config = config;
27
- const { baseUrl, apiKey, manifestPath = '/manifest', executePath = '/execute/:name', timeoutMs = 30_000, manifestTtlMs = 60_000, client, proxy, } = config;
28
+ const { baseUrl, apiKey, manifestPath = '/manifest', executePath = '/execute/:name', timeoutMs = 30_000, manifestTtlMs = 60_000, client, proxy, getExecuteAuthHeaders, } = config;
28
29
  if (!baseUrl) {
29
30
  throw new Error('ToolsServerCapabilityProvider: baseUrl is required');
30
31
  }
@@ -35,6 +36,7 @@ class ToolsServerCapabilityProvider {
35
36
  this.manifestPath = manifestPath;
36
37
  this.executePath = executePath;
37
38
  this.cache = new toolManifest.ManifestCache({ ttlMs: manifestTtlMs });
39
+ this.getExecuteAuthHeaders = getExecuteAuthHeaders;
38
40
  if (client) {
39
41
  this.client = client;
40
42
  }
@@ -81,6 +83,7 @@ class ToolsServerCapabilityProvider {
81
83
  return capabilities.map((cap) => proxyTool.buildProxyTool(cap, credentials, {
82
84
  client: this.client,
83
85
  executePath: this.executePath,
86
+ getAuthHeaders: this.getExecuteAuthHeaders,
84
87
  }));
85
88
  }
86
89
  /** Force a manifest refresh on next fetchManifest call. */
@@ -1 +1 @@
1
- {"version":3,"file":"ToolsServerCapabilityProvider.cjs","sources":["../../../../src/providers/tools-server/ToolsServerCapabilityProvider.ts"],"sourcesContent":["/**\n * ToolsServerCapabilityProvider — capabilities sourced from a tools-server\n * HTTP endpoint.\n *\n * Fetches the manifest via GET /manifest, builds proxy StructuredTools via\n * POST /execute/:tool. Manifest is cached (TTL-configurable) so repeated\n * init cycles don't refetch.\n *\n * See docs/architecture-capability-layer.md for the design rationale.\n */\n\nimport type { AxiosInstance } from 'axios';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport {\n CapabilityKind,\n type Capability,\n type CapabilityFilter,\n type CapabilityProvider,\n type CredentialMap,\n} from '@/providers/types';\nimport {\n createHttpClient,\n type HttpClientConfig,\n assertOk,\n} from '@/utils/httpClient';\nimport {\n ManifestCache,\n filterToCacheKey,\n applyFilter,\n} from '@/utils/toolManifest';\nimport { buildProxyTool } from '@/tools/proxyTool';\n\n/** Configuration for ToolsServerCapabilityProvider. */\nexport interface ToolsServerConfig {\n /** Tools-server base URL (e.g., https://tools.illuma.ai or http://localhost:3500). */\n baseUrl: string;\n /** API key sent as x-api-key header on all requests. Required. */\n apiKey: string;\n /** Optional override for manifest path. Defaults to `/manifest`. */\n manifestPath?: string;\n /** Optional override for execute path template. Defaults to `/execute/:name`. */\n executePath?: string;\n /** Request timeout in ms. Defaults to 30_000. */\n timeoutMs?: number;\n /** Manifest cache TTL in ms. 0 disables. Defaults to 60_000 (1 min). */\n manifestTtlMs?: number;\n /** Optional pre-built axios instance (for testing). */\n client?: AxiosInstance;\n /** Optional proxy override (defaults to process.env.PROXY). */\n proxy?: string | null;\n}\n\n/**\n * Shape of a single entry from tools-server's /manifest response.\n *\n * Matches the tools-server ManifestToolEntry type. We define our own here\n * to avoid cross-package coupling — the shape is a stable HTTP contract.\n */\ninterface ToolsServerManifestEntry {\n pluginKey: string;\n name: string;\n description: string;\n icon?: string;\n authConfig?: Array<{\n authField: string;\n label?: string;\n description?: string;\n source?: string;\n required?: boolean;\n prod?: boolean;\n admin?: boolean;\n hide?: boolean;\n }>;\n /** Input schema as JSON Schema (tools-server emits `schema`). */\n schema?: unknown;\n /** Legacy alias accepted for backwards compatibility. */\n jsonSchema?: unknown;\n category?: string;\n tags?: string[];\n toolkit?: string;\n endpoint?: string;\n /** Governance flags forwarded by tools-server getManifest(). */\n hidden?: boolean;\n admin?: boolean;\n env?: Array<'development' | 'production' | 'test' | 'staging'>;\n}\n\nexport class ToolsServerCapabilityProvider implements CapabilityProvider {\n readonly providerId: string;\n private readonly client: AxiosInstance;\n private readonly manifestPath: string;\n private readonly executePath: string;\n private readonly cache: ManifestCache;\n\n constructor(private readonly config: ToolsServerConfig) {\n const {\n baseUrl,\n apiKey,\n manifestPath = '/manifest',\n executePath = '/execute/:name',\n timeoutMs = 30_000,\n manifestTtlMs = 60_000,\n client,\n proxy,\n } = config;\n\n if (!baseUrl) {\n throw new Error('ToolsServerCapabilityProvider: baseUrl is required');\n }\n if (!apiKey) {\n throw new Error('ToolsServerCapabilityProvider: apiKey is required');\n }\n\n this.providerId = `tools-server:${baseUrl}`;\n this.manifestPath = manifestPath;\n this.executePath = executePath;\n this.cache = new ManifestCache({ ttlMs: manifestTtlMs });\n\n if (client) {\n this.client = client;\n } else {\n const httpConfig: HttpClientConfig = {\n baseURL: baseUrl,\n apiKey,\n timeoutMs,\n proxy,\n };\n this.client = createHttpClient(httpConfig);\n }\n }\n\n async fetchManifest(filter?: CapabilityFilter): Promise<Capability[]> {\n const cacheKey = filterToCacheKey(filter);\n const cached = this.cache.get(cacheKey);\n if (cached) {\n // DEBUG: cache hit (remove after stabilization)\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] manifest cache hit — ${cached.length} caps`\n );\n return cached;\n }\n\n // DEBUG\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] fetching manifest from ${this.manifestPath}`\n );\n\n const res = await this.client.get<ToolsServerManifestEntry[]>(\n this.manifestPath\n );\n assertOk(res.status, this.manifestPath, res.data);\n\n const entries = Array.isArray(res.data) ? res.data : [];\n const capabilities: Capability[] = entries.map((entry) =>\n normalizeEntry(entry)\n );\n\n // Apply filter before caching the full list separately so unfiltered\n // refetches don't re-hit the network.\n const fullCacheKey = filterToCacheKey();\n this.cache.set(fullCacheKey, capabilities);\n\n const filtered = applyFilter(capabilities, filter);\n if (cacheKey !== fullCacheKey) {\n this.cache.set(cacheKey, filtered);\n }\n\n // DEBUG\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] manifest loaded — ${capabilities.length} caps, ${filtered.length} after filter`\n );\n\n return filtered;\n }\n\n async createRunnables(\n capabilities: Capability[],\n credentials: CredentialMap\n ): Promise<StructuredToolInterface[]> {\n return capabilities.map((cap) =>\n buildProxyTool(cap, credentials, {\n client: this.client,\n executePath: this.executePath,\n })\n );\n }\n\n /** Force a manifest refresh on next fetchManifest call. */\n invalidateCache(): void {\n this.cache.clear();\n }\n}\n\n/**\n * Translate a raw tools-server manifest entry into a typed Capability.\n * Defensive — tools-server may add fields; we pull only what we need.\n */\nfunction normalizeEntry(entry: ToolsServerManifestEntry): Capability {\n return {\n kind: CapabilityKind.TOOL,\n name: entry.pluginKey,\n description: entry.description ?? '',\n schema: entry.schema ?? entry.jsonSchema,\n authConfig: (entry.authConfig ?? []).map((ac) => ({\n authField: ac.authField,\n label: ac.label,\n description: ac.description,\n required: ac.required,\n prod: ac.prod,\n admin: ac.admin,\n hide: ac.hide,\n // Source is a string in wire format; downstream consumers coerce to AuthSource.\n source: ac.source as Capability['authConfig'][number]['source'],\n })),\n metadata: {\n icon: entry.icon,\n category: entry.category,\n tags: entry.tags,\n // Governance — hosts use these to gate UI/role/environment.\n hidden: entry.hidden,\n admin: entry.admin,\n env: entry.env,\n },\n };\n}\n"],"names":["ManifestCache","createHttpClient","filterToCacheKey","assertOk","applyFilter","buildProxyTool","CapabilityKind"],"mappings":"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"}
1
+ {"version":3,"file":"ToolsServerCapabilityProvider.cjs","sources":["../../../../src/providers/tools-server/ToolsServerCapabilityProvider.ts"],"sourcesContent":["/**\n * ToolsServerCapabilityProvider — capabilities sourced from a tools-server\n * HTTP endpoint.\n *\n * Fetches the manifest via GET /manifest, builds proxy StructuredTools via\n * POST /execute/:tool. Manifest is cached (TTL-configurable) so repeated\n * init cycles don't refetch.\n *\n * See docs/architecture-capability-layer.md for the design rationale.\n */\n\nimport type { AxiosInstance } from 'axios';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport {\n CapabilityKind,\n type Capability,\n type CapabilityFilter,\n type CapabilityProvider,\n type CredentialMap,\n} from '@/providers/types';\nimport {\n createHttpClient,\n type HttpClientConfig,\n assertOk,\n} from '@/utils/httpClient';\nimport {\n ManifestCache,\n filterToCacheKey,\n applyFilter,\n} from '@/utils/toolManifest';\nimport { buildProxyTool } from '@/tools/proxyTool';\n\n/** Configuration for ToolsServerCapabilityProvider. */\nexport interface ToolsServerConfig {\n /** Tools-server base URL (e.g., https://tools.illuma.ai or http://localhost:3500). */\n baseUrl: string;\n /** API key sent as x-api-key header on all requests. Required. */\n apiKey: string;\n /** Optional override for manifest path. Defaults to `/manifest`. */\n manifestPath?: string;\n /** Optional override for execute path template. Defaults to `/execute/:name`. */\n executePath?: string;\n /** Request timeout in ms. Defaults to 30_000. */\n timeoutMs?: number;\n /** Manifest cache TTL in ms. 0 disables. Defaults to 60_000 (1 min). */\n manifestTtlMs?: number;\n /** Optional pre-built axios instance (for testing). */\n client?: AxiosInstance;\n /** Optional proxy override (defaults to process.env.PROXY). */\n proxy?: string | null;\n /**\n * Optional per-request auth header builder — invoked on every tool\n * invocation (NOT on the manifest fetch, which is service-to-service).\n * When provided, the returned headers are merged into the `/execute`\n * request so the host can pass user-scoped identity (e.g.,\n * `Authorization: Bearer <jwt>`) that tools-server verifies for\n * admin-gated tools.\n *\n * Typical host wiring: mint a short-lived JWT per call carrying the\n * authenticated user's `{ userId, role }` claims; tools-server's\n * `TOOLS_SERVER_JWT_SECRET` validates.\n */\n getExecuteAuthHeaders?: () =>\n | Record<string, string>\n | Promise<Record<string, string>>;\n}\n\n/**\n * Shape of a single entry from tools-server's /manifest response.\n *\n * Matches the tools-server ManifestToolEntry type. We define our own here\n * to avoid cross-package coupling — the shape is a stable HTTP contract.\n */\ninterface ToolsServerManifestEntry {\n pluginKey: string;\n name: string;\n description: string;\n icon?: string;\n authConfig?: Array<{\n authField: string;\n label?: string;\n description?: string;\n source?: string;\n required?: boolean;\n prod?: boolean;\n admin?: boolean;\n hide?: boolean;\n }>;\n /** Input schema as JSON Schema (tools-server emits `schema`). */\n schema?: unknown;\n /** Legacy alias accepted for backwards compatibility. */\n jsonSchema?: unknown;\n category?: string;\n tags?: string[];\n toolkit?: string;\n endpoint?: string;\n /** Governance flags forwarded by tools-server getManifest(). */\n hidden?: boolean;\n admin?: boolean;\n env?: Array<'development' | 'production' | 'test' | 'staging'>;\n}\n\nexport class ToolsServerCapabilityProvider implements CapabilityProvider {\n readonly providerId: string;\n private readonly client: AxiosInstance;\n private readonly manifestPath: string;\n private readonly executePath: string;\n private readonly cache: ManifestCache;\n private readonly getExecuteAuthHeaders?:\n | (() => Record<string, string> | Promise<Record<string, string>>);\n\n constructor(private readonly config: ToolsServerConfig) {\n const {\n baseUrl,\n apiKey,\n manifestPath = '/manifest',\n executePath = '/execute/:name',\n timeoutMs = 30_000,\n manifestTtlMs = 60_000,\n client,\n proxy,\n getExecuteAuthHeaders,\n } = config;\n\n if (!baseUrl) {\n throw new Error('ToolsServerCapabilityProvider: baseUrl is required');\n }\n if (!apiKey) {\n throw new Error('ToolsServerCapabilityProvider: apiKey is required');\n }\n\n this.providerId = `tools-server:${baseUrl}`;\n this.manifestPath = manifestPath;\n this.executePath = executePath;\n this.cache = new ManifestCache({ ttlMs: manifestTtlMs });\n this.getExecuteAuthHeaders = getExecuteAuthHeaders;\n\n if (client) {\n this.client = client;\n } else {\n const httpConfig: HttpClientConfig = {\n baseURL: baseUrl,\n apiKey,\n timeoutMs,\n proxy,\n };\n this.client = createHttpClient(httpConfig);\n }\n }\n\n async fetchManifest(filter?: CapabilityFilter): Promise<Capability[]> {\n const cacheKey = filterToCacheKey(filter);\n const cached = this.cache.get(cacheKey);\n if (cached) {\n // DEBUG: cache hit (remove after stabilization)\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] manifest cache hit — ${cached.length} caps`\n );\n return cached;\n }\n\n // DEBUG\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] fetching manifest from ${this.manifestPath}`\n );\n\n const res = await this.client.get<ToolsServerManifestEntry[]>(\n this.manifestPath\n );\n assertOk(res.status, this.manifestPath, res.data);\n\n const entries = Array.isArray(res.data) ? res.data : [];\n const capabilities: Capability[] = entries.map((entry) =>\n normalizeEntry(entry)\n );\n\n // Apply filter before caching the full list separately so unfiltered\n // refetches don't re-hit the network.\n const fullCacheKey = filterToCacheKey();\n this.cache.set(fullCacheKey, capabilities);\n\n const filtered = applyFilter(capabilities, filter);\n if (cacheKey !== fullCacheKey) {\n this.cache.set(cacheKey, filtered);\n }\n\n // DEBUG\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] manifest loaded — ${capabilities.length} caps, ${filtered.length} after filter`\n );\n\n return filtered;\n }\n\n async createRunnables(\n capabilities: Capability[],\n credentials: CredentialMap\n ): Promise<StructuredToolInterface[]> {\n return capabilities.map((cap) =>\n buildProxyTool(cap, credentials, {\n client: this.client,\n executePath: this.executePath,\n getAuthHeaders: this.getExecuteAuthHeaders,\n })\n );\n }\n\n /** Force a manifest refresh on next fetchManifest call. */\n invalidateCache(): void {\n this.cache.clear();\n }\n}\n\n/**\n * Translate a raw tools-server manifest entry into a typed Capability.\n * Defensive — tools-server may add fields; we pull only what we need.\n */\nfunction normalizeEntry(entry: ToolsServerManifestEntry): Capability {\n return {\n kind: CapabilityKind.TOOL,\n name: entry.pluginKey,\n description: entry.description ?? '',\n schema: entry.schema ?? entry.jsonSchema,\n authConfig: (entry.authConfig ?? []).map((ac) => ({\n authField: ac.authField,\n label: ac.label,\n description: ac.description,\n required: ac.required,\n prod: ac.prod,\n admin: ac.admin,\n hide: ac.hide,\n // Source is a string in wire format; downstream consumers coerce to AuthSource.\n source: ac.source as Capability['authConfig'][number]['source'],\n })),\n metadata: {\n icon: entry.icon,\n category: entry.category,\n tags: entry.tags,\n // Governance — hosts use these to gate UI/role/environment.\n hidden: entry.hidden,\n admin: entry.admin,\n env: entry.env,\n },\n };\n}\n"],"names":["ManifestCache","createHttpClient","filterToCacheKey","assertOk","applyFilter","buildProxyTool","CapabilityKind"],"mappings":"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"}
@@ -24,7 +24,7 @@ var httpClient = require('../utils/httpClient.cjs');
24
24
  * per-invocation credential rotation should rebuild the tool.
25
25
  */
26
26
  function buildProxyTool(capability, credentials, options) {
27
- const { client, executePath = '/execute/:name', onExecute } = options;
27
+ const { client, executePath = '/execute/:name', onExecute, getAuthHeaders, } = options;
28
28
  const url = executePath.replace(':name', encodeURIComponent(capability.name));
29
29
  return tools.tool(async (input) => {
30
30
  const startMs = Date.now();
@@ -33,10 +33,12 @@ function buildProxyTool(capability, credentials, options) {
33
33
  // DEBUG: log (remove after POC stabilizes)
34
34
  // eslint-disable-next-line no-console
35
35
  console.debug(`${debugPrefix} invoking — inputKeys=${input && typeof input === 'object' ? Object.keys(input).length : 0}`);
36
- const res = await client.post(url, {
37
- input,
38
- credentials,
39
- });
36
+ const extraHeaders = getAuthHeaders
37
+ ? await getAuthHeaders()
38
+ : undefined;
39
+ const res = await client.post(url, { input, credentials }, extraHeaders && Object.keys(extraHeaders).length > 0
40
+ ? { headers: extraHeaders }
41
+ : undefined);
40
42
  const durationMs = Date.now() - startMs;
41
43
  if (res.status < 200 || res.status >= 300) {
42
44
  throw new httpClient.HttpError(res.status, url, res.data);
@@ -1 +1 @@
1
- {"version":3,"file":"proxyTool.cjs","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":["tool","HttpError"],"mappings":";;;;;AAAA;;;;;;;;;;;;;AAaG;AAuCH;;;;;AAKG;SACa,cAAc,CAC5B,UAAsB,EACtB,WAA0B,EAC1B,OAAyB,EAAA;IAEzB,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,gBAAgB,EAAE,SAAS,EAAE,GAAG,OAAO;AACrE,IAAA,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AAE7E,IAAA,OAAOA,UAAI,CACT,OAAO,KAAc,KAAqB;AACxC,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE;AAC1B,QAAA,MAAM,WAAW,GAAG,CAAA,WAAA,EAAc,UAAU,CAAC,IAAI,GAAG;AAEpD,QAAA,IAAI;;;AAGF,YAAA,OAAO,CAAC,KAAK,CACX,CAAA,EAAG,WAAW,CAAA,sBAAA,EAAyB,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA,CAAE,CACtH;YAED,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAkB,GAAG,EAAE;gBAClD,KAAK;gBACL,WAAW;AACZ,aAAA,CAAC;YAEF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;AAEvC,YAAA,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,EAAE;AACzC,gBAAA,MAAM,IAAIC,oBAAS,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC;YAChD;AAEA,YAAA,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI;YACrB,IAAI,SAAS,EAAE;AACb,gBAAA,IAAI;AACF,oBAAA,SAAS,CAAC;wBACR,cAAc,EAAE,UAAU,CAAC,IAAI;wBAC/B,KAAK;AACL,wBAAA,QAAQ,EAAE,IAAI;wBACd,UAAU;AACX,qBAAA,CAAC;gBACJ;AAAE,gBAAA,MAAM;;gBAER;YACF;AAEA,YAAA,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;;;gBAGjB,OAAO,CAAC,KAAK,CACX,CAAA,EAAG,WAAW,CAAA,4BAAA,EAA+B,IAAI,CAAC,KAAK,CAAA,CAAE,CAC1D;gBACD,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,uBAAuB,CAAC;YACxD;;AAGA,YAAA,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE;gBACnC,OAAO,IAAI,CAAC,MAAM;YACpB;YACA,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;QACpC;QAAE,OAAO,GAAG,EAAE;YACZ,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;YACvC,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,GAAG,GAAG,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,IAAI,SAAS,EAAE;AACb,gBAAA,IAAI;AACF,oBAAA,SAAS,CAAC;wBACR,cAAc,EAAE,UAAU,CAAC,IAAI;wBAC/B,KAAK;wBACL,KAAK;wBACL,UAAU;AACX,qBAAA,CAAC;gBACJ;AAAE,gBAAA,MAAM;;gBAER;YACF;AACA,YAAA,MAAM,KAAK;QACb;AACF,IAAA,CAAC,EACD;QACE,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,WAAW,EAAE,UAAU,CAAC,WAAW;AACnC,QAAA,MAAM,EAAG,UAAU,CAAC,MAAiB,IAAI;AACvC,YAAA,IAAI,EAAE,QAAQ;AACd,YAAA,UAAU,EAAE,EAAE;AACf,SAAA;AACD,QAAA,cAAc,EAAE,SAAS;AAC1B,KAAA,CACF;AACH;;;;"}
1
+ {"version":3,"file":"proxyTool.cjs","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":["tool","HttpError"],"mappings":"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"}
@@ -20,9 +20,10 @@ class ToolsServerCapabilityProvider {
20
20
  manifestPath;
21
21
  executePath;
22
22
  cache;
23
+ getExecuteAuthHeaders;
23
24
  constructor(config) {
24
25
  this.config = config;
25
- const { baseUrl, apiKey, manifestPath = '/manifest', executePath = '/execute/:name', timeoutMs = 30_000, manifestTtlMs = 60_000, client, proxy, } = config;
26
+ const { baseUrl, apiKey, manifestPath = '/manifest', executePath = '/execute/:name', timeoutMs = 30_000, manifestTtlMs = 60_000, client, proxy, getExecuteAuthHeaders, } = config;
26
27
  if (!baseUrl) {
27
28
  throw new Error('ToolsServerCapabilityProvider: baseUrl is required');
28
29
  }
@@ -33,6 +34,7 @@ class ToolsServerCapabilityProvider {
33
34
  this.manifestPath = manifestPath;
34
35
  this.executePath = executePath;
35
36
  this.cache = new ManifestCache({ ttlMs: manifestTtlMs });
37
+ this.getExecuteAuthHeaders = getExecuteAuthHeaders;
36
38
  if (client) {
37
39
  this.client = client;
38
40
  }
@@ -79,6 +81,7 @@ class ToolsServerCapabilityProvider {
79
81
  return capabilities.map((cap) => buildProxyTool(cap, credentials, {
80
82
  client: this.client,
81
83
  executePath: this.executePath,
84
+ getAuthHeaders: this.getExecuteAuthHeaders,
82
85
  }));
83
86
  }
84
87
  /** Force a manifest refresh on next fetchManifest call. */
@@ -1 +1 @@
1
- {"version":3,"file":"ToolsServerCapabilityProvider.mjs","sources":["../../../../src/providers/tools-server/ToolsServerCapabilityProvider.ts"],"sourcesContent":["/**\n * ToolsServerCapabilityProvider — capabilities sourced from a tools-server\n * HTTP endpoint.\n *\n * Fetches the manifest via GET /manifest, builds proxy StructuredTools via\n * POST /execute/:tool. Manifest is cached (TTL-configurable) so repeated\n * init cycles don't refetch.\n *\n * See docs/architecture-capability-layer.md for the design rationale.\n */\n\nimport type { AxiosInstance } from 'axios';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport {\n CapabilityKind,\n type Capability,\n type CapabilityFilter,\n type CapabilityProvider,\n type CredentialMap,\n} from '@/providers/types';\nimport {\n createHttpClient,\n type HttpClientConfig,\n assertOk,\n} from '@/utils/httpClient';\nimport {\n ManifestCache,\n filterToCacheKey,\n applyFilter,\n} from '@/utils/toolManifest';\nimport { buildProxyTool } from '@/tools/proxyTool';\n\n/** Configuration for ToolsServerCapabilityProvider. */\nexport interface ToolsServerConfig {\n /** Tools-server base URL (e.g., https://tools.illuma.ai or http://localhost:3500). */\n baseUrl: string;\n /** API key sent as x-api-key header on all requests. Required. */\n apiKey: string;\n /** Optional override for manifest path. Defaults to `/manifest`. */\n manifestPath?: string;\n /** Optional override for execute path template. Defaults to `/execute/:name`. */\n executePath?: string;\n /** Request timeout in ms. Defaults to 30_000. */\n timeoutMs?: number;\n /** Manifest cache TTL in ms. 0 disables. Defaults to 60_000 (1 min). */\n manifestTtlMs?: number;\n /** Optional pre-built axios instance (for testing). */\n client?: AxiosInstance;\n /** Optional proxy override (defaults to process.env.PROXY). */\n proxy?: string | null;\n}\n\n/**\n * Shape of a single entry from tools-server's /manifest response.\n *\n * Matches the tools-server ManifestToolEntry type. We define our own here\n * to avoid cross-package coupling — the shape is a stable HTTP contract.\n */\ninterface ToolsServerManifestEntry {\n pluginKey: string;\n name: string;\n description: string;\n icon?: string;\n authConfig?: Array<{\n authField: string;\n label?: string;\n description?: string;\n source?: string;\n required?: boolean;\n prod?: boolean;\n admin?: boolean;\n hide?: boolean;\n }>;\n /** Input schema as JSON Schema (tools-server emits `schema`). */\n schema?: unknown;\n /** Legacy alias accepted for backwards compatibility. */\n jsonSchema?: unknown;\n category?: string;\n tags?: string[];\n toolkit?: string;\n endpoint?: string;\n /** Governance flags forwarded by tools-server getManifest(). */\n hidden?: boolean;\n admin?: boolean;\n env?: Array<'development' | 'production' | 'test' | 'staging'>;\n}\n\nexport class ToolsServerCapabilityProvider implements CapabilityProvider {\n readonly providerId: string;\n private readonly client: AxiosInstance;\n private readonly manifestPath: string;\n private readonly executePath: string;\n private readonly cache: ManifestCache;\n\n constructor(private readonly config: ToolsServerConfig) {\n const {\n baseUrl,\n apiKey,\n manifestPath = '/manifest',\n executePath = '/execute/:name',\n timeoutMs = 30_000,\n manifestTtlMs = 60_000,\n client,\n proxy,\n } = config;\n\n if (!baseUrl) {\n throw new Error('ToolsServerCapabilityProvider: baseUrl is required');\n }\n if (!apiKey) {\n throw new Error('ToolsServerCapabilityProvider: apiKey is required');\n }\n\n this.providerId = `tools-server:${baseUrl}`;\n this.manifestPath = manifestPath;\n this.executePath = executePath;\n this.cache = new ManifestCache({ ttlMs: manifestTtlMs });\n\n if (client) {\n this.client = client;\n } else {\n const httpConfig: HttpClientConfig = {\n baseURL: baseUrl,\n apiKey,\n timeoutMs,\n proxy,\n };\n this.client = createHttpClient(httpConfig);\n }\n }\n\n async fetchManifest(filter?: CapabilityFilter): Promise<Capability[]> {\n const cacheKey = filterToCacheKey(filter);\n const cached = this.cache.get(cacheKey);\n if (cached) {\n // DEBUG: cache hit (remove after stabilization)\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] manifest cache hit — ${cached.length} caps`\n );\n return cached;\n }\n\n // DEBUG\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] fetching manifest from ${this.manifestPath}`\n );\n\n const res = await this.client.get<ToolsServerManifestEntry[]>(\n this.manifestPath\n );\n assertOk(res.status, this.manifestPath, res.data);\n\n const entries = Array.isArray(res.data) ? res.data : [];\n const capabilities: Capability[] = entries.map((entry) =>\n normalizeEntry(entry)\n );\n\n // Apply filter before caching the full list separately so unfiltered\n // refetches don't re-hit the network.\n const fullCacheKey = filterToCacheKey();\n this.cache.set(fullCacheKey, capabilities);\n\n const filtered = applyFilter(capabilities, filter);\n if (cacheKey !== fullCacheKey) {\n this.cache.set(cacheKey, filtered);\n }\n\n // DEBUG\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] manifest loaded — ${capabilities.length} caps, ${filtered.length} after filter`\n );\n\n return filtered;\n }\n\n async createRunnables(\n capabilities: Capability[],\n credentials: CredentialMap\n ): Promise<StructuredToolInterface[]> {\n return capabilities.map((cap) =>\n buildProxyTool(cap, credentials, {\n client: this.client,\n executePath: this.executePath,\n })\n );\n }\n\n /** Force a manifest refresh on next fetchManifest call. */\n invalidateCache(): void {\n this.cache.clear();\n }\n}\n\n/**\n * Translate a raw tools-server manifest entry into a typed Capability.\n * Defensive — tools-server may add fields; we pull only what we need.\n */\nfunction normalizeEntry(entry: ToolsServerManifestEntry): Capability {\n return {\n kind: CapabilityKind.TOOL,\n name: entry.pluginKey,\n description: entry.description ?? '',\n schema: entry.schema ?? entry.jsonSchema,\n authConfig: (entry.authConfig ?? []).map((ac) => ({\n authField: ac.authField,\n label: ac.label,\n description: ac.description,\n required: ac.required,\n prod: ac.prod,\n admin: ac.admin,\n hide: ac.hide,\n // Source is a string in wire format; downstream consumers coerce to AuthSource.\n source: ac.source as Capability['authConfig'][number]['source'],\n })),\n metadata: {\n icon: entry.icon,\n category: entry.category,\n tags: entry.tags,\n // Governance — hosts use these to gate UI/role/environment.\n hidden: entry.hidden,\n admin: entry.admin,\n env: entry.env,\n },\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ToolsServerCapabilityProvider.mjs","sources":["../../../../src/providers/tools-server/ToolsServerCapabilityProvider.ts"],"sourcesContent":["/**\n * ToolsServerCapabilityProvider — capabilities sourced from a tools-server\n * HTTP endpoint.\n *\n * Fetches the manifest via GET /manifest, builds proxy StructuredTools via\n * POST /execute/:tool. Manifest is cached (TTL-configurable) so repeated\n * init cycles don't refetch.\n *\n * See docs/architecture-capability-layer.md for the design rationale.\n */\n\nimport type { AxiosInstance } from 'axios';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport {\n CapabilityKind,\n type Capability,\n type CapabilityFilter,\n type CapabilityProvider,\n type CredentialMap,\n} from '@/providers/types';\nimport {\n createHttpClient,\n type HttpClientConfig,\n assertOk,\n} from '@/utils/httpClient';\nimport {\n ManifestCache,\n filterToCacheKey,\n applyFilter,\n} from '@/utils/toolManifest';\nimport { buildProxyTool } from '@/tools/proxyTool';\n\n/** Configuration for ToolsServerCapabilityProvider. */\nexport interface ToolsServerConfig {\n /** Tools-server base URL (e.g., https://tools.illuma.ai or http://localhost:3500). */\n baseUrl: string;\n /** API key sent as x-api-key header on all requests. Required. */\n apiKey: string;\n /** Optional override for manifest path. Defaults to `/manifest`. */\n manifestPath?: string;\n /** Optional override for execute path template. Defaults to `/execute/:name`. */\n executePath?: string;\n /** Request timeout in ms. Defaults to 30_000. */\n timeoutMs?: number;\n /** Manifest cache TTL in ms. 0 disables. Defaults to 60_000 (1 min). */\n manifestTtlMs?: number;\n /** Optional pre-built axios instance (for testing). */\n client?: AxiosInstance;\n /** Optional proxy override (defaults to process.env.PROXY). */\n proxy?: string | null;\n /**\n * Optional per-request auth header builder — invoked on every tool\n * invocation (NOT on the manifest fetch, which is service-to-service).\n * When provided, the returned headers are merged into the `/execute`\n * request so the host can pass user-scoped identity (e.g.,\n * `Authorization: Bearer <jwt>`) that tools-server verifies for\n * admin-gated tools.\n *\n * Typical host wiring: mint a short-lived JWT per call carrying the\n * authenticated user's `{ userId, role }` claims; tools-server's\n * `TOOLS_SERVER_JWT_SECRET` validates.\n */\n getExecuteAuthHeaders?: () =>\n | Record<string, string>\n | Promise<Record<string, string>>;\n}\n\n/**\n * Shape of a single entry from tools-server's /manifest response.\n *\n * Matches the tools-server ManifestToolEntry type. We define our own here\n * to avoid cross-package coupling — the shape is a stable HTTP contract.\n */\ninterface ToolsServerManifestEntry {\n pluginKey: string;\n name: string;\n description: string;\n icon?: string;\n authConfig?: Array<{\n authField: string;\n label?: string;\n description?: string;\n source?: string;\n required?: boolean;\n prod?: boolean;\n admin?: boolean;\n hide?: boolean;\n }>;\n /** Input schema as JSON Schema (tools-server emits `schema`). */\n schema?: unknown;\n /** Legacy alias accepted for backwards compatibility. */\n jsonSchema?: unknown;\n category?: string;\n tags?: string[];\n toolkit?: string;\n endpoint?: string;\n /** Governance flags forwarded by tools-server getManifest(). */\n hidden?: boolean;\n admin?: boolean;\n env?: Array<'development' | 'production' | 'test' | 'staging'>;\n}\n\nexport class ToolsServerCapabilityProvider implements CapabilityProvider {\n readonly providerId: string;\n private readonly client: AxiosInstance;\n private readonly manifestPath: string;\n private readonly executePath: string;\n private readonly cache: ManifestCache;\n private readonly getExecuteAuthHeaders?:\n | (() => Record<string, string> | Promise<Record<string, string>>);\n\n constructor(private readonly config: ToolsServerConfig) {\n const {\n baseUrl,\n apiKey,\n manifestPath = '/manifest',\n executePath = '/execute/:name',\n timeoutMs = 30_000,\n manifestTtlMs = 60_000,\n client,\n proxy,\n getExecuteAuthHeaders,\n } = config;\n\n if (!baseUrl) {\n throw new Error('ToolsServerCapabilityProvider: baseUrl is required');\n }\n if (!apiKey) {\n throw new Error('ToolsServerCapabilityProvider: apiKey is required');\n }\n\n this.providerId = `tools-server:${baseUrl}`;\n this.manifestPath = manifestPath;\n this.executePath = executePath;\n this.cache = new ManifestCache({ ttlMs: manifestTtlMs });\n this.getExecuteAuthHeaders = getExecuteAuthHeaders;\n\n if (client) {\n this.client = client;\n } else {\n const httpConfig: HttpClientConfig = {\n baseURL: baseUrl,\n apiKey,\n timeoutMs,\n proxy,\n };\n this.client = createHttpClient(httpConfig);\n }\n }\n\n async fetchManifest(filter?: CapabilityFilter): Promise<Capability[]> {\n const cacheKey = filterToCacheKey(filter);\n const cached = this.cache.get(cacheKey);\n if (cached) {\n // DEBUG: cache hit (remove after stabilization)\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] manifest cache hit — ${cached.length} caps`\n );\n return cached;\n }\n\n // DEBUG\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] fetching manifest from ${this.manifestPath}`\n );\n\n const res = await this.client.get<ToolsServerManifestEntry[]>(\n this.manifestPath\n );\n assertOk(res.status, this.manifestPath, res.data);\n\n const entries = Array.isArray(res.data) ? res.data : [];\n const capabilities: Capability[] = entries.map((entry) =>\n normalizeEntry(entry)\n );\n\n // Apply filter before caching the full list separately so unfiltered\n // refetches don't re-hit the network.\n const fullCacheKey = filterToCacheKey();\n this.cache.set(fullCacheKey, capabilities);\n\n const filtered = applyFilter(capabilities, filter);\n if (cacheKey !== fullCacheKey) {\n this.cache.set(cacheKey, filtered);\n }\n\n // DEBUG\n // eslint-disable-next-line no-console\n console.debug(\n `[${this.providerId}] manifest loaded — ${capabilities.length} caps, ${filtered.length} after filter`\n );\n\n return filtered;\n }\n\n async createRunnables(\n capabilities: Capability[],\n credentials: CredentialMap\n ): Promise<StructuredToolInterface[]> {\n return capabilities.map((cap) =>\n buildProxyTool(cap, credentials, {\n client: this.client,\n executePath: this.executePath,\n getAuthHeaders: this.getExecuteAuthHeaders,\n })\n );\n }\n\n /** Force a manifest refresh on next fetchManifest call. */\n invalidateCache(): void {\n this.cache.clear();\n }\n}\n\n/**\n * Translate a raw tools-server manifest entry into a typed Capability.\n * Defensive — tools-server may add fields; we pull only what we need.\n */\nfunction normalizeEntry(entry: ToolsServerManifestEntry): Capability {\n return {\n kind: CapabilityKind.TOOL,\n name: entry.pluginKey,\n description: entry.description ?? '',\n schema: entry.schema ?? entry.jsonSchema,\n authConfig: (entry.authConfig ?? []).map((ac) => ({\n authField: ac.authField,\n label: ac.label,\n description: ac.description,\n required: ac.required,\n prod: ac.prod,\n admin: ac.admin,\n hide: ac.hide,\n // Source is a string in wire format; downstream consumers coerce to AuthSource.\n source: ac.source as Capability['authConfig'][number]['source'],\n })),\n metadata: {\n icon: entry.icon,\n category: entry.category,\n tags: entry.tags,\n // Governance — hosts use these to gate UI/role/environment.\n hidden: entry.hidden,\n admin: entry.admin,\n env: entry.env,\n },\n };\n}\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[]>;
@@ -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.4",
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
  /**
@@ -91,6 +106,8 @@ export class ToolsServerCapabilityProvider implements CapabilityProvider {
91
106
  private readonly manifestPath: string;
92
107
  private readonly executePath: string;
93
108
  private readonly cache: ManifestCache;
109
+ private readonly getExecuteAuthHeaders?:
110
+ | (() => Record<string, string> | Promise<Record<string, string>>);
94
111
 
95
112
  constructor(private readonly config: ToolsServerConfig) {
96
113
  const {
@@ -102,6 +119,7 @@ export class ToolsServerCapabilityProvider implements CapabilityProvider {
102
119
  manifestTtlMs = 60_000,
103
120
  client,
104
121
  proxy,
122
+ getExecuteAuthHeaders,
105
123
  } = config;
106
124
 
107
125
  if (!baseUrl) {
@@ -115,6 +133,7 @@ export class ToolsServerCapabilityProvider implements CapabilityProvider {
115
133
  this.manifestPath = manifestPath;
116
134
  this.executePath = executePath;
117
135
  this.cache = new ManifestCache({ ttlMs: manifestTtlMs });
136
+ this.getExecuteAuthHeaders = getExecuteAuthHeaders;
118
137
 
119
138
  if (client) {
120
139
  this.client = client;
@@ -184,6 +203,7 @@ export class ToolsServerCapabilityProvider implements CapabilityProvider {
184
203
  buildProxyTool(cap, credentials, {
185
204
  client: this.client,
186
205
  executePath: this.executePath,
206
+ getAuthHeaders: this.getExecuteAuthHeaders,
187
207
  })
188
208
  );
189
209
  }
@@ -40,6 +40,15 @@ export interface ProxyToolOptions {
40
40
  * telemetry, debug logging. Errors in the hook are swallowed.
41
41
  */
42
42
  onExecute?: (ctx: ExecuteCallbackContext) => void;
43
+ /**
44
+ * Optional per-invocation auth header builder. Called on every tool
45
+ * invocation before POSTing; returned headers are merged into the
46
+ * request alongside the base client's headers. Typical use: pass a
47
+ * freshly minted per-user JWT for admin-gated tools.
48
+ */
49
+ getAuthHeaders?: () =>
50
+ | Record<string, string>
51
+ | Promise<Record<string, string>>;
43
52
  }
44
53
 
45
54
  export interface ExecuteCallbackContext {
@@ -61,7 +70,12 @@ export function buildProxyTool(
61
70
  credentials: CredentialMap,
62
71
  options: ProxyToolOptions
63
72
  ): StructuredToolInterface {
64
- const { client, executePath = '/execute/:name', onExecute } = options;
73
+ const {
74
+ client,
75
+ executePath = '/execute/:name',
76
+ onExecute,
77
+ getAuthHeaders,
78
+ } = options;
65
79
  const url = executePath.replace(':name', encodeURIComponent(capability.name));
66
80
 
67
81
  return tool(
@@ -76,10 +90,16 @@ export function buildProxyTool(
76
90
  `${debugPrefix} invoking — inputKeys=${input && typeof input === 'object' ? Object.keys(input as object).length : 0}`
77
91
  );
78
92
 
79
- const res = await client.post<ExecuteResponse>(url, {
80
- input,
81
- credentials,
82
- });
93
+ const extraHeaders = getAuthHeaders
94
+ ? await getAuthHeaders()
95
+ : undefined;
96
+ const res = await client.post<ExecuteResponse>(
97
+ url,
98
+ { input, credentials },
99
+ extraHeaders && Object.keys(extraHeaders).length > 0
100
+ ? { headers: extraHeaders }
101
+ : undefined,
102
+ );
83
103
 
84
104
  const durationMs = Date.now() - startMs;
85
105