@kindgi/handler-runtime 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 +218 -2
- package/dist/build-extensions.d.ts +75 -0
- package/dist/build-extensions.d.ts.map +1 -0
- package/dist/build-extensions.js +27 -0
- package/dist/build-extensions.js.map +1 -0
- package/dist/discovery.d.ts +19 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +81 -0
- package/dist/discovery.js.map +1 -0
- package/dist/entrypoint.d.ts +10 -0
- package/dist/entrypoint.d.ts.map +1 -0
- package/dist/entrypoint.js +24 -0
- package/dist/entrypoint.js.map +1 -0
- package/dist/handler-runner.d.ts +127 -0
- package/dist/handler-runner.d.ts.map +1 -0
- package/dist/handler-runner.js +318 -0
- package/dist/handler-runner.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/kindgi-index-main.d.ts +2 -0
- package/dist/kindgi-index-main.d.ts.map +1 -0
- package/dist/kindgi-index-main.js +15 -0
- package/dist/kindgi-index-main.js.map +1 -0
- package/dist/kindgi-index.d.ts +316 -0
- package/dist/kindgi-index.d.ts.map +1 -0
- package/dist/kindgi-index.js +1201 -0
- package/dist/kindgi-index.js.map +1 -0
- package/dist/pack-env.d.ts +66 -0
- package/dist/pack-env.d.ts.map +1 -0
- package/dist/pack-env.js +97 -0
- package/dist/pack-env.js.map +1 -0
- package/dist/pack-service/index.d.ts +7 -0
- package/dist/pack-service/index.d.ts.map +1 -0
- package/dist/pack-service/index.js +6 -0
- package/dist/pack-service/index.js.map +1 -0
- package/dist/pack-service/main.d.ts +47 -0
- package/dist/pack-service/main.d.ts.map +1 -0
- package/dist/pack-service/main.js +201 -0
- package/dist/pack-service/main.js.map +1 -0
- package/dist/pack-service/service.d.ts +61 -0
- package/dist/pack-service/service.d.ts.map +1 -0
- package/dist/pack-service/service.js +341 -0
- package/dist/pack-service/service.js.map +1 -0
- package/dist/pack-service/supervisor.d.ts +138 -0
- package/dist/pack-service/supervisor.d.ts.map +1 -0
- package/dist/pack-service/supervisor.js +424 -0
- package/dist/pack-service/supervisor.js.map +1 -0
- package/dist/protocol.d.ts +104 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +116 -0
- package/dist/protocol.js.map +1 -0
- package/package.json +87 -4
- package/src/build-extensions.ts +100 -0
- package/src/discovery.ts +89 -0
- package/src/entrypoint.ts +23 -0
- package/src/handler-runner.ts +500 -0
- package/src/index.ts +66 -0
- package/src/kindgi-index-main.ts +17 -0
- package/src/kindgi-index.ts +1605 -0
- package/src/pack-env.ts +148 -0
- package/src/pack-service/index.ts +17 -0
- package/src/pack-service/main.ts +246 -0
- package/src/pack-service/service.ts +478 -0
- package/src/pack-service/supervisor.ts +600 -0
- package/src/protocol.ts +214 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The handler runner: runs one tool call or one guardrail check of a
|
|
6
|
+
* pack, in this process. The pack service (`./pack-service`) runs every
|
|
7
|
+
* call through it.
|
|
8
|
+
*
|
|
9
|
+
* A tool call:
|
|
10
|
+
* 1. validates the input against the tool's JSON Schema (a copy, with
|
|
11
|
+
* each property's `default` filled in), then parses it with the
|
|
12
|
+
* module's Zod schema when it exports one (`defineTool`'s
|
|
13
|
+
* `inputZod`), so the handler gets what its type says;
|
|
14
|
+
* 2. imports the module and resolves its handler (`default`,
|
|
15
|
+
* `handler` or `run`, or a `defineTool` export holding one);
|
|
16
|
+
* 3. calls `handler(input, ctx)`;
|
|
17
|
+
* 4. validates the output against the tool's output JSON Schema.
|
|
18
|
+
*
|
|
19
|
+
* A check imports the module, resolves its `evaluate`, and calls
|
|
20
|
+
* `evaluate(config, trace, bindings)`.
|
|
21
|
+
*
|
|
22
|
+
* Neither throws: every failure is a typed `HandlerError`. The runner
|
|
23
|
+
* is not a sandbox; the pack service's process is the boundary, and
|
|
24
|
+
* pack code is the team's own (trusted).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { ValidateFunction } from 'ajv';
|
|
28
|
+
import * as addFormatsModule from 'ajv-formats';
|
|
29
|
+
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
30
|
+
|
|
31
|
+
import { type ZodLikeSchema, isZodSchema, parseWithSchema } from '@kindgi/schema';
|
|
32
|
+
import type { Result } from '@kindgi/types';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* What a handler gets beside its input: the tenant and run it serves,
|
|
36
|
+
* and its resolved "typed needs" (env, secrets, config), composed by
|
|
37
|
+
* the server and sent with the call.
|
|
38
|
+
*/
|
|
39
|
+
export interface HandlerContext {
|
|
40
|
+
readonly tenantId: string;
|
|
41
|
+
readonly runId: string;
|
|
42
|
+
readonly requestId?: string;
|
|
43
|
+
readonly env?: Readonly<Record<string, unknown>>;
|
|
44
|
+
readonly secrets?: Readonly<Record<string, unknown>>;
|
|
45
|
+
readonly config?: Readonly<Record<string, unknown>>;
|
|
46
|
+
/**
|
|
47
|
+
* Never on the wire; the pack service adds it: aborted when the call
|
|
48
|
+
* is cancelled or passes its deadline. Handlers that do slow I/O
|
|
49
|
+
* should pass it on so they stop promptly.
|
|
50
|
+
*/
|
|
51
|
+
readonly abortSignal?: AbortSignal;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The tool being called, from the pack's `index.json` (the `kindgi-index`
|
|
56
|
+
* indexer writes it): its id, its module, and its JSON Schemas. The
|
|
57
|
+
* schemas are authoritative; a TypeScript type on the handler is not
|
|
58
|
+
* checked.
|
|
59
|
+
*/
|
|
60
|
+
export interface ToolInvocationSpec {
|
|
61
|
+
readonly id: string;
|
|
62
|
+
readonly modulePath: string;
|
|
63
|
+
readonly inputSchema: Readonly<Record<string, unknown>>;
|
|
64
|
+
readonly outputSchema: Readonly<Record<string, unknown>>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The guardrail check being called (`RegisteredCheck.evaluate(config,
|
|
69
|
+
* trace, bindings)`): its id and its module. The module's check is
|
|
70
|
+
* resolved like a handler (`default`, `check`, or a bare export with
|
|
71
|
+
* `evaluate`).
|
|
72
|
+
*
|
|
73
|
+
* A check in the pack service gets no callable bindings (no
|
|
74
|
+
* `providerRegistry`), so llm-judge checks run in the server, not here.
|
|
75
|
+
* It does get the call's `abortSignal`.
|
|
76
|
+
*/
|
|
77
|
+
export interface CheckInvocationSpec {
|
|
78
|
+
readonly id: string;
|
|
79
|
+
readonly modulePath: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Every way a call can fail; the pack service answers with the same code. */
|
|
83
|
+
export type HandlerErrorCode =
|
|
84
|
+
| 'input-validation-failed'
|
|
85
|
+
| 'output-validation-failed'
|
|
86
|
+
| 'handler-import-failed'
|
|
87
|
+
| 'handler-shape-invalid'
|
|
88
|
+
| 'handler-throw'
|
|
89
|
+
| 'check-shape-invalid';
|
|
90
|
+
|
|
91
|
+
export interface HandlerError {
|
|
92
|
+
readonly code: HandlerErrorCode;
|
|
93
|
+
readonly message: string;
|
|
94
|
+
readonly toolId?: string;
|
|
95
|
+
readonly cause?: unknown;
|
|
96
|
+
readonly issues?: readonly unknown[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The handler surface — `handler(input, ctx)`. Return value may be
|
|
101
|
+
* either the raw output or a Promise thereof. Throwing is captured as
|
|
102
|
+
* a `handler-throw` error; a handler never takes down the process.
|
|
103
|
+
*/
|
|
104
|
+
export type HandlerFn = (input: unknown, ctx: HandlerContext) => unknown | Promise<unknown>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Shape the runner accepts back from a resolved handler module. The
|
|
108
|
+
* default `importHandler` accepts any of:
|
|
109
|
+
* - a bare function export (`export default (input, ctx) => ...`)
|
|
110
|
+
* - `{ default: HandlerFn }`
|
|
111
|
+
* - `{ handler: HandlerFn }`
|
|
112
|
+
* - `{ run: HandlerFn }` (the shape the sandbox entrypoint calls, so
|
|
113
|
+
* a single module can serve both)
|
|
114
|
+
*/
|
|
115
|
+
export type HandlerModule =
|
|
116
|
+
| HandlerFn
|
|
117
|
+
| { readonly default: HandlerFn }
|
|
118
|
+
| { readonly handler: HandlerFn }
|
|
119
|
+
| { readonly run: HandlerFn };
|
|
120
|
+
|
|
121
|
+
// -----------------------------------------------------------------------
|
|
122
|
+
// Ajv wiring
|
|
123
|
+
// -----------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
type AddFormatsFn = (ajv: InstanceType<typeof Ajv2020>, opts?: unknown) => unknown;
|
|
126
|
+
const addFormatsRaw = addFormatsModule as unknown;
|
|
127
|
+
const addFormats: AddFormatsFn =
|
|
128
|
+
typeof addFormatsRaw === 'function'
|
|
129
|
+
? (addFormatsRaw as AddFormatsFn)
|
|
130
|
+
: (addFormatsRaw as { default: AddFormatsFn }).default;
|
|
131
|
+
|
|
132
|
+
/** Compiled validators by side and schema, oldest first; see `compileSchema`. */
|
|
133
|
+
const compiledValidators = new Map<string, ValidateFunction>();
|
|
134
|
+
/** Enough for every schema of a large pack, a few edits over. */
|
|
135
|
+
const MAX_COMPILED_VALIDATORS = 512;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The ajv validator for a JSON Schema — compiled once per side and
|
|
139
|
+
* schema, then reused: a pack service validates every call of a tool
|
|
140
|
+
* against the same two schemas, and compiling is the cost. An edited
|
|
141
|
+
* schema is a new key, so a hot reload takes effect. A validator is
|
|
142
|
+
* shared by concurrent calls safely: validation is synchronous.
|
|
143
|
+
*/
|
|
144
|
+
function compileSchema(
|
|
145
|
+
schema: Readonly<Record<string, unknown>>,
|
|
146
|
+
side: 'input' | 'output',
|
|
147
|
+
): ValidateFunction {
|
|
148
|
+
const key = `${side}:${JSON.stringify(schema)}`;
|
|
149
|
+
const cached = compiledValidators.get(key);
|
|
150
|
+
if (cached !== undefined) return cached;
|
|
151
|
+
// An input validator fills in each property's JSON Schema `default`;
|
|
152
|
+
// it checks a copy of the caller's input.
|
|
153
|
+
const ajv = new Ajv2020({
|
|
154
|
+
strict: true,
|
|
155
|
+
allErrors: true,
|
|
156
|
+
allowUnionTypes: false,
|
|
157
|
+
...(side === 'input' && { useDefaults: true }),
|
|
158
|
+
});
|
|
159
|
+
addFormats(ajv);
|
|
160
|
+
const validator = ajv.compile(schema as object);
|
|
161
|
+
if (compiledValidators.size >= MAX_COMPILED_VALIDATORS) {
|
|
162
|
+
const oldest = compiledValidators.keys().next().value;
|
|
163
|
+
if (oldest !== undefined) compiledValidators.delete(oldest);
|
|
164
|
+
}
|
|
165
|
+
compiledValidators.set(key, validator);
|
|
166
|
+
return validator;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// -----------------------------------------------------------------------
|
|
170
|
+
// Tool calls — `runHandler`
|
|
171
|
+
// -----------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
export interface RunHandlerOptions {
|
|
174
|
+
readonly tool: ToolInvocationSpec;
|
|
175
|
+
readonly input: unknown;
|
|
176
|
+
readonly ctx: HandlerContext;
|
|
177
|
+
/**
|
|
178
|
+
* Override the default `import(modulePath)` resolver (tests). Return
|
|
179
|
+
* the same shape `HandlerModule` covers.
|
|
180
|
+
*/
|
|
181
|
+
readonly importHandler?: (modulePath: string) => Promise<HandlerModule> | HandlerModule;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Run one tool call; a typed `Result`, never a throw — every failure is
|
|
186
|
+
* a `HandlerError`.
|
|
187
|
+
*/
|
|
188
|
+
export async function runHandler(
|
|
189
|
+
options: RunHandlerOptions,
|
|
190
|
+
): Promise<Result<unknown, HandlerError>> {
|
|
191
|
+
const { tool, input, ctx } = options;
|
|
192
|
+
const importHandler = options.importHandler ?? defaultImportHandler;
|
|
193
|
+
|
|
194
|
+
let inputValidator: ValidateFunction;
|
|
195
|
+
try {
|
|
196
|
+
inputValidator = compileSchema(tool.inputSchema, 'input');
|
|
197
|
+
} catch (cause) {
|
|
198
|
+
return {
|
|
199
|
+
kind: 'err',
|
|
200
|
+
error: {
|
|
201
|
+
code: 'input-validation-failed',
|
|
202
|
+
message: `Tool "${tool.id}" input schema failed to compile: ${stringifyError(cause)}`,
|
|
203
|
+
toolId: tool.id,
|
|
204
|
+
cause: serializeCause(cause),
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const candidate = structuredClone(input);
|
|
210
|
+
if (!inputValidator(candidate)) {
|
|
211
|
+
return {
|
|
212
|
+
kind: 'err',
|
|
213
|
+
error: {
|
|
214
|
+
code: 'input-validation-failed',
|
|
215
|
+
message: `Tool "${tool.id}" input failed validation`,
|
|
216
|
+
toolId: tool.id,
|
|
217
|
+
issues: inputValidator.errors ?? [],
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let module_: HandlerModule;
|
|
223
|
+
try {
|
|
224
|
+
module_ = await importHandler(tool.modulePath);
|
|
225
|
+
} catch (cause) {
|
|
226
|
+
return {
|
|
227
|
+
kind: 'err',
|
|
228
|
+
error: {
|
|
229
|
+
code: 'handler-import-failed',
|
|
230
|
+
message: `Failed to import handler module '${tool.modulePath}': ${stringifyError(cause)}`,
|
|
231
|
+
toolId: tool.id,
|
|
232
|
+
cause: serializeCause(cause),
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const handler = resolveHandler(module_);
|
|
238
|
+
const inputZod = resolveInputZod(module_);
|
|
239
|
+
if (handler === undefined) {
|
|
240
|
+
return {
|
|
241
|
+
kind: 'err',
|
|
242
|
+
error: {
|
|
243
|
+
code: 'handler-shape-invalid',
|
|
244
|
+
message: `Handler module '${tool.modulePath}' does not export a handler function (looked for default / handler / run)`,
|
|
245
|
+
toolId: tool.id,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// A Zod-authored tool gets its parsed input: defaults, transforms and
|
|
251
|
+
// refinements applied, as its handler's type says.
|
|
252
|
+
let prepared: unknown = candidate;
|
|
253
|
+
if (inputZod !== undefined) {
|
|
254
|
+
const parsed = await parseWithSchema(inputZod, candidate);
|
|
255
|
+
if (parsed.kind === 'err') {
|
|
256
|
+
return {
|
|
257
|
+
kind: 'err',
|
|
258
|
+
error: {
|
|
259
|
+
code: 'input-validation-failed',
|
|
260
|
+
message: `Tool "${tool.id}" input failed validation`,
|
|
261
|
+
toolId: tool.id,
|
|
262
|
+
issues: parsed.issues.map((i) => ({
|
|
263
|
+
instancePath: (i.path ?? []).map((p) => `/${String(p)}`).join(''),
|
|
264
|
+
message: i.message,
|
|
265
|
+
})),
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
prepared = parsed.value;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let output: unknown;
|
|
273
|
+
try {
|
|
274
|
+
output = await handler(prepared, ctx);
|
|
275
|
+
} catch (cause) {
|
|
276
|
+
return {
|
|
277
|
+
kind: 'err',
|
|
278
|
+
error: {
|
|
279
|
+
code: 'handler-throw',
|
|
280
|
+
message: `Handler for tool "${tool.id}" threw: ${stringifyError(cause)}`,
|
|
281
|
+
toolId: tool.id,
|
|
282
|
+
cause: serializeCause(cause),
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let outputValidator: ValidateFunction;
|
|
288
|
+
try {
|
|
289
|
+
outputValidator = compileSchema(tool.outputSchema, 'output');
|
|
290
|
+
} catch (cause) {
|
|
291
|
+
return {
|
|
292
|
+
kind: 'err',
|
|
293
|
+
error: {
|
|
294
|
+
code: 'output-validation-failed',
|
|
295
|
+
message: `Tool "${tool.id}" output schema failed to compile: ${stringifyError(cause)}`,
|
|
296
|
+
toolId: tool.id,
|
|
297
|
+
cause: serializeCause(cause),
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (!outputValidator(output)) {
|
|
302
|
+
return {
|
|
303
|
+
kind: 'err',
|
|
304
|
+
error: {
|
|
305
|
+
code: 'output-validation-failed',
|
|
306
|
+
message: `Tool "${tool.id}" handler produced output that failed validation`,
|
|
307
|
+
toolId: tool.id,
|
|
308
|
+
issues: outputValidator.errors ?? [],
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return { kind: 'ok', value: output };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// -----------------------------------------------------------------------
|
|
317
|
+
// Check invocation (guardrail `RegisteredCheck.evaluate`)
|
|
318
|
+
// -----------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Shape the runner accepts from a resolved check module. Analogous to
|
|
322
|
+
* `HandlerModule` — matches the `RegisteredCheck` shape (has `.evaluate`
|
|
323
|
+
* function) accessed via `default`, `check`, or bare export.
|
|
324
|
+
*/
|
|
325
|
+
export type CheckModule =
|
|
326
|
+
| { readonly evaluate: (...args: unknown[]) => unknown | Promise<unknown> }
|
|
327
|
+
| {
|
|
328
|
+
readonly default:
|
|
329
|
+
| { readonly evaluate: (...args: unknown[]) => unknown | Promise<unknown> }
|
|
330
|
+
| ((...args: unknown[]) => unknown | Promise<unknown>);
|
|
331
|
+
}
|
|
332
|
+
| {
|
|
333
|
+
readonly check:
|
|
334
|
+
| { readonly evaluate: (...args: unknown[]) => unknown | Promise<unknown> }
|
|
335
|
+
| ((...args: unknown[]) => unknown | Promise<unknown>);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
export interface RunCheckOptions {
|
|
339
|
+
readonly check: CheckInvocationSpec;
|
|
340
|
+
readonly config: Readonly<Record<string, unknown>>;
|
|
341
|
+
readonly trace: unknown;
|
|
342
|
+
/**
|
|
343
|
+
* Aborted when the call is cancelled or passes its deadline; the
|
|
344
|
+
* check finds it as `bindings.abortSignal`.
|
|
345
|
+
*/
|
|
346
|
+
readonly abortSignal?: AbortSignal;
|
|
347
|
+
readonly importCheck?: (modulePath: string) => Promise<CheckModule> | CheckModule;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Run one guardrail check: `evaluate(config, trace, bindings)`, where
|
|
352
|
+
* `bindings` carries only the call's `abortSignal` (no provider
|
|
353
|
+
* registry, so no llm-judge checks here). A typed `Result`, never a
|
|
354
|
+
* throw — every failure is a `HandlerError`.
|
|
355
|
+
*/
|
|
356
|
+
export async function runCheck(options: RunCheckOptions): Promise<Result<unknown, HandlerError>> {
|
|
357
|
+
const { check, config, trace } = options;
|
|
358
|
+
const importCheck = options.importCheck ?? defaultImportCheck;
|
|
359
|
+
|
|
360
|
+
let module_: CheckModule;
|
|
361
|
+
try {
|
|
362
|
+
module_ = await importCheck(check.modulePath);
|
|
363
|
+
} catch (cause) {
|
|
364
|
+
return {
|
|
365
|
+
kind: 'err',
|
|
366
|
+
error: {
|
|
367
|
+
code: 'handler-import-failed',
|
|
368
|
+
message: `Failed to import check module '${check.modulePath}': ${stringifyError(cause)}`,
|
|
369
|
+
toolId: check.id,
|
|
370
|
+
cause: serializeCause(cause),
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const evaluate = resolveCheckEvaluate(module_);
|
|
376
|
+
if (evaluate === undefined) {
|
|
377
|
+
return {
|
|
378
|
+
kind: 'err',
|
|
379
|
+
error: {
|
|
380
|
+
code: 'check-shape-invalid',
|
|
381
|
+
message: `Check module '${check.modulePath}' does not export a check with .evaluate() (looked for default / check / bare export with evaluate)`,
|
|
382
|
+
toolId: check.id,
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
let result: unknown;
|
|
388
|
+
try {
|
|
389
|
+
result = await evaluate(config as unknown, trace, {
|
|
390
|
+
...(options.abortSignal !== undefined && { abortSignal: options.abortSignal }),
|
|
391
|
+
});
|
|
392
|
+
} catch (cause) {
|
|
393
|
+
return {
|
|
394
|
+
kind: 'err',
|
|
395
|
+
error: {
|
|
396
|
+
code: 'handler-throw',
|
|
397
|
+
message: `Check "${check.id}" evaluate() threw: ${stringifyError(cause)}`,
|
|
398
|
+
toolId: check.id,
|
|
399
|
+
cause: serializeCause(cause),
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return { kind: 'ok', value: result };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function resolveCheckEvaluate(
|
|
408
|
+
module_: CheckModule,
|
|
409
|
+
):
|
|
410
|
+
| ((config: unknown, trace: unknown, bindings: unknown) => unknown | Promise<unknown>)
|
|
411
|
+
| undefined {
|
|
412
|
+
const candidates: unknown[] = [
|
|
413
|
+
(module_ as { evaluate?: unknown }).evaluate,
|
|
414
|
+
(module_ as { default?: unknown }).default,
|
|
415
|
+
(module_ as { check?: unknown }).check,
|
|
416
|
+
];
|
|
417
|
+
for (const candidate of candidates) {
|
|
418
|
+
if (candidate === undefined || candidate === null) continue;
|
|
419
|
+
if (typeof candidate === 'function') {
|
|
420
|
+
return candidate as (c: unknown, t: unknown, b: unknown) => unknown | Promise<unknown>;
|
|
421
|
+
}
|
|
422
|
+
const nested = (candidate as { evaluate?: unknown }).evaluate;
|
|
423
|
+
if (typeof nested === 'function') {
|
|
424
|
+
return nested as (c: unknown, t: unknown, b: unknown) => unknown | Promise<unknown>;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function defaultImportCheck(modulePath: string): Promise<CheckModule> {
|
|
431
|
+
return (await import(modulePath)) as CheckModule;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** The Zod input schema of a `defineTool` export (`inputZod`), when the module has one. */
|
|
435
|
+
function resolveInputZod(module_: HandlerModule): ZodLikeSchema | undefined {
|
|
436
|
+
if (typeof module_ !== 'object' || module_ === null) return undefined;
|
|
437
|
+
const record = module_ as Record<string, unknown>;
|
|
438
|
+
for (const candidate of [record.default, record.handler, record.run]) {
|
|
439
|
+
if (typeof candidate !== 'object' || candidate === null) continue;
|
|
440
|
+
const inputZod = (candidate as { readonly inputZod?: unknown }).inputZod;
|
|
441
|
+
if (isZodSchema(inputZod)) return inputZod as ZodLikeSchema;
|
|
442
|
+
}
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function resolveHandler(module_: HandlerModule): HandlerFn | undefined {
|
|
447
|
+
if (typeof module_ === 'function') return module_;
|
|
448
|
+
if (typeof module_ !== 'object' || module_ === null) return undefined;
|
|
449
|
+
const record = module_ as Record<string, unknown>;
|
|
450
|
+
const candidates: unknown[] = [record.default, record.handler, record.run];
|
|
451
|
+
for (const candidate of candidates) {
|
|
452
|
+
if (candidate === undefined || candidate === null) continue;
|
|
453
|
+
if (typeof candidate === 'function') return candidate as HandlerFn;
|
|
454
|
+
// `export default defineTool({..., handler: fn})` — the export is
|
|
455
|
+
// a Tool object; the callable lives at `.handler` (or `.run` for
|
|
456
|
+
// the sandbox-entrypoint shape). Walk one level in before giving up.
|
|
457
|
+
if (typeof candidate === 'object') {
|
|
458
|
+
const nested = candidate as Record<string, unknown>;
|
|
459
|
+
if (typeof nested.handler === 'function') return nested.handler as HandlerFn;
|
|
460
|
+
if (typeof nested.run === 'function') return nested.run as HandlerFn;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return undefined;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function defaultImportHandler(modulePath: string): Promise<HandlerModule> {
|
|
467
|
+
return (await import(modulePath)) as HandlerModule;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function stringifyError(err: unknown): string {
|
|
471
|
+
if (err instanceof Error) return `${err.name}: ${err.message}`;
|
|
472
|
+
return String(err);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Handlers may throw arbitrary values (Error, string, plain objects,
|
|
477
|
+
* undefined). The wire protocol carries `cause` as JSON — serialize to
|
|
478
|
+
* a shape JSON.stringify handles cleanly. Preserve the original message
|
|
479
|
+
* + name when `err` is an Error; fall back to a stringified rep otherwise.
|
|
480
|
+
*/
|
|
481
|
+
function serializeCause(err: unknown): unknown {
|
|
482
|
+
if (err instanceof Error) {
|
|
483
|
+
return { name: err.name, message: err.message, stack: err.stack };
|
|
484
|
+
}
|
|
485
|
+
if (err === undefined) return null;
|
|
486
|
+
if (
|
|
487
|
+
err === null ||
|
|
488
|
+
typeof err === 'string' ||
|
|
489
|
+
typeof err === 'number' ||
|
|
490
|
+
typeof err === 'boolean'
|
|
491
|
+
) {
|
|
492
|
+
return err;
|
|
493
|
+
}
|
|
494
|
+
try {
|
|
495
|
+
JSON.stringify(err);
|
|
496
|
+
return err;
|
|
497
|
+
} catch {
|
|
498
|
+
return String(err);
|
|
499
|
+
}
|
|
500
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
CheckInvocationSpec,
|
|
6
|
+
CheckModule,
|
|
7
|
+
HandlerContext,
|
|
8
|
+
HandlerError,
|
|
9
|
+
HandlerErrorCode,
|
|
10
|
+
HandlerFn,
|
|
11
|
+
HandlerModule,
|
|
12
|
+
RunCheckOptions,
|
|
13
|
+
RunHandlerOptions,
|
|
14
|
+
ToolInvocationSpec,
|
|
15
|
+
} from './handler-runner.js';
|
|
16
|
+
export { runCheck, runHandler } from './handler-runner.js';
|
|
17
|
+
|
|
18
|
+
export type {
|
|
19
|
+
DiscoveryConfig,
|
|
20
|
+
Index,
|
|
21
|
+
IndexedAgent,
|
|
22
|
+
IndexedFlow,
|
|
23
|
+
IndexedGuardrail,
|
|
24
|
+
IndexedTool,
|
|
25
|
+
IndexEnvelopeVersion,
|
|
26
|
+
IndexerError,
|
|
27
|
+
IndexerErrorCode,
|
|
28
|
+
IndexerReport,
|
|
29
|
+
PrimitiveKind,
|
|
30
|
+
RunIndexerOptions,
|
|
31
|
+
KindgiConfig,
|
|
32
|
+
KindgiConfigFile,
|
|
33
|
+
LoadKindgiConfigOptions,
|
|
34
|
+
PackLanguage,
|
|
35
|
+
} from './kindgi-index.js';
|
|
36
|
+
export {
|
|
37
|
+
DEFAULT_DISCOVERY,
|
|
38
|
+
DEFAULT_PYTHON_DISCOVERY,
|
|
39
|
+
HELP_TEXT as KINDGI_INDEX_HELP_TEXT,
|
|
40
|
+
INDEX_ENVELOPE_VERSION,
|
|
41
|
+
KERNEL_PAYLOAD_VERSION,
|
|
42
|
+
KINDGI_CONFIG_FILENAMES,
|
|
43
|
+
PYPROJECT_FILENAME,
|
|
44
|
+
findKindgiConfig,
|
|
45
|
+
loadKindgiConfig,
|
|
46
|
+
packLanguage,
|
|
47
|
+
resolveDiscovery,
|
|
48
|
+
main as kindgiIndexMain,
|
|
49
|
+
runIndexer,
|
|
50
|
+
} from './kindgi-index.js';
|
|
51
|
+
export type { PackEnvCheck, PackEnvConfig, PackEnvDeclaration, PackEnvResult } from './pack-env.js';
|
|
52
|
+
export {
|
|
53
|
+
PACK_ENV_CHECK_VAR,
|
|
54
|
+
PACK_ENV_NAME,
|
|
55
|
+
RESERVED_ENV_PREFIX,
|
|
56
|
+
missingPackEnv,
|
|
57
|
+
parsePackEnvCheck,
|
|
58
|
+
resolvePackEnv,
|
|
59
|
+
} from './pack-env.js';
|
|
60
|
+
export {
|
|
61
|
+
TEST_FILE_REGEX,
|
|
62
|
+
createGlobMatcher,
|
|
63
|
+
discoveryRoots,
|
|
64
|
+
globStaticPrefix,
|
|
65
|
+
globToRegex,
|
|
66
|
+
} from './discovery.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The `kindgi-index` process: `node <this file> --pack-dir … [--bundle-map …]`
|
|
6
|
+
* (package export `@kindgi/handler-runtime/kindgi-index-main`). A pack
|
|
7
|
+
* image's indexer stage runs it, bundled as `dist/kindgi-index.mjs`.
|
|
8
|
+
*
|
|
9
|
+
* A module of its own that runs `main()` as it loads, rather than a
|
|
10
|
+
* process-entry check inside `kindgi-index.ts`: bundled, every module in
|
|
11
|
+
* a bundle shares the bundle's `import.meta.url`, so such a check would
|
|
12
|
+
* also fire in the pack service's bundle, which imports `kindgi-index`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { main } from './kindgi-index.js';
|
|
16
|
+
|
|
17
|
+
process.exitCode = await main(process.argv.slice(2));
|