@zackbart/connecta 0.40.0 → 0.42.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 +148 -0
- package/bin/connecta.mjs +26 -17
- package/bin/meta-tool-names.d.mts +3 -1
- package/bin/meta-tool-names.mjs +4 -1
- package/bin/version.mjs +1 -1
- package/dist/activity.d.ts +32 -5
- package/dist/activity.js +32 -6
- package/dist/advertised-schema.js +15 -0
- package/dist/agent-observation.js +24 -2
- package/dist/agent-routes.js +33 -44
- package/dist/auth/clerk.d.ts +6 -1
- package/dist/auth/clerk.js +16 -0
- package/dist/auth/cloudflare-access.js +49 -7
- package/dist/auth/downstream-oauth/consent.js +79 -29
- package/dist/auth/downstream-oauth/grant-store.js +58 -7
- package/dist/auth/downstream-oauth/refresh-coordinator.js +4 -1
- package/dist/auth/downstream-oauth/refresh-records.js +6 -5
- package/dist/auth/downstream-oauth.js +15 -4
- package/dist/auth/oauth-request-not-sent.js +4 -0
- package/dist/auth/static-oauth.js +77 -39
- package/dist/auth-elicitation.js +118 -61
- package/dist/authorization-handoff.js +36 -3
- package/dist/catalog-cache.js +352 -43
- package/dist/catalog-freshness.js +25 -0
- package/dist/catalog-service.js +408 -78
- package/dist/catalog.js +216 -62
- package/dist/config-defaults.d.ts +2 -1
- package/dist/config-defaults.js +4 -1
- package/dist/config.d.ts +53 -21
- package/dist/config.js +56 -17
- package/dist/connectors/remote-mcp/call.js +1 -1
- package/dist/connectors/remote-mcp/catalog.js +52 -28
- package/dist/connectors/remote-mcp/connection.js +113 -22
- package/dist/connectors/remote-mcp/oauth.js +2 -2
- package/dist/connectors/remote-mcp/resources.js +5 -2
- package/dist/connectors/remote-mcp/transport.js +24 -6
- package/dist/connectors/remote-mcp.js +14 -5
- package/dist/credential-contract.d.ts +3 -1
- package/dist/describe-config.d.ts +4 -2
- package/dist/describe-config.js +1 -0
- package/dist/discovery-health.js +26 -11
- package/dist/downstream-input.js +11 -2
- package/dist/errors.d.ts +60 -4
- package/dist/errors.js +88 -3
- package/dist/execute.js +491 -202
- package/dist/executor-admission.js +2 -0
- package/dist/executor-contract.js +6 -0
- package/dist/executor-result.js +3 -3
- package/dist/executors/quickjs-child.js +14 -5
- package/dist/executors/quickjs-runtime.d.ts +2 -0
- package/dist/executors/quickjs-runtime.js +37 -20
- package/dist/executors/quickjs.js +24 -7
- package/dist/guarded-transport.js +2 -0
- package/dist/guest-functions.js +5 -4
- package/dist/guest-runtime.js +37 -1
- package/dist/guest-types.d.ts +18 -0
- package/dist/http/client.js +14 -2
- package/dist/http/errors.js +9 -4
- package/dist/http/fields.js +17 -5
- package/dist/http/hatch.js +11 -3
- package/dist/http/node-transport.js +110 -4
- package/dist/http/oauth.js +17 -6
- package/dist/http/paging.js +1 -1
- package/dist/http/provider.js +2 -6
- package/dist/http/vendor.js +6 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +15 -21
- package/dist/invocation-failure.js +28 -0
- package/dist/invocation.js +167 -64
- package/dist/membership.d.ts +1 -1
- package/dist/meta-output.js +24 -18
- package/dist/meta-tool-names.d.ts +1 -1
- package/dist/meta-tool-names.js +1 -1
- package/dist/meta-tools.js +286 -73
- package/dist/module-contracts.d.ts +3 -1
- package/dist/native-recovery.js +4 -1
- package/dist/native-tools.js +177 -55
- package/dist/node.d.ts +7 -0
- package/dist/node.js +85 -4
- package/dist/oauth-handoff.js +53 -43
- package/dist/operation-activity.js +104 -0
- package/dist/operation-vocabulary.js +6 -0
- package/dist/operator-record.d.ts +9 -1
- package/dist/operator-record.js +16 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/program-result.js +2 -0
- package/dist/program-source.js +1 -1
- package/dist/program-writes.js +112 -19
- package/dist/provider.d.ts +1 -14
- package/dist/provider.js +0 -3
- package/dist/providers/_shared/google/kit.js +2 -2
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/breeze/api.js +2 -2
- package/dist/providers/breeze/index.js +1 -1
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/ccb/api.js +2 -2
- package/dist/providers/ccb/index.js +1 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/cloudflare/api.js +2 -2
- package/dist/providers/cloudflare/index.js +3 -1
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/index.js +1 -1
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/drive/index.js +1 -1
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/forms/index.js +2 -1
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/github/api.js +98 -6
- package/dist/providers/github/index.js +8 -3
- package/dist/providers/github/lib/auth.d.ts +3 -1
- package/dist/providers/github/lib/auth.js +43 -12
- package/dist/providers/github/lib/discovery.js +11 -1
- package/dist/providers/github/lib/operations.js +8 -0
- package/dist/providers/github/safety.js +39 -0
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +12 -5
- package/dist/providers/gmail/index.js +2 -1
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/granola/index.js +1 -1
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/index.js +3 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/planning-center/index.js +12 -6
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/quickbooks/api.js +28 -25
- package/dist/providers/quickbooks/index.js +3 -6
- package/dist/providers/quickbooks/lib/writes.js +55 -0
- package/dist/providers/quickbooks/safety.js +17 -5
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/index.js +2 -1
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/slides/index.js +3 -3
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/stripe/index.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/index.js +2 -1
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/vercel/api.js +3 -2
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/registry.d.ts +36 -4
- package/dist/registry.js +142 -30
- package/dist/resource-read.js +21 -2
- package/dist/result-preview.js +210 -0
- package/dist/result-seal.js +168 -0
- package/dist/route-names.js +53 -0
- package/dist/routes/activity-context.js +1 -0
- package/dist/routes/activity-disclosure.js +26 -4
- package/dist/routes/activity.js +2 -1
- package/dist/routes/connect.js +21 -13
- package/dist/routes/mcp.js +83 -35
- package/dist/routes/shared.d.ts +6 -0
- package/dist/routes/ui-config.js +1 -1
- package/dist/routes/ui-data.js +3 -2
- package/dist/runtime/call.js +15 -10
- package/dist/runtime/native-storage.js +12 -0
- package/dist/runtime/request-reads.js +66 -14
- package/dist/runtime/request-scope.js +38 -2
- package/dist/runtime/run.js +4 -1
- package/dist/sent-secrets.d.ts +20 -0
- package/dist/sent-secrets.js +49 -1
- package/dist/skill-origin.js +16 -0
- package/dist/skills.js +213 -57
- package/dist/storage/d1-driver.js +33 -5
- package/dist/storage/keys.js +16 -7
- package/dist/storage/sql-native.js +60 -3
- package/dist/storage/sql.js +6 -0
- package/dist/timing-trace.js +84 -0
- package/dist/tool-safety.d.ts +33 -0
- package/dist/tool-safety.js +51 -15
- package/dist/types.d.ts +36 -0
- package/dist/url-safety.js +38 -20
- package/dist/usage-guide.js +14 -13
- package/dist/utility-recovery.js +33 -0
- package/dist/validate.js +29 -10
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +60 -15
- package/documentation/architecture.md +152 -43
- package/documentation/auth.md +160 -53
- package/documentation/code-mode.md +396 -119
- package/documentation/deploying.md +35 -10
- package/documentation/hosted-mcp-migration.md +9 -3
- package/documentation/integrating.md +27 -5
- package/documentation/meta-tools.md +761 -157
- package/documentation/operating.md +91 -4
- package/documentation/operator-ui.md +51 -2
- package/documentation/providers.md +34 -20
- package/examples/worker/README.md +3 -3
- package/guides/how-it-works.md +2 -1
- package/guides/safety-and-trust.md +1 -3
- package/package.json +10 -8
- package/templates/node/AGENTS.md +3 -3
- package/templates/node/README.md +1 -1
- package/templates/node/package.json +1 -1
package/dist/usage-guide.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { META_ROUTES } from "./agent-routes.js";
|
|
2
|
+
import { READ_PROGRAM_SOURCE } from "./tool-safety.js";
|
|
2
3
|
import { UNCOUNTED_GUEST_FUNCTIONS } from "./guest-functions.js";
|
|
3
4
|
/** Self-contained content for the usage skill; independent of skill registration. */
|
|
4
5
|
export const USAGE_SKILL = `---
|
|
@@ -14,7 +15,7 @@ Route as the server instructions and ${META_ROUTES.program}'s first sentence say
|
|
|
14
15
|
Read one account with ${META_ROUTES.read}: \`{ "address": "crm.get_account", "args": { "id": "acct_42" } }\`.
|
|
15
16
|
On a read-only endpoint search omits writes (guides may name them) and each
|
|
16
17
|
fails read_only_endpoint; tell the user. Unannotated tools fail closed to writes.
|
|
17
|
-
|
|
18
|
+
Where listed, ${READ_PROGRAM_SOURCE} refuses writes, marked \`callable: false\`.
|
|
18
19
|
|
|
19
20
|
## The global and the envelopes
|
|
20
21
|
|
|
@@ -28,7 +29,7 @@ Parse \`format: "text"\` only when documented as JSON. Verify collection roots.
|
|
|
28
29
|
\`connecta.read("resource://id/"+encodeURIComponent(uri))\` returns \`{ contents }\` for advertised URIs.
|
|
29
30
|
|
|
30
31
|
Search/describe return \`{ tools }\`, never an array. Check search's
|
|
31
|
-
\`catalogErrors\` and \`absence\` before selecting a match. Search defaults to a
|
|
32
|
+
\`catalogErrors\` and \`absence\` before selecting a match; a \`stale: true\` error's tools are listed from an older catalog, and a call still checks the live one. Search defaults to a
|
|
32
33
|
TypeScript \`signature\`; text schemas have \`schemaFormat: "text"\` and no .properties.
|
|
33
34
|
A \`readyCalls\` entry is a complete read: \`connecta.call(entry.address, entry.args)\`. Select the exact tool and
|
|
34
35
|
account/environment, inspect its schema, and supply its required keys and types.
|
|
@@ -85,14 +86,14 @@ Otherwise fetch for units, pagination, aliases, or sequences.
|
|
|
85
86
|
|
|
86
87
|
## Large results and recovery
|
|
87
88
|
|
|
88
|
-
Large
|
|
89
|
-
|
|
90
|
-
returned id in place of result-id below; this example assumes documented JSON.
|
|
89
|
+
Large results carry resultId. Follow nextOffset in UTF-8 bytes, reassemble and reduce
|
|
90
|
+
inside one program. Use that id for result-id below; this example assumes documented JSON.
|
|
91
91
|
|
|
92
|
-
Calls
|
|
93
|
-
\`{ format: "paged", valueFormat, resultId?, totalBytes, hint }\`.
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
Calls inline up to 1 MiB; larger return
|
|
93
|
+
\`{ format: "paged", valueFormat, resultId?, totalBytes, hint }\`. Follow its hint:
|
|
94
|
+
\`connecta.result(handle, { all: true })\` gives \`{ data, format }\` for one host call,
|
|
95
|
+
or throws result_too_large; then page. IDs last 15 minutes; only explicit
|
|
96
|
+
paging spends extra host calls.
|
|
96
97
|
Use zero-based \`{ page: 0 }\` or UTF-8 \`{ offset }\`, never both. Keep maxBytes
|
|
97
98
|
fixed across pages; connector caps and the 32 KiB bridge ceiling apply.
|
|
98
99
|
|
|
@@ -100,7 +101,7 @@ fixed across pages; connector caps and the 32 KiB bridge ceiling apply.
|
|
|
100
101
|
async () => {
|
|
101
102
|
let text = "", offset = 0, page;
|
|
102
103
|
do {
|
|
103
|
-
page = await connecta.result("result-id", { offset
|
|
104
|
+
page = await connecta.result("result-id", { offset });
|
|
104
105
|
text += page.text;
|
|
105
106
|
offset = page.nextOffset;
|
|
106
107
|
} while (page.hasMore);
|
|
@@ -108,13 +109,13 @@ async () => {
|
|
|
108
109
|
}
|
|
109
110
|
\`\`\`
|
|
110
111
|
|
|
111
|
-
Oversized returns lead with nextAction. Never rerun writes for output. write_outcome_unknown or writeOutcome forbids retry, even if caught.
|
|
112
|
+
Oversized returns lead with nextAction. Never rerun writes for output. write_outcome_unknown or writeOutcome forbids retry, even if caught. Give the user any approval link in the tool's words; a call after approval is new.
|
|
112
113
|
Recovery args may be partial (argsRedacted) or absent (argsOmitted). Reconcile
|
|
113
114
|
before retrying with the original args or repeating a program that sent writes.
|
|
114
115
|
Non-retryable errors need repair.
|
|
115
116
|
host_auth_required needs host connection repair; provider_permission_denied needs
|
|
116
|
-
resource-owner access. Auth failures carry the handoff in error.data
|
|
117
|
-
|
|
117
|
+
resource-owner access. Auth failures carry the handoff in error.data; give it to the
|
|
118
|
+
user or operator.
|
|
118
119
|
|
|
119
120
|
## Output
|
|
120
121
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Actions outside the tool namespace never invent a route on a serving surface. */
|
|
2
|
+
export function utilityRecovery(code, message, retryable = false) {
|
|
3
|
+
return {
|
|
4
|
+
code,
|
|
5
|
+
message,
|
|
6
|
+
retryable,
|
|
7
|
+
nextAction: {
|
|
8
|
+
action: ["unsupported_capability", "input_required_unsupported", "invalid_args"].includes(code)
|
|
9
|
+
? "inspect_capabilities"
|
|
10
|
+
: code === "not_found"
|
|
11
|
+
? "recover_result"
|
|
12
|
+
: "contact_operator",
|
|
13
|
+
purpose: ["unsupported_capability", "input_required_unsupported", "invalid_args"].includes(code)
|
|
14
|
+
? "Inspect the supported utility input and client capabilities before a fresh call. Never repeat a write whose outcome is unknown."
|
|
15
|
+
: code === "not_found"
|
|
16
|
+
? "Use a retained result handle if available. Otherwise report the missing output; never repeat a write to recover output."
|
|
17
|
+
: "Ask the operator to restore availability. Retry reads only after recovery; reconcile any write whose outcome is unknown.",
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Missing skill names need local discovery, never service restoration or result recovery. */
|
|
22
|
+
export function skillLookupRecovery(message) {
|
|
23
|
+
return {
|
|
24
|
+
code: "not_found",
|
|
25
|
+
message,
|
|
26
|
+
retryable: false,
|
|
27
|
+
nextAction: {
|
|
28
|
+
tool: "skills",
|
|
29
|
+
arguments: {},
|
|
30
|
+
purpose: "List available skills, then fetch an exact name from that list.",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
package/dist/validate.js
CHANGED
|
@@ -190,7 +190,7 @@ function receivedType(value) {
|
|
|
190
190
|
* No caller values are used to invent an example, and a writeOnly position or
|
|
191
191
|
* anything inside one never renders a schema literal. A candidate must validate.
|
|
192
192
|
*/
|
|
193
|
-
function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, active: new Set() }, secret = false) {
|
|
193
|
+
function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, active: new Set(), placeholders: false }, secret = false, key) {
|
|
194
194
|
if (--budget.remaining < 0)
|
|
195
195
|
throw new Error("Example synthesis budget exhausted");
|
|
196
196
|
if (depth > 12 || schema === false)
|
|
@@ -224,7 +224,7 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
|
|
|
224
224
|
candidate = Object.fromEntries(required
|
|
225
225
|
.filter((key) => typeof key === "string")
|
|
226
226
|
.slice(0, 30)
|
|
227
|
-
.map((key) => [key, exampleCandidate(properties[key], root, depth + 1, budget, secret)]));
|
|
227
|
+
.map((key) => [key, exampleCandidate(properties[key], root, depth + 1, budget, secret, key)]));
|
|
228
228
|
if (Array.isArray(node.allOf)) {
|
|
229
229
|
for (const branch of node.allOf.slice(0, 20)) {
|
|
230
230
|
candidate = mergeExample(candidate, exampleCandidate(branch, root, depth + 1, budget, secret));
|
|
@@ -258,7 +258,7 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
|
|
|
258
258
|
// Branches may declare required keys while their types live on the parent.
|
|
259
259
|
for (const key of Object.keys(schemaObject(candidate))) {
|
|
260
260
|
if (schemaObject(candidate)[key] === undefined) {
|
|
261
|
-
schemaObject(candidate)[key] = exampleCandidate(properties[key], root, depth + 1, budget, secret);
|
|
261
|
+
schemaObject(candidate)[key] = exampleCandidate(properties[key], root, depth + 1, budget, secret, key);
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
264
|
}
|
|
@@ -290,10 +290,11 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
|
|
|
290
290
|
uri: "https://example.com",
|
|
291
291
|
uuid: "00000000-0000-4000-8000-000000000000",
|
|
292
292
|
};
|
|
293
|
+
const named = budget.placeholders && !secret && key !== undefined ? namedPlaceholder(key, node) : undefined;
|
|
293
294
|
candidate =
|
|
294
295
|
typeof node.format === "string" && formats[node.format]
|
|
295
296
|
? formats[node.format]
|
|
296
|
-
: "x".repeat(Math.min(typeof node.minLength === "number" ? node.minLength : 0, 128));
|
|
297
|
+
: (named ?? "x".repeat(Math.min(typeof node.minLength === "number" ? node.minLength : 0, 128)));
|
|
297
298
|
}
|
|
298
299
|
return candidate;
|
|
299
300
|
}
|
|
@@ -301,6 +302,19 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
|
|
|
301
302
|
budget.active.delete(node);
|
|
302
303
|
}
|
|
303
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* `<id>` for a plain string argument: an empty string would satisfy local
|
|
307
|
+
* validation and reach the downstream looking like a real value. A secret
|
|
308
|
+
* position, an odd key, and a length bound it misses keep the plain sample.
|
|
309
|
+
*/
|
|
310
|
+
function namedPlaceholder(key, node) {
|
|
311
|
+
if (!/^[\w.-]{1,64}$/.test(key))
|
|
312
|
+
return undefined;
|
|
313
|
+
const text = `<${key}>`;
|
|
314
|
+
const min = typeof node.minLength === "number" ? node.minLength : 0;
|
|
315
|
+
const max = typeof node.maxLength === "number" ? node.maxLength : Infinity;
|
|
316
|
+
return text.length >= min && text.length <= max ? text : undefined;
|
|
317
|
+
}
|
|
304
318
|
function mergeExample(a, b) {
|
|
305
319
|
if (a !== null &&
|
|
306
320
|
b !== null &&
|
|
@@ -501,12 +515,17 @@ function validationIssues(schema, args, units) {
|
|
|
501
515
|
export function schemaExample(schema) {
|
|
502
516
|
try {
|
|
503
517
|
const validator = validators.get(schema) ?? new Validator(schema, "2020-12", false);
|
|
504
|
-
//
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
518
|
+
// Named placeholders first; a schema that refuses them, such as one with a
|
|
519
|
+
// pattern, falls back to the plain sample rather than losing its example.
|
|
520
|
+
for (const placeholders of [true, false]) {
|
|
521
|
+
const budget = { remaining: 128, active: new Set(), placeholders };
|
|
522
|
+
// Serialization makes the validation cover exactly what the agent receives.
|
|
523
|
+
const example = JSON.parse(JSON.stringify(exampleCandidate(schema, schema, 0, budget)));
|
|
524
|
+
if (validator.validate(example).valid) {
|
|
525
|
+
minimizeExample(example, validator);
|
|
526
|
+
if (validator.validate(example).valid)
|
|
527
|
+
return { example };
|
|
528
|
+
}
|
|
510
529
|
}
|
|
511
530
|
}
|
|
512
531
|
catch {
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CONNECTA_VERSION = "0.
|
|
1
|
+
export declare const CONNECTA_VERSION = "0.42.0";
|
package/dist/version.js
CHANGED
package/dist/worker.js
CHANGED
|
@@ -7,6 +7,7 @@ import { awaitExecutorLease, provideExecutorProgram, executorProgram } from "./r
|
|
|
7
7
|
import { DynamicWorkerExecutor, normalizeCode } from "@cloudflare/codemode";
|
|
8
8
|
import { AdmissionController, ExecutorExecutionError } from "./executor-admission.js";
|
|
9
9
|
import { brandExecutor } from "./executor-contract.js";
|
|
10
|
+
import { TOKEN_END } from "./errors.js";
|
|
10
11
|
import { InvocationFailure } from "./invocation.js";
|
|
11
12
|
import { parse } from "acorn";
|
|
12
13
|
import { guestInitializer, guestPrelude, guestPromiseInitializer, isolateGuestProgram } from "./guest-runtime.js";
|
|
@@ -43,15 +44,25 @@ function routeGuestImports(source) {
|
|
|
43
44
|
}
|
|
44
45
|
return source;
|
|
45
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* The guest deadline when `timeout` is unset: below `execute.watchdogMs`, and
|
|
49
|
+
* so below the 60 s MCP clients give one request. Upstream's own default is
|
|
50
|
+
* that 60 s, which races them.
|
|
51
|
+
*/
|
|
52
|
+
const DEFAULT_TIMEOUT_MS = 50_000;
|
|
53
|
+
const DEFAULT_QUEUE_TIMEOUT_MS = 40_000;
|
|
46
54
|
/** Loader-only upstream sandbox whose lease disposes handles at run end. */
|
|
47
55
|
export function workerExecutor(options) {
|
|
48
|
-
|
|
56
|
+
const timeoutMs = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
57
|
+
// One pool per executor, so per isolate. A queued run may wait most of a
|
|
49
58
|
// default guest deadline: programs spend it awaiting host calls, and a
|
|
50
|
-
// five-second wait refused nearly every run queued behind one.
|
|
59
|
+
// five-second wait refused nearly every run queued behind one. The wait
|
|
60
|
+
// spends the request's response ceiling, so it stops short enough to leave
|
|
61
|
+
// the run time to work and the refusal time to reach the client.
|
|
51
62
|
const admission = new AdmissionController(options.admission ?? {
|
|
52
63
|
concurrency: 2,
|
|
53
64
|
maxQueueSize: 8,
|
|
54
|
-
queueTimeoutMs:
|
|
65
|
+
queueTimeoutMs: DEFAULT_QUEUE_TIMEOUT_MS,
|
|
55
66
|
retryAfterMs: 1_000,
|
|
56
67
|
});
|
|
57
68
|
const active = new Set();
|
|
@@ -99,6 +110,20 @@ export function workerExecutor(options) {
|
|
|
99
110
|
// The SDK prepares its source/dispatchers against this inert loader.
|
|
100
111
|
// Actual Loader and RPC acquisition belongs to the native program below.
|
|
101
112
|
let prepared;
|
|
113
|
+
// Upstream 0.5.5+ answers every host call that settles after its
|
|
114
|
+
// evaluate() with a late-call error. Its inert evaluate stays pending
|
|
115
|
+
// until the native evaluation ends, so the SDK's gate stays open for the
|
|
116
|
+
// whole run and closes once the run is over or the lease is released.
|
|
117
|
+
let evaluating;
|
|
118
|
+
const evaluated = new Promise((resolve) => {
|
|
119
|
+
evaluating = resolve;
|
|
120
|
+
});
|
|
121
|
+
let settleUpstream;
|
|
122
|
+
const endUpstream = () => {
|
|
123
|
+
const settle = settleUpstream;
|
|
124
|
+
settleUpstream = undefined;
|
|
125
|
+
settle?.();
|
|
126
|
+
};
|
|
102
127
|
const dispose = (resource) => Effect.sync(() => {
|
|
103
128
|
try {
|
|
104
129
|
const method = Reflect.get(resource, Symbol.dispose);
|
|
@@ -163,19 +188,27 @@ export function workerExecutor(options) {
|
|
|
163
188
|
isolated = isolated.replace("setTimeout(() => reject(timeoutError)", "nativeSetTimeout(() => reject(timeoutError)");
|
|
164
189
|
isolated = isolated.replace("return { result: undefined, error: err.message, logs: __logs };", `
|
|
165
190
|
const failureId = lookup(failures, err);
|
|
191
|
+
// tokenPrefix, for text the host redacts after this cut.
|
|
192
|
+
const tokenEnd = (unit) => ${[...TOKEN_END].map((unit) => `unit === ${unit}`).join(" || ")};
|
|
193
|
+
const prefix = (text, units) => {
|
|
194
|
+
if (units >= text.length) return text;
|
|
195
|
+
let end = units;
|
|
196
|
+
while (end > 0 && !tokenEnd(charCodeAt(text, end))) end--;
|
|
197
|
+
return slice(text, 0, end);
|
|
198
|
+
};
|
|
166
199
|
let name = "Error", message = "Program threw a value.", stack = "";
|
|
167
200
|
try {
|
|
168
|
-
if (typeof err === "string") message =
|
|
201
|
+
if (typeof err === "string") message = prefix(err, 1000);
|
|
169
202
|
else if (err) {
|
|
170
|
-
if (typeof err.name === "string") name =
|
|
171
|
-
if (typeof err.message === "string") message =
|
|
203
|
+
if (typeof err.name === "string") name = prefix(err.name, 64);
|
|
204
|
+
if (typeof err.message === "string") message = prefix(err.message, 1000);
|
|
172
205
|
if (typeof err.stack === "string") stack = slice(err.stack, 0, 1000);
|
|
173
206
|
}
|
|
174
207
|
} catch {}
|
|
175
208
|
const location = exec(/connecta-guest\\.js:(\\d+):\\d+/, stack);
|
|
176
209
|
return { result: undefined, error: message, logs: __logs, failure: {
|
|
177
210
|
name, ...(failureId ? { failureId } : {}), ...(location ? { line: toNumber(location[1]) - 1 } : {}),
|
|
178
|
-
...(err === timeoutError ? { timeout: { elapsedMs: ${
|
|
211
|
+
...(err === timeoutError ? { timeout: { elapsedMs: ${timeoutMs}, deadlineMs: ${timeoutMs} } } : {})
|
|
179
212
|
} };`);
|
|
180
213
|
const preludes = [];
|
|
181
214
|
let firstInitializer;
|
|
@@ -309,7 +342,10 @@ const {
|
|
|
309
342
|
getEntrypoint: () => ({
|
|
310
343
|
evaluate: (...evaluateArgs) => {
|
|
311
344
|
acquisition.args = evaluateArgs;
|
|
312
|
-
return Promise
|
|
345
|
+
return new Promise((resolve) => {
|
|
346
|
+
settleUpstream = () => resolve({ result: undefined });
|
|
347
|
+
evaluating?.();
|
|
348
|
+
});
|
|
313
349
|
},
|
|
314
350
|
}),
|
|
315
351
|
};
|
|
@@ -342,15 +378,21 @@ const {
|
|
|
342
378
|
const started = Date.now();
|
|
343
379
|
const operation = Effect.gen(function* () {
|
|
344
380
|
const setup = yield* Effect.tryPromise({
|
|
345
|
-
try: () =>
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
381
|
+
try: () => {
|
|
382
|
+
const upstream = new DynamicWorkerExecutor({
|
|
383
|
+
loader,
|
|
384
|
+
timeout: timeoutMs,
|
|
385
|
+
}).execute(code, detached);
|
|
386
|
+
// The SDK's own result, which settles only after endUpstream(),
|
|
387
|
+
// carries nothing; the native evaluation below owns the outcome.
|
|
388
|
+
upstream.catch(() => undefined);
|
|
389
|
+
return Promise.race([upstream, evaluated.then(() => undefined)]);
|
|
390
|
+
},
|
|
349
391
|
catch: (error) => error,
|
|
350
392
|
});
|
|
351
393
|
// Provider validation can return before the SDK reaches its loader.
|
|
352
|
-
if (!prepared)
|
|
353
|
-
return setup;
|
|
394
|
+
if (setup !== undefined || !prepared)
|
|
395
|
+
return setup ?? { result: undefined };
|
|
354
396
|
const load = prepared.load;
|
|
355
397
|
const args = prepared.args;
|
|
356
398
|
const open = () => Effect.suspend(() => released || resources.state._tag === "Closed"
|
|
@@ -369,7 +411,7 @@ const {
|
|
|
369
411
|
try: () => Reflect.apply(Reflect.get(entrypoint, "evaluate"), entrypoint, args ?? []),
|
|
370
412
|
catch: (error) => error,
|
|
371
413
|
});
|
|
372
|
-
}).pipe(Scope.provide(resources));
|
|
414
|
+
}).pipe(Scope.provide(resources), Effect.ensuring(Effect.sync(endUpstream)));
|
|
373
415
|
const result = yield* evaluation;
|
|
374
416
|
const outcome = result;
|
|
375
417
|
if (outcome.failure) {
|
|
@@ -407,6 +449,7 @@ const {
|
|
|
407
449
|
}));
|
|
408
450
|
}).pipe(Effect.raceFirst(Deferred.await(stopped)), Effect.catchDefect((error) => Effect.fail(error)), Effect.ensuring(Effect.sync(() => {
|
|
409
451
|
runEnded = true;
|
|
452
|
+
endUpstream();
|
|
410
453
|
hostProviders = undefined;
|
|
411
454
|
prepared = undefined;
|
|
412
455
|
hostFailures.clear();
|
|
@@ -420,6 +463,7 @@ const {
|
|
|
420
463
|
if (released)
|
|
421
464
|
return;
|
|
422
465
|
released = true;
|
|
466
|
+
endUpstream();
|
|
423
467
|
hostProviders = undefined;
|
|
424
468
|
prepared = undefined;
|
|
425
469
|
hostFailures.clear();
|
|
@@ -438,6 +482,7 @@ const {
|
|
|
438
482
|
const acquireShell = (request) => runEdge(acquire(request));
|
|
439
483
|
const executor = {
|
|
440
484
|
name: "DynamicWorkerExecutor",
|
|
485
|
+
deadlineMs: timeoutMs,
|
|
441
486
|
acquire: acquireShell,
|
|
442
487
|
execute: (code, providers) => runEdge(execute(code, providers)),
|
|
443
488
|
admissionSnapshot: () => admission.snapshot(),
|