@zackbart/connecta 0.28.0 → 0.29.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 (320) hide show
  1. package/CHANGELOG.md +961 -5
  2. package/README.md +62 -39
  3. package/bin/connecta.mjs +110 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1632 -1509
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +526 -0
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +220 -0
  52. package/dist/connectors/api-connector.js +214 -0
  53. package/dist/connectors/api.d.ts +8 -71
  54. package/dist/connectors/api.js +13 -99
  55. package/dist/connectors/guarded-fetch.d.ts +144 -0
  56. package/dist/connectors/guarded-fetch.js +108 -25
  57. package/dist/connectors/negotiation-cache.js +46 -0
  58. package/dist/connectors/option-shapes.js +27 -0
  59. package/dist/connectors/remote-mcp.d.ts +30 -4
  60. package/dist/connectors/remote-mcp.js +1780 -351
  61. package/dist/connectors/resource-uri.js +335 -0
  62. package/dist/credential-contract.d.ts +10 -0
  63. package/dist/credential-rules.js +1 -3
  64. package/dist/credential-url.js +108 -0
  65. package/dist/credentials.d.ts +6 -1
  66. package/dist/credentials.js +27 -18
  67. package/dist/d1.d.ts +130 -0
  68. package/dist/d1.js +290 -0
  69. package/dist/describe-config.d.ts +165 -0
  70. package/dist/describe-config.js +315 -0
  71. package/dist/described.js +106 -0
  72. package/dist/downstream-input-context.js +119 -0
  73. package/dist/downstream-input.js +323 -0
  74. package/dist/errors.d.ts +97 -20
  75. package/dist/errors.js +158 -80
  76. package/dist/execute.js +453 -305
  77. package/dist/executor-admission.d.ts +1 -0
  78. package/dist/executor-admission.js +11 -15
  79. package/dist/executor-contract.d.ts +20 -0
  80. package/dist/executor-contract.js +48 -0
  81. package/dist/executor-result.js +24 -9
  82. package/dist/executors/quickjs-child.js +3 -4
  83. package/dist/executors/quickjs-runtime.js +147 -59
  84. package/dist/executors/quickjs.js +29 -35
  85. package/dist/favicon.js +1 -2
  86. package/dist/fix-prompt.js +1 -3
  87. package/dist/guest-runtime.js +188 -0
  88. package/dist/guest-types.d.ts +176 -0
  89. package/dist/guest-types.js +1 -0
  90. package/dist/html-security.js +24 -0
  91. package/dist/identity.js +1 -3
  92. package/dist/inbound-credential.js +52 -0
  93. package/dist/index.d.ts +25 -257
  94. package/dist/index.js +65 -478
  95. package/dist/invocation-auth.js +69 -0
  96. package/dist/invocation.js +230 -73
  97. package/dist/json-schema.d.ts +1 -1
  98. package/dist/mcp-client-context.js +35 -0
  99. package/dist/mcp-result.js +11 -7
  100. package/dist/meta-output.js +76 -0
  101. package/dist/meta-tool-names.d.ts +2 -0
  102. package/dist/meta-tool-names.js +3 -0
  103. package/dist/meta-tools.js +285 -209
  104. package/dist/module-contracts.d.ts +8 -23
  105. package/dist/node.d.ts +0 -2
  106. package/dist/node.js +2 -2
  107. package/dist/oauth-callback-outcome.js +1 -3
  108. package/dist/oauth-handoff.js +117 -0
  109. package/dist/oauth-partition.js +17 -0
  110. package/dist/oauth-sealing.js +20 -0
  111. package/dist/operator-record.d.ts +137 -0
  112. package/dist/operator-record.js +420 -0
  113. package/dist/operator-ui/assets.js +31 -0
  114. package/dist/operator-ui/contract.d.ts +72 -0
  115. package/dist/operator-ui/contract.js +1 -0
  116. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  117. package/dist/operator-ui/generated.js +5 -4
  118. package/dist/operator-ui/model.d.ts +12 -87
  119. package/dist/operator-ui/model.js +2 -9
  120. package/dist/operator-ui/setup-commands.js +1 -3
  121. package/dist/operator-ui/view.d.ts +323 -0
  122. package/dist/operator-ui/view.js +62 -189
  123. package/dist/page-styles.js +2 -3
  124. package/dist/program-source.js +14 -2
  125. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  126. package/dist/provider.d.ts +142 -0
  127. package/dist/provider.js +187 -0
  128. package/dist/providers/_shared/google/result-size.js +57 -0
  129. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  130. package/dist/providers/_shared/google/service-account.js +500 -0
  131. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  132. package/dist/providers/_shared/google/workspace.js +518 -0
  133. package/dist/providers/basecamp/index.d.ts +43 -0
  134. package/dist/providers/basecamp/index.js +997 -0
  135. package/dist/providers/basecamp/skill.generated.js +9 -0
  136. package/dist/providers/breeze/index.d.ts +34 -0
  137. package/dist/providers/breeze/index.js +1446 -0
  138. package/dist/providers/breeze/skill.generated.js +9 -0
  139. package/dist/providers/ccb/index.d.ts +103 -0
  140. package/dist/providers/ccb/index.js +1725 -0
  141. package/dist/providers/ccb/skill.generated.js +9 -0
  142. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  143. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  144. package/dist/providers/cloudflare/skill.generated.js +10 -0
  145. package/dist/providers/docs/index.d.ts +54 -0
  146. package/dist/providers/docs/index.js +1237 -0
  147. package/dist/providers/docs/skill.generated.js +9 -0
  148. package/dist/providers/drive/index.d.ts +53 -0
  149. package/dist/providers/drive/index.js +1523 -0
  150. package/dist/providers/drive/skill.generated.js +9 -0
  151. package/dist/providers/forms/index.d.ts +54 -0
  152. package/dist/providers/forms/index.js +1217 -0
  153. package/dist/providers/forms/skill.generated.js +11 -0
  154. package/dist/providers/github/auth.d.ts +32 -0
  155. package/dist/providers/github/auth.js +361 -0
  156. package/dist/providers/github/hosted.js +218 -0
  157. package/dist/providers/github/index.d.ts +13 -0
  158. package/dist/providers/github/index.js +140 -0
  159. package/dist/providers/github/key.js +100 -0
  160. package/dist/providers/github/rest.js +321 -0
  161. package/dist/providers/github/scope.d.ts +29 -0
  162. package/dist/providers/github/scope.js +124 -0
  163. package/dist/providers/github/skill.generated.js +8 -0
  164. package/dist/providers/gmail/index.d.ts +51 -0
  165. package/dist/providers/gmail/index.js +1436 -0
  166. package/dist/providers/gmail/skill.generated.js +9 -0
  167. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  168. package/dist/providers/linear/index.js +352 -0
  169. package/dist/providers/linear/skill.generated.js +8 -0
  170. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  171. package/dist/providers/mixpanel/index.js +416 -0
  172. package/dist/providers/mixpanel/skill.generated.js +9 -0
  173. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  174. package/dist/providers/{notion.js → notion/index.js} +226 -239
  175. package/dist/providers/notion/skill.generated.js +10 -0
  176. package/dist/providers/overflow/index.d.ts +60 -0
  177. package/dist/providers/overflow/index.js +1535 -0
  178. package/dist/providers/overflow/skill.generated.js +8 -0
  179. package/dist/providers/planning-center/index.d.ts +59 -0
  180. package/dist/providers/planning-center/index.js +2199 -0
  181. package/dist/providers/planning-center/skill.generated.js +10 -0
  182. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  183. package/dist/providers/revenuecat/index.js +557 -0
  184. package/dist/providers/revenuecat/skill.generated.js +11 -0
  185. package/dist/providers/sheets/index.d.ts +54 -0
  186. package/dist/providers/sheets/index.js +1237 -0
  187. package/dist/providers/sheets/skill.generated.js +10 -0
  188. package/dist/providers/slides/index.d.ts +53 -0
  189. package/dist/providers/slides/index.js +2645 -0
  190. package/dist/providers/slides/skill.generated.js +9 -0
  191. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  192. package/dist/providers/stripe/index.js +266 -0
  193. package/dist/providers/stripe/skill.generated.js +11 -0
  194. package/dist/providers/tithely/index.d.ts +49 -0
  195. package/dist/providers/tithely/index.js +1003 -0
  196. package/dist/providers/tithely/skill.generated.js +8 -0
  197. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  198. package/dist/providers/vercel/index.js +1038 -0
  199. package/dist/providers/vercel/mcp-ownership.js +212 -0
  200. package/dist/providers/vercel/skill.generated.js +11 -0
  201. package/dist/registry.d.ts +108 -171
  202. package/dist/registry.js +513 -925
  203. package/dist/request-state.js +21 -0
  204. package/dist/resource-read.js +100 -0
  205. package/dist/result-shapes.js +17 -49
  206. package/dist/retired-toolkits.js +1 -1
  207. package/dist/routes/access-tokens.js +3 -6
  208. package/dist/routes/activity-context.js +21 -0
  209. package/dist/routes/activity-disclosure.js +40 -0
  210. package/dist/routes/activity.js +59 -21
  211. package/dist/routes/actor-labels.js +3 -50
  212. package/dist/routes/connect.js +221 -0
  213. package/dist/routes/credentials.js +15 -27
  214. package/dist/routes/mcp.js +356 -130
  215. package/dist/routes/oauth-client-metadata.js +15 -0
  216. package/dist/routes/oauth-management.js +45 -107
  217. package/dist/routes/oauth.js +136 -57
  218. package/dist/routes/operator.js +3 -3
  219. package/dist/routes/shared.d.ts +25 -64
  220. package/dist/routes/shared.js +160 -80
  221. package/dist/routes/skills.js +30 -0
  222. package/dist/routes/ui-config.js +225 -0
  223. package/dist/routes/ui-data.js +40 -35
  224. package/dist/routes/ui.js +66 -56
  225. package/dist/runtime/admission.js +4 -2
  226. package/dist/runtime/call-admission.js +3 -1
  227. package/dist/runtime/connector-scope.js +1 -4
  228. package/dist/runtime/run.js +10 -39
  229. package/dist/runtime/services.js +12 -117
  230. package/dist/runtime/shared-read.js +1 -3
  231. package/dist/runtime/storage.js +8 -1
  232. package/dist/sent-secrets.d.ts +52 -0
  233. package/dist/sent-secrets.js +474 -0
  234. package/dist/server.js +28 -71
  235. package/dist/skills.js +406 -174
  236. package/dist/sqlite.d.ts +48 -0
  237. package/dist/sqlite.js +166 -0
  238. package/dist/storage/keys.js +428 -0
  239. package/dist/storage/memory.js +7 -0
  240. package/dist/storage/sql.d.ts +66 -0
  241. package/dist/storage/sql.js +534 -0
  242. package/dist/timeout.js +4 -2
  243. package/dist/tool-name.js +17 -0
  244. package/dist/tool-safety.d.ts +9 -35
  245. package/dist/tool-safety.js +20 -40
  246. package/dist/types.d.ts +320 -54
  247. package/dist/types.js +0 -1
  248. package/dist/ui.d.ts +7 -19
  249. package/dist/ui.js +76 -90
  250. package/dist/url-safety.js +3 -6
  251. package/dist/usage-guide.js +137 -0
  252. package/dist/validate.d.ts +7 -0
  253. package/dist/validate.js +309 -47
  254. package/dist/version.d.ts +1 -7
  255. package/dist/version.js +3 -7
  256. package/dist/worker.d.ts +11 -0
  257. package/dist/worker.js +434 -0
  258. package/documentation/architecture.md +665 -230
  259. package/documentation/auth.md +962 -458
  260. package/documentation/code-mode.md +501 -312
  261. package/documentation/meta-tools.md +449 -200
  262. package/documentation/operator-tests.md +93 -0
  263. package/documentation/provider-migration-0.29.md +60 -0
  264. package/examples/worker/AGENTS.md +34 -12
  265. package/examples/worker/README.md +286 -255
  266. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  267. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  268. package/examples/worker/src/connecta.config.ts +151 -0
  269. package/examples/worker/src/index.ts +14 -197
  270. package/examples/worker/wrangler.jsonc +15 -35
  271. package/package.json +125 -35
  272. package/templates/node/.dockerignore +1 -2
  273. package/templates/node/.env.example +20 -21
  274. package/templates/node/AGENTS.md +21 -17
  275. package/templates/node/Dockerfile +3 -5
  276. package/templates/node/README.md +133 -58
  277. package/templates/node/docker-compose.yml +11 -12
  278. package/templates/node/package.json +4 -2
  279. package/templates/node/src/connecta.config.ts +119 -0
  280. package/templates/node/src/index.ts +9 -154
  281. package/templates/node/src/provision-token.ts +17 -0
  282. package/AGENTS.md +0 -170
  283. package/dist/artifacts/connector.d.ts +0 -58
  284. package/dist/artifacts/connector.js +0 -730
  285. package/dist/artifacts/document.js +0 -86
  286. package/dist/artifacts/guide.js +0 -140
  287. package/dist/artifacts/html-scan.js +0 -329
  288. package/dist/artifacts/json.js +0 -78
  289. package/dist/artifacts/kv-store.d.ts +0 -20
  290. package/dist/artifacts/kv-store.js +0 -140
  291. package/dist/artifacts/markdown.js +0 -478
  292. package/dist/artifacts/module.d.ts +0 -37
  293. package/dist/artifacts/module.js +0 -66
  294. package/dist/artifacts/operations.d.ts +0 -197
  295. package/dist/artifacts/operations.js +0 -1002
  296. package/dist/artifacts/refresh.d.ts +0 -55
  297. package/dist/artifacts/refresh.js +0 -236
  298. package/dist/artifacts/routes.js +0 -206
  299. package/dist/artifacts/types.d.ts +0 -214
  300. package/dist/artifacts/types.js +0 -22
  301. package/dist/artifacts/validate.d.ts +0 -62
  302. package/dist/artifacts/validate.js +0 -651
  303. package/dist/artifacts.d.ts +0 -6
  304. package/dist/artifacts.js +0 -9
  305. package/dist/auth/bearer.d.ts +0 -11
  306. package/dist/auth/bearer.js +0 -50
  307. package/dist/providers/linear.js +0 -217
  308. package/dist/providers/mixpanel.js +0 -247
  309. package/dist/providers/revenuecat.js +0 -317
  310. package/dist/providers/stripe.js +0 -253
  311. package/dist/providers/vercel.js +0 -1253
  312. package/dist/storage/file.d.ts +0 -15
  313. package/dist/storage/file.js +0 -455
  314. package/ethos.md +0 -93
  315. package/examples/worker/src/cloudflare-kv.ts +0 -43
  316. package/examples/worker/src/d1-activity-row.ts +0 -155
  317. package/examples/worker/src/d1-activity.ts +0 -147
  318. package/examples/worker/src/d1-storage.ts +0 -138
  319. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  320. package/templates/node/src/file-activity.ts +0 -182
