@zackbart/connecta 0.15.1 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/AGENTS.md +40 -20
  2. package/CHANGELOG.md +653 -0
  3. package/README.md +25 -5
  4. package/bin/connecta.mjs +44 -3
  5. package/dist/access-tokens.d.ts +0 -1
  6. package/dist/access-tokens.js +0 -1
  7. package/dist/activity.d.ts +50 -2
  8. package/dist/activity.js +34 -1
  9. package/dist/apps-shell.d.ts +0 -1
  10. package/dist/apps-shell.js +0 -1
  11. package/dist/auth/bearer.d.ts +0 -1
  12. package/dist/auth/bearer.js +0 -1
  13. package/dist/auth/clerk.d.ts +0 -1
  14. package/dist/auth/clerk.js +45 -9
  15. package/dist/auth/downstream-oauth.d.ts +0 -1
  16. package/dist/auth/downstream-oauth.js +0 -1
  17. package/dist/call-admission.d.ts +0 -1
  18. package/dist/call-admission.js +0 -1
  19. package/dist/catalog-drift.d.ts +99 -0
  20. package/dist/catalog-drift.js +251 -0
  21. package/dist/catalog-fingerprint.d.ts +0 -1
  22. package/dist/catalog-fingerprint.js +0 -1
  23. package/dist/catalog-limits.d.ts +0 -1
  24. package/dist/catalog-limits.js +0 -1
  25. package/dist/catalog-service.d.ts +0 -1
  26. package/dist/catalog-service.js +41 -5
  27. package/dist/catalog.d.ts +11 -1
  28. package/dist/catalog.js +20 -1
  29. package/dist/concurrency.d.ts +0 -1
  30. package/dist/concurrency.js +0 -1
  31. package/dist/connector-scope.d.ts +0 -1
  32. package/dist/connector-scope.js +0 -1
  33. package/dist/connectors/api.d.ts +29 -21
  34. package/dist/connectors/api.js +45 -19
  35. package/dist/connectors/guarded-fetch.d.ts +106 -0
  36. package/dist/connectors/guarded-fetch.js +289 -0
  37. package/dist/connectors/remote-mcp.d.ts +0 -1
  38. package/dist/connectors/remote-mcp.js +0 -1
  39. package/dist/credentials.d.ts +0 -1
  40. package/dist/credentials.js +0 -1
  41. package/dist/errors.d.ts +30 -3
  42. package/dist/errors.js +1 -1
  43. package/dist/execute.d.ts +0 -1
  44. package/dist/execute.js +0 -1
  45. package/dist/executor-admission.d.ts +11 -1
  46. package/dist/executor-admission.js +28 -1
  47. package/dist/executor-result.d.ts +0 -1
  48. package/dist/executor-result.js +0 -1
  49. package/dist/executors/quickjs-child.d.ts +0 -1
  50. package/dist/executors/quickjs-child.js +0 -1
  51. package/dist/executors/quickjs-protocol.d.ts +0 -1
  52. package/dist/executors/quickjs-protocol.js +0 -1
  53. package/dist/executors/quickjs-runtime.d.ts +0 -1
  54. package/dist/executors/quickjs-runtime.js +0 -1
  55. package/dist/executors/quickjs.d.ts +0 -1
  56. package/dist/executors/quickjs.js +2 -1
  57. package/dist/favicon.d.ts +0 -1
  58. package/dist/favicon.js +0 -1
  59. package/dist/index.d.ts +2 -3
  60. package/dist/index.js +24 -7
  61. package/dist/invocation.d.ts +0 -1
  62. package/dist/invocation.js +0 -1
  63. package/dist/json-schema.d.ts +0 -1
  64. package/dist/json-schema.js +0 -1
  65. package/dist/mcp-result.d.ts +0 -1
  66. package/dist/mcp-result.js +0 -1
  67. package/dist/meta-tools.d.ts +0 -1
  68. package/dist/meta-tools.js +0 -1
  69. package/dist/node.d.ts +0 -1
  70. package/dist/node.js +0 -1
  71. package/dist/operator-ui/generated.d.ts +2 -3
  72. package/dist/operator-ui/generated.js +3 -4
  73. package/dist/operator-ui/model.d.ts +10 -1
  74. package/dist/operator-ui/model.js +0 -1
  75. package/dist/operator-ui/view.d.ts +167 -0
  76. package/dist/operator-ui/view.js +270 -0
  77. package/dist/providers/cloudflare.d.ts +0 -1
  78. package/dist/providers/cloudflare.js +261 -299
  79. package/dist/providers/linear.d.ts +26 -5
  80. package/dist/providers/linear.js +32 -61
  81. package/dist/providers/mixpanel.d.ts +35 -4
  82. package/dist/providers/mixpanel.js +53 -79
  83. package/dist/providers/notion.d.ts +0 -1
  84. package/dist/providers/notion.js +87 -42
  85. package/dist/providers/stripe.d.ts +13 -1
  86. package/dist/providers/stripe.js +31 -60
  87. package/dist/registry.d.ts +34 -2
  88. package/dist/registry.js +85 -5
  89. package/dist/retired-toolkits.d.ts +0 -1
  90. package/dist/retired-toolkits.js +0 -1
  91. package/dist/routes/access-tokens.d.ts +0 -1
  92. package/dist/routes/access-tokens.js +0 -1
  93. package/dist/routes/activity.d.ts +0 -1
  94. package/dist/routes/activity.js +0 -1
  95. package/dist/routes/credentials.d.ts +0 -1
  96. package/dist/routes/credentials.js +0 -1
  97. package/dist/routes/mcp.d.ts +0 -1
  98. package/dist/routes/mcp.js +0 -1
  99. package/dist/routes/oauth.d.ts +0 -1
  100. package/dist/routes/oauth.js +0 -1
  101. package/dist/routes/shared.d.ts +2 -1
  102. package/dist/routes/shared.js +0 -1
  103. package/dist/routes/ui.d.ts +0 -1
  104. package/dist/routes/ui.js +0 -1
  105. package/dist/server.d.ts +0 -1
  106. package/dist/server.js +11 -1
  107. package/dist/skills.d.ts +0 -1
  108. package/dist/skills.js +0 -1
  109. package/dist/storage/file.d.ts +0 -1
  110. package/dist/storage/file.js +0 -1
  111. package/dist/storage/memory.d.ts +0 -1
  112. package/dist/storage/memory.js +0 -1
  113. package/dist/timeout.d.ts +0 -1
  114. package/dist/timeout.js +0 -1
  115. package/dist/tool-safety.d.ts +0 -1
  116. package/dist/tool-safety.js +0 -1
  117. package/dist/types.d.ts +45 -1
  118. package/dist/types.js +0 -1
  119. package/dist/ui.d.ts +0 -1
  120. package/dist/ui.js +13 -137
  121. package/dist/validate.d.ts +13 -15
  122. package/dist/validate.js +24 -11
  123. package/dist/version.d.ts +1 -2
  124. package/dist/version.js +1 -2
  125. package/documentation/architecture.md +186 -5
  126. package/documentation/auth.md +11 -0
  127. package/documentation/call-admission.md +142 -6
  128. package/documentation/cloudflare.md +92 -10
  129. package/documentation/code-first-exploration.md +2 -2
  130. package/documentation/code-mode.md +14 -14
  131. package/documentation/connector-guides.md +172 -5
  132. package/documentation/connectors.md +89 -0
  133. package/documentation/linear.md +20 -2
  134. package/documentation/mcp-ui-design.md +2 -2
  135. package/documentation/meta-tools.md +7 -1
  136. package/documentation/mixpanel.md +42 -9
  137. package/documentation/notion.md +23 -2
  138. package/documentation/operations.md +325 -5
  139. package/documentation/operator-ui.md +120 -5
  140. package/documentation/provider-audit.md +160 -0
  141. package/documentation/provider-conventions.md +667 -0
  142. package/documentation/request-admission.md +167 -5
  143. package/documentation/rich-output-design.md +1 -2
  144. package/documentation/stripe.md +15 -0
  145. package/documentation/upgrading.md +454 -0
  146. package/ethos.md +14 -9
  147. package/examples/worker/README.md +102 -12
  148. package/examples/worker/src/index.ts +46 -1
  149. package/examples/worker/wrangler.jsonc +14 -1
  150. package/package.json +10 -6
  151. package/templates/node/.dockerignore +10 -0
  152. package/templates/node/.env.example +40 -3
  153. package/templates/node/AGENTS.md +16 -1
  154. package/templates/node/Dockerfile +49 -0
  155. package/templates/node/README.md +117 -2
  156. package/templates/node/docker-compose.yml +39 -0
  157. package/templates/node/package.json +1 -1
  158. package/templates/node/src/file-activity.ts +182 -0
  159. package/templates/node/src/index.ts +80 -4
  160. package/assets/connecta-clay-hero.png +0 -0
  161. package/dist/access-tokens.d.ts.map +0 -1
  162. package/dist/access-tokens.js.map +0 -1
  163. package/dist/activity.d.ts.map +0 -1
  164. package/dist/activity.js.map +0 -1
  165. package/dist/apps-shell.d.ts.map +0 -1
  166. package/dist/apps-shell.js.map +0 -1
  167. package/dist/auth/bearer.d.ts.map +0 -1
  168. package/dist/auth/bearer.js.map +0 -1
  169. package/dist/auth/clerk.d.ts.map +0 -1
  170. package/dist/auth/clerk.js.map +0 -1
  171. package/dist/auth/downstream-oauth.d.ts.map +0 -1
  172. package/dist/auth/downstream-oauth.js.map +0 -1
  173. package/dist/call-admission.d.ts.map +0 -1
  174. package/dist/call-admission.js.map +0 -1
  175. package/dist/catalog-fingerprint.d.ts.map +0 -1
  176. package/dist/catalog-fingerprint.js.map +0 -1
  177. package/dist/catalog-limits.d.ts.map +0 -1
  178. package/dist/catalog-limits.js.map +0 -1
  179. package/dist/catalog-service.d.ts.map +0 -1
  180. package/dist/catalog-service.js.map +0 -1
  181. package/dist/catalog.d.ts.map +0 -1
  182. package/dist/catalog.js.map +0 -1
  183. package/dist/concurrency.d.ts.map +0 -1
  184. package/dist/concurrency.js.map +0 -1
  185. package/dist/connector-scope.d.ts.map +0 -1
  186. package/dist/connector-scope.js.map +0 -1
  187. package/dist/connectors/api.d.ts.map +0 -1
  188. package/dist/connectors/api.js.map +0 -1
  189. package/dist/connectors/remote-mcp.d.ts.map +0 -1
  190. package/dist/connectors/remote-mcp.js.map +0 -1
  191. package/dist/credentials.d.ts.map +0 -1
  192. package/dist/credentials.js.map +0 -1
  193. package/dist/errors.d.ts.map +0 -1
  194. package/dist/errors.js.map +0 -1
  195. package/dist/execute.d.ts.map +0 -1
  196. package/dist/execute.js.map +0 -1
  197. package/dist/executor-admission.d.ts.map +0 -1
  198. package/dist/executor-admission.js.map +0 -1
  199. package/dist/executor-result.d.ts.map +0 -1
  200. package/dist/executor-result.js.map +0 -1
  201. package/dist/executors/quickjs-child.d.ts.map +0 -1
  202. package/dist/executors/quickjs-child.js.map +0 -1
  203. package/dist/executors/quickjs-protocol.d.ts.map +0 -1
  204. package/dist/executors/quickjs-protocol.js.map +0 -1
  205. package/dist/executors/quickjs-runtime.d.ts.map +0 -1
  206. package/dist/executors/quickjs-runtime.js.map +0 -1
  207. package/dist/executors/quickjs.d.ts.map +0 -1
  208. package/dist/executors/quickjs.js.map +0 -1
  209. package/dist/favicon.d.ts.map +0 -1
  210. package/dist/favicon.js.map +0 -1
  211. package/dist/index.d.ts.map +0 -1
  212. package/dist/index.js.map +0 -1
  213. package/dist/invocation.d.ts.map +0 -1
  214. package/dist/invocation.js.map +0 -1
  215. package/dist/json-schema.d.ts.map +0 -1
  216. package/dist/json-schema.js.map +0 -1
  217. package/dist/mcp-result.d.ts.map +0 -1
  218. package/dist/mcp-result.js.map +0 -1
  219. package/dist/meta-tools.d.ts.map +0 -1
  220. package/dist/meta-tools.js.map +0 -1
  221. package/dist/node.d.ts.map +0 -1
  222. package/dist/node.js.map +0 -1
  223. package/dist/operator-ui/generated.d.ts.map +0 -1
  224. package/dist/operator-ui/generated.js.map +0 -1
  225. package/dist/operator-ui/model.d.ts.map +0 -1
  226. package/dist/operator-ui/model.js.map +0 -1
  227. package/dist/providers/cloudflare.d.ts.map +0 -1
  228. package/dist/providers/cloudflare.js.map +0 -1
  229. package/dist/providers/linear.d.ts.map +0 -1
  230. package/dist/providers/linear.js.map +0 -1
  231. package/dist/providers/mixpanel.d.ts.map +0 -1
  232. package/dist/providers/mixpanel.js.map +0 -1
  233. package/dist/providers/notion.d.ts.map +0 -1
  234. package/dist/providers/notion.js.map +0 -1
  235. package/dist/providers/stripe.d.ts.map +0 -1
  236. package/dist/providers/stripe.js.map +0 -1
  237. package/dist/registry.d.ts.map +0 -1
  238. package/dist/registry.js.map +0 -1
  239. package/dist/retired-toolkits.d.ts.map +0 -1
  240. package/dist/retired-toolkits.js.map +0 -1
  241. package/dist/routes/access-tokens.d.ts.map +0 -1
  242. package/dist/routes/access-tokens.js.map +0 -1
  243. package/dist/routes/activity.d.ts.map +0 -1
  244. package/dist/routes/activity.js.map +0 -1
  245. package/dist/routes/credentials.d.ts.map +0 -1
  246. package/dist/routes/credentials.js.map +0 -1
  247. package/dist/routes/mcp.d.ts.map +0 -1
  248. package/dist/routes/mcp.js.map +0 -1
  249. package/dist/routes/oauth.d.ts.map +0 -1
  250. package/dist/routes/oauth.js.map +0 -1
  251. package/dist/routes/shared.d.ts.map +0 -1
  252. package/dist/routes/shared.js.map +0 -1
  253. package/dist/routes/ui.d.ts.map +0 -1
  254. package/dist/routes/ui.js.map +0 -1
  255. package/dist/server.d.ts.map +0 -1
  256. package/dist/server.js.map +0 -1
  257. package/dist/skills.d.ts.map +0 -1
  258. package/dist/skills.js.map +0 -1
  259. package/dist/storage/file.d.ts.map +0 -1
  260. package/dist/storage/file.js.map +0 -1
  261. package/dist/storage/memory.d.ts.map +0 -1
  262. package/dist/storage/memory.js.map +0 -1
  263. package/dist/timeout.d.ts.map +0 -1
  264. package/dist/timeout.js.map +0 -1
  265. package/dist/tool-safety.d.ts.map +0 -1
  266. package/dist/tool-safety.js.map +0 -1
  267. package/dist/types.d.ts.map +0 -1
  268. package/dist/types.js.map +0 -1
  269. package/dist/ui.d.ts.map +0 -1
  270. package/dist/ui.js.map +0 -1
  271. package/dist/validate.d.ts.map +0 -1
  272. package/dist/validate.js.map +0 -1
  273. package/dist/version.d.ts.map +0 -1
  274. package/dist/version.js.map +0 -1
  275. package/examples/node/README.md +0 -52
  276. package/examples/node/src/index.ts +0 -71
  277. package/src/access-tokens.ts +0 -289
  278. package/src/activity.ts +0 -234
  279. package/src/apps-shell.ts +0 -370
  280. package/src/auth/bearer.ts +0 -61
  281. package/src/auth/clerk.ts +0 -553
  282. package/src/auth/downstream-oauth.ts +0 -575
  283. package/src/call-admission.ts +0 -521
  284. package/src/catalog-fingerprint.ts +0 -52
  285. package/src/catalog-limits.ts +0 -11
  286. package/src/catalog-service.ts +0 -1153
  287. package/src/catalog.ts +0 -790
  288. package/src/concurrency.ts +0 -39
  289. package/src/connector-scope.ts +0 -66
  290. package/src/connectors/api.ts +0 -159
  291. package/src/connectors/remote-mcp.ts +0 -1090
  292. package/src/credentials.ts +0 -428
  293. package/src/errors.ts +0 -364
  294. package/src/execute.ts +0 -1406
  295. package/src/executor-admission.ts +0 -328
  296. package/src/executor-result.ts +0 -107
  297. package/src/executors/quickjs-child.ts +0 -172
  298. package/src/executors/quickjs-protocol.ts +0 -99
  299. package/src/executors/quickjs-runtime.ts +0 -470
  300. package/src/executors/quickjs.ts +0 -776
  301. package/src/favicon.ts +0 -53
  302. package/src/index.ts +0 -666
  303. package/src/invocation.ts +0 -655
  304. package/src/json-schema.ts +0 -11
  305. package/src/mcp-result.ts +0 -43
  306. package/src/meta-tools.ts +0 -1607
  307. package/src/node.ts +0 -206
  308. package/src/operator-ui/browser.css +0 -423
  309. package/src/operator-ui/browser.ts +0 -1281
  310. package/src/operator-ui/generated.ts +0 -4
  311. package/src/operator-ui/model.ts +0 -111
  312. package/src/providers/cloudflare.ts +0 -3999
  313. package/src/providers/linear.ts +0 -302
  314. package/src/providers/mixpanel.ts +0 -229
  315. package/src/providers/notion.ts +0 -1879
  316. package/src/providers/stripe.ts +0 -307
  317. package/src/registry.ts +0 -1085
  318. package/src/retired-toolkits.ts +0 -14
  319. package/src/routes/access-tokens.ts +0 -115
  320. package/src/routes/activity.ts +0 -215
  321. package/src/routes/credentials.ts +0 -254
  322. package/src/routes/mcp.ts +0 -429
  323. package/src/routes/oauth.ts +0 -322
  324. package/src/routes/shared.ts +0 -247
  325. package/src/routes/ui.ts +0 -152
  326. package/src/server.ts +0 -195
  327. package/src/skills.ts +0 -271
  328. package/src/storage/file.ts +0 -123
  329. package/src/storage/memory.ts +0 -44
  330. package/src/timeout.ts +0 -84
  331. package/src/tool-safety.ts +0 -15
  332. package/src/types.ts +0 -523
  333. package/src/ui.ts +0 -744
  334. package/src/validate.ts +0 -387
  335. package/src/version.ts +0 -7
