@zackbart/connecta 0.37.1 → 0.38.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.
Files changed (168) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +2 -2
  3. package/bin/connecta.mjs +1 -39
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.js +2 -4
  6. package/dist/agent-identity.js +1 -1
  7. package/dist/auth/clerk-sdk/generated.js +2 -1
  8. package/dist/auth/downstream-oauth.js +19 -153
  9. package/dist/auth/downstream-revocation.js +1 -1
  10. package/dist/auth/oauth-grant.js +11 -0
  11. package/dist/auth/oauth-token-response.js +1 -1
  12. package/dist/auth/vercel-agent.js +2 -1
  13. package/dist/auth-elicitation.js +2 -2
  14. package/dist/branding.d.ts +1 -15
  15. package/dist/branding.js +2 -2
  16. package/dist/bytes.js +42 -0
  17. package/dist/call-admission-context.js +2 -5
  18. package/dist/call-recovery.js +0 -3
  19. package/dist/catalog-fingerprint.js +2 -18
  20. package/dist/catalog-service.js +1 -1
  21. package/dist/config-defaults.d.ts +1 -0
  22. package/dist/config-defaults.js +1 -0
  23. package/dist/config-schema.d.ts +1 -1
  24. package/dist/config-schema.js +6 -2
  25. package/dist/config.d.ts +41 -14
  26. package/dist/config.js +21 -71
  27. package/dist/connectors/api-connector.js +4 -3
  28. package/dist/connectors/guarded-fetch.d.ts +0 -6
  29. package/dist/connectors/guarded-fetch.js +1 -11
  30. package/dist/connectors/remote-mcp.js +10 -43
  31. package/dist/connectors/resource-uri.js +0 -4
  32. package/dist/d1.d.ts +0 -89
  33. package/dist/d1.js +0 -34
  34. package/dist/downstream-input.js +2 -2
  35. package/dist/execute.js +22 -13
  36. package/dist/executor-result.js +2 -1
  37. package/dist/executors/quickjs-runtime.js +2 -25
  38. package/dist/executors/quickjs.js +1 -1
  39. package/dist/guest-runtime.js +5 -3
  40. package/dist/guest-types.d.ts +5 -5
  41. package/dist/http/client.js +1 -1
  42. package/dist/http/errors.js +2 -2
  43. package/dist/http/json-schema.js +1 -1
  44. package/dist/http/node-transport.js +0 -1
  45. package/dist/http/oauth.js +1 -1
  46. package/dist/http/paging.js +6 -9
  47. package/dist/http/provider.js +1 -1
  48. package/dist/http/safety.js +1 -1
  49. package/dist/http/services.js +1 -8
  50. package/dist/http/tools.js +1 -7
  51. package/dist/http/vendor.js +7 -7
  52. package/dist/identity.js +2 -6
  53. package/dist/invocation-auth.js +2 -1
  54. package/dist/meta-tools.js +27 -27
  55. package/dist/native-tools.js +2 -1
  56. package/dist/oauth-handoff.js +2 -2
  57. package/dist/operation-vocabulary.js +1 -1
  58. package/dist/operator-record.js +1 -49
  59. package/dist/operator-ui/contract.d.ts +2 -2
  60. package/dist/operator-ui/generated.js +3 -3
  61. package/dist/program-writes.js +3 -8
  62. package/dist/provider-call.js +0 -7
  63. package/dist/provider.d.ts +1 -1
  64. package/dist/providers/_shared/google/kit.js +1 -6
  65. package/dist/providers/_shared/google/service-account.js +1 -3
  66. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  67. package/dist/providers/_shared/guide.js +3 -0
  68. package/dist/providers/_shared/json.js +12 -0
  69. package/dist/providers/_shared/rest/compact-index.js +1 -1
  70. package/dist/providers/_shared/rest/kit.js +1 -3
  71. package/dist/providers/_shared/rest/operation-index.js +1 -3
  72. package/dist/providers/_shared/rest/value-safety.js +26 -28
  73. package/dist/providers/breeze/api.js +2 -7
  74. package/dist/providers/breeze/index.js +4 -2
  75. package/dist/providers/breeze/tools.js +3 -4
  76. package/dist/providers/ccb/api.js +1 -6
  77. package/dist/providers/ccb/index.js +2 -1
  78. package/dist/providers/ccb/tools.js +1 -6
  79. package/dist/providers/cloudflare/api.js +2 -2
  80. package/dist/providers/cloudflare/index.js +2 -1
  81. package/dist/providers/cloudflare/safety.js +3 -3
  82. package/dist/providers/docs/tools.js +2 -10
  83. package/dist/providers/drive/tools.js +12 -25
  84. package/dist/providers/forms/tools.js +1 -9
  85. package/dist/providers/gmail/tools.js +5 -21
  86. package/dist/providers/overflow/api.js +1 -3
  87. package/dist/providers/overflow/index.js +2 -1
  88. package/dist/providers/overflow/tools.js +3 -7
  89. package/dist/providers/planning-center/api.js +2 -1
  90. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  91. package/dist/providers/planning-center/lib/json-api.js +1 -9
  92. package/dist/providers/planning-center/tools.js +2 -1
  93. package/dist/providers/sheets/tools.js +1 -9
  94. package/dist/providers/slides/tools.js +4 -13
  95. package/dist/providers/stripe/index.js +2 -1
  96. package/dist/providers/stripe/safety.js +2 -2
  97. package/dist/providers/tithely/api.js +2 -7
  98. package/dist/providers/tithely/index.js +3 -2
  99. package/dist/providers/tithely/tools.js +7 -5
  100. package/dist/providers/vercel/api.js +1 -6
  101. package/dist/providers/vercel/lib/pin.js +1 -1
  102. package/dist/providers/vercel/safety.js +1 -1
  103. package/dist/providers/vercel/tools.js +2 -1
  104. package/dist/redaction-marker.js +2 -2
  105. package/dist/registry.d.ts +1 -1
  106. package/dist/registry.js +5 -7
  107. package/dist/request-principal.js +1 -1
  108. package/dist/request-state.js +2 -2
  109. package/dist/routes/access-tokens.js +1 -1
  110. package/dist/routes/activity-disclosure.js +1 -1
  111. package/dist/routes/connect.js +2 -2
  112. package/dist/routes/credentials.js +1 -1
  113. package/dist/routes/mcp.js +4 -3
  114. package/dist/routes/oauth-management.js +1 -1
  115. package/dist/routes/operator.js +4 -3
  116. package/dist/routes/shared.d.ts +8 -8
  117. package/dist/routes/shared.js +9 -9
  118. package/dist/routes/ui-config.js +1 -1
  119. package/dist/routes/ui.js +1 -1
  120. package/dist/runtime/call-admission.js +1 -1
  121. package/dist/runtime/call.js +1 -1
  122. package/dist/runtime/settlement.js +1 -1
  123. package/dist/runtime/shared-read.js +2 -15
  124. package/dist/sent-secrets.d.ts +1 -2
  125. package/dist/sent-secrets.js +3 -8
  126. package/dist/server.js +4 -4
  127. package/dist/skills.js +8 -35
  128. package/dist/sqlite.d.ts +0 -19
  129. package/dist/sqlite.js +2 -83
  130. package/dist/storage/keys.js +16 -125
  131. package/dist/storage/sql-native.js +0 -101
  132. package/dist/storage/sql.d.ts +1 -24
  133. package/dist/storage/sql.js +1 -12
  134. package/dist/tool-error.js +4 -0
  135. package/dist/url-safety.js +0 -4
  136. package/dist/usage-guide.js +2 -2
  137. package/dist/version.d.ts +1 -1
  138. package/dist/version.js +1 -1
  139. package/dist/worker.js +1 -1
  140. package/documentation/README.md +6 -42
  141. package/documentation/architecture.md +86 -496
  142. package/documentation/auth.md +12 -25
  143. package/documentation/code-mode.md +90 -66
  144. package/documentation/deploying.md +19 -40
  145. package/documentation/integrating.md +4 -19
  146. package/documentation/meta-tools.md +6 -6
  147. package/documentation/migration-0.33.md +5 -5
  148. package/documentation/migration-0.34.md +2 -4
  149. package/documentation/migration-retired-layouts.md +74 -0
  150. package/documentation/operator-tests.md +19 -119
  151. package/documentation/operator-ui.md +3 -4
  152. package/documentation/provider-auth-migration.md +4 -10
  153. package/documentation/provider-migration-0.29.md +4 -15
  154. package/examples/worker/AGENTS.md +4 -6
  155. package/examples/worker/README.md +20 -115
  156. package/guides/connecting-agents.md +7 -30
  157. package/guides/how-it-works.md +2 -2
  158. package/guides/safety-and-trust.md +4 -1
  159. package/package.json +19 -19
  160. package/templates/node/README.md +9 -26
  161. package/templates/node/package.json +1 -1
  162. package/dist/auth/oauth-v2-migration.js +0 -135
  163. package/dist/provider-call-facts.d.ts +0 -115
  164. package/dist/provider-call.d.ts +0 -112
  165. package/dist/storage/d1-copy.js +0 -255
  166. package/dist/storage/sql-schema.d.ts +0 -6
  167. package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
  168. package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
