@qilin-agent/tools 0.0.0-stage → 3.1.4

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.
@@ -0,0 +1,243 @@
1
+ /** Unified JSON-value schema DSL, inference, compilation, and typed tool helper. @module qilin-tools/schema */
2
+ import { HarnessError } from '@qilin-agent/llm';
3
+ import type { ContentBlock } from '@qilin-agent/llm';
4
+ import type { JsonValue } from '@qilin-agent/util-values';
5
+ import type { ToolDefinition, ToolExecution, ToolExecutionResult, ToolRunContext, ToolResult } from './index.ts';
6
+ import type { JsonSchemaNode, ObjectJsonSchema } from './json-schema.ts';
7
+ import type { ToolCallView, ToolResultView } from './presentation.ts';
8
+ /** Annotation keywords shared by every author-facing schema node. */
9
+ export interface ValueSchemaAnnotations {
10
+ /** Human-readable description projected into JSON Schema and generated types. */
11
+ description?: string;
12
+ /** Human-readable title projected into JSON Schema. */
13
+ title?: string;
14
+ /** Non-validating default annotation; it must be lossless JSON data. */
15
+ default?: JsonValue;
16
+ /** Non-validating examples annotation; it must be lossless JSON data. */
17
+ examples?: JsonValue;
18
+ }
19
+ /** String value schema with type-correct literal constraints. */
20
+ export interface StringValueSchemaSpec extends ValueSchemaAnnotations {
21
+ type: 'string';
22
+ enum?: readonly string[];
23
+ const?: string;
24
+ }
25
+ /** Finite JSON-number schema with type-correct literal constraints. */
26
+ export interface NumberValueSchemaSpec extends ValueSchemaAnnotations {
27
+ type: 'number';
28
+ enum?: readonly number[];
29
+ const?: number;
30
+ }
31
+ /** Integer schema with type-correct literal constraints. */
32
+ export interface IntegerValueSchemaSpec extends ValueSchemaAnnotations {
33
+ type: 'integer';
34
+ enum?: readonly number[];
35
+ const?: number;
36
+ }
37
+ /** Boolean value schema with type-correct literal constraints. */
38
+ export interface BooleanValueSchemaSpec extends ValueSchemaAnnotations {
39
+ type: 'boolean';
40
+ enum?: readonly boolean[];
41
+ const?: boolean;
42
+ }
43
+ /** Null value schema with type-correct literal constraints. */
44
+ export interface NullValueSchemaSpec extends ValueSchemaAnnotations {
45
+ type: 'null';
46
+ enum?: readonly null[];
47
+ const?: null;
48
+ }
49
+ /** Array value schema; omitted `items` accepts any lossless JSON item. */
50
+ export interface ArrayValueSchemaSpec extends ValueSchemaAnnotations {
51
+ type: 'array';
52
+ items?: ValueSchemaSpec;
53
+ }
54
+ /**
55
+ * Explicit object value schema. Openness is mandatory so a nested or output
56
+ * object never acquires an accidental JSON Schema default.
57
+ */
58
+ export interface ObjectValueSchemaSpec extends ValueSchemaAnnotations {
59
+ type: 'object';
60
+ properties?: ParameterSchemaSpec;
61
+ additionalProperties: boolean;
62
+ }
63
+ /** Author-only unconstrained lossless JSON node. */
64
+ export interface JsonValueSchemaSpec extends ValueSchemaAnnotations {
65
+ type: 'json';
66
+ }
67
+ /** Exact-one union schema; at least two branches are required. */
68
+ export interface OneOfValueSchemaSpec extends ValueSchemaAnnotations {
69
+ oneOf: readonly [ValueSchemaSpec, ValueSchemaSpec, ...ValueSchemaSpec[]];
70
+ }
71
+ /** One author-facing schema for any lossless JSON value root. */
72
+ export type ValueSchemaSpec = StringValueSchemaSpec | NumberValueSchemaSpec | IntegerValueSchemaSpec | BooleanValueSchemaSpec | NullValueSchemaSpec | ArrayValueSchemaSpec | ObjectValueSchemaSpec | JsonValueSchemaSpec | OneOfValueSchemaSpec;
73
+ /** One implicit parameter-root property, optionally required. */
74
+ export type ParameterPropertySpec = ValueSchemaSpec & {
75
+ required?: true;
76
+ };
77
+ /**
78
+ * Tool parameter schema. The map itself is an implicit open object root;
79
+ * requiredness remains a per-property `required: true` annotation.
80
+ */
81
+ export type ParameterSchemaSpec = {
82
+ [key: string]: ParameterPropertySpec;
83
+ [key: symbol]: never;
84
+ };
85
+ /** Raw JSON Schema projection of the implicit parameter object. */
86
+ export interface ParameterJsonSchema extends ObjectJsonSchema {
87
+ properties: Record<string, JsonSchemaNode>;
88
+ }
89
+ /** Flatten an intersection into one object type for readable hovers. */
90
+ type Simplify<T> = {
91
+ [K in keyof T]: T[K];
92
+ } & {};
93
+ /** String keys of one property map; runtime compilation rejects symbol keys. */
94
+ type StringKeyOf<S> = Extract<keyof S, string>;
95
+ /** Keys of a property map marked `required: true`. */
96
+ type RequiredKeys<S> = {
97
+ [K in StringKeyOf<S>]: S[K] extends {
98
+ required: true;
99
+ } ? K : never;
100
+ }[StringKeyOf<S>];
101
+ /** Infer the declared value of one parameter property without key optionality. */
102
+ type InferProperty<P, Depth extends unknown[]> = InferValueAt<P, Depth>;
103
+ /** Infer an implicit property map into required and optional object keys. */
104
+ type InferProperties<S, Depth extends unknown[]> = Simplify<{
105
+ [K in RequiredKeys<S>]: InferProperty<S[K], Depth>;
106
+ } & {
107
+ [K in Exclude<StringKeyOf<S>, RequiredKeys<S>>]?: InferProperty<S[K], Depth>;
108
+ }>;
109
+ /** Infer an explicit object node, including its declared openness. */
110
+ type InferObject<S extends {
111
+ additionalProperties: boolean;
112
+ }, Depth extends unknown[]> = S extends {
113
+ properties: infer P;
114
+ } ? S['additionalProperties'] extends true ? InferProperties<P, Depth> & Record<string, JsonValue> : InferProperties<P, Depth> : S['additionalProperties'] extends true ? Record<string, JsonValue> : Record<string, never>;
115
+ /** Infer a scalar node's literal constraint before its broad primitive type. */
116
+ type InferScalar<S, Fallback> = S extends {
117
+ const: infer C;
118
+ } ? C : S extends {
119
+ enum: readonly (infer E)[];
120
+ } ? E : Fallback;
121
+ /** Add one schema-container level to bounded compile-time inference. */
122
+ type NextInferenceDepth<Depth extends unknown[]> = [unknown, ...Depth];
123
+ /** Infer one node without recursively checking it against the full author union. */
124
+ type InferValueAt<S, Depth extends unknown[]> = Depth['length'] extends 16 ? JsonValue : S extends {
125
+ type: 'string';
126
+ } ? InferScalar<S, string> : S extends {
127
+ type: 'number' | 'integer';
128
+ } ? InferScalar<S, number> : S extends {
129
+ type: 'boolean';
130
+ } ? InferScalar<S, boolean> : S extends {
131
+ type: 'null';
132
+ } ? null : S extends {
133
+ type: 'array';
134
+ } ? S extends {
135
+ items: infer I;
136
+ } ? InferValueAt<I, NextInferenceDepth<Depth>>[] : JsonValue[] : S extends {
137
+ type: 'object';
138
+ additionalProperties: boolean;
139
+ } ? InferObject<S, NextInferenceDepth<Depth>> : S extends {
140
+ type: 'json';
141
+ } ? JsonValue : S extends {
142
+ oneOf: readonly unknown[];
143
+ } ? InferValueAt<S['oneOf'][number], NextInferenceDepth<Depth>> : never;
144
+ /**
145
+ * Infer the TypeScript value accepted by an author-facing value schema. Exact
146
+ * inference is bounded to 16 container levels, then falls back to `JsonValue`.
147
+ */
148
+ export type InferValue<S> = InferValueAt<S, []>;
149
+ /** Infer the TypeScript argument object for an implicit parameter schema. */
150
+ export type InferArgs<S> = InferProperties<S, []>;
151
+ /**
152
+ * Compile one author-facing value schema to the enforced raw JSON Schema
153
+ * subset. The author-only `json` node becomes an annotation-only schema.
154
+ * @param spec - schema for any JSON-value root.
155
+ * @returns The asserted raw schema projection.
156
+ */
157
+ export declare function valueSchemaSpecToJsonSchema(spec: ValueSchemaSpec): JsonSchemaNode;
158
+ /**
159
+ * Compile the implicit open parameter object into raw JSON Schema.
160
+ * @param spec - per-property parameter definitions.
161
+ * @returns An object-rooted raw schema with no implicit-root openness override.
162
+ */
163
+ export declare function parameterSchemaSpecToJsonSchema(spec: ParameterSchemaSpec): ParameterJsonSchema;
164
+ /** Invalid model-generated arguments for a typed tool. */
165
+ export declare class ToolArgsError extends HarnessError {
166
+ /** Individual violations in schema-walk order. */
167
+ readonly violations: string[];
168
+ constructor(violations: string[]);
169
+ }
170
+ /**
171
+ * Validate model-generated arguments against an implicit parameter schema.
172
+ * @param spec - declared parameter schema.
173
+ * @param args - candidate arguments, however malformed.
174
+ * @returns Path-qualified violations; empty means valid.
175
+ */
176
+ export declare function validateArgs(spec: ParameterSchemaSpec, args: unknown): string[];
177
+ /** Options for {@link defineTool}. */
178
+ export interface DefineToolOptions<S extends ParameterSchemaSpec, O extends ValueSchemaSpec> {
179
+ /** Tool name (must be unique). */
180
+ readonly name: string;
181
+ /** Human-readable description sent to the model. */
182
+ readonly description: string;
183
+ /** Per-property parameter schema compiled to an implicit open object root. */
184
+ readonly parameters: S;
185
+ /** Canonical output schema plus pure Native and presentation projections. */
186
+ readonly output: {
187
+ /** Schema enforced against every successful body or policy-replaced value. */
188
+ readonly schema: O;
189
+ /** Pure Native/model rendering of one validated canonical value. */
190
+ render(args: InferArgs<S>, value: InferValue<NoInfer<O>>): ContentBlock[];
191
+ /** Pure replayable presentation metadata for native and nested calls. */
192
+ presentationMeta?(args: InferArgs<S>, value: InferValue<NoInfer<O>>): JsonValue;
193
+ };
194
+ /** Requests deferred loading of the tool definition; see {@link @qilin-agent/llm#ToolSchema.deferLoading}. */
195
+ readonly deferLoading?: true;
196
+ /** Optional positive cooperative timeout budget in milliseconds. */
197
+ readonly timeoutMs?: number;
198
+ /**
199
+ * Pure classifier for sibling overlap.
200
+ * @param args - typed validated arguments.
201
+ * @returns Whether the call may join a parallel group.
202
+ */
203
+ isConcurrencySafe?(args: InferArgs<S>): boolean;
204
+ /**
205
+ * Execute the tool after argument validation.
206
+ * @param args - typed validated arguments.
207
+ * @param exec - execution identity, caller, cancellation, and nesting data.
208
+ * @returns The canonical value declared by `output.schema`.
209
+ */
210
+ execute(args: InferArgs<S>, exec: ToolRunContext): Promise<InferValue<NoInfer<O>>>;
211
+ /**
212
+ * Optional last-mile content transform for every normalized outcome. Unlike
213
+ * `execute`, arguments remain `unknown` because invalid-input failures also
214
+ * reach this callback. See {@link ToolDefinition.finalizeContent}.
215
+ * @param exec - immutable execution identity and arguments.
216
+ * @param result - complete normalized outcome before materialization.
217
+ * @returns replacement content, or `undefined` to preserve it.
218
+ */
219
+ finalizeContent?(exec: Readonly<ToolExecution>, result: Readonly<ToolExecutionResult>): ContentBlock[] | undefined;
220
+ /**
221
+ * Pure pending-state presenter.
222
+ * @param args - typed validated arguments.
223
+ * @returns Tool-owned render intent, or `undefined` for the generic card.
224
+ */
225
+ presentCall?(args: InferArgs<S>): ToolCallView | undefined;
226
+ /**
227
+ * Pure completed-state presenter.
228
+ * @param args - typed validated arguments.
229
+ * @param result - final model-facing tool result.
230
+ * @returns Tool-owned render intent, or `undefined` for the generic card.
231
+ */
232
+ presentResult?(args: InferArgs<S>, result: ToolResult): ToolResultView | undefined;
233
+ }
234
+ /**
235
+ * Define a first-party tool with inferred arguments and strict execution
236
+ * validation. Replay-only presenters validate softly and fall back to generic
237
+ * rendering for obsolete logged arguments.
238
+ * @param options - typed definition and optional finalizer and presenters.
239
+ * @returns A registry-ready definition.
240
+ */
241
+ export declare function defineTool<const S extends ParameterSchemaSpec, const O extends ValueSchemaSpec>(options: DefineToolOptions<S, O>): ToolDefinition;
242
+ export {};
243
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1,350 @@
1
+ /** Unified JSON-value schema DSL, inference, compilation, and typed tool helper. @module qilin-tools/schema */
2
+ import { HarnessError } from '@qilin-agent/llm';
3
+ import { assertSupportedJsonSchema, isJsonSchemaRecord, isPlainJsonArray, JsonSchemaError, validateJsonSchemaValue } from "./json-schema.js";
4
+ const ANNOTATION_KEYS = ['description', 'title', 'default', 'examples'];
5
+ /** Throw one author-schema violation through the shared schema error type. */
6
+ function authorError(message) {
7
+ throw new JsonSchemaError([message]);
8
+ }
9
+ /** Copy own annotation fields for validation by the raw-schema boundary. */
10
+ function copyAnnotations(source, target) {
11
+ if (Object.hasOwn(source, 'description'))
12
+ target.description = source.description;
13
+ if (Object.hasOwn(source, 'title'))
14
+ target.title = source.title;
15
+ if (Object.hasOwn(source, 'default'))
16
+ target.default = source.default;
17
+ if (Object.hasOwn(source, 'examples'))
18
+ target.examples = source.examples;
19
+ }
20
+ /** Reject author-only keys outside one node's declared vocabulary. */
21
+ function assertAuthorKeys(source, path, allowed) {
22
+ for (const key of Object.keys(source)) {
23
+ if (!allowed.includes(key))
24
+ authorError(`${path}.${key} is not supported by the value schema DSL`);
25
+ }
26
+ }
27
+ /** Install a compiled node without giving `__proto__` assignment semantics. */
28
+ function assignCompiledNode(destination, node) {
29
+ switch (destination.kind) {
30
+ case 'root':
31
+ destination.holder.value = node;
32
+ break;
33
+ case 'property':
34
+ Object.defineProperty(destination.target, destination.key, {
35
+ value: node,
36
+ enumerable: true,
37
+ configurable: true,
38
+ writable: true,
39
+ });
40
+ break;
41
+ case 'item':
42
+ destination.target.items = node;
43
+ break;
44
+ case 'one-of':
45
+ destination.target[destination.index] = node;
46
+ break;
47
+ }
48
+ }
49
+ /** Install a compiled property map at its root or containing object node. */
50
+ function assignCompiledPropertyMap(destination, compiled) {
51
+ if (destination.kind === 'root') {
52
+ destination.holder.value = compiled;
53
+ }
54
+ else {
55
+ destination.target.properties = compiled.properties;
56
+ }
57
+ }
58
+ /** Execute an author-schema compilation task graph without recursive descent. */
59
+ function runSchemaCompiler(initial) {
60
+ const seen = new Set();
61
+ const tasks = [initial];
62
+ for (let task = tasks.pop(); task !== undefined; task = tasks.pop()) {
63
+ if (task.kind === 'leave') {
64
+ seen.delete(task.input);
65
+ continue;
66
+ }
67
+ if (task.kind === 'property-map-tail') {
68
+ if (task.required.length > 0) {
69
+ task.compiled.required = task.required;
70
+ if (task.destination.kind === 'object')
71
+ task.destination.target.required = task.required;
72
+ }
73
+ continue;
74
+ }
75
+ if (task.kind === 'property') {
76
+ if (!isJsonSchemaRecord(task.property))
77
+ authorError(`${task.path} must be a value schema object`);
78
+ if (Object.hasOwn(task.property, 'required') && task.property.required !== true) {
79
+ authorError(`${task.path}.required must be true when present`);
80
+ }
81
+ if (Object.hasOwn(task.property, 'required') && task.property.required === true)
82
+ task.required.push(task.key);
83
+ tasks.push({
84
+ kind: 'value',
85
+ input: task.property,
86
+ path: task.path,
87
+ allowRequired: true,
88
+ destination: { kind: 'property', target: task.properties, key: task.key },
89
+ });
90
+ continue;
91
+ }
92
+ if (task.kind === 'property-map') {
93
+ if (!isJsonSchemaRecord(task.input))
94
+ authorError(`${task.path} must be an object of value schemas`);
95
+ if (seen.has(task.input))
96
+ authorError(`${task.path} is circular`);
97
+ seen.add(task.input);
98
+ const compiled = { properties: {} };
99
+ const required = [];
100
+ assignCompiledPropertyMap(task.destination, compiled);
101
+ tasks.push({ kind: 'leave', input: task.input });
102
+ tasks.push({ kind: 'property-map-tail', compiled, required, destination: task.destination });
103
+ const entries = Object.entries(task.input);
104
+ for (let index = entries.length - 1; index >= 0; index--) {
105
+ const entry = entries[index];
106
+ /* v8 ignore next -- the loop is bounded by the captured entry count. */
107
+ if (entry === undefined)
108
+ continue;
109
+ tasks.push({
110
+ kind: 'property',
111
+ property: entry[1],
112
+ path: `${task.path}.${entry[0]}`,
113
+ key: entry[0],
114
+ properties: compiled.properties,
115
+ required,
116
+ });
117
+ }
118
+ continue;
119
+ }
120
+ const { input, path } = task;
121
+ if (!isJsonSchemaRecord(input))
122
+ authorError(`${path} must be a value schema object`);
123
+ if (seen.has(input))
124
+ authorError(`${path} is circular`);
125
+ seen.add(input);
126
+ const authorKeys = [...ANNOTATION_KEYS, ...(task.allowRequired ? ['required'] : [])];
127
+ const node = {};
128
+ assignCompiledNode(task.destination, node);
129
+ tasks.push({ kind: 'leave', input });
130
+ if (Object.hasOwn(input, 'oneOf')) {
131
+ assertAuthorKeys(input, path, [...authorKeys, 'oneOf', 'type']);
132
+ if (Object.hasOwn(input, 'type'))
133
+ authorError(`${path} cannot declare both type and oneOf`);
134
+ if (!isPlainJsonArray(input.oneOf))
135
+ authorError(`${path}.oneOf must be an array of at least two value schemas`);
136
+ const branches = [];
137
+ node.oneOf = branches;
138
+ copyAnnotations(input, node);
139
+ for (let index = input.oneOf.length - 1; index >= 0; index--) {
140
+ tasks.push({
141
+ kind: 'value',
142
+ input: input.oneOf[index],
143
+ path: `${path}.oneOf[${index}]`,
144
+ allowRequired: false,
145
+ destination: { kind: 'one-of', target: branches, index },
146
+ });
147
+ }
148
+ continue;
149
+ }
150
+ const inputType = Object.hasOwn(input, 'type') ? input.type : undefined;
151
+ switch (inputType) {
152
+ case 'json':
153
+ assertAuthorKeys(input, path, [...authorKeys, 'type']);
154
+ copyAnnotations(input, node);
155
+ break;
156
+ case 'object':
157
+ assertAuthorKeys(input, path, [...authorKeys, 'type', 'properties', 'additionalProperties']);
158
+ if (!Object.hasOwn(input, 'additionalProperties') || typeof input.additionalProperties !== 'boolean') {
159
+ authorError(`${path}.additionalProperties must be explicitly true or false`);
160
+ }
161
+ node.type = 'object';
162
+ copyAnnotations(input, node);
163
+ node.additionalProperties = input.additionalProperties;
164
+ if (Object.hasOwn(input, 'properties')) {
165
+ tasks.push({
166
+ kind: 'property-map',
167
+ input: input.properties,
168
+ path: `${path}.properties`,
169
+ destination: { kind: 'object', target: node },
170
+ });
171
+ }
172
+ break;
173
+ case 'array':
174
+ assertAuthorKeys(input, path, [...authorKeys, 'type', 'items']);
175
+ node.type = 'array';
176
+ copyAnnotations(input, node);
177
+ if (Object.hasOwn(input, 'items')) {
178
+ tasks.push({
179
+ kind: 'value',
180
+ input: input.items,
181
+ path: `${path}.items`,
182
+ allowRequired: false,
183
+ destination: { kind: 'item', target: node },
184
+ });
185
+ }
186
+ break;
187
+ case 'string':
188
+ case 'number':
189
+ case 'integer':
190
+ case 'boolean':
191
+ case 'null':
192
+ assertAuthorKeys(input, path, [...authorKeys, 'type', 'enum', 'const']);
193
+ node.type = inputType;
194
+ copyAnnotations(input, node);
195
+ if (Object.hasOwn(input, 'enum')) {
196
+ if (!isPlainJsonArray(input.enum))
197
+ authorError(`${path}.enum must be a non-empty array of scalar values`);
198
+ node.enum = Array.from(input.enum, entry => entry);
199
+ }
200
+ if (Object.hasOwn(input, 'const'))
201
+ node.const = input.const;
202
+ break;
203
+ default:
204
+ authorError(`${path}.type must be string/number/integer/boolean/null/array/object/json, or use oneOf`);
205
+ }
206
+ }
207
+ }
208
+ /** Compile one implicit property map, collecting per-property requiredness. */
209
+ function compilePropertyMap(input, path) {
210
+ const holder = {};
211
+ runSchemaCompiler({ kind: 'property-map', input, path, destination: { kind: 'root', holder } });
212
+ /* v8 ignore next -- the root task assigns before scheduling any descendants. */
213
+ return holder.value ?? authorError(`${path} did not compile`);
214
+ }
215
+ /** Compile one author node without applying any consumer root restriction. */
216
+ function compileValueSchema(input, path) {
217
+ const holder = {};
218
+ runSchemaCompiler({ kind: 'value', input, path, allowRequired: false, destination: { kind: 'root', holder } });
219
+ /* v8 ignore next -- the root task assigns before scheduling any descendants. */
220
+ return holder.value ?? authorError(`${path} did not compile`);
221
+ }
222
+ /**
223
+ * Compile one author-facing value schema to the enforced raw JSON Schema
224
+ * subset. The author-only `json` node becomes an annotation-only schema.
225
+ * @param spec - schema for any JSON-value root.
226
+ * @returns The asserted raw schema projection.
227
+ */
228
+ export function valueSchemaSpecToJsonSchema(spec) {
229
+ const schema = compileValueSchema(spec, 'schema');
230
+ assertSupportedJsonSchema(schema);
231
+ return schema;
232
+ }
233
+ /**
234
+ * Compile the implicit open parameter object into raw JSON Schema.
235
+ * @param spec - per-property parameter definitions.
236
+ * @returns An object-rooted raw schema with no implicit-root openness override.
237
+ */
238
+ export function parameterSchemaSpecToJsonSchema(spec) {
239
+ const compiled = compilePropertyMap(spec, 'parameters');
240
+ const schema = {
241
+ type: 'object',
242
+ properties: compiled.properties,
243
+ ...(compiled.required === undefined ? {} : { required: compiled.required }),
244
+ };
245
+ assertSupportedJsonSchema(schema);
246
+ return schema;
247
+ }
248
+ /** Invalid model-generated arguments for a typed tool. */
249
+ export class ToolArgsError extends HarnessError {
250
+ /** Individual violations in schema-walk order. */
251
+ violations;
252
+ constructor(violations) {
253
+ super(`invalid arguments: ${violations.join('; ')}`, 'INVALID_ARGS');
254
+ this.name = 'ToolArgsError';
255
+ this.violations = violations;
256
+ }
257
+ }
258
+ /**
259
+ * Validate model-generated arguments against an implicit parameter schema.
260
+ * @param spec - declared parameter schema.
261
+ * @param args - candidate arguments, however malformed.
262
+ * @returns Path-qualified violations; empty means valid.
263
+ */
264
+ export function validateArgs(spec, args) {
265
+ return validateJsonSchemaValue(parameterSchemaSpecToJsonSchema(spec), args, '');
266
+ }
267
+ /**
268
+ * Define a first-party tool with inferred arguments and strict execution
269
+ * validation. Replay-only presenters validate softly and fall back to generic
270
+ * rendering for obsolete logged arguments.
271
+ * @param options - typed definition and optional finalizer and presenters.
272
+ * @returns A registry-ready definition.
273
+ */
274
+ export function defineTool(options) {
275
+ // Object-literal methods do not use `this`; retaining references is safe.
276
+ // oxlint-disable-next-line typescript/unbound-method
277
+ const userExecute = options.execute;
278
+ // oxlint-disable-next-line typescript/unbound-method
279
+ const userFinalizeContent = options.finalizeContent;
280
+ // oxlint-disable-next-line typescript/unbound-method
281
+ const userRender = options.output.render;
282
+ // oxlint-disable-next-line typescript/unbound-method
283
+ const userPresentationMeta = options.output.presentationMeta;
284
+ // oxlint-disable-next-line typescript/unbound-method
285
+ const userPresentCall = options.presentCall;
286
+ // oxlint-disable-next-line typescript/unbound-method
287
+ const userPresentResult = options.presentResult;
288
+ // oxlint-disable-next-line typescript/unbound-method
289
+ const userIsConcurrencySafe = options.isConcurrencySafe;
290
+ if (options.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) {
291
+ throw new Error(`defineTool(${options.name}): timeoutMs must be a positive finite number`);
292
+ }
293
+ const parameters = parameterSchemaSpecToJsonSchema(options.parameters);
294
+ const outputSchema = valueSchemaSpecToJsonSchema(options.output.schema);
295
+ const validate = (args) => validateJsonSchemaValue(parameters, args, '');
296
+ const tool = {
297
+ name: options.name,
298
+ description: options.description,
299
+ parameters: parameters,
300
+ output: {
301
+ schema: outputSchema,
302
+ render(args, value) {
303
+ return userRender(args, value);
304
+ },
305
+ ...userPresentationMeta !== undefined ? {
306
+ presentationMeta(args, value) {
307
+ return userPresentationMeta(args, value);
308
+ },
309
+ } : {},
310
+ },
311
+ ...(options.deferLoading === true ? { deferLoading: options.deferLoading } : {}),
312
+ ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
313
+ async execute(args, exec) {
314
+ const violations = validate(args);
315
+ if (violations.length > 0)
316
+ throw new ToolArgsError(violations);
317
+ return userExecute(args, exec);
318
+ },
319
+ };
320
+ if (userFinalizeContent) {
321
+ tool.finalizeContent = (exec, result) => userFinalizeContent(exec, result);
322
+ }
323
+ // Presentation is display-only and may run on REPLAY of arbitrary logged args
324
+ // (possibly from an older schema), so it must never throw: validate softly and
325
+ // fall back to `undefined` (a generic UI presentation) on any mismatch, rather
326
+ // than the hard `ToolArgsError` the execute path raises.
327
+ if (userPresentCall) {
328
+ tool.presentCall = (args) => {
329
+ if (validate(args).length > 0)
330
+ return undefined;
331
+ return userPresentCall(args);
332
+ };
333
+ }
334
+ if (userPresentResult) {
335
+ tool.presentResult = (args, result) => {
336
+ if (validate(args).length > 0)
337
+ return undefined;
338
+ return userPresentResult(args, result);
339
+ };
340
+ }
341
+ if (userIsConcurrencySafe) {
342
+ tool.isConcurrencySafe = (args) => {
343
+ if (validate(args).length > 0)
344
+ return false;
345
+ return userIsConcurrencySafe(args);
346
+ };
347
+ }
348
+ return tool;
349
+ }
350
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1,25 @@
1
+ /** Canonical tool-definition fixtures for repository tests. @module qilin-tools/testing */
2
+ import type { ContentBlock } from '@qilin-agent/llm';
3
+ import type { DefineToolOptions, ParameterSchemaSpec } from './schema.ts';
4
+ import type { ToolDefinition, ToolRunContext } from './index.ts';
5
+ declare const CONTENT_VALUE_SCHEMA: {
6
+ readonly type: "array";
7
+ readonly items: {
8
+ readonly type: "json";
9
+ };
10
+ };
11
+ /** Options for a fixture whose canonical value is its rendered content array. */
12
+ export type ContentToolFixtureOptions<S extends ParameterSchemaSpec> = Omit<DefineToolOptions<S, typeof CONTENT_VALUE_SCHEMA>, 'output' | 'execute'> & {
13
+ /** Produce the fixture's content blocks as its canonical test value. */
14
+ execute(args: import('./schema.ts').InferArgs<S>, exec: ToolRunContext): Promise<ContentBlock[]>;
15
+ };
16
+ /**
17
+ * Define a test fixture that deliberately uses its content blocks as the
18
+ * canonical JSON value. Product tools must declare domain-owned DTOs instead.
19
+ * @param options - ordinary fixture fields plus a content-producing body.
20
+ * @returns a registry-ready tool with an explicit JSON-array output contract.
21
+ * @internal
22
+ */
23
+ export declare function defineContentToolFixture<const S extends ParameterSchemaSpec>(options: ContentToolFixtureOptions<S>): ToolDefinition;
24
+ export {};
25
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1,25 @@
1
+ /** Canonical tool-definition fixtures for repository tests. @module qilin-tools/testing */
2
+ import { defineTool } from "./schema.js";
3
+ const CONTENT_VALUE_SCHEMA = { type: 'array', items: { type: 'json' } };
4
+ /**
5
+ * Define a test fixture that deliberately uses its content blocks as the
6
+ * canonical JSON value. Product tools must declare domain-owned DTOs instead.
7
+ * @param options - ordinary fixture fields plus a content-producing body.
8
+ * @returns a registry-ready tool with an explicit JSON-array output contract.
9
+ * @internal
10
+ */
11
+ export function defineContentToolFixture(options) {
12
+ // oxlint-disable-next-line typescript/unbound-method
13
+ const execute = options.execute;
14
+ return defineTool({
15
+ ...options,
16
+ output: {
17
+ schema: CONTENT_VALUE_SCHEMA,
18
+ render: (_args, value) => value,
19
+ },
20
+ async execute(args, exec) {
21
+ return await execute(args, exec);
22
+ },
23
+ });
24
+ }
25
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * PTC mode codegen: the pure projection from registered tool schemas to the TypeScript SDK
3
+ * text the model programs against (the `tools:sdk` prompt section). Sibling of
4
+ * `json-schema.ts` — `schemas()` (native function calling) and this module (the generated
5
+ * `declare const tools` API) are two projections of the same store.
6
+ * @module @qilin-agent/tools/src/ts-types
7
+ */
8
+ import type { ToolSchema } from '@qilin-agent/llm';
9
+ import type { JsonSchemaNode } from './json-schema.ts';
10
+ /** Internal PTC mode projection: the model-facing schema plus the canonical output schema. */
11
+ export interface ToolSdkSchema extends ToolSchema {
12
+ /** Validated canonical value returned by the tool binding. */
13
+ output: JsonSchemaNode;
14
+ }
15
+ /**
16
+ * Map one enforced JSON-Schema node to a TypeScript type literal. Supports
17
+ * every unified schema construct and returns `unknown` for malformed or
18
+ * unsupported inputs without throwing.
19
+ * @param schema - the JSON-Schema node (any shape; hostile inputs degrade).
20
+ * @param indent - the indentation level for nested object members.
21
+ * @returns the TS type text (multi-line for objects with properties).
22
+ */
23
+ export declare function jsonSchemaToTs(schema: unknown, indent?: number): string;
24
+ /**
25
+ * Render the full `tools:sdk` prompt section: the fixed usage instructions
26
+ * plus one `declare const tools` interface covering every given tool.
27
+ * Deterministic — tools are emitted in lexicographic name order, so an
28
+ * unchanged tool set produces byte-identical text across assemblies. The sort
29
+ * is not a total order on byte-equal names, so two schemas sharing a name
30
+ * would render in argument order; the caller's visible-capability map is keyed
31
+ * by name, so the input never carries a duplicate.
32
+ * @param schemas - the tool schemas to declare (the caller excludes
33
+ * `run_code` itself).
34
+ * @returns the complete section text.
35
+ */
36
+ export declare function renderToolsSdk(schemas: ToolSdkSchema[]): string;
37
+ //# sourceMappingURL=ts-types.d.ts.map