package/src/invocation.ts DELETED
@@ -1,655 +0,0 @@
1
- import {
2
- recordToolActivity,
3
- type ActivityCallSource,
4
- type ActivityRequestContext,
5
- type AgentFriction,
6
- } from "./activity.js";
7
- import { isCallAdmissionError } from "./call-admission.js";
8
- import {
9
- CatalogService,
10
- type CatalogResolution,
11
- type ResolvedCatalogTool,
12
- } from "./catalog-service.js";
13
- import {
14
- classifyCallError,
15
- ConnectorCallError,
16
- echoedCallArgs,
17
- framingError,
18
- type AuthRecoveryMode,
19
- type CallErrorDetails,
20
- } from "./errors.js";
21
- import { unwrapMcpResult } from "./mcp-result.js";
22
- import { splitAddress, type RegistryView } from "./registry.js";
23
- import { isExplicitlyReadOnly } from "./tool-safety.js";
24
- import type { ToolDef } from "./types.js";
25
- import { validateToolInput } from "./validate.js";
26
-
27
- /**
28
- * The longest the engine will park a synchronous inbound request in *waiting
29
- * alone*. The engine already treats ~15 s as the outer bound of one reasonable
30
- * connector call (EXECUTE_HOST_CALL_TIMEOUT_MS), so sleeping for minutes trades
31
- * a fast, informative failure for a hung one. A connector-reported window this
32
- * long isn't truncated — it's declined (see `retryBackoffMs`) and reported
33
- * verbatim as `error.retryAfterMs`, so the agent, which can afford to wait,
34
- * decides when to re-issue.
35
- */
36
- export const MAX_RETRY_BACKOFF_MS = 10_000;
37
-
38
- /**
39
- * How long to wait before the next attempt, or `undefined` for "don't retry".
40
- *
41
- * A connector that read a `Retry-After` header knows the window exactly, so it
42
- * is honoured **exactly or not at all**: truncating an exponential *guess* is
43
- * harmless, but truncating a *known* window means deliberately retrying inside
44
- * a rate limit — the harm this channel exists to prevent. A window longer than
45
- * `MAX_RETRY_BACKOFF_MS` therefore declines the retry rather than shortening
46
- * it. (`retryAfterMs` is normalized non-negative, so `0` means "retry now".)
47
- * Connectors that report no window keep the historical exponential guess.
48
- *
49
- * Waits are per attempt, matching the per-attempt `timeoutMs` race in
50
- * `InvocationService.invoke`. Exported for direct testing.
51
- */
52
- export function retryBackoffMs(
53
- attempt: number,
54
- retryAfterMs: number | undefined,
55
- ): number | undefined {
56
- if (retryAfterMs === undefined) {
57
- return Math.min(250 * 2 ** (attempt - 1), 1_000);
58
- }
59
- return retryAfterMs <= MAX_RETRY_BACKOFF_MS ? retryAfterMs : undefined;
60
- }
61
-
62
- function retrySafe(definition: ToolDef): boolean {
63
- return (
64
- definition.annotations?.readOnlyHint === true ||
65
- definition.annotations?.idempotentHint === true
66
- );
67
- }
68
-
69
- function callerCancelledDetails(): CallErrorDetails {
70
- return {
71
- code: "cancelled",
72
- message: "Tool call was cancelled by the caller.",
73
- retryable: false,
74
- };
75
- }
76
-
77
- function recoveryMode(
78
- registry: RegistryView,
79
- connector: ResolvedCatalogTool["connector"],
80
- baseUrl: string,
81
- ): AuthRecoveryMode {
82
- if (connector.startAuth) return "oauth";
83
- if (
84
- connector.credential &&
85
- registry.contextFor(connector.id, baseUrl).credential
86
- ) {
87
- return "operator_config";
88
- }
89
- return "unavailable";
90
- }
91
-
92
- function isCallerCancellation(
93
- error: unknown,
94
- signal: AbortSignal | undefined,
95
- ): boolean {
96
- return (
97
- signal?.aborted === true ||
98
- (isCallAdmissionError(error) && error.admissionKind === "cancelled")
99
- );
100
- }
101
-
102
- function assertRawMcpSuccess(
103
- kind: ResolvedCatalogTool["connector"]["kind"],
104
- result: unknown,
105
- ): void {
106
- if (kind !== "mcp" || result == null || typeof result !== "object") return;
107
- const mcpResult = result as {
108
- content?: Array<{ type?: string; text?: string }>;
109
- isError?: boolean;
110
- };
111
- if (!mcpResult.isError) return;
112
- throw new Error(
113
- mcpResult.content
114
- ?.filter((block) => block.type === "text")
115
- .map((block) => block.text ?? "")
116
- .join("") || "Downstream tool call failed",
117
- );
118
- }
119
-
120
- export interface InvocationTiming {
121
- catalogMs: number;
122
- admissionMs: number;
123
- connectorMs: number;
124
- backoffMs: number;
125
- resultProcessingMs: number;
126
- totalMs: number;
127
- }
128
-
129
- interface InvocationBase {
130
- durationMs: number;
131
- attempts: number;
132
- timing: InvocationTiming;
133
- resolved?: ResolvedCatalogTool;
134
- }
135
-
136
- export type InvocationOutcome<T> =
137
- | (InvocationBase & { ok: true; value: T; resolved: ResolvedCatalogTool })
138
- | (InvocationBase & { ok: false; error: CallErrorDetails });
139
-
140
- export interface InvocationContext<T> {
141
- source: ActivityCallSource;
142
- allowDestructive?: boolean;
143
- timeoutMs?: number;
144
- maxRetries?: number;
145
- requestSignal?: AbortSignal;
146
- unwrapResult?: boolean;
147
- /**
148
- * Caller-owned result policy. MCP passes fields + paging here; code mode
149
- * normally accepts the already-unwrapped value unchanged.
150
- */
151
- processResult?: (
152
- value: unknown,
153
- resolved: ResolvedCatalogTool,
154
- ) => T | Promise<T>;
155
- /**
156
- * Optional payload-free friction class derived from a *successful* result —
157
- * today only an oversized one that had to be paged. It is deliberately not an
158
- * `errorCode`: the call succeeded, and a consumer that keys its dashboards on
159
- * "has an error code" must not count a truncation as a failure.
160
- */
161
- activityFriction?: (value: T) => AgentFriction | undefined;
162
- /**
163
- * Called after address/catalog/safety admission and before the first provider
164
- * attempt. Code mode uses it for its host-call budget.
165
- */
166
- beforeDispatch?: () => void;
167
- }
168
-
169
- export class InvocationFailure extends Error {
170
- readonly code: string;
171
- readonly retryable: boolean;
172
- readonly retryAfterMs: number | undefined;
173
- readonly connector: string | undefined;
174
- readonly operation: string | undefined;
175
- readonly recovery: CallErrorDetails["recovery"];
176
- readonly nextAction: CallErrorDetails["nextAction"];
177
- readonly retry: string | undefined;
178
-
179
- constructor(readonly details: CallErrorDetails) {
180
- super(details.message);
181
- this.name = "InvocationFailure";
182
- this.code = details.code;
183
- this.retryable = details.retryable;
184
- this.retryAfterMs = details.retryAfterMs;
185
- this.connector = details.connector;
186
- this.operation = details.operation;
187
- this.recovery = details.recovery;
188
- this.nextAction = details.nextAction;
189
- this.retry = details.retry;
190
- }
191
- }
192
-
193
- /**
194
- * Shared downstream invocation engine. Address/catalog resolution is delegated
195
- * to the request-local CatalogService; every remaining call semantic lives
196
- * here so MCP and code mode cannot drift independently.
197
- */
198
- export class InvocationService {
199
- constructor(
200
- private readonly registry: RegistryView,
201
- private readonly catalog: CatalogService,
202
- private readonly activity?: ActivityRequestContext,
203
- ) {}
204
-
205
- async invoke<T = unknown>(
206
- address: string,
207
- args: unknown,
208
- context: InvocationContext<T>,
209
- ): Promise<InvocationOutcome<T>> {
210
- return this.invokeWithResolution(address, args, context, () =>
211
- this.catalog.resolveTool(
212
- address,
213
- context.requestSignal !== undefined
214
- ? { signal: context.requestSignal }
215
- : {},
216
- ),
217
- );
218
- }
219
-
220
- /**
221
- * Code-mode namespace dispatch preserves JavaScript-safe tool aliases while
222
- * still feeding the resolved catalog entry through the one invocation path.
223
- */
224
- async invokeToolAlias<T = unknown>(
225
- connectorId: string,
226
- toolAlias: string,
227
- aliasFor: (toolName: string) => string,
228
- args: unknown,
229
- context: InvocationContext<T>,
230
- ): Promise<InvocationOutcome<T>> {
231
- return this.invokeWithResolution(
232
- `${connectorId}.${toolAlias}`,
233
- args,
234
- context,
235
- () =>
236
- this.catalog.resolveToolAlias(
237
- connectorId,
238
- toolAlias,
239
- aliasFor,
240
- context.requestSignal !== undefined
241
- ? { signal: context.requestSignal }
242
- : {},
243
- ),
244
- );
245
- }
246
-
247
- private async invokeWithResolution<T>(
248
- address: string,
249
- args: unknown,
250
- context: InvocationContext<T>,
251
- resolve: () => Promise<CatalogResolution>,
252
- ): Promise<InvocationOutcome<T>> {
253
- const started = Date.now();
254
- let catalogMs = 0;
255
- let admissionMs = 0;
256
- let connectorMs = 0;
257
- let backoffMs = 0;
258
- let resultProcessingMs = 0;
259
- let attempts = 0;
260
- let resolved: ResolvedCatalogTool | undefined;
261
- let activityTarget:
262
- | Pick<ResolvedCatalogTool, "connector" | "toolName">
263
- | undefined;
264
- // The address as written, used for activity when resolution never reached
265
- // a connector. Only its two halves are recorded — the same fields activity
266
- // has always carried — so no new class of payload enters the log.
267
- const attempted = splitAddress(address);
268
- const timing = (): InvocationTiming => ({
269
- catalogMs,
270
- admissionMs,
271
- connectorMs,
272
- backoffMs,
273
- resultProcessingMs,
274
- totalMs: Date.now() - started,
275
- });
276
- const record = (
277
- outcome: "success" | "error" | "timeout" | "cancelled",
278
- classification: { errorCode?: string; friction?: AgentFriction } = {},
279
- ) => {
280
- const identity = activityTarget
281
- ? {
282
- connectorId: activityTarget.connector.id,
283
- toolName: activityTarget.toolName,
284
- }
285
- : attempted;
286
- if (!identity) return;
287
- recordToolActivity(this.activity, {
288
- connectorId: identity.connectorId,
289
- toolName: identity.toolName,
290
- address: `${identity.connectorId}.${identity.toolName}`,
291
- source: context.source,
292
- outcome,
293
- durationMs: Date.now() - started,
294
- attempts,
295
- ...(classification.errorCode
296
- ? { errorCode: classification.errorCode }
297
- : {}),
298
- ...(classification.friction
299
- ? { friction: classification.friction }
300
- : {}),
301
- });
302
- };
303
- const failed = (error: CallErrorDetails): InvocationOutcome<T> => {
304
- const diagnostics = timing();
305
- const target = resolved ?? activityTarget;
306
- const echoed =
307
- error.code === "destructive_tool_requires_approval"
308
- ? echoedCallArgs(args)
309
- : {};
310
- const details =
311
- error.code === "destructive_tool_requires_approval" && target
312
- ? {
313
- ...error,
314
- nextAction: {
315
- tool: "call_destructive_tool" as const,
316
- arguments: {
317
- address: `${target.connector.id}.${target.toolName}`,
318
- ...echoed,
319
- },
320
- purpose:
321
- "Ask the MCP host to approve this consequential call. " +
322
- ("args" in echoed
323
- ? "Re-send these arguments and add a short reason for the human reviewer."
324
- : "Re-send the arguments you just sent — they are too large to echo back — and add a short reason for the human reviewer."),
325
- },
326
- }
327
- : error.code === "auth_required" && target
328
- ? {
329
- ...error,
330
- connector: target.connector.id,
331
- operation: `${target.connector.id}.${target.toolName}`,
332
- recovery: recoveryMode(
333
- this.registry,
334
- target.connector,
335
- this.catalog.baseUrl,
336
- ),
337
- nextAction: {
338
- tool: "authorize_connector" as const,
339
- arguments: { connector: target.connector.id },
340
- operatorHandoff:
341
- "Give the URL and instructions it returns to the operator.",
342
- },
343
- retry:
344
- `Retry ${target.connector.id}.${target.toolName} after ` +
345
- "the operator completes recovery.",
346
- }
347
- : error.code === "invalid_args" && error.validation && target
348
- ? {
349
- ...error,
350
- connector: target.connector.id,
351
- operation: `${target.connector.id}.${target.toolName}`,
352
- nextAction: this.catalog.searchRecovery(
353
- {
354
- query: target.toolName,
355
- connector: target.connector.id,
356
- },
357
- "Inspect the current input shape if the validation findings are not sufficient.",
358
- ),
359
- retry:
360
- `Correct the listed arguments and retry ` +
361
- `${target.connector.id}.${target.toolName}.`,
362
- }
363
- : error;
364
- record(
365
- details.code === "timeout"
366
- ? "timeout"
367
- : details.code === "cancelled"
368
- ? "cancelled"
369
- : "error",
370
- { errorCode: details.code },
371
- );
372
- return {
373
- ok: false,
374
- durationMs: Date.now() - started,
375
- attempts,
376
- timing: diagnostics,
377
- ...(resolved ? { resolved } : {}),
378
- error: details,
379
- };
380
- };
381
-
382
- const resolution = await resolve();
383
- catalogMs += resolution.catalogMs;
384
- if (!resolution.ok) {
385
- if (resolution.connector && resolution.toolName) {
386
- activityTarget = {
387
- connector: resolution.connector,
388
- toolName: resolution.toolName,
389
- };
390
- }
391
- if (resolution.cause && resolution.connector) {
392
- if (context.requestSignal?.aborted) {
393
- return failed(callerCancelledDetails());
394
- }
395
- // A connector whose catalog cannot be fetched is as unusable as one
396
- // whose execution fails, so it feeds health accounting the same way the
397
- // attempt catch below does — otherwise a connector every call fails
398
- // against (a revoked downstream grant, say) still reads clean in the
399
- // deployment's health log.
400
- //
401
- // Recorded HERE rather than inside the registry's catalog fetch because
402
- // a cache hit that avoids a live listTools call records nothing — it is
403
- // not evidence of health. Success stays what it has always been: an
404
- // actual downstream call that returned.
405
- this.registry.recordFailure(
406
- resolution.connector.id,
407
- Date.now() - started,
408
- resolution.cause,
409
- );
410
- }
411
- return failed(resolution.error);
412
- }
413
- resolved = resolution.resolved;
414
- activityTarget = resolved;
415
-
416
- if (!isExplicitlyReadOnly(resolved.definition) && !context.allowDestructive) {
417
- const canonicalAddress = `${resolved.connector.id}.${resolved.toolName}`;
418
- return failed(
419
- framingError(
420
- "destructive_tool_requires_approval",
421
- `Tool "${canonicalAddress}" is not explicitly read-only. Invoke it through call_destructive_tool so the MCP host can request explicit approval.`,
422
- ),
423
- );
424
- }
425
-
426
- // Remote MCP tools advertise their input schema in the catalog. Validate
427
- // against that same request-local definition before admission or provider
428
- // dispatch, so a predictable mismatch stays structured instead of being
429
- // flattened into provider-specific error prose. Unsupported schemas retain
430
- // validateToolInput's fail-open behavior and reach the downstream normally.
431
- if (
432
- resolved.connector.kind === "mcp" &&
433
- resolved.definition.inputSchema
434
- ) {
435
- const invalid = validateToolInput(
436
- resolved.definition.inputSchema,
437
- args ?? {},
438
- {
439
- address: `${resolved.connector.id}.${resolved.toolName}`,
440
- logger: this.registry.contextFor(
441
- resolved.connector.id,
442
- this.catalog.baseUrl,
443
- this.catalog.requestScope,
444
- ).logger,
445
- },
446
- );
447
- if (invalid) return failed(classifyCallError(invalid));
448
- }
449
-
450
- try {
451
- context.beforeDispatch?.();
452
- } catch (error) {
453
- return failed(classifyCallError(error));
454
- }
455
-
456
- const maxRetries = Math.min(
457
- 2,
458
- Math.max(0, Math.trunc(context.maxRetries ?? 0)),
459
- );
460
- let result: unknown;
461
- while (true) {
462
- attempts++;
463
- let permit: Awaited<ReturnType<RegistryView["admitCall"]>> | undefined;
464
- const controller =
465
- context.timeoutMs || context.requestSignal
466
- ? new AbortController()
467
- : undefined;
468
- const forwardAbort = () =>
469
- controller?.abort(context.requestSignal?.reason);
470
- if (context.requestSignal?.aborted) forwardAbort();
471
- else {
472
- context.requestSignal?.addEventListener("abort", forwardAbort, {
473
- once: true,
474
- });
475
- }
476
- let timer: ReturnType<typeof setTimeout> | undefined;
477
- let onAbort: (() => void) | undefined;
478
- let attemptError: unknown;
479
- let attemptFailed = false;
480
- try {
481
- const admissionStarted = Date.now();
482
- try {
483
- permit = await this.registry.admitCall(resolved.connector.id, {
484
- toolName: resolved.toolName,
485
- args: args ?? {},
486
- ...(context.requestSignal !== undefined
487
- ? { signal: context.requestSignal }
488
- : {}),
489
- });
490
- } finally {
491
- admissionMs += Date.now() - admissionStarted;
492
- }
493
- const connectorContext = this.registry.contextFor(
494
- resolved.connector.id,
495
- this.catalog.baseUrl,
496
- this.catalog.requestScope,
497
- {
498
- ...(controller?.signal !== undefined
499
- ? { signal: controller.signal }
500
- : {}),
501
- ...(context.timeoutMs !== undefined
502
- ? { timeoutMs: context.timeoutMs }
503
- : {}),
504
- },
505
- );
506
- if (resolved.connector.credential && !connectorContext.credential) {
507
- throw new ConnectorCallError(
508
- "auth_required",
509
- "Operator-managed credential storage is not configured. Call " +
510
- `authorize_connector({ connector: "${resolved.connector.id}" }).`,
511
- );
512
- }
513
- let rejectCancelled!: (reason: unknown) => void;
514
- const cancelled = controller
515
- ? new Promise<never>((_, reject) => {
516
- rejectCancelled = reject;
517
- })
518
- : undefined;
519
- onAbort = () => {
520
- rejectCancelled(
521
- controller?.signal.reason ??
522
- new ConnectorCallError("timeout", "Tool call was cancelled"),
523
- );
524
- };
525
- controller?.signal.addEventListener("abort", onAbort, { once: true });
526
- if (controller?.signal.aborted) onAbort();
527
- if (controller?.signal.aborted) await cancelled;
528
- if (context.timeoutMs) {
529
- timer = setTimeout(() => {
530
- controller?.abort(
531
- new ConnectorCallError(
532
- "timeout",
533
- `Tool call timed out after ${context.timeoutMs}ms`,
534
- ),
535
- );
536
- }, context.timeoutMs);
537
- }
538
- const connectorStarted = Date.now();
539
- try {
540
- const pending = resolved.connector.callTool(
541
- resolved.toolName,
542
- args ?? {},
543
- connectorContext,
544
- );
545
- const raw = cancelled
546
- ? await Promise.race([pending, cancelled])
547
- : await pending;
548
- // isError is checked here for BOTH result shapes so every adapter
549
- // reports the same downstream-failure wording, and the throw lands
550
- // inside the attempt where it stays retry-eligible and feeds health.
551
- assertRawMcpSuccess(resolved.connector.kind, raw);
552
- result = context.unwrapResult
553
- ? unwrapMcpResult(resolved.connector.kind, raw)
554
- : raw;
555
- } finally {
556
- connectorMs += Date.now() - connectorStarted;
557
- }
558
- } catch (error) {
559
- attemptFailed = true;
560
- attemptError = error;
561
- } finally {
562
- if (timer) clearTimeout(timer);
563
- if (onAbort) {
564
- controller?.signal.removeEventListener("abort", onAbort);
565
- }
566
- context.requestSignal?.removeEventListener("abort", forwardAbort);
567
- permit?.release();
568
- }
569
-
570
- if (attemptFailed) {
571
- const callerCancelled = isCallerCancellation(
572
- attemptError,
573
- context.requestSignal,
574
- );
575
- const details = callerCancelled
576
- ? callerCancelledDetails()
577
- : classifyCallError(attemptError);
578
- if (
579
- !callerCancelled &&
580
- attempts <= maxRetries &&
581
- retrySafe(resolved.definition) &&
582
- details.retryable
583
- ) {
584
- const wait = retryBackoffMs(attempts, details.retryAfterMs);
585
- if (wait !== undefined) {
586
- const backoffStarted = Date.now();
587
- if (wait > 0) {
588
- const completed = await new Promise<boolean>((resolve) => {
589
- let settled = false;
590
- const finish = (value: boolean) => {
591
- if (settled) return;
592
- settled = true;
593
- clearTimeout(timer);
594
- context.requestSignal?.removeEventListener("abort", cancel);
595
- resolve(value);
596
- };
597
- const timer = setTimeout(() => finish(true), wait);
598
- const cancel = () => finish(false);
599
- context.requestSignal?.addEventListener("abort", cancel, {
600
- once: true,
601
- });
602
- if (context.requestSignal?.aborted) cancel();
603
- });
604
- backoffMs += Date.now() - backoffStarted;
605
- if (!completed) return failed(callerCancelledDetails());
606
- } else {
607
- backoffMs += Date.now() - backoffStarted;
608
- }
609
- continue;
610
- }
611
- // The reported window is longer than the engine will park a
612
- // synchronous request for. Fall through to failure with
613
- // retryAfterMs reported verbatim so the agent can re-issue.
614
- }
615
- if (!callerCancelled && !isCallAdmissionError(attemptError)) {
616
- this.registry.recordFailure(
617
- resolved.connector.id,
618
- Date.now() - started,
619
- attemptError,
620
- );
621
- }
622
- return failed(details);
623
- }
624
- break;
625
- }
626
-
627
- this.registry.recordSuccess(resolved.connector.id, Date.now() - started);
628
- const processingStarted = Date.now();
629
- try {
630
- const value = context.processResult
631
- ? await context.processResult(result, resolved)
632
- : (result as T);
633
- resultProcessingMs += Date.now() - processingStarted;
634
- const diagnostics = timing();
635
- const friction = context.activityFriction?.(value);
636
- record("success", friction ? { friction } : {});
637
- return {
638
- ok: true,
639
- value,
640
- resolved,
641
- durationMs: Date.now() - started,
642
- attempts,
643
- timing: diagnostics,
644
- };
645
- } catch (error) {
646
- resultProcessingMs += Date.now() - processingStarted;
647
- return failed(
648
- framingError(
649
- "result_processing_failed",
650
- error instanceof Error ? error.message : String(error),
651
- ),
652
- );
653
- }
654
- }
655
- }
@@ -1,11 +0,0 @@
1
- // Public re-export of the JSON Schema validator connecta itself uses, so
2
- // downstream code that validates at build time (a manifest generator asserting
3
- // its own output, say) resolves the same implementation and version through an
4
- // explicit subpath rather than through npm hoisting.
5
- export { Validator } from "@cfworker/json-schema";
6
- export type {
7
- OutputUnit,
8
- Schema,
9
- SchemaDraft,
10
- ValidationResult,
11
- } from "@cfworker/json-schema";
package/src/mcp-result.ts DELETED
@@ -1,43 +0,0 @@
1
- import type { Connector } from "./types.js";
2
-
3
- interface McpishResult {
4
- content?: { type?: string; text?: string }[];
5
- isError?: boolean;
6
- structuredContent?: unknown;
7
- toolResult?: unknown;
8
- }
9
-
10
- /**
11
- * Unwrap an MCP CallToolResult into an ordinary JavaScript value:
12
- * `toolResult` wins when present, then `structuredContent`; all-text content is
13
- * JSON-parsed when possible. Downstream `isError` results become exceptions.
14
- * Non-MCP connectors already return plain values.
15
- */
16
- export function unwrapMcpResult(
17
- kind: Connector["kind"],
18
- result: unknown,
19
- ): unknown {
20
- if (kind !== "mcp" || result == null || typeof result !== "object") {
21
- return result;
22
- }
23
- const r = result as McpishResult;
24
- if ("toolResult" in r) return r.toolResult;
25
- const content = Array.isArray(r.content) ? r.content : [];
26
- if (r.isError) {
27
- const text = content
28
- .filter((c) => c.type === "text")
29
- .map((c) => c.text ?? "")
30
- .join("\n");
31
- throw new Error(text || "Tool call failed");
32
- }
33
- if (r.structuredContent != null) return r.structuredContent;
34
- if (content.length > 0 && content.every((c) => c.type === "text")) {
35
- const text = content.map((c) => c.text ?? "").join("\n");
36
- try {
37
- return JSON.parse(text);
38
- } catch {
39
- return text;
40
- }
41
- }
42
- return result;
43
- }