@@ -0,0 +1,220 @@
1
+ import type { Connector, ConnectorCallAdmissionPolicy, ConnectorCredentialConfig, ConnectorCredentialValues, ConnectorContext, ConnectorStatus, ConnectorUsageGuide, CredentialTestResult, JsonSchema, ToolAnnotations } from "../types.js";
2
+ export declare function defined<T extends object>(value: T): {
3
+ [K in keyof T]?: Exclude<T[K], undefined>;
4
+ };
5
+ export interface ApiTool {
6
+ name: string;
7
+ /**
8
+ * Required, non-empty. Discovery has nothing else to go on: a nameless
9
+ * capability costs the agent a guess, and a guess costs a wrong call.
10
+ */
11
+ description: string;
12
+ /**
13
+ * A plain JSON Schema object describing the tool input. Optional, but what
14
+ * you supply must be a schema the validator can compile — `api()` refuses to
15
+ * construct otherwise.
16
+ */
17
+ inputSchema?: JsonSchema;
18
+ /** A plain JSON Schema object describing the tool's structured output. */
19
+ outputSchema?: JsonSchema;
20
+ /**
21
+ * Standard MCP-style behavior hints, with an explicit `readOnlyHint`
22
+ * required: `true` declares a read and admits the tool to call_tool and
23
+ * execute_code, `false` declares work that must cross
24
+ * `call_destructive_tool` where a host can ask a human. Connecta never
25
+ * infers the classification from a name, a description, a schema, or the
26
+ * other annotations.
27
+ */
28
+ annotations: ToolAnnotations & {
29
+ readOnlyHint: boolean;
30
+ };
31
+ handler: (args: any, ctx: ApiHandlerContext) => Promise<unknown> | unknown;
32
+ }
33
+ /** How a pre-registered client authenticates at the token endpoint. */
34
+ export type ApiOAuthClientAuthentication = "client_secret_basic" | "client_secret_post" | "none";
35
+ /**
36
+ * Downstream OAuth 2.0 authorization code grant for a hand-written API whose
37
+ * provider issues clients by hand and publishes no discovery metadata. Every
38
+ * endpoint is declared here; nothing is learned from the network.
39
+ */
40
+ export interface ApiOAuthConfig {
41
+ /** Where the operator's browser is sent for consent. Absolute https URL. */
42
+ authorizationEndpoint: string;
43
+ /** Where codes are exchanged and refresh tokens redeemed. Absolute https URL. */
44
+ tokenEndpoint: string;
45
+ /** The client id the provider issued for this deployment. */
46
+ clientId: string;
47
+ /**
48
+ * The client secret, from deployment configuration (an environment variable
49
+ * or Worker secret), never a literal in source. Omit for a public client.
50
+ * It is never written to storage.
51
+ */
52
+ clientSecret?: string;
53
+ /**
54
+ * Token-endpoint client authentication. Defaults to `client_secret_basic`
55
+ * with a secret and `none` without one.
56
+ */
57
+ tokenEndpointAuthMethod?: ApiOAuthClientAuthentication;
58
+ /** Space-separated scopes requested at consent. */
59
+ scope?: string;
60
+ /** PKCE with S256 (default true). Disable only for a server that rejects it. */
61
+ pkce?: boolean;
62
+ /**
63
+ * Extra authorization-request parameters, such as a provider's
64
+ * `access_type`. The grant's own parameters cannot be overridden.
65
+ */
66
+ authorizationParams?: Record<string, string>;
67
+ /**
68
+ * Headers added to every token-endpoint request — the code exchange and
69
+ * each refresh — for a provider whose token endpoint demands its own
70
+ * framing, such as Church Community Builder's
71
+ * `Accept: application/vnd.ccbchurch.v2+json`. They replace the grant's
72
+ * defaults of the same name; `Authorization`, `Content-Type`,
73
+ * `Content-Length`, `Cookie`, and `Host` stay the grant's.
74
+ */
75
+ tokenRequestHeaders?: Record<string, string>;
76
+ /**
77
+ * The exact origins `ctx.oauth.fetch` sends the access token to, such as
78
+ * `"https://api.ccbchurch.com"`. A request anywhere else is refused before
79
+ * it is sent, so an untrusted URL in a response cannot carry the token off.
80
+ */
81
+ apiOrigins: readonly string[];
82
+ }
83
+ /** A handler's use of its connector's OAuth grant, never the grant itself. */
84
+ export interface ApiOAuthAccess {
85
+ /**
86
+ * `fetch` with the calling owner's access token as `Authorization: Bearer`.
87
+ * Only to `apiOrigins`; redirects are returned unfollowed. A 401 earns one
88
+ * coordinated refresh and one replay; a second 401, a refused refresh, or
89
+ * no grant at all throws `auth_required`, and an authorization-server
90
+ * outage throws a retryable `unavailable`.
91
+ */
92
+ fetch(input: string | URL, init?: RequestInit): Promise<Response>;
93
+ }
94
+ /** The context an `api()` handler receives. */
95
+ export interface ApiHandlerContext extends ConnectorContext {
96
+ /** Fetch with final outgoing credential headers and query values registered. */
97
+ fetch: typeof fetch;
98
+ /** Present exactly when the connector declares `oauth`. */
99
+ oauth?: ApiOAuthAccess;
100
+ }
101
+ export interface ApiOptions {
102
+ /** Human-readable display name; the connector id remains the address prefix. */
103
+ title?: string;
104
+ description?: string;
105
+ /** Downstream auth ownership. Defaults to one shared deployment grant. */
106
+ authScope?: "shared" | "personal";
107
+ /**
108
+ * Max inline result size (bytes) for this connector's tools before
109
+ * call_tool truncates and stashes the full text for connecta.result
110
+ * paging. Overrides the deployment's `calls.maxResultBytes`; omit to inherit
111
+ * it. Must be a whole number of bytes >= 1; anything else refuses to
112
+ * construct.
113
+ */
114
+ maxResultBytes?: number;
115
+ /** Optional per-runtime downstream call-admission policy. */
116
+ callAdmission?: ConnectorCallAdmissionPolicy;
117
+ /**
118
+ * Optional agent-facing usage guide served by `skills` as
119
+ * `connector:<id>`. A string is markdown; the structured form adds bounded
120
+ * discovery metadata. See `Connector.usageGuide`.
121
+ */
122
+ usageGuide?: string | ConnectorUsageGuide;
123
+ /** Optional operator-managed credential exposed through ctx.credential and the connection in the operator UI. */
124
+ credential?: ConnectorCredentialConfig;
125
+ /**
126
+ * Optional downstream OAuth, exposed to handlers as `ctx.oauth` and managed
127
+ * like a `remoteMcp()` grant: Connect in the operator UI, or
128
+ * `authorize_connector`. Exclusive with `credential` — one connector, one
129
+ * way to authenticate, so `auth_required` has one recovery.
130
+ */
131
+ oauth?: ApiOAuthConfig;
132
+ /** Optional validation behind the connection's Test action in the operator UI. */
133
+ testCredential?: (value: string, ctx: ConnectorContext) => Promise<CredentialTestResult>;
134
+ /** Optional validation for named multi-field credentials. */
135
+ testCredentials?: (values: ConnectorCredentialValues, ctx: ConnectorContext) => Promise<CredentialTestResult>;
136
+ /**
137
+ * Validate call arguments against each tool's `inputSchema` before invoking
138
+ * the handler (default true). Mismatches fail with a non-retryable
139
+ * `invalid_args` ConnectorCallError instead of reaching the handler. Set
140
+ * false to restore the pre-validation pass-through for deployments relying
141
+ * on loose coercion.
142
+ */
143
+ validateArgs?: boolean;
144
+ tools: ApiTool[];
145
+ }
146
+ /** The closed options api() accepts; see `assertKnownOptions`. */
147
+ export declare const API_OPTIONS: import("../config-schema.js").Field<unknown, unknown, true> & {
148
+ readonly shape: {
149
+ callAdmission: import("../config-schema.js").Field<unknown, unknown, true> & {
150
+ readonly shape: {
151
+ rules: import("../config-schema.js").Field<unknown[], readonly unknown[] | undefined, true>;
152
+ maxPartitions: import("../config-schema.js").Field<unknown, unknown, true>;
153
+ };
154
+ };
155
+ usageGuide: import("../config-schema.js").Field<unknown, unknown, true> & {
156
+ readonly shape: {
157
+ readonly required: import("../config-schema.js").Field<unknown, unknown, true>;
158
+ readonly content: import("../config-schema.js").Field<unknown, unknown, true>;
159
+ readonly summary: import("../config-schema.js").Field<unknown, unknown, true>;
160
+ };
161
+ };
162
+ credential: import("../config-schema.js").Field<unknown, unknown, true> & {
163
+ readonly shape: {
164
+ fields: import("../config-schema.js").Field<unknown[], readonly unknown[] | undefined, true>;
165
+ label: import("../config-schema.js").Field<unknown, unknown, true>;
166
+ description: import("../config-schema.js").Field<unknown, unknown, true>;
167
+ placeholder: import("../config-schema.js").Field<unknown, unknown, true>;
168
+ };
169
+ };
170
+ oauth: import("../config-schema.js").Field<unknown, unknown, true> & {
171
+ readonly shape: {
172
+ authorizationParams: import("../config-schema.js").Field<Record<string, string>, Readonly<Record<string, string>> | undefined, true>;
173
+ tokenRequestHeaders: import("../config-schema.js").Field<Record<string, string>, Readonly<Record<string, string>> | undefined, true>;
174
+ scope: import("../config-schema.js").Field<unknown, unknown, true>;
175
+ authorizationEndpoint: import("../config-schema.js").Field<unknown, unknown, true>;
176
+ tokenEndpoint: import("../config-schema.js").Field<unknown, unknown, true>;
177
+ apiOrigins: import("../config-schema.js").Field<unknown, unknown, true>;
178
+ tokenEndpointAuthMethod: import("../config-schema.js").Field<unknown, unknown, true>;
179
+ pkce: import("../config-schema.js").Field<unknown, unknown, true>;
180
+ clientId: import("../config-schema.js").Field<unknown, unknown, true>;
181
+ clientSecret: import("../config-schema.js").Field<unknown, unknown, true>;
182
+ };
183
+ };
184
+ tools: import("../config-schema.js").Field<unknown[], readonly unknown[] | undefined, true>;
185
+ title: import("../config-schema.js").Field<unknown, unknown, true>;
186
+ description: import("../config-schema.js").Field<unknown, unknown, true>;
187
+ testCredential: import("../config-schema.js").Field<unknown, unknown, true>;
188
+ testCredentials: import("../config-schema.js").Field<unknown, unknown, true>;
189
+ authScope: import("../config-schema.js").Field<unknown, unknown, true>;
190
+ maxResultBytes: import("../config-schema.js").Field<unknown, unknown, true>;
191
+ validateArgs: import("../config-schema.js").Field<unknown, unknown, true>;
192
+ };
193
+ };
194
+ /**
195
+ * The OAuth half of an `api()` connector: the hooks the callback route,
196
+ * operator UI, and `authorize_connector` key on, and the handler accessor.
197
+ * Supplied by `api()` from `src/auth/static-oauth.ts`; defined here so this
198
+ * module never imports the grant machinery.
199
+ */
200
+ export interface ApiOAuthHooks {
201
+ status(ctx: ConnectorContext): Promise<ConnectorStatus>;
202
+ startAuth(ctx: ConnectorContext, opts?: {
203
+ force?: boolean;
204
+ }): Promise<ConnectorStatus>;
205
+ disconnectAuth(ctx: ConnectorContext): Promise<void>;
206
+ verifyState(state: string | null, ctx: ConnectorContext): Promise<boolean>;
207
+ verifyCallbackIssuer(issuer: string | null, ctx: ConnectorContext): Promise<boolean>;
208
+ consumeAuthError(ctx: ConnectorContext): Promise<void>;
209
+ /** Requires callbackParams with a nonempty state, including without PKCE. */
210
+ finishAuth(code: string, ctx: ConnectorContext, callbackParams: URLSearchParams): Promise<void>;
211
+ /** The handler-facing accessor for one call's context. */
212
+ access(ctx: ConnectorContext): ApiOAuthAccess;
213
+ }
214
+ /**
215
+ * A static connector; every tool passes {@link checkToolContract} first.
216
+ *
217
+ * The builder behind `api()`, without the downstream OAuth machinery: credential-free
218
+ * providers import this without paying for a grant they cannot declare.
219
+ */
220
+ export declare function apiConnector(id: string, opts: ApiOptions, oauth?: ApiOAuthHooks): Connector;
@@ -0,0 +1,214 @@
1
+ import { ConnectorCallError, networkErrorCode, unavailableCallError, WithheldTextError } from "../errors.js";
2
+ import { attachFailureFacts, carryFailureFacts, errorLabel } from "../operator-record.js";
3
+ import { compileValidator, validateCatalogToolInput } from "../validate.js";
4
+ import { array, assertKnownOptions, instance, keys, optionsOf, strings } from "../config-schema.js";
5
+ import { describedEndpoint, describedOrigin, describedTools } from "../described.js";
6
+ import { CALL_ADMISSION, CREDENTIAL, USAGE_GUIDE } from "./option-shapes.js";
7
+ import { assertStaticToolNames } from "../tool-name.js";
8
+ import { registerInvocationAuth } from "../invocation-auth.js";
9
+ import { redactCatalog, redactSentSecrets, sentSecretsFetch, trackCredentialReads } from "../sent-secrets.js";
10
+ export function defined(value) {
11
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
12
+ }
13
+ /**
14
+ * Three things a hand-written surface is refused for at construction rather
15
+ * than in production (#340): no description (discovery has nothing to route on
16
+ * and a guess costs a call), no explicit `readOnlyHint` (connecta never infers
17
+ * the safety class, so an unclassified tool is a deployment bug), and an
18
+ * `inputSchema` the validator cannot compile (declaring one is optional;
19
+ * declaring an unenforceable one is not). None of this reaches a proxied
20
+ * catalog — the contract binds the surfaces we write, not the ones we relay.
21
+ */
22
+ function checkToolContract(id, tool) {
23
+ const address = `${id}.${tool.name}`;
24
+ if (typeof tool.description !== "string" || tool.description.trim() === "") {
25
+ throw new Error(`api() tool "${address}" needs a non-empty description — it is what an ` +
26
+ "agent reads to choose the tool (convention: imperative one-liner, " +
27
+ 'e.g. "Send an email via Resend").');
28
+ }
29
+ if (typeof tool.annotations?.readOnlyHint !== "boolean") {
30
+ throw new Error(`api() tool "${address}" needs an explicit annotations.readOnlyHint: ` +
31
+ "true for a read, false for work that must cross " +
32
+ "call_destructive_tool. Connecta never infers the classification " +
33
+ "from a tool name, description, schema, or other annotations.");
34
+ }
35
+ if (typeof tool.handler !== "function") {
36
+ throw new Error(`api() tool "${address}" needs a handler function.`);
37
+ }
38
+ if (tool.inputSchema)
39
+ compileValidator(tool.inputSchema, { address });
40
+ }
41
+ /** The closed options api() accepts; see `assertKnownOptions`. */
42
+ export const API_OPTIONS = optionsOf()({
43
+ ...keys("title", "description", "authScope", "maxResultBytes", "testCredential", "testCredentials", "validateArgs"),
44
+ callAdmission: CALL_ADMISSION,
45
+ usageGuide: USAGE_GUIDE,
46
+ credential: CREDENTIAL,
47
+ oauth: optionsOf()({
48
+ ...keys("authorizationEndpoint", "tokenEndpoint", "clientId", "clientSecret", "tokenEndpointAuthMethod", "scope", "pkce", "apiOrigins"),
49
+ authorizationParams: strings(),
50
+ tokenRequestHeaders: strings(),
51
+ }),
52
+ // A tool's annotations stay open: MCP lets a tool carry hints Connecta
53
+ // does not interpret, and its schemas are JSON Schema, not options. A tool
54
+ // carries its handler, so it is checked in place and kept, never copied.
55
+ tools: array(instance(optionsOf()(keys("name", "description", "inputSchema", "outputSchema", "annotations", "handler")))),
56
+ });
57
+ /**
58
+ * How an `api()` connector authenticates, as names and public endpoints. The
59
+ * client id and secret, extra parameter values, and token-request header
60
+ * values never leave the options.
61
+ */
62
+ function describedApiAuth(opts) {
63
+ const oauth = opts.oauth;
64
+ if (!oauth)
65
+ return { mode: opts.credential ? "credential" : "none" };
66
+ const confidential = typeof oauth.clientSecret === "string" && oauth.clientSecret !== "";
67
+ const authorizationEndpoint = describedEndpoint(oauth.authorizationEndpoint);
68
+ const tokenEndpoint = describedEndpoint(oauth.tokenEndpoint);
69
+ const apiOrigins = Array.isArray(oauth.apiOrigins)
70
+ ? oauth.apiOrigins.flatMap((origin) => describedOrigin(origin) ?? [])
71
+ : [];
72
+ return {
73
+ mode: "oauth",
74
+ ...(authorizationEndpoint ? { authorizationEndpoint } : {}),
75
+ ...(tokenEndpoint ? { tokenEndpoint } : {}),
76
+ apiOrigins,
77
+ tokenEndpointAuthMethod: oauth.tokenEndpointAuthMethod ?? (confidential ? "client_secret_basic" : "none"),
78
+ confidentialClient: confidential,
79
+ pkce: oauth.pkce ?? true,
80
+ ...(oauth.scope !== undefined ? { scope: oauth.scope } : {}),
81
+ ...(oauth.authorizationParams ? { authorizationParamNames: Object.keys(oauth.authorizationParams) } : {}),
82
+ ...(oauth.tokenRequestHeaders ? { tokenRequestHeaderNames: Object.keys(oauth.tokenRequestHeaders) } : {}),
83
+ };
84
+ }
85
+ /**
86
+ * A static connector; every tool passes {@link checkToolContract} first.
87
+ *
88
+ * The builder behind `api()`, without the downstream OAuth machinery: credential-free
89
+ * providers import this without paying for a grant they cannot declare.
90
+ */
91
+ export function apiConnector(id, opts, oauth) {
92
+ opts = assertKnownOptions(opts, `api(${JSON.stringify(id)})`, API_OPTIONS);
93
+ assertStaticToolNames(opts.tools, `api(${JSON.stringify(id)}).tools`);
94
+ if (opts.oauth !== undefined && oauth === undefined) {
95
+ throw new Error(`api() connector "${id}" declares oauth but was built without its grant; construct it with api().`);
96
+ }
97
+ const names = new Set();
98
+ for (const tool of opts.tools) {
99
+ if (names.has(tool.name)) {
100
+ throw new Error(`api() tool "${id}.${tool.name}" is declared more than once; discovery and dispatch must use one definition.`);
101
+ }
102
+ names.add(tool.name);
103
+ checkToolContract(id, tool);
104
+ }
105
+ const defs = opts.tools.map((t) => ({
106
+ name: t.name,
107
+ description: t.description,
108
+ ...defined({
109
+ inputSchema: t.inputSchema,
110
+ outputSchema: t.outputSchema,
111
+ }),
112
+ annotations: t.annotations,
113
+ }));
114
+ const byName = new Map(opts.tools.map((t) => [t.name, t]));
115
+ const validateArgs = opts.validateArgs ?? true;
116
+ const auth = describedApiAuth(opts);
117
+ const connector = {
118
+ id,
119
+ ...defined({ title: opts.title }),
120
+ kind: "api",
121
+ describe: () => ({ source: { kind: "api" }, auth, tools: describedTools(defs) }),
122
+ ...defined({
123
+ description: opts.description,
124
+ authScope: opts.authScope,
125
+ maxResultBytes: opts.maxResultBytes,
126
+ callAdmission: opts.callAdmission,
127
+ usageGuide: opts.usageGuide,
128
+ credential: opts.credential,
129
+ testCredential: opts.testCredential,
130
+ testCredentials: opts.testCredentials,
131
+ }),
132
+ ...(oauth
133
+ ? {
134
+ status: oauth.status,
135
+ startAuth: oauth.startAuth,
136
+ disconnectAuth: oauth.disconnectAuth,
137
+ verifyState: oauth.verifyState,
138
+ verifyCallbackIssuer: oauth.verifyCallbackIssuer,
139
+ consumeAuthError: oauth.consumeAuthError,
140
+ finishAuth: oauth.finishAuth,
141
+ }
142
+ : {}),
143
+ staticTools: defs,
144
+ async listTools(ctx) {
145
+ return redactCatalog(ctx, defs);
146
+ },
147
+ async callTool(name, args, ctx) {
148
+ trackCredentialReads(ctx);
149
+ const tool = byName.get(name);
150
+ if (!tool) {
151
+ throw new Error(`Unknown tool "${name}" on connector "${id}"`);
152
+ }
153
+ const input = args ?? {};
154
+ if (validateArgs && tool.inputSchema) {
155
+ const invalid = validateCatalogToolInput(tool.inputSchema, input, {
156
+ address: `${id}.${name}`,
157
+ logger: ctx.logger,
158
+ // Always: the schema compiled at construction, so anything that
159
+ // fails here is a schema that cannot be enforced, and a surface we
160
+ // wrote ourselves does not get to admit unvalidated input quietly.
161
+ failClosed: true,
162
+ }, { connector: id, tool });
163
+ if (invalid)
164
+ throw invalid;
165
+ }
166
+ // `await` (not a bare promise return) so a handler that throws before
167
+ // its first await never sits handler-less for the thenable-adoption
168
+ // microtask — workerd and vitest both report that gap as an unhandled
169
+ // rejection even though the caller catches the failure.
170
+ // Preserve the registry's context identity: private caller/provenance
171
+ // markers belong to it, as does the grant its accessor reaches.
172
+ const handlerCtx = Object.assign(ctx, {
173
+ fetch: sentSecretsFetch(ctx),
174
+ ...(oauth ? { oauth: oauth.access(ctx) } : {}),
175
+ });
176
+ try {
177
+ return redactSentSecrets(ctx, await tool.handler(input, handlerCtx));
178
+ }
179
+ catch (error) {
180
+ // The request's own abort reason, by identity, is the caller's.
181
+ if (ctx.signal?.aborted === true && error === ctx.signal.reason)
182
+ throw error;
183
+ // A handler owns its destinations. ctx.baseUrl is Connecta's inbound
184
+ // URL, so it must never masquerade as the failed downstream host.
185
+ if (error instanceof ConnectorCallError)
186
+ throw redactSentSecrets(ctx, error);
187
+ if (!networkErrorCode(error)) {
188
+ // Anything else is a runtime's, parser's, or stream's account of
189
+ // what the handler read (a JSON parser quotes the reply it choked
190
+ // on; a body stream rejects with whatever the runtime says), so it
191
+ // is told in connecta's words, classified as the original would
192
+ // have been, and rebuilt without its cause or nested errors. A
193
+ // handler that means the agent to read its words throws a
194
+ // ConnectorCallError. No host is named: connecta does not know
195
+ // which destination the handler read.
196
+ const kind = errorLabel(error);
197
+ throw attachFailureFacts(carryFailureFacts(error, new WithheldTextError(`Connector "${id}" tool "${name}" handler failed` +
198
+ `${kind ? ` (${kind})` : ""}. Its text is withheld because it ` +
199
+ "can quote what the downstream sent.", error)), { step: "handler" });
200
+ }
201
+ throw unavailableCallError(error, undefined, undefined, ctx.signal);
202
+ }
203
+ },
204
+ };
205
+ if (oauth)
206
+ registerInvocationAuth(connector, async (ctx) => {
207
+ // The built-in static OAuth status only reads the stored grant. It never
208
+ // refreshes, invokes a handler, or sends a downstream request.
209
+ if ((await oauth.status(ctx)).state === "auth_required") {
210
+ throw new ConnectorCallError("downstream_oauth_required", `Connector "${id}" requires authorization. Call authorize_connector.`);
211
+ }
212
+ });
213
+ return connector;
214
+ }
@@ -1,72 +1,9 @@
1
- import type { Connector, ConnectorCallAdmissionPolicy, ConnectorCredentialConfig, ConnectorCredentialValues, ConnectorContext, ConnectorUsageGuide, CredentialTestResult, JsonSchema, ToolAnnotations } from "../types.js";
2
- export declare function defined<T extends object>(value: T): {
3
- [K in keyof T]?: Exclude<T[K], undefined>;
4
- };
5
- export interface ApiTool {
6
- name: string;
7
- /**
8
- * Required, non-empty. Discovery has nothing else to go on: a nameless
9
- * capability costs the agent a guess, and a guess costs a wrong call.
10
- */
11
- description: string;
12
- /**
13
- * A plain JSON Schema object describing the tool input. Optional, but what
14
- * you supply must be a schema the validator can compile — `api()` refuses to
15
- * construct otherwise.
16
- */
17
- inputSchema?: JsonSchema;
18
- /** A plain JSON Schema object describing the tool's structured output. */
19
- outputSchema?: JsonSchema;
20
- /**
21
- * Standard MCP-style behavior hints, with an explicit `readOnlyHint`
22
- * required: `true` declares a read and admits the tool to call_tool and
23
- * execute_code, `false` declares work that must cross
24
- * `call_destructive_tool` where a host can ask a human. Connecta never
25
- * infers the classification from a name, a description, a schema, or the
26
- * other annotations.
27
- */
28
- annotations: ToolAnnotations & {
29
- readOnlyHint: boolean;
30
- };
31
- handler: (args: any, ctx: ConnectorContext) => Promise<unknown> | unknown;
32
- }
33
- export interface ApiOptions {
34
- /** Human-readable display name; the connector id remains the address prefix. */
35
- title?: string;
36
- description?: string;
37
- /** Downstream auth ownership. Defaults to one shared deployment grant. */
38
- authScope?: "shared" | "personal";
39
- /**
40
- * Max inline result size (bytes) for this connector's tools before
41
- * call_tool truncates and stashes the full text for get_result
42
- * paging. Overrides the deployment's `calls.maxResultBytes`; omit to inherit
43
- * it. Must be a whole number of bytes >= 1; anything else warns at startup
44
- * and is ignored.
45
- */
46
- maxResultBytes?: number;
47
- /** Optional per-runtime downstream call-admission policy. */
48
- callAdmission?: ConnectorCallAdmissionPolicy;
49
- /**
50
- * Optional agent-facing usage guide served by `skills` as
51
- * `connector:<id>`. A string is markdown; the structured form adds bounded
52
- * discovery metadata. See `Connector.usageGuide`.
53
- */
54
- usageGuide?: string | ConnectorUsageGuide;
55
- /** Optional operator-managed credential exposed through ctx.credential and the connection in the operator UI. */
56
- credential?: ConnectorCredentialConfig;
57
- /** Optional validation behind the connection's Test action in the operator UI. */
58
- testCredential?: (value: string, ctx: ConnectorContext) => Promise<CredentialTestResult>;
59
- /** Optional validation for named multi-field credentials. */
60
- testCredentials?: (values: ConnectorCredentialValues, ctx: ConnectorContext) => Promise<CredentialTestResult>;
61
- /**
62
- * Validate call arguments against each tool's `inputSchema` before invoking
63
- * the handler (default true). Mismatches fail with a non-retryable
64
- * `invalid_args` ConnectorCallError instead of reaching the handler. Set
65
- * false to restore the pre-validation pass-through for deployments relying
66
- * on loose coercion.
67
- */
68
- validateArgs?: boolean;
69
- tools: ApiTool[];
70
- }
71
- /** A static connector; every tool passes {@link checkToolContract} first. */
1
+ import type { Connector } from "../types.js";
2
+ import type { ApiOptions } from "./api-connector.js";
3
+ export type { ApiHandlerContext, ApiOAuthAccess, ApiOAuthClientAuthentication, ApiOAuthConfig, ApiOptions, ApiTool, } from "./api-connector.js";
4
+ /**
5
+ * A hand-written connector: static tools, each with a handler. With `oauth`,
6
+ * its handlers reach the API through the calling owner's downstream grant
7
+ * (`ctx.oauth`), managed exactly like a `remoteMcp()` grant.
8
+ */
72
9
  export declare function api(id: string, opts: ApiOptions): Connector;
