@kindgi/tools 0.0.0-bootstrap.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +86 -1
- package/dist/define.d.ts +138 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +318 -0
- package/dist/define.js.map +1 -0
- package/dist/errors.d.ts +113 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +4 -0
- package/dist/errors.js.map +1 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +213 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/invoke.d.ts +27 -0
- package/dist/invoke.d.ts.map +1 -0
- package/dist/invoke.js +127 -0
- package/dist/invoke.js.map +1 -0
- package/dist/manifest.d.ts +23 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +37 -0
- package/dist/manifest.js.map +1 -0
- package/dist/precondition.d.ts +25 -0
- package/dist/precondition.d.ts.map +1 -0
- package/dist/precondition.js +34 -0
- package/dist/precondition.js.map +1 -0
- package/dist/registry.d.ts +87 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +171 -0
- package/dist/registry.js.map +1 -0
- package/dist/spec-registry.d.ts +48 -0
- package/dist/spec-registry.d.ts.map +1 -0
- package/dist/spec-registry.js +37 -0
- package/dist/spec-registry.js.map +1 -0
- package/dist/tool.schema.json +297 -0
- package/dist/types.d.ts +508 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -4
- package/src/define.ts +537 -0
- package/src/errors.ts +139 -0
- package/src/http.ts +268 -0
- package/src/index.ts +68 -0
- package/src/invoke.ts +165 -0
- package/src/manifest.ts +40 -0
- package/src/precondition.ts +40 -0
- package/src/registry.ts +262 -0
- package/src/spec-registry.ts +78 -0
- package/src/tool.schema.json +297 -0
- 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
|
+
}
|