@hunterzhu/pulse-tool-sdk 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/dist/index.d.ts +163 -0
- package/dist/index.js +412 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.js +82 -0
- package/package.json +21 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { z, type ZodTypeAny } from 'zod';
|
|
2
|
+
export { matchesJsonSchema } from './schema.js';
|
|
3
|
+
export declare const TOOL_SDK_VERSION = "0.1.0";
|
|
4
|
+
export type ConcurrencyClass = 'llm' | 'tool' | 'agent' | 'none';
|
|
5
|
+
export interface ResourceClaim {
|
|
6
|
+
resource: string;
|
|
7
|
+
mode: 'shared' | 'exclusive';
|
|
8
|
+
}
|
|
9
|
+
export interface ToolContext {
|
|
10
|
+
toolCallId: string;
|
|
11
|
+
effectId: string;
|
|
12
|
+
attemptId: string;
|
|
13
|
+
idempotencyKey?: string;
|
|
14
|
+
agentId: string;
|
|
15
|
+
laneId: string;
|
|
16
|
+
signal: AbortSignal;
|
|
17
|
+
emit(event: {
|
|
18
|
+
type: 'progress' | 'warning' | 'diagnostic';
|
|
19
|
+
data: JsonValue;
|
|
20
|
+
}): void;
|
|
21
|
+
}
|
|
22
|
+
export interface ReconcileContext {
|
|
23
|
+
toolCallId: string;
|
|
24
|
+
effectId: string;
|
|
25
|
+
attemptId: string;
|
|
26
|
+
agentId: string;
|
|
27
|
+
laneId: string;
|
|
28
|
+
signal: AbortSignal;
|
|
29
|
+
}
|
|
30
|
+
export interface ReconcileResult<TOutput> {
|
|
31
|
+
status: 'succeeded' | 'failed' | 'cancelled' | 'unknown';
|
|
32
|
+
output?: TOutput;
|
|
33
|
+
error?: {
|
|
34
|
+
code: string;
|
|
35
|
+
message: string;
|
|
36
|
+
retryable?: boolean;
|
|
37
|
+
details?: JsonValue;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export interface ToolPermissions {
|
|
41
|
+
workspaceRoots?: string[];
|
|
42
|
+
networkHosts?: string[];
|
|
43
|
+
}
|
|
44
|
+
export interface ToolManifest {
|
|
45
|
+
name: string;
|
|
46
|
+
version: string;
|
|
47
|
+
description: string;
|
|
48
|
+
tags?: string[];
|
|
49
|
+
inputSchema: Record<string, unknown>;
|
|
50
|
+
outputSchema: Record<string, unknown>;
|
|
51
|
+
concurrencyClass: ConcurrencyClass;
|
|
52
|
+
locks: ResourceClaim[];
|
|
53
|
+
resources?: ResourceClaim[];
|
|
54
|
+
supportsAbortSignal: boolean;
|
|
55
|
+
sideEffectPolicy: 'none' | 'read' | 'write' | 'external';
|
|
56
|
+
retrySafety: 'read_only' | 'idempotent' | 'unsafe';
|
|
57
|
+
defaultTimeoutMs: number;
|
|
58
|
+
maxResultSummaryBytes?: number;
|
|
59
|
+
permissions?: ToolPermissions;
|
|
60
|
+
}
|
|
61
|
+
export interface ToolDiscoveryQuery {
|
|
62
|
+
text?: string;
|
|
63
|
+
tags?: string[];
|
|
64
|
+
sideEffectPolicy?: ToolManifest['sideEffectPolicy'];
|
|
65
|
+
concurrencyClass?: ConcurrencyClass;
|
|
66
|
+
limit?: number;
|
|
67
|
+
}
|
|
68
|
+
export interface ToolDiscoveryResult {
|
|
69
|
+
manifest: ToolManifest;
|
|
70
|
+
score: number;
|
|
71
|
+
}
|
|
72
|
+
export interface ToolSetSnapshot {
|
|
73
|
+
id: string;
|
|
74
|
+
version: string;
|
|
75
|
+
tools: ToolManifest[];
|
|
76
|
+
}
|
|
77
|
+
export interface ToolRegistryPolicy {
|
|
78
|
+
allow?: string[];
|
|
79
|
+
deny?: string[];
|
|
80
|
+
workspaceRoots?: string[];
|
|
81
|
+
networkHosts?: string[];
|
|
82
|
+
allowNetwork?: boolean;
|
|
83
|
+
}
|
|
84
|
+
export interface ToolAdmission {
|
|
85
|
+
locks: ResourceClaim[];
|
|
86
|
+
sideEffectPolicy: ToolManifest['sideEffectPolicy'];
|
|
87
|
+
defaultTimeoutMs: number;
|
|
88
|
+
retrySafety: ToolManifest['retrySafety'];
|
|
89
|
+
version: string;
|
|
90
|
+
}
|
|
91
|
+
export interface ToolDefinition<TInput = unknown, TOutput = unknown> {
|
|
92
|
+
manifest: ToolManifest;
|
|
93
|
+
resourceAdmissionMode?: 'explicit' | 'default';
|
|
94
|
+
validateInput?(input: unknown): TInput;
|
|
95
|
+
execute(input: TInput, context: ToolContext): Promise<TOutput> | TOutput;
|
|
96
|
+
executionRef?(input: TInput, context: ToolContext): JsonValue;
|
|
97
|
+
resolveResources?(input: TInput): ResourceClaim[];
|
|
98
|
+
reconcile?(executionRef: JsonValue, context: ReconcileContext): Promise<ReconcileResult<TOutput>>;
|
|
99
|
+
normalize?(output: TOutput): JsonValue;
|
|
100
|
+
summarize?(output: TOutput): JsonValue;
|
|
101
|
+
}
|
|
102
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
103
|
+
[key: string]: JsonValue;
|
|
104
|
+
};
|
|
105
|
+
export declare class ToolError extends Error {
|
|
106
|
+
readonly code: string;
|
|
107
|
+
readonly retryable: boolean;
|
|
108
|
+
readonly details: JsonValue | undefined;
|
|
109
|
+
constructor(code: string, message: string, options?: {
|
|
110
|
+
retryable?: boolean;
|
|
111
|
+
details?: JsonValue;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
export declare class ToolRegistry {
|
|
115
|
+
private readonly definitions;
|
|
116
|
+
private readonly policy;
|
|
117
|
+
constructor(policy?: ToolRegistryPolicy);
|
|
118
|
+
register<TInput, TOutput>(definition: ToolDefinition<TInput, TOutput>): void;
|
|
119
|
+
get(name: string): ToolDefinition<any, any> | undefined;
|
|
120
|
+
validateInput(name: string, input: unknown): unknown;
|
|
121
|
+
isAllowed(name: string): boolean;
|
|
122
|
+
permissionReasons(name: string): string[];
|
|
123
|
+
list(): ToolManifest[];
|
|
124
|
+
discover(query?: ToolDiscoveryQuery): ToolDiscoveryResult[];
|
|
125
|
+
compileToolSet(id: string, query?: ToolDiscoveryQuery, version?: string): ToolSetSnapshot;
|
|
126
|
+
execute(name: string, input: unknown, context: ToolContext | AbortSignal): Promise<unknown>;
|
|
127
|
+
executeDetailed(name: string, input: unknown, context: ToolContext | AbortSignal): Promise<{
|
|
128
|
+
output: unknown;
|
|
129
|
+
normalized?: JsonValue;
|
|
130
|
+
summary?: JsonValue;
|
|
131
|
+
manifest: ToolManifest;
|
|
132
|
+
}>;
|
|
133
|
+
reconcileDetailed(name: string, executionRef: JsonValue, context: ReconcileContext): Promise<ReconcileResult<unknown>>;
|
|
134
|
+
executionRef(name: string, input: unknown, context: ToolContext): JsonValue | undefined;
|
|
135
|
+
resolveResources(name: string, input: unknown): ResourceClaim[];
|
|
136
|
+
admission(name: string, input: unknown): ToolAdmission;
|
|
137
|
+
private require;
|
|
138
|
+
private permissionsAllowed;
|
|
139
|
+
}
|
|
140
|
+
export declare function zodToJsonSchema(schema: ZodTypeAny): Record<string, unknown>;
|
|
141
|
+
export declare function defineTool<TInput, TOutput>(config: {
|
|
142
|
+
name: string;
|
|
143
|
+
version?: string;
|
|
144
|
+
description: string;
|
|
145
|
+
tags?: string[];
|
|
146
|
+
input: z.ZodType<TInput>;
|
|
147
|
+
output: z.ZodType<TOutput>;
|
|
148
|
+
concurrencyClass?: ConcurrencyClass;
|
|
149
|
+
locks?: ResourceClaim[];
|
|
150
|
+
resources?: ResourceClaim[];
|
|
151
|
+
supportsAbortSignal?: boolean;
|
|
152
|
+
sideEffectPolicy?: ToolManifest['sideEffectPolicy'];
|
|
153
|
+
retrySafety?: ToolManifest['retrySafety'];
|
|
154
|
+
defaultTimeoutMs?: number;
|
|
155
|
+
maxResultSummaryBytes?: number;
|
|
156
|
+
permissions?: ToolPermissions;
|
|
157
|
+
resolveResources?: (input: TInput) => ResourceClaim[];
|
|
158
|
+
reconcile?: (executionRef: JsonValue, context: ReconcileContext) => Promise<ReconcileResult<TOutput>>;
|
|
159
|
+
normalize?: (output: TOutput) => JsonValue;
|
|
160
|
+
summarize?: (output: TOutput) => JsonValue;
|
|
161
|
+
execute(input: TInput, context: ToolContext): Promise<TOutput> | TOutput;
|
|
162
|
+
executionRef?: (input: TInput, context: ToolContext) => JsonValue;
|
|
163
|
+
}): ToolDefinition<TInput, TOutput>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { normalize } from 'node:path';
|
|
4
|
+
import { matchesJsonSchema } from './schema.js';
|
|
5
|
+
export { matchesJsonSchema } from './schema.js';
|
|
6
|
+
function normalizeWorkspaceRoot(root) { if (root === '*')
|
|
7
|
+
return root; const value = normalize(root); return value.length > 1 ? value.replace(/\/$/, '') : value; }
|
|
8
|
+
function normalizeNetworkHost(host) { return host.toLocaleLowerCase().replace(/\.$/, ''); }
|
|
9
|
+
export const TOOL_SDK_VERSION = '0.1.0';
|
|
10
|
+
function isJsonSchema(value) {
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
const visit = (candidate) => {
|
|
13
|
+
if (candidate === null || typeof candidate !== 'object' || Array.isArray(candidate))
|
|
14
|
+
return false;
|
|
15
|
+
const schema = candidate;
|
|
16
|
+
if (seen.has(schema))
|
|
17
|
+
return false;
|
|
18
|
+
seen.add(schema);
|
|
19
|
+
try {
|
|
20
|
+
if (schema.type !== undefined && (typeof schema.type !== 'string' || !['null', 'boolean', 'number', 'integer', 'string', 'array', 'object'].includes(schema.type)))
|
|
21
|
+
return false;
|
|
22
|
+
for (const key of ['anyOf', 'oneOf', 'allOf'])
|
|
23
|
+
if (schema[key] !== undefined && (!Array.isArray(schema[key]) || schema[key].length === 0 || !schema[key].every(visit)))
|
|
24
|
+
return false;
|
|
25
|
+
if (schema.not !== undefined && !visit(schema.not))
|
|
26
|
+
return false;
|
|
27
|
+
if (schema.items !== undefined && !visit(schema.items))
|
|
28
|
+
return false;
|
|
29
|
+
if (schema.properties !== undefined && (schema.properties === null || typeof schema.properties !== 'object' || Array.isArray(schema.properties) || !Object.values(schema.properties).every(visit)))
|
|
30
|
+
return false;
|
|
31
|
+
if (schema.additionalProperties !== undefined && typeof schema.additionalProperties !== 'boolean' && !visit(schema.additionalProperties))
|
|
32
|
+
return false;
|
|
33
|
+
if (schema.required !== undefined && (!Array.isArray(schema.required) || new Set(schema.required).size !== schema.required.length || schema.required.some((key) => typeof key !== 'string')))
|
|
34
|
+
return false;
|
|
35
|
+
if (schema.enum !== undefined && !Array.isArray(schema.enum))
|
|
36
|
+
return false;
|
|
37
|
+
if (schema.pattern !== undefined) {
|
|
38
|
+
if (typeof schema.pattern !== 'string')
|
|
39
|
+
return false;
|
|
40
|
+
try {
|
|
41
|
+
new RegExp(schema.pattern);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const key of ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf'])
|
|
48
|
+
if (schema[key] !== undefined && (typeof schema[key] !== 'number' || !Number.isFinite(schema[key])))
|
|
49
|
+
return false;
|
|
50
|
+
for (const key of ['minLength', 'maxLength', 'minItems', 'maxItems'])
|
|
51
|
+
if (schema[key] !== undefined && (!Number.isInteger(schema[key]) || schema[key] < 0))
|
|
52
|
+
return false;
|
|
53
|
+
if (schema.uniqueItems !== undefined && typeof schema.uniqueItems !== 'boolean')
|
|
54
|
+
return false;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
seen.delete(schema);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return visit(value);
|
|
62
|
+
}
|
|
63
|
+
function isResourceClaims(value) {
|
|
64
|
+
if (!Array.isArray(value))
|
|
65
|
+
return false;
|
|
66
|
+
return value.every((claim) => {
|
|
67
|
+
if (claim === null || typeof claim !== 'object' || Array.isArray(claim))
|
|
68
|
+
return false;
|
|
69
|
+
const record = claim;
|
|
70
|
+
return typeof record.resource === 'string' && record.resource.length > 0 && (record.mode === 'shared' || record.mode === 'exclusive');
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function validateResolvedClaims(value, name) {
|
|
74
|
+
if (!isResourceClaims(value))
|
|
75
|
+
throw new ToolError('INVALID_TOOL_RESOURCE_LOCKS', `Resolved resources are invalid for tool ${name}.`, { retryable: false });
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
function isPermissions(value) {
|
|
79
|
+
if (value === undefined)
|
|
80
|
+
return true;
|
|
81
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
82
|
+
return false;
|
|
83
|
+
const permissions = value;
|
|
84
|
+
return (permissions.workspaceRoots === undefined || (Array.isArray(permissions.workspaceRoots) && permissions.workspaceRoots.every((root) => typeof root === 'string' && root.length > 0))) && (permissions.networkHosts === undefined || (Array.isArray(permissions.networkHosts) && permissions.networkHosts.every((host) => typeof host === 'string' && host.length > 0)));
|
|
85
|
+
}
|
|
86
|
+
function isManifestContract(manifest) {
|
|
87
|
+
return typeof manifest.description === 'string' && isJsonSchema(manifest.inputSchema) && isJsonSchema(manifest.outputSchema) && ['llm', 'tool', 'agent', 'none'].includes(String(manifest.concurrencyClass)) && ['none', 'read', 'write', 'external'].includes(String(manifest.sideEffectPolicy)) && ['read_only', 'idempotent', 'unsafe'].includes(String(manifest.retrySafety)) && isResourceClaims(manifest.locks) && (manifest.resources === undefined || isResourceClaims(manifest.resources)) && (manifest.tags === undefined || (Array.isArray(manifest.tags) && manifest.tags.every((tag) => typeof tag === 'string' && tag.length > 0))) && (manifest.maxResultSummaryBytes === undefined || (Number.isInteger(manifest.maxResultSummaryBytes) && manifest.maxResultSummaryBytes >= 0)) && isPermissions(manifest.permissions);
|
|
88
|
+
}
|
|
89
|
+
function summaryWithinBudget(value, maxBytes) {
|
|
90
|
+
try {
|
|
91
|
+
return Buffer.byteLength(JSON.stringify(value), 'utf8') <= maxBytes;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function isStrictJsonValue(value, seen = new Set()) {
|
|
98
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
99
|
+
return true;
|
|
100
|
+
if (typeof value === 'number')
|
|
101
|
+
return Number.isFinite(value);
|
|
102
|
+
if (typeof value !== 'object')
|
|
103
|
+
return false;
|
|
104
|
+
if (seen.has(value))
|
|
105
|
+
return false;
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
seen.add(value);
|
|
108
|
+
try {
|
|
109
|
+
return value.every((item) => isStrictJsonValue(item, seen));
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
seen.delete(value);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
|
|
116
|
+
return false;
|
|
117
|
+
seen.add(value);
|
|
118
|
+
try {
|
|
119
|
+
return Object.values(value).every((item) => isStrictJsonValue(item, seen));
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
seen.delete(value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function validateStrictJsonValue(value, code, message) {
|
|
126
|
+
if (!isStrictJsonValue(value))
|
|
127
|
+
throw new ToolError(code, message, { retryable: false });
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
function validateDiscoveryQuery(query) {
|
|
131
|
+
if (query === null || typeof query !== 'object' || Array.isArray(query))
|
|
132
|
+
throw Object.assign(new Error('Invalid tool discovery query.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
|
|
133
|
+
const value = query;
|
|
134
|
+
if (value.text !== undefined && typeof value.text !== 'string')
|
|
135
|
+
throw Object.assign(new Error('Invalid tool discovery query text.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
|
|
136
|
+
if (value.tags !== undefined && (!Array.isArray(value.tags) || value.tags.some((tag) => typeof tag !== 'string')))
|
|
137
|
+
throw Object.assign(new Error('Invalid tool discovery query tags.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
|
|
138
|
+
if (value.sideEffectPolicy !== undefined && !['none', 'read', 'write', 'external'].includes(String(value.sideEffectPolicy)))
|
|
139
|
+
throw Object.assign(new Error('Invalid tool discovery side-effect policy.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
|
|
140
|
+
if (value.concurrencyClass !== undefined && !['llm', 'tool', 'agent', 'none'].includes(String(value.concurrencyClass)))
|
|
141
|
+
throw Object.assign(new Error('Invalid tool discovery concurrency class.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
|
|
142
|
+
if (value.limit !== undefined && (!Number.isInteger(value.limit) || value.limit < 0))
|
|
143
|
+
throw Object.assign(new Error('Invalid tool discovery limit.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
|
|
144
|
+
}
|
|
145
|
+
export class ToolError extends Error {
|
|
146
|
+
code;
|
|
147
|
+
retryable;
|
|
148
|
+
details;
|
|
149
|
+
constructor(code, message, options = {}) {
|
|
150
|
+
super(message);
|
|
151
|
+
this.name = 'ToolError';
|
|
152
|
+
this.code = code;
|
|
153
|
+
this.retryable = options.retryable ?? true;
|
|
154
|
+
this.details = options.details;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export class ToolRegistry {
|
|
158
|
+
definitions = new Map();
|
|
159
|
+
policy;
|
|
160
|
+
constructor(policy = {}) {
|
|
161
|
+
this.policy = { ...(policy.allow === undefined ? {} : { allow: new Set(policy.allow) }), deny: new Set(policy.deny ?? []), ...(policy.workspaceRoots === undefined ? {} : { workspaceRoots: new Set(policy.workspaceRoots.map(normalizeWorkspaceRoot)) }), ...(policy.networkHosts === undefined ? {} : { networkHosts: new Set(policy.networkHosts.map(normalizeNetworkHost)) }), allowNetwork: policy.allowNetwork ?? true };
|
|
162
|
+
}
|
|
163
|
+
register(definition) {
|
|
164
|
+
const manifest = definition?.manifest;
|
|
165
|
+
const name = typeof manifest?.name === 'string' ? manifest.name : '';
|
|
166
|
+
if (!manifest?.name || this.definitions.has(name))
|
|
167
|
+
throw new Error(`TOOL_ALREADY_REGISTERED:${name}`);
|
|
168
|
+
if (typeof manifest.version !== 'string' || !manifest.version || typeof manifest.defaultTimeoutMs !== 'number' || !Number.isFinite(manifest.defaultTimeoutMs) || manifest.defaultTimeoutMs < 0 || !isManifestContract(manifest))
|
|
169
|
+
throw new Error(`INVALID_TOOL_MANIFEST:${name}`);
|
|
170
|
+
if (!definition.manifest.supportsAbortSignal)
|
|
171
|
+
throw new Error(`TOOL_ABORT_SIGNAL_REQUIRED:${definition.manifest.name}`);
|
|
172
|
+
this.definitions.set(definition.manifest.name, definition);
|
|
173
|
+
}
|
|
174
|
+
get(name) { return this.isAllowed(name) ? this.definitions.get(name) : undefined; }
|
|
175
|
+
validateInput(name, input) {
|
|
176
|
+
const definition = this.require(name);
|
|
177
|
+
try {
|
|
178
|
+
const parsed = definition.validateInput ? definition.validateInput(input) : input;
|
|
179
|
+
if (!definition.validateInput && !matchesJsonSchema(parsed, definition.manifest.inputSchema))
|
|
180
|
+
throw new Error('schema mismatch');
|
|
181
|
+
return parsed;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
throw new ToolError('INVALID_TOOL_INPUT', `Input does not match the manifest for tool ${name}.`, { retryable: false });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
isAllowed(name) {
|
|
188
|
+
const definition = this.definitions.get(name);
|
|
189
|
+
return this.policy.deny.has(name) === false && (this.policy.allow === undefined || this.policy.allow.has(name)) && (definition === undefined || this.permissionsAllowed(definition.manifest));
|
|
190
|
+
}
|
|
191
|
+
permissionReasons(name) {
|
|
192
|
+
const definition = this.definitions.get(name);
|
|
193
|
+
if (!definition)
|
|
194
|
+
return ['UNKNOWN_TOOL'];
|
|
195
|
+
const permissions = definition.manifest.permissions;
|
|
196
|
+
if (!permissions)
|
|
197
|
+
return [];
|
|
198
|
+
const reasons = [];
|
|
199
|
+
if (!this.policy.allowNetwork && (permissions.networkHosts?.length ?? 0) > 0)
|
|
200
|
+
reasons.push('NETWORK_DISABLED');
|
|
201
|
+
if (this.policy.networkHosts !== undefined)
|
|
202
|
+
for (const rawHost of permissions.networkHosts ?? []) {
|
|
203
|
+
const host = normalizeNetworkHost(rawHost);
|
|
204
|
+
if (!this.policy.networkHosts.has('*') && !this.policy.networkHosts.has(host))
|
|
205
|
+
reasons.push(`NETWORK_HOST_NOT_ALLOWED:${rawHost}`);
|
|
206
|
+
}
|
|
207
|
+
if (this.policy.workspaceRoots !== undefined)
|
|
208
|
+
for (const rawRoot of permissions.workspaceRoots ?? []) {
|
|
209
|
+
const root = normalizeWorkspaceRoot(rawRoot);
|
|
210
|
+
if (![...this.policy.workspaceRoots].some((allowed) => allowed === '*' || root === allowed || root.startsWith(`${allowed}/`)))
|
|
211
|
+
reasons.push(`WORKSPACE_ROOT_NOT_ALLOWED:${rawRoot}`);
|
|
212
|
+
}
|
|
213
|
+
return reasons;
|
|
214
|
+
}
|
|
215
|
+
list() { return [...this.definitions.values()].filter((definition) => this.isAllowed(definition.manifest.name)).map((definition) => structuredClone(definition.manifest)); }
|
|
216
|
+
discover(query = {}) {
|
|
217
|
+
validateDiscoveryQuery(query);
|
|
218
|
+
const terms = (query.text ?? '').toLocaleLowerCase().split(/[^a-z0-9_:-]+/).filter(Boolean);
|
|
219
|
+
const requestedTags = new Set((query.tags ?? []).map((tag) => tag.toLocaleLowerCase()));
|
|
220
|
+
const results = this.list().flatMap((manifest) => {
|
|
221
|
+
if (query.sideEffectPolicy !== undefined && manifest.sideEffectPolicy !== query.sideEffectPolicy)
|
|
222
|
+
return [];
|
|
223
|
+
if (query.concurrencyClass !== undefined && manifest.concurrencyClass !== query.concurrencyClass)
|
|
224
|
+
return [];
|
|
225
|
+
const tags = (manifest.tags ?? []).map((tag) => tag.toLocaleLowerCase());
|
|
226
|
+
if ([...requestedTags].some((tag) => !tags.includes(tag)))
|
|
227
|
+
return [];
|
|
228
|
+
const haystack = [manifest.name, manifest.description, ...tags].join(' ').toLocaleLowerCase();
|
|
229
|
+
const score = terms.length === 0 ? 1 + requestedTags.size * 2 : terms.reduce((total, term) => total + (manifest.name.toLocaleLowerCase() === term ? 10 : manifest.name.toLocaleLowerCase().includes(term) ? 5 : haystack.includes(term) ? 1 : 0), requestedTags.size * 2);
|
|
230
|
+
return score > 0 ? [{ manifest, score }] : [];
|
|
231
|
+
});
|
|
232
|
+
results.sort((left, right) => right.score - left.score || left.manifest.name.localeCompare(right.manifest.name) || left.manifest.version.localeCompare(right.manifest.version));
|
|
233
|
+
return query.limit === undefined ? results : results.slice(0, Math.max(0, query.limit));
|
|
234
|
+
}
|
|
235
|
+
compileToolSet(id, query = {}, version) {
|
|
236
|
+
if (!id)
|
|
237
|
+
throw new Error('INVALID_TOOL_SET_ID');
|
|
238
|
+
const tools = this.discover(query).map((result) => result.manifest);
|
|
239
|
+
const derivedVersion = createHash('sha256').update(JSON.stringify(tools)).digest('hex').slice(0, 16);
|
|
240
|
+
return { id, version: version ?? derivedVersion, tools: structuredClone(tools) };
|
|
241
|
+
}
|
|
242
|
+
async execute(name, input, context) {
|
|
243
|
+
const definition = this.require(name);
|
|
244
|
+
const parsedInput = this.validateInput(name, input);
|
|
245
|
+
const toolContext = 'aborted' in context ? { toolCallId: '', effectId: '', attemptId: '', agentId: '', laneId: '', signal: context, emit: () => { } } : context;
|
|
246
|
+
const output = await definition.execute(parsedInput, toolContext);
|
|
247
|
+
if (!matchesJsonSchema(output, definition.manifest.outputSchema))
|
|
248
|
+
throw new ToolError('TOOL_OUTPUT_SCHEMA_VIOLATION', `Output does not match the manifest for tool ${name}.`, { retryable: false });
|
|
249
|
+
return output;
|
|
250
|
+
}
|
|
251
|
+
async executeDetailed(name, input, context) {
|
|
252
|
+
const definition = this.require(name);
|
|
253
|
+
const parsedInput = this.validateInput(name, input);
|
|
254
|
+
const toolContext = 'aborted' in context ? { toolCallId: '', effectId: '', attemptId: '', agentId: '', laneId: '', signal: context, emit: () => { } } : context;
|
|
255
|
+
const output = await definition.execute(parsedInput, toolContext);
|
|
256
|
+
if (!matchesJsonSchema(output, definition.manifest.outputSchema))
|
|
257
|
+
throw new ToolError('TOOL_OUTPUT_SCHEMA_VIOLATION', `Output does not match the manifest for tool ${name}.`, { retryable: false });
|
|
258
|
+
const summary = definition.summarize?.(output);
|
|
259
|
+
const summaryAllowed = summary === undefined || summaryWithinBudget(summary, definition.manifest.maxResultSummaryBytes ?? 4096);
|
|
260
|
+
const normalized = definition.normalize?.(output);
|
|
261
|
+
if (normalized !== undefined)
|
|
262
|
+
validateStrictJsonValue(normalized, 'TOOL_NORMALIZED_OUTPUT_INVALID', `Normalized output is not JSON-serializable for tool ${name}.`);
|
|
263
|
+
return { output, ...(normalized === undefined ? {} : { normalized }), ...(summaryAllowed && summary !== undefined ? { summary } : {}), manifest: structuredClone(definition.manifest) };
|
|
264
|
+
}
|
|
265
|
+
async reconcileDetailed(name, executionRef, context) {
|
|
266
|
+
const definition = this.require(name);
|
|
267
|
+
if (!definition.reconcile)
|
|
268
|
+
throw new Error(`TOOL_NOT_RECOVERABLE:${name}`);
|
|
269
|
+
const result = await definition.reconcile(executionRef, context);
|
|
270
|
+
if (!result || typeof result !== 'object' || !['succeeded', 'failed', 'cancelled', 'unknown'].includes(result.status))
|
|
271
|
+
throw new ToolError('TOOL_RECONCILE_RESULT_INVALID', `Reconcile returned an invalid result for tool ${name}.`, { retryable: false });
|
|
272
|
+
if (result.status === 'succeeded' && !matchesJsonSchema(result.output, definition.manifest.outputSchema))
|
|
273
|
+
throw new ToolError('TOOL_RECONCILE_OUTPUT_SCHEMA_VIOLATION', `Reconcile output does not match the manifest for tool ${name}.`, { retryable: false });
|
|
274
|
+
if (result.status === 'succeeded' && result.output !== undefined)
|
|
275
|
+
validateStrictJsonValue(result.output, 'TOOL_RECONCILE_OUTPUT_INVALID', `Reconcile output is not JSON-serializable for tool ${name}.`);
|
|
276
|
+
if (result.error !== undefined && (typeof result.error !== 'object' || result.error === null || typeof result.error.code !== 'string' || typeof result.error.message !== 'string' || (result.error.details !== undefined && !isStrictJsonValue(result.error.details))))
|
|
277
|
+
throw new ToolError('TOOL_RECONCILE_ERROR_INVALID', `Reconcile returned an invalid error for tool ${name}.`, { retryable: false });
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
executionRef(name, input, context) {
|
|
281
|
+
const definition = this.require(name);
|
|
282
|
+
if (!definition.executionRef)
|
|
283
|
+
return undefined;
|
|
284
|
+
return validateStrictJsonValue(definition.executionRef(this.validateInput(name, input), context), 'TOOL_EXECUTION_REF_INVALID', `Execution reference is not JSON-serializable for tool ${name}.`);
|
|
285
|
+
}
|
|
286
|
+
resolveResources(name, input) {
|
|
287
|
+
const definition = this.require(name);
|
|
288
|
+
if (definition.resolveResources)
|
|
289
|
+
return validateResolvedClaims(definition.resolveResources(this.validateInput(name, input)), name);
|
|
290
|
+
if (definition.manifest.resources !== undefined)
|
|
291
|
+
return definition.manifest.resources;
|
|
292
|
+
if (definition.manifest.locks.length > 0 || definition.resourceAdmissionMode === 'explicit')
|
|
293
|
+
return definition.manifest.locks;
|
|
294
|
+
if (definition.manifest.sideEffectPolicy === 'write')
|
|
295
|
+
return [{ resource: 'workspace', mode: 'exclusive' }];
|
|
296
|
+
if (definition.manifest.sideEffectPolicy === 'read')
|
|
297
|
+
return [{ resource: 'workspace', mode: 'shared' }];
|
|
298
|
+
if (definition.manifest.sideEffectPolicy === 'external')
|
|
299
|
+
return [{ resource: `external:${name}`, mode: 'exclusive' }];
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
admission(name, input) { const definition = this.require(name); const parsedInput = this.validateInput(name, input); return { locks: structuredClone(this.resolveResources(name, parsedInput)), sideEffectPolicy: definition.manifest.sideEffectPolicy, defaultTimeoutMs: definition.manifest.defaultTimeoutMs, retrySafety: definition.manifest.retrySafety, version: definition.manifest.version }; }
|
|
303
|
+
require(name) {
|
|
304
|
+
if (!this.isAllowed(name))
|
|
305
|
+
throw new Error(`TOOL_NOT_ALLOWED:${name}`);
|
|
306
|
+
const definition = this.definitions.get(name);
|
|
307
|
+
if (!definition)
|
|
308
|
+
throw new Error(`UNKNOWN_TOOL:${name}`);
|
|
309
|
+
return definition;
|
|
310
|
+
}
|
|
311
|
+
permissionsAllowed(manifest) { return this.permissionReasons(manifest.name).length === 0; }
|
|
312
|
+
}
|
|
313
|
+
function schemaToJsonSchema(schema, seen = new Set()) {
|
|
314
|
+
if (seen.has(schema))
|
|
315
|
+
throw new Error('UNSUPPORTED_SCHEMA_TYPE:recursive');
|
|
316
|
+
const nextSeen = new Set(seen).add(schema);
|
|
317
|
+
const definition = schema._def;
|
|
318
|
+
const typeName = definition.typeName;
|
|
319
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodObject) {
|
|
320
|
+
const shape = typeof definition.shape === 'function' ? definition.shape() : definition.shape;
|
|
321
|
+
if (!shape || typeof shape !== 'object')
|
|
322
|
+
throw new Error('INVALID_SCHEMA_DEFINITION:object');
|
|
323
|
+
const entries = Object.entries(shape);
|
|
324
|
+
const required = entries.filter(([, value]) => !value.isOptional()).map(([key]) => key);
|
|
325
|
+
return {
|
|
326
|
+
type: 'object',
|
|
327
|
+
properties: Object.fromEntries(entries.map(([key, value]) => [key, schemaToJsonSchema(value, nextSeen)])),
|
|
328
|
+
...(required.length ? { required } : {}),
|
|
329
|
+
...(definition.unknownKeys === 'strict' ? { additionalProperties: false } : {}),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodArray)
|
|
333
|
+
return { type: 'array', items: schemaToJsonSchema(definition.type, nextSeen), ...(arrayChecks(definition.checks) ?? {}) };
|
|
334
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodString)
|
|
335
|
+
return { type: 'string', ...(stringChecks(definition.checks) ?? {}) };
|
|
336
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodNumber)
|
|
337
|
+
return { type: definition.isInt ? 'integer' : 'number', ...(numberChecks(definition.checks) ?? {}) };
|
|
338
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodBoolean)
|
|
339
|
+
return { type: 'boolean' };
|
|
340
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodNull)
|
|
341
|
+
return { type: 'null' };
|
|
342
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodOptional || typeName === z.ZodFirstPartyTypeKind.ZodDefault)
|
|
343
|
+
return schemaToJsonSchema(definition.innerType, nextSeen);
|
|
344
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodNullable)
|
|
345
|
+
return { anyOf: [schemaToJsonSchema(definition.innerType, nextSeen), { type: 'null' }] };
|
|
346
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodEnum)
|
|
347
|
+
return { type: 'string', enum: [...definition.values] };
|
|
348
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodNativeEnum)
|
|
349
|
+
return { enum: Object.values(definition.values).filter((value) => typeof value === 'string' || typeof value === 'number') };
|
|
350
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodLiteral)
|
|
351
|
+
return { const: definition.value };
|
|
352
|
+
if (typeName === z.ZodFirstPartyTypeKind.ZodUnion || typeName === z.ZodFirstPartyTypeKind.ZodDiscriminatedUnion) {
|
|
353
|
+
const options = typeName === z.ZodFirstPartyTypeKind.ZodDiscriminatedUnion ? [...definition.options.values()] : definition.options;
|
|
354
|
+
if (!Array.isArray(options) || options.length === 0)
|
|
355
|
+
throw new Error('INVALID_SCHEMA_DEFINITION:union');
|
|
356
|
+
return { anyOf: options.map((option) => schemaToJsonSchema(option, nextSeen)) };
|
|
357
|
+
}
|
|
358
|
+
throw new Error(`UNSUPPORTED_SCHEMA_TYPE:${typeName ?? 'unknown'}`);
|
|
359
|
+
}
|
|
360
|
+
function stringChecks(checks) {
|
|
361
|
+
if (!Array.isArray(checks))
|
|
362
|
+
return undefined;
|
|
363
|
+
const result = {};
|
|
364
|
+
for (const check of checks) {
|
|
365
|
+
if (check.kind === 'min')
|
|
366
|
+
result.minLength = check.value;
|
|
367
|
+
else if (check.kind === 'max')
|
|
368
|
+
result.maxLength = check.value;
|
|
369
|
+
else if (check.kind === 'length') {
|
|
370
|
+
result.minLength = check.value;
|
|
371
|
+
result.maxLength = check.value;
|
|
372
|
+
}
|
|
373
|
+
else if (check.kind === 'regex' && check.regex instanceof RegExp)
|
|
374
|
+
result.pattern = check.regex.source;
|
|
375
|
+
}
|
|
376
|
+
return Object.keys(result).length ? result : undefined;
|
|
377
|
+
}
|
|
378
|
+
function numberChecks(checks) {
|
|
379
|
+
if (!Array.isArray(checks))
|
|
380
|
+
return undefined;
|
|
381
|
+
const result = {};
|
|
382
|
+
for (const check of checks) {
|
|
383
|
+
if (check.kind === 'min')
|
|
384
|
+
result[check.inclusive === false ? 'exclusiveMinimum' : 'minimum'] = check.value;
|
|
385
|
+
else if (check.kind === 'max')
|
|
386
|
+
result[check.inclusive === false ? 'exclusiveMaximum' : 'maximum'] = check.value;
|
|
387
|
+
else if (check.kind === 'int')
|
|
388
|
+
result.type = 'integer';
|
|
389
|
+
}
|
|
390
|
+
return Object.keys(result).length ? result : undefined;
|
|
391
|
+
}
|
|
392
|
+
function arrayChecks(checks) {
|
|
393
|
+
if (!Array.isArray(checks))
|
|
394
|
+
return undefined;
|
|
395
|
+
const result = {};
|
|
396
|
+
for (const check of checks) {
|
|
397
|
+
if (check.kind === 'min')
|
|
398
|
+
result.minItems = check.value;
|
|
399
|
+
else if (check.kind === 'max')
|
|
400
|
+
result.maxItems = check.value;
|
|
401
|
+
else if (check.kind === 'length') {
|
|
402
|
+
result.minItems = check.value;
|
|
403
|
+
result.maxItems = check.value;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return Object.keys(result).length ? result : undefined;
|
|
407
|
+
}
|
|
408
|
+
export function zodToJsonSchema(schema) { return schemaToJsonSchema(schema); }
|
|
409
|
+
export function defineTool(config) {
|
|
410
|
+
const manifest = { name: config.name, version: config.version ?? '1', description: config.description, ...(config.tags === undefined ? {} : { tags: [...new Set(config.tags)] }), inputSchema: zodToJsonSchema(config.input), outputSchema: zodToJsonSchema(config.output), concurrencyClass: config.concurrencyClass ?? 'tool', locks: config.locks ?? [], ...(config.resources === undefined ? {} : { resources: config.resources }), supportsAbortSignal: config.supportsAbortSignal ?? true, sideEffectPolicy: config.sideEffectPolicy ?? 'none', retrySafety: config.retrySafety ?? (config.sideEffectPolicy === 'write' || config.sideEffectPolicy === 'external' ? 'unsafe' : 'read_only'), defaultTimeoutMs: config.defaultTimeoutMs ?? 30_000, ...(config.maxResultSummaryBytes === undefined ? {} : { maxResultSummaryBytes: config.maxResultSummaryBytes }), ...(config.permissions === undefined ? {} : { permissions: structuredClone(config.permissions) }) };
|
|
411
|
+
return { manifest, resourceAdmissionMode: config.resolveResources !== undefined || config.resources !== undefined || config.locks !== undefined ? 'explicit' : 'default', validateInput: (input) => config.input.parse(input), execute: async (input, context) => config.output.parse(await config.execute(config.input.parse(input), context)), ...(config.executionRef === undefined ? {} : { executionRef: (input, context) => config.executionRef(config.input.parse(input), context) }), ...(config.resolveResources === undefined ? {} : { resolveResources: (input) => config.resolveResources(config.input.parse(input)) }), ...(config.reconcile === undefined ? {} : { reconcile: async (executionRef, context) => { const result = await config.reconcile(executionRef, context); return result.status === 'succeeded' && result.output !== undefined ? { ...result, output: config.output.parse(result.output) } : result; } }), ...(config.normalize === undefined ? {} : { normalize: config.normalize }), ...(config.summarize === undefined ? {} : { summarize: (output) => config.summarize(output) }) };
|
|
412
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function matchesJsonSchema(value: unknown, schema: unknown): boolean;
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export function matchesJsonSchema(value, schema) {
|
|
2
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema))
|
|
3
|
+
return false;
|
|
4
|
+
const document = schema;
|
|
5
|
+
if (Array.isArray(document.anyOf) && !document.anyOf.some((candidate) => matchesJsonSchema(value, candidate)))
|
|
6
|
+
return false;
|
|
7
|
+
if (Array.isArray(document.oneOf) && document.oneOf.filter((candidate) => matchesJsonSchema(value, candidate)).length !== 1)
|
|
8
|
+
return false;
|
|
9
|
+
if (Array.isArray(document.allOf) && document.allOf.some((candidate) => !matchesJsonSchema(value, candidate)))
|
|
10
|
+
return false;
|
|
11
|
+
if (document.not !== undefined && matchesJsonSchema(value, document.not))
|
|
12
|
+
return false;
|
|
13
|
+
if (document.const !== undefined && JSON.stringify(value) !== JSON.stringify(document.const))
|
|
14
|
+
return false;
|
|
15
|
+
if (Array.isArray(document.enum) && !document.enum.some((candidate) => JSON.stringify(value) === JSON.stringify(candidate)))
|
|
16
|
+
return false;
|
|
17
|
+
if (typeof document.type === 'string') {
|
|
18
|
+
const matches = document.type === 'null' ? value === null
|
|
19
|
+
: document.type === 'boolean' ? typeof value === 'boolean'
|
|
20
|
+
: document.type === 'number' ? typeof value === 'number' && Number.isFinite(value)
|
|
21
|
+
: document.type === 'integer' ? typeof value === 'number' && Number.isInteger(value)
|
|
22
|
+
: document.type === 'string' ? typeof value === 'string'
|
|
23
|
+
: document.type === 'array' ? Array.isArray(value)
|
|
24
|
+
: document.type === 'object' ? typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
25
|
+
: false;
|
|
26
|
+
if (!matches)
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === 'string') {
|
|
30
|
+
if (typeof document.minLength === 'number' && value.length < document.minLength)
|
|
31
|
+
return false;
|
|
32
|
+
if (typeof document.maxLength === 'number' && value.length > document.maxLength)
|
|
33
|
+
return false;
|
|
34
|
+
if (typeof document.pattern === 'string') {
|
|
35
|
+
try {
|
|
36
|
+
if (!new RegExp(document.pattern).test(value))
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
45
|
+
if (typeof document.minimum === 'number' && value < document.minimum)
|
|
46
|
+
return false;
|
|
47
|
+
if (typeof document.maximum === 'number' && value > document.maximum)
|
|
48
|
+
return false;
|
|
49
|
+
if (typeof document.exclusiveMinimum === 'number' && value <= document.exclusiveMinimum)
|
|
50
|
+
return false;
|
|
51
|
+
if (typeof document.exclusiveMaximum === 'number' && value >= document.exclusiveMaximum)
|
|
52
|
+
return false;
|
|
53
|
+
if (typeof document.multipleOf === 'number' && document.multipleOf > 0 && Math.abs(value / document.multipleOf - Math.round(value / document.multipleOf)) > Number.EPSILON)
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(value)) {
|
|
57
|
+
if (typeof document.minItems === 'number' && value.length < document.minItems)
|
|
58
|
+
return false;
|
|
59
|
+
if (typeof document.maxItems === 'number' && value.length > document.maxItems)
|
|
60
|
+
return false;
|
|
61
|
+
if (document.uniqueItems === true && new Set(value.map((item) => JSON.stringify(item))).size !== value.length)
|
|
62
|
+
return false;
|
|
63
|
+
if (document.items !== undefined && value.some((item) => !matchesJsonSchema(item, document.items)))
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
67
|
+
const object = value;
|
|
68
|
+
if (Array.isArray(document.required) && document.required.some((key) => typeof key !== 'string' || !(key in object)))
|
|
69
|
+
return false;
|
|
70
|
+
if (document.properties && typeof document.properties === 'object' && !Array.isArray(document.properties)) {
|
|
71
|
+
const properties = document.properties;
|
|
72
|
+
for (const [key, childSchema] of Object.entries(properties))
|
|
73
|
+
if (key in object && !matchesJsonSchema(object[key], childSchema))
|
|
74
|
+
return false;
|
|
75
|
+
if (document.additionalProperties === false && Object.keys(object).some((key) => !(key in properties)))
|
|
76
|
+
return false;
|
|
77
|
+
if (document.additionalProperties && typeof document.additionalProperties === 'object' && Object.keys(object).some((key) => !(key in properties) && !matchesJsonSchema(object[key], document.additionalProperties)))
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hunterzhu/pulse-tool-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/zhuhengtan/Pulse"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"registry": "https://registry.npmjs.org"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"zod": "^3.24.1"
|
|
20
|
+
}
|
|
21
|
+
}
|