@kindgi/tools 0.0.0-bootstrap.0 → 0.1.1

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 (55) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +86 -1
  3. package/dist/define.d.ts +138 -0
  4. package/dist/define.d.ts.map +1 -0
  5. package/dist/define.js +318 -0
  6. package/dist/define.js.map +1 -0
  7. package/dist/errors.d.ts +113 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +4 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/http.d.ts +2 -0
  12. package/dist/http.d.ts.map +1 -0
  13. package/dist/http.js +213 -0
  14. package/dist/http.js.map +1 -0
  15. package/dist/index.d.ts +15 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +14 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/invoke.d.ts +27 -0
  20. package/dist/invoke.d.ts.map +1 -0
  21. package/dist/invoke.js +127 -0
  22. package/dist/invoke.js.map +1 -0
  23. package/dist/manifest.d.ts +23 -0
  24. package/dist/manifest.d.ts.map +1 -0
  25. package/dist/manifest.js +37 -0
  26. package/dist/manifest.js.map +1 -0
  27. package/dist/precondition.d.ts +25 -0
  28. package/dist/precondition.d.ts.map +1 -0
  29. package/dist/precondition.js +34 -0
  30. package/dist/precondition.js.map +1 -0
  31. package/dist/registry.d.ts +87 -0
  32. package/dist/registry.d.ts.map +1 -0
  33. package/dist/registry.js +171 -0
  34. package/dist/registry.js.map +1 -0
  35. package/dist/spec-registry.d.ts +48 -0
  36. package/dist/spec-registry.d.ts.map +1 -0
  37. package/dist/spec-registry.js +37 -0
  38. package/dist/spec-registry.js.map +1 -0
  39. package/dist/tool.schema.json +297 -0
  40. package/dist/types.d.ts +508 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +14 -0
  43. package/dist/types.js.map +1 -0
  44. package/package.json +64 -4
  45. package/src/define.ts +537 -0
  46. package/src/errors.ts +139 -0
  47. package/src/http.ts +268 -0
  48. package/src/index.ts +68 -0
  49. package/src/invoke.ts +165 -0
  50. package/src/manifest.ts +40 -0
  51. package/src/precondition.ts +40 -0
  52. package/src/registry.ts +262 -0
  53. package/src/spec-registry.ts +78 -0
  54. package/src/tool.schema.json +297 -0
  55. package/src/types.ts +538 -0
