@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
|
@@ -0,0 +1,1311 @@
|
|
|
1
|
+
import { randomBytes, createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
+
import { writeFile, readFile } from 'fs/promises';
|
|
3
|
+
import { createServer } from 'http';
|
|
4
|
+
|
|
5
|
+
// src/version.ts
|
|
6
|
+
var VERSION = "0.7.0";
|
|
7
|
+
var MANIFEST_SDK_VERSION = "1.0.0";
|
|
8
|
+
|
|
9
|
+
// src/errors.ts
|
|
10
|
+
var ConvilynAuthorError = class extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "ConvilynAuthorError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var InvalidSignatureError = class extends ConvilynAuthorError {
|
|
17
|
+
reason;
|
|
18
|
+
constructor(reason, message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "InvalidSignatureError";
|
|
21
|
+
this.reason = reason;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var ConvilynStartupError = class extends ConvilynAuthorError {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "ConvilynStartupError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var ConvilynApiError = class extends ConvilynAuthorError {
|
|
31
|
+
status;
|
|
32
|
+
body;
|
|
33
|
+
constructor(status, message, body) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "ConvilynApiError";
|
|
36
|
+
this.status = status;
|
|
37
|
+
this.body = body;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var ConfirmationInvalidError = class extends ConvilynAuthorError {
|
|
41
|
+
reason;
|
|
42
|
+
constructor(reason, message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "ConfirmationInvalidError";
|
|
45
|
+
this.reason = reason;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var SIGNATURE_HEADER = "x-convilyn-signature";
|
|
49
|
+
var TIMESTAMP_HEADER = "x-convilyn-timestamp";
|
|
50
|
+
var SERVER_ID_HEADER = "x-convilyn-server-id";
|
|
51
|
+
var DEFAULT_HMAC_TOLERANCE_SECONDS = 300;
|
|
52
|
+
function expectedSignature(secret, timestamp, body) {
|
|
53
|
+
return createHmac("sha256", secret).update(timestamp).update(".").update(body).digest("hex");
|
|
54
|
+
}
|
|
55
|
+
function signRequest(secret, body, unixSeconds) {
|
|
56
|
+
const timestamp = String(Math.trunc(unixSeconds));
|
|
57
|
+
return { signature: expectedSignature(secret, timestamp, body), timestamp };
|
|
58
|
+
}
|
|
59
|
+
function constantTimeEqual(a, b) {
|
|
60
|
+
const aBuf = Buffer.from(a);
|
|
61
|
+
const bBuf = Buffer.from(b);
|
|
62
|
+
if (aBuf.length !== bBuf.length) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return timingSafeEqual(aBuf, bBuf);
|
|
66
|
+
}
|
|
67
|
+
function verifySignature(secret, body, headers, options = {}) {
|
|
68
|
+
const tolerance = options.toleranceSeconds != null && options.toleranceSeconds > 0 ? options.toleranceSeconds : DEFAULT_HMAC_TOLERANCE_SECONDS;
|
|
69
|
+
const now = options.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
70
|
+
if (!secret) {
|
|
71
|
+
throw new InvalidSignatureError("missing_secret", "HMAC secret is not configured on the server");
|
|
72
|
+
}
|
|
73
|
+
const signature = headers[SIGNATURE_HEADER];
|
|
74
|
+
const timestamp = headers[TIMESTAMP_HEADER];
|
|
75
|
+
if (!signature || !timestamp) {
|
|
76
|
+
throw new InvalidSignatureError(
|
|
77
|
+
"missing_header",
|
|
78
|
+
"Missing X-Convilyn-Signature or X-Convilyn-Timestamp"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (!/^[+-]?\d+$/.test(timestamp)) {
|
|
82
|
+
throw new InvalidSignatureError(
|
|
83
|
+
"invalid_timestamp",
|
|
84
|
+
"X-Convilyn-Timestamp must be an integer Unix-seconds value"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const tsValue = Number(timestamp);
|
|
88
|
+
if (Math.abs(now() - tsValue) > tolerance) {
|
|
89
|
+
throw new InvalidSignatureError(
|
|
90
|
+
"timestamp_out_of_range",
|
|
91
|
+
"Request timestamp outside acceptable tolerance window"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
const expected = expectedSignature(secret, timestamp, body);
|
|
95
|
+
if (!constantTimeEqual(expected, signature)) {
|
|
96
|
+
throw new InvalidSignatureError(
|
|
97
|
+
"signature_mismatch",
|
|
98
|
+
"Signature did not match expected HMAC-SHA256 digest"
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/types.ts
|
|
104
|
+
var ToolResult = {
|
|
105
|
+
ok(data, summary) {
|
|
106
|
+
return summary !== void 0 ? { success: true, data, summary } : { success: true, data };
|
|
107
|
+
},
|
|
108
|
+
fail(code, message, details, summary) {
|
|
109
|
+
const error = details ? { code, message, details } : { code, message };
|
|
110
|
+
return summary !== void 0 ? { success: false, error, summary } : { success: false, error };
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
function allChecksPassed(report) {
|
|
114
|
+
return report.results.every((result) => result.passed);
|
|
115
|
+
}
|
|
116
|
+
function failedChecks(report) {
|
|
117
|
+
return report.results.filter((result) => !result.passed);
|
|
118
|
+
}
|
|
119
|
+
var DEFAULT_MANIFEST_PATH = "convilyn.manifest.json";
|
|
120
|
+
function isRecord(value) {
|
|
121
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
122
|
+
}
|
|
123
|
+
function serverToWire(server) {
|
|
124
|
+
const wire = {
|
|
125
|
+
name: server.name,
|
|
126
|
+
version: server.version,
|
|
127
|
+
description: server.description
|
|
128
|
+
};
|
|
129
|
+
if (server.mcpServerName != null && server.mcpServerName !== "") {
|
|
130
|
+
wire.mcp_server_name = server.mcpServerName;
|
|
131
|
+
}
|
|
132
|
+
return wire;
|
|
133
|
+
}
|
|
134
|
+
function toolToWire(tool) {
|
|
135
|
+
const wire = {
|
|
136
|
+
name: tool.name,
|
|
137
|
+
description: tool.description,
|
|
138
|
+
input_schema: tool.inputSchema,
|
|
139
|
+
idempotent: tool.idempotent
|
|
140
|
+
};
|
|
141
|
+
if (tool.outputSchema != null) {
|
|
142
|
+
wire.output_schema = tool.outputSchema;
|
|
143
|
+
}
|
|
144
|
+
return wire;
|
|
145
|
+
}
|
|
146
|
+
function serverFromWire(wire) {
|
|
147
|
+
const server = {
|
|
148
|
+
name: String(wire.name ?? ""),
|
|
149
|
+
version: String(wire.version ?? ""),
|
|
150
|
+
description: String(wire.description ?? "")
|
|
151
|
+
};
|
|
152
|
+
if (typeof wire.mcp_server_name === "string") {
|
|
153
|
+
server.mcpServerName = wire.mcp_server_name;
|
|
154
|
+
}
|
|
155
|
+
return server;
|
|
156
|
+
}
|
|
157
|
+
function toolFromWire(wire) {
|
|
158
|
+
const tool = {
|
|
159
|
+
name: String(wire.name ?? ""),
|
|
160
|
+
description: String(wire.description ?? ""),
|
|
161
|
+
inputSchema: isRecord(wire.input_schema) ? wire.input_schema : {},
|
|
162
|
+
idempotent: Boolean(wire.idempotent)
|
|
163
|
+
};
|
|
164
|
+
if (isRecord(wire.output_schema)) {
|
|
165
|
+
tool.outputSchema = wire.output_schema;
|
|
166
|
+
}
|
|
167
|
+
return tool;
|
|
168
|
+
}
|
|
169
|
+
var ConvilynManifest = class _ConvilynManifest {
|
|
170
|
+
sdkVersion;
|
|
171
|
+
server;
|
|
172
|
+
tools;
|
|
173
|
+
capabilities;
|
|
174
|
+
constructor(server, options = {}) {
|
|
175
|
+
this.sdkVersion = options.sdkVersion ?? MANIFEST_SDK_VERSION;
|
|
176
|
+
this.server = server;
|
|
177
|
+
this.tools = options.tools ?? [];
|
|
178
|
+
this.capabilities = options.capabilities ?? [];
|
|
179
|
+
}
|
|
180
|
+
/** The platform wire object (snake_case; empty lists kept as `[]`). */
|
|
181
|
+
toWire() {
|
|
182
|
+
return {
|
|
183
|
+
sdk_version: this.sdkVersion,
|
|
184
|
+
server: serverToWire(this.server),
|
|
185
|
+
tools: this.tools.map(toolToWire),
|
|
186
|
+
capabilities: [...this.capabilities]
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** The manifest serialised as 2-space-indented JSON. */
|
|
190
|
+
toJson() {
|
|
191
|
+
return JSON.stringify(this.toWire(), null, 2);
|
|
192
|
+
}
|
|
193
|
+
/** Write the manifest to a JSON file (default `convilyn.manifest.json`). */
|
|
194
|
+
async save(path = DEFAULT_MANIFEST_PATH) {
|
|
195
|
+
await writeFile(path, this.toJson(), "utf8");
|
|
196
|
+
return path;
|
|
197
|
+
}
|
|
198
|
+
/** Parse a manifest from its wire object. */
|
|
199
|
+
static fromWire(wire) {
|
|
200
|
+
const server = serverFromWire(isRecord(wire.server) ? wire.server : {});
|
|
201
|
+
const tools = Array.isArray(wire.tools) ? wire.tools.filter(isRecord).map(toolFromWire) : [];
|
|
202
|
+
const capabilities = Array.isArray(wire.capabilities) ? wire.capabilities.filter((cap) => typeof cap === "string") : [];
|
|
203
|
+
const sdkVersion = typeof wire.sdk_version === "string" ? wire.sdk_version : MANIFEST_SDK_VERSION;
|
|
204
|
+
return new _ConvilynManifest(server, { tools, capabilities, sdkVersion });
|
|
205
|
+
}
|
|
206
|
+
/** Parse a manifest from a JSON string. */
|
|
207
|
+
static fromJson(data) {
|
|
208
|
+
const parsed = JSON.parse(data);
|
|
209
|
+
if (!isRecord(parsed)) {
|
|
210
|
+
throw new Error("manifest JSON must be an object");
|
|
211
|
+
}
|
|
212
|
+
return _ConvilynManifest.fromWire(parsed);
|
|
213
|
+
}
|
|
214
|
+
/** Read a manifest from a JSON file (default `convilyn.manifest.json`). */
|
|
215
|
+
static async load(path = DEFAULT_MANIFEST_PATH) {
|
|
216
|
+
const raw = await readFile(path, "utf8");
|
|
217
|
+
return _ConvilynManifest.fromJson(raw);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
function generateRefId() {
|
|
221
|
+
return `td_${randomBytes(6).toString("hex")}`;
|
|
222
|
+
}
|
|
223
|
+
var InMemoryDataStore = class {
|
|
224
|
+
#store = /* @__PURE__ */ new Map();
|
|
225
|
+
async store(data) {
|
|
226
|
+
const refId = generateRefId();
|
|
227
|
+
this.#store.set(refId, data);
|
|
228
|
+
return refId;
|
|
229
|
+
}
|
|
230
|
+
async get(refId) {
|
|
231
|
+
return this.#store.get(refId) ?? null;
|
|
232
|
+
}
|
|
233
|
+
/** Number of stored entries. */
|
|
234
|
+
get size() {
|
|
235
|
+
return this.#store.size;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
function createDataStore(_config) {
|
|
239
|
+
return new InMemoryDataStore();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/context.ts
|
|
243
|
+
var ConsoleProgressBackend = class {
|
|
244
|
+
#write;
|
|
245
|
+
constructor(write) {
|
|
246
|
+
this.#write = write ?? ((line) => {
|
|
247
|
+
if (typeof process !== "undefined" && process.stderr) {
|
|
248
|
+
process.stderr.write(`${line}
|
|
249
|
+
`);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
report(requestId, percent, message) {
|
|
254
|
+
this.#write(`[progress ${percent}%] ${requestId}: ${message}`);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
var ToolContext = class {
|
|
258
|
+
requestId;
|
|
259
|
+
dataStore;
|
|
260
|
+
#progress;
|
|
261
|
+
constructor(requestId, dataStore, progress = null) {
|
|
262
|
+
this.requestId = requestId;
|
|
263
|
+
this.dataStore = dataStore;
|
|
264
|
+
this.#progress = progress;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Emit a progress update (0–100). Throws {@link RangeError} when `percent` is
|
|
268
|
+
* out of range; a no-op when no {@link ProgressBackend} is wired.
|
|
269
|
+
*/
|
|
270
|
+
reportProgress(percent, message) {
|
|
271
|
+
if (percent < 0 || percent > 100) {
|
|
272
|
+
throw new RangeError(`progress percent must be in [0, 100], got ${percent}`);
|
|
273
|
+
}
|
|
274
|
+
this.#progress?.report(this.requestId, percent, message);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
function createToolContext(dataStore, payload) {
|
|
278
|
+
const requestId = typeof payload.request_id === "string" && payload.request_id !== "" ? payload.request_id : "unknown";
|
|
279
|
+
return new ToolContext(requestId, dataStore, null);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/config.ts
|
|
283
|
+
function envOr(env, key, fallback) {
|
|
284
|
+
const value = env[key];
|
|
285
|
+
return value !== void 0 && value !== "" ? value : fallback;
|
|
286
|
+
}
|
|
287
|
+
function envIntOr(env, key, fallback) {
|
|
288
|
+
const value = env[key];
|
|
289
|
+
if (value !== void 0 && value !== "") {
|
|
290
|
+
const parsed = Number(value);
|
|
291
|
+
if (Number.isInteger(parsed)) {
|
|
292
|
+
return parsed;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return fallback;
|
|
296
|
+
}
|
|
297
|
+
var SDKConfig = class _SDKConfig {
|
|
298
|
+
serverHost;
|
|
299
|
+
serverPort;
|
|
300
|
+
logLevel;
|
|
301
|
+
awsRegion;
|
|
302
|
+
dynamodbEndpoint;
|
|
303
|
+
toolDataTable;
|
|
304
|
+
environment;
|
|
305
|
+
apiKey;
|
|
306
|
+
platformUrl;
|
|
307
|
+
hmacSecret;
|
|
308
|
+
hmacToleranceSeconds;
|
|
309
|
+
toolConfirmationSecret;
|
|
310
|
+
#lambda;
|
|
311
|
+
constructor(options = {}) {
|
|
312
|
+
this.serverHost = options.serverHost ?? "127.0.0.1";
|
|
313
|
+
this.serverPort = options.serverPort ?? 8080;
|
|
314
|
+
this.logLevel = options.logLevel ?? "INFO";
|
|
315
|
+
this.awsRegion = options.awsRegion ?? "ap-northeast-1";
|
|
316
|
+
this.dynamodbEndpoint = options.dynamodbEndpoint ?? "";
|
|
317
|
+
this.toolDataTable = options.toolDataTable ?? "tool-data";
|
|
318
|
+
this.environment = options.environment ?? "local";
|
|
319
|
+
this.apiKey = options.apiKey ?? "";
|
|
320
|
+
this.platformUrl = options.platformUrl ?? "https://api.convilyn.corenovus.com";
|
|
321
|
+
this.hmacSecret = options.hmacSecret ?? "";
|
|
322
|
+
this.hmacToleranceSeconds = options.hmacToleranceSeconds ?? DEFAULT_HMAC_TOLERANCE_SECONDS;
|
|
323
|
+
this.toolConfirmationSecret = options.toolConfirmationSecret ?? "";
|
|
324
|
+
this.#lambda = options.lambda ?? false;
|
|
325
|
+
}
|
|
326
|
+
/** True when running in AWS Lambda. */
|
|
327
|
+
get isLambda() {
|
|
328
|
+
return this.#lambda;
|
|
329
|
+
}
|
|
330
|
+
/** True for a local/dev environment (relaxes HMAC). */
|
|
331
|
+
get isLocal() {
|
|
332
|
+
if (this.environment === "local") {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
return !this.#lambda && (this.serverHost === "127.0.0.1" || this.serverHost === "localhost");
|
|
336
|
+
}
|
|
337
|
+
/** Load configuration from environment variables, falling back to defaults. */
|
|
338
|
+
static fromEnv(env = typeof process !== "undefined" ? process.env : {}) {
|
|
339
|
+
return new _SDKConfig({
|
|
340
|
+
serverHost: envOr(env, "CONVILYN_HOST", "127.0.0.1"),
|
|
341
|
+
serverPort: envIntOr(env, "CONVILYN_PORT", 8080),
|
|
342
|
+
logLevel: envOr(env, "CONVILYN_LOG_LEVEL", "INFO"),
|
|
343
|
+
awsRegion: envOr(env, "AWS_REGION", "ap-northeast-1"),
|
|
344
|
+
dynamodbEndpoint: envOr(env, "DYNAMODB_ENDPOINT", ""),
|
|
345
|
+
toolDataTable: envOr(env, "TOOL_DATA_TABLE", "tool-data"),
|
|
346
|
+
environment: envOr(env, "CONVILYN_ENVIRONMENT", "local"),
|
|
347
|
+
apiKey: envOr(env, "CONVILYN_API_KEY", ""),
|
|
348
|
+
platformUrl: envOr(env, "CONVILYN_PLATFORM_URL", "https://api.convilyn.corenovus.com"),
|
|
349
|
+
hmacSecret: envOr(env, "CONVILYN_HMAC_SECRET", ""),
|
|
350
|
+
hmacToleranceSeconds: envIntOr(
|
|
351
|
+
env,
|
|
352
|
+
"CONVILYN_HMAC_TOLERANCE_SECONDS",
|
|
353
|
+
DEFAULT_HMAC_TOLERANCE_SECONDS
|
|
354
|
+
),
|
|
355
|
+
toolConfirmationSecret: envOr(env, "CONVILYN_TOOL_CONFIRMATION_SECRET", ""),
|
|
356
|
+
lambda: Boolean(env["AWS_LAMBDA_FUNCTION_NAME"])
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
var PUBLIC_SCHEMA_VERSION = "1";
|
|
361
|
+
var DEFAULT_SCHEMA_VERSION = "1.0";
|
|
362
|
+
var MAX_NAME = 80;
|
|
363
|
+
var MAX_DESCRIPTION = 500;
|
|
364
|
+
var MAX_SYSTEM_PROMPT = 8e3;
|
|
365
|
+
var MAX_TOOLS = 20;
|
|
366
|
+
var MAX_TOOL_NAME = 128;
|
|
367
|
+
var MAX_NOTE = 200;
|
|
368
|
+
var MAX_TAGS = 8;
|
|
369
|
+
var MAX_TAG_LEN = 24;
|
|
370
|
+
var ORIGIN = { x: 0, y: 0 };
|
|
371
|
+
var FORBIDDEN_TOOL = "request_user_input";
|
|
372
|
+
var DEFAULT_VERSION = "1.0.0";
|
|
373
|
+
var MAX_SPEC_ID = 128;
|
|
374
|
+
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
375
|
+
function validateName(name) {
|
|
376
|
+
if (name.length < 1 || name.length > MAX_NAME) {
|
|
377
|
+
throw new ConvilynAuthorError(`name must be 1\u2013${MAX_NAME} characters (got ${name.length})`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function validateSpecId(specId) {
|
|
381
|
+
if (specId.length < 1 || specId.length > MAX_SPEC_ID || /\s/.test(specId)) {
|
|
382
|
+
throw new ConvilynAuthorError(
|
|
383
|
+
`specId must be 1\u2013${MAX_SPEC_ID} characters with no whitespace (the portal expects the "dev_<developer-id>.<name>" namespace)`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function validateVersion(version) {
|
|
388
|
+
if (!SEMVER_RE.test(version)) {
|
|
389
|
+
throw new ConvilynAuthorError(`version must be valid semver (e.g. "1.0.0"; got "${version}")`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function validateDescription(description) {
|
|
393
|
+
if (description.length > MAX_DESCRIPTION) {
|
|
394
|
+
throw new ConvilynAuthorError(`description must be \u2264 ${MAX_DESCRIPTION} characters`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function validateSystemPrompt(systemPrompt) {
|
|
398
|
+
if (systemPrompt.length > MAX_SYSTEM_PROMPT) {
|
|
399
|
+
throw new ConvilynAuthorError(`systemPrompt must be \u2264 ${MAX_SYSTEM_PROMPT} characters`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function validateTag(tag) {
|
|
403
|
+
if (tag.length < 1 || tag.length > MAX_TAG_LEN) {
|
|
404
|
+
throw new ConvilynAuthorError(`tag "${tag}" must be 1\u2013${MAX_TAG_LEN} characters`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function validateTags(tags) {
|
|
408
|
+
if (tags.length > MAX_TAGS) {
|
|
409
|
+
throw new ConvilynAuthorError(`at most ${MAX_TAGS} tags are allowed (got ${tags.length})`);
|
|
410
|
+
}
|
|
411
|
+
tags.forEach(validateTag);
|
|
412
|
+
}
|
|
413
|
+
function toolKey(toolName) {
|
|
414
|
+
const parts = toolName.split(":");
|
|
415
|
+
return parts[parts.length - 1] ?? toolName;
|
|
416
|
+
}
|
|
417
|
+
function validateToolName(toolName) {
|
|
418
|
+
if (toolName.length < 1 || toolName.length > MAX_TOOL_NAME) {
|
|
419
|
+
throw new ConvilynAuthorError(`toolName must be 1\u2013${MAX_TOOL_NAME} characters`);
|
|
420
|
+
}
|
|
421
|
+
if (toolKey(toolName) === FORBIDDEN_TOOL) {
|
|
422
|
+
throw new ConvilynAuthorError(
|
|
423
|
+
`tool "${FORBIDDEN_TOOL}" is not permitted in a workflow palette (the agent cannot pause mid-execution)`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
var WorkflowSpec = class _WorkflowSpec {
|
|
428
|
+
#state;
|
|
429
|
+
constructor(name, options = {}) {
|
|
430
|
+
validateName(name);
|
|
431
|
+
if (options.specId != null) {
|
|
432
|
+
validateSpecId(options.specId);
|
|
433
|
+
}
|
|
434
|
+
const version = options.version ?? DEFAULT_VERSION;
|
|
435
|
+
validateVersion(version);
|
|
436
|
+
this.#state = {
|
|
437
|
+
name,
|
|
438
|
+
systemPrompt: "",
|
|
439
|
+
schemaVersion: DEFAULT_SCHEMA_VERSION,
|
|
440
|
+
specId: options.specId,
|
|
441
|
+
version,
|
|
442
|
+
tags: [],
|
|
443
|
+
toolPalette: []
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
#cloneWith(patch) {
|
|
447
|
+
const next = new _WorkflowSpec(this.#state.name);
|
|
448
|
+
next.#state = {
|
|
449
|
+
...this.#state,
|
|
450
|
+
...patch,
|
|
451
|
+
tags: [...patch.tags ?? this.#state.tags],
|
|
452
|
+
toolPalette: [...patch.toolPalette ?? this.#state.toolPalette]
|
|
453
|
+
};
|
|
454
|
+
return next;
|
|
455
|
+
}
|
|
456
|
+
// ── read-only accessors (return copies to preserve immutability) ──────────
|
|
457
|
+
get name() {
|
|
458
|
+
return this.#state.name;
|
|
459
|
+
}
|
|
460
|
+
get description() {
|
|
461
|
+
return this.#state.description;
|
|
462
|
+
}
|
|
463
|
+
get systemPrompt() {
|
|
464
|
+
return this.#state.systemPrompt;
|
|
465
|
+
}
|
|
466
|
+
get schemaVersion() {
|
|
467
|
+
return this.#state.schemaVersion;
|
|
468
|
+
}
|
|
469
|
+
get specId() {
|
|
470
|
+
return this.#state.specId;
|
|
471
|
+
}
|
|
472
|
+
get version() {
|
|
473
|
+
return this.#state.version;
|
|
474
|
+
}
|
|
475
|
+
get tags() {
|
|
476
|
+
return [...this.#state.tags];
|
|
477
|
+
}
|
|
478
|
+
get toolPalette() {
|
|
479
|
+
return this.#state.toolPalette.map((item) => ({ ...item, position: { ...item.position } }));
|
|
480
|
+
}
|
|
481
|
+
get canvasLayout() {
|
|
482
|
+
return this.#state.canvasLayout;
|
|
483
|
+
}
|
|
484
|
+
// ── builder methods ───────────────────────────────────────────────────────
|
|
485
|
+
withName(name) {
|
|
486
|
+
validateName(name);
|
|
487
|
+
return this.#cloneWith({ name });
|
|
488
|
+
}
|
|
489
|
+
withDescription(description) {
|
|
490
|
+
validateDescription(description);
|
|
491
|
+
return this.#cloneWith({ description });
|
|
492
|
+
}
|
|
493
|
+
withSystemPrompt(systemPrompt) {
|
|
494
|
+
validateSystemPrompt(systemPrompt);
|
|
495
|
+
return this.#cloneWith({ systemPrompt });
|
|
496
|
+
}
|
|
497
|
+
withSchemaVersion(schemaVersion) {
|
|
498
|
+
return this.#cloneWith({ schemaVersion });
|
|
499
|
+
}
|
|
500
|
+
/** Set the portal spec id (`dev_<developer-id>.<name>` namespace). */
|
|
501
|
+
withSpecId(specId) {
|
|
502
|
+
validateSpecId(specId);
|
|
503
|
+
return this.#cloneWith({ specId });
|
|
504
|
+
}
|
|
505
|
+
/** Set the blueprint semver version (default `1.0.0`). */
|
|
506
|
+
withVersion(version) {
|
|
507
|
+
validateVersion(version);
|
|
508
|
+
return this.#cloneWith({ version });
|
|
509
|
+
}
|
|
510
|
+
/** Replace the tag set. */
|
|
511
|
+
withTags(tags) {
|
|
512
|
+
validateTags(tags);
|
|
513
|
+
return this.#cloneWith({ tags: [...tags] });
|
|
514
|
+
}
|
|
515
|
+
/** Append a tag (no-op if already present). */
|
|
516
|
+
addTag(tag) {
|
|
517
|
+
validateTag(tag);
|
|
518
|
+
if (this.#state.tags.includes(tag)) {
|
|
519
|
+
return this.#cloneWith({});
|
|
520
|
+
}
|
|
521
|
+
const tags = [...this.#state.tags, tag];
|
|
522
|
+
validateTags(tags);
|
|
523
|
+
return this.#cloneWith({ tags });
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Add (or replace, by `toolName`) one tool on the palette. Rejects the
|
|
527
|
+
* `request_user_input` tool (invariant I3) and palettes larger than
|
|
528
|
+
* {@link MAX_TOOLS}.
|
|
529
|
+
*/
|
|
530
|
+
addTool(toolName, options = {}) {
|
|
531
|
+
validateToolName(toolName);
|
|
532
|
+
if (options.note != null && options.note.length > MAX_NOTE) {
|
|
533
|
+
throw new ConvilynAuthorError(`tool note must be \u2264 ${MAX_NOTE} characters`);
|
|
534
|
+
}
|
|
535
|
+
const item = {
|
|
536
|
+
toolName,
|
|
537
|
+
position: options.position ? { ...options.position } : { ...ORIGIN },
|
|
538
|
+
...options.note != null ? { note: options.note } : {}
|
|
539
|
+
};
|
|
540
|
+
const existing = this.#state.toolPalette.findIndex((entry) => entry.toolName === toolName);
|
|
541
|
+
const toolPalette = existing >= 0 ? this.#state.toolPalette.map((entry, index) => index === existing ? item : entry) : [...this.#state.toolPalette, item];
|
|
542
|
+
if (toolPalette.length > MAX_TOOLS) {
|
|
543
|
+
throw new ConvilynAuthorError(`at most ${MAX_TOOLS} tools are allowed`);
|
|
544
|
+
}
|
|
545
|
+
return this.#cloneWith({ toolPalette });
|
|
546
|
+
}
|
|
547
|
+
/** Add several tools by name (default canvas position), de-duplicated. */
|
|
548
|
+
useTools(...toolNames) {
|
|
549
|
+
return toolNames.reduce((spec, toolName) => spec.addTool(toolName), this);
|
|
550
|
+
}
|
|
551
|
+
withCanvasLayout(canvasLayout) {
|
|
552
|
+
return this.#cloneWith({ canvasLayout });
|
|
553
|
+
}
|
|
554
|
+
// ── compile / serialize ───────────────────────────────────────────────────
|
|
555
|
+
/**
|
|
556
|
+
* The PUBLIC publish blueprint (snake_case; Python-SDK parity). This is the
|
|
557
|
+
* shape `submitWorkflow` / `convilyn-author push` send to the Developer
|
|
558
|
+
* Portal. Requires a `specId` (constructor option or {@link withSpecId}).
|
|
559
|
+
*
|
|
560
|
+
* Mirrors the Python SDK's `exclude_none` semantics: `agent_config` is
|
|
561
|
+
* emitted only when a systemPrompt is set, `mcp_config` only when the
|
|
562
|
+
* palette is non-empty. UI-only metadata (positions, notes, canvasLayout)
|
|
563
|
+
* is deliberately NOT emitted — the portal's blueprint schema rejects
|
|
564
|
+
* unknown fields; that data belongs to {@link toExportPayload}.
|
|
565
|
+
*/
|
|
566
|
+
compile() {
|
|
567
|
+
const specId = this.#state.specId;
|
|
568
|
+
if (specId == null) {
|
|
569
|
+
throw new ConvilynAuthorError(
|
|
570
|
+
'compile() requires a specId \u2014 pass it to the constructor (new WorkflowSpec(name, { specId })) or call withSpecId(). The Developer Portal expects the "dev_<developer-id>.<name>" namespace.'
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
const blueprint = {
|
|
574
|
+
public_schema_version: PUBLIC_SCHEMA_VERSION,
|
|
575
|
+
spec_id: specId,
|
|
576
|
+
version: this.#state.version,
|
|
577
|
+
name: this.#state.name,
|
|
578
|
+
keywords: [...this.#state.tags]
|
|
579
|
+
};
|
|
580
|
+
if (this.#state.description != null) {
|
|
581
|
+
blueprint.description = this.#state.description;
|
|
582
|
+
}
|
|
583
|
+
if (this.#state.systemPrompt !== "") {
|
|
584
|
+
blueprint.agent_config = { system_prompt: this.#state.systemPrompt };
|
|
585
|
+
}
|
|
586
|
+
if (this.#state.toolPalette.length > 0) {
|
|
587
|
+
const tools = this.#state.toolPalette.map((item) => item.toolName);
|
|
588
|
+
const servers = [
|
|
589
|
+
...new Set(
|
|
590
|
+
tools.filter((toolName) => toolName.includes(":")).map((toolName) => toolName.split(":")[0])
|
|
591
|
+
)
|
|
592
|
+
];
|
|
593
|
+
blueprint.mcp_config = { mcp_servers: servers, tools };
|
|
594
|
+
}
|
|
595
|
+
return blueprint;
|
|
596
|
+
}
|
|
597
|
+
/** The compiled blueprint serialised as 2-space-indented JSON. */
|
|
598
|
+
compileJson() {
|
|
599
|
+
return JSON.stringify(this.compile(), null, 2);
|
|
600
|
+
}
|
|
601
|
+
/** Write the compiled blueprint to a JSON file (default `workflow.spec.json`). */
|
|
602
|
+
async save(path = "workflow.spec.json") {
|
|
603
|
+
await writeFile(path, this.compileJson(), "utf8");
|
|
604
|
+
return path;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* The camelCase user_workflows `ExportPayload` (canvas/authoring contract).
|
|
608
|
+
* Carries the UI-only metadata (positions, notes, canvasLayout) that the
|
|
609
|
+
* publish blueprint omits.
|
|
610
|
+
*/
|
|
611
|
+
toExportPayload() {
|
|
612
|
+
const payload = {
|
|
613
|
+
schemaVersion: this.#state.schemaVersion,
|
|
614
|
+
name: this.#state.name,
|
|
615
|
+
systemPrompt: this.#state.systemPrompt,
|
|
616
|
+
toolPalette: this.#state.toolPalette.map((item) => ({
|
|
617
|
+
toolName: item.toolName,
|
|
618
|
+
position: { ...item.position },
|
|
619
|
+
...item.note != null ? { note: item.note } : {}
|
|
620
|
+
})),
|
|
621
|
+
tags: [...this.#state.tags]
|
|
622
|
+
};
|
|
623
|
+
if (this.#state.description != null) {
|
|
624
|
+
payload.description = this.#state.description;
|
|
625
|
+
}
|
|
626
|
+
if (this.#state.canvasLayout != null) {
|
|
627
|
+
payload.canvasLayout = this.#state.canvasLayout;
|
|
628
|
+
}
|
|
629
|
+
return payload;
|
|
630
|
+
}
|
|
631
|
+
/** Rebuild a {@link WorkflowSpec} from a user_workflows `ExportPayload`. */
|
|
632
|
+
static fromExportPayload(payload) {
|
|
633
|
+
let spec = new _WorkflowSpec(payload.name).withSystemPrompt(payload.systemPrompt ?? "").withSchemaVersion(payload.schemaVersion ?? DEFAULT_SCHEMA_VERSION).withTags(payload.tags ?? []);
|
|
634
|
+
if (payload.description != null) {
|
|
635
|
+
spec = spec.withDescription(payload.description);
|
|
636
|
+
}
|
|
637
|
+
if (payload.canvasLayout != null) {
|
|
638
|
+
spec = spec.withCanvasLayout(payload.canvasLayout);
|
|
639
|
+
}
|
|
640
|
+
for (const item of payload.toolPalette ?? []) {
|
|
641
|
+
spec = spec.addTool(item.toolName, {
|
|
642
|
+
position: item.position,
|
|
643
|
+
...item.note != null ? { note: item.note } : {}
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
return spec;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Rebuild a {@link WorkflowSpec} from a publish blueprint. Lossy for canvas
|
|
650
|
+
* metadata by design — that data lives in the `ExportPayload` contract.
|
|
651
|
+
*/
|
|
652
|
+
static fromBlueprint(blueprint) {
|
|
653
|
+
let spec = new _WorkflowSpec(blueprint.name, {
|
|
654
|
+
specId: blueprint.spec_id,
|
|
655
|
+
...blueprint.version != null ? { version: blueprint.version } : {}
|
|
656
|
+
}).withSystemPrompt(blueprint.agent_config?.system_prompt ?? "").withTags(blueprint.keywords ?? []);
|
|
657
|
+
if (blueprint.description != null) {
|
|
658
|
+
spec = spec.withDescription(blueprint.description);
|
|
659
|
+
}
|
|
660
|
+
for (const toolName of blueprint.mcp_config?.tools ?? []) {
|
|
661
|
+
spec = spec.addTool(toolName);
|
|
662
|
+
}
|
|
663
|
+
return spec;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Parse a spec from a JSON string. Shape-sniffs: objects carrying `spec_id`
|
|
667
|
+
* or `public_schema_version` parse as a blueprint; anything else as an
|
|
668
|
+
* `ExportPayload` — so `push --workflow-file` accepts both file formats.
|
|
669
|
+
*/
|
|
670
|
+
static fromJson(data) {
|
|
671
|
+
const parsed = JSON.parse(data);
|
|
672
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
673
|
+
throw new ConvilynAuthorError("workflow spec JSON must be an object");
|
|
674
|
+
}
|
|
675
|
+
if ("spec_id" in parsed || "public_schema_version" in parsed) {
|
|
676
|
+
return _WorkflowSpec.fromBlueprint(parsed);
|
|
677
|
+
}
|
|
678
|
+
return _WorkflowSpec.fromExportPayload(parsed);
|
|
679
|
+
}
|
|
680
|
+
/** Read a saved spec from a JSON file (default `workflow.spec.json`). */
|
|
681
|
+
static async load(path = "workflow.spec.json") {
|
|
682
|
+
return _WorkflowSpec.fromJson(await readFile(path, "utf8"));
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
// src/jsonrpc.ts
|
|
687
|
+
var JSONRPC_VERSION = "2.0";
|
|
688
|
+
var METHOD_NOT_FOUND = -32601;
|
|
689
|
+
var INVALID_PARAMS = -32602;
|
|
690
|
+
var INTERNAL_ERROR = -32603;
|
|
691
|
+
var TOOLS_CALL = "tools/call";
|
|
692
|
+
function isRecord2(value) {
|
|
693
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
694
|
+
}
|
|
695
|
+
function asRpcRequest(parsed) {
|
|
696
|
+
if (!isRecord2(parsed) || typeof parsed.method !== "string") {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
const id = typeof parsed.id === "string" || typeof parsed.id === "number" ? parsed.id : null;
|
|
700
|
+
return {
|
|
701
|
+
jsonrpc: typeof parsed.jsonrpc === "string" ? parsed.jsonrpc : JSONRPC_VERSION,
|
|
702
|
+
method: parsed.method,
|
|
703
|
+
params: isRecord2(parsed.params) ? parsed.params : {},
|
|
704
|
+
id
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function rpcResult(id, result) {
|
|
708
|
+
return { jsonrpc: JSONRPC_VERSION, result, id };
|
|
709
|
+
}
|
|
710
|
+
function rpcError(id, code, message, data) {
|
|
711
|
+
const error = data !== void 0 ? { code, message, data } : { code, message };
|
|
712
|
+
return { jsonrpc: JSONRPC_VERSION, error, id };
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// src/tool-result-wire.ts
|
|
716
|
+
var OUTPUT_INLINE_THRESHOLD_BYTES = 16384;
|
|
717
|
+
var SUMMARY_MAX = 2e3;
|
|
718
|
+
function clampSummary(summary) {
|
|
719
|
+
const trimmed = summary.length > SUMMARY_MAX ? summary.slice(0, SUMMARY_MAX) : summary;
|
|
720
|
+
return trimmed.length > 0 ? trimmed : "Tool executed.";
|
|
721
|
+
}
|
|
722
|
+
async function okEnvelope(data, summary, dataStore) {
|
|
723
|
+
const base = {
|
|
724
|
+
summary: clampSummary(summary),
|
|
725
|
+
status: "ok",
|
|
726
|
+
success: true,
|
|
727
|
+
error_code: null,
|
|
728
|
+
error_message: null
|
|
729
|
+
};
|
|
730
|
+
const serialised = JSON.stringify(data);
|
|
731
|
+
if (Buffer.byteLength(serialised, "utf8") > OUTPUT_INLINE_THRESHOLD_BYTES) {
|
|
732
|
+
base.ref_id = await dataStore.store(data);
|
|
733
|
+
} else {
|
|
734
|
+
base.data = data;
|
|
735
|
+
}
|
|
736
|
+
return base;
|
|
737
|
+
}
|
|
738
|
+
function toolErrorEnvelope(code, message, summary, details) {
|
|
739
|
+
return {
|
|
740
|
+
summary: clampSummary(summary),
|
|
741
|
+
status: "tool_error",
|
|
742
|
+
error: details ? { code, message, details } : { code, message },
|
|
743
|
+
success: false,
|
|
744
|
+
error_code: code,
|
|
745
|
+
error_message: message
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function validationErrorEnvelope(issues) {
|
|
749
|
+
const first = issues[0];
|
|
750
|
+
const summary = first ? `Invalid input: ${first.field} \u2014 ${first.issue}` : "Invalid input";
|
|
751
|
+
return {
|
|
752
|
+
summary: clampSummary(summary),
|
|
753
|
+
status: "validation_error",
|
|
754
|
+
validation_errors: issues,
|
|
755
|
+
error: { code: "INPUT_VALIDATION_ERROR", message: summary },
|
|
756
|
+
success: false,
|
|
757
|
+
error_code: "INPUT_VALIDATION_ERROR",
|
|
758
|
+
error_message: summary
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
async function toWireEnvelope(result, dataStore) {
|
|
762
|
+
if (result.success) {
|
|
763
|
+
const data = result.data ?? {};
|
|
764
|
+
const summary2 = result.summary ?? "Tool executed successfully.";
|
|
765
|
+
return okEnvelope(data, summary2, dataStore);
|
|
766
|
+
}
|
|
767
|
+
const code = result.error?.code ?? "TOOL_INTERNAL_ERROR";
|
|
768
|
+
const message = result.error?.message ?? "Tool execution failed";
|
|
769
|
+
const summary = result.summary ?? message;
|
|
770
|
+
return toolErrorEnvelope(code, message, summary, result.error?.details);
|
|
771
|
+
}
|
|
772
|
+
function rawOkEnvelope(data, summary) {
|
|
773
|
+
return {
|
|
774
|
+
summary: clampSummary(summary),
|
|
775
|
+
status: "ok",
|
|
776
|
+
data,
|
|
777
|
+
success: true,
|
|
778
|
+
error_code: null,
|
|
779
|
+
error_message: null
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function rawToolErrorEnvelope(code, message, summary) {
|
|
783
|
+
return toolErrorEnvelope(code, message, summary);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/tool-server.ts
|
|
787
|
+
var GET_TOOL_DATA = "get_tool_data";
|
|
788
|
+
function isRecord3(value) {
|
|
789
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
790
|
+
}
|
|
791
|
+
function toValidationIssues(issues) {
|
|
792
|
+
return issues.map((issue) => {
|
|
793
|
+
const field = issue.path.length > 0 ? issue.path.join(".") : ".";
|
|
794
|
+
const wire = { field, issue: issue.message };
|
|
795
|
+
if (issue.code) {
|
|
796
|
+
wire.expected = issue.code;
|
|
797
|
+
}
|
|
798
|
+
return wire;
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
function errorMessage(err) {
|
|
802
|
+
return err instanceof Error ? err.message : String(err);
|
|
803
|
+
}
|
|
804
|
+
var ToolServer = class {
|
|
805
|
+
server;
|
|
806
|
+
dataStore;
|
|
807
|
+
#tools = /* @__PURE__ */ new Map();
|
|
808
|
+
#capabilities;
|
|
809
|
+
#sdkVersion;
|
|
810
|
+
constructor(server, options = {}) {
|
|
811
|
+
this.server = server;
|
|
812
|
+
this.dataStore = options.dataStore ?? new InMemoryDataStore();
|
|
813
|
+
this.#capabilities = [...options.capabilities ?? []];
|
|
814
|
+
this.#sdkVersion = options.sdkVersion;
|
|
815
|
+
for (const tool of options.tools ?? []) {
|
|
816
|
+
this.register(tool);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/** Register a tool. Throws on a duplicate name. Chainable. */
|
|
820
|
+
register(tool) {
|
|
821
|
+
if (tool.name === GET_TOOL_DATA) {
|
|
822
|
+
throw new ConvilynAuthorError(`tool name "${GET_TOOL_DATA}" is reserved`);
|
|
823
|
+
}
|
|
824
|
+
if (this.#tools.has(tool.name)) {
|
|
825
|
+
throw new ConvilynAuthorError(`duplicate tool name: ${tool.name}`);
|
|
826
|
+
}
|
|
827
|
+
this.#tools.set(tool.name, tool);
|
|
828
|
+
return this;
|
|
829
|
+
}
|
|
830
|
+
/** Names of the registered (author) tools. */
|
|
831
|
+
get toolNames() {
|
|
832
|
+
return [...this.#tools.keys()];
|
|
833
|
+
}
|
|
834
|
+
/** Number of registered (author) tools — the `/health` `tool_count`. */
|
|
835
|
+
get toolCount() {
|
|
836
|
+
return this.#tools.size;
|
|
837
|
+
}
|
|
838
|
+
/** The compiled manifest blueprint for this server. */
|
|
839
|
+
manifest() {
|
|
840
|
+
const tools = [...this.#tools.values()].map((tool) => tool.toSpec());
|
|
841
|
+
return new ConvilynManifest(this.server, {
|
|
842
|
+
tools,
|
|
843
|
+
capabilities: this.#capabilities,
|
|
844
|
+
sdkVersion: this.#sdkVersion
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Dispatch one JSON-RPC request. Pure and transport-free: never throws for a
|
|
849
|
+
* tool fault — protocol faults become a JSON-RPC `error`, tool/validation
|
|
850
|
+
* faults become a ToolResult envelope inside `result`.
|
|
851
|
+
*/
|
|
852
|
+
async dispatch(request) {
|
|
853
|
+
const id = request.id ?? null;
|
|
854
|
+
if (request.method !== TOOLS_CALL) {
|
|
855
|
+
return rpcError(id, METHOD_NOT_FOUND, `unsupported method: ${request.method}`);
|
|
856
|
+
}
|
|
857
|
+
const params = request.params ?? {};
|
|
858
|
+
const name = params.name;
|
|
859
|
+
if (typeof name !== "string" || name === "") {
|
|
860
|
+
return rpcError(id, INVALID_PARAMS, "params.name is required");
|
|
861
|
+
}
|
|
862
|
+
const args = isRecord3(params.arguments) ? params.arguments : {};
|
|
863
|
+
const ctx = createToolContext(this.dataStore, isRecord3(params.context) ? params.context : {});
|
|
864
|
+
if (name === GET_TOOL_DATA) {
|
|
865
|
+
return this.#dispatchGetToolData(id, args);
|
|
866
|
+
}
|
|
867
|
+
const tool = this.#tools.get(name);
|
|
868
|
+
if (!tool) {
|
|
869
|
+
return rpcError(id, METHOD_NOT_FOUND, `unknown tool: ${name}`);
|
|
870
|
+
}
|
|
871
|
+
const parsed = tool.inputSchema.safeParse(args);
|
|
872
|
+
if (!parsed.success) {
|
|
873
|
+
const issues = toValidationIssues(parsed.error.issues);
|
|
874
|
+
return rpcResult(id, validationErrorEnvelope(issues));
|
|
875
|
+
}
|
|
876
|
+
try {
|
|
877
|
+
const result = await tool.handler(parsed.data, ctx);
|
|
878
|
+
const envelope = await toWireEnvelope(result, this.dataStore);
|
|
879
|
+
return rpcResult(id, envelope);
|
|
880
|
+
} catch (err) {
|
|
881
|
+
return rpcError(id, INTERNAL_ERROR, errorMessage(err));
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
async #dispatchGetToolData(id, args) {
|
|
885
|
+
const refId = args.ref_id;
|
|
886
|
+
if (typeof refId !== "string" || refId === "") {
|
|
887
|
+
return rpcResult(
|
|
888
|
+
id,
|
|
889
|
+
rawToolErrorEnvelope(
|
|
890
|
+
"INPUT_VALIDATION_ERROR",
|
|
891
|
+
"get_tool_data requires a string ref_id",
|
|
892
|
+
"Invalid input: ref_id is required"
|
|
893
|
+
)
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
const stored = await this.dataStore.get(refId);
|
|
897
|
+
if (stored === null) {
|
|
898
|
+
return rpcResult(
|
|
899
|
+
id,
|
|
900
|
+
rawToolErrorEnvelope(
|
|
901
|
+
"DEPENDENCY_NOT_FOUND",
|
|
902
|
+
`no data stored for ref_id ${refId}`,
|
|
903
|
+
`No data for ${refId}`
|
|
904
|
+
)
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
return rpcResult(id, rawOkEnvelope(stored, `Retrieved data for ${refId}`));
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
var ENV_DEV_INSECURE = "CONVILYN_DEV_INSECURE";
|
|
911
|
+
var DEV_INSECURE_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
912
|
+
function devInsecureRequested(env = process.env) {
|
|
913
|
+
return DEV_INSECURE_TRUTHY.has((env[ENV_DEV_INSECURE] ?? "").trim().toLowerCase());
|
|
914
|
+
}
|
|
915
|
+
var JSON_HEADERS = { "content-type": "application/json" };
|
|
916
|
+
function sendJson(res, statusCode, body) {
|
|
917
|
+
res.writeHead(statusCode, JSON_HEADERS);
|
|
918
|
+
res.end(JSON.stringify(body));
|
|
919
|
+
}
|
|
920
|
+
var MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
921
|
+
var BodyTooLargeError = class extends Error {
|
|
922
|
+
constructor() {
|
|
923
|
+
super(`Request body exceeds ${MAX_BODY_BYTES} bytes`);
|
|
924
|
+
this.name = "BodyTooLargeError";
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
function readBody(req, maxBytes = MAX_BODY_BYTES) {
|
|
928
|
+
return new Promise((resolve, reject) => {
|
|
929
|
+
const chunks = [];
|
|
930
|
+
let total = 0;
|
|
931
|
+
req.on("data", (chunk) => {
|
|
932
|
+
total += chunk.length;
|
|
933
|
+
if (total > maxBytes) {
|
|
934
|
+
chunks.length = 0;
|
|
935
|
+
reject(new BodyTooLargeError());
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
chunks.push(chunk);
|
|
939
|
+
});
|
|
940
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
941
|
+
req.on("error", reject);
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
async function handleMcp(server, req, res, secret, toleranceSeconds, allowInsecure) {
|
|
945
|
+
let body;
|
|
946
|
+
try {
|
|
947
|
+
body = await readBody(req);
|
|
948
|
+
} catch (err) {
|
|
949
|
+
if (err instanceof BodyTooLargeError) {
|
|
950
|
+
res.once("finish", () => req.destroy());
|
|
951
|
+
sendJson(res, 413, {
|
|
952
|
+
code: "PAYLOAD_TOO_LARGE",
|
|
953
|
+
message: "Request body too large"
|
|
954
|
+
});
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
throw err;
|
|
958
|
+
}
|
|
959
|
+
if (secret === "" && allowInsecure) {
|
|
960
|
+
await dispatchParsed(server, res, body);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
try {
|
|
964
|
+
verifySignature(secret, body, req.headers, {
|
|
965
|
+
toleranceSeconds
|
|
966
|
+
});
|
|
967
|
+
} catch (err) {
|
|
968
|
+
if (err instanceof InvalidSignatureError) {
|
|
969
|
+
sendJson(res, 401, {
|
|
970
|
+
code: "INVALID_SIGNATURE",
|
|
971
|
+
message: "Request signature could not be verified"
|
|
972
|
+
});
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
throw err;
|
|
976
|
+
}
|
|
977
|
+
await dispatchParsed(server, res, body);
|
|
978
|
+
}
|
|
979
|
+
async function dispatchParsed(server, res, body) {
|
|
980
|
+
let parsed;
|
|
981
|
+
try {
|
|
982
|
+
parsed = JSON.parse(body.toString("utf8"));
|
|
983
|
+
} catch {
|
|
984
|
+
sendJson(res, 400, { error: "Invalid JSON" });
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const request = asRpcRequest(parsed);
|
|
988
|
+
if (request === null) {
|
|
989
|
+
sendJson(res, 400, { error: "Invalid JSON" });
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
const response = await server.dispatch(request);
|
|
993
|
+
sendJson(res, 200, response);
|
|
994
|
+
}
|
|
995
|
+
function serve(toolServer, options = {}) {
|
|
996
|
+
const config = options.config ?? SDKConfig.fromEnv();
|
|
997
|
+
const host = options.host ?? config.serverHost;
|
|
998
|
+
const port = options.port ?? config.serverPort;
|
|
999
|
+
const secret = options.hmacSecret ?? config.hmacSecret;
|
|
1000
|
+
const toleranceSeconds = options.toleranceSeconds ?? config.hmacToleranceSeconds;
|
|
1001
|
+
const allowInsecure = options.allowInsecure ?? devInsecureRequested();
|
|
1002
|
+
if (secret === "" && !allowInsecure) {
|
|
1003
|
+
throw new ConvilynStartupError(
|
|
1004
|
+
`CONVILYN_HMAC_SECRET is required to serve inbound /mcp traffic. Refusing to start without signature verification. For local development use \`convilyn-author dev\` or set ${ENV_DEV_INSECURE}=1 (INSECURE \u2014 local only).`
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
if (secret === "") {
|
|
1008
|
+
console.warn(
|
|
1009
|
+
"convilyn-author: CONVILYN_HMAC_SECRET not set; serving /mcp WITHOUT signature verification (insecure local dev only \u2014 do NOT deploy this configuration)"
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
const httpServer = createServer((req, res) => {
|
|
1013
|
+
void route(toolServer, req, res, secret, toleranceSeconds, allowInsecure).catch(() => {
|
|
1014
|
+
if (!res.headersSent) {
|
|
1015
|
+
sendJson(res, 500, { error: "Internal server error" });
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
httpServer.listen(port, host);
|
|
1020
|
+
return httpServer;
|
|
1021
|
+
}
|
|
1022
|
+
async function route(toolServer, req, res, secret, toleranceSeconds, allowInsecure) {
|
|
1023
|
+
const path = (req.url ?? "").split("?")[0];
|
|
1024
|
+
const method = req.method ?? "GET";
|
|
1025
|
+
if (method === "GET" && path === "/health") {
|
|
1026
|
+
sendJson(res, 200, {
|
|
1027
|
+
status: "healthy",
|
|
1028
|
+
server: toolServer.server.name,
|
|
1029
|
+
version: VERSION,
|
|
1030
|
+
tool_count: toolServer.toolCount
|
|
1031
|
+
});
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
if (method === "GET" && path === "/manifest") {
|
|
1035
|
+
sendJson(res, 200, toolServer.manifest().toWire());
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
if (method === "POST" && path === "/mcp") {
|
|
1039
|
+
await handleMcp(toolServer, req, res, secret, toleranceSeconds, allowInsecure);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
sendJson(res, 404, { error: "Not found" });
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/compliance.ts
|
|
1046
|
+
function isRecord4(value) {
|
|
1047
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1048
|
+
}
|
|
1049
|
+
function pass(checkName, message) {
|
|
1050
|
+
return { passed: true, checkName, message };
|
|
1051
|
+
}
|
|
1052
|
+
function fail(checkName, message) {
|
|
1053
|
+
return { passed: false, checkName, message };
|
|
1054
|
+
}
|
|
1055
|
+
function checkServerMetadata(server) {
|
|
1056
|
+
const { name, version, description } = server.server;
|
|
1057
|
+
if (!name || !version || !description) {
|
|
1058
|
+
return fail("server_metadata", "server name, version, and description must all be non-empty");
|
|
1059
|
+
}
|
|
1060
|
+
return pass("server_metadata", "server metadata is complete");
|
|
1061
|
+
}
|
|
1062
|
+
function checkToolsExist(server) {
|
|
1063
|
+
return server.toolCount > 0 ? pass("tools_exist", `server has ${server.toolCount} tool(s)`) : fail("tools_exist", "server has no registered tools");
|
|
1064
|
+
}
|
|
1065
|
+
function checkToolSchemas(server) {
|
|
1066
|
+
for (const tool of server.manifest().tools) {
|
|
1067
|
+
if (!tool.name || !tool.description) {
|
|
1068
|
+
return fail("tool_schemas", `tool "${tool.name}" is missing a name or description`);
|
|
1069
|
+
}
|
|
1070
|
+
if (!isRecord4(tool.inputSchema) || tool.inputSchema.type !== "object") {
|
|
1071
|
+
return fail("tool_schemas", `tool "${tool.name}" input_schema is not a JSON-Schema object`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return pass("tool_schemas", "all tool schemas are valid");
|
|
1075
|
+
}
|
|
1076
|
+
function checkManifestSynth(server) {
|
|
1077
|
+
try {
|
|
1078
|
+
const wire = server.manifest().toWire();
|
|
1079
|
+
return isRecord4(wire) ? pass("manifest_synth", "manifest compiles to a wire object") : fail("manifest_synth", "manifest did not compile to an object");
|
|
1080
|
+
} catch (err) {
|
|
1081
|
+
return fail("manifest_synth", `manifest synth threw: ${err.message}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
async function checkRefIdPattern(server) {
|
|
1085
|
+
const refId = await server.dataStore.store({ __compliance_probe__: true });
|
|
1086
|
+
return /^td_[0-9a-f]{12}$/.test(refId) ? pass("ref_id_pattern", "data store returns a td_<12-hex> ref_id") : fail("ref_id_pattern", `ref_id "${refId}" does not match td_<12-hex>`);
|
|
1087
|
+
}
|
|
1088
|
+
async function checkGetToolData(server) {
|
|
1089
|
+
const refId = await server.dataStore.store({ probe: 1 });
|
|
1090
|
+
const response = await server.dispatch({
|
|
1091
|
+
jsonrpc: "2.0",
|
|
1092
|
+
method: TOOLS_CALL,
|
|
1093
|
+
params: { name: GET_TOOL_DATA, arguments: { ref_id: refId } },
|
|
1094
|
+
id: "compliance"
|
|
1095
|
+
});
|
|
1096
|
+
const result = response.result;
|
|
1097
|
+
if (isRecord4(result) && result.status === "ok") {
|
|
1098
|
+
return pass("get_tool_data", "built-in get_tool_data resolves a stored ref_id");
|
|
1099
|
+
}
|
|
1100
|
+
return fail("get_tool_data", "built-in get_tool_data did not resolve a stored ref_id");
|
|
1101
|
+
}
|
|
1102
|
+
async function runComplianceChecks(server) {
|
|
1103
|
+
const results = [
|
|
1104
|
+
checkServerMetadata(server),
|
|
1105
|
+
checkToolsExist(server),
|
|
1106
|
+
checkToolSchemas(server),
|
|
1107
|
+
checkManifestSynth(server),
|
|
1108
|
+
await checkRefIdPattern(server),
|
|
1109
|
+
await checkGetToolData(server)
|
|
1110
|
+
];
|
|
1111
|
+
return { results };
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/client.ts
|
|
1115
|
+
function withApiV1(platformUrl) {
|
|
1116
|
+
const trimmed = platformUrl.replace(/\/+$/, "");
|
|
1117
|
+
return trimmed.endsWith("/api/v1") ? trimmed : `${trimmed}/api/v1`;
|
|
1118
|
+
}
|
|
1119
|
+
var CONSUMER_KEY_PREFIX = "ck_";
|
|
1120
|
+
function isConsumerKey(key) {
|
|
1121
|
+
return key.startsWith(CONSUMER_KEY_PREFIX);
|
|
1122
|
+
}
|
|
1123
|
+
function maskKey(key) {
|
|
1124
|
+
return key.length <= 8 ? "***" : `${key.slice(0, 4)}\u2026${key.slice(-2)}`;
|
|
1125
|
+
}
|
|
1126
|
+
var ConvilynClient = class {
|
|
1127
|
+
baseUrl;
|
|
1128
|
+
#apiKey;
|
|
1129
|
+
#fetch;
|
|
1130
|
+
constructor(options = {}) {
|
|
1131
|
+
const config = options.config ?? SDKConfig.fromEnv();
|
|
1132
|
+
this.baseUrl = options.baseUrl ?? withApiV1(config.platformUrl);
|
|
1133
|
+
this.#apiKey = options.apiKey ?? config.apiKey;
|
|
1134
|
+
if (this.#apiKey && isConsumerKey(this.#apiKey)) {
|
|
1135
|
+
throw new ConvilynAuthorError(
|
|
1136
|
+
`${maskKey(this.#apiKey)} looks like a Convilyn consumer API key ("${CONSUMER_KEY_PREFIX}"), not an Author SDK / developer-portal token. The Author SDK authenticates with a cvl_ developer key \u2014 call register() to mint one, or set CONVILYN_API_KEY to your cvl_ key. (Consumer keys call the data-plane API; they do not publish workflows / tools.)`
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
1140
|
+
}
|
|
1141
|
+
/** The `cvl_` developer key currently in use (empty until set or registered). */
|
|
1142
|
+
get apiKey() {
|
|
1143
|
+
return this.#apiKey;
|
|
1144
|
+
}
|
|
1145
|
+
#headers(auth) {
|
|
1146
|
+
const headers = { "content-type": "application/json" };
|
|
1147
|
+
if (auth) {
|
|
1148
|
+
if (!this.#apiKey) {
|
|
1149
|
+
throw new ConvilynAuthorError(
|
|
1150
|
+
"No developer API key set. Provide `apiKey`, set CONVILYN_API_KEY to a cvl_ developer key, or call register() first."
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
headers.authorization = `Bearer ${this.#apiKey}`;
|
|
1154
|
+
}
|
|
1155
|
+
return headers;
|
|
1156
|
+
}
|
|
1157
|
+
async #request(method, path, body, auth = true) {
|
|
1158
|
+
const res = await this.#fetch(`${this.baseUrl}${path}`, {
|
|
1159
|
+
method,
|
|
1160
|
+
headers: this.#headers(auth),
|
|
1161
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
1162
|
+
});
|
|
1163
|
+
if (!res.ok) {
|
|
1164
|
+
let parsed;
|
|
1165
|
+
try {
|
|
1166
|
+
parsed = await res.json();
|
|
1167
|
+
} catch {
|
|
1168
|
+
parsed = void 0;
|
|
1169
|
+
}
|
|
1170
|
+
throw new ConvilynApiError(
|
|
1171
|
+
res.status,
|
|
1172
|
+
`${method} ${path} failed (HTTP ${res.status})`,
|
|
1173
|
+
parsed
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
if (res.status === 204) {
|
|
1177
|
+
return void 0;
|
|
1178
|
+
}
|
|
1179
|
+
return await res.json();
|
|
1180
|
+
}
|
|
1181
|
+
// ── Developer registration ──────────────────────────────────────
|
|
1182
|
+
/**
|
|
1183
|
+
* `POST /developers/register` — create a developer account and mint a `cvl_`
|
|
1184
|
+
* key. No auth required. The returned key is captured on this client for
|
|
1185
|
+
* subsequent calls (and returned so the caller can persist it — it is shown
|
|
1186
|
+
* only once).
|
|
1187
|
+
*/
|
|
1188
|
+
async register(request) {
|
|
1189
|
+
const result = await this.#request(
|
|
1190
|
+
"POST",
|
|
1191
|
+
"/developers/register",
|
|
1192
|
+
request,
|
|
1193
|
+
false
|
|
1194
|
+
);
|
|
1195
|
+
if (result.api_key && !this.#apiKey) {
|
|
1196
|
+
this.#apiKey = result.api_key;
|
|
1197
|
+
}
|
|
1198
|
+
return result;
|
|
1199
|
+
}
|
|
1200
|
+
// ── Server operations ───────────────────────────────────────────
|
|
1201
|
+
/** `POST /developers/servers` — submit a tool-server manifest for verification. */
|
|
1202
|
+
async submitServer(request) {
|
|
1203
|
+
return this.#request("POST", "/developers/servers", request);
|
|
1204
|
+
}
|
|
1205
|
+
/** `GET /developers/servers` — list this developer's servers. */
|
|
1206
|
+
async listServers() {
|
|
1207
|
+
return this.#request("GET", "/developers/servers");
|
|
1208
|
+
}
|
|
1209
|
+
/** `GET /developers/servers/{id}/status` — verification status of a server. */
|
|
1210
|
+
async serverStatus(serverId) {
|
|
1211
|
+
return this.#request(
|
|
1212
|
+
"GET",
|
|
1213
|
+
`/developers/servers/${encodeURIComponent(serverId)}/status`
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
/** `POST /developers/servers/{id}/test` — trigger a sandbox test run. */
|
|
1217
|
+
async testServer(serverId, slots = {}) {
|
|
1218
|
+
return this.#request(
|
|
1219
|
+
"POST",
|
|
1220
|
+
`/developers/servers/${encodeURIComponent(serverId)}/test`,
|
|
1221
|
+
{ slots }
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
/** `DELETE /developers/servers/{id}` — deactivate a server. */
|
|
1225
|
+
async deactivateServer(serverId) {
|
|
1226
|
+
await this.#request("DELETE", `/developers/servers/${encodeURIComponent(serverId)}`);
|
|
1227
|
+
}
|
|
1228
|
+
// ── Workflow operations ─────────────────────────────────────────
|
|
1229
|
+
/** `POST /developers/workflows` — submit a compiled workflow spec + its server ids. */
|
|
1230
|
+
async submitWorkflow(request) {
|
|
1231
|
+
return this.#request("POST", "/developers/workflows", request);
|
|
1232
|
+
}
|
|
1233
|
+
/** `GET /developers/workflows` — list this developer's workflows. */
|
|
1234
|
+
async listWorkflows() {
|
|
1235
|
+
return this.#request("GET", "/developers/workflows");
|
|
1236
|
+
}
|
|
1237
|
+
/** `GET /developers/workflows/{id}/status` — verification status of a workflow. */
|
|
1238
|
+
async workflowStatus(workflowId) {
|
|
1239
|
+
return this.#request(
|
|
1240
|
+
"GET",
|
|
1241
|
+
`/developers/workflows/${encodeURIComponent(workflowId)}/status`
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
/** `POST /developers/workflows/{id}/test` — sandbox-test a submitted workflow. */
|
|
1245
|
+
async testWorkflow(workflowId, testInput = {}) {
|
|
1246
|
+
return this.#request(
|
|
1247
|
+
"POST",
|
|
1248
|
+
`/developers/workflows/${encodeURIComponent(workflowId)}/test`,
|
|
1249
|
+
{ test_input: testInput }
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
/** `DELETE /developers/workflows/{id}` — deactivate a workflow. */
|
|
1253
|
+
async deactivateWorkflow(workflowId) {
|
|
1254
|
+
await this.#request("DELETE", `/developers/workflows/${encodeURIComponent(workflowId)}`);
|
|
1255
|
+
}
|
|
1256
|
+
// ── Convilyn-Hosted Author Runtime (port of the Python client) ─────────────
|
|
1257
|
+
/**
|
|
1258
|
+
* `POST /developers/runtimes/hosted` — deploy a tool server into the
|
|
1259
|
+
* Convilyn-Hosted Author Runtime. Counterpart to {@link submitServer}:
|
|
1260
|
+
* instead of registering a caller-deployed endpoint, the platform
|
|
1261
|
+
* provisions a sandboxed Lambda and returns its public endpoint URL.
|
|
1262
|
+
*
|
|
1263
|
+
* Backends without the hosted stack answer 503 `HOSTED_NOT_AVAILABLE`
|
|
1264
|
+
* (older builds: 501); environments with the router unmounted answer 404.
|
|
1265
|
+
* All surface as {@link ConvilynApiError} — catch and fall back to
|
|
1266
|
+
* `push --endpoint-url` (BYO) when a hard guarantee is needed.
|
|
1267
|
+
*/
|
|
1268
|
+
async deployHostedRuntime(manifest, options) {
|
|
1269
|
+
const body = { manifest, region: options.region };
|
|
1270
|
+
if (options.workflowSpec !== void 0) {
|
|
1271
|
+
body.workflow_spec = options.workflowSpec;
|
|
1272
|
+
}
|
|
1273
|
+
return this.#request("POST", "/developers/runtimes/hosted", body);
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* `POST /developers/runtimes/{id}/rollback` — atomically flip the runtime
|
|
1277
|
+
* back to its previous active version.
|
|
1278
|
+
*/
|
|
1279
|
+
async rollbackHostedRuntime(runtimeId) {
|
|
1280
|
+
return this.#request(
|
|
1281
|
+
"POST",
|
|
1282
|
+
`/developers/runtimes/${encodeURIComponent(runtimeId)}/rollback`
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* `GET /developers/runtimes/{id}/logs` — recent runtime log lines.
|
|
1287
|
+
* `since` is an ISO-8601 timestamp or relative shorthand (`"5m"`, `"1h"`)
|
|
1288
|
+
* interpreted server-side; `limit` defaults to 100 (server caps ~1000).
|
|
1289
|
+
* Accepts either a bare list or an `{entries: [...]}` envelope (mirror of
|
|
1290
|
+
* the Python client) so the SDK survives the wire shape stabilising.
|
|
1291
|
+
*/
|
|
1292
|
+
async getHostedRuntimeLogs(runtimeId, options = {}) {
|
|
1293
|
+
const limit = Math.trunc(options.limit ?? 100);
|
|
1294
|
+
let path = `/developers/runtimes/${encodeURIComponent(runtimeId)}/logs?limit=${limit}`;
|
|
1295
|
+
if (options.since) {
|
|
1296
|
+
path = `${path}&since=${encodeURIComponent(options.since)}`;
|
|
1297
|
+
}
|
|
1298
|
+
const result = await this.#request("GET", path);
|
|
1299
|
+
if (Array.isArray(result)) {
|
|
1300
|
+
return result;
|
|
1301
|
+
}
|
|
1302
|
+
if (typeof result === "object" && result !== null && Array.isArray(result.entries)) {
|
|
1303
|
+
return result.entries;
|
|
1304
|
+
}
|
|
1305
|
+
return [];
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
|
|
1309
|
+
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 };
|
|
1310
|
+
//# sourceMappingURL=chunk-ZNZXT5ZP.js.map
|
|
1311
|
+
//# sourceMappingURL=chunk-ZNZXT5ZP.js.map
|