@convilyn/sdk-author 0.7.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/CHANGELOG.md +280 -0
- package/LICENSE +201 -0
- package/README.md +310 -0
- package/dist/chunk-ZNZXT5ZP.js +1311 -0
- package/dist/chunk-ZNZXT5ZP.js.map +1 -0
- package/dist/cli.cjs +1391 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +471 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +1627 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1158 -0
- package/dist/index.d.ts +1158 -0
- package/dist/index.js +279 -0
- package/dist/index.js.map +1 -0
- package/package.json +84 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { ConfirmationInvalidError, TOOLS_CALL, ConvilynAuthorError } from './chunk-ZNZXT5ZP.js';
|
|
2
|
+
export { ConfirmationInvalidError, ConsoleProgressBackend, ConvilynApiError, ConvilynAuthorError, ConvilynClient, ConvilynManifest, ConvilynStartupError, DEFAULT_HMAC_TOLERANCE_SECONDS, ENV_DEV_INSECURE, GET_TOOL_DATA, InMemoryDataStore, InvalidSignatureError, JSONRPC_VERSION, MANIFEST_SDK_VERSION, OUTPUT_INLINE_THRESHOLD_BYTES, PUBLIC_SCHEMA_VERSION, SDKConfig, SERVER_ID_HEADER, SIGNATURE_HEADER, TIMESTAMP_HEADER, TOOLS_CALL, ToolContext, ToolResult, ToolServer, VERSION, WorkflowSpec, allChecksPassed, createDataStore, createToolContext, devInsecureRequested, failedChecks, generateRefId, runComplianceChecks, serve, signRequest, verifySignature } from './chunk-ZNZXT5ZP.js';
|
|
3
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
4
|
+
import { createHash, createHmac, timingSafeEqual } from 'crypto';
|
|
5
|
+
|
|
6
|
+
// src/policies.ts
|
|
7
|
+
function requireRange(value, min, max, field) {
|
|
8
|
+
if (value === void 0) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (!Number.isFinite(value) || value < min || max !== null && value > max) {
|
|
12
|
+
const bound = max !== null ? `${min}\u2013${max}` : `\u2265 ${min}`;
|
|
13
|
+
throw new ConvilynAuthorError(`${field} must be ${bound} (got ${value})`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function buildWorkflowPolicies(input) {
|
|
17
|
+
const out = {};
|
|
18
|
+
if (input.retry) {
|
|
19
|
+
requireRange(input.retry.maxAttempts, 1, 10, "retry.maxAttempts");
|
|
20
|
+
requireRange(input.retry.backoffMs, 0, null, "retry.backoffMs");
|
|
21
|
+
out.retry = { ...input.retry };
|
|
22
|
+
}
|
|
23
|
+
if (input.timeout) {
|
|
24
|
+
requireRange(input.timeout.maxStepCount, 1, null, "timeout.maxStepCount");
|
|
25
|
+
requireRange(input.timeout.maxWallClockSeconds, 1, null, "timeout.maxWallClockSeconds");
|
|
26
|
+
out.timeout = { ...input.timeout };
|
|
27
|
+
}
|
|
28
|
+
if (input.outputValidation) {
|
|
29
|
+
requireRange(input.outputValidation.minLengthChars, 0, null, "outputValidation.minLengthChars");
|
|
30
|
+
out.outputValidation = { ...input.outputValidation };
|
|
31
|
+
}
|
|
32
|
+
if (input.humanReview) {
|
|
33
|
+
out.humanReview = { ...input.humanReview };
|
|
34
|
+
}
|
|
35
|
+
if (input.fallback) {
|
|
36
|
+
out.fallback = { ...input.fallback };
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function isRecord(value) {
|
|
41
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
42
|
+
}
|
|
43
|
+
function compileInputSchema(schema) {
|
|
44
|
+
const generated = zodToJsonSchema(schema, { $refStrategy: "none", target: "jsonSchema7" });
|
|
45
|
+
const json = isRecord(generated) ? { ...generated } : {};
|
|
46
|
+
delete json.$schema;
|
|
47
|
+
delete json.$ref;
|
|
48
|
+
if (json.type === "object") {
|
|
49
|
+
json.additionalProperties = false;
|
|
50
|
+
}
|
|
51
|
+
return json;
|
|
52
|
+
}
|
|
53
|
+
function defineTool(def) {
|
|
54
|
+
if (!def.name) {
|
|
55
|
+
throw new Error("defineTool: name is required");
|
|
56
|
+
}
|
|
57
|
+
const jsonSchema = compileInputSchema(def.input);
|
|
58
|
+
const handler = def.handler;
|
|
59
|
+
return {
|
|
60
|
+
name: def.name,
|
|
61
|
+
description: def.description,
|
|
62
|
+
idempotent: def.idempotent,
|
|
63
|
+
inputSchema: def.input,
|
|
64
|
+
jsonSchema,
|
|
65
|
+
outputSchema: def.outputSchema,
|
|
66
|
+
handler,
|
|
67
|
+
toSpec() {
|
|
68
|
+
const spec = {
|
|
69
|
+
name: def.name,
|
|
70
|
+
description: def.description,
|
|
71
|
+
inputSchema: jsonSchema,
|
|
72
|
+
idempotent: def.idempotent
|
|
73
|
+
};
|
|
74
|
+
if (def.outputSchema != null) {
|
|
75
|
+
spec.outputSchema = def.outputSchema;
|
|
76
|
+
}
|
|
77
|
+
return spec;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
var CONFIRMATION_TTL_SECONDS = 300;
|
|
82
|
+
var CONFIRMATION_FIELD = "confirmation_token";
|
|
83
|
+
var VOLATILE_SIGNED_URL_PARAMS = /* @__PURE__ */ new Set([
|
|
84
|
+
// S3 SigV4
|
|
85
|
+
"x-amz-algorithm",
|
|
86
|
+
"x-amz-credential",
|
|
87
|
+
"x-amz-date",
|
|
88
|
+
"x-amz-expires",
|
|
89
|
+
"x-amz-signature",
|
|
90
|
+
"x-amz-signedheaders",
|
|
91
|
+
"x-amz-security-token",
|
|
92
|
+
// S3 SigV2 (legacy presign)
|
|
93
|
+
"awsaccesskeyid",
|
|
94
|
+
"signature",
|
|
95
|
+
"expires",
|
|
96
|
+
// CloudFront signed URLs
|
|
97
|
+
"key-pair-id",
|
|
98
|
+
"policy"
|
|
99
|
+
]);
|
|
100
|
+
var ALWAYS_SAFE = /^[A-Za-z0-9_.~-]$/;
|
|
101
|
+
function quotePlus(value) {
|
|
102
|
+
let out = "";
|
|
103
|
+
for (const byte of Buffer.from(value, "utf8")) {
|
|
104
|
+
const ch = String.fromCharCode(byte);
|
|
105
|
+
if (ALWAYS_SAFE.test(ch)) {
|
|
106
|
+
out += ch;
|
|
107
|
+
} else if (ch === " ") {
|
|
108
|
+
out += "+";
|
|
109
|
+
} else {
|
|
110
|
+
out += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
function unquotePlus(value) {
|
|
116
|
+
const bytes = [];
|
|
117
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
118
|
+
const ch = value[i];
|
|
119
|
+
if (ch === "+") {
|
|
120
|
+
bytes.push(32);
|
|
121
|
+
} else if (ch === "%" && i + 2 < value.length) {
|
|
122
|
+
bytes.push(parseInt(value.slice(i + 1, i + 3), 16));
|
|
123
|
+
i += 2;
|
|
124
|
+
} else {
|
|
125
|
+
bytes.push(ch.charCodeAt(0));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return Buffer.from(bytes).toString("utf8");
|
|
129
|
+
}
|
|
130
|
+
function stripVolatileParams(url) {
|
|
131
|
+
const queryStart = url.indexOf("?");
|
|
132
|
+
const hashStart = url.indexOf("#", queryStart);
|
|
133
|
+
const base = url.slice(0, queryStart);
|
|
134
|
+
const query = url.slice(queryStart + 1, hashStart === -1 ? void 0 : hashStart);
|
|
135
|
+
const fragment = hashStart === -1 ? "" : url.slice(hashStart);
|
|
136
|
+
const kept = [];
|
|
137
|
+
for (const pair of query.split("&")) {
|
|
138
|
+
if (pair === "") {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const eq = pair.indexOf("=");
|
|
142
|
+
const rawKey = eq === -1 ? pair : pair.slice(0, eq);
|
|
143
|
+
const rawValue = eq === -1 ? "" : pair.slice(eq + 1);
|
|
144
|
+
const key = unquotePlus(rawKey);
|
|
145
|
+
if (VOLATILE_SIGNED_URL_PARAMS.has(key.toLowerCase())) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
kept.push(`${quotePlus(key)}=${quotePlus(unquotePlus(rawValue))}`);
|
|
149
|
+
}
|
|
150
|
+
const rebuiltQuery = kept.join("&");
|
|
151
|
+
return rebuiltQuery === "" ? `${base}${fragment}` : `${base}?${rebuiltQuery}${fragment}`;
|
|
152
|
+
}
|
|
153
|
+
function normalizeForDigest(value) {
|
|
154
|
+
if (Array.isArray(value)) {
|
|
155
|
+
return value.map(normalizeForDigest);
|
|
156
|
+
}
|
|
157
|
+
if (value !== null && typeof value === "object") {
|
|
158
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
159
|
+
for (const [k, v] of Object.entries(value)) {
|
|
160
|
+
out[k] = normalizeForDigest(v);
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
if (typeof value === "string" && value.includes("://") && value.includes("?")) {
|
|
165
|
+
return stripVolatileParams(value);
|
|
166
|
+
}
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
function deepSortKeys(value) {
|
|
170
|
+
if (Array.isArray(value)) {
|
|
171
|
+
return value.map(deepSortKeys);
|
|
172
|
+
}
|
|
173
|
+
if (value !== null && typeof value === "object") {
|
|
174
|
+
const source = value;
|
|
175
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
176
|
+
for (const key of Object.keys(source).sort()) {
|
|
177
|
+
out[key] = deepSortKeys(source[key]);
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
function canonicalJson(value) {
|
|
184
|
+
const compact = JSON.stringify(deepSortKeys(value));
|
|
185
|
+
return compact.replace(
|
|
186
|
+
/[\u0080-\uffff]/g,
|
|
187
|
+
(c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
function payloadDigest(args) {
|
|
191
|
+
return createHash("sha256").update(canonicalJson(normalizeForDigest(args)), "utf8").digest("hex");
|
|
192
|
+
}
|
|
193
|
+
function sign(secret, toolName, expires, digest) {
|
|
194
|
+
return createHmac("sha256", secret).update(`${toolName}|${expires}|${digest}`, "utf8").digest("hex");
|
|
195
|
+
}
|
|
196
|
+
function base64urlEncode(raw) {
|
|
197
|
+
return Buffer.from(raw, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_");
|
|
198
|
+
}
|
|
199
|
+
function base64urlDecode(token) {
|
|
200
|
+
return Buffer.from(token.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8");
|
|
201
|
+
}
|
|
202
|
+
function constantTimeEqual(a, b) {
|
|
203
|
+
const aBuf = Buffer.from(a);
|
|
204
|
+
const bBuf = Buffer.from(b);
|
|
205
|
+
return aBuf.length === bBuf.length && timingSafeEqual(aBuf, bBuf);
|
|
206
|
+
}
|
|
207
|
+
function mintConfirmationToken(input) {
|
|
208
|
+
if (!input.secret) {
|
|
209
|
+
throw new ConfirmationInvalidError("malformed", "confirmation secret is not configured");
|
|
210
|
+
}
|
|
211
|
+
const digest = payloadDigest(input.arguments);
|
|
212
|
+
const sig = sign(input.secret, input.toolName, input.expiresAtUnix, digest);
|
|
213
|
+
return base64urlEncode(`${input.expiresAtUnix}:${digest}:${sig}`);
|
|
214
|
+
}
|
|
215
|
+
function verifyConfirmationToken(input) {
|
|
216
|
+
if (!input.secret) {
|
|
217
|
+
throw new ConfirmationInvalidError("malformed", "confirmation secret is not configured");
|
|
218
|
+
}
|
|
219
|
+
const now = input.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
220
|
+
let expiresStr;
|
|
221
|
+
let digest;
|
|
222
|
+
let sig;
|
|
223
|
+
try {
|
|
224
|
+
;
|
|
225
|
+
[expiresStr, digest, sig] = base64urlDecode(input.token).split(":");
|
|
226
|
+
} catch {
|
|
227
|
+
throw new ConfirmationInvalidError("malformed", "confirmation token could not be decoded");
|
|
228
|
+
}
|
|
229
|
+
if (expiresStr === void 0 || digest === void 0 || sig === void 0) {
|
|
230
|
+
throw new ConfirmationInvalidError("malformed", "confirmation token is malformed");
|
|
231
|
+
}
|
|
232
|
+
if (!/^\d+$/.test(expiresStr)) {
|
|
233
|
+
throw new ConfirmationInvalidError("malformed", "confirmation token has a non-integer expiry");
|
|
234
|
+
}
|
|
235
|
+
const expiresAtUnix = Number(expiresStr);
|
|
236
|
+
if (expiresAtUnix < now()) {
|
|
237
|
+
throw new ConfirmationInvalidError("expired", "confirmation token expired");
|
|
238
|
+
}
|
|
239
|
+
const stripped = /* @__PURE__ */ Object.create(null);
|
|
240
|
+
for (const [k, v] of Object.entries(input.arguments)) {
|
|
241
|
+
if (k !== CONFIRMATION_FIELD) {
|
|
242
|
+
stripped[k] = v;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!constantTimeEqual(digest, payloadDigest(stripped))) {
|
|
246
|
+
throw new ConfirmationInvalidError(
|
|
247
|
+
"digest_mismatch",
|
|
248
|
+
"confirmation token does not match arguments"
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const expectedSig = sign(input.secret, input.toolName, expiresAtUnix, digest);
|
|
252
|
+
if (!constantTimeEqual(sig, expectedSig)) {
|
|
253
|
+
throw new ConfirmationInvalidError("signature_mismatch", "confirmation token signature invalid");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/harness.ts
|
|
258
|
+
async function invokeTool(server, toolName, args = {}, options = {}) {
|
|
259
|
+
const response = await server.dispatch({
|
|
260
|
+
jsonrpc: "2.0",
|
|
261
|
+
method: TOOLS_CALL,
|
|
262
|
+
params: {
|
|
263
|
+
name: toolName,
|
|
264
|
+
arguments: args,
|
|
265
|
+
context: options.requestId !== void 0 ? { request_id: options.requestId } : {}
|
|
266
|
+
},
|
|
267
|
+
id: "harness"
|
|
268
|
+
});
|
|
269
|
+
if (response.error) {
|
|
270
|
+
throw new ConvilynAuthorError(
|
|
271
|
+
`invokeTool(${toolName}) failed: [${response.error.code}] ${response.error.message}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
return response.result;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export { CONFIRMATION_TTL_SECONDS, buildWorkflowPolicies, defineTool, invokeTool, mintConfirmationToken, normalizeForDigest, verifyConfirmationToken };
|
|
278
|
+
//# sourceMappingURL=index.js.map
|
|
279
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/policies.ts","../src/tool.ts","../src/confirmation.ts","../src/harness.ts"],"names":[],"mappings":";;;;;;AAiEA,SAAS,YAAA,CACP,KAAA,EACA,GAAA,EACA,GAAA,EACA,KAAA,EACM;AACN,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA;AAAA,EACF;AACA,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,IAAK,QAAQ,GAAA,IAAQ,GAAA,KAAQ,IAAA,IAAQ,KAAA,GAAQ,GAAA,EAAM;AAC3E,IAAA,MAAM,KAAA,GAAQ,QAAQ,IAAA,GAAO,CAAA,EAAG,GAAG,CAAA,MAAA,EAAI,GAAG,CAAA,CAAA,GAAK,CAAA,OAAA,EAAK,GAAG,CAAA,CAAA;AACvD,IAAA,MAAM,IAAI,oBAAoB,CAAA,EAAG,KAAK,YAAY,KAAK,CAAA,MAAA,EAAS,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,EAC1E;AACF;AAQO,SAAS,sBAAsB,KAAA,EAA2C;AAC/E,EAAA,MAAM,MAAwB,EAAC;AAC/B,EAAA,IAAI,MAAM,KAAA,EAAO;AACf,IAAA,YAAA,CAAa,KAAA,CAAM,KAAA,CAAM,WAAA,EAAa,CAAA,EAAG,IAAI,mBAAmB,CAAA;AAChE,IAAA,YAAA,CAAa,KAAA,CAAM,KAAA,CAAM,SAAA,EAAW,CAAA,EAAG,MAAM,iBAAiB,CAAA;AAC9D,IAAA,GAAA,CAAI,KAAA,GAAQ,EAAE,GAAG,KAAA,CAAM,KAAA,EAAM;AAAA,EAC/B;AACA,EAAA,IAAI,MAAM,OAAA,EAAS;AACjB,IAAA,YAAA,CAAa,KAAA,CAAM,OAAA,CAAQ,YAAA,EAAc,CAAA,EAAG,MAAM,sBAAsB,CAAA;AACxE,IAAA,YAAA,CAAa,KAAA,CAAM,OAAA,CAAQ,mBAAA,EAAqB,CAAA,EAAG,MAAM,6BAA6B,CAAA;AACtF,IAAA,GAAA,CAAI,OAAA,GAAU,EAAE,GAAG,KAAA,CAAM,OAAA,EAAQ;AAAA,EACnC;AACA,EAAA,IAAI,MAAM,gBAAA,EAAkB;AAC1B,IAAA,YAAA,CAAa,KAAA,CAAM,gBAAA,CAAiB,cAAA,EAAgB,CAAA,EAAG,MAAM,iCAAiC,CAAA;AAC9F,IAAA,GAAA,CAAI,gBAAA,GAAmB,EAAE,GAAG,KAAA,CAAM,gBAAA,EAAiB;AAAA,EACrD;AACA,EAAA,IAAI,MAAM,WAAA,EAAa;AACrB,IAAA,GAAA,CAAI,WAAA,GAAc,EAAE,GAAG,KAAA,CAAM,WAAA,EAAY;AAAA,EAC3C;AACA,EAAA,IAAI,MAAM,QAAA,EAAU;AAClB,IAAA,GAAA,CAAI,QAAA,GAAW,EAAE,GAAG,KAAA,CAAM,QAAA,EAAS;AAAA,EACrC;AACA,EAAA,OAAO,GAAA;AACT;AC3DA,SAAS,SAAS,KAAA,EAAkD;AAClE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AAQA,SAAS,mBAAmB,MAAA,EAA6C;AACvE,EAAA,MAAM,SAAA,GAAY,gBAAgB,MAAA,EAAQ,EAAE,cAAc,MAAA,EAAQ,MAAA,EAAQ,eAAe,CAAA;AACzF,EAAA,MAAM,IAAA,GAAgC,SAAS,SAAS,CAAA,GAAI,EAAE,GAAG,SAAA,KAAc,EAAC;AAChF,EAAA,OAAO,IAAA,CAAK,OAAA;AACZ,EAAA,OAAO,IAAA,CAAK,IAAA;AACZ,EAAA,IAAI,IAAA,CAAK,SAAS,QAAA,EAAU;AAC1B,IAAA,IAAA,CAAK,oBAAA,GAAuB,KAAA;AAAA,EAC9B;AACA,EAAA,OAAO,IAAA;AACT;AAOO,SAAS,WAAiC,GAAA,EAA6C;AAC5F,EAAA,IAAI,CAAC,IAAI,IAAA,EAAM;AACb,IAAA,MAAM,IAAI,MAAM,8BAA8B,CAAA;AAAA,EAChD;AACA,EAAA,MAAM,UAAA,GAAa,kBAAA,CAAmB,GAAA,CAAI,KAAK,CAAA;AAC/C,EAAA,MAAM,UAAU,GAAA,CAAI,OAAA;AACpB,EAAA,OAAO;AAAA,IACL,MAAM,GAAA,CAAI,IAAA;AAAA,IACV,aAAa,GAAA,CAAI,WAAA;AAAA,IACjB,YAAY,GAAA,CAAI,UAAA;AAAA,IAChB,aAAa,GAAA,CAAI,KAAA;AAAA,IACjB,UAAA;AAAA,IACA,cAAc,GAAA,CAAI,YAAA;AAAA,IAClB,OAAA;AAAA,IACA,MAAA,GAAmB;AACjB,MAAA,MAAM,IAAA,GAAiB;AAAA,QACrB,MAAM,GAAA,CAAI,IAAA;AAAA,QACV,aAAa,GAAA,CAAI,WAAA;AAAA,QACjB,WAAA,EAAa,UAAA;AAAA,QACb,YAAY,GAAA,CAAI;AAAA,OAClB;AACA,MAAA,IAAI,GAAA,CAAI,gBAAgB,IAAA,EAAM;AAC5B,QAAA,IAAA,CAAK,eAAe,GAAA,CAAI,YAAA;AAAA,MAC1B;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,GACF;AACF;AC7EO,IAAM,wBAAA,GAA2B;AAGxC,IAAM,kBAAA,GAAqB,oBAAA;AAG3B,IAAM,0BAAA,uBAAiC,GAAA,CAAI;AAAA;AAAA,EAEzC,iBAAA;AAAA,EACA,kBAAA;AAAA,EACA,YAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,qBAAA;AAAA,EACA,sBAAA;AAAA;AAAA,EAEA,gBAAA;AAAA,EACA,WAAA;AAAA,EACA,SAAA;AAAA;AAAA,EAEA,aAAA;AAAA,EACA;AACF,CAAC,CAAA;AAED,IAAM,WAAA,GAAc,mBAAA;AAGpB,SAAS,UAAU,KAAA,EAAuB;AACxC,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,IAAA,CAAK,KAAA,EAAO,MAAM,CAAA,EAAG;AAC7C,IAAA,MAAM,EAAA,GAAK,MAAA,CAAO,YAAA,CAAa,IAAI,CAAA;AACnC,IAAA,IAAI,WAAA,CAAY,IAAA,CAAK,EAAE,CAAA,EAAG;AACxB,MAAA,GAAA,IAAO,EAAA;AAAA,IACT,CAAA,MAAA,IAAW,OAAO,GAAA,EAAK;AACrB,MAAA,GAAA,IAAO,GAAA;AAAA,IACT,CAAA,MAAO;AACL,MAAA,GAAA,IAAO,CAAA,CAAA,EAAI,IAAA,CAAK,QAAA,CAAS,EAAE,CAAA,CAAE,aAAY,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAAA,IAC7D;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAGA,SAAS,YAAY,KAAA,EAAuB;AAC1C,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,CAAA,EAAG;AACxC,IAAA,MAAM,EAAA,GAAK,MAAM,CAAC,CAAA;AAClB,IAAA,IAAI,OAAO,GAAA,EAAK;AACd,MAAA,KAAA,CAAM,KAAK,EAAI,CAAA;AAAA,IACjB,WAAW,EAAA,KAAO,GAAA,IAAO,CAAA,GAAI,CAAA,GAAI,MAAM,MAAA,EAAQ;AAC7C,MAAA,KAAA,CAAM,IAAA,CAAK,QAAA,CAAS,KAAA,CAAM,KAAA,CAAM,CAAA,GAAI,GAAG,CAAA,GAAI,CAAC,CAAA,EAAG,EAAE,CAAC,CAAA;AAClD,MAAA,CAAA,IAAK,CAAA;AAAA,IACP,CAAA,MAAO;AACL,MAAA,KAAA,CAAM,IAAA,CAAK,EAAA,CAAG,UAAA,CAAW,CAAC,CAAC,CAAA;AAAA,IAC7B;AAAA,EACF;AACA,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,CAAE,SAAS,MAAM,CAAA;AAC3C;AAGA,SAAS,oBAAoB,GAAA,EAAqB;AAChD,EAAA,MAAM,UAAA,GAAa,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAClC,EAAA,MAAM,SAAA,GAAY,GAAA,CAAI,OAAA,CAAQ,GAAA,EAAK,UAAU,CAAA;AAC7C,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,UAAU,CAAA;AACpC,EAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,CAAM,UAAA,GAAa,GAAG,SAAA,KAAc,EAAA,GAAK,SAAY,SAAS,CAAA;AAChF,EAAA,MAAM,WAAW,SAAA,KAAc,EAAA,GAAK,EAAA,GAAK,GAAA,CAAI,MAAM,SAAS,CAAA;AAE5D,EAAA,MAAM,OAAiB,EAAC;AACxB,EAAA,KAAA,MAAW,IAAA,IAAQ,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA,EAAG;AACnC,IAAA,IAAI,SAAS,EAAA,EAAI;AACf,MAAA;AAAA,IACF;AACA,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAC3B,IAAA,MAAM,SAAS,EAAA,KAAO,EAAA,GAAK,OAAO,IAAA,CAAK,KAAA,CAAM,GAAG,EAAE,CAAA;AAClD,IAAA,MAAM,WAAW,EAAA,KAAO,EAAA,GAAK,KAAK,IAAA,CAAK,KAAA,CAAM,KAAK,CAAC,CAAA;AACnD,IAAA,MAAM,GAAA,GAAM,YAAY,MAAM,CAAA;AAC9B,IAAA,IAAI,0BAAA,CAA2B,GAAA,CAAI,GAAA,CAAI,WAAA,EAAa,CAAA,EAAG;AACrD,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,SAAA,CAAU,GAAG,CAAC,CAAA,CAAA,EAAI,SAAA,CAAU,WAAA,CAAY,QAAQ,CAAC,CAAC,CAAA,CAAE,CAAA;AAAA,EACnE;AAEA,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,IAAA,CAAK,GAAG,CAAA;AAClC,EAAA,OAAO,YAAA,KAAiB,EAAA,GAAK,CAAA,EAAG,IAAI,CAAA,EAAG,QAAQ,CAAA,CAAA,GAAK,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,YAAY,CAAA,EAAG,QAAQ,CAAA,CAAA;AACxF;AAGO,SAAS,mBAAmB,KAAA,EAAyB;AAC1D,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,IAAA,OAAO,KAAA,CAAM,IAAI,kBAAkB,CAAA;AAAA,EACrC;AACA,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,QAAA,EAAU;AAI/C,IAAA,MAAM,GAAA,mBAA+B,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA;AACvD,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC1C,MAAA,GAAA,CAAI,CAAC,CAAA,GAAI,kBAAA,CAAmB,CAAC,CAAA;AAAA,IAC/B;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,QAAA,CAAS,KAAK,CAAA,IAAK,KAAA,CAAM,QAAA,CAAS,GAAG,CAAA,EAAG;AAC7E,IAAA,OAAO,oBAAoB,KAAK,CAAA;AAAA,EAClC;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,aAAa,KAAA,EAAyB;AAC7C,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,IAAA,OAAO,KAAA,CAAM,IAAI,YAAY,CAAA;AAAA,EAC/B;AACA,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,QAAA,EAAU;AAC/C,IAAA,MAAM,MAAA,GAAS,KAAA;AAGf,IAAA,MAAM,GAAA,mBAA+B,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA;AACvD,IAAA,KAAA,MAAW,OAAO,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,CAAE,MAAK,EAAG;AAC5C,MAAA,GAAA,CAAI,GAAG,CAAA,GAAI,YAAA,CAAa,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,IACrC;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA;AACT;AAGA,SAAS,cAAc,KAAA,EAAwB;AAC7C,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,SAAA,CAAU,YAAA,CAAa,KAAK,CAAC,CAAA;AAElD,EAAA,OAAO,OAAA,CAAQ,OAAA;AAAA,IACb,kBAAA;AAAA,IACA,CAAC,CAAA,KAAM,CAAA,GAAA,EAAM,CAAA,CAAE,UAAA,CAAW,CAAC,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA;AAAA,GAC5D;AACF;AAEA,SAAS,cAAc,IAAA,EAAuC;AAC5D,EAAA,OAAO,UAAA,CAAW,QAAQ,CAAA,CACvB,MAAA,CAAO,aAAA,CAAc,kBAAA,CAAmB,IAAI,CAAC,CAAA,EAAG,MAAM,CAAA,CACtD,MAAA,CAAO,KAAK,CAAA;AACjB;AAEA,SAAS,IAAA,CAAK,MAAA,EAAgB,QAAA,EAAkB,OAAA,EAAiB,MAAA,EAAwB;AACvF,EAAA,OAAO,UAAA,CAAW,QAAA,EAAU,MAAM,CAAA,CAC/B,OAAO,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,OAAO,IAAI,MAAM,CAAA,CAAA,EAAI,MAAM,CAAA,CACjD,OAAO,KAAK,CAAA;AACjB;AAEA,SAAS,gBAAgB,GAAA,EAAqB;AAE5C,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,GAAA,EAAK,MAAM,EAAE,QAAA,CAAS,QAAQ,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAO,GAAG,CAAA;AAC3F;AAEA,SAAS,gBAAgB,KAAA,EAAuB;AAC9C,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,MAAM,GAAG,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA,EAAG,QAAQ,CAAA,CAAE,SAAS,MAAM,CAAA;AAC3F;AAEA,SAAS,iBAAA,CAAkB,GAAW,CAAA,EAAoB;AACxD,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA;AAC1B,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA;AAC1B,EAAA,OAAO,KAAK,MAAA,KAAW,IAAA,CAAK,MAAA,IAAU,eAAA,CAAgB,MAAM,IAAI,CAAA;AAClE;AAeO,SAAS,sBAAsB,KAAA,EAAuC;AAC3E,EAAA,IAAI,CAAC,MAAM,MAAA,EAAQ;AACjB,IAAA,MAAM,IAAI,wBAAA,CAAyB,WAAA,EAAa,uCAAuC,CAAA;AAAA,EACzF;AACA,EAAA,MAAM,MAAA,GAAS,aAAA,CAAc,KAAA,CAAM,SAAS,CAAA;AAC5C,EAAA,MAAM,GAAA,GAAM,KAAK,KAAA,CAAM,MAAA,EAAQ,MAAM,QAAA,EAAU,KAAA,CAAM,eAAe,MAAM,CAAA;AAC1E,EAAA,OAAO,eAAA,CAAgB,GAAG,KAAA,CAAM,aAAa,IAAI,MAAM,CAAA,CAAA,EAAI,GAAG,CAAA,CAAE,CAAA;AAClE;AAkBO,SAAS,wBAAwB,KAAA,EAAsC;AAC5E,EAAA,IAAI,CAAC,MAAM,MAAA,EAAQ;AACjB,IAAA,MAAM,IAAI,wBAAA,CAAyB,WAAA,EAAa,uCAAuC,CAAA;AAAA,EACzF;AACA,EAAA,MAAM,GAAA,GAAM,MAAM,GAAA,KAAQ,MAAM,KAAK,KAAA,CAAM,IAAA,CAAK,GAAA,EAAI,GAAI,GAAI,CAAA,CAAA;AAE5D,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA;AAAC,IAAA,CAAC,UAAA,EAAY,QAAQ,GAAG,CAAA,GAAI,gBAAgB,KAAA,CAAM,KAAK,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA;AAAA,EACrE,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,wBAAA,CAAyB,WAAA,EAAa,yCAAyC,CAAA;AAAA,EAC3F;AACA,EAAA,IAAI,UAAA,KAAe,MAAA,IAAa,MAAA,KAAW,MAAA,IAAa,QAAQ,MAAA,EAAW;AACzE,IAAA,MAAM,IAAI,wBAAA,CAAyB,WAAA,EAAa,iCAAiC,CAAA;AAAA,EACnF;AACA,EAAA,IAAI,CAAC,OAAA,CAAQ,IAAA,CAAK,UAAU,CAAA,EAAG;AAC7B,IAAA,MAAM,IAAI,wBAAA,CAAyB,WAAA,EAAa,6CAA6C,CAAA;AAAA,EAC/F;AACA,EAAA,MAAM,aAAA,GAAgB,OAAO,UAAU,CAAA;AAEvC,EAAA,IAAI,aAAA,GAAgB,KAAI,EAAG;AACzB,IAAA,MAAM,IAAI,wBAAA,CAAyB,SAAA,EAAW,4BAA4B,CAAA;AAAA,EAC5E;AAIA,EAAA,MAAM,QAAA,mBAAoC,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA;AAC5D,EAAA,KAAA,MAAW,CAAC,GAAG,CAAC,CAAA,IAAK,OAAO,OAAA,CAAQ,KAAA,CAAM,SAAS,CAAA,EAAG;AACpD,IAAA,IAAI,MAAM,kBAAA,EAAoB;AAC5B,MAAA,QAAA,CAAS,CAAC,CAAA,GAAI,CAAA;AAAA,IAChB;AAAA,EACF;AACA,EAAA,IAAI,CAAC,iBAAA,CAAkB,MAAA,EAAQ,aAAA,CAAc,QAAQ,CAAC,CAAA,EAAG;AACvD,IAAA,MAAM,IAAI,wBAAA;AAAA,MACR,iBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,MAAM,cAAc,IAAA,CAAK,KAAA,CAAM,QAAQ,KAAA,CAAM,QAAA,EAAU,eAAe,MAAM,CAAA;AAC5E,EAAA,IAAI,CAAC,iBAAA,CAAkB,GAAA,EAAK,WAAW,CAAA,EAAG;AACxC,IAAA,MAAM,IAAI,wBAAA,CAAyB,oBAAA,EAAsB,sCAAsC,CAAA;AAAA,EACjG;AACF;;;AClPA,eAAsB,UAAA,CACpB,QACA,QAAA,EACA,IAAA,GAAgC,EAAC,EACjC,OAAA,GAA6B,EAAC,EACL;AACzB,EAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,QAAA,CAAS;AAAA,IACrC,OAAA,EAAS,KAAA;AAAA,IACT,MAAA,EAAQ,UAAA;AAAA,IACR,MAAA,EAAQ;AAAA,MACN,IAAA,EAAM,QAAA;AAAA,MACN,SAAA,EAAW,IAAA;AAAA,MACX,OAAA,EAAS,QAAQ,SAAA,KAAc,MAAA,GAAY,EAAE,UAAA,EAAY,OAAA,CAAQ,SAAA,EAAU,GAAI;AAAC,KAClF;AAAA,IACA,EAAA,EAAI;AAAA,GACL,CAAA;AACD,EAAA,IAAI,SAAS,KAAA,EAAO;AAClB,IAAA,MAAM,IAAI,mBAAA;AAAA,MACR,CAAA,WAAA,EAAc,QAAQ,CAAA,WAAA,EAAc,QAAA,CAAS,MAAM,IAAI,CAAA,EAAA,EAAK,QAAA,CAAS,KAAA,CAAM,OAAO,CAAA;AAAA,KACpF;AAAA,EACF;AACA,EAAA,OAAO,QAAA,CAAS,MAAA;AAClB","file":"index.js","sourcesContent":["/**\n * The five high-level workflow policies an author may attach to a published\n * workflow. Grounded in `docs/contracts/sdk_public_openapi.yaml`\n * (§WorkflowPolicies) — camelCase wire, every field optional. These attach to a\n * community-workflow patch (the platform client, a later increment), so they are\n * standalone here and are NOT folded into {@link WorkflowSpec.compile}.\n */\n\nimport { ConvilynAuthorError } from './errors'\n\n/** Failure categories that trigger a retry. */\nexport type RetryCategory = 'transient' | 'rate_limited' | 'tool_error'\n\n/** When and how a failed step is retried. */\nexport interface RetryPolicy {\n /** 1–10. */\n maxAttempts?: number\n /** ≥ 0. */\n backoffMs?: number\n retryOn?: RetryCategory[]\n}\n\n/** Per-workflow wall-clock + step-count bound. */\nexport interface TimeoutPolicy {\n /** ≥ 1. */\n maxStepCount?: number\n /** ≥ 1. */\n maxWallClockSeconds?: number\n}\n\n/** Structural + quality checks the final artefact must pass. */\nexport interface OutputValidationPolicy {\n requiredSections?: string[]\n /** ≥ 0. */\n minLengthChars?: number\n qualityRubric?: string\n}\n\n/** Situations in which the workflow must pause for a human answer. */\nexport type PauseTrigger = 'ambiguity' | 'high_cost' | 'low_confidence'\n\n/** When the workflow must pause for a human. */\nexport interface HumanReviewPolicy {\n pauseFor?: PauseTrigger[]\n slotPromptStyle?: 'terse' | 'verbose'\n}\n\n/** What to do when retries are exhausted or output validation fails. */\nexport type FallbackAction = 'abort' | 'return_partial' | 'escalate'\n\n/** Behaviour after retries are exhausted / validation fails. */\nexport interface FallbackPolicy {\n action?: FallbackAction\n escalationContact?: string\n}\n\n/** The five policies; declare only those you want to constrain. */\nexport interface WorkflowPolicies {\n retry?: RetryPolicy\n timeout?: TimeoutPolicy\n outputValidation?: OutputValidationPolicy\n humanReview?: HumanReviewPolicy\n fallback?: FallbackPolicy\n}\n\nfunction requireRange(\n value: number | undefined,\n min: number,\n max: number | null,\n field: string\n): void {\n if (value === undefined) {\n return\n }\n if (!Number.isFinite(value) || value < min || (max !== null && value > max)) {\n const bound = max !== null ? `${min}–${max}` : `≥ ${min}`\n throw new ConvilynAuthorError(`${field} must be ${bound} (got ${value})`)\n }\n}\n\n/**\n * Validate + normalise a {@link WorkflowPolicies} object: range-checks the\n * numeric bounds from the contract and returns a fresh object containing only\n * the sub-policies that were supplied. Throws {@link ConvilynAuthorError} on an\n * out-of-range value.\n */\nexport function buildWorkflowPolicies(input: WorkflowPolicies): WorkflowPolicies {\n const out: WorkflowPolicies = {}\n if (input.retry) {\n requireRange(input.retry.maxAttempts, 1, 10, 'retry.maxAttempts')\n requireRange(input.retry.backoffMs, 0, null, 'retry.backoffMs')\n out.retry = { ...input.retry }\n }\n if (input.timeout) {\n requireRange(input.timeout.maxStepCount, 1, null, 'timeout.maxStepCount')\n requireRange(input.timeout.maxWallClockSeconds, 1, null, 'timeout.maxWallClockSeconds')\n out.timeout = { ...input.timeout }\n }\n if (input.outputValidation) {\n requireRange(input.outputValidation.minLengthChars, 0, null, 'outputValidation.minLengthChars')\n out.outputValidation = { ...input.outputValidation }\n }\n if (input.humanReview) {\n out.humanReview = { ...input.humanReview }\n }\n if (input.fallback) {\n out.fallback = { ...input.fallback }\n }\n return out\n}\n","/**\n * `defineTool` — declare a tool for a {@link ToolServer}. The author supplies a\n * Zod schema for the input; the SDK derives the manifest `input_schema`\n * (JSON Schema) from it via `zod-to-json-schema`, statically types the handler\n * args via `z.infer`, and validates inbound `/mcp` arguments against it at\n * runtime. (Design decision: TS has no type-hint reflection, so the\n * schema is author-supplied — Zod is the single source for schema + types +\n * validation. `zod` is a peerDependency and is **not** re-exported.)\n */\n\n// Type-only import: erased at build, so zod stays a peerDependency (the author's\n// instance is authoritative) and never enters this package's runtime bundle.\nimport type { ZodTypeAny, TypeOf } from 'zod'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\nimport type { ToolContext } from './context'\nimport type { ToolResult, ToolSpec } from './types'\n\n/** A handler's return — sync or async {@link ToolResult}. */\nexport type ToolHandlerResult = ToolResult | Promise<ToolResult>\n\n/** A compiled tool: its manifest spec, its input validator, and its handler. */\nexport interface ToolDefinition {\n readonly name: string\n readonly description: string\n readonly idempotent: boolean\n /** The Zod schema; used to validate inbound `/mcp` arguments. */\n readonly inputSchema: ZodTypeAny\n /** The JSON Schema derived from `inputSchema` (manifest `input_schema`). */\n readonly jsonSchema: Record<string, unknown>\n /** Optional manifest `output_schema` (a JSON Schema object). */\n readonly outputSchema?: Record<string, unknown>\n /** Runs the tool with already-validated arguments. */\n readonly handler: (args: unknown, ctx: ToolContext) => ToolHandlerResult\n /** The manifest row for this tool. */\n toSpec(): ToolSpec\n}\n\n/** Definition object accepted by {@link defineTool}. */\nexport interface ToolDefinitionInput<S extends ZodTypeAny> {\n name: string\n description: string\n /** A Zod object schema describing the tool's arguments. */\n input: S\n idempotent: boolean\n /** Receives arguments typed by `input` (via `z.infer`) and the execution context. */\n handler: (args: TypeOf<S>, ctx: ToolContext) => ToolHandlerResult\n /** Optional JSON Schema for the tool's output (manifest only; not validated). */\n outputSchema?: Record<string, unknown>\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Compile a Zod schema to the manifest JSON Schema shape: inline all `$ref`s,\n * drop the `$schema`/`$ref` wrapper keys, and force `additionalProperties:false`\n * on the root object so the gateway validator rejects unknown args — matching the\n * Python SDK's Pydantic output.\n */\nfunction compileInputSchema(schema: ZodTypeAny): Record<string, unknown> {\n const generated = zodToJsonSchema(schema, { $refStrategy: 'none', target: 'jsonSchema7' })\n const json: Record<string, unknown> = isRecord(generated) ? { ...generated } : {}\n delete json.$schema\n delete json.$ref\n if (json.type === 'object') {\n json.additionalProperties = false\n }\n return json\n}\n\n/**\n * Declare a tool. Returns a {@link ToolDefinition} ready to register on a\n * {@link ToolServer}. The `input` Zod schema is the single source of truth for\n * the manifest schema, the handler's argument types, and runtime validation.\n */\nexport function defineTool<S extends ZodTypeAny>(def: ToolDefinitionInput<S>): ToolDefinition {\n if (!def.name) {\n throw new Error('defineTool: name is required')\n }\n const jsonSchema = compileInputSchema(def.input)\n const handler = def.handler as (args: unknown, ctx: ToolContext) => ToolHandlerResult\n return {\n name: def.name,\n description: def.description,\n idempotent: def.idempotent,\n inputSchema: def.input,\n jsonSchema,\n outputSchema: def.outputSchema,\n handler,\n toSpec(): ToolSpec {\n const spec: ToolSpec = {\n name: def.name,\n description: def.description,\n inputSchema: jsonSchema,\n idempotent: def.idempotent,\n }\n if (def.outputSchema != null) {\n spec.outputSchema = def.outputSchema\n }\n return spec\n },\n }\n}\n","/**\n * Confirmation-handshake token signing — byte-for-byte compatible with the\n * `mcp-shared` framework and the Python / Go author SDKs, so a token minted by a\n * TS-authored server verifies against the gateway (and vice versa).\n *\n * Token wire format: `base64url(\"<expires>:<digest>:<sig>\")` (RFC 4648 url-safe,\n * **padding kept**), where\n * digest = sha256hex(canonicalJson(normalizeForDigest(arguments)))\n * sig = hmacSha256hex(secret, \"<toolName>|<expires>|<digest>\")\n *\n * This is a **distinct** scheme from the inbound request HMAC in `./auth` (which\n * signs `<timestamp>.<body>`); the two are not interchangeable.\n *\n * Byte-exactness notes:\n * - `canonicalJson` matches Python `json.dumps(sort_keys=True, separators=(\",\",\":\"))`\n * with the default `ensure_ascii=True` — every code point ≥ 0x80 is emitted as\n * `\\uXXXX` (surrogate pairs for non-BMP), keys sorted by UTF-16 code unit.\n * - `normalizeForDigest` strips volatile presigned-URL query params before hashing\n * (re-encoding kept params with Python `quote_plus` semantics) so a re-presigned\n * URL still matches the confirmed arguments.\n */\n\nimport { createHash, createHmac, timingSafeEqual } from 'node:crypto'\nimport { ConfirmationInvalidError } from './errors'\n\n/** Confirmation token lifetime (seconds); matches the framework constant. */\nexport const CONFIRMATION_TTL_SECONDS = 300\n\n/** The argument key carrying the token on the confirming re-call (stripped before digest). */\nconst CONFIRMATION_FIELD = 'confirmation_token'\n\n/** Volatile presigned-URL query params dropped before hashing (matched case-insensitively). */\nconst VOLATILE_SIGNED_URL_PARAMS = new Set([\n // S3 SigV4\n 'x-amz-algorithm',\n 'x-amz-credential',\n 'x-amz-date',\n 'x-amz-expires',\n 'x-amz-signature',\n 'x-amz-signedheaders',\n 'x-amz-security-token',\n // S3 SigV2 (legacy presign)\n 'awsaccesskeyid',\n 'signature',\n 'expires',\n // CloudFront signed URLs\n 'key-pair-id',\n 'policy',\n])\n\nconst ALWAYS_SAFE = /^[A-Za-z0-9_.~-]$/\n\n/** Python `urllib.parse.quote_plus` over UTF-8 bytes (space → '+', else %XX upper). */\nfunction quotePlus(value: string): string {\n let out = ''\n for (const byte of Buffer.from(value, 'utf8')) {\n const ch = String.fromCharCode(byte)\n if (ALWAYS_SAFE.test(ch)) {\n out += ch\n } else if (ch === ' ') {\n out += '+'\n } else {\n out += `%${byte.toString(16).toUpperCase().padStart(2, '0')}`\n }\n }\n return out\n}\n\n/** Python `urllib.parse.unquote_plus` ('+' → space, %XX → byte) decoding to UTF-8. */\nfunction unquotePlus(value: string): string {\n const bytes: number[] = []\n for (let i = 0; i < value.length; i += 1) {\n const ch = value[i]!\n if (ch === '+') {\n bytes.push(0x20)\n } else if (ch === '%' && i + 2 < value.length) {\n bytes.push(parseInt(value.slice(i + 1, i + 3), 16))\n i += 2\n } else {\n bytes.push(ch.charCodeAt(0))\n }\n }\n return Buffer.from(bytes).toString('utf8')\n}\n\n/** Strip volatile presign params from a URL string, re-encoding kept params (quote_plus). */\nfunction stripVolatileParams(url: string): string {\n const queryStart = url.indexOf('?')\n const hashStart = url.indexOf('#', queryStart)\n const base = url.slice(0, queryStart)\n const query = url.slice(queryStart + 1, hashStart === -1 ? undefined : hashStart)\n const fragment = hashStart === -1 ? '' : url.slice(hashStart)\n\n const kept: string[] = []\n for (const pair of query.split('&')) {\n if (pair === '') {\n continue\n }\n const eq = pair.indexOf('=')\n const rawKey = eq === -1 ? pair : pair.slice(0, eq)\n const rawValue = eq === -1 ? '' : pair.slice(eq + 1)\n const key = unquotePlus(rawKey)\n if (VOLATILE_SIGNED_URL_PARAMS.has(key.toLowerCase())) {\n continue\n }\n kept.push(`${quotePlus(key)}=${quotePlus(unquotePlus(rawValue))}`)\n }\n\n const rebuiltQuery = kept.join('&')\n return rebuiltQuery === '' ? `${base}${fragment}` : `${base}?${rebuiltQuery}${fragment}`\n}\n\n/** Recursively drop volatile presign params from any URL strings within `value`. */\nexport function normalizeForDigest(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(normalizeForDigest)\n }\n if (value !== null && typeof value === 'object') {\n // Null-prototype accumulator: a plain `{}` would swallow a `__proto__` /\n // `constructor`-named key via the prototype setter, silently dropping it\n // from the digest (divergence from Python's json.dumps, which keeps it).\n const out: Record<string, unknown> = Object.create(null) as Record<string, unknown>\n for (const [k, v] of Object.entries(value)) {\n out[k] = normalizeForDigest(v)\n }\n return out\n }\n if (typeof value === 'string' && value.includes('://') && value.includes('?')) {\n return stripVolatileParams(value)\n }\n return value\n}\n\nfunction deepSortKeys(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(deepSortKeys)\n }\n if (value !== null && typeof value === 'object') {\n const source = value as Record<string, unknown>\n // Null-prototype for the same reason as normalizeForDigest: reserved key\n // names must survive as ordinary own properties.\n const out: Record<string, unknown> = Object.create(null) as Record<string, unknown>\n for (const key of Object.keys(source).sort()) {\n out[key] = deepSortKeys(source[key])\n }\n return out\n }\n return value\n}\n\n/** Match Python `json.dumps(..., sort_keys=True, separators=(\",\",\":\"))` (ensure_ascii). */\nfunction canonicalJson(value: unknown): string {\n const compact = JSON.stringify(deepSortKeys(value))\n // ensure_ascii: escape every code point >= 0x80 as \\uXXXX (surrogate pairs intact).\n return compact.replace(\n /[\\u0080-\\uffff]/g,\n (c) => `\\\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`\n )\n}\n\nfunction payloadDigest(args: Record<string, unknown>): string {\n return createHash('sha256')\n .update(canonicalJson(normalizeForDigest(args)), 'utf8')\n .digest('hex')\n}\n\nfunction sign(secret: string, toolName: string, expires: number, digest: string): string {\n return createHmac('sha256', secret)\n .update(`${toolName}|${expires}|${digest}`, 'utf8')\n .digest('hex')\n}\n\nfunction base64urlEncode(raw: string): string {\n // url-safe alphabet WITH padding kept (matches Python base64.urlsafe_b64encode).\n return Buffer.from(raw, 'utf8').toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_')\n}\n\nfunction base64urlDecode(token: string): string {\n return Buffer.from(token.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8')\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n const aBuf = Buffer.from(a)\n const bBuf = Buffer.from(b)\n return aBuf.length === bBuf.length && timingSafeEqual(aBuf, bBuf)\n}\n\n/** Arguments passed to {@link mintConfirmationToken} / {@link verifyConfirmationToken}. */\nexport interface ConfirmationTokenInput {\n toolName: string\n arguments: Record<string, unknown>\n /** Unix seconds at which the token expires. */\n expiresAtUnix: number\n secret: string\n}\n\n/**\n * Mint a confirmation token for a tool call, byte-for-byte compatible with the\n * gateway / Python / Go signer.\n */\nexport function mintConfirmationToken(input: ConfirmationTokenInput): string {\n if (!input.secret) {\n throw new ConfirmationInvalidError('malformed', 'confirmation secret is not configured')\n }\n const digest = payloadDigest(input.arguments)\n const sig = sign(input.secret, input.toolName, input.expiresAtUnix, digest)\n return base64urlEncode(`${input.expiresAtUnix}:${digest}:${sig}`)\n}\n\n/** Verification input (`expiresAtUnix` is ignored — it is read from the token). */\nexport interface VerifyConfirmationInput {\n toolName: string\n arguments: Record<string, unknown>\n token: string\n secret: string\n /** Override \"now\" (Unix seconds) for tests; defaults to the real clock. */\n now?: () => number\n}\n\n/**\n * Verify a confirmation token against the (re-)supplied arguments. Throws\n * {@link ConfirmationInvalidError} on a malformed / expired / mismatched token.\n * The `confirmation_token` argument (present on the confirming re-call) is\n * stripped before re-computing the digest.\n */\nexport function verifyConfirmationToken(input: VerifyConfirmationInput): void {\n if (!input.secret) {\n throw new ConfirmationInvalidError('malformed', 'confirmation secret is not configured')\n }\n const now = input.now ?? (() => Math.floor(Date.now() / 1000))\n\n let expiresStr: string | undefined\n let digest: string | undefined\n let sig: string | undefined\n try {\n ;[expiresStr, digest, sig] = base64urlDecode(input.token).split(':')\n } catch {\n throw new ConfirmationInvalidError('malformed', 'confirmation token could not be decoded')\n }\n if (expiresStr === undefined || digest === undefined || sig === undefined) {\n throw new ConfirmationInvalidError('malformed', 'confirmation token is malformed')\n }\n if (!/^\\d+$/.test(expiresStr)) {\n throw new ConfirmationInvalidError('malformed', 'confirmation token has a non-integer expiry')\n }\n const expiresAtUnix = Number(expiresStr)\n\n if (expiresAtUnix < now()) {\n throw new ConfirmationInvalidError('expired', 'confirmation token expired')\n }\n\n // Null-prototype accumulator (see normalizeForDigest): a `__proto__`-named\n // argument key must survive the strip as an ordinary own property.\n const stripped: Record<string, unknown> = Object.create(null) as Record<string, unknown>\n for (const [k, v] of Object.entries(input.arguments)) {\n if (k !== CONFIRMATION_FIELD) {\n stripped[k] = v\n }\n }\n if (!constantTimeEqual(digest, payloadDigest(stripped))) {\n throw new ConfirmationInvalidError(\n 'digest_mismatch',\n 'confirmation token does not match arguments'\n )\n }\n\n const expectedSig = sign(input.secret, input.toolName, expiresAtUnix, digest)\n if (!constantTimeEqual(sig, expectedSig)) {\n throw new ConfirmationInvalidError('signature_mismatch', 'confirmation token signature invalid')\n }\n}\n","/**\n * Local testing harness — invoke a registered tool in-process and get its wire\n * envelope back, without standing up an HTTP server. Lets authors unit-test\n * their tools (and the platform's confirmation / validation behaviour) directly\n * against a {@link ToolServer}.\n *\n * It drives the same transport-free `ToolServer.dispatch` the `/mcp` runtime\n * uses, so what you assert in a test is exactly what the gateway would receive.\n */\n\nimport { TOOLS_CALL } from './jsonrpc'\nimport { ConvilynAuthorError } from './errors'\nimport type { ToolServer } from './tool-server'\nimport type { ToolResultWire } from './tool-result-wire'\n\n/** Options for {@link invokeTool}. */\nexport interface InvokeToolOptions {\n /** `request_id` surfaced to the tool's {@link ToolContext}. */\n requestId?: string\n}\n\n/**\n * Invoke `toolName` on `server` with `args` and return the ToolResult wire\n * envelope. Throws {@link ConvilynAuthorError} if the dispatch returns a\n * JSON-RPC protocol error (unknown tool / method / bad params) — tool-level\n * outcomes (`tool_error` / `validation_error`) come back as an envelope, not a\n * throw, so they can be asserted on.\n */\nexport async function invokeTool(\n server: ToolServer,\n toolName: string,\n args: Record<string, unknown> = {},\n options: InvokeToolOptions = {}\n): Promise<ToolResultWire> {\n const response = await server.dispatch({\n jsonrpc: '2.0',\n method: TOOLS_CALL,\n params: {\n name: toolName,\n arguments: args,\n context: options.requestId !== undefined ? { request_id: options.requestId } : {},\n },\n id: 'harness',\n })\n if (response.error) {\n throw new ConvilynAuthorError(\n `invokeTool(${toolName}) failed: [${response.error.code}] ${response.error.message}`\n )\n }\n return response.result as ToolResultWire\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@convilyn/sdk-author",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Official TypeScript/JavaScript Author SDK for Convilyn — build tool servers and workflow specs for the platform.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"packageManager": "pnpm@10.10.0",
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18"
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.cjs",
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"bin": {
|
|
18
|
+
"convilyn-author": "./dist/cli.cjs"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"require": "./dist/index.cjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"CHANGELOG.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"lint": "eslint src",
|
|
38
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
39
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
40
|
+
"test": "vitest run --coverage"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"convilyn",
|
|
44
|
+
"sdk",
|
|
45
|
+
"author",
|
|
46
|
+
"tool-server",
|
|
47
|
+
"workflow",
|
|
48
|
+
"mcp",
|
|
49
|
+
"ai"
|
|
50
|
+
],
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/CoreNovus/convilyn-author-js.git"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/CoreNovus/convilyn-author-js#readme",
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/CoreNovus/convilyn-author-js/issues"
|
|
58
|
+
},
|
|
59
|
+
"pnpm": {
|
|
60
|
+
"onlyBuiltDependencies": ["esbuild"],
|
|
61
|
+
"overrides": {
|
|
62
|
+
"esbuild": ">=0.28.1"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"commander": "^15.0.0",
|
|
67
|
+
"zod-to-json-schema": "^3.23.5"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"zod": "^3.23.0"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/node": "^22.10.0",
|
|
74
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
75
|
+
"eslint": "^10.6.0",
|
|
76
|
+
"prettier": "^3.9.4",
|
|
77
|
+
"tsup": "^8.3.5",
|
|
78
|
+
"typescript": "^5.7.2",
|
|
79
|
+
"typescript-eslint": "^8.63.0",
|
|
80
|
+
"vite": "^8.0.0",
|
|
81
|
+
"vitest": "^4.1.10",
|
|
82
|
+
"zod": "^3.23.0"
|
|
83
|
+
}
|
|
84
|
+
}
|