package/dist/d1.js CHANGED
@@ -3,12 +3,8 @@
3
3
  // One D1 database holds everything: `d1Storage(db)` is the deployment's
4
4
  // `KVStorage`, and `d1ActivityStore(db)` its activity history. Both create
5
5
  // their tables on first use, so binding the database is the whole setup.
6
- // `copyKvToD1` moves a 0.28 deployment's Workers KV state into it once.
7
- import { runEdge } from "./runtime/run.js";
8
6
  import { d1Driver } from "./storage/d1-driver.js";
9
- import { copyKvToD1Effect, markKvToD1LiveEffect } from "./storage/d1-copy.js";
10
7
  import { sqlActivityStore, sqlStorage } from "./storage/sql.js";
11
- import { keepErrorName } from "./redaction-identity.js";
12
8
  /**
13
9
  * `KVStorage` over a D1 database, with the atomic `compareAndSet` every
14
10
  * subsystem relies on. The `connecta_kv` table is created on first use; a
@@ -25,33 +21,3 @@ export function d1Storage(db) {
25
21
  export function d1ActivityStore(db, options) {
26
22
  return sqlActivityStore(d1Driver(db), "d1", options);
27
23
  }
28
- /**
29
- * A copy that stopped. The message is fixed wording that names a family at
30
- * most, never a key or value, and the binding's own error is not attached:
31
- * KV and D1 errors can quote either. Entries before `cursor` are copied;
32
- * rerunning from it is safe while the maintenance window still holds.
33
- */
34
- export class KvToD1CopyError extends Error {
35
- cursor;
36
- name = "KvToD1CopyError";
37
- constructor(message,
38
- /** Resume point; absent means start over, which is also safe. */
39
- cursor) {
40
- super(message);
41
- this.cursor = cursor;
42
- }
43
- }
44
- keepErrorName(KvToD1CopyError, "KvToD1CopyError");
45
- /** Mark cutover before reopening traffic. The marker stays in D1 permanently. */
46
- export function markKvToD1Live(db, source) {
47
- return runEdge(markKvToD1LiveEffect(db, source));
48
- }
49
- /**
50
- * Copy live KV entries verbatim while all writers and traffic are stopped.
51
- * A live D1 row is kept unless its family is explicitly allowed to overwrite.
52
- * `verify` reads hashes and absolute expiries without changing copied entries.
53
- * Results contain counts only. Resume tokens are source-bound atomic claims.
54
- */
55
- export function copyKvToD1(kv, db, options) {
56
- return runEdge(copyKvToD1Effect(kv, db, options));
57
- }
@@ -1,3 +1,4 @@
1
+ import { toolError } from "./tool-error.js";
1
2
  import { RequestScope, requestOwner } from "./runtime/request-scope.js";