@@ -1,104 +1,18 @@
1
- import { ConnectorCallError, networkErrorCode, unavailableCallError, } from "../errors.js";
2
- import { compileValidator, validateToolInput } from "../validate.js";
3
- export function defined(value) {
4
- return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
5
- }
1
+ import { staticOAuth } from "../auth/static-oauth.js";
2
+ import { assertKnownOptions } from "../config-schema.js";
3
+ import { API_OPTIONS, apiConnector } from "./api-connector.js";
6
4
  /**
7
- * Three things a hand-written surface is refused for at construction rather
8
- * than in production (#340): no description (discovery has nothing to route on
9
- * and a guess costs a call), no explicit `readOnlyHint` (connecta never infers
10
- * the safety class, so an unclassified tool is a deployment bug), and an
11
- * `inputSchema` the validator cannot compile (declaring one is optional;
12
- * declaring an unenforceable one is not). None of this reaches a proxied
13
- * catalog — the contract binds the surfaces we write, not the ones we relay.
5
+ * A hand-written connector: static tools, each with a handler. With `oauth`,
6
+ * its handlers reach the API through the calling owner's downstream grant
7
+ * (`ctx.oauth`), managed exactly like a `remoteMcp()` grant.
14
8
  */
15
- function checkToolContract(id, tool) {
16
- const address = `${id}.${tool.name}`;
17
- if (typeof tool.description !== "string" || tool.description.trim() === "") {
18
- throw new Error(`api() tool "${address}" needs a non-empty description — it is what an ` +
19
- "agent reads to choose the tool (convention: imperative one-liner, " +
20
- 'e.g. "Send an email via Resend").');
21
- }
22
- if (typeof tool.annotations?.readOnlyHint !== "boolean") {
23
- throw new Error(`api() tool "${address}" needs an explicit annotations.readOnlyHint: ` +
24
- "true for a read, false for work that must cross " +
25
- "call_destructive_tool. Connecta never infers the classification " +
26
- "from a tool name, description, schema, or other annotations.");
27
- }
28
- if (tool.inputSchema)
29
- compileValidator(tool.inputSchema, { address });
30
- }
31
- /** A static connector; every tool passes {@link checkToolContract} first. */
32
9
  export function api(id, opts) {
33
- const names = new Set();
34
- for (const tool of opts.tools) {
35
- if (names.has(tool.name)) {
36
- throw new Error(`api() tool "${id}.${tool.name}" is declared more than once; discovery and dispatch must use one definition.`);
37
- }
38
- names.add(tool.name);
39
- checkToolContract(id, tool);
10
+ // Read the options once, as plain data, before choosing a grant from them.
11
+ opts = assertKnownOptions(opts, `api(${JSON.stringify(id)})`, API_OPTIONS);
12
+ if (opts.oauth !== undefined && opts.credential !== undefined) {
13
+ throw new Error(`api() connector "${id}" declares both oauth and credential. Declare ` +
14
+ "one: auth_required must name a single recovery. A provider offering " +
15
+ "both lets the deployment choose which one to pass.");
40
16
  }
41
- const defs = opts.tools.map((t) => ({
42
- name: t.name,
43
- description: t.description,
44
- ...defined({
45
- inputSchema: t.inputSchema,
46
- outputSchema: t.outputSchema,
47
- }),
48
- annotations: t.annotations,
49
- }));
50
- const byName = new Map(opts.tools.map((t) => [t.name, t]));
51
- const validateArgs = opts.validateArgs ?? true;
52
- return {
53
- id,
54
- ...defined({ title: opts.title }),
55
- kind: "api",
56
- ...defined({
57
- description: opts.description,
58
- authScope: opts.authScope,
59
- maxResultBytes: opts.maxResultBytes,
60
- callAdmission: opts.callAdmission,
61
- usageGuide: opts.usageGuide,
62
- credential: opts.credential,
63
- testCredential: opts.testCredential,
64
- testCredentials: opts.testCredentials,
65
- }),
66
- staticTools: defs,
67
- async listTools() {
68
- return defs;
69
- },
70
- async callTool(name, args, ctx) {
71
- const tool = byName.get(name);
72
- if (!tool) {
73
- throw new Error(`Unknown tool "${name}" on connector "${id}"`);
74
- }
75
- const input = args ?? {};
76
- if (validateArgs && tool.inputSchema) {
77
- const invalid = validateToolInput(tool.inputSchema, input, {
78
- address: `${id}.${name}`,
79
- logger: ctx.logger,
80
- // Always: the schema compiled at construction, so anything that
81
- // fails here is a schema that cannot be enforced, and a surface we
82
- // wrote ourselves does not get to admit unvalidated input quietly.
83
- failClosed: true,
84
- });
85
- if (invalid)
86
- throw invalid;
87
- }
88
- // `await` (not a bare promise return) so a handler that throws before
89
- // its first await never sits handler-less for the thenable-adoption
90
- // microtask — workerd and vitest both report that gap as an unhandled
91
- // rejection even though the caller catches the failure.
92
- try {
93
- return await tool.handler(input, ctx);
94
- }
95
- catch (error) {
96
- // A handler owns its destinations. ctx.baseUrl is Connecta's inbound
97
- // URL, so it must never masquerade as the failed downstream host.
98
- if (error instanceof ConnectorCallError || !networkErrorCode(error))
99
- throw error;
100
- throw unavailableCallError(error);
101
- }
102
- },
103
- };
17
+ return apiConnector(id, opts, opts.oauth !== undefined ? staticOAuth(id, opts.oauth) : undefined);
104
18
  }