@zackbart/connecta 0.37.1 → 0.39.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 +94 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/agent-routes.js +231 -0
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2516
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/native.js +27 -0
- package/dist/auth/oauth-grant.js +24 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +9 -3
- package/dist/authorization-handoff.js +11 -5
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +130 -43
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +285 -48
- package/dist/catalog.js +18 -2
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +57 -25
- package/dist/config.js +30 -77
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +11 -7
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +671 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +469 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +544 -0
- package/dist/connectors/remote-mcp.js +61 -2898
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +4 -2
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +172 -125
- package/dist/executor-result.js +5 -2
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +63 -20
- package/dist/guest-types.d.ts +24 -10
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +7 -6
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +4 -3
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +33 -5
- package/dist/http/safety.js +9 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +45 -30
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +21 -6
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +50 -35
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +6 -2
- package/dist/oauth-handoff.js +2 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +136 -14
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -49
- package/dist/operator-ui/contract.d.ts +12 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +49 -53
- package/dist/providers/_shared/rest/value-safety.js +106 -28
- package/dist/providers/breeze/api.js +9 -8
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +7 -7
- package/dist/providers/ccb/api.js +3 -7
- package/dist/providers/ccb/index.js +7 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +10 -3
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +4 -14
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +23 -41
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +11 -16
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +12 -26
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +8 -4
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +5 -2
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +14 -23
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +19 -26
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +5 -8
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +2 -7
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +6 -4
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +11 -5
- package/dist/registry.js +46 -20
- package/dist/request-principal.js +1 -1
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +130 -61
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +34 -17
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +6 -5
- package/dist/routes/shared.d.ts +13 -8
- package/dist/routes/shared.js +15 -9
- package/dist/routes/ui-config.js +3 -3
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +17 -1
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +8 -3
- package/dist/runtime/native-storage.js +9 -0
- package/dist/runtime/request-reads.js +182 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/runtime/storage.js +12 -0
- package/dist/sent-secrets.d.ts +1 -2
- package/dist/sent-secrets.js +3 -8
- package/dist/server.js +4 -4
- package/dist/skills.js +44 -54
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +69 -128
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +27 -101
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +13 -14
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +33 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +18 -37
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +19 -55
- package/documentation/architecture.md +436 -515
- package/documentation/auth.md +229 -88
- package/documentation/code-mode.md +201 -94
- package/documentation/deploying.md +25 -43
- package/documentation/integrating.md +18 -19
- package/documentation/meta-tools.md +225 -51
- package/documentation/migration-0.33.md +10 -6
- package/documentation/migration-0.34.md +5 -5
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +20 -120
- package/documentation/operator-ui.md +108 -16
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +14 -16
- package/examples/worker/README.md +24 -118
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +9 -31
- package/guides/how-it-works.md +6 -4
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
package/dist/execute.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { META_ROUTES, routeNames } from "./agent-routes.js";
|
|
1
2
|
import { authCall, authFlow, authInvoke, authMethod } from "./auth/native.js";
|
|
2
3
|
import { nativeRecovery } from "./native-recovery.js";
|
|
3
|
-
import { GUEST_API_DECLARATION } from "./
|
|
4
|
+
import { GUEST_API_DECLARATION, GUEST_FUNCTIONS, UNCOUNTED_GUEST_FUNCTIONS, } from "./guest-functions.js";
|
|
4
5
|
import { bindMcpClient } from "./mcp-client-context.js";
|
|
5
6
|
import { CONFIG_DEFAULTS } from "./config-defaults.js";
|
|
6
7
|
import { Cause, Deferred, Duration, Effect, Exit } from "effect";
|
|
@@ -37,8 +38,11 @@ const EXECUTE_MAX_CODE_BYTES = 64 * 1024;
|
|
|
37
38
|
* never settles at all.
|
|
38
39
|
*/
|
|
39
40
|
const EXECUTE_WATCHDOG_MS = CONFIG_DEFAULTS.execute.watchdogMs;
|
|
40
|
-
/**
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Room to name every connector of a typical deployment. Past it, complete
|
|
43
|
+
* entries plus an exact omission count stay inside this byte cap.
|
|
44
|
+
*/
|
|
45
|
+
export const CONNECTOR_INVENTORY_MAX_BYTES = 1_024;
|
|
42
46
|
/**
|
|
43
47
|
* Default budgets for `connecta.emit`. The byte budget is a transport bound,
|
|
44
48
|
* not a context bound — emitted image/audio blocks reach the model as media,
|
|
@@ -175,13 +179,15 @@ export class EmitCollector {
|
|
|
175
179
|
maxBytes;
|
|
176
180
|
maxBlocks;
|
|
177
181
|
diagnostics;
|
|
182
|
+
maxResultChars;
|
|
178
183
|
blocks = [];
|
|
179
184
|
bytes = 0;
|
|
180
185
|
textChars = 0;
|
|
181
|
-
constructor(maxBytes, maxBlocks, diagnostics) {
|
|
186
|
+
constructor(maxBytes, maxBlocks, diagnostics, maxResultChars = MAX_EXECUTE_RESULT_CHARS) {
|
|
182
187
|
this.maxBytes = maxBytes;
|
|
183
188
|
this.maxBlocks = maxBlocks;
|
|
184
189
|
this.diagnostics = diagnostics;
|
|
190
|
+
this.maxResultChars = maxResultChars;
|
|
185
191
|
}
|
|
186
192
|
accept(raw) {
|
|
187
193
|
this.acceptNative([requireEmittedBlock(raw)]);
|
|
@@ -205,8 +211,8 @@ export class EmitCollector {
|
|
|
205
211
|
// the transport budget, just like images and audio.
|
|
206
212
|
if (block.type === "resource" && "text" in block.resource)
|
|
207
213
|
textChars += serialized.length;
|
|
208
|
-
if (textChars >
|
|
209
|
-
throw guestFailure("result_too_large",
|
|
214
|
+
if (textChars > this.maxResultChars - 512) {
|
|
215
|
+
throw guestFailure("result_too_large", `Emitted text shares the ${this.maxResultChars.toLocaleString("en-US")}-character program result limit. Reduce the text before emitting it.`);
|
|
210
216
|
}
|
|
211
217
|
count++;
|
|
212
218
|
bytes += size;
|
|
@@ -423,6 +429,7 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
423
429
|
concurrency: limits.discoveryConcurrency,
|
|
424
430
|
probeTimeoutMs: limits.probeTimeoutMs,
|
|
425
431
|
defer: limits.defer,
|
|
432
|
+
routes: limits.routes,
|
|
426
433
|
});
|
|
427
434
|
const invocation = new InvocationService(registry, catalog, activity);
|
|
428
435
|
const maxHostCalls = Math.max(1, Math.trunc(limits.maxHostCalls ?? EXECUTE_MAX_HOST_CALLS));
|
|
@@ -466,6 +473,7 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
466
473
|
source: "execute_code",
|
|
467
474
|
sentSecrets,
|
|
468
475
|
timeoutMs,
|
|
476
|
+
...(limits.routes ? { routes: limits.routes } : {}),
|
|
469
477
|
...(signal !== undefined ? { requestSignal: signal } : {}),
|
|
470
478
|
...(limits.dispatchController ? { dispatchSignal: limits.dispatchController.signal } : {}),
|
|
471
479
|
unwrapResult: true,
|
|
@@ -696,6 +704,11 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
696
704
|
catalogDependency(resolved.connector.id, resolved.toolName);
|
|
697
705
|
};
|
|
698
706
|
const operations = {
|
|
707
|
+
// Uncounted: a failed program read a result field off an unawaited host call.
|
|
708
|
+
unawaited: () => {
|
|
709
|
+
if (limits.unawaited)
|
|
710
|
+
limits.unawaited.read = true;
|
|
711
|
+
},
|
|
699
712
|
call,
|
|
700
713
|
result: (id, options, signal, onStage) => Effect.tryPromise({
|
|
701
714
|
try: async () => {
|
|
@@ -750,8 +763,10 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
750
763
|
}),
|
|
751
764
|
skill: (name, _options, utilitySignal) => Effect.tryPromise({
|
|
752
765
|
try: async () => {
|
|
753
|
-
|
|
754
|
-
|
|
766
|
+
// Accept the top-level skills tool's { name } shape too: models copy it into programs.
|
|
767
|
+
const skillName = name && typeof name === "object" ? name.name : name;
|
|
768
|
+
if (typeof skillName !== "string")
|
|
769
|
+
throw guestFailure("invalid_args", 'Use connecta.skill("usage") or connecta.skill("connector:<id>") with an exact skill name; the skills tool lists names.');
|
|
755
770
|
const skills = new SkillsRegistry(registry, baseUrl, {
|
|
756
771
|
requestScope,
|
|
757
772
|
requestSignal: utilitySignal ?? hostAccessSignal,
|
|
@@ -763,7 +778,7 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
763
778
|
probeTimeoutMs: limits.probeTimeoutMs,
|
|
764
779
|
defer: limits.defer,
|
|
765
780
|
});
|
|
766
|
-
return { name, format: "text", text: await skills.text(
|
|
781
|
+
return { name: skillName, format: "text", text: await skills.text(skillName) };
|
|
767
782
|
},
|
|
768
783
|
catch: (err) => err instanceof ConnectorCallError ? guestFailure(err.code, err.message, err.code === "unavailable") : err,
|
|
769
784
|
}),
|
|
@@ -818,7 +833,7 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
818
833
|
const args = (raw ?? {});
|
|
819
834
|
const result = flatSearchResult(await catalog.search({
|
|
820
835
|
...args,
|
|
821
|
-
includeSchemas: args.includeSchemas ?? "
|
|
836
|
+
includeSchemas: args.includeSchemas ?? "typescript",
|
|
822
837
|
includeSchemaKeys: args.includeSchemaKeys !== false,
|
|
823
838
|
}, catalogDependency));
|
|
824
839
|
boundedDiscoveryText(result, "Request a smaller limit, omit fullDescriptions, use compact schemas, or pass includeSchemaKeys: false.");
|
|
@@ -852,90 +867,102 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
|
|
|
852
867
|
const failure = boundedGuestFailure(redactAgentOutput(sentSecrets, err));
|
|
853
868
|
return Effect.fail(failure);
|
|
854
869
|
});
|
|
870
|
+
/** End the whole run: later bridge calls, including emit, lose host access. */
|
|
871
|
+
const endRun = (failure) => {
|
|
872
|
+
budgetFailure = failure;
|
|
873
|
+
programWrites?.close();
|
|
874
|
+
limits.dispatchController?.abort();
|
|
875
|
+
limits.onHostCallBudgetExceeded?.(failure);
|
|
876
|
+
return stopped;
|
|
877
|
+
};
|
|
855
878
|
return {
|
|
856
879
|
name: "connecta",
|
|
857
880
|
// Trusted host code the program runs after and cannot undo.
|
|
858
881
|
prelude: guestPrelude(),
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
programWrites?.close();
|
|
869
|
-
limits.dispatchController?.abort();
|
|
870
|
-
limits.onHostCallBudgetExceeded?.(budgetFailure);
|
|
871
|
-
return stopped;
|
|
872
|
-
}
|
|
873
|
-
// L4/M7: spend on entry, before even invalid arguments are checked;
|
|
874
|
-
// emit has a separate budget and never spends this one.
|
|
875
|
-
// Transparent transfer pages consume the already-admitted call, not guest host calls.
|
|
876
|
-
const counted = name !== "emit" && !(name === "result" && typeof args[0] === "string" && transfers.has(args[0]));
|
|
877
|
-
if (counted && ++hostCalls.attempted > maxHostCalls) {
|
|
878
|
-
hostCalls.failed++;
|
|
879
|
-
budgetFailure = new HostCallBudgetExceeded(hostCalls, maxHostCalls);
|
|
880
|
-
programWrites?.close();
|
|
881
|
-
limits.dispatchController?.abort();
|
|
882
|
-
limits.onHostCallBudgetExceeded?.(budgetFailure);
|
|
883
|
-
return stopped;
|
|
884
|
-
}
|
|
885
|
-
if (counted)
|
|
886
|
-
hostCalls.admitted++;
|
|
887
|
-
const started = Date.now();
|
|
888
|
-
const utility = name === "result" || name === "skill";
|
|
889
|
-
let stage = name === "result" ? "result storage" : "skill lookup";
|
|
890
|
-
const invoke = Effect.suspend(() => operation(...args));
|
|
891
|
-
const guarded = utility
|
|
892
|
-
? withDeadlineEffect((utilitySignal) => Effect.suspend(() => operation(args[0], args[1], utilitySignal, (current) => {
|
|
893
|
-
stage = current;
|
|
894
|
-
})), {
|
|
895
|
-
timeoutMs: hostCallTimeoutMs,
|
|
896
|
-
...(hostAccessSignal ? { signal: hostAccessSignal } : {}),
|
|
897
|
-
timeoutError: guestFailure("timeout", `connecta.${name} timed out.`),
|
|
898
|
-
})
|
|
899
|
-
: invoke;
|
|
900
|
-
const settled = runEdge(guarded.pipe(Effect.catch((error) => {
|
|
901
|
-
if (utility && hostAccessSignal?.aborted)
|
|
902
|
-
return typedFailure(guestFailure("cancelled", `connecta.${name} was cancelled because the run ended.`));
|
|
903
|
-
if (utility && error instanceof InvocationFailure && error.code === "timeout") {
|
|
904
|
-
const elapsedMs = Date.now() - started;
|
|
905
|
-
const message = `Operation "connecta.${name}" timed out during ${stage} after ${elapsedMs}ms (effective deadline ${hostCallTimeoutMs}ms).`;
|
|
906
|
-
return typedFailure(new InvocationFailure({
|
|
907
|
-
code: "timeout",
|
|
908
|
-
message,
|
|
909
|
-
retryable: true,
|
|
910
|
-
details: {
|
|
911
|
-
operation: `connecta.${name}`,
|
|
912
|
-
stage,
|
|
913
|
-
elapsedMs,
|
|
914
|
-
deadlineMs: hostCallTimeoutMs,
|
|
915
|
-
},
|
|
916
|
-
}));
|
|
917
|
-
}
|
|
918
|
-
return typedFailure(error);
|
|
919
|
-
}))).then((value) => {
|
|
920
|
-
if (counted)
|
|
921
|
-
hostCalls.succeeded++;
|
|
922
|
-
return budgetFailure ? stopped : redactAgentOutput(sentSecrets, value);
|
|
923
|
-
}, (err) => {
|
|
924
|
-
if (counted)
|
|
925
|
-
hostCalls.failed++;
|
|
882
|
+
// One bridge function per registry entry, admitted by its budget class.
|
|
883
|
+
fns: Object.fromEntries(GUEST_FUNCTIONS.map((fn) => {
|
|
884
|
+
const { name, budget } = fn;
|
|
885
|
+
const operation = operations[name];
|
|
886
|
+
const deadline = "deadline" in fn ? fn.deadline : undefined;
|
|
887
|
+
let notices = 0;
|
|
888
|
+
return [
|
|
889
|
+
name,
|
|
890
|
+
(...args) => {
|
|
926
891
|
if (budgetFailure)
|
|
927
892
|
return stopped;
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
893
|
+
// Uncounted: one per run; any further call through the bridge ends the run.
|
|
894
|
+
if (budget === "once") {
|
|
895
|
+
if (++notices > 1)
|
|
896
|
+
return endRun(guestFailure("budget_exceeded", `execute_code ${fn.label} is limited to one per run.`));
|
|
897
|
+
operation();
|
|
898
|
+
return Promise.resolve();
|
|
899
|
+
}
|
|
900
|
+
// Invalid emits still allocate guest promises and typed rejections.
|
|
901
|
+
// Bound those attempts without consuming the invocation budget or discarding identities.
|
|
902
|
+
if (budget === "emission" && ++emitAttempts > maxEmitAttempts)
|
|
903
|
+
return endRun(guestFailure("budget_exceeded", `execute_code emission-attempt budget exceeded (${maxEmitAttempts} attempts maximum).`));
|
|
904
|
+
// L4/M7: spend on entry, before even invalid arguments are checked;
|
|
905
|
+
// emission has a separate budget and never spends this one.
|
|
906
|
+
// Transparent transfer pages consume the already-admitted call, not guest host calls.
|
|
907
|
+
const counted = budget === "hostCall" && !(name === "result" && typeof args[0] === "string" && transfers.has(args[0]));
|
|
908
|
+
if (counted && ++hostCalls.attempted > maxHostCalls) {
|
|
909
|
+
hostCalls.failed++;
|
|
910
|
+
return endRun(new HostCallBudgetExceeded(hostCalls, maxHostCalls));
|
|
911
|
+
}
|
|
912
|
+
if (counted)
|
|
913
|
+
hostCalls.admitted++;
|
|
914
|
+
const started = Date.now();
|
|
915
|
+
let stage = deadline ?? "";
|
|
916
|
+
const invoke = Effect.suspend(() => operation(...args));
|
|
917
|
+
const guarded = deadline
|
|
918
|
+
? withDeadlineEffect((utilitySignal) => Effect.suspend(() => operation(args[0], args[1], utilitySignal, (current) => {
|
|
919
|
+
stage = current;
|
|
920
|
+
})), {
|
|
921
|
+
timeoutMs: hostCallTimeoutMs,
|
|
922
|
+
...(hostAccessSignal ? { signal: hostAccessSignal } : {}),
|
|
923
|
+
timeoutError: guestFailure("timeout", `connecta.${name} timed out.`),
|
|
924
|
+
})
|
|
925
|
+
: invoke;
|
|
926
|
+
const settled = runEdge(guarded.pipe(Effect.catch((error) => {
|
|
927
|
+
if (deadline && hostAccessSignal?.aborted)
|
|
928
|
+
return typedFailure(guestFailure("cancelled", `connecta.${name} was cancelled because the run ended.`));
|
|
929
|
+
if (deadline && error instanceof InvocationFailure && error.code === "timeout") {
|
|
930
|
+
const elapsedMs = Date.now() - started;
|
|
931
|
+
const message = `Operation "connecta.${name}" timed out during ${stage} after ${elapsedMs}ms (effective deadline ${hostCallTimeoutMs}ms).`;
|
|
932
|
+
return typedFailure(new InvocationFailure({
|
|
933
|
+
code: "timeout",
|
|
934
|
+
message,
|
|
935
|
+
retryable: true,
|
|
936
|
+
details: {
|
|
937
|
+
operation: `connecta.${name}`,
|
|
938
|
+
stage,
|
|
939
|
+
elapsedMs,
|
|
940
|
+
deadlineMs: hostCallTimeoutMs,
|
|
941
|
+
},
|
|
942
|
+
}));
|
|
943
|
+
}
|
|
944
|
+
return typedFailure(error);
|
|
945
|
+
}))).then((value) => {
|
|
946
|
+
if (counted)
|
|
947
|
+
hostCalls.succeeded++;
|
|
948
|
+
return budgetFailure ? stopped : redactAgentOutput(sentSecrets, value);
|
|
949
|
+
}, (err) => {
|
|
950
|
+
if (counted)
|
|
951
|
+
hostCalls.failed++;
|
|
952
|
+
if (budgetFailure)
|
|
953
|
+
return stopped;
|
|
954
|
+
throw redactAgentOutput(sentSecrets, err);
|
|
955
|
+
});
|
|
956
|
+
// The executor owns this promise, and a program may abandon a call
|
|
957
|
+
// that the run's end then cancels before anyone has awaited it.
|
|
958
|
+
// The rejection is still there for whoever does; it is just not an
|
|
959
|
+
// unhandled rejection of the host's, which workerd reports as soon
|
|
960
|
+
// as a microtask passes without a handler.
|
|
961
|
+
settled.catch(() => { });
|
|
962
|
+
return settled;
|
|
963
|
+
},
|
|
964
|
+
];
|
|
965
|
+
})),
|
|
939
966
|
};
|
|
940
967
|
}
|
|
941
968
|
/**
|
|
@@ -1044,11 +1071,13 @@ export function createExecuteTool(registry, baseUrl, executor, logger, activity,
|
|
|
1044
1071
|
logger,
|
|
1045
1072
|
};
|
|
1046
1073
|
const hostCallTimeoutMs = resolveBudget(config.hostCallTimeoutMs, EXECUTE_HOST_CALL_TIMEOUT_MS);
|
|
1074
|
+
const maxResultChars = resolveBudget(config.maxResultChars, MAX_EXECUTE_RESULT_CHARS);
|
|
1047
1075
|
const play = (program, diagnosticsRequested, callerSignal, requestScope, hostCalls, programWrites) => Effect.suspend(() => {
|
|
1048
1076
|
const sentSecrets = sentSecretsForRequest(requestScope);
|
|
1049
1077
|
const diagnostics = diagnosticsRequested ? new ExecuteDiagnostics() : undefined;
|
|
1050
|
-
const emitted = new EmitCollector(resolveBudget(config.maxEmittedBytes, EXECUTE_MAX_EMITTED_BYTES), resolveBudget(config.maxEmittedBlocks, EXECUTE_MAX_EMITTED_BLOCKS), diagnostics);
|
|
1078
|
+
const emitted = new EmitCollector(resolveBudget(config.maxEmittedBytes, EXECUTE_MAX_EMITTED_BYTES), resolveBudget(config.maxEmittedBlocks, EXECUTE_MAX_EMITTED_BLOCKS), diagnostics, maxResultChars);
|
|
1051
1079
|
const resultDependencies = [];
|
|
1080
|
+
const unawaited = { read: false };
|
|
1052
1081
|
const dispatchController = new AbortController();
|
|
1053
1082
|
const terminal = Deferred.makeUnsafe();
|
|
1054
1083
|
let budgetFailure;
|
|
@@ -1099,9 +1128,11 @@ export function createExecuteTool(registry, baseUrl, executor, logger, activity,
|
|
|
1099
1128
|
defer: config.defer,
|
|
1100
1129
|
readOnly: config.readOnly,
|
|
1101
1130
|
maxWrites: config.maxWrites,
|
|
1131
|
+
routes: config.routes,
|
|
1102
1132
|
programWrites,
|
|
1103
1133
|
resultDependencies,
|
|
1104
1134
|
dispatchController,
|
|
1135
|
+
unawaited,
|
|
1105
1136
|
}, requestScope)));
|
|
1106
1137
|
if (signal.aborted) {
|
|
1107
1138
|
return yield* Effect.fail(new ExecutorAdmissionError("executor_cancelled", "Execution was cancelled during sandbox setup."));
|
|
@@ -1137,15 +1168,18 @@ export function createExecuteTool(registry, baseUrl, executor, logger, activity,
|
|
|
1137
1168
|
emitted,
|
|
1138
1169
|
diagnostics,
|
|
1139
1170
|
program,
|
|
1140
|
-
|
|
1171
|
+
unawaited,
|
|
1172
|
+
// Emitted text shares the return cap; a follow-up page run gets all of it.
|
|
1173
|
+
guardResult: (value) => authCall(guardProgramReturn, [
|
|
1141
1174
|
value,
|
|
1142
1175
|
registry,
|
|
1143
1176
|
baseUrl,
|
|
1144
1177
|
[...new Map(resultDependencies.map((dependency) => [JSON.stringify(dependency), dependency])).values()],
|
|
1145
1178
|
sentSecrets,
|
|
1146
1179
|
logger,
|
|
1147
|
-
|
|
1180
|
+
maxResultChars - emitted.textChars,
|
|
1148
1181
|
programWrites.hasSucceeded,
|
|
1182
|
+
maxResultChars,
|
|
1149
1183
|
]),
|
|
1150
1184
|
};
|
|
1151
1185
|
const exited = Effect.exit(Effect.scoped(run)).pipe(Effect.flatMap((exit) =>
|
|
@@ -1249,7 +1283,7 @@ function failedRun(err, logger, { emitted, diagnostics, program }) {
|
|
|
1249
1283
|
});
|
|
1250
1284
|
}
|
|
1251
1285
|
/** The response for an ExecuteResult: the program's error or its value. */
|
|
1252
|
-
function finishedRun(outcome, { emitted, diagnostics, program, guardResult }) {
|
|
1286
|
+
function finishedRun(outcome, { emitted, diagnostics, program, unawaited, guardResult }) {
|
|
1253
1287
|
return authFlow(function* () {
|
|
1254
1288
|
if (outcome === null || typeof outcome !== "object" || Array.isArray(outcome)) {
|
|
1255
1289
|
return failureResponse("Executor failed: expected an ExecuteResult object.", {
|
|
@@ -1293,6 +1327,7 @@ function finishedRun(outcome, { emitted, diagnostics, program, guardResult }) {
|
|
|
1293
1327
|
name: outcome.failure?.name ?? /^(SyntaxError|ReferenceError|TypeError|RangeError):/.exec(outcome.error)?.[1],
|
|
1294
1328
|
message: outcome.error || "Execution failed without an error message.",
|
|
1295
1329
|
...(outcome.failure?.line !== undefined ? { line: outcome.failure.line - 1 } : {}),
|
|
1330
|
+
unawaited: unawaited.read,
|
|
1296
1331
|
}, program),
|
|
1297
1332
|
});
|
|
1298
1333
|
}
|
|
@@ -1301,7 +1336,7 @@ function finishedRun(outcome, { emitted, diagnostics, program, guardResult }) {
|
|
|
1301
1336
|
// error path so captured logs survive instead of a raw SDK 500.
|
|
1302
1337
|
let result;
|
|
1303
1338
|
try {
|
|
1304
|
-
result = yield* guardResult(outcome.result
|
|
1339
|
+
result = yield* guardResult(outcome.result);
|
|
1305
1340
|
}
|
|
1306
1341
|
catch (err) {
|
|
1307
1342
|
const message = `Error: result is not JSON-serializable: ${msg(err)}`;
|
|
@@ -1349,50 +1384,59 @@ function failureResponse(message, options) {
|
|
|
1349
1384
|
function discardedEmits(emitted) {
|
|
1350
1385
|
return emitted.blocks.length > 0 ? { emittedDiscarded: emitted.blocks.length } : {};
|
|
1351
1386
|
}
|
|
1387
|
+
const inventoryWords = (text) => text
|
|
1388
|
+
.toLowerCase()
|
|
1389
|
+
.split(/[^\p{L}\p{N}]+/u)
|
|
1390
|
+
.filter(Boolean);
|
|
1391
|
+
/** A title that only respells the id ("Google Calendar" for google_calendar) adds nothing. */
|
|
1392
|
+
function titleAddsInformation(id, title) {
|
|
1393
|
+
const words = inventoryWords(title);
|
|
1394
|
+
const idWords = inventoryWords(id);
|
|
1395
|
+
return words.join("") !== idWords.join("") && words.some((word) => !idWords.includes(word));
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Every configured connector, from configuration alone. Detail degrades before
|
|
1399
|
+
* names do: titles go first, then optional guide pointers, and only past the
|
|
1400
|
+
* byte cap do whole entries give way to an exact omission count.
|
|
1401
|
+
*/
|
|
1352
1402
|
function connectorInventory(connectors) {
|
|
1353
1403
|
const prefix = "Connectors: ";
|
|
1354
1404
|
if (connectors.length === 0)
|
|
1355
1405
|
return `${prefix}none.`;
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1406
|
+
const fits = (line) => boundedEchoText(line, CONNECTOR_INVENTORY_MAX_BYTES) === line;
|
|
1407
|
+
const entry = (connector, detail) => {
|
|
1408
|
+
const notes = [];
|
|
1358
1409
|
const title = connector.title?.replace(/\s+/g, " ").trim();
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
const shown = [];
|
|
1366
|
-
for (let index = 0; index < entries.length; index++) {
|
|
1367
|
-
const entry = entries[index];
|
|
1368
|
-
if (entry === undefined)
|
|
1369
|
-
break;
|
|
1370
|
-
const candidate = [...shown, entry].join(", ");
|
|
1371
|
-
const omitted = entries.length - index - 1;
|
|
1372
|
-
const suffix = omitted > 0 ? `; +${omitted} more.` : ".";
|
|
1373
|
-
const serialized = prefix + candidate + suffix;
|
|
1374
|
-
if (boundedEchoText(serialized, CONNECTOR_INVENTORY_MAX_BYTES) !== serialized) {
|
|
1375
|
-
break;
|
|
1410
|
+
if (detail >= 2 && title && titleAddsInformation(connector.id, title))
|
|
1411
|
+
notes.push(boundedEchoText(title, 45));
|
|
1412
|
+
const required = connectorGuideRequired(connector);
|
|
1413
|
+
if (connectorGuide(connector) && (required || detail >= 1)) {
|
|
1414
|
+
const fetch = `skills({name:"${connectorSkillName(connector.id)}"})`;
|
|
1415
|
+
notes.push(required ? `read first: ${fetch}` : `guide: ${fetch}`);
|
|
1376
1416
|
}
|
|
1377
|
-
|
|
1417
|
+
return notes.length > 0 ? `${connector.id} (${notes.join("; ")})` : connector.id;
|
|
1418
|
+
};
|
|
1419
|
+
for (const detail of [2, 1, 0]) {
|
|
1420
|
+
const line = `${prefix}${connectors.map((connector) => entry(connector, detail)).join(", ")}.`;
|
|
1421
|
+
if (fits(line))
|
|
1422
|
+
return line;
|
|
1378
1423
|
}
|
|
1379
|
-
const
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1424
|
+
const entries = connectors.map((connector) => entry(connector, 0));
|
|
1425
|
+
let shown = 0;
|
|
1426
|
+
while (fits(`${prefix}${entries.slice(0, shown + 1).join(", ")}; +${entries.length - shown - 1} more.`))
|
|
1427
|
+
shown++;
|
|
1428
|
+
return `${prefix}${entries.slice(0, shown).join(", ")}${shown > 0 ? "; " : ""}+${entries.length - shown} more.`;
|
|
1383
1429
|
}
|
|
1384
|
-
const executeDescription = (emitBudgets, hostLimits, connectorGuides, connectors, maxWrites, readOnly, native = false) => `${native ? `Call tools directly; combine work here using their program addresses. ${readOnly ? "Read-only endpoint: writes are omitted and refused." : "Writable endpoint: programs may read and write; the client approves."}` : readOnly ?
|
|
1430
|
+
const executeDescription = (emitBudgets, hostLimits, connectorGuides, connectors, maxWrites, readOnly, native = false, routes = META_ROUTES) => `${native ? `Call tools directly; combine work here using their program addresses. ${readOnly ? "Read-only endpoint: writes are omitted and refused." : "Writable endpoint: programs may read and write; the client approves."}` : `${routes === routeNames("code") ? `Every operation uses ${routes.program}.` : readOnly ? `One known read: ${routes.read}. Everything else: ${routes.program}.` : `One known read: ${routes.read}. One known write: ${routes.write}. Everything else: ${routes.program}.`} ${readOnly ? "Read-only endpoint: search and describe omit write tools, and every write call is refused." : "Writable endpoint: programs may read and write; the client approves."}`} Limits: ${hostLimits.maxHostCalls} host calls, ${readOnly ? "" : `${maxWrites} writes, `}${hostLimits.callTimeoutMs === hostLimits.hostCallTimeoutMs ? `${hostLimits.callTimeoutMs / 1_000}s/host call` : `${hostLimits.callTimeoutMs / 1_000}s/call (${hostLimits.hostCallTimeoutMs / 1_000}s result/skill/read)`}.
|
|
1385
1431
|
${connectorInventory(connectors)}
|
|
1386
1432
|
|
|
1387
1433
|
Use async () => { ... }, no arguments. No portable ambient capabilities. API:
|
|
1388
1434
|
\`\`\`ts
|
|
1389
|
-
declare const connecta: {
|
|
1390
1435
|
${GUEST_API_DECLARATION}
|
|
1391
|
-
};
|
|
1392
1436
|
\`\`\`
|
|
1393
|
-
|
|
1437
|
+
search and describe return objects, not arrays. Search shows a TypeScript signature by default; describe, JSON Schema. A call result over 1 MiB returns a paged handle; read it with connecta.result(handle, { offset }), which pages by UTF-8 byte offset. Every function except ${UNCOUNTED_GUEST_FUNCTIONS} spends a host call. Promise.allSettled keeps per-call errors, but exceeding the host-call limit ends the program. When paging is available, an oversized program return carries resultId and nextAction.
|
|
1394
1438
|
|
|
1395
|
-
Never repeat writes for output. emit(result) or emit(block) from result.content forwards MCP blocks
|
|
1439
|
+
Never repeat writes for output. emit(result) or emit(block) from result.content forwards MCP blocks unchanged; an authored Block's image/audio data is base64. emit needs no await. Caps: ${emitBudgets.maxBlocks} blocks/${emitBudgets.maxBytes} bytes. Text shares result cap. connecta.skill("usage"): repair${connectorGuides ? ", guide handling" : ""}.`;
|
|
1396
1440
|
// Module scope, like the other five meta-tool inputs: its JSON Schema is
|
|
1397
1441
|
// derived once per process. Budgets and connectors vary by deployment and
|
|
1398
1442
|
// view, so they live in the description, never here.
|
|
@@ -1430,13 +1474,16 @@ export function registerExecuteTool(server, registry, ctx) {
|
|
|
1430
1474
|
defer: ctx.defer,
|
|
1431
1475
|
readOnly: ctx.readOnly,
|
|
1432
1476
|
maxWrites,
|
|
1477
|
+
maxResultChars: ctx.maxResultChars,
|
|
1433
1478
|
requestScope: ctx.requestScope,
|
|
1479
|
+
routes: ctx.routes,
|
|
1434
1480
|
});
|
|
1435
1481
|
server.registerTool("execute_code", {
|
|
1436
1482
|
description: executeDescription(emitBudgets, {
|
|
1437
1483
|
...hostLimits,
|
|
1438
|
-
|
|
1439
|
-
|
|
1484
|
+
// connecta.call falls back to calls.defaultTimeoutMs; result, skill and read always use hostCallTimeoutMs.
|
|
1485
|
+
callTimeoutMs: normalizeTimeoutMs(ctx.defaultToolTimeoutMs) ?? hostLimits.hostCallTimeoutMs,
|
|
1486
|
+
}, hasConnectorGuides(connectors), connectors, maxWrites, ctx.readOnly === true, ctx.native === true, ctx.routes),
|
|
1440
1487
|
inputSchema: EXECUTE_INPUT,
|
|
1441
1488
|
outputSchema: EXECUTE_OUTPUT,
|
|
1442
1489
|
// The client sees execute_code as a write exactly when the endpoint writes.
|
|
@@ -1471,6 +1518,6 @@ export function registerExecuteTool(server, registry, ctx) {
|
|
|
1471
1518
|
const result = yield* ctx.authElicitation
|
|
1472
1519
|
? authInvoke(ctx.authElicitation, "run", ["execute_code", args, extra, run])
|
|
1473
1520
|
: authCall(run, []);
|
|
1474
|
-
return ctx.native ? nativeRecovery(result
|
|
1521
|
+
return ctx.native ? nativeRecovery(result) : result;
|
|
1475
1522
|
})));
|
|
1476
1523
|
}
|
package/dist/executor-result.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { META_ROUTES } from "./agent-routes.js";
|
|
2
|
+
import { CONFIG_DEFAULTS } from "./config-defaults.js";
|
|
1
3
|
import { msg } from "./errors.js";
|
|
2
4
|
/** ~6k tokens. Sandbox code should filter data down before returning. */
|
|
3
|
-
export const MAX_EXECUTE_RESULT_CHARS =
|
|
5
|
+
export const MAX_EXECUTE_RESULT_CHARS = CONFIG_DEFAULTS.execute.maxResultChars;
|
|
4
6
|
export const MAX_EXECUTE_LOG_CHARS = 4_000;
|
|
5
7
|
export function serializeResultText(value) {
|
|
6
8
|
const serialized = JSON.stringify(value);
|
|
7
9
|
return serialized === undefined ? String(value) : serialized;
|
|
8
10
|
}
|
|
9
|
-
|
|
11
|
+
// Only program returns carry it, and every surface running programs lists execute_code.
|
|
12
|
+
const TRUNCATION_HINT = `filter/map/slice data inside ${META_ROUTES.program} and return only what you need`;
|
|
10
13
|
/**
|
|
11
14
|
* Shape the over-cap notice so the **serialized envelope** fits the same cap
|
|
12
15
|
* the raw value missed. Escaping matters: a preview sliced to the cap is JSON
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { utf8Length } from "../bytes.js";
|
|
1
2
|
// QuickJS-in-WebAssembly runtime used inside the disposable child process.
|
|
2
3
|
//
|
|
3
4
|
// Model-written code runs inside a QuickJS interpreter compiled to WASM:
|
|
@@ -37,30 +38,6 @@ function logTransportBytes(entry) {
|
|
|
37
38
|
// encoded into ChildToParentMessage. Measure the units the IPC cap sees.
|
|
38
39
|
return serializedBytes(JSON.stringify(JSON.stringify(entry)));
|
|
39
40
|
}
|
|
40
|
-
function exceedsUtf8ByteLimit(value, limit) {
|
|
41
|
-
let bytes = 0;
|
|
42
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
43
|
-
const code = value.charCodeAt(index);
|
|
44
|
-
if (code <= 0x7f)
|
|
45
|
-
bytes += 1;
|
|
46
|
-
else if (code <= 0x7ff)
|
|
47
|
-
bytes += 2;
|
|
48
|
-
else if (code >= 0xd800 &&
|
|
49
|
-
code <= 0xdbff &&
|
|
50
|
-
index + 1 < value.length &&
|
|
51
|
-
value.charCodeAt(index + 1) >= 0xdc00 &&
|
|
52
|
-
value.charCodeAt(index + 1) <= 0xdfff) {
|
|
53
|
-
bytes += 4;
|
|
54
|
-
index += 1;
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
bytes += 3;
|
|
58
|
-
}
|
|
59
|
-
if (bytes > limit)
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
41
|
/** Normalize model output into an async-arrow expression: strip markdown fences, wrap bare bodies. */
|
|
65
42
|
export function normalizeCode(code) {
|
|
66
43
|
let c = code.trim();
|
|
@@ -216,7 +193,7 @@ function installBridge(ctx, providers, logs, onLog) {
|
|
|
216
193
|
catch (err) {
|
|
217
194
|
throw new Error(`Host result from ${label} could not be serialized: ${msg(err)}`);
|
|
218
195
|
}
|
|
219
|
-
if (
|
|
196
|
+
if (utf8Length(json, MAX_HOST_RESULT_BYTES) > MAX_HOST_RESULT_BYTES) {
|
|
220
197
|
throw new Error(`Host result from ${label} exceeds the ${MAX_HOST_RESULT_BYTES}-byte serialized bridge limit.`);
|
|
221
198
|
}
|
|
222
199
|
return { json };
|
|
@@ -119,7 +119,7 @@ function terminate(child) {
|
|
|
119
119
|
}
|
|
120
120
|
let providePoolProgram;
|
|
121
121
|
class QuickJsChildPool {
|
|
122
|
-
/** What `/health` and `connecta doctor` call this sandbox
|
|
122
|
+
/** What `/health` and `connecta doctor` call this sandbox. */
|
|
123
123
|
name = "QuickJS";
|
|
124
124
|
admission;
|
|
125
125
|
slots;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every `connecta.<name>` function a guest program can reach, in one list.
|
|
3
|
+
*
|
|
4
|
+
* Guest-safe data only: no host implementation, no imports. The guest runtime
|
|
5
|
+
* builds its namespace from it, execute.ts admits each host call by its budget
|
|
6
|
+
* class and binds it by name to an implementation typed against this list, and
|
|
7
|
+
* the execute_code description's compact declaration is joined from it.
|
|
8
|
+
* `GuestApi` in guest-types.ts stays the published type; the package-surface
|
|
9
|
+
* test compiles the joined declaration against it.
|
|
10
|
+
*/
|
|
11
|
+
export const GUEST_FUNCTIONS = [
|
|
12
|
+
/** Searches the catalog and returns one flat page of `{ tools }`. */
|
|
13
|
+
{
|
|
14
|
+
name: "search",
|
|
15
|
+
budget: "hostCall",
|
|
16
|
+
declaration: "search(args?: { query?: string; connector?: string; limit?: number; offset?: number; includeSchemas?: Schemas }): Promise<{ tools: Tool[]; catalogErrors: { connector: string; code: string; message: string }[]; hasMore: boolean; nextOffset?: number }>;",
|
|
17
|
+
},
|
|
18
|
+
/** Describes exact addresses, one entry per address in order. */
|
|
19
|
+
{
|
|
20
|
+
name: "describe",
|
|
21
|
+
budget: "hostCall",
|
|
22
|
+
declaration: "describe(args?: { address?: string; addresses?: string[]; format?: Schemas }): Promise<{ tools: (Tool & { error?: string })[] }>;",
|
|
23
|
+
},
|
|
24
|
+
/** Calls one tool and inlines transfer pages and native content. */
|
|
25
|
+
{
|
|
26
|
+
name: "call",
|
|
27
|
+
budget: "hostCall",
|
|
28
|
+
wrapped: true,
|
|
29
|
+
declaration: "call(address: string, args?: unknown, options?: { timeoutMs?: number }): Promise<Result>;",
|
|
30
|
+
},
|
|
31
|
+
/** Reads one advertised resource URI. */
|
|
32
|
+
{
|
|
33
|
+
name: "read",
|
|
34
|
+
budget: "hostCall",
|
|
35
|
+
declaration: "read(uri: string): Promise<{ contents: { uri: string; text?: string; blob?: string }[] }>;",
|
|
36
|
+
},
|
|
37
|
+
/** Pages a retained result by page or UTF-8 byte offset. */
|
|
38
|
+
{
|
|
39
|
+
name: "result",
|
|
40
|
+
budget: "hostCall",
|
|
41
|
+
deadline: "result storage",
|
|
42
|
+
declaration: "result(id: string | Paged, options?: { page?: number; offset?: number; maxBytes?: number }): Promise<{ text: string; hasMore: boolean; nextOffset?: number }>;",
|
|
43
|
+
},
|
|
44
|
+
/** Fetches a skill's text by exact name. */
|
|
45
|
+
{
|
|
46
|
+
name: "skill",
|
|
47
|
+
budget: "hostCall",
|
|
48
|
+
deadline: "skill lookup",
|
|
49
|
+
alias: { name: "skills", note: "models write connecta.skills, the top-level tool's name." },
|
|
50
|
+
declaration: "skill(name: string): Promise<{ text: string }>;",
|
|
51
|
+
},
|
|
52
|
+
/** Forwards a content block to the client; needs no await. */
|
|
53
|
+
{
|
|
54
|
+
name: "emit",
|
|
55
|
+
budget: "emission",
|
|
56
|
+
declaration: "emit(block: Block | Native | Exclude<Result, Paged>): PromiseLike<void>;",
|
|
57
|
+
},
|
|
58
|
+
/** Tells the host a failed program read a result field off an unawaited call. */
|
|
59
|
+
{
|
|
60
|
+
name: "unawaited",
|
|
61
|
+
budget: "once",
|
|
62
|
+
label: "missing-await notice",
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
/** Public guest functions that spend no host call, for the agent-facing text. */
|
|
66
|
+
export const UNCOUNTED_GUEST_FUNCTIONS = GUEST_FUNCTIONS.filter((fn) => fn.budget === "emission")
|
|
67
|
+
.map((fn) => fn.name)
|
|
68
|
+
.join(" and ");
|
|
69
|
+
/**
|
|
70
|
+
* The guest API as the execute_code description shows it: compact shapes of
|
|
71
|
+
* GuestApi in src/guest-types.ts, checked against it by the package-surface
|
|
72
|
+
* declaration test. Arguments match what GuestApi accepts in both directions,
|
|
73
|
+
* results are supertypes of its results, and no field is invented. Type notation, not runnable JS.
|
|
74
|
+
*/
|
|
75
|
+
export const GUEST_API_DECLARATION = `declare const connecta: {
|
|
76
|
+
${GUEST_FUNCTIONS.flatMap((fn) => ("declaration" in fn ? [fn.declaration] : [])).join("\n")}
|
|
77
|
+
};
|
|
78
|
+
type Schemas = "json" | "compact" | "typescript";
|
|
79
|
+
type Tool = { address: string; description?: string; inputSchema?: unknown; outputSchema?: unknown; signature?: string };
|
|
80
|
+
type Paged = { format: "paged"; valueFormat: "json" | "text"; resultId?: string; totalBytes: number; truncated: true; hint: string };
|
|
81
|
+
type Result = ({ format: "json"; data: unknown } | { format: "text"; data: string } | Paged) & { content?: Native[] };
|
|
82
|
+
type Block = { type: "text"; text: string } | { type: "image" | "audio"; data: string; mimeType: string };
|
|
83
|
+
type Native = Block | { type: "resource"; resource: { uri: string } & ({ text: string } | { blob: string }) } | { type: "resource_link"; uri: string; name: string };`;
|