2
3
  import { Effect } from "effect";
3
4
  import { upwardStep } from "./upward.js";
@@ -129,8 +130,7 @@ function failure(code, message, write = false) {
129
130
  writeOutcome: "unknown",
130
131
  }
131
132
  : { code, message, retryable: false };
132
- const structuredContent = { ok: false, error };
133
- return { content: [{ type: "text", text: JSON.stringify(structuredContent) }], structuredContent, isError: true };
133
+ return toolError(error, { ok: false });
134
134
  }
135
135
  /** MCP-only continuation coordination. No downstream prompt enters operator records. */
136
136
  export class DownstreamElicitation {
package/dist/execute.js CHANGED
@@ -175,13 +175,15 @@ export class EmitCollector {
175
175
  maxBytes;
176
176
  maxBlocks;
177
177
  diagnostics;
178
+ maxResultChars;
178
179
  blocks = [];
179
180
  bytes = 0;
180
181
  textChars = 0;
181
- constructor(maxBytes, maxBlocks, diagnostics) {
182
+ constructor(maxBytes, maxBlocks, diagnostics, maxResultChars = MAX_EXECUTE_RESULT_CHARS) {
182
183
  this.maxBytes = maxBytes;
183
184
  this.maxBlocks = maxBlocks;
184
185
  this.diagnostics = diagnostics;
186
+ this.maxResultChars = maxResultChars;
185
187
  }
186
188
  accept(raw) {
187
189
  this.acceptNative([requireEmittedBlock(raw)]);
@@ -205,8 +207,8 @@ export class EmitCollector {
205
207
  // the transport budget, just like images and audio.
206
208
  if (block.type === "resource" && "text" in block.resource)
207
209
  textChars += serialized.length;
208
- if (textChars > MAX_EXECUTE_RESULT_CHARS - 512) {
209
- throw guestFailure("result_too_large", "Emitted text shares the 24,000-character program result limit. Reduce the text before emitting it.");
210
+ if (textChars > this.maxResultChars - 512) {
211
+ 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
212
  }
211
213
  count++;
212
214
  bytes += size;
@@ -750,8 +752,10 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
750
752
  }),
751
753
  skill: (name, _options, utilitySignal) => Effect.tryPromise({
752
754
  try: async () => {
753
- if (typeof name !== "string")
754
- throw guestFailure("invalid_args", "Use connecta.skill(name) with an exact skill name.");
755
+ // Accept the top-level skills tool's { name } shape too: models copy it into programs.
756
+ const skillName = name && typeof name === "object" ? name.name : name;
757
+ if (typeof skillName !== "string")
758
+ throw guestFailure("invalid_args", 'Use connecta.skill("usage") or connecta.skill("connector:<id>") with an exact skill name; the skills tool lists names.');
755
759
  const skills = new SkillsRegistry(registry, baseUrl, {
756
760
  requestScope,
757
761
  requestSignal: utilitySignal ?? hostAccessSignal,
@@ -763,7 +767,7 @@ function sandboxProvider(registry, baseUrl, activity, limits, requestScope = {})
763
767
  probeTimeoutMs: limits.probeTimeoutMs,
764
768
  defer: limits.defer,
765
769
  });
766
- return { name, format: "text", text: await skills.text(name) };
770
+ return { name: skillName, format: "text", text: await skills.text(skillName) };
767
771
  },
768
772
  catch: (err) => err instanceof ConnectorCallError ? guestFailure(err.code, err.message, err.code === "unavailable") : err,
769
773
  }),
@@ -1044,10 +1048,11 @@ export function createExecuteTool(registry, baseUrl, executor, logger, activity,
1044
1048
  logger,
1045
1049
  };
1046
1050
  const hostCallTimeoutMs = resolveBudget(config.hostCallTimeoutMs, EXECUTE_HOST_CALL_TIMEOUT_MS);
1051
+ const maxResultChars = resolveBudget(config.maxResultChars, MAX_EXECUTE_RESULT_CHARS);
1047
1052
  const play = (program, diagnosticsRequested, callerSignal, requestScope, hostCalls, programWrites) => Effect.suspend(() => {
1048
1053
  const sentSecrets = sentSecretsForRequest(requestScope);
1049
1054
  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);
1055
+ const emitted = new EmitCollector(resolveBudget(config.maxEmittedBytes, EXECUTE_MAX_EMITTED_BYTES), resolveBudget(config.maxEmittedBlocks, EXECUTE_MAX_EMITTED_BLOCKS), diagnostics, maxResultChars);
1051
1056
  const resultDependencies = [];
1052
1057
  const dispatchController = new AbortController();
1053
1058
  const terminal = Deferred.makeUnsafe();
@@ -1137,15 +1142,17 @@ export function createExecuteTool(registry, baseUrl, executor, logger, activity,
1137
1142
  emitted,
1138
1143
  diagnostics,
1139
1144
  program,
1140
- guardResult: (value, maxChars) => authCall(guardProgramReturn, [
1145
+ // Emitted text shares the return cap; a follow-up page run gets all of it.
1146
+ guardResult: (value) => authCall(guardProgramReturn, [
1141
1147
  value,
1142
1148
  registry,
1143
1149
  baseUrl,
1144
1150
  [...new Map(resultDependencies.map((dependency) => [JSON.stringify(dependency), dependency])).values()],
1145
1151
  sentSecrets,
1146
1152
  logger,
1147
- maxChars,
1153
+ maxResultChars - emitted.textChars,
1148
1154
  programWrites.hasSucceeded,
1155
+ maxResultChars,
1149
1156
  ]),
1150
1157
  };
1151
1158
  const exited = Effect.exit(Effect.scoped(run)).pipe(Effect.flatMap((exit) =>
@@ -1301,7 +1308,7 @@ function finishedRun(outcome, { emitted, diagnostics, program, guardResult }) {
1301
1308
  // error path so captured logs survive instead of a raw SDK 500.
1302
1309
  let result;
1303
1310
  try {
1304
- result = yield* guardResult(outcome.result, MAX_EXECUTE_RESULT_CHARS - emitted.textChars);
1311
+ result = yield* guardResult(outcome.result);
1305
1312
  }
1306
1313
  catch (err) {
1307
1314
  const message = `Error: result is not JSON-serializable: ${msg(err)}`;
@@ -1381,7 +1388,7 @@ function connectorInventory(connectors) {
1381
1388
  return `${prefix}${shown.join(", ")}.`;
1382
1389
  return `${prefix}${shown.join(", ")}${shown.length > 0 ? "; " : ""}+${omitted} more.`;
1383
1390
  }
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 ? "One known read: call_tool. Everything else: execute_code. Read-only endpoint: search and describe omit write tools, and every write call is refused." : "One known read: call_tool. One known write: call_destructive_tool. Everything else: execute_code. Writable endpoint: programs may read and write; the client approves."} Limits: ${hostLimits.maxHostCalls} host calls, ${readOnly ? "" : `${maxWrites} writes, `}${hostLimits.hostCallTimeoutMs / 1_000}s/host call.
1391
+ 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 ? "One known read: call_tool. Everything else: execute_code. Read-only endpoint: search and describe omit write tools, and every write call is refused." : "One known read: call_tool. One known write: call_destructive_tool. Everything else: execute_code. 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
1392
  ${connectorInventory(connectors)}
1386
1393
 
1387
1394
  Use async () => { ... }, no arguments. No portable ambient capabilities. API:
@@ -1392,7 +1399,7 @@ ${GUEST_API_DECLARATION}
1392
1399
  \`\`\`
1393
1400
  Search: {tools}, catalogErrors. JSON schemas default; compact text. Calls: {data, format:"json"|"text"}; >1 MiB: paged handle. Oversized returns: resultId/nextAction. connecta.result pages; UTF-8 byte offsets. allSettled keeps typed errors; host budget_exceeded ends the run.
1394
1401
 
1395
- Never repeat writes for output. emit(result) or emit(block) from result.content forwards MCP blocks. Authored: text/base64 image/audio; no await. Caps: ${emitBudgets.maxBlocks} blocks/${emitBudgets.maxBytes} bytes. Text shares result cap. skills({ name: "investigate" }): workflow; skills({ name: "usage" }): repair${connectorGuides ? ", guide handling" : ""}.`;
1402
+ Never repeat writes for output. emit(result) or emit(block) from result.content forwards MCP blocks. Authored: text/base64 image/audio; no await. Caps: ${emitBudgets.maxBlocks} blocks/${emitBudgets.maxBytes} bytes. Text shares result cap. connecta.skill("usage"): repair${connectorGuides ? ", guide handling" : ""}; connecta.skill("investigate"): workflow.`;
1396
1403
  // Module scope, like the other five meta-tool inputs: its JSON Schema is
1397
1404
  // derived once per process. Budgets and connectors vary by deployment and
1398
1405
  // view, so they live in the description, never here.
@@ -1430,12 +1437,14 @@ export function registerExecuteTool(server, registry, ctx) {
1430
1437
  defer: ctx.defer,
1431
1438
  readOnly: ctx.readOnly,
1432
1439
  maxWrites,
1440
+ maxResultChars: ctx.maxResultChars,
1433
1441
  requestScope: ctx.requestScope,
1434
1442
  });
1435
1443
  server.registerTool("execute_code", {
1436
1444
  description: executeDescription(emitBudgets, {
1437
1445
  ...hostLimits,
1438
- hostCallTimeoutMs: normalizeTimeoutMs(ctx.defaultToolTimeoutMs) ?? hostLimits.hostCallTimeoutMs,
1446
+ // connecta.call falls back to calls.defaultTimeoutMs; result, skill and read always use hostCallTimeoutMs.
1447
+ callTimeoutMs: normalizeTimeoutMs(ctx.defaultToolTimeoutMs) ?? hostLimits.hostCallTimeoutMs,
1439
1448
  }, hasConnectorGuides(connectors), connectors, maxWrites, ctx.readOnly === true, ctx.native === true),
1440
1449
  inputSchema: EXECUTE_INPUT,
1441
1450
  outputSchema: EXECUTE_OUTPUT,
@@ -1,6 +1,7 @@
1
+ import { CONFIG_DEFAULTS } from "./config-defaults.js";
1
2
  import { msg } from "./errors.js";
2
3
  /** ~6k tokens. Sandbox code should filter data down before returning. */
3
- export const MAX_EXECUTE_RESULT_CHARS = 24_000;
4
+ export const MAX_EXECUTE_RESULT_CHARS = CONFIG_DEFAULTS.execute.maxResultChars;
4
5
  export const MAX_EXECUTE_LOG_CHARS = 4_000;
5
6
  export function serializeResultText(value) {
6
7
  const serialized = JSON.stringify(value);
@@ -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 (exceedsUtf8ByteLimit(json, MAX_HOST_RESULT_BYTES)) {
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 (#368). */
122
+ /** What `/health` and `connecta doctor` call this sandbox. */
123
123
  name = "QuickJS";
124
124
  admission;
125
125
  slots;
@@ -160,6 +160,8 @@ export function guestInitializer() {
160
160
  read: provider.read,
161
161
  result: provider.result,
162
162
  skill: provider.skill,
163
+ // Undeclared alias: models write connecta.skills, the top-level tool's name.
164
+ skills: provider.skill,
163
165
  emit(block) {
164
166
  const ref = block && typeof block === "object" ? weakGet(nativeRefs, block) : undefined;
165
167
  return trackEmission(emit(ref || block));
@@ -224,10 +226,10 @@ export function programError(raw, code) {
224
226
  /(?:const|let|var|function|\()\s*connecta\b/.test(code)) {
225
227
  hint = "Do not shadow connecta. Use the host-provided connecta global.";
226
228
  }
227
- else if (/callTool|\bskills\b|connecta\.(?:guide|skills)/.test(message) ||
228
- (["TypeError", "ReferenceError"].includes(name) && /connecta\.(?:guide|skills)\b/.test(code))) {
229
+ else if (/callTool|\bskills\b|connecta\.guide/.test(message) ||
230
+ (["TypeError", "ReferenceError"].includes(name) && /connecta\.guide\b/.test(code))) {
229
231
  hint =
230
- "Use connecta.call(address, args), connecta.search, connecta.describe, connecta.result, and connecta.skill(name). Services are addressed by connector.tool, never guest globals.";
232
+ 'Use connecta.call(address, args), connecta.search, connecta.describe, connecta.result, and connecta.skill("usage"). Services are addressed by connector.tool, never guest globals.';
231
233
  }
232
234
  return {
233
235
  code: "program_error",
@@ -103,7 +103,7 @@ export type GuestBlock = {
103
103
  mimeType: string;
104
104
  };
105
105
  type SchemaFormat = "json" | "compact" | "typescript";
106
- export interface CatalogSearchArgs {
106
+ interface CatalogSearchArgs {
107
107
  query?: string;
108
108
  connector?: string;
109
109
  safety?: "readOnly" | "approvalRequired" | "all";
@@ -113,7 +113,7 @@ export interface CatalogSearchArgs {
113
113
  includeSchemas?: SchemaFormat;
114
114
  includeSchemaKeys?: boolean;
115
115
  }
116
- export interface CatalogDescribeArgs {
116
+ interface CatalogDescribeArgs {
117
117
  address?: unknown;
118
118
  addresses?: unknown;
119
119
  format?: SchemaFormat;
@@ -140,7 +140,7 @@ interface CatalogSearchFailure extends CatalogFailureDetail {
140
140
  }>;
141
141
  retry?: string;
142
142
  }
143
- export interface CatalogSearchPage {
143
+ interface CatalogSearchPage {
144
144
  catalogErrors: CatalogSearchFailure[];
145
145
  absence?: {
146
146
  service: string;
@@ -170,14 +170,14 @@ export interface CatalogSearchPage {
170
170
  guidance?: string;
171
171
  };
172
172
  }
173
- export type CatalogSearchResult = CatalogSearchPage & {
173
+ type CatalogSearchResult = CatalogSearchPage & {
174
174
  tools: Array<CatalogSearchTool & {
175
175
  connectorTitle?: string;
176
176
  guide?: string;
177
177
  guideSummary?: string;
178
178
  }>;
179
179
  };
180
- export interface CatalogDescription {
180
+ interface CatalogDescription {
181
181
  address: string;
182
182
  schemaFormat?: "json" | "text";
183
183
  name?: string;
@@ -1,6 +1,6 @@
1
1
  import { confinementBase, REDIRECT_STATUSES, BODILESS_STATUSES } from "../http-policy.js";
2
2
  import { guardedTransport, sendGuarded } from "../guarded-transport.js";
3
- // Guarded HTTP transport (decision 0010, B.2). Unreachable from src/index.ts.
3
+ // Guarded HTTP transport. Unreachable from src/index.ts.
4
4
  //
5
5
  // The guarded transport as an Effect `HttpClient`, with the behaviour of
6
6
  // src/connectors/guarded-fetch.ts: requests confined beneath one base URL,
@@ -1,4 +1,4 @@
1
- // Internal HTTP failure contract (decision 0010, B.3). Unreachable from src/index.ts.
1
+ // Internal HTTP failure contract. Unreachable from src/index.ts.
2
2
  //
3
3
  // Every way a kit request can fail, as a tagged error, and the one mapping
4
4
  // from those to the wire `ConnectorCallError`. A provider supplies meaning
@@ -26,7 +26,7 @@
26
26
  // refusing status counts only for the request that received it. Advice to
27
27
  // send a call again is a segment of its own (`resendAdvice`), never part of
28
28
  // the message, so output composes it only where the outcome allows.
29
- // Q8 policy is decided once by the core's `settleWrite` (#917), from the
29
+ // Q8 policy is decided once by the core's `settleWrite`, from the
30
30
  // ledger entries this kit forwards its matched proof and renewals to. A
31
31
  // write answered 401 stays unknown; only renewal removes its handoff, and
32
32
  // only a reviewed authentication-before-execution declaration proves refusal.
@@ -1,4 +1,4 @@
1
- // Provider kit (decision 0010, B.4). Unreachable from src/index.ts.
1
+ // Provider kit. Unreachable from src/index.ts.
2
2
  //
3
3
  // One Schema is a tool's type, its argument decoder, and its catalog JSON
4
4
  // Schema. Effect's generator is correct but spells some things differently
@@ -238,4 +238,3 @@ export function createNodeGuardedTransport(proxy) {
238
238
  },
239
239
  };
240
240
  }
241
- export const nodeGuardedTransport = createNodeGuardedTransport();
@@ -1,4 +1,4 @@
1
- // Refresh-once middleware (decision 0010, B.2). Unreachable from src/index.ts.
1
+ // Refresh-once middleware. Unreachable from src/index.ts.
2
2
  //
3
3
  // Refresh-once for a connection that authenticates with a token it can
4
4
  // renew: a downstream OAuth grant, a service-account assertion, a machine
@@ -1,4 +1,5 @@
1
- // Provider kit (decision 0010, B.4). Unreachable from src/index.ts.
1
+ import { bytesToHex, decodeBase64url } from "../bytes.js";
2
+ // Provider kit. Unreachable from src/index.ts.
2
3
  //
3
4
  // List tools take one page at a time. Lookahead detects an extra row; bound
4
5
  // cursors authenticate the tool, arguments, connection and credential owner.
@@ -52,7 +53,7 @@ async function signingKey(storage) {
52
53
  return cached;
53
54
  let raw = await storage.get(continuationKeys.signingKey);
54
55
  if (raw === null) {
55
- const candidate = Array.from(crypto.getRandomValues(new Uint8Array(32)), (byte) => byte.toString(16).padStart(2, "0")).join("");
56
+ const candidate = bytesToHex(crypto.getRandomValues(new Uint8Array(32)));
56
57
  raw = (await storage.compareAndSet(continuationKeys.signingKey, null, candidate))
57
58
  ? candidate
58
59
  : await storage.get(continuationKeys.signingKey);
@@ -92,7 +93,7 @@ export const boundCursor = {
92
93
  const expected = yield* digest(tool, args);
93
94
  let parsed;
94
95
  try {
95
- parsed = JSON.parse(new TextDecoder().decode(fromBase64url(token)));
96
+ parsed = JSON.parse(new TextDecoder().decode(decodeBase64url(token)));
96
97
  }
97
98
  catch {
98
99
  parsed = undefined;
@@ -108,7 +109,7 @@ export const boundCursor = {
108
109
  if (token !== base64url(JSON.stringify({ v: record.v, d: record.d, c: record.c, m: record.m })))
109
110
  return yield* Effect.fail(new Refusal({ error: new ConnectorCallError("invalid_args", STRAY_CURSOR) }));
110
111
  const key = yield* keyForCall;
111
- const mac = fromBase64url(record.m);
112
+ const mac = decodeBase64url(record.m);
112
113
  const binding = yield* ContinuationBinding;
113
114
  const claims = new TextEncoder().encode(canonicalJson([record.v, binding.connection, binding.principal, record.d, record.c]));
114
115
  const valid = yield* Effect.promise(() => crypto.subtle.verify("HMAC", key, mac, claims));
@@ -125,7 +126,7 @@ function digest(tool, args) {
125
126
  .sort()
126
127
  .map((key) => [key, args[key]]);
127
128
  const bytes = new TextEncoder().encode(canonicalJson([tool, shape]));
128
- return Effect.promise(() => crypto.subtle.digest("SHA-256", bytes)).pipe(Effect.map((hash) => Array.from(new Uint8Array(hash).slice(0, 16), (byte) => byte.toString(16).padStart(2, "0")).join("")));
129
+ return Effect.promise(() => crypto.subtle.digest("SHA-256", bytes)).pipe(Effect.map((hash) => bytesToHex(new Uint8Array(hash).slice(0, 16))));
129
130
  }
130
131
  function base64url(text) {
131
132
  return base64urlBytes(new TextEncoder().encode(text));
@@ -136,7 +137,3 @@ function base64urlBytes(bytes) {
136
137
  binary += String.fromCharCode(byte);
137
138
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
138
139
  }
139
- function fromBase64url(token) {
140
- const binary = atob(token.replace(/-/g, "+").replace(/_/g, "/"));
141
- return Uint8Array.from(binary, (char) => char.charCodeAt(0));
142
- }
@@ -1,4 +1,4 @@
1
- // Internal provider kit (decision 0010, B.4). Unreachable from src/index.ts.
1
+ // Internal provider kit. Unreachable from src/index.ts.
2
2
  import { Context, Effect, Option } from "effect";
3
3
  import { api } from "../connectors/api.js";
4
4
  import { OAUTH_CHALLENGE_MAX_BYTES } from "../auth/renewable-grant.js";
@@ -1,4 +1,4 @@
1
- // Provider kit (decision 0010, B.4). Unreachable from src/index.ts.
1
+ // Provider kit. Unreachable from src/index.ts.
2
2
  //
3
3
  // The one value-safety boundary of a kit connection. `connect` requires it,
4
4
  // and `Send` hands a provider nothing it has not passed: a named tool, a
@@ -3,7 +3,7 @@ import { Context, Effect } from "effect";
3
3
  import { dispatchProviderRequest, providerRequest } from "../provider-call.js";
4
4
  import { callClassification } from "../call-classification.js";
5
5
  import { fromSignal } from "../runtime/run.js";
6
- import { callFromContext, ContinuationBinding, ContinuationStorage, openCall as openCoreCall, serviceFor, Signal, } from "../runtime/call.js";
6
+ import { callFromContext, ContinuationBinding, ContinuationStorage, serviceFor, Signal } from "../runtime/call.js";
7
7
  import { RequestRefused, ResendRefused } from "./errors.js";
8
8
  export { writeRequestKey } from "./write-request.js";
9
9
  export { Caller, Credentials, Secrets, RequestSecrets, Budget, Recovery, Signal, Classification, Ledger, } from "../runtime/call.js";
@@ -70,10 +70,3 @@ export function fromConnectorContext(ctx, fallback) {
70
70
  services: lease.services.pipe(Context.add(ContinuationStorage, serviceFor(ctx, ContinuationStorage) ?? ctx.storage), Context.add(ContinuationBinding, serviceFor(ctx, ContinuationBinding) ?? { connection: "", principal: null }), Context.add(Dispatches, []), Context.add(WriteCapacity, new Map([["write", new Map([[undefined, undefined]])]]))),
71
71
  };
72
72
  }
73
- export function openCall(inputs) {
74
- const lease = openCoreCall(inputs);
75
- return {
76
- ...lease,
77
- services: lease.services.pipe(Context.add(Dispatches, []), Context.add(WriteCapacity, new Map([["write", new Map([[undefined, undefined]])]]))),
78
- };
79
- }
@@ -1,4 +1,4 @@
1
- // Provider kit (decision 0010, B.4). Unreachable from src/index.ts.
1
+ // Provider kit. Unreachable from src/index.ts.
2
2
  //
3
3
  // Tool builders. A tool is a name, a description, an input Schema, an output
4
4
  // Schema, and a program; the builder renders both Schemas as catalog JSON
@@ -24,7 +24,6 @@ import { fieldsInput, fieldsProblem, selectFields } from "./fields.js";
24
24
  import { catalogSchema, Integer } from "./json-schema.js";
25
25
  import { boundCursor, Cursor, StandardPage } from "./paging.js";
26
26
  import { Classification, Dispatches, IdempotencyKey, Recovery, ReadTool, WriteCapacity, writeRequestKey, fromConnectorContext, } from "./services.js";
27
- const inputs = new WeakMap();
28
27
  const keyed = new WeakSet();
29
28
  /** Whether a kit write builder declared an idempotency key for this tool. */
30
29
  export function takesIdempotencyKey(tool) {
@@ -62,10 +61,6 @@ function originalOrder(decoded, original) {
62
61
  };
63
62
  return copy(decoded, original);
64
63
  }
65
- /** The input Schema a kit tool decodes with, for the catalog/decoder agreement check. */
66
- export function kitInput(tool) {
67
- return inputs.get(tool);
68
- }
69
64
  /** A Struct's own property names, for `fields`. */
70
65
  function propertyNames(schema, what) {
71
66
  const fields = schema.fields;
@@ -350,7 +345,6 @@ rendered) {
350
345
  })));
351
346
  });
352
347
  registerToolEntry(tool, (args, ctx) => program(args, ctx, inLogicalCall(ctx)));
353
- inputs.set(tool, spec.input);
354
348
  if (idempotency)
355
349
  keyed.add(tool);
356
350
  return tool;
@@ -1,4 +1,4 @@
1
- // Provider kit (decision 0010, B.4). Unreachable from src/index.ts.
1
+ // Provider kit. Unreachable from src/index.ts.
2
2
  //
3
3
  // A vendor is an `HttpApi`: endpoints with Schemas for path params, query,
4
4
  // and payload, from which requests are encoded and the endpoint manifest is
@@ -13,13 +13,13 @@
13
13
  // empty 200. Every answer, from a declared operation or a hatch's path, takes
14
14
  // one route: bounded read, JSON, closed public classifier facts captured,
15
15
  // the request's sent secrets redacted from
16
- // every value of the whole body before anything can cut or extract from it
17
- // (#911), its keys kept for value safety to match by name, status (with
16
+ // every value of the whole body before anything can cut or extract from it,
17
+ // its keys kept for value safety to match by name, status (with
18
18
  // its operation, so value safety decides what a failure may quote), the
19
19
  // vendor's `result` hook, then the connection's ResultSafety. A provider
20
20
  // cannot reach a body earlier. A Content-Type redaction altered selects the
21
21
  // format the vendor reads as JSON, so no altered type routes a body around
22
- // ResultSafety as text or bytes (#703).
22
+ // ResultSafety as text or bytes.
23
23
  import { Context, Effect } from "effect";
24
24
  import * as HttpClientRequest from "effect/http/HttpClientRequest";
25
25
  import * as HttpApi from "effect/http-api/HttpApi";
@@ -233,12 +233,12 @@ export function connect(vendor, guarded, options) {
233
233
  : defect)));
234
234
  });
235
235
  // Header-derived format decisions see only redacted Content-Type. Names are protocol
236
- // constants `Headers` must accept, so only the values are redacted, together (#703).
236
+ // constants `Headers` must accept, so only the values are redacted, together.
237
237
  const entries = [...reply.headers];
238
238
  const values = secrets.redact(entries.map(([, value]) => value));
239
239
  const headers = new Headers(entries.map(([name], index) => [name, values[index]]));
240
240
  // Whole, before a detail line trims it or a projection extracts from it. Values only:
241
- // result safety and projections match fields by their vendor names (#703).
241
+ // result safety and projections match fields by their vendor names.
242
242
  let classifierFacts;
243
243
  const parsed = reply.bytes !== undefined
244
244
  ? {
@@ -400,7 +400,7 @@ export function connect(vendor, guarded, options) {
400
400
  /**
401
401
  * The Content-Type a format decision reads. One redaction altered reads as
402
402
  * `application/json`: a vendor's text or binary choice for it would hand
403
- * result safety a body it cannot read field by field (#703).
403
+ * result safety a body it cannot read field by field.
404
404
  */
405
405
  function formatType(redacted) {
406
406
  return redactionAltered(redacted) ? "application/json" : redacted.toLowerCase();
package/dist/identity.js CHANGED
@@ -1,5 +1,5 @@
1
+ import { sha256Hex } from "./bytes.js";
1
2
  const IDENTITY_PART_RE = /^[\x21-\x7e]{1,256}$/;
2
- const encoder = new TextEncoder();
3
3
  export function validIdentityReference(value) {
4
4
  return Boolean(value &&
5
5
  typeof value.namespace === "string" &&
@@ -7,11 +7,7 @@ export function validIdentityReference(value) {
7
7
  IDENTITY_PART_RE.test(value.namespace) &&
8
8
  IDENTITY_PART_RE.test(value.id));
9
9
  }
10
- function bytesToHex(bytes) {
11
- return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
12
- }
13
10
  /** Deterministic pseudonymous partition; raw emails and ids do not enter keys. */
14
11
  export async function identityStorageKey(identity) {
15
- const digest = await crypto.subtle.digest("SHA-256", encoder.encode(`${identity.namespace}\n${identity.id}`));
16
- return bytesToHex(new Uint8Array(digest));
12
+ return sha256Hex(`${identity.namespace}\n${identity.id}`);
17
13
  }
@@ -1,3 +1,4 @@
1
+ import { bytesToHex } from "./bytes.js";
1
2
  import { authCall, authFlow, registerAuth } from "./auth/native.js";
2
3
  import { upwardStep } from "./upward.js";
3
4
  import { runEdge } from "./runtime/run.js";
@@ -34,7 +35,7 @@ export async function classificationDigest(definition) {
34
35
  annotations,
35
36
  schema,
36
37
  }));
37
- return Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)), (byte) => byte.toString(16).padStart(2, "0")).join("");
38
+ return bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)));
38
39
  }
39
40
  catch {
40
41
  // A digest refusal blocks recovery, not an ordinary call.