@zackbart/connecta 0.15.1 → 0.16.1
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/AGENTS.md +40 -20
- package/CHANGELOG.md +653 -0
- package/README.md +25 -5
- package/bin/connecta.mjs +44 -3
- package/dist/access-tokens.d.ts +0 -1
- package/dist/access-tokens.js +0 -1
- package/dist/activity.d.ts +50 -2
- package/dist/activity.js +34 -1
- package/dist/apps-shell.d.ts +0 -1
- package/dist/apps-shell.js +0 -1
- package/dist/auth/bearer.d.ts +0 -1
- package/dist/auth/bearer.js +0 -1
- package/dist/auth/clerk.d.ts +0 -1
- package/dist/auth/clerk.js +45 -9
- package/dist/auth/downstream-oauth.d.ts +0 -1
- package/dist/auth/downstream-oauth.js +0 -1
- package/dist/call-admission.d.ts +0 -1
- package/dist/call-admission.js +0 -1
- package/dist/catalog-drift.d.ts +99 -0
- package/dist/catalog-drift.js +251 -0
- package/dist/catalog-fingerprint.d.ts +0 -1
- package/dist/catalog-fingerprint.js +0 -1
- package/dist/catalog-limits.d.ts +0 -1
- package/dist/catalog-limits.js +0 -1
- package/dist/catalog-service.d.ts +0 -1
- package/dist/catalog-service.js +41 -5
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +20 -1
- package/dist/concurrency.d.ts +0 -1
- package/dist/concurrency.js +0 -1
- package/dist/connector-scope.d.ts +0 -1
- package/dist/connector-scope.js +0 -1
- package/dist/connectors/api.d.ts +29 -21
- package/dist/connectors/api.js +45 -19
- package/dist/connectors/guarded-fetch.d.ts +106 -0
- package/dist/connectors/guarded-fetch.js +289 -0
- package/dist/connectors/remote-mcp.d.ts +0 -1
- package/dist/connectors/remote-mcp.js +0 -1
- package/dist/credentials.d.ts +0 -1
- package/dist/credentials.js +0 -1
- package/dist/errors.d.ts +30 -3
- package/dist/errors.js +1 -1
- package/dist/execute.d.ts +0 -1
- package/dist/execute.js +0 -1
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +28 -1
- package/dist/executor-result.d.ts +0 -1
- package/dist/executor-result.js +0 -1
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-child.js +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -1
- package/dist/executors/quickjs-protocol.js +0 -1
- package/dist/executors/quickjs-runtime.d.ts +0 -1
- package/dist/executors/quickjs-runtime.js +0 -1
- package/dist/executors/quickjs.d.ts +0 -1
- package/dist/executors/quickjs.js +2 -1
- package/dist/favicon.d.ts +0 -1
- package/dist/favicon.js +0 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +24 -7
- package/dist/invocation.d.ts +0 -1
- package/dist/invocation.js +0 -1
- package/dist/json-schema.d.ts +0 -1
- package/dist/json-schema.js +0 -1
- package/dist/mcp-result.d.ts +0 -1
- package/dist/mcp-result.js +0 -1
- package/dist/meta-tools.d.ts +0 -1
- package/dist/meta-tools.js +0 -1
- package/dist/node.d.ts +0 -1
- package/dist/node.js +0 -1
- package/dist/operator-ui/generated.d.ts +2 -3
- package/dist/operator-ui/generated.js +3 -4
- package/dist/operator-ui/model.d.ts +10 -1
- package/dist/operator-ui/model.js +0 -1
- package/dist/operator-ui/view.d.ts +167 -0
- package/dist/operator-ui/view.js +270 -0
- package/dist/providers/cloudflare.d.ts +0 -1
- package/dist/providers/cloudflare.js +261 -299
- package/dist/providers/linear.d.ts +26 -5
- package/dist/providers/linear.js +32 -61
- package/dist/providers/mixpanel.d.ts +35 -4
- package/dist/providers/mixpanel.js +53 -79
- package/dist/providers/notion.d.ts +0 -1
- package/dist/providers/notion.js +87 -42
- package/dist/providers/stripe.d.ts +13 -1
- package/dist/providers/stripe.js +31 -60
- package/dist/registry.d.ts +34 -2
- package/dist/registry.js +85 -5
- package/dist/retired-toolkits.d.ts +0 -1
- package/dist/retired-toolkits.js +0 -1
- package/dist/routes/access-tokens.d.ts +0 -1
- package/dist/routes/access-tokens.js +0 -1
- package/dist/routes/activity.d.ts +0 -1
- package/dist/routes/activity.js +0 -1
- package/dist/routes/credentials.d.ts +0 -1
- package/dist/routes/credentials.js +0 -1
- package/dist/routes/mcp.d.ts +0 -1
- package/dist/routes/mcp.js +0 -1
- package/dist/routes/oauth.d.ts +0 -1
- package/dist/routes/oauth.js +0 -1
- package/dist/routes/shared.d.ts +2 -1
- package/dist/routes/shared.js +0 -1
- package/dist/routes/ui.d.ts +0 -1
- package/dist/routes/ui.js +0 -1
- package/dist/server.d.ts +0 -1
- package/dist/server.js +11 -1
- package/dist/skills.d.ts +0 -1
- package/dist/skills.js +0 -1
- package/dist/storage/file.d.ts +0 -1
- package/dist/storage/file.js +0 -1
- package/dist/storage/memory.d.ts +0 -1
- package/dist/storage/memory.js +0 -1
- package/dist/timeout.d.ts +0 -1
- package/dist/timeout.js +0 -1
- package/dist/tool-safety.d.ts +0 -1
- package/dist/tool-safety.js +0 -1
- package/dist/types.d.ts +45 -1
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +0 -1
- package/dist/ui.js +13 -137
- package/dist/validate.d.ts +13 -15
- package/dist/validate.js +24 -11
- package/dist/version.d.ts +1 -2
- package/dist/version.js +1 -2
- package/documentation/architecture.md +186 -5
- package/documentation/auth.md +11 -0
- package/documentation/call-admission.md +142 -6
- package/documentation/cloudflare.md +92 -10
- package/documentation/code-first-exploration.md +2 -2
- package/documentation/code-mode.md +14 -14
- package/documentation/connector-guides.md +172 -5
- package/documentation/connectors.md +89 -0
- package/documentation/linear.md +20 -2
- package/documentation/mcp-ui-design.md +2 -2
- package/documentation/meta-tools.md +7 -1
- package/documentation/mixpanel.md +42 -9
- package/documentation/notion.md +23 -2
- package/documentation/operations.md +325 -5
- package/documentation/operator-ui.md +120 -5
- package/documentation/provider-audit.md +160 -0
- package/documentation/provider-conventions.md +667 -0
- package/documentation/request-admission.md +167 -5
- package/documentation/rich-output-design.md +1 -2
- package/documentation/stripe.md +15 -0
- package/documentation/upgrading.md +454 -0
- package/ethos.md +14 -9
- package/examples/worker/README.md +102 -12
- package/examples/worker/src/index.ts +46 -1
- package/examples/worker/wrangler.jsonc +14 -1
- package/package.json +10 -6
- package/templates/node/.dockerignore +10 -0
- package/templates/node/.env.example +40 -3
- package/templates/node/AGENTS.md +16 -1
- package/templates/node/Dockerfile +49 -0
- package/templates/node/README.md +117 -2
- package/templates/node/docker-compose.yml +39 -0
- package/templates/node/package.json +1 -1
- package/templates/node/src/file-activity.ts +182 -0
- package/templates/node/src/index.ts +80 -4
- package/assets/connecta-clay-hero.png +0 -0
- package/dist/access-tokens.d.ts.map +0 -1
- package/dist/access-tokens.js.map +0 -1
- package/dist/activity.d.ts.map +0 -1
- package/dist/activity.js.map +0 -1
- package/dist/apps-shell.d.ts.map +0 -1
- package/dist/apps-shell.js.map +0 -1
- package/dist/auth/bearer.d.ts.map +0 -1
- package/dist/auth/bearer.js.map +0 -1
- package/dist/auth/clerk.d.ts.map +0 -1
- package/dist/auth/clerk.js.map +0 -1
- package/dist/auth/downstream-oauth.d.ts.map +0 -1
- package/dist/auth/downstream-oauth.js.map +0 -1
- package/dist/call-admission.d.ts.map +0 -1
- package/dist/call-admission.js.map +0 -1
- package/dist/catalog-fingerprint.d.ts.map +0 -1
- package/dist/catalog-fingerprint.js.map +0 -1
- package/dist/catalog-limits.d.ts.map +0 -1
- package/dist/catalog-limits.js.map +0 -1
- package/dist/catalog-service.d.ts.map +0 -1
- package/dist/catalog-service.js.map +0 -1
- package/dist/catalog.d.ts.map +0 -1
- package/dist/catalog.js.map +0 -1
- package/dist/concurrency.d.ts.map +0 -1
- package/dist/concurrency.js.map +0 -1
- package/dist/connector-scope.d.ts.map +0 -1
- package/dist/connector-scope.js.map +0 -1
- package/dist/connectors/api.d.ts.map +0 -1
- package/dist/connectors/api.js.map +0 -1
- package/dist/connectors/remote-mcp.d.ts.map +0 -1
- package/dist/connectors/remote-mcp.js.map +0 -1
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js.map +0 -1
- package/dist/executor-admission.d.ts.map +0 -1
- package/dist/executor-admission.js.map +0 -1
- package/dist/executor-result.d.ts.map +0 -1
- package/dist/executor-result.js.map +0 -1
- package/dist/executors/quickjs-child.d.ts.map +0 -1
- package/dist/executors/quickjs-child.js.map +0 -1
- package/dist/executors/quickjs-protocol.d.ts.map +0 -1
- package/dist/executors/quickjs-protocol.js.map +0 -1
- package/dist/executors/quickjs-runtime.d.ts.map +0 -1
- package/dist/executors/quickjs-runtime.js.map +0 -1
- package/dist/executors/quickjs.d.ts.map +0 -1
- package/dist/executors/quickjs.js.map +0 -1
- package/dist/favicon.d.ts.map +0 -1
- package/dist/favicon.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invocation.d.ts.map +0 -1
- package/dist/invocation.js.map +0 -1
- package/dist/json-schema.d.ts.map +0 -1
- package/dist/json-schema.js.map +0 -1
- package/dist/mcp-result.d.ts.map +0 -1
- package/dist/mcp-result.js.map +0 -1
- package/dist/meta-tools.d.ts.map +0 -1
- package/dist/meta-tools.js.map +0 -1
- package/dist/node.d.ts.map +0 -1
- package/dist/node.js.map +0 -1
- package/dist/operator-ui/generated.d.ts.map +0 -1
- package/dist/operator-ui/generated.js.map +0 -1
- package/dist/operator-ui/model.d.ts.map +0 -1
- package/dist/operator-ui/model.js.map +0 -1
- package/dist/providers/cloudflare.d.ts.map +0 -1
- package/dist/providers/cloudflare.js.map +0 -1
- package/dist/providers/linear.d.ts.map +0 -1
- package/dist/providers/linear.js.map +0 -1
- package/dist/providers/mixpanel.d.ts.map +0 -1
- package/dist/providers/mixpanel.js.map +0 -1
- package/dist/providers/notion.d.ts.map +0 -1
- package/dist/providers/notion.js.map +0 -1
- package/dist/providers/stripe.d.ts.map +0 -1
- package/dist/providers/stripe.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/retired-toolkits.d.ts.map +0 -1
- package/dist/retired-toolkits.js.map +0 -1
- package/dist/routes/access-tokens.d.ts.map +0 -1
- package/dist/routes/access-tokens.js.map +0 -1
- package/dist/routes/activity.d.ts.map +0 -1
- package/dist/routes/activity.js.map +0 -1
- package/dist/routes/credentials.d.ts.map +0 -1
- package/dist/routes/credentials.js.map +0 -1
- package/dist/routes/mcp.d.ts.map +0 -1
- package/dist/routes/mcp.js.map +0 -1
- package/dist/routes/oauth.d.ts.map +0 -1
- package/dist/routes/oauth.js.map +0 -1
- package/dist/routes/shared.d.ts.map +0 -1
- package/dist/routes/shared.js.map +0 -1
- package/dist/routes/ui.d.ts.map +0 -1
- package/dist/routes/ui.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js.map +0 -1
- package/dist/storage/file.d.ts.map +0 -1
- package/dist/storage/file.js.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js.map +0 -1
- package/dist/timeout.d.ts.map +0 -1
- package/dist/timeout.js.map +0 -1
- package/dist/tool-safety.d.ts.map +0 -1
- package/dist/tool-safety.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/ui.d.ts.map +0 -1
- package/dist/ui.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
- package/examples/node/README.md +0 -52
- package/examples/node/src/index.ts +0 -71
- package/src/access-tokens.ts +0 -289
- package/src/activity.ts +0 -234
- package/src/apps-shell.ts +0 -370
- package/src/auth/bearer.ts +0 -61
- package/src/auth/clerk.ts +0 -553
- package/src/auth/downstream-oauth.ts +0 -575
- package/src/call-admission.ts +0 -521
- package/src/catalog-fingerprint.ts +0 -52
- package/src/catalog-limits.ts +0 -11
- package/src/catalog-service.ts +0 -1153
- package/src/catalog.ts +0 -790
- package/src/concurrency.ts +0 -39
- package/src/connector-scope.ts +0 -66
- package/src/connectors/api.ts +0 -159
- package/src/connectors/remote-mcp.ts +0 -1090
- package/src/credentials.ts +0 -428
- package/src/errors.ts +0 -364
- package/src/execute.ts +0 -1406
- package/src/executor-admission.ts +0 -328
- package/src/executor-result.ts +0 -107
- package/src/executors/quickjs-child.ts +0 -172
- package/src/executors/quickjs-protocol.ts +0 -99
- package/src/executors/quickjs-runtime.ts +0 -470
- package/src/executors/quickjs.ts +0 -776
- package/src/favicon.ts +0 -53
- package/src/index.ts +0 -666
- package/src/invocation.ts +0 -655
- package/src/json-schema.ts +0 -11
- package/src/mcp-result.ts +0 -43
- package/src/meta-tools.ts +0 -1607
- package/src/node.ts +0 -206
- package/src/operator-ui/browser.css +0 -423
- package/src/operator-ui/browser.ts +0 -1281
- package/src/operator-ui/generated.ts +0 -4
- package/src/operator-ui/model.ts +0 -111
- package/src/providers/cloudflare.ts +0 -3999
- package/src/providers/linear.ts +0 -302
- package/src/providers/mixpanel.ts +0 -229
- package/src/providers/notion.ts +0 -1879
- package/src/providers/stripe.ts +0 -307
- package/src/registry.ts +0 -1085
- package/src/retired-toolkits.ts +0 -14
- package/src/routes/access-tokens.ts +0 -115
- package/src/routes/activity.ts +0 -215
- package/src/routes/credentials.ts +0 -254
- package/src/routes/mcp.ts +0 -429
- package/src/routes/oauth.ts +0 -322
- package/src/routes/shared.ts +0 -247
- package/src/routes/ui.ts +0 -152
- package/src/server.ts +0 -195
- package/src/skills.ts +0 -271
- package/src/storage/file.ts +0 -123
- package/src/storage/memory.ts +0 -44
- package/src/timeout.ts +0 -84
- package/src/tool-safety.ts +0 -15
- package/src/types.ts +0 -523
- package/src/ui.ts +0 -744
- package/src/validate.ts +0 -387
- package/src/version.ts +0 -7
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AdmittingExecutor,
|
|
3
|
-
AdmissionSnapshot,
|
|
4
|
-
Executor,
|
|
5
|
-
ExecutorLease,
|
|
6
|
-
} from "./types.js";
|
|
7
|
-
|
|
8
|
-
export type ExecutorAdmissionErrorCode =
|
|
9
|
-
| "executor_overloaded"
|
|
10
|
-
| "executor_cancelled"
|
|
11
|
-
| "executor_closed";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A stable, machine-readable admission failure. Overload is retryable; caller
|
|
15
|
-
* cancellation and shutdown are terminal for this invocation.
|
|
16
|
-
*/
|
|
17
|
-
export class ExecutorAdmissionError extends Error {
|
|
18
|
-
readonly retryable: boolean;
|
|
19
|
-
readonly retryAfterMs?: number;
|
|
20
|
-
|
|
21
|
-
constructor(
|
|
22
|
-
readonly code: ExecutorAdmissionErrorCode,
|
|
23
|
-
message: string,
|
|
24
|
-
opts: { retryAfterMs?: number } = {},
|
|
25
|
-
) {
|
|
26
|
-
super(message);
|
|
27
|
-
this.name = "ExecutorAdmissionError";
|
|
28
|
-
this.retryable = code === "executor_overloaded";
|
|
29
|
-
if (
|
|
30
|
-
opts.retryAfterMs !== undefined &&
|
|
31
|
-
Number.isFinite(opts.retryAfterMs) &&
|
|
32
|
-
opts.retryAfterMs >= 0
|
|
33
|
-
) {
|
|
34
|
-
this.retryAfterMs = Math.trunc(opts.retryAfterMs);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* A lifecycle failure after the sandbox started running. It keeps the stable
|
|
41
|
-
* admission-error envelope while letting response assembly distinguish work
|
|
42
|
-
* torn down in flight from work that never entered the executor.
|
|
43
|
-
*/
|
|
44
|
-
export class ExecutorExecutionError extends ExecutorAdmissionError {
|
|
45
|
-
constructor(code: ExecutorAdmissionErrorCode, message: string) {
|
|
46
|
-
super(code, message);
|
|
47
|
-
this.name = "ExecutorExecutionError";
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface AdmissionLease {
|
|
52
|
-
/** Time spent waiting behind active work. Zero for immediate admission. */
|
|
53
|
-
readonly waitMs: number;
|
|
54
|
-
release(): void;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface Waiter {
|
|
58
|
-
queuedAt: number;
|
|
59
|
-
resolve: (lease: AdmissionLease) => void;
|
|
60
|
-
reject: (error: ExecutorAdmissionError) => void;
|
|
61
|
-
signal?: AbortSignal;
|
|
62
|
-
onAbort?: () => void;
|
|
63
|
-
timer?: ReturnType<typeof setTimeout>;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface AdmissionControllerOptions {
|
|
67
|
-
concurrency: number;
|
|
68
|
-
maxQueueSize: number;
|
|
69
|
-
queueTimeoutMs: number;
|
|
70
|
-
/** Suggested delay exposed with retryable overload failures. */
|
|
71
|
-
retryAfterMs?: number;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function positiveWhole(value: number, name: string): number {
|
|
75
|
-
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
|
|
76
|
-
throw new TypeError(`${name} must be a positive whole number.`);
|
|
77
|
-
}
|
|
78
|
-
return value;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function nonNegativeWhole(value: number, name: string): number {
|
|
82
|
-
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
|
|
83
|
-
throw new TypeError(`${name} must be a non-negative whole number.`);
|
|
84
|
-
}
|
|
85
|
-
return value;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Runtime-portable bounded admission. It deliberately owns no executor or
|
|
90
|
-
* request state: queued requests retain only a resolver and AbortSignal, while
|
|
91
|
-
* provider catalogs are constructed after the returned lease is granted.
|
|
92
|
-
*/
|
|
93
|
-
export class AdmissionController {
|
|
94
|
-
readonly concurrency: number;
|
|
95
|
-
readonly maxQueueSize: number;
|
|
96
|
-
readonly queueTimeoutMs: number;
|
|
97
|
-
readonly retryAfterMs: number;
|
|
98
|
-
|
|
99
|
-
private active = 0;
|
|
100
|
-
private closed = false;
|
|
101
|
-
private readonly waiters: Waiter[] = [];
|
|
102
|
-
private admittedTotal = 0;
|
|
103
|
-
private queuedTotal = 0;
|
|
104
|
-
private rejectedTotal = 0;
|
|
105
|
-
private cancelledTotal = 0;
|
|
106
|
-
private closedTotal = 0;
|
|
107
|
-
private queueWaitCount = 0;
|
|
108
|
-
private queueWaitTotalMs = 0;
|
|
109
|
-
private queueWaitMaxMs = 0;
|
|
110
|
-
|
|
111
|
-
constructor(options: AdmissionControllerOptions) {
|
|
112
|
-
this.concurrency = positiveWhole(options.concurrency, "concurrency");
|
|
113
|
-
this.maxQueueSize = nonNegativeWhole(
|
|
114
|
-
options.maxQueueSize,
|
|
115
|
-
"maxQueueSize",
|
|
116
|
-
);
|
|
117
|
-
this.queueTimeoutMs = positiveWhole(
|
|
118
|
-
options.queueTimeoutMs,
|
|
119
|
-
"queueTimeoutMs",
|
|
120
|
-
);
|
|
121
|
-
this.retryAfterMs = nonNegativeWhole(
|
|
122
|
-
options.retryAfterMs ?? this.queueTimeoutMs,
|
|
123
|
-
"retryAfterMs",
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
get activeCount(): number {
|
|
128
|
-
return this.active;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
get queuedCount(): number {
|
|
132
|
-
return this.waiters.length;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
snapshot(): AdmissionSnapshot {
|
|
136
|
-
return {
|
|
137
|
-
concurrency: this.concurrency,
|
|
138
|
-
maxQueueSize: this.maxQueueSize,
|
|
139
|
-
queueTimeoutMs: this.queueTimeoutMs,
|
|
140
|
-
retryAfterMs: this.retryAfterMs,
|
|
141
|
-
active: this.active,
|
|
142
|
-
queued: this.waiters.length,
|
|
143
|
-
closed: this.closed,
|
|
144
|
-
totals: {
|
|
145
|
-
admitted: this.admittedTotal,
|
|
146
|
-
queued: this.queuedTotal,
|
|
147
|
-
rejected: this.rejectedTotal,
|
|
148
|
-
cancelled: this.cancelledTotal,
|
|
149
|
-
closed: this.closedTotal,
|
|
150
|
-
},
|
|
151
|
-
queueWaitMs: {
|
|
152
|
-
count: this.queueWaitCount,
|
|
153
|
-
total: this.queueWaitTotalMs,
|
|
154
|
-
max: this.queueWaitMaxMs,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
acquire(options: { signal?: AbortSignal } = {}): Promise<AdmissionLease> {
|
|
160
|
-
if (this.closed) {
|
|
161
|
-
this.closedTotal++;
|
|
162
|
-
return Promise.reject(
|
|
163
|
-
new ExecutorAdmissionError(
|
|
164
|
-
"executor_closed",
|
|
165
|
-
"Executor is shutting down.",
|
|
166
|
-
),
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
if (options.signal?.aborted) {
|
|
170
|
-
this.cancelledTotal++;
|
|
171
|
-
return Promise.reject(
|
|
172
|
-
new ExecutorAdmissionError(
|
|
173
|
-
"executor_cancelled",
|
|
174
|
-
"Execution was cancelled before admission.",
|
|
175
|
-
),
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
if (this.active < this.concurrency) {
|
|
179
|
-
this.active++;
|
|
180
|
-
this.admittedTotal++;
|
|
181
|
-
return Promise.resolve(this.makeLease(0));
|
|
182
|
-
}
|
|
183
|
-
if (this.waiters.length >= this.maxQueueSize) {
|
|
184
|
-
this.rejectedTotal++;
|
|
185
|
-
return Promise.reject(this.overloaded("Executor queue is full."));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return new Promise<AdmissionLease>((resolve, reject) => {
|
|
189
|
-
const waiter: Waiter = {
|
|
190
|
-
queuedAt: Date.now(),
|
|
191
|
-
resolve,
|
|
192
|
-
reject,
|
|
193
|
-
...(options.signal ? { signal: options.signal } : {}),
|
|
194
|
-
};
|
|
195
|
-
waiter.onAbort = () => {
|
|
196
|
-
if (!this.remove(waiter)) return;
|
|
197
|
-
this.cleanup(waiter);
|
|
198
|
-
this.cancelledTotal++;
|
|
199
|
-
reject(
|
|
200
|
-
new ExecutorAdmissionError(
|
|
201
|
-
"executor_cancelled",
|
|
202
|
-
"Execution was cancelled while queued.",
|
|
203
|
-
),
|
|
204
|
-
);
|
|
205
|
-
};
|
|
206
|
-
options.signal?.addEventListener("abort", waiter.onAbort, { once: true });
|
|
207
|
-
waiter.timer = setTimeout(() => {
|
|
208
|
-
if (!this.remove(waiter)) return;
|
|
209
|
-
this.cleanup(waiter);
|
|
210
|
-
this.rejectedTotal++;
|
|
211
|
-
reject(
|
|
212
|
-
this.overloaded(
|
|
213
|
-
`Executor admission timed out after ${this.queueTimeoutMs}ms.`,
|
|
214
|
-
),
|
|
215
|
-
);
|
|
216
|
-
}, this.queueTimeoutMs);
|
|
217
|
-
this.waiters.push(waiter);
|
|
218
|
-
this.queuedTotal++;
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
close(): void {
|
|
223
|
-
if (this.closed) return;
|
|
224
|
-
this.closed = true;
|
|
225
|
-
for (const waiter of this.waiters.splice(0)) {
|
|
226
|
-
this.cleanup(waiter);
|
|
227
|
-
this.closedTotal++;
|
|
228
|
-
waiter.reject(
|
|
229
|
-
new ExecutorAdmissionError(
|
|
230
|
-
"executor_closed",
|
|
231
|
-
"Executor is shutting down.",
|
|
232
|
-
),
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
private overloaded(message: string): ExecutorAdmissionError {
|
|
238
|
-
return new ExecutorAdmissionError("executor_overloaded", message, {
|
|
239
|
-
retryAfterMs: this.retryAfterMs,
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private makeLease(waitMs: number): AdmissionLease {
|
|
244
|
-
let released = false;
|
|
245
|
-
return {
|
|
246
|
-
waitMs,
|
|
247
|
-
release: () => {
|
|
248
|
-
if (released) return;
|
|
249
|
-
released = true;
|
|
250
|
-
this.release();
|
|
251
|
-
},
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
private release(): void {
|
|
256
|
-
if (this.active > 0) this.active--;
|
|
257
|
-
if (this.closed) return;
|
|
258
|
-
const waiter = this.waiters.shift();
|
|
259
|
-
if (!waiter) return;
|
|
260
|
-
this.cleanup(waiter);
|
|
261
|
-
const waitMs = Math.max(0, Date.now() - waiter.queuedAt);
|
|
262
|
-
this.admittedTotal++;
|
|
263
|
-
this.queueWaitCount++;
|
|
264
|
-
this.queueWaitTotalMs += waitMs;
|
|
265
|
-
this.queueWaitMaxMs = Math.max(this.queueWaitMaxMs, waitMs);
|
|
266
|
-
this.active++;
|
|
267
|
-
waiter.resolve(this.makeLease(waitMs));
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
private remove(waiter: Waiter): boolean {
|
|
271
|
-
const index = this.waiters.indexOf(waiter);
|
|
272
|
-
if (index < 0) return false;
|
|
273
|
-
this.waiters.splice(index, 1);
|
|
274
|
-
return true;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
private cleanup(waiter: Waiter): void {
|
|
278
|
-
if (waiter.timer !== undefined) clearTimeout(waiter.timer);
|
|
279
|
-
if (waiter.onAbort) {
|
|
280
|
-
waiter.signal?.removeEventListener("abort", waiter.onAbort);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/** Preserve the one-method Workers seam while recognizing richer Node executors. */
|
|
286
|
-
export function isAdmittingExecutor(
|
|
287
|
-
executor: Executor,
|
|
288
|
-
): executor is AdmittingExecutor {
|
|
289
|
-
return (
|
|
290
|
-
"acquire" in executor &&
|
|
291
|
-
typeof (executor as { acquire?: unknown }).acquire === "function"
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Give a structurally-compatible but otherwise unbounded executor the same
|
|
297
|
-
* admission contract as the built-in Node executor. The wrapper owns only the
|
|
298
|
-
* queue; closing the underlying runtime remains the Connecta lifecycle's job.
|
|
299
|
-
*/
|
|
300
|
-
export function withExecutorAdmission(
|
|
301
|
-
executor: Executor,
|
|
302
|
-
admission: AdmissionController,
|
|
303
|
-
): AdmittingExecutor {
|
|
304
|
-
return {
|
|
305
|
-
async acquire(options = {}): Promise<ExecutorLease> {
|
|
306
|
-
const token = await admission.acquire(options);
|
|
307
|
-
let released = false;
|
|
308
|
-
return {
|
|
309
|
-
waitMs: token.waitMs,
|
|
310
|
-
execute: (code, providers) => executor.execute(code, providers),
|
|
311
|
-
release: () => {
|
|
312
|
-
if (released) return;
|
|
313
|
-
released = true;
|
|
314
|
-
token.release();
|
|
315
|
-
},
|
|
316
|
-
};
|
|
317
|
-
},
|
|
318
|
-
async execute(code, providers) {
|
|
319
|
-
const lease = await this.acquire();
|
|
320
|
-
try {
|
|
321
|
-
return await lease.execute(code, providers);
|
|
322
|
-
} finally {
|
|
323
|
-
lease.release();
|
|
324
|
-
}
|
|
325
|
-
},
|
|
326
|
-
admissionSnapshot: () => admission.snapshot(),
|
|
327
|
-
};
|
|
328
|
-
}
|
package/src/executor-result.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import type { ExecuteResult } from "./types.js";
|
|
2
|
-
|
|
3
|
-
/** ~6k tokens. Sandbox code should filter data down before returning. */
|
|
4
|
-
const MAX_EXECUTE_RESULT_CHARS = 24_000;
|
|
5
|
-
export const MAX_EXECUTE_LOG_CHARS = 4_000;
|
|
6
|
-
|
|
7
|
-
function msg(err: unknown): string {
|
|
8
|
-
return err instanceof Error ? err.message : String(err);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function serializeExecuteValue(value: unknown): string {
|
|
12
|
-
const serialized = JSON.stringify(value);
|
|
13
|
-
return serialized === undefined ? String(value) : serialized;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const TRUNCATION_HINT =
|
|
17
|
-
"filter/map/slice data inside execute_code and return only what you need";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Shape the over-cap notice so the **serialized envelope** fits the same cap
|
|
21
|
-
* the raw value missed. Escaping matters: a preview sliced to the cap is JSON
|
|
22
|
-
* text whose quotes and newlines re-escape to well over it, so a fixed slice
|
|
23
|
-
* would leave the envelope over-cap and a second pass through this guard would
|
|
24
|
-
* truncate the truncation — reporting the envelope's length as `totalChars` and
|
|
25
|
-
* burying the real size. Shrinking proportionally until it fits keeps the guard
|
|
26
|
-
* idempotent by construction: `totalChars` is always the true serialized size
|
|
27
|
-
* of what the program returned, and truncation happens exactly once no matter
|
|
28
|
-
* how many hops the value takes.
|
|
29
|
-
*/
|
|
30
|
-
function truncationEnvelope(text: string): {
|
|
31
|
-
truncated: true;
|
|
32
|
-
preview: string;
|
|
33
|
-
totalChars: number;
|
|
34
|
-
hint: string;
|
|
35
|
-
} {
|
|
36
|
-
const base = {
|
|
37
|
-
truncated: true as const,
|
|
38
|
-
preview: "",
|
|
39
|
-
totalChars: text.length,
|
|
40
|
-
hint: TRUNCATION_HINT,
|
|
41
|
-
};
|
|
42
|
-
let budget = Math.max(
|
|
43
|
-
0,
|
|
44
|
-
MAX_EXECUTE_RESULT_CHARS - JSON.stringify(base).length,
|
|
45
|
-
);
|
|
46
|
-
for (let attempt = 0; attempt < 8 && budget > 0; attempt += 1) {
|
|
47
|
-
const candidate = { ...base, preview: text.slice(0, budget) };
|
|
48
|
-
const size = JSON.stringify(candidate).length;
|
|
49
|
-
if (size <= MAX_EXECUTE_RESULT_CHARS) return candidate;
|
|
50
|
-
// Every character costs at least one serialized character, so scaling by
|
|
51
|
-
// the overshoot ratio (minus a step) strictly shrinks the budget.
|
|
52
|
-
budget = Math.max(
|
|
53
|
-
0,
|
|
54
|
-
Math.floor(budget * (MAX_EXECUTE_RESULT_CHARS / size)) - 8,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
// The loop shrinks monotonically, so this is unreachable in practice — but an
|
|
58
|
-
// unchecked slice is exactly how a "bounded" envelope stops being bounded.
|
|
59
|
-
const clamped = { ...base, preview: text.slice(0, Math.max(0, budget)) };
|
|
60
|
-
return JSON.stringify(clamped).length <= MAX_EXECUTE_RESULT_CHARS
|
|
61
|
-
? clamped
|
|
62
|
-
: { ...base, preview: "" };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function guardExecuteResultValue(value: unknown): unknown {
|
|
66
|
-
const text = serializeExecuteValue(value);
|
|
67
|
-
if (text.length <= MAX_EXECUTE_RESULT_CHARS) return value;
|
|
68
|
-
return truncationEnvelope(text);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function truncateExecuteText(text: string, max: number): string {
|
|
72
|
-
if (text.length <= max) return text;
|
|
73
|
-
return `${text.slice(0, max)}\n--- TRUNCATED (${text.length} chars total) — filter/map/slice data inside your code and return only what you need ---`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Apply the public execute_code result/log policy before a child result enters
|
|
78
|
-
* IPC. The parent repeats the guard for third-party Executor implementations.
|
|
79
|
-
*/
|
|
80
|
-
export function prepareExecuteResultForTransport(
|
|
81
|
-
outcome: ExecuteResult,
|
|
82
|
-
): ExecuteResult {
|
|
83
|
-
// QuickJS already bounds captured logs at source (entries + cumulative
|
|
84
|
-
// characters). Preserve that Executor-level shape; createExecuteTool applies
|
|
85
|
-
// the smaller model-facing 4k presentation cap in the parent.
|
|
86
|
-
const logs =
|
|
87
|
-
outcome.logs && outcome.logs.length > 0 ? outcome.logs : undefined;
|
|
88
|
-
if (outcome.error) {
|
|
89
|
-
return {
|
|
90
|
-
result: undefined,
|
|
91
|
-
error: outcome.error,
|
|
92
|
-
...(logs ? { logs } : {}),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
return {
|
|
97
|
-
result: guardExecuteResultValue(outcome.result),
|
|
98
|
-
...(logs ? { logs } : {}),
|
|
99
|
-
};
|
|
100
|
-
} catch (err) {
|
|
101
|
-
return {
|
|
102
|
-
result: undefined,
|
|
103
|
-
error: `result is not JSON-serializable: ${msg(err)}`,
|
|
104
|
-
...(logs ? { logs } : {}),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { prepareExecuteResultForTransport } from "../executor-result.js";
|
|
2
|
-
import type { ExecutorProvider } from "../types.js";
|
|
3
|
-
import {
|
|
4
|
-
MAX_QUICKJS_IPC_BYTES,
|
|
5
|
-
MAX_QUICKJS_HOST_RPC_BYTES,
|
|
6
|
-
type ChildToParentMessage,
|
|
7
|
-
type ExecutionPayload,
|
|
8
|
-
type HostCallPayload,
|
|
9
|
-
type HostResultPayload,
|
|
10
|
-
type ParentToChildMessage,
|
|
11
|
-
type RunPayload,
|
|
12
|
-
serializedBytes,
|
|
13
|
-
stringifyBounded,
|
|
14
|
-
} from "./quickjs-protocol.js";
|
|
15
|
-
import { executeQuickJs, prepareQuickJs } from "./quickjs-runtime.js";
|
|
16
|
-
|
|
17
|
-
let activeJobId: number | undefined;
|
|
18
|
-
let nextCallId = 1;
|
|
19
|
-
const pending = new Map<
|
|
20
|
-
number,
|
|
21
|
-
{
|
|
22
|
-
resolve: (value: unknown) => void;
|
|
23
|
-
reject: (error: Error) => void;
|
|
24
|
-
}
|
|
25
|
-
>();
|
|
26
|
-
|
|
27
|
-
function msg(err: unknown): string {
|
|
28
|
-
return err instanceof Error ? err.message : String(err);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function send(message: ChildToParentMessage): void {
|
|
32
|
-
if (!process.send) throw new Error("QuickJS child IPC channel is unavailable.");
|
|
33
|
-
// Bound the actual process.send envelope, not only its nested payloadJson.
|
|
34
|
-
// Guest-controlled strings must be rejected while they are still isolated
|
|
35
|
-
// in this disposable process, before Node materializes them in the parent.
|
|
36
|
-
stringifyBounded(message, "QuickJS child IPC envelope");
|
|
37
|
-
process.send(message);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function provider(
|
|
41
|
-
name: string,
|
|
42
|
-
jobId: number,
|
|
43
|
-
prelude?: string,
|
|
44
|
-
): ExecutorProvider {
|
|
45
|
-
const functions = new Proxy(
|
|
46
|
-
Object.create(null) as ExecutorProvider["fns"],
|
|
47
|
-
{
|
|
48
|
-
get: (_target, key) => {
|
|
49
|
-
if (typeof key !== "string") return undefined;
|
|
50
|
-
return async (...args: unknown[]): Promise<unknown> => {
|
|
51
|
-
const callId = nextCallId++;
|
|
52
|
-
const payloadJson = stringifyBounded(
|
|
53
|
-
{
|
|
54
|
-
namespace: name,
|
|
55
|
-
functionName: key,
|
|
56
|
-
args,
|
|
57
|
-
} satisfies HostCallPayload,
|
|
58
|
-
"Host call payload",
|
|
59
|
-
MAX_QUICKJS_HOST_RPC_BYTES,
|
|
60
|
-
);
|
|
61
|
-
const result = new Promise<unknown>((resolve, reject) => {
|
|
62
|
-
pending.set(callId, { resolve, reject });
|
|
63
|
-
});
|
|
64
|
-
send({
|
|
65
|
-
type: "host-call",
|
|
66
|
-
jobId,
|
|
67
|
-
callId,
|
|
68
|
-
payloadJson,
|
|
69
|
-
});
|
|
70
|
-
return result;
|
|
71
|
-
};
|
|
72
|
-
},
|
|
73
|
-
getOwnPropertyDescriptor: (_target, key) =>
|
|
74
|
-
typeof key === "string"
|
|
75
|
-
? { configurable: true, enumerable: false }
|
|
76
|
-
: undefined,
|
|
77
|
-
},
|
|
78
|
-
);
|
|
79
|
-
return { name, fns: functions, ...(prelude ? { prelude } : {}) };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function fixedTransportFailure(message: string): ExecutionPayload {
|
|
83
|
-
return {
|
|
84
|
-
outcome: {
|
|
85
|
-
result: undefined,
|
|
86
|
-
error: message.slice(0, 4_000),
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function run(payload: RunPayload): Promise<void> {
|
|
92
|
-
if (activeJobId !== undefined) {
|
|
93
|
-
throw new Error("QuickJS child received overlapping executions.");
|
|
94
|
-
}
|
|
95
|
-
activeJobId = payload.id;
|
|
96
|
-
try {
|
|
97
|
-
const providers = payload.providers.map(({ name, prelude }) =>
|
|
98
|
-
provider(name, payload.id, prelude),
|
|
99
|
-
);
|
|
100
|
-
const raw = await executeQuickJs(
|
|
101
|
-
payload.code,
|
|
102
|
-
providers,
|
|
103
|
-
payload.options,
|
|
104
|
-
);
|
|
105
|
-
const prepared: ExecutionPayload = {
|
|
106
|
-
outcome: prepareExecuteResultForTransport(raw),
|
|
107
|
-
...(raw.timedOut ? { timedOut: true } : {}),
|
|
108
|
-
};
|
|
109
|
-
let payloadJson: string;
|
|
110
|
-
try {
|
|
111
|
-
payloadJson = stringifyBounded(prepared, "QuickJS execution result");
|
|
112
|
-
} catch (err) {
|
|
113
|
-
payloadJson = JSON.stringify(
|
|
114
|
-
fixedTransportFailure(
|
|
115
|
-
`QuickJS execution result exceeded the ${MAX_QUICKJS_IPC_BYTES}-byte IPC limit: ${msg(err)}`,
|
|
116
|
-
),
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
send({ type: "result", jobId: payload.id, payloadJson });
|
|
120
|
-
} finally {
|
|
121
|
-
activeJobId = undefined;
|
|
122
|
-
for (const request of pending.values()) {
|
|
123
|
-
request.reject(new Error("Execution ended before the host call settled."));
|
|
124
|
-
}
|
|
125
|
-
pending.clear();
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
process.on("message", (message: ParentToChildMessage) => {
|
|
130
|
-
if (!message || typeof message !== "object") return;
|
|
131
|
-
if (message.type === "host-result") {
|
|
132
|
-
if (message.jobId !== activeJobId) return;
|
|
133
|
-
const request = pending.get(message.callId);
|
|
134
|
-
if (!request) return;
|
|
135
|
-
pending.delete(message.callId);
|
|
136
|
-
if (
|
|
137
|
-
serializedBytes(message.payloadJson) > MAX_QUICKJS_HOST_RPC_BYTES
|
|
138
|
-
) {
|
|
139
|
-
request.reject(new Error("Host result exceeded the IPC limit."));
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
const result = JSON.parse(message.payloadJson) as HostResultPayload;
|
|
144
|
-
if (result.ok) request.resolve(result.value);
|
|
145
|
-
else request.reject(new Error(result.error));
|
|
146
|
-
} catch (err) {
|
|
147
|
-
request.reject(new Error(`Invalid host result: ${msg(err)}`));
|
|
148
|
-
}
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
if (message.type !== "run") return;
|
|
152
|
-
if (serializedBytes(message.payloadJson) > MAX_QUICKJS_IPC_BYTES) {
|
|
153
|
-
throw new Error("Run payload exceeded the IPC limit.");
|
|
154
|
-
}
|
|
155
|
-
const payload = JSON.parse(message.payloadJson) as RunPayload;
|
|
156
|
-
void run(payload).catch((err) => {
|
|
157
|
-
const payloadJson = JSON.stringify(
|
|
158
|
-
fixedTransportFailure(`QuickJS child failed: ${msg(err)}`),
|
|
159
|
-
);
|
|
160
|
-
send({ type: "result", jobId: payload.id, payloadJson });
|
|
161
|
-
activeJobId = undefined;
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
process.on("disconnect", () => process.exit(0));
|
|
166
|
-
|
|
167
|
-
// The parent does not start an execution's wall budget until the trusted WASM
|
|
168
|
-
// module is loaded. This keeps cold-start latency from being misclassified as
|
|
169
|
-
// guest CPU/wall failure while the parent's startup timeout still bounds a
|
|
170
|
-
// child that never becomes ready.
|
|
171
|
-
await prepareQuickJs();
|
|
172
|
-
send({ type: "ready" });
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import type { ExecuteResult } from "../types.js";
|
|
2
|
-
import type { QuickJsRuntimeOptions } from "./quickjs-runtime.js";
|
|
3
|
-
|
|
4
|
-
// An execution result is serialized once into payloadJson and again as that
|
|
5
|
-
// string is embedded in ChildToParentMessage. Captured logs therefore get a
|
|
6
|
-
// separate 512 KiB budget measured after both JSON encodings. The final result
|
|
7
|
-
// is shaped to 24k characters; even if every character takes its worst-case
|
|
8
|
-
// seven transport bytes, that leaves ample structural overhead below this hard
|
|
9
|
-
// process.send ceiling.
|
|
10
|
-
export const MAX_QUICKJS_IPC_BYTES = 1024 * 1024;
|
|
11
|
-
export const MAX_QUICKJS_LOG_TRANSPORT_BYTES = 512 * 1024;
|
|
12
|
-
/** Preserve #84's stopgap before a host value enters the child/WASM process. */
|
|
13
|
-
export const MAX_QUICKJS_HOST_RPC_BYTES = 256 * 1024;
|
|
14
|
-
|
|
15
|
-
export interface RunPayload {
|
|
16
|
-
id: number;
|
|
17
|
-
code: string;
|
|
18
|
-
providers: Array<{ name: string; prelude?: string }>;
|
|
19
|
-
options: QuickJsRuntimeOptions;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type ParentToChildMessage =
|
|
23
|
-
| { type: "run"; payloadJson: string }
|
|
24
|
-
| {
|
|
25
|
-
type: "host-result";
|
|
26
|
-
jobId: number;
|
|
27
|
-
callId: number;
|
|
28
|
-
payloadJson: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type ChildToParentMessage =
|
|
32
|
-
| { type: "ready" }
|
|
33
|
-
| {
|
|
34
|
-
type: "host-call";
|
|
35
|
-
jobId: number;
|
|
36
|
-
callId: number;
|
|
37
|
-
payloadJson: string;
|
|
38
|
-
}
|
|
39
|
-
| { type: "result"; jobId: number; payloadJson: string };
|
|
40
|
-
|
|
41
|
-
export interface HostCallPayload {
|
|
42
|
-
namespace: string;
|
|
43
|
-
functionName: string;
|
|
44
|
-
args: unknown[];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export type HostResultPayload =
|
|
48
|
-
| { ok: true; value: unknown }
|
|
49
|
-
| { ok: false; error: string };
|
|
50
|
-
|
|
51
|
-
export interface ExecutionPayload {
|
|
52
|
-
outcome: ExecuteResult;
|
|
53
|
-
/**
|
|
54
|
-
* Set by the runtime on wall-clock expiry. The parent recycles the child on
|
|
55
|
-
* this flag, never on error text a guest can fabricate.
|
|
56
|
-
*/
|
|
57
|
-
timedOut?: boolean;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* How a host call should be named in an error a program will read. The lazy
|
|
62
|
-
* connector namespaces all dispatch through one internal function, so the raw
|
|
63
|
-
* provider/function pair would report every shortcut call as
|
|
64
|
-
* `connecta.__callNamespace` — an internal name that appears nowhere in the
|
|
65
|
-
* documented surface. Report the address the program actually called.
|
|
66
|
-
*/
|
|
67
|
-
export function hostCallLabel(payload: {
|
|
68
|
-
namespace: string;
|
|
69
|
-
functionName: string;
|
|
70
|
-
args: unknown[];
|
|
71
|
-
}): string {
|
|
72
|
-
if (payload.functionName === "__callNamespace") {
|
|
73
|
-
const [connectorId, toolAlias] = payload.args;
|
|
74
|
-
return `${String(connectorId)}.${String(toolAlias)}`;
|
|
75
|
-
}
|
|
76
|
-
return `${payload.namespace}.${payload.functionName}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function serializedBytes(text: string): number {
|
|
80
|
-
return new TextEncoder().encode(text).length;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function stringifyBounded(
|
|
84
|
-
value: unknown,
|
|
85
|
-
label: string,
|
|
86
|
-
limit = MAX_QUICKJS_IPC_BYTES,
|
|
87
|
-
): string {
|
|
88
|
-
const json = JSON.stringify(value);
|
|
89
|
-
if (json === undefined) {
|
|
90
|
-
throw new TypeError(`${label} is not JSON-serializable.`);
|
|
91
|
-
}
|
|
92
|
-
const bytes = serializedBytes(json);
|
|
93
|
-
if (bytes > limit) {
|
|
94
|
-
throw new RangeError(
|
|
95
|
-
`${label} is ${bytes} UTF-8 bytes, over the ${limit}-byte IPC limit.`,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
return json;
|
|
99
|
-
}
|