package/src/types.ts ADDED
@@ -0,0 +1,538 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import type { ZodLikeSchema } from '@kindgi/schema';
5
+ import type { TenantId, ToolId, UserId } from '@kindgi/types';
6
+
7
+ /**
8
+ * JSON Schema for a tool's input/output. Author-supplied, Draft 2020-12.
9
+ * Kept as `unknown` here — the loader validates that the provided value is
10
+ * a legal Ajv schema at `defineTool` time.
11
+ */
12
+ export type JsonSchema = Readonly<Record<string, unknown>>;
13
+
14
+ /** Closed set of effect kinds Kindgi recognises. Matches @kindgi/specs/tool.schema.json. */
15
+ export const EFFECT_KINDS = [
16
+ 'reads',
17
+ 'writes',
18
+ 'deletes',
19
+ 'network',
20
+ 'spawns-run',
21
+ 'emits-event',
22
+ 'external-side-effect',
23
+ 'sensitive-data-egress',
24
+ ] as const;
25
+
26
+ export type EffectKind = (typeof EFFECT_KINDS)[number];
27
+
28
+ /**
29
+ * A declared side-effect. Enables policy enforcement (can this tool run in
30
+ * this tenant?), safe parallelization (are two invocations conflicting?),
31
+ * and causal provenance annotation.
32
+ */
33
+ export interface Effect {
34
+ readonly kind: EffectKind;
35
+ /**
36
+ * Resource identifier the effect targets — for example `memory:facts`,
37
+ * `blob:*`, `external:api.example.com`. Opaque to this package; the
38
+ * policy engine interprets it when enforcing tenant policy.
39
+ */
40
+ readonly resource?: string;
41
+ readonly notes?: string;
42
+ }
43
+
44
+ /**
45
+ * A typed context requirement — a named slot the tool expects the runtime
46
+ * to supply. The name matches an entry in the tenant's provider registry
47
+ * (`tenant`, `db`, `memory:facts`, `blob`, etc.). Not enforced by this
48
+ * package; declarative metadata for capability + policy layers.
49
+ */
50
+ export interface Need {
51
+ readonly name: string;
52
+ readonly optional?: boolean;
53
+ }
54
+
55
+ /**
56
+ * The transport-neutral context a tool receives at invocation. Whether the
57
+ * tool is called from a kernel run (in-process) or over MCP (remote), the
58
+ * caller synthesises a `ToolContext` and passes it in.
59
+ *
60
+ * Deliberately narrow — kernel-specific things like the journal or
61
+ * NodeContext helpers stay on the kernel side of the bridge.
62
+ */
63
+ export interface ToolContext {
64
+ /**
65
+ * Tenant scope for the invocation. Every tool call is tenant-scoped;
66
+ * cross-tenant access is denied by policy at the kernel layer.
67
+ * Handlers use this to key persistence (per-tenant DBs, per-tenant
68
+ * blob buckets) and to route capability calls (secrets scoped to
69
+ * this tenant only).
70
+ */
71
+ readonly tenantId: TenantId;
72
+ /**
73
+ * The acting principal, if the runtime can identify one. Optional
74
+ * because some invocations (scheduled background jobs) are system-run.
75
+ */
76
+ readonly principal?: UserId;
77
+ /**
78
+ * The kernel run the call belongs to, when a run invokes the tool — an
79
+ * agent turn or a flow step. Stable across every call the run makes.
80
+ */
81
+ readonly runId?: string;
82
+ /**
83
+ * Correlation id for this individual call (the model's tool-call id,
84
+ * an MCP request id, a webhook id). Opaque to tools; useful for logs,
85
+ * provenance and idempotency.
86
+ */
87
+ readonly requestId?: string;
88
+ /**
89
+ * Signal that fires when the caller wants the tool to abort — kernel
90
+ * cancel/failure teardown, MCP client disconnect, HTTP request cancel.
91
+ * Cooperative handlers should observe this and exit early.
92
+ */
93
+ readonly abortSignal: AbortSignal;
94
+ /**
95
+ * Optional secret resolver. HTTP tools built via
96
+ * `defineTool({spec: {kind: 'http', ...}})` uses this at invoke time to resolve its
97
+ * declared `secret_ref`s to plaintext credentials. Absent → HTTP
98
+ * tools that declare a `secret_ref` throw with a clear
99
+ * "resolveSecret not wired" error; tools that don't need secrets
100
+ * (dev-echo, in-process handlers) ignore it.
101
+ *
102
+ * Caller responsibility: the dispatch site (the agent turn's
103
+ * `dispatch-tools` step in `@kindgi/agents`, a flow node that runs
104
+ * the tool, an MCP server) populates this from the deployment's
105
+ * `SecretBinding` scoped to the invoking tenant.
106
+ */
107
+ readonly resolveSecret?: (ref: ToolSecretRef) => Promise<string>;
108
+ /**
109
+ * The secrets this tool declares in `needsSpec.secrets`, by name,
110
+ * resolved by the runtime for this call: for the call's tenant, in the
111
+ * env the runtime serves (`KINDGI_ENV`; in `kindgi dev`, `local` — the
112
+ * pack's `.env` and `.env.local`). Every declared name is present: one
113
+ * the runtime can't resolve, or whose value doesn't match its schema,
114
+ * fails the call before the handler runs. Absent when the tool
115
+ * declares none, and wherever nothing resolves them — a unit test
116
+ * passes its own.
117
+ */
118
+ readonly secrets?: Readonly<Record<string, string>>;
119
+ }
120
+
121
+ /**
122
+ * Pointer to a tenant-scoped secret used by a declarative tool
123
+ * (e.g. `{kind: 'http'}` with a bearer secretRef). Same shape as
124
+ * `ProviderSecretRef` in `@kindgi/api` — both are `{envName, name}`
125
+ * references resolved via the deployment's `SecretBinding`. Kept
126
+ * local to `@kindgi/tools` so the tools package doesn't take an
127
+ * api-layer dep.
128
+ */
129
+ export interface ToolSecretRef {
130
+ readonly envName: string;
131
+ readonly name: string;
132
+ }
133
+
134
+ /**
135
+ * Isolation posture the runtime enforces around the handler. Three tiers:
136
+ * - `'none'` — no isolation. First-party framework handlers
137
+ * only; registration rejects tenant-authored
138
+ * tools declaring `'none'`.
139
+ * - `'context-isolated'` — same-process `node:vm` context. NOT a
140
+ * security boundary; prevents accidental state
141
+ * bleed.
142
+ * - `'strict'` — real container / MicroVM isolation. Required
143
+ * for anything running untrusted code.
144
+ *
145
+ * Sandbox declaration + capability manifest = the handler's declared
146
+ * capability contract.
147
+ */
148
+ export type SandboxMode = 'none' | 'context-isolated' | 'strict';
149
+
150
+ /**
151
+ * Runtime resource caps enforced by the sandbox layer at dispatch. Wall-
152
+ * clock (`wallMs`) intentionally NOT here — it belongs on the incoming
153
+ * edge as `EdgePolicy.timeoutMs`.
154
+ */
155
+ export interface RuntimeLimits {
156
+ readonly memMB: number;
157
+ readonly cpuMs: number;
158
+ }
159
+
160
+ /**
161
+ * Network egress policy for the sandboxed handler. Discriminated on
162
+ * `kind`; `allowlist` carries the explicit host list, the other two are
163
+ * unary.
164
+ */
165
+ export type NetworkPolicy =
166
+ | { readonly kind: 'none' }
167
+ | { readonly kind: 'allowlist'; readonly hosts: readonly string[] }
168
+ | { readonly kind: 'unrestricted' };
169
+
170
+ /**
171
+ * Typed dependency declarations for handler code, carried on the
172
+ * manifest as `needsSpec`. Each slot is optional; a handler that
173
+ * consumes none of a slot omits it entirely.
174
+ *
175
+ * In this shape (the serialisable projection carried in `index.json`
176
+ * and over the wire) values in `env` / `secrets` / `config` are JSON
177
+ * Schemas. An authoring tool that accepts Zod converts with
178
+ * `z.toJSONSchema()` before the manifest is built.
179
+ *
180
+ * `capabilities` names capability-router features (e.g. `'embedding'`);
181
+ * `bindings` names deployment-plugged binding keys (e.g. `'blob'`).
182
+ *
183
+ * The manifest field is `needsSpec` because `needs` holds the flat
184
+ * `Need[]` list of named context slots; both fields exist side by side.
185
+ */
186
+ export interface TypedNeeds {
187
+ readonly env?: Readonly<Record<string, JsonSchema>>;
188
+ readonly secrets?: Readonly<Record<string, JsonSchema>>;
189
+ readonly config?: Readonly<Record<string, JsonSchema>>;
190
+ readonly capabilities?: readonly string[];
191
+ readonly bindings?: readonly string[];
192
+ }
193
+
194
+ /**
195
+ * Handler-artifact pointer. Discriminated on `kind`:
196
+ *
197
+ * - `'oci'` — production. Populated by the deploy pipeline
198
+ * once the image is built
199
+ * + signed. `modulePath` resolves inside the pinned
200
+ * `imageRef`. `artifactVersion` is the auto-assigned
201
+ * `YYYYMMDD.N` version pinning one deploy (distinct
202
+ * from `ToolManifest.version` — semver, pack-authored).
203
+ * - `'filesystem'` — dev-mode. Populated by local development tooling
204
+ * at registration time. `modulePath` is an
205
+ * absolute host filesystem path pointing at the
206
+ * pack's on-disk handler file. Production
207
+ * deployments SHOULD reject this variant — the
208
+ * substrate is dev-only.
209
+ *
210
+ * Absent on framework-owned in-process manifests.
211
+ */
212
+ export type CodeArtifactRef =
213
+ | {
214
+ readonly kind: 'oci';
215
+ readonly imageRef: string;
216
+ readonly modulePath: string;
217
+ readonly artifactVersion: string;
218
+ }
219
+ | {
220
+ readonly kind: 'filesystem';
221
+ readonly modulePath: string;
222
+ };
223
+
224
+ /**
225
+ * The serialisable projection of a tool — everything except the runtime
226
+ * handler binding. Matches `@kindgi/specs/tool.schema.json` and round-trips over
227
+ * the wire. This is what MCP and cross-runtime discovery consume.
228
+ *
229
+ * ## Additive extensions
230
+ *
231
+ * `sandbox`, `limits`, `network`, `needsSpec`, `codeArtifactRef` are
232
+ * optional extensions that the dispatch, SDK and deploy layers
233
+ * populate + honor. Registrations that don't set them leave them
234
+ * undefined; consumers that don't know about them ignore the fields.
235
+ */
236
+ export interface ToolManifest {
237
+ /**
238
+ * Globally-unique tool identifier. Convention: `<pack-id>.<tool-name>`
239
+ * (kebab-case, dot-namespaced) — e.g. `acme.verify-citation`,
240
+ * `demo.echo`. The `<pack-id>` prefix scopes the tool to its pack;
241
+ * `<tool-name>` names the operation. Enforced as a `ToolId` brand;
242
+ * `defineTool` refuses ids that don't match the convention.
243
+ */
244
+ readonly id: ToolId;
245
+ /**
246
+ * Human-readable one-liner describing what the tool does. Surfaced
247
+ * to the model as part of the tool definition (`ModelToolDefinition.description`)
248
+ * — the model uses it to decide when to call. Write it for the
249
+ * model: what the tool DOES, what it RETURNS, and when to prefer
250
+ * it over other tools. Skip implementation details.
251
+ */
252
+ readonly description: string;
253
+ /**
254
+ * Semver — REQUIRED. Every tool registered in a Kindgi deployment
255
+ * ships versioned; an agent's `tools[].version` is a semver range
256
+ * matched against these versions at dispatch time via
257
+ * `semver.maxSatisfying`. Enforced by
258
+ * `defineTool(...)` at the API boundary.
259
+ */
260
+ readonly version: string;
261
+ /**
262
+ * JSON Schema Draft 2020-12 for the tool's input. Handed to the
263
+ * model as the tool's parameter schema so it can generate valid
264
+ * calls. `invokeTool` validates every incoming input against this
265
+ * before invoking the handler — invalid input surfaces as a
266
+ * `bad-input` error, never reaches the handler.
267
+ *
268
+ * Zod authors: pass a Zod v4 schema to `defineTool({input: zSchema})`;
269
+ * the framework converts to JSON Schema at author time and preserves
270
+ * the Zod schema on `tool.inputZod` for TS-side inference.
271
+ */
272
+ readonly input: JsonSchema;
273
+ /**
274
+ * JSON Schema Draft 2020-12 for the tool's output. `invokeTool`
275
+ * validates the handler's return value against this before surfacing
276
+ * it — a handler that returns off-schema fails with
277
+ * `bad-output`. Same Zod-conversion story as `input`.
278
+ *
279
+ * Prefer explicit schemas over `type: 'object', additionalProperties: true`
280
+ * — the model uses the output schema to interpret tool results
281
+ * downstream.
282
+ */
283
+ readonly output: JsonSchema;
284
+ /**
285
+ * Named context slots the tool depends on (e.g. `tenant`, `run`,
286
+ * `blob`, `memory:facts`). Framework-level dependencies the tool
287
+ * expects the runtime to inject via `ToolContext`. Declarative: not
288
+ * checked when a flow is loaded. `needsSpec` is the typed,
289
+ * discriminated form (env / secrets / config / capabilities /
290
+ * bindings).
291
+ */
292
+ readonly needs?: readonly Need[];
293
+ /**
294
+ * Declared side-effects the tool causes — an `EFFECT_KINDS` kind
295
+ * (`writes`, `network`, `external-side-effect`, …), optionally with
296
+ * the `resource` it targets. Consumed by policy + audit machinery for
297
+ * effect-based gating (e.g. "block any tool with an
298
+ * `external-side-effect` in a dry-run"). Distinct from `mutating` —
299
+ * `effects` names WHICH effects; `mutating` is a coarser boolean.
300
+ */
301
+ readonly effects?: readonly Effect[];
302
+ /**
303
+ * How the tool is dispatched at invoke time. `'native'` (default)
304
+ * uses the framework's in-process dispatch; `'mcp'` routes through
305
+ * the Model Context Protocol endpoint declared at `mcpEndpoint`;
306
+ * `'auto'` picks native if a handler exists, else mcp. Most
307
+ * pack-authored tools omit this and default to native.
308
+ */
309
+ readonly transport?: 'auto' | 'native' | 'mcp';
310
+ /**
311
+ * MCP server URL for `transport: 'mcp'` tools. Ignored when
312
+ * transport is native. See the Model Context Protocol spec for the
313
+ * expected endpoint shape.
314
+ */
315
+ readonly mcpEndpoint?: string;
316
+ /**
317
+ * Free-form key/value metadata that rides along with the manifest.
318
+ * Not interpreted by the framework — passthrough for pack-authored
319
+ * annotations (e.g. `{owner: 'team-legal', ticket: 'PROJ-123'}`).
320
+ * Surfaced in `GET /v1/tools/:id` responses for tooling.
321
+ */
322
+ readonly metadata?: Readonly<Record<string, unknown>>;
323
+ /**
324
+ * Semantic marker: `true` when this tool causes observable side
325
+ * effects (writes state, calls external APIs with mutations, sends
326
+ * messages, etc.). Read-only tools (queries, computations, retrievals)
327
+ * set `false`. Absent = defaults to `true` (safer — the framework's
328
+ * `never_ask / ask_on_first_use / always_ask` policy defaults hinge
329
+ * on this flag; mistakenly marking a mutating tool as read-only would
330
+ * bypass HITL, while the reverse only adds friction).
331
+ *
332
+ * Consumed by `@kindgi/agents` as the per-tool HITL default when an
333
+ * agent's `conversationPolicy.hitl.tools` sets neither an override
334
+ * nor a `default` for the tool — a read-only tool passes straight
335
+ * through, a mutating tool asks on first use.
336
+ */
337
+ readonly mutating?: boolean;
338
+ /** Isolation posture — see `SandboxMode`. */
339
+ readonly sandbox?: SandboxMode;
340
+ /** Memory + CPU caps enforced by the sandbox at dispatch. */
341
+ readonly limits?: RuntimeLimits;
342
+ /** Network egress policy the sandbox honors. */
343
+ readonly network?: NetworkPolicy;
344
+ /**
345
+ * Discriminated typed-dependency declarations (env / secrets / config /
346
+ * capabilities / bindings). See `TypedNeeds` for the shape.
347
+ */
348
+ readonly needsSpec?: TypedNeeds;
349
+ /**
350
+ * Signed pointer at the deploy-time OCI image + module path carrying
351
+ * the handler bytes. Absent for in-process manifests.
352
+ */
353
+ readonly codeArtifactRef?: CodeArtifactRef;
354
+ /**
355
+ * Declarative handler specification. When present, the runtime
356
+ * `Tool.handler` is synthesized from this spec — the manifest
357
+ * carries everything needed for a first-party framework
358
+ * synthesizer to build the fetch/query/dispatch call at register
359
+ * time. Author writes the spec; framework owns the correctness
360
+ * bits (timeouts, aborts, secret resolution, retries).
361
+ *
362
+ * Discriminated on `spec.kind`. The framework ships `'http'` (see
363
+ * `HttpToolSpec`) as the first-party kind; other kinds plug in via
364
+ * `registerToolSpecSynthesizer`.
365
+ *
366
+ * `spec` and `handler` are MUTUALLY EXCLUSIVE at `defineTool` time
367
+ * — a tool is either declarative or imperative. Mixed usage
368
+ * fails to compile.
369
+ *
370
+ * The full JSON-serializable manifest (spec included) is what
371
+ * `POST /v1/tools` accepts — declarative tools can be registered
372
+ * from configuration without shipping code.
373
+ */
374
+ readonly spec?: ToolSpec;
375
+ }
376
+
377
+ /**
378
+ * Union of all declarative handler specifications the framework
379
+ * synthesizes into runtime handlers. Discriminated on `kind`; `'http'`
380
+ * is the only kind in this union. Adding a kind does not change the
381
+ * `defineTool({ spec })` author surface.
382
+ */
383
+ export type ToolSpec = HttpToolSpec;
384
+
385
+ /**
386
+ * Declarative HTTP-invocation spec. All fields serialize cleanly to
387
+ * JSON — no runtime closures. Credentials appear ONLY as
388
+ * `secret_ref` pointers; the actual plaintext is resolved at invoke
389
+ * time via `ToolContext.resolveSecret`.
390
+ */
391
+ export interface HttpToolSpec {
392
+ /**
393
+ * Discriminant. Every declarative tool spec self-identifies via
394
+ * `kind` so the framework's synthesizer registry can route to the
395
+ * right handler builder.
396
+ */
397
+ readonly kind: 'http';
398
+ /**
399
+ * HTTP method. `GET` and `DELETE` typically carry no body;
400
+ * `POST`, `PUT`, `PATCH` typically do. The default body shape is
401
+ * `json-input` (see `requestBody`).
402
+ */
403
+ readonly method: HttpMethod;
404
+ /**
405
+ * URL template with `{param}` placeholders substituted from the
406
+ * tool's input at invoke time. Example:
407
+ * `https://api.github.com/repos/{owner}/{repo}/issues/{number}`.
408
+ * Every placeholder must correspond to an input property; missing
409
+ * substitutions surface as a `handler-error` at invoke time.
410
+ */
411
+ readonly urlTemplate: string;
412
+ /**
413
+ * Static or templated headers. Values may include `{param}`
414
+ * placeholders substituted from the tool's input (e.g.
415
+ * `X-Trace-Id: {requestId}`). For credentialed headers use
416
+ * `authorization` — it keeps secret references out of `headers`.
417
+ */
418
+ readonly headers?: readonly HttpHeaderSpec[];
419
+ /**
420
+ * Optional authentication injected as a header on the outbound
421
+ * request. Union kinds:
422
+ * - `bearer` — writes `Authorization: Bearer <secret>` where
423
+ * `<secret>` is the plaintext of `secret_ref`.
424
+ * - `header` — writes `<headerName>: <secret>` verbatim (for
425
+ * vendors that use a non-`Authorization` header,
426
+ * e.g. `X-API-Key`).
427
+ */
428
+ readonly authorization?: HttpAuthSpec;
429
+ /**
430
+ * Request-body specification for methods that carry a body
431
+ * (`POST`, `PUT`, `PATCH`, `DELETE`). Absent → no body sent.
432
+ *
433
+ * The default body strategy is `{kind: 'json-input'}` — the tool's
434
+ * INPUT (after URL-template substitution consumes its keys) is
435
+ * JSON-serialized as the body. `input-passthrough` sends the raw
436
+ * input object (JSON body) without URL-consuming any keys, so
437
+ * keys used in the URL template appear redundantly in the body
438
+ * too. Choose based on the target API's expected shape.
439
+ */
440
+ readonly requestBody?: HttpRequestBodySpec;
441
+ /**
442
+ * Wall-clock timeout in milliseconds. Enforced via AbortController.
443
+ * Defaults to 30_000 (30s) when absent — matches typical vendor
444
+ * SLOs.
445
+ */
446
+ readonly timeoutMs?: number;
447
+ /**
448
+ * When the response body should be parsed as JSON before returning
449
+ * to the invoker. Default: `true`. Set `false` for adapters that
450
+ * hand-parse (rare) or for text-only endpoints.
451
+ */
452
+ readonly parseJson?: boolean;
453
+ /**
454
+ * Acceptable status-code range. Responses outside this range are
455
+ * treated as `handler-error` with the response text as the message.
456
+ * Default: `[200, 299]` (any 2xx).
457
+ */
458
+ readonly successStatus?: {
459
+ readonly min: number;
460
+ readonly max: number;
461
+ };
462
+ }
463
+
464
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
465
+
466
+ export interface HttpHeaderSpec {
467
+ readonly name: string;
468
+ readonly value: string;
469
+ }
470
+
471
+ export type HttpAuthSpec =
472
+ | {
473
+ readonly kind: 'bearer';
474
+ readonly secretRef: ToolSecretRef;
475
+ }
476
+ | {
477
+ readonly kind: 'header';
478
+ readonly headerName: string;
479
+ readonly secretRef: ToolSecretRef;
480
+ };
481
+
482
+ export type HttpRequestBodySpec =
483
+ | { readonly kind: 'json-input' }
484
+ | { readonly kind: 'input-passthrough' }
485
+ | { readonly kind: 'text'; readonly template: string };
486
+
487
+ /**
488
+ * The runtime object: manifest + local handler. Only exists in-process;
489
+ * `handler` is stripped when the tool is projected to a manifest for
490
+ * serialisation, MCP exposure, or discovery.
491
+ *
492
+ * Type parameters are caller-asserted — the runtime validates against
493
+ * `input` / `output` JSON Schema, not the TS type — but let authors write
494
+ * typed handlers when they know the shape.
495
+ */
496
+ export interface Tool<TInput = unknown, TOutput = unknown> extends ToolManifest {
497
+ /**
498
+ * The runtime handler — the function `invokeTool` actually calls.
499
+ * Types are caller-asserted (matches the `defineTool` generics);
500
+ * the runtime validates against `input` / `output` JSON Schema at
501
+ * every invocation, not the TS type.
502
+ */
503
+ readonly handler: (input: TInput, ctx: ToolContext) => Promise<TOutput>;
504
+ /**
505
+ * The original Zod schema, preserved when the tool was authored with a
506
+ * Zod v4 schema at the `input` slot. Present iff the author passed a
507
+ * Zod schema to `defineTool`; otherwise `undefined`. Wire form is
508
+ * always the converted JSON Schema on `input` — this field exists so
509
+ * TS callers can `z.infer<typeof tool.inputZod>` for static types.
510
+ *
511
+ * Explicitly `ZodLikeSchema | undefined` (not a bare optional marker):
512
+ * the more specific `DefinedTool<TInSchema, TOutSchema>` returned by
513
+ * `defineTool` narrows this to the exact Zod schema type when Zod is
514
+ * used, and to `undefined` when JSON Schema is used. Consumers reading
515
+ * a heterogeneous `AnyTool` see the union and narrow with `!== undefined`.
516
+ */
517
+ readonly inputZod?: ZodLikeSchema | undefined;
518
+ /** Symmetric to `inputZod` for the `output` slot. */
519
+ readonly outputZod?: ZodLikeSchema | undefined;
520
+ }
521
+
522
+ /**
523
+ * Type-erased alias for cross-boundary storage (registries, arrays of mixed
524
+ * tools). Handlers are contravariant in their input type, so a
525
+ * `Tool<{ text: string }, ...>` is not assignable to `Tool<unknown, unknown>`
526
+ * — this alias uses `unknown` on both sides so heterogeneous collections
527
+ * type-check without lying about the runtime shape.
528
+ */
529
+ // biome-ignore lint/suspicious/noExplicitAny: covariance boundary for heterogeneous storage
530
+ export type AnyTool = Tool<any, any>;
531
+
532
+ /** MCP-compatible tool manifest. Narrower shape than `ToolManifest`. */
533
+ export interface McpToolManifest {
534
+ readonly name: string;
535
+ readonly description: string;
536
+ readonly inputSchema: JsonSchema;
537
+ readonly outputSchema?: JsonSchema;
538
+ }