@executor-js/sdk 0.0.1-beta.6 → 0.0.2

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 (96) hide show
  1. package/README.md +125 -107
  2. package/dist/blob.d.ts +48 -0
  3. package/dist/blob.d.ts.map +1 -0
  4. package/dist/blob.test.d.ts +2 -0
  5. package/dist/blob.test.d.ts.map +1 -0
  6. package/dist/chunk-6LMMN2GP.js +4396 -0
  7. package/dist/chunk-6LMMN2GP.js.map +1 -0
  8. package/dist/config.d.ts +14 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/connections.d.ts +107 -0
  11. package/dist/connections.d.ts.map +1 -0
  12. package/dist/connections.test.d.ts +2 -0
  13. package/dist/connections.test.d.ts.map +1 -0
  14. package/dist/core-schema.d.ts +372 -0
  15. package/dist/core-schema.d.ts.map +1 -0
  16. package/dist/core.js +273 -57
  17. package/dist/core.js.map +1 -1
  18. package/dist/elicitation.d.ts +18 -34
  19. package/dist/elicitation.d.ts.map +1 -1
  20. package/dist/error-handling.test.d.ts +2 -0
  21. package/dist/error-handling.test.d.ts.map +1 -0
  22. package/dist/errors.d.ts +95 -24
  23. package/dist/errors.d.ts.map +1 -1
  24. package/dist/executor.d.ts +107 -48
  25. package/dist/executor.d.ts.map +1 -1
  26. package/dist/executor.test.d.ts +2 -0
  27. package/dist/executor.test.d.ts.map +1 -0
  28. package/dist/ids.d.ts +6 -4
  29. package/dist/ids.d.ts.map +1 -1
  30. package/dist/index.d.ts +22 -16
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +80 -308
  33. package/dist/index.js.map +1 -1
  34. package/dist/oauth-discovery.d.ts +138 -0
  35. package/dist/oauth-discovery.d.ts.map +1 -0
  36. package/dist/oauth-discovery.test.d.ts +2 -0
  37. package/dist/oauth-discovery.test.d.ts.map +1 -0
  38. package/dist/oauth-helpers.d.ts +89 -0
  39. package/dist/oauth-helpers.d.ts.map +1 -0
  40. package/dist/oauth-helpers.test.d.ts +2 -0
  41. package/dist/oauth-helpers.test.d.ts.map +1 -0
  42. package/dist/oauth-popup-types.d.ts +14 -0
  43. package/dist/oauth-popup-types.d.ts.map +1 -0
  44. package/dist/oauth-service.d.ts +33 -0
  45. package/dist/oauth-service.d.ts.map +1 -0
  46. package/dist/oauth.d.ts +275 -0
  47. package/dist/oauth.d.ts.map +1 -0
  48. package/dist/plugin.d.ts +261 -27
  49. package/dist/plugin.d.ts.map +1 -1
  50. package/dist/policies.d.ts +56 -64
  51. package/dist/policies.d.ts.map +1 -1
  52. package/dist/policies.test.d.ts +2 -0
  53. package/dist/policies.test.d.ts.map +1 -0
  54. package/dist/promise-executor.d.ts +26 -128
  55. package/dist/promise-executor.d.ts.map +1 -1
  56. package/dist/promise.d.ts +12 -6
  57. package/dist/promise.d.ts.map +1 -1
  58. package/dist/promise.test.d.ts +2 -0
  59. package/dist/promise.test.d.ts.map +1 -0
  60. package/dist/schema-types.d.ts +6 -5
  61. package/dist/schema-types.d.ts.map +1 -1
  62. package/dist/scope.d.ts +5 -15
  63. package/dist/scope.d.ts.map +1 -1
  64. package/dist/scoped-adapter.d.ts +13 -0
  65. package/dist/scoped-adapter.d.ts.map +1 -0
  66. package/dist/scoped-adapter.test.d.ts +2 -0
  67. package/dist/scoped-adapter.test.d.ts.map +1 -0
  68. package/dist/secret-backed-value.d.ts +27 -0
  69. package/dist/secret-backed-value.d.ts.map +1 -0
  70. package/dist/secrets.d.ts +52 -106
  71. package/dist/secrets.d.ts.map +1 -1
  72. package/dist/testing.d.ts +5 -3
  73. package/dist/testing.d.ts.map +1 -1
  74. package/dist/types.d.ts +84 -0
  75. package/dist/types.d.ts.map +1 -0
  76. package/package.json +7 -4
  77. package/dist/chunk-CJY7TT3J.js +0 -1384
  78. package/dist/chunk-CJY7TT3J.js.map +0 -1
  79. package/dist/in-memory/policy-engine.d.ts +0 -10
  80. package/dist/in-memory/policy-engine.d.ts.map +0 -1
  81. package/dist/in-memory/secret-store.d.ts +0 -16
  82. package/dist/in-memory/secret-store.d.ts.map +0 -1
  83. package/dist/in-memory/tool-registry.d.ts +0 -35
  84. package/dist/in-memory/tool-registry.d.ts.map +0 -1
  85. package/dist/index.test.d.ts +0 -2
  86. package/dist/index.test.d.ts.map +0 -1
  87. package/dist/plugin-kv.d.ts +0 -48
  88. package/dist/plugin-kv.d.ts.map +0 -1
  89. package/dist/plugins/in-memory-tools.d.ts +0 -42
  90. package/dist/plugins/in-memory-tools.d.ts.map +0 -1
  91. package/dist/runtime-tools.d.ts +0 -41
  92. package/dist/runtime-tools.d.ts.map +0 -1
  93. package/dist/sources.d.ts +0 -124
  94. package/dist/sources.d.ts.map +0 -1
  95. package/dist/tools.d.ts +0 -219
  96. package/dist/tools.d.ts.map +0 -1
@@ -0,0 +1,4396 @@
1
+ // src/blob.ts
2
+ import { Effect } from "effect";
3
+ import { StorageError } from "@executor-js/storage-core";
4
+ var assertScope = (scope, scopes) => scopes.includes(scope) ? Effect.void : Effect.fail(
5
+ new StorageError({
6
+ message: `Blob write targets scope "${scope}" which is not in the executor's scope stack [${scopes.join(", ")}].`,
7
+ cause: void 0
8
+ })
9
+ );
10
+ var nsFor = (scope, pluginId) => `${scope}/${pluginId}`;
11
+ var pluginBlobStore = (store, scopes, pluginId) => ({
12
+ get: (key) => Effect.gen(function* () {
13
+ const namespaces = scopes.map((s) => nsFor(s, pluginId));
14
+ const hits = yield* store.getMany(namespaces, key);
15
+ if (hits.size === 0) return null;
16
+ for (const ns of namespaces) {
17
+ const v = hits.get(ns);
18
+ if (v !== void 0) return v;
19
+ }
20
+ return null;
21
+ }),
22
+ put: (key, value, options) => Effect.flatMap(
23
+ assertScope(options.scope, scopes),
24
+ () => store.put(nsFor(options.scope, pluginId), key, value)
25
+ ),
26
+ delete: (key, options) => Effect.flatMap(
27
+ assertScope(options.scope, scopes),
28
+ () => store.delete(nsFor(options.scope, pluginId), key)
29
+ ),
30
+ has: (key) => store.getMany(
31
+ scopes.map((s) => nsFor(s, pluginId)),
32
+ key
33
+ ).pipe(Effect.map((hits) => hits.size > 0))
34
+ });
35
+ var makeInMemoryBlobStore = () => {
36
+ const store = /* @__PURE__ */ new Map();
37
+ const k = (ns, key) => `${ns}::${key}`;
38
+ return {
39
+ get: (ns, key) => Effect.sync(() => store.get(k(ns, key)) ?? null),
40
+ getMany: (namespaces, key) => Effect.sync(() => {
41
+ const hits = /* @__PURE__ */ new Map();
42
+ for (const ns of namespaces) {
43
+ const v = store.get(k(ns, key));
44
+ if (v !== void 0) hits.set(ns, v);
45
+ }
46
+ return hits;
47
+ }),
48
+ put: (ns, key, value) => Effect.sync(() => {
49
+ store.set(k(ns, key), value);
50
+ }),
51
+ delete: (ns, key) => Effect.sync(() => {
52
+ store.delete(k(ns, key));
53
+ }),
54
+ has: (ns, key) => Effect.sync(() => store.has(k(ns, key)))
55
+ };
56
+ };
57
+
58
+ // src/ids.ts
59
+ import { Schema } from "effect";
60
+ var ScopeId = Schema.String.pipe(Schema.brand("ScopeId"));
61
+ var ToolId = Schema.String.pipe(Schema.brand("ToolId"));
62
+ var SecretId = Schema.String.pipe(Schema.brand("SecretId"));
63
+ var PolicyId = Schema.String.pipe(Schema.brand("PolicyId"));
64
+ var ConnectionId = Schema.String.pipe(Schema.brand("ConnectionId"));
65
+
66
+ // src/connections.ts
67
+ import { Data, Schema as Schema2 } from "effect";
68
+ var ConnectionProviderState = Schema2.Record(Schema2.String, Schema2.Unknown);
69
+ var ConnectionRef = class extends Schema2.Class("ConnectionRef")({
70
+ id: ConnectionId,
71
+ scopeId: ScopeId,
72
+ provider: Schema2.String,
73
+ identityLabel: Schema2.NullOr(Schema2.String),
74
+ accessTokenSecretId: SecretId,
75
+ refreshTokenSecretId: Schema2.NullOr(SecretId),
76
+ /** Epoch ms when the access token expires; null if not declared. */
77
+ expiresAt: Schema2.NullOr(Schema2.Number),
78
+ /** OAuth-style scope string as returned by the token endpoint. Named
79
+ * `oauthScope` to avoid collision with the executor scope id. */
80
+ oauthScope: Schema2.NullOr(Schema2.String),
81
+ providerState: Schema2.NullOr(ConnectionProviderState),
82
+ createdAt: Schema2.Date,
83
+ updatedAt: Schema2.Date
84
+ }) {
85
+ };
86
+ var TokenMaterial = class extends Schema2.Class("TokenMaterial")({
87
+ /** Target secret id. Plugins typically derive this from the source id
88
+ * + a stable suffix (e.g. `${sourceId}.access_token`). */
89
+ secretId: SecretId,
90
+ /** Display name stamped on the secret row. Only visible to code — the
91
+ * Connections UI hides connection-owned secrets. */
92
+ name: Schema2.String,
93
+ value: Schema2.String
94
+ }) {
95
+ };
96
+ var CreateConnectionInput = class extends Schema2.Class(
97
+ "CreateConnectionInput"
98
+ )({
99
+ id: ConnectionId,
100
+ /** Executor scope id that will own this connection + its backing
101
+ * secrets. This is the sharing boundary: a user scope is personal,
102
+ * an org/workspace scope is shared with descendants. */
103
+ scope: ScopeId,
104
+ provider: Schema2.String,
105
+ identityLabel: Schema2.NullOr(Schema2.String),
106
+ accessToken: TokenMaterial,
107
+ refreshToken: Schema2.NullOr(TokenMaterial),
108
+ expiresAt: Schema2.NullOr(Schema2.Number),
109
+ /** OAuth-style scope string. Distinct from the executor scope above. */
110
+ oauthScope: Schema2.NullOr(Schema2.String),
111
+ providerState: Schema2.NullOr(ConnectionProviderState)
112
+ }) {
113
+ };
114
+ var ConnectionRefreshError = class extends Data.TaggedError(
115
+ "ConnectionRefreshError"
116
+ ) {
117
+ };
118
+ var UpdateConnectionTokensInput = class extends Schema2.Class(
119
+ "UpdateConnectionTokensInput"
120
+ )({
121
+ id: ConnectionId,
122
+ accessToken: Schema2.String,
123
+ refreshToken: Schema2.optional(Schema2.NullOr(Schema2.String)),
124
+ expiresAt: Schema2.optional(Schema2.NullOr(Schema2.Number)),
125
+ oauthScope: Schema2.optional(Schema2.NullOr(Schema2.String)),
126
+ providerState: Schema2.optional(Schema2.NullOr(ConnectionProviderState)),
127
+ identityLabel: Schema2.optional(Schema2.NullOr(Schema2.String))
128
+ }) {
129
+ };
130
+
131
+ // src/core-schema.ts
132
+ var coreSchema = {
133
+ source: {
134
+ fields: {
135
+ id: { type: "string", required: true },
136
+ scope_id: { type: "string", required: true, index: true },
137
+ plugin_id: { type: "string", required: true, index: true },
138
+ kind: { type: "string", required: true },
139
+ name: { type: "string", required: true },
140
+ url: { type: "string", required: false },
141
+ can_remove: {
142
+ type: "boolean",
143
+ required: true,
144
+ defaultValue: true
145
+ },
146
+ can_refresh: {
147
+ type: "boolean",
148
+ required: true,
149
+ defaultValue: false
150
+ },
151
+ can_edit: {
152
+ type: "boolean",
153
+ required: true,
154
+ defaultValue: false
155
+ },
156
+ created_at: { type: "date", required: true },
157
+ updated_at: { type: "date", required: true }
158
+ }
159
+ },
160
+ tool: {
161
+ fields: {
162
+ id: { type: "string", required: true },
163
+ scope_id: { type: "string", required: true, index: true },
164
+ source_id: { type: "string", required: true, index: true },
165
+ plugin_id: { type: "string", required: true, index: true },
166
+ name: { type: "string", required: true },
167
+ description: { type: "string", required: true },
168
+ input_schema: { type: "json", required: false },
169
+ output_schema: { type: "json", required: false },
170
+ // NOTE: tool annotations (requiresApproval, approvalDescription,
171
+ // mayElicit) are NOT stored on this row. They're derived at read
172
+ // time from plugin-owned data via `plugin.resolveAnnotations`,
173
+ // because the source of truth already lives in each plugin's own
174
+ // storage (openapi's OperationBinding, etc.) and duplicating it
175
+ // here would just mean bulk-rewriting rows every time the
176
+ // derivation logic changes.
177
+ created_at: { type: "date", required: true },
178
+ updated_at: { type: "date", required: true }
179
+ }
180
+ },
181
+ // Shared JSON-schema `$defs` stored once per source. Tool input/output
182
+ // schemas carry `$ref: "#/$defs/X"` pointers; the read path attaches
183
+ // matching defs under `$defs` before returning. Keyed by synthetic id
184
+ // `${source_id}.${name}` so cleanup on source removal is a single
185
+ // deleteMany by source_id.
186
+ definition: {
187
+ fields: {
188
+ id: { type: "string", required: true },
189
+ scope_id: { type: "string", required: true, index: true },
190
+ source_id: { type: "string", required: true, index: true },
191
+ plugin_id: { type: "string", required: true, index: true },
192
+ name: { type: "string", required: true },
193
+ schema: { type: "json", required: true },
194
+ created_at: { type: "date", required: true }
195
+ }
196
+ },
197
+ // Secrets live in the core surface as metadata (id, display name,
198
+ // provider key). Actual values never touch this table — they live in
199
+ // the secret provider (keychain, 1password, file, etc.) and are
200
+ // resolved on demand via `ctx.secrets.get(id)`.
201
+ //
202
+ // `owned_by_connection_id` ties the row to a connection. Connection-
203
+ // owned secrets are plumbing, not user-facing values: `ctx.secrets.list`
204
+ // filters them out (the user sees the Connection instead), and
205
+ // `ctx.secrets.remove` refuses to delete them (Connection.remove is
206
+ // the single owner of the lifecycle). The FK is nullable so existing
207
+ // "bare" secrets (API keys entered by the user, pre-connection OAuth
208
+ // rows during migration) remain visible and removable unchanged.
209
+ secret: {
210
+ fields: {
211
+ id: { type: "string", required: true },
212
+ scope_id: { type: "string", required: true, index: true },
213
+ name: { type: "string", required: true },
214
+ provider: { type: "string", required: true, index: true },
215
+ owned_by_connection_id: {
216
+ type: "string",
217
+ required: false,
218
+ index: true
219
+ },
220
+ created_at: { type: "date", required: true }
221
+ }
222
+ },
223
+ // Connections — sign-in state for one identity against one remote
224
+ // provider. A Connection owns one or more `secret` rows (access +
225
+ // refresh tokens, etc.) via `secret.owned_by_connection_id`, and the
226
+ // SDK exposes `ctx.connections.accessToken(id)` which transparently
227
+ // refreshes the backing secrets when they're near expiry. Plugins
228
+ // contribute refresh behavior via `plugin.connectionProviders[].refresh`
229
+ // keyed by `provider`, same pattern as `secretProviders`.
230
+ //
231
+ // `provider_state` is plugin-owned opaque JSON — token endpoint URL,
232
+ // scopes, issuer, auth-server metadata — whatever the provider's
233
+ // refresh handler needs to re-hit the token endpoint. It's NOT
234
+ // sensitive (all secrets go through the provider-backed secret rows);
235
+ // it's just enough metadata to drive a refresh without re-running
236
+ // discovery.
237
+ connection: {
238
+ fields: {
239
+ id: { type: "string", required: true },
240
+ scope_id: { type: "string", required: true, index: true },
241
+ /** Routing key into `plugin.connectionProviders`. Typical shape
242
+ * is `${pluginId}:${kind}` (e.g. `openapi:oauth2`, `mcp:oauth2`,
243
+ * `google-discovery:google`). Mirrors `secret.provider`. */
244
+ provider: { type: "string", required: true, index: true },
245
+ /** Display label shown in the Connections UI. Usually the account
246
+ * email / handle / org name the user signed in as. */
247
+ identity_label: { type: "string", required: false },
248
+ /** Stable id of the access-token secret. Always present. */
249
+ access_token_secret_id: { type: "string", required: true },
250
+ /** Stable id of the refresh-token secret. Null for flows that
251
+ * don't mint a refresh token (client_credentials, etc.). */
252
+ refresh_token_secret_id: { type: "string", required: false },
253
+ /** Epoch ms when the access token expires. Null if the provider
254
+ * didn't declare an expiry. Used as the refresh trigger. Stored as
255
+ * `bigint` because `Date.now()` overflows int32. */
256
+ expires_at: { type: "number", required: false, bigint: true },
257
+ /** Scope string as returned by the token endpoint. */
258
+ scope: { type: "string", required: false },
259
+ /** Opaque plugin-owned JSON — token endpoint URL, scopes list,
260
+ * discovery hints, etc. Never sensitive. */
261
+ provider_state: { type: "json", required: false },
262
+ created_at: { type: "date", required: true },
263
+ updated_at: { type: "date", required: true }
264
+ }
265
+ },
266
+ // Pending OAuth authorization rows shared by every OAuth-capable plugin.
267
+ // Rows are short-lived and deleted after completion/cancel; the resulting
268
+ // `connection` row is the durable sign-in state.
269
+ oauth2_session: {
270
+ fields: {
271
+ id: { type: "string", required: true },
272
+ scope_id: { type: "string", required: true, index: true },
273
+ plugin_id: { type: "string", required: true, index: true },
274
+ strategy: { type: "string", required: true },
275
+ connection_id: { type: "string", required: true, index: true },
276
+ token_scope: { type: "string", required: true },
277
+ redirect_url: { type: "string", required: true },
278
+ payload: { type: "json", required: true },
279
+ expires_at: { type: "number", required: true, bigint: true },
280
+ created_at: { type: "date", required: true }
281
+ }
282
+ },
283
+ // User-authored overrides for tool permissions. Each row is one rule:
284
+ // a glob-ish pattern + an action (approve / require_approval / block).
285
+ // Resolution walks the scope stack innermost-first, then `position`
286
+ // ascending within each scope; first match wins. Plugin-derived
287
+ // annotations from `resolveAnnotations` apply only when no rule
288
+ // matches.
289
+ //
290
+ // Pattern grammar (v1):
291
+ // - `*` every tool id (universal)
292
+ // - `vercel.dns.create` exact tool id
293
+ // - `vercel.dns.*` any tool whose id starts with `vercel.dns.`
294
+ // - `vercel.*` plugin-wide
295
+ // No `**`, no brace expansion, no leading-`*` prefixes (`*foo`, `*.foo`).
296
+ tool_policy: {
297
+ fields: {
298
+ id: { type: "string", required: true },
299
+ scope_id: { type: "string", required: true, index: true },
300
+ pattern: { type: "string", required: true },
301
+ /** "approve" | "require_approval" | "block". */
302
+ action: { type: "string", required: true },
303
+ /** Fractional-indexing key (Jira lexorank style). Lower lex order =
304
+ * higher precedence. New rules default to a key generated above
305
+ * the current minimum. Strings instead of numbers so we can
306
+ * always lengthen the key to insert between two adjacent rows
307
+ * without precision loss; see `fractional-indexing` in
308
+ * `policies.ts`. */
309
+ position: { type: "string", required: true, index: true },
310
+ created_at: { type: "date", required: true },
311
+ updated_at: { type: "date", required: true }
312
+ }
313
+ }
314
+ };
315
+ var TOOL_POLICY_ACTIONS = [
316
+ "approve",
317
+ "require_approval",
318
+ "block"
319
+ ];
320
+ var isToolPolicyAction = (value) => typeof value === "string" && TOOL_POLICY_ACTIONS.includes(value);
321
+
322
+ // src/elicitation.ts
323
+ import { Schema as Schema3 } from "effect";
324
+ var FormElicitation = class extends Schema3.TaggedClass()("FormElicitation", {
325
+ message: Schema3.String,
326
+ /** JSON Schema describing the fields to collect */
327
+ requestedSchema: Schema3.Record(Schema3.String, Schema3.Unknown)
328
+ }) {
329
+ };
330
+ var UrlElicitation = class extends Schema3.TaggedClass()("UrlElicitation", {
331
+ message: Schema3.String,
332
+ url: Schema3.String,
333
+ /** Unique ID so the host can correlate the callback */
334
+ elicitationId: Schema3.String
335
+ }) {
336
+ };
337
+ var ElicitationAction = Schema3.Literals(["accept", "decline", "cancel"]);
338
+ var ElicitationResponse = class extends Schema3.Class("ElicitationResponse")({
339
+ action: ElicitationAction,
340
+ /** Present when action is "accept" — the data the user provided */
341
+ content: Schema3.optional(Schema3.Record(Schema3.String, Schema3.Unknown))
342
+ }) {
343
+ };
344
+ var ElicitationDeclinedError = class extends Schema3.TaggedErrorClass()(
345
+ "ElicitationDeclinedError",
346
+ {
347
+ toolId: ToolId,
348
+ action: Schema3.Literals(["decline", "cancel"])
349
+ }
350
+ ) {
351
+ };
352
+
353
+ // src/errors.ts
354
+ import { Data as Data2, Schema as Schema4 } from "effect";
355
+ var ToolNotFoundError = class extends Schema4.TaggedErrorClass()(
356
+ "ToolNotFoundError",
357
+ { toolId: ToolId }
358
+ ) {
359
+ };
360
+ var ToolInvocationError = class extends Data2.TaggedError("ToolInvocationError") {
361
+ };
362
+ var PluginNotLoadedError = class extends Schema4.TaggedErrorClass()(
363
+ "PluginNotLoadedError",
364
+ {
365
+ pluginId: Schema4.String,
366
+ toolId: ToolId
367
+ }
368
+ ) {
369
+ };
370
+ var NoHandlerError = class extends Schema4.TaggedErrorClass()(
371
+ "NoHandlerError",
372
+ {
373
+ toolId: ToolId,
374
+ pluginId: Schema4.String
375
+ }
376
+ ) {
377
+ };
378
+ var ToolBlockedError = class extends Schema4.TaggedErrorClass()(
379
+ "ToolBlockedError",
380
+ {
381
+ toolId: ToolId,
382
+ pattern: Schema4.String
383
+ }
384
+ ) {
385
+ };
386
+ var SourceNotFoundError = class extends Schema4.TaggedErrorClass()(
387
+ "SourceNotFoundError",
388
+ { sourceId: Schema4.String }
389
+ ) {
390
+ };
391
+ var SourceRemovalNotAllowedError = class extends Schema4.TaggedErrorClass()(
392
+ "SourceRemovalNotAllowedError",
393
+ { sourceId: Schema4.String }
394
+ ) {
395
+ };
396
+ var SecretNotFoundError = class extends Schema4.TaggedErrorClass()(
397
+ "SecretNotFoundError",
398
+ { secretId: SecretId }
399
+ ) {
400
+ };
401
+ var SecretResolutionError = class extends Schema4.TaggedErrorClass()(
402
+ "SecretResolutionError",
403
+ {
404
+ secretId: SecretId,
405
+ message: Schema4.String
406
+ }
407
+ ) {
408
+ };
409
+ var SecretOwnedByConnectionError = class extends Schema4.TaggedErrorClass()(
410
+ "SecretOwnedByConnectionError",
411
+ {
412
+ secretId: SecretId,
413
+ connectionId: ConnectionId
414
+ }
415
+ ) {
416
+ };
417
+ var ConnectionNotFoundError = class extends Schema4.TaggedErrorClass()(
418
+ "ConnectionNotFoundError",
419
+ { connectionId: ConnectionId }
420
+ ) {
421
+ };
422
+ var ConnectionProviderNotRegisteredError = class extends Schema4.TaggedErrorClass()(
423
+ "ConnectionProviderNotRegisteredError",
424
+ {
425
+ provider: Schema4.String,
426
+ connectionId: Schema4.optional(ConnectionId)
427
+ }
428
+ ) {
429
+ };
430
+ var ConnectionRefreshNotSupportedError = class extends Schema4.TaggedErrorClass()(
431
+ "ConnectionRefreshNotSupportedError",
432
+ {
433
+ connectionId: ConnectionId,
434
+ provider: Schema4.String
435
+ }
436
+ ) {
437
+ };
438
+ var ConnectionReauthRequiredError = class extends Schema4.TaggedErrorClass()(
439
+ "ConnectionReauthRequiredError",
440
+ {
441
+ connectionId: ConnectionId,
442
+ provider: Schema4.String,
443
+ message: Schema4.String
444
+ }
445
+ ) {
446
+ };
447
+
448
+ // src/secrets.ts
449
+ import { Schema as Schema5 } from "effect";
450
+ var SecretRef = class extends Schema5.Class("SecretRef")({
451
+ id: SecretId,
452
+ scopeId: ScopeId,
453
+ /** Human-readable label (e.g. "Cloudflare API Token") */
454
+ name: Schema5.String,
455
+ /** Which provider holds the value */
456
+ provider: Schema5.String,
457
+ createdAt: Schema5.Date
458
+ }) {
459
+ };
460
+ var SetSecretInput = class extends Schema5.Class(
461
+ "SetSecretInput"
462
+ )({
463
+ id: SecretId,
464
+ /** Scope id to own this secret. Must be one of the executor's
465
+ * configured scopes. */
466
+ scope: ScopeId,
467
+ /** Display name shown in secret-list UI. */
468
+ name: Schema5.String,
469
+ /** The secret value itself — never persisted outside the provider. */
470
+ value: Schema5.String,
471
+ /** Optional provider routing. If unset the executor picks the first
472
+ * writable provider in registration order. */
473
+ provider: Schema5.optional(Schema5.String)
474
+ }) {
475
+ };
476
+
477
+ // src/oauth.ts
478
+ import { Effect as Effect5, Schema as Schema6 } from "effect";
479
+ var OAuthDynamicDcrStrategy = Schema6.Struct({
480
+ kind: Schema6.Literal("dynamic-dcr"),
481
+ /** Scopes to request. Defaults to whatever `scopes_supported`
482
+ * advertises; caller can narrow or extend. */
483
+ scopes: Schema6.optional(Schema6.Array(Schema6.String))
484
+ });
485
+ var OAuthAuthorizationCodeStrategy = Schema6.Struct({
486
+ kind: Schema6.Literal("authorization-code"),
487
+ authorizationEndpoint: Schema6.String,
488
+ tokenEndpoint: Schema6.String,
489
+ /** Expected authorization-server issuer for ID token validation. Some
490
+ * providers use a token endpoint host that differs from issuer, or a
491
+ * path-scoped issuer such as Okta custom authorization servers. */
492
+ issuerUrl: Schema6.optional(Schema6.NullOr(Schema6.String)),
493
+ /** Secret id holding the `client_id`. Using a secret row rather than
494
+ * an inline string so the value lives at the scope where the caller
495
+ * configured it and shadowing behaves consistently. */
496
+ clientIdSecretId: Schema6.String,
497
+ /** Secret id for `client_secret`. Null for public clients using
498
+ * PKCE without a confidential secret. */
499
+ clientSecretSecretId: Schema6.NullOr(Schema6.String),
500
+ scopes: Schema6.Array(Schema6.String),
501
+ /** Separator between scopes. RFC 6749 says space; some providers
502
+ * (GitHub classic) use comma. */
503
+ scopeSeparator: Schema6.optional(Schema6.String),
504
+ /** Provider-specific params injected at authorization URL build time
505
+ * (Google's `access_type=offline`, `prompt=consent`, ...). */
506
+ extraAuthorizationParams: Schema6.optional(
507
+ Schema6.Record(Schema6.String, Schema6.String)
508
+ ),
509
+ /** `"body"` (default) sends client creds in the form body; `"basic"`
510
+ * uses HTTP Basic auth. Stripe-style servers require basic. */
511
+ clientAuth: Schema6.optional(Schema6.Literals(["body", "basic"]))
512
+ });
513
+ var OAuthClientCredentialsStrategy = Schema6.Struct({
514
+ kind: Schema6.Literal("client-credentials"),
515
+ tokenEndpoint: Schema6.String,
516
+ clientIdSecretId: Schema6.String,
517
+ clientSecretSecretId: Schema6.String,
518
+ scopes: Schema6.optional(Schema6.Array(Schema6.String)),
519
+ scopeSeparator: Schema6.optional(Schema6.String),
520
+ clientAuth: Schema6.optional(Schema6.Literals(["body", "basic"]))
521
+ });
522
+ var OAuthStrategy = Schema6.Union([
523
+ OAuthDynamicDcrStrategy,
524
+ OAuthAuthorizationCodeStrategy,
525
+ OAuthClientCredentialsStrategy
526
+ ]);
527
+ var OAuthProviderState = Schema6.Union([
528
+ Schema6.Struct({
529
+ kind: Schema6.Literal("dynamic-dcr"),
530
+ tokenEndpoint: Schema6.String,
531
+ issuerUrl: Schema6.optional(Schema6.NullOr(Schema6.String)),
532
+ authorizationServerUrl: Schema6.optional(Schema6.NullOr(Schema6.String)),
533
+ authorizationServerMetadataUrl: Schema6.NullOr(Schema6.String),
534
+ idTokenSigningAlgValuesSupported: Schema6.optional(
535
+ Schema6.Array(Schema6.String)
536
+ ),
537
+ /** DCR-minted client_id. Embedded inline (not a secret) — DCR
538
+ * clients are public-ish by design; the secret part (if the AS
539
+ * issued one) is a separate secret row. */
540
+ clientId: Schema6.String,
541
+ clientSecretSecretId: Schema6.NullOr(Schema6.String),
542
+ clientAuth: Schema6.Literals(["body", "basic"]),
543
+ scopes: Schema6.Array(Schema6.String).pipe(Schema6.withDecodingDefaultType(Effect5.succeed([]))),
544
+ scopeSeparator: Schema6.optional(Schema6.String),
545
+ scope: Schema6.NullOr(Schema6.String)
546
+ }),
547
+ Schema6.Struct({
548
+ kind: Schema6.Literal("authorization-code"),
549
+ tokenEndpoint: Schema6.String,
550
+ issuerUrl: Schema6.optional(Schema6.NullOr(Schema6.String)),
551
+ clientIdSecretId: Schema6.String,
552
+ clientSecretSecretId: Schema6.NullOr(Schema6.String),
553
+ clientAuth: Schema6.Literals(["body", "basic"]),
554
+ scopes: Schema6.Array(Schema6.String).pipe(Schema6.withDecodingDefaultType(Effect5.succeed([]))),
555
+ scopeSeparator: Schema6.optional(Schema6.String),
556
+ scope: Schema6.NullOr(Schema6.String)
557
+ }),
558
+ Schema6.Struct({
559
+ kind: Schema6.Literal("client-credentials"),
560
+ tokenEndpoint: Schema6.String,
561
+ clientIdSecretId: Schema6.String,
562
+ clientSecretSecretId: Schema6.String,
563
+ scopes: Schema6.Array(Schema6.String),
564
+ scopeSeparator: Schema6.optional(Schema6.String),
565
+ clientAuth: Schema6.Literals(["body", "basic"]),
566
+ scope: Schema6.NullOr(Schema6.String)
567
+ })
568
+ ]);
569
+ var OAUTH2_PROVIDER_KEY = "oauth2";
570
+ var OAuthProbeError = class extends Schema6.TaggedErrorClass()(
571
+ "OAuthProbeError",
572
+ {
573
+ message: Schema6.String
574
+ }
575
+ ) {
576
+ static annotations = { httpApiStatus: 400 };
577
+ };
578
+ var OAuthStartError = class extends Schema6.TaggedErrorClass()(
579
+ "OAuthStartError",
580
+ {
581
+ message: Schema6.String
582
+ }
583
+ ) {
584
+ static annotations = { httpApiStatus: 400 };
585
+ };
586
+ var OAuthCompleteError = class extends Schema6.TaggedErrorClass()(
587
+ "OAuthCompleteError",
588
+ {
589
+ message: Schema6.String,
590
+ /** RFC 6749 §5.2 error code, when the token endpoint returned one.
591
+ * Callers distinguish terminal failures (`invalid_grant` ⇒
592
+ * re-auth required) from transient ones. */
593
+ code: Schema6.optional(Schema6.String)
594
+ }
595
+ ) {
596
+ static annotations = { httpApiStatus: 400 };
597
+ };
598
+ var OAuthSessionNotFoundError = class extends Schema6.TaggedErrorClass()(
599
+ "OAuthSessionNotFoundError",
600
+ {
601
+ sessionId: Schema6.String
602
+ }
603
+ ) {
604
+ static annotations = { httpApiStatus: 404 };
605
+ };
606
+ var OAUTH2_SESSION_TTL_MS = 15 * 60 * 1e3;
607
+
608
+ // src/oauth-helpers.ts
609
+ import { Data as Data3, Effect as Effect6 } from "effect";
610
+ import * as oauth from "oauth4webapi";
611
+ var OAuth2Error = class extends Data3.TaggedError("OAuth2Error") {
612
+ };
613
+ var OAUTH2_REFRESH_SKEW_MS = 6e4;
614
+ var OAUTH2_DEFAULT_TIMEOUT_MS = 2e4;
615
+ var createPkceCodeVerifier = () => oauth.generateRandomCodeVerifier();
616
+ var createPkceCodeChallenge = (verifier) => oauth.calculatePKCECodeChallenge(verifier);
617
+ var buildAuthorizationUrl = (input) => {
618
+ const url = new URL(input.authorizationUrl);
619
+ const separator = input.scopeSeparator ?? " ";
620
+ url.searchParams.set("client_id", input.clientId);
621
+ url.searchParams.set("redirect_uri", input.redirectUrl);
622
+ url.searchParams.set("response_type", "code");
623
+ url.searchParams.set("scope", input.scopes.join(separator));
624
+ url.searchParams.set("state", input.state);
625
+ url.searchParams.set("code_challenge_method", "S256");
626
+ url.searchParams.set("code_challenge", input.codeChallenge);
627
+ if (input.extraParams) {
628
+ for (const [k, v] of Object.entries(input.extraParams)) {
629
+ url.searchParams.set(k, v);
630
+ }
631
+ }
632
+ return url.toString();
633
+ };
634
+ var toOAuth2Error = (cause) => {
635
+ if (typeof cause === "object" && cause !== null) {
636
+ const c = cause;
637
+ const code = typeof c.error === "string" ? c.error : void 0;
638
+ const description = typeof c.error_description === "string" ? c.error_description : typeof c.message === "string" ? c.message : void 0;
639
+ return new OAuth2Error({
640
+ message: `OAuth token exchange failed: ${description ?? code ?? "unknown error"}`,
641
+ error: code,
642
+ cause
643
+ });
644
+ }
645
+ return new OAuth2Error({
646
+ message: `OAuth token exchange failed: ${String(cause)}`,
647
+ cause
648
+ });
649
+ };
650
+ var asFromTokenUrl = (tokenUrl) => {
651
+ const url = new URL(tokenUrl);
652
+ return {
653
+ issuer: `${url.protocol}//${url.host}`,
654
+ token_endpoint: tokenUrl
655
+ };
656
+ };
657
+ var asFromTokenUrlAndIssuer = (tokenUrl, issuerUrl, options = {}) => {
658
+ const as = asFromTokenUrl(tokenUrl);
659
+ const withIssuer = issuerUrl ? { ...as, issuer: issuerUrl } : as;
660
+ return options.idTokenSigningAlgValuesSupported ? {
661
+ ...withIssuer,
662
+ id_token_signing_alg_values_supported: [
663
+ ...options.idTokenSigningAlgValuesSupported
664
+ ]
665
+ } : withIssuer;
666
+ };
667
+ var isLoopbackHttpUrl = (value) => {
668
+ try {
669
+ const url = new URL(value);
670
+ if (url.protocol !== "http:") return false;
671
+ const hostname = url.hostname.toLowerCase();
672
+ return hostname === "localhost" || hostname === "0.0.0.0" || hostname === "::1" || hostname === "[::1]" || hostname.startsWith("127.");
673
+ } catch {
674
+ return false;
675
+ }
676
+ };
677
+ var oauth4webapiRequestOptions = (targetUrl, timeoutMs) => {
678
+ const options = {
679
+ signal: AbortSignal.timeout(timeoutMs ?? OAUTH2_DEFAULT_TIMEOUT_MS)
680
+ };
681
+ if (isLoopbackHttpUrl(targetUrl)) {
682
+ options[oauth.allowInsecureRequests] = true;
683
+ }
684
+ return options;
685
+ };
686
+ var pickClientAuth = (clientSecret, method) => {
687
+ if (!clientSecret) return oauth.None();
688
+ return method === "basic" ? oauth.ClientSecretBasic(clientSecret) : oauth.ClientSecretPost(clientSecret);
689
+ };
690
+ var tokenResponseFrom = (r) => ({
691
+ access_token: r.access_token,
692
+ token_type: r.token_type,
693
+ refresh_token: r.refresh_token,
694
+ expires_in: typeof r.expires_in === "number" ? r.expires_in : void 0,
695
+ scope: r.scope
696
+ });
697
+ var isUnexpectedIdTokenAlg = (cause) => cause instanceof Error && cause.message.includes('unexpected JWT "alg" header parameter');
698
+ var looseTokenResponseFrom = (body) => {
699
+ if (typeof body !== "object" || body === null) {
700
+ throw new Error("token endpoint response body is not an object");
701
+ }
702
+ const record = body;
703
+ if (typeof record.access_token !== "string" || !record.access_token) {
704
+ throw new Error('token endpoint response is missing "access_token"');
705
+ }
706
+ const expiresIn = typeof record.expires_in === "number" ? record.expires_in : typeof record.expires_in === "string" ? Number.parseFloat(record.expires_in) : void 0;
707
+ if (expiresIn !== void 0 && !Number.isFinite(expiresIn)) {
708
+ throw new Error('token endpoint response has invalid "expires_in"');
709
+ }
710
+ return {
711
+ access_token: record.access_token,
712
+ token_type: typeof record.token_type === "string" ? record.token_type : void 0,
713
+ refresh_token: typeof record.refresh_token === "string" ? record.refresh_token : void 0,
714
+ expires_in: expiresIn,
715
+ scope: typeof record.scope === "string" ? record.scope : void 0
716
+ };
717
+ };
718
+ var processTokenEndpointResponse = async (as, client, response) => {
719
+ const fallback = response.clone();
720
+ try {
721
+ return tokenResponseFrom(
722
+ await oauth.processGenericTokenEndpointResponse(as, client, response)
723
+ );
724
+ } catch (cause) {
725
+ if (!isUnexpectedIdTokenAlg(cause)) throw cause;
726
+ return looseTokenResponseFrom(await fallback.json());
727
+ }
728
+ };
729
+ var exchangeAuthorizationCode = (input) => Effect6.tryPromise({
730
+ try: async () => {
731
+ const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, {
732
+ idTokenSigningAlgValuesSupported: input.idTokenSigningAlgValuesSupported
733
+ });
734
+ const client = { client_id: input.clientId };
735
+ const clientAuth = pickClientAuth(
736
+ input.clientSecret,
737
+ input.clientAuth ?? "body"
738
+ );
739
+ const params = new URLSearchParams({
740
+ code: input.code,
741
+ redirect_uri: input.redirectUrl,
742
+ code_verifier: input.codeVerifier
743
+ });
744
+ const response = await oauth.genericTokenEndpointRequest(
745
+ as,
746
+ client,
747
+ clientAuth,
748
+ "authorization_code",
749
+ params,
750
+ oauth4webapiRequestOptions(input.tokenUrl, input.timeoutMs)
751
+ );
752
+ return processTokenEndpointResponse(as, client, response);
753
+ },
754
+ catch: toOAuth2Error
755
+ });
756
+ var exchangeClientCredentials = (input) => Effect6.tryPromise({
757
+ try: async () => {
758
+ const as = asFromTokenUrl(input.tokenUrl);
759
+ const client = { client_id: input.clientId };
760
+ const clientAuth = pickClientAuth(
761
+ input.clientSecret,
762
+ input.clientAuth ?? "body"
763
+ );
764
+ const params = new URLSearchParams();
765
+ if (input.scopes && input.scopes.length > 0) {
766
+ params.set("scope", input.scopes.join(input.scopeSeparator ?? " "));
767
+ }
768
+ const response = await oauth.clientCredentialsGrantRequest(
769
+ as,
770
+ client,
771
+ clientAuth,
772
+ params,
773
+ oauth4webapiRequestOptions(input.tokenUrl, input.timeoutMs)
774
+ );
775
+ const result = await oauth.processClientCredentialsResponse(
776
+ as,
777
+ client,
778
+ response
779
+ );
780
+ return tokenResponseFrom(result);
781
+ },
782
+ catch: toOAuth2Error
783
+ });
784
+ var refreshAccessToken = (input) => Effect6.tryPromise({
785
+ try: async () => {
786
+ const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, {
787
+ idTokenSigningAlgValuesSupported: input.idTokenSigningAlgValuesSupported
788
+ });
789
+ const client = { client_id: input.clientId };
790
+ const clientAuth = pickClientAuth(
791
+ input.clientSecret,
792
+ input.clientAuth ?? "body"
793
+ );
794
+ const additionalParameters = input.scopes && input.scopes.length > 0 ? new URLSearchParams({
795
+ scope: input.scopes.join(input.scopeSeparator ?? " ")
796
+ }) : void 0;
797
+ const response = await oauth.refreshTokenGrantRequest(
798
+ as,
799
+ client,
800
+ clientAuth,
801
+ input.refreshToken,
802
+ {
803
+ ...oauth4webapiRequestOptions(input.tokenUrl, input.timeoutMs),
804
+ additionalParameters
805
+ }
806
+ );
807
+ const fallback = response.clone();
808
+ try {
809
+ const result = await oauth.processRefreshTokenResponse(
810
+ as,
811
+ client,
812
+ response
813
+ );
814
+ return tokenResponseFrom(result);
815
+ } catch (cause) {
816
+ if (!isUnexpectedIdTokenAlg(cause)) throw cause;
817
+ return looseTokenResponseFrom(await fallback.json());
818
+ }
819
+ },
820
+ catch: toOAuth2Error
821
+ });
822
+ var shouldRefreshToken = (input) => {
823
+ if (input.expiresAt === null) return false;
824
+ const now = input.now ?? Date.now();
825
+ const skew = input.skewMs ?? OAUTH2_REFRESH_SKEW_MS;
826
+ return input.expiresAt <= now + skew;
827
+ };
828
+
829
+ // src/oauth-discovery.ts
830
+ import { Data as Data4, Effect as Effect7, Result, Schema as Schema7 } from "effect";
831
+ import * as oauth2 from "oauth4webapi";
832
+ var OAuthDiscoveryError = class extends Data4.TaggedError(
833
+ "OAuthDiscoveryError"
834
+ ) {
835
+ };
836
+ var discoveryError = (message, options = {}) => new OAuthDiscoveryError({
837
+ message,
838
+ status: options.status,
839
+ cause: options.cause
840
+ });
841
+ var StringArray = Schema7.Array(Schema7.String);
842
+ var OAuthProtectedResourceMetadataSchema = Schema7.Struct({
843
+ resource: Schema7.optional(Schema7.String),
844
+ authorization_servers: Schema7.optional(StringArray),
845
+ scopes_supported: Schema7.optional(StringArray),
846
+ bearer_methods_supported: Schema7.optional(StringArray),
847
+ resource_documentation: Schema7.optional(Schema7.String)
848
+ }).annotate({ identifier: "OAuthProtectedResourceMetadata" });
849
+ var OAuthAuthorizationServerMetadataSchema = Schema7.Struct({
850
+ issuer: Schema7.String,
851
+ authorization_endpoint: Schema7.String,
852
+ token_endpoint: Schema7.String,
853
+ registration_endpoint: Schema7.optional(Schema7.String),
854
+ scopes_supported: Schema7.optional(StringArray),
855
+ response_types_supported: Schema7.optional(StringArray),
856
+ grant_types_supported: Schema7.optional(StringArray),
857
+ code_challenge_methods_supported: Schema7.optional(StringArray),
858
+ token_endpoint_auth_methods_supported: Schema7.optional(StringArray),
859
+ revocation_endpoint: Schema7.optional(Schema7.String),
860
+ introspection_endpoint: Schema7.optional(Schema7.String),
861
+ userinfo_endpoint: Schema7.optional(Schema7.String),
862
+ id_token_signing_alg_values_supported: Schema7.optional(StringArray)
863
+ }).annotate({ identifier: "OAuthAuthorizationServerMetadata" });
864
+ var OAuthClientInformationSchema = Schema7.Struct({
865
+ client_id: Schema7.String,
866
+ client_secret: Schema7.optional(Schema7.String),
867
+ client_id_issued_at: Schema7.optional(Schema7.Number),
868
+ client_secret_expires_at: Schema7.optional(Schema7.Number),
869
+ registration_access_token: Schema7.optional(Schema7.String),
870
+ registration_client_uri: Schema7.optional(Schema7.String),
871
+ token_endpoint_auth_method: Schema7.optional(Schema7.String),
872
+ grant_types: Schema7.optional(StringArray),
873
+ response_types: Schema7.optional(StringArray),
874
+ redirect_uris: Schema7.optional(StringArray),
875
+ client_name: Schema7.optional(Schema7.String),
876
+ scope: Schema7.optional(Schema7.String)
877
+ }).annotate({ identifier: "OAuthClientInformation" });
878
+ var decodeResourceMetadata = Schema7.decodeUnknownEffect(
879
+ OAuthProtectedResourceMetadataSchema
880
+ );
881
+ var decodeAuthServerMetadata = Schema7.decodeUnknownEffect(
882
+ OAuthAuthorizationServerMetadataSchema
883
+ );
884
+ var decodeClientInformation = Schema7.decodeUnknownEffect(
885
+ OAuthClientInformationSchema
886
+ );
887
+ var MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
888
+ var isLoopbackHttpUrl2 = (value) => {
889
+ try {
890
+ const url = new URL(value);
891
+ if (url.protocol !== "http:") return false;
892
+ const hostname = url.hostname.toLowerCase();
893
+ return hostname === "localhost" || hostname === "0.0.0.0" || hostname === "::1" || hostname === "[::1]" || hostname.startsWith("127.");
894
+ } catch {
895
+ return false;
896
+ }
897
+ };
898
+ var oauth4webapiOptions = (options, targetUrl) => {
899
+ const out = {};
900
+ if (options.fetch) out[customFetch] = options.fetch;
901
+ if (targetUrl && isLoopbackHttpUrl2(targetUrl)) {
902
+ out[oauth2.allowInsecureRequests] = true;
903
+ }
904
+ const signal = AbortSignal.timeout(options.timeoutMs ?? OAUTH2_DEFAULT_TIMEOUT_MS);
905
+ out.signal = signal;
906
+ if (options.mcpProtocolVersion) {
907
+ out.headers = new Headers({
908
+ [MCP_PROTOCOL_VERSION_HEADER]: options.mcpProtocolVersion
909
+ });
910
+ }
911
+ return out;
912
+ };
913
+ var customFetch = /* @__PURE__ */ Symbol.for("oauth4webapi.customFetch");
914
+ var buildResourceMetadataUrls = (resourceUrl) => {
915
+ const url = new URL(resourceUrl);
916
+ const origin = `${url.protocol}//${url.host}`;
917
+ const path = url.pathname.replace(/\/+$/, "");
918
+ const urls = [];
919
+ if (path && path !== "/") {
920
+ urls.push(`${origin}/.well-known/oauth-protected-resource${path}`);
921
+ }
922
+ urls.push(`${origin}/.well-known/oauth-protected-resource`);
923
+ return urls;
924
+ };
925
+ var withResourceQueryParams = (url, queryParams) => {
926
+ if (!queryParams || Object.keys(queryParams).length === 0) return url;
927
+ const parsed = new URL(url);
928
+ for (const [key, value] of Object.entries(queryParams)) {
929
+ parsed.searchParams.set(key, value);
930
+ }
931
+ return parsed.toString();
932
+ };
933
+ var discoverProtectedResourceMetadata = (resourceUrl, options = {}) => Effect7.gen(function* () {
934
+ const fetchImpl = options.fetch ?? globalThis.fetch;
935
+ const timeoutMs = options.timeoutMs ?? OAUTH2_DEFAULT_TIMEOUT_MS;
936
+ for (const url of buildResourceMetadataUrls(resourceUrl)) {
937
+ const result = yield* Effect7.tryPromise({
938
+ try: async () => {
939
+ const requestUrl = withResourceQueryParams(url, options.resourceQueryParams);
940
+ const headers = {
941
+ ...options.resourceHeaders,
942
+ accept: "application/json"
943
+ };
944
+ if (options.mcpProtocolVersion) {
945
+ headers[MCP_PROTOCOL_VERSION_HEADER] = options.mcpProtocolVersion;
946
+ }
947
+ const response = await fetchImpl(requestUrl, {
948
+ method: "GET",
949
+ headers,
950
+ signal: AbortSignal.timeout(timeoutMs)
951
+ });
952
+ if (response.status === 404 || response.status === 405) return "skip";
953
+ if (response.status < 200 || response.status >= 300) {
954
+ return { status: response.status };
955
+ }
956
+ const text = await response.text();
957
+ if (text.length === 0) return "skip";
958
+ return { status: response.status, body: JSON.parse(text) };
959
+ },
960
+ catch: (cause) => discoveryError(
961
+ `Failed to fetch ${url}: ${cause instanceof Error ? cause.message : String(cause)}`,
962
+ { cause }
963
+ )
964
+ });
965
+ if (result === "skip") continue;
966
+ if (!("body" in result)) {
967
+ return yield* Effect7.fail(
968
+ discoveryError(
969
+ `Protected resource metadata returned status ${result.status}`,
970
+ { status: result.status }
971
+ )
972
+ );
973
+ }
974
+ const metadata = yield* decodeResourceMetadata(result.body).pipe(
975
+ Effect7.mapError(
976
+ (err) => new OAuthDiscoveryError({
977
+ message: `Protected resource metadata is malformed: ${Schema7.isSchemaError(err) ? err.message : String(err)}`,
978
+ cause: err
979
+ })
980
+ )
981
+ );
982
+ return { metadataUrl: url, metadata };
983
+ }
984
+ return null;
985
+ });
986
+ var wellKnownUrlFor = (issuerOrigin, algorithm, issuerPath) => {
987
+ const suffix = algorithm === "oauth2" ? "oauth-authorization-server" : "openid-configuration";
988
+ return issuerPath && issuerPath !== "/" ? `${issuerOrigin}/.well-known/${suffix}${issuerPath}` : `${issuerOrigin}/.well-known/${suffix}`;
989
+ };
990
+ var discoverAuthorizationServerMetadata = (issuer, options = {}) => Effect7.gen(function* () {
991
+ const issuerUrl = new URL(issuer);
992
+ const issuerOrigin = `${issuerUrl.protocol}//${issuerUrl.host}`;
993
+ const issuerPath = issuerUrl.pathname.replace(/\/+$/, "");
994
+ for (const algorithm of ["oauth2", "oidc"]) {
995
+ const result = yield* Effect7.tryPromise({
996
+ try: async () => {
997
+ const response = await oauth2.discoveryRequest(issuerUrl, {
998
+ algorithm,
999
+ ...oauth4webapiOptions(options, issuer)
1000
+ });
1001
+ if (response.status === 404 || response.status === 405) {
1002
+ return null;
1003
+ }
1004
+ const as = await oauth2.processDiscoveryResponse(issuerUrl, response);
1005
+ return {
1006
+ metadataUrl: wellKnownUrlFor(issuerOrigin, algorithm, issuerPath),
1007
+ raw: as
1008
+ };
1009
+ },
1010
+ catch: (cause) => {
1011
+ if (cause instanceof OAuthDiscoveryError) return cause;
1012
+ return discoveryError(
1013
+ `Discovery (${algorithm}) failed for ${issuer}: ${cause instanceof Error ? cause.message : String(cause)}`,
1014
+ { cause }
1015
+ );
1016
+ }
1017
+ }).pipe(
1018
+ // If one algorithm fails mid-roundtrip (network, parse, issuer
1019
+ // mismatch) we still want to try the other before giving up.
1020
+ Effect7.result
1021
+ );
1022
+ if (Result.isFailure(result)) continue;
1023
+ if (result.success === null) continue;
1024
+ const metadata = yield* decodeAuthServerMetadata(result.success.raw).pipe(
1025
+ Effect7.mapError(
1026
+ (err) => new OAuthDiscoveryError({
1027
+ message: `Authorization server metadata is malformed: ${Schema7.isSchemaError(err) ? err.message : String(err)}`,
1028
+ cause: err
1029
+ })
1030
+ )
1031
+ );
1032
+ return { metadataUrl: result.success.metadataUrl, metadata };
1033
+ }
1034
+ return null;
1035
+ });
1036
+ var asForDcr = (registrationEndpoint) => {
1037
+ const url = new URL(registrationEndpoint);
1038
+ return {
1039
+ issuer: `${url.protocol}//${url.host}`,
1040
+ registration_endpoint: registrationEndpoint
1041
+ };
1042
+ };
1043
+ var registerDynamicClient = (input, options = {}) => Effect7.tryPromise({
1044
+ try: async () => {
1045
+ const as = asForDcr(input.registrationEndpoint);
1046
+ const m = input.metadata;
1047
+ const clientMetadata = {
1048
+ redirect_uris: [...m.redirect_uris]
1049
+ };
1050
+ if (m.client_name !== void 0) clientMetadata.client_name = m.client_name;
1051
+ if (m.grant_types !== void 0) clientMetadata.grant_types = [...m.grant_types];
1052
+ if (m.response_types !== void 0) clientMetadata.response_types = [...m.response_types];
1053
+ if (m.token_endpoint_auth_method !== void 0) {
1054
+ clientMetadata.token_endpoint_auth_method = m.token_endpoint_auth_method;
1055
+ }
1056
+ if (m.scope !== void 0) clientMetadata.scope = m.scope;
1057
+ if (m.application_type !== void 0) clientMetadata.application_type = m.application_type;
1058
+ if (m.client_uri !== void 0) clientMetadata.client_uri = m.client_uri;
1059
+ if (m.logo_uri !== void 0) clientMetadata.logo_uri = m.logo_uri;
1060
+ if (m.contacts !== void 0) clientMetadata.contacts = [...m.contacts];
1061
+ if (m.software_id !== void 0) clientMetadata.software_id = m.software_id;
1062
+ if (m.software_version !== void 0) clientMetadata.software_version = m.software_version;
1063
+ if (m.extra) for (const [k, v] of Object.entries(m.extra)) clientMetadata[k] = v;
1064
+ const reqOptions = oauth4webapiOptions(options);
1065
+ if (isLoopbackHttpUrl2(input.registrationEndpoint)) {
1066
+ reqOptions[oauth2.allowInsecureRequests] = true;
1067
+ }
1068
+ if (input.initialAccessToken) {
1069
+ reqOptions.initialAccessToken = input.initialAccessToken;
1070
+ }
1071
+ const response = await oauth2.dynamicClientRegistrationRequest(
1072
+ as,
1073
+ clientMetadata,
1074
+ reqOptions
1075
+ );
1076
+ const client = await oauth2.processDynamicClientRegistrationResponse(
1077
+ response
1078
+ );
1079
+ return client;
1080
+ },
1081
+ catch: (cause) => {
1082
+ if (cause instanceof oauth2.ResponseBodyError) {
1083
+ return discoveryError(
1084
+ `Dynamic Client Registration failed: ${cause.error}${cause.error_description ? ` \u2014 ${cause.error_description}` : ""}`,
1085
+ { status: cause.status, cause }
1086
+ );
1087
+ }
1088
+ return discoveryError(
1089
+ `Dynamic Client Registration failed: ${cause instanceof Error ? cause.message : String(cause)}`,
1090
+ { cause }
1091
+ );
1092
+ }
1093
+ }).pipe(
1094
+ Effect7.flatMap(
1095
+ (raw) => decodeClientInformation(raw).pipe(
1096
+ Effect7.mapError(
1097
+ (err) => new OAuthDiscoveryError({
1098
+ message: `Dynamic Client Registration response is malformed: ${Schema7.isSchemaError(err) ? err.message : String(err)}`,
1099
+ cause: err
1100
+ })
1101
+ )
1102
+ )
1103
+ )
1104
+ );
1105
+ var beginDynamicAuthorization = (input, options = {}) => Effect7.gen(function* () {
1106
+ const prior = input.previousState ?? {};
1107
+ const canSkipResourceDiscovery = prior.resourceMetadata !== void 0 || !!prior.authorizationServerUrl || !!prior.authorizationServerMetadata;
1108
+ const resource = canSkipResourceDiscovery ? prior.resourceMetadata ? {
1109
+ metadata: prior.resourceMetadata,
1110
+ metadataUrl: prior.resourceMetadataUrl ?? null
1111
+ } : null : yield* discoverProtectedResourceMetadata(input.endpoint, options);
1112
+ const authorizationServerUrl = (() => {
1113
+ if (prior.authorizationServerUrl) return prior.authorizationServerUrl;
1114
+ const fromResource = resource && resource.metadata.authorization_servers?.[0];
1115
+ if (fromResource) return fromResource;
1116
+ const u = new URL(input.endpoint);
1117
+ return `${u.protocol}//${u.host}`;
1118
+ })();
1119
+ const authServer = prior.authorizationServerMetadata && prior.authorizationServerMetadataUrl ? {
1120
+ metadata: prior.authorizationServerMetadata,
1121
+ metadataUrl: prior.authorizationServerMetadataUrl
1122
+ } : yield* discoverAuthorizationServerMetadata(
1123
+ authorizationServerUrl,
1124
+ options
1125
+ );
1126
+ if (!authServer) {
1127
+ return yield* Effect7.fail(
1128
+ discoveryError(
1129
+ `No OAuth authorization server metadata at ${authorizationServerUrl}`
1130
+ )
1131
+ );
1132
+ }
1133
+ const pkceMethods = authServer.metadata.code_challenge_methods_supported ?? [];
1134
+ if (pkceMethods.length > 0 && !pkceMethods.includes("S256")) {
1135
+ return yield* Effect7.fail(
1136
+ discoveryError(
1137
+ `Authorization server does not support PKCE S256 (advertised: ${pkceMethods.join(", ")})`
1138
+ )
1139
+ );
1140
+ }
1141
+ const responseTypes = authServer.metadata.response_types_supported ?? [];
1142
+ if (responseTypes.length > 0 && !responseTypes.includes("code")) {
1143
+ return yield* Effect7.fail(
1144
+ discoveryError(
1145
+ `Authorization server does not support response_type=code (advertised: ${responseTypes.join(", ")})`
1146
+ )
1147
+ );
1148
+ }
1149
+ const baseClientMetadata = {
1150
+ grant_types: ["authorization_code", "refresh_token"],
1151
+ response_types: ["code"],
1152
+ token_endpoint_auth_method: "none",
1153
+ client_name: "Executor",
1154
+ ...input.clientMetadata ?? {},
1155
+ redirect_uris: input.clientMetadata?.redirect_uris ?? [input.redirectUrl]
1156
+ };
1157
+ const clientInformation = prior.clientInformation ?? (yield* (() => {
1158
+ const reg = authServer.metadata.registration_endpoint;
1159
+ if (!reg) {
1160
+ return Effect7.fail(
1161
+ discoveryError(
1162
+ "Authorization server does not advertise registration_endpoint \u2014 cannot auto-register a client"
1163
+ )
1164
+ );
1165
+ }
1166
+ return registerDynamicClient(
1167
+ { registrationEndpoint: reg, metadata: baseClientMetadata },
1168
+ options
1169
+ );
1170
+ })());
1171
+ const codeVerifier = createPkceCodeVerifier();
1172
+ const codeChallenge = yield* Effect7.promise(
1173
+ () => createPkceCodeChallenge(codeVerifier)
1174
+ );
1175
+ const scopes = input.scopes ?? authServer.metadata.scopes_supported ?? [];
1176
+ const authorizationUrl = buildAuthorizationUrl({
1177
+ authorizationUrl: authServer.metadata.authorization_endpoint,
1178
+ clientId: clientInformation.client_id,
1179
+ redirectUrl: input.redirectUrl,
1180
+ scopes,
1181
+ state: input.state,
1182
+ codeChallenge
1183
+ });
1184
+ return {
1185
+ authorizationUrl,
1186
+ codeVerifier,
1187
+ state: {
1188
+ resourceMetadata: resource?.metadata ?? null,
1189
+ resourceMetadataUrl: resource?.metadataUrl ?? null,
1190
+ authorizationServerUrl,
1191
+ authorizationServerMetadataUrl: authServer.metadataUrl,
1192
+ authorizationServerMetadata: authServer.metadata,
1193
+ clientInformation
1194
+ }
1195
+ };
1196
+ });
1197
+
1198
+ // src/oauth-service.ts
1199
+ import { Effect as Effect8, Schema as Schema8 } from "effect";
1200
+ var OAuthAuthorizationServerMetadataJson = Schema8.Record(Schema8.String, Schema8.Unknown);
1201
+ var OAuthClientInformationJson = Schema8.Record(Schema8.String, Schema8.Unknown);
1202
+ var DynamicDcrSessionPayload = Schema8.Struct({
1203
+ kind: Schema8.Literal("dynamic-dcr"),
1204
+ identityLabel: Schema8.NullOr(Schema8.String),
1205
+ codeVerifier: Schema8.String,
1206
+ authorizationServerUrl: Schema8.String,
1207
+ authorizationServerMetadataUrl: Schema8.String,
1208
+ authorizationServerMetadata: OAuthAuthorizationServerMetadataJson,
1209
+ clientInformation: OAuthClientInformationJson,
1210
+ resourceMetadataUrl: Schema8.NullOr(Schema8.String),
1211
+ resourceMetadata: Schema8.NullOr(
1212
+ Schema8.Record(Schema8.String, Schema8.Unknown)
1213
+ ),
1214
+ scopes: Schema8.Array(Schema8.String)
1215
+ });
1216
+ var AuthorizationCodeSessionPayload = Schema8.Struct({
1217
+ kind: Schema8.Literal("authorization-code"),
1218
+ identityLabel: Schema8.NullOr(Schema8.String),
1219
+ codeVerifier: Schema8.String,
1220
+ authorizationEndpoint: Schema8.String,
1221
+ tokenEndpoint: Schema8.String,
1222
+ issuerUrl: Schema8.NullOr(Schema8.String).pipe(Schema8.withDecodingDefaultType(Effect8.succeed(null))),
1223
+ clientIdSecretId: Schema8.String,
1224
+ clientSecretSecretId: Schema8.NullOr(Schema8.String),
1225
+ scopes: Schema8.Array(Schema8.String),
1226
+ scopeSeparator: Schema8.optional(Schema8.String),
1227
+ clientAuth: Schema8.Literals(["body", "basic"])
1228
+ });
1229
+ var OAuthSessionPayload = Schema8.Union([
1230
+ DynamicDcrSessionPayload,
1231
+ AuthorizationCodeSessionPayload
1232
+ ]);
1233
+ var decodeSessionPayload = Schema8.decodeUnknownSync(OAuthSessionPayload);
1234
+ var encodeSessionPayload = Schema8.encodeSync(OAuthSessionPayload);
1235
+ var coerceJson = (value) => {
1236
+ if (typeof value !== "string") return value;
1237
+ try {
1238
+ return JSON.parse(value);
1239
+ } catch {
1240
+ return value;
1241
+ }
1242
+ };
1243
+ var stringArray = (value) => Array.isArray(value) ? value.filter((scope) => typeof scope === "string") : [];
1244
+ var originOrNull = (value) => {
1245
+ if (typeof value !== "string") return null;
1246
+ try {
1247
+ return new URL(value).origin;
1248
+ } catch {
1249
+ return null;
1250
+ }
1251
+ };
1252
+ var decodeProviderState = (value) => {
1253
+ const raw = coerceJson(value);
1254
+ const record = raw && typeof raw === "object" ? raw : null;
1255
+ if (record && !("kind" in record) && "flow" in record && "tokenUrl" in record) {
1256
+ const flow = record.flow;
1257
+ if (flow === "authorizationCode") {
1258
+ return Schema8.decodeUnknownSync(OAuthProviderState)({
1259
+ kind: "authorization-code",
1260
+ tokenEndpoint: record.tokenUrl,
1261
+ issuerUrl: originOrNull(record.authorizationEndpoint),
1262
+ clientIdSecretId: record.clientIdSecretId,
1263
+ clientSecretSecretId: record.clientSecretSecretId ?? null,
1264
+ clientAuth: "body",
1265
+ scope: stringArray(record.scopes).join(" ") || null
1266
+ });
1267
+ }
1268
+ if (flow === "clientCredentials") {
1269
+ return Schema8.decodeUnknownSync(OAuthProviderState)({
1270
+ kind: "client-credentials",
1271
+ tokenEndpoint: record.tokenUrl,
1272
+ clientIdSecretId: record.clientIdSecretId,
1273
+ clientSecretSecretId: record.clientSecretSecretId,
1274
+ scopes: stringArray(record.scopes),
1275
+ clientAuth: "body",
1276
+ scope: stringArray(record.scopes).join(" ") || null
1277
+ });
1278
+ }
1279
+ }
1280
+ if (record && !("kind" in record) && "clientIdSecretId" in record && "scopes" in record) {
1281
+ const scopes = stringArray(record.scopes);
1282
+ return Schema8.decodeUnknownSync(OAuthProviderState)({
1283
+ kind: "authorization-code",
1284
+ tokenEndpoint: "https://oauth2.googleapis.com/token",
1285
+ issuerUrl: "https://accounts.google.com",
1286
+ clientIdSecretId: record.clientIdSecretId,
1287
+ clientSecretSecretId: record.clientSecretSecretId ?? null,
1288
+ clientAuth: "body",
1289
+ scope: scopes.join(" ") || null
1290
+ });
1291
+ }
1292
+ if (record && !("kind" in record) && "clientInformation" in record && "endpoint" in record) {
1293
+ const clientInformation = record.clientInformation && typeof record.clientInformation === "object" ? record.clientInformation : null;
1294
+ return Schema8.decodeUnknownSync(OAuthProviderState)({
1295
+ kind: "dynamic-dcr",
1296
+ tokenEndpoint: typeof record.tokenEndpoint === "string" ? record.tokenEndpoint : record.authorizationServerMetadata && typeof record.authorizationServerMetadata === "object" && typeof record.authorizationServerMetadata.token_endpoint === "string" ? record.authorizationServerMetadata.token_endpoint : "",
1297
+ issuerUrl: record.authorizationServerMetadata && typeof record.authorizationServerMetadata === "object" && typeof record.authorizationServerMetadata.issuer === "string" ? record.authorizationServerMetadata.issuer : null,
1298
+ authorizationServerUrl: typeof record.authorizationServerUrl === "string" ? record.authorizationServerUrl : null,
1299
+ authorizationServerMetadataUrl: typeof record.authorizationServerMetadataUrl === "string" ? record.authorizationServerMetadataUrl : null,
1300
+ clientId: typeof clientInformation?.client_id === "string" ? clientInformation.client_id : "",
1301
+ clientSecretSecretId: null,
1302
+ clientAuth: "body",
1303
+ scope: null
1304
+ });
1305
+ }
1306
+ return Schema8.decodeUnknownSync(OAuthProviderState)(raw);
1307
+ };
1308
+ var defaultSessionId = () => {
1309
+ const crypto = globalThis.crypto;
1310
+ if (crypto?.randomUUID) return `oauth2_session_${crypto.randomUUID()}`;
1311
+ const bytes = new Uint8Array(16);
1312
+ crypto.getRandomValues(bytes);
1313
+ return `oauth2_session_${Array.from(
1314
+ bytes,
1315
+ (byte) => byte.toString(16).padStart(2, "0")
1316
+ ).join("")}`;
1317
+ };
1318
+ var secretIdPart = (value) => value.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "oauth";
1319
+ var oauthSecretId = (connectionId, suffix) => {
1320
+ const base = secretIdPart(connectionId);
1321
+ const readable = base.length <= 48 ? base : base.slice(0, 40);
1322
+ return `oauth2-${readable}-${suffix}`;
1323
+ };
1324
+ var scopedSessionId = (scopeId, sessionId) => `${sessionId}_${secretIdPart(scopeId).slice(0, 24)}`;
1325
+ var terminalRefreshErrors = /* @__PURE__ */ new Set([
1326
+ "invalid_grant",
1327
+ "invalid_client",
1328
+ "unauthorized_client"
1329
+ ]);
1330
+ var makeOAuth2Service = (deps) => {
1331
+ const now = deps.now ?? (() => Date.now());
1332
+ const newSessionId = deps.newSessionId ?? defaultSessionId;
1333
+ const probe = (input) => Effect8.gen(function* () {
1334
+ const resource = yield* discoverProtectedResourceMetadata(
1335
+ input.endpoint,
1336
+ { resourceHeaders: input.headers, resourceQueryParams: input.queryParams }
1337
+ ).pipe(
1338
+ Effect8.catchTag(
1339
+ "OAuthDiscoveryError",
1340
+ (err) => Effect8.fail(
1341
+ new OAuthProbeError({
1342
+ message: `Protected resource metadata probe failed: ${err.message}`
1343
+ })
1344
+ )
1345
+ )
1346
+ );
1347
+ const authorizationServerUrl = (() => {
1348
+ const fromResource = resource?.metadata.authorization_servers?.[0];
1349
+ if (fromResource) return fromResource;
1350
+ try {
1351
+ const u = new URL(input.endpoint);
1352
+ return `${u.protocol}//${u.host}`;
1353
+ } catch {
1354
+ return null;
1355
+ }
1356
+ })();
1357
+ const authServer = authorizationServerUrl ? yield* discoverAuthorizationServerMetadata(
1358
+ authorizationServerUrl
1359
+ ).pipe(
1360
+ Effect8.catchTag(
1361
+ "OAuthDiscoveryError",
1362
+ () => Effect8.succeed(null)
1363
+ )
1364
+ ) : null;
1365
+ const supportsDynamicRegistration = !!(authServer?.metadata.registration_endpoint && (authServer.metadata.token_endpoint_auth_methods_supported ?? []).includes(
1366
+ "none"
1367
+ ));
1368
+ const isBearerChallengeEndpoint = yield* Effect8.tryPromise({
1369
+ try: async () => {
1370
+ const controller = new AbortController();
1371
+ const timer = setTimeout(() => controller.abort(), 6e3);
1372
+ try {
1373
+ const probeUrl = new URL(input.endpoint);
1374
+ for (const [key, value] of Object.entries(input.queryParams ?? {})) {
1375
+ probeUrl.searchParams.set(key, value);
1376
+ }
1377
+ const response = await fetch(probeUrl.toString(), {
1378
+ method: "POST",
1379
+ headers: {
1380
+ ...input.headers ?? {},
1381
+ "content-type": "application/json",
1382
+ accept: "application/json, text/event-stream"
1383
+ },
1384
+ body: JSON.stringify({
1385
+ jsonrpc: "2.0",
1386
+ id: 1,
1387
+ method: "initialize",
1388
+ params: {
1389
+ protocolVersion: "2025-06-18",
1390
+ capabilities: {},
1391
+ clientInfo: { name: "executor-probe", version: "0" }
1392
+ }
1393
+ }),
1394
+ signal: controller.signal
1395
+ });
1396
+ if (response.status !== 401) return false;
1397
+ const wwwAuth = response.headers.get("www-authenticate") ?? response.headers.get("WWW-Authenticate");
1398
+ return !!wwwAuth && /^\s*bearer\b/i.test(wwwAuth);
1399
+ } finally {
1400
+ clearTimeout(timer);
1401
+ }
1402
+ },
1403
+ catch: () => null
1404
+ }).pipe(Effect8.catch(() => Effect8.succeed(false)));
1405
+ return {
1406
+ resourceMetadata: resource?.metadata ?? null,
1407
+ resourceMetadataUrl: resource?.metadataUrl ?? null,
1408
+ authorizationServerMetadata: authServer?.metadata ?? null,
1409
+ authorizationServerMetadataUrl: authServer?.metadataUrl ?? null,
1410
+ authorizationServerUrl: authorizationServerUrl ?? null,
1411
+ supportsDynamicRegistration,
1412
+ isBearerChallengeEndpoint
1413
+ };
1414
+ });
1415
+ const startDynamicDcr = (input, strategy) => Effect8.gen(function* () {
1416
+ const started = yield* beginDynamicAuthorization({
1417
+ endpoint: input.endpoint,
1418
+ redirectUrl: input.redirectUrl,
1419
+ state: "",
1420
+ scopes: strategy.scopes
1421
+ }, {
1422
+ resourceHeaders: input.headers,
1423
+ resourceQueryParams: input.queryParams
1424
+ }).pipe(
1425
+ Effect8.catchTag(
1426
+ "OAuthDiscoveryError",
1427
+ (err) => Effect8.fail(
1428
+ new OAuthStartError({
1429
+ message: `Dynamic authorization setup failed: ${err.message}`
1430
+ })
1431
+ )
1432
+ )
1433
+ );
1434
+ const sessionId = scopedSessionId(input.tokenScope, newSessionId());
1435
+ const codeChallenge = yield* Effect8.promise(
1436
+ () => createPkceCodeChallenge(started.codeVerifier)
1437
+ );
1438
+ const authorizationUrl = buildAuthorizationUrl({
1439
+ authorizationUrl: started.state.authorizationServerMetadata.authorization_endpoint,
1440
+ clientId: started.state.clientInformation.client_id,
1441
+ redirectUrl: input.redirectUrl,
1442
+ scopes: strategy.scopes ?? started.state.authorizationServerMetadata.scopes_supported ?? [],
1443
+ state: sessionId,
1444
+ codeChallenge
1445
+ });
1446
+ const payload = {
1447
+ kind: "dynamic-dcr",
1448
+ identityLabel: input.identityLabel ?? null,
1449
+ codeVerifier: started.codeVerifier,
1450
+ authorizationServerUrl: started.state.authorizationServerUrl,
1451
+ authorizationServerMetadataUrl: started.state.authorizationServerMetadataUrl,
1452
+ authorizationServerMetadata: started.state.authorizationServerMetadata,
1453
+ clientInformation: (() => {
1454
+ const value = started.state.clientInformation;
1455
+ return value;
1456
+ })(),
1457
+ resourceMetadataUrl: started.state.resourceMetadataUrl,
1458
+ resourceMetadata: started.state.resourceMetadata ?? null,
1459
+ scopes: [
1460
+ ...strategy.scopes ?? started.state.authorizationServerMetadata.scopes_supported ?? []
1461
+ ]
1462
+ };
1463
+ yield* writeSession({
1464
+ sessionId,
1465
+ input,
1466
+ payload,
1467
+ strategyKind: "dynamic-dcr"
1468
+ });
1469
+ return {
1470
+ sessionId,
1471
+ authorizationUrl,
1472
+ completedConnection: null
1473
+ };
1474
+ });
1475
+ const startAuthorizationCode = (input, strategy) => Effect8.gen(function* () {
1476
+ const clientId = yield* deps.secretsGet(strategy.clientIdSecretId).pipe(
1477
+ Effect8.mapError(
1478
+ (err) => (
1479
+ // Storage failure propagates; null returns aren't errors — the
1480
+ // branch below handles them.
1481
+ err
1482
+ )
1483
+ )
1484
+ );
1485
+ if (clientId === null) {
1486
+ return yield* Effect8.fail(
1487
+ new OAuthStartError({
1488
+ message: `client_id secret "${strategy.clientIdSecretId}" not found`
1489
+ })
1490
+ );
1491
+ }
1492
+ const sessionId = scopedSessionId(input.tokenScope, newSessionId());
1493
+ const codeVerifier = createPkceCodeVerifier();
1494
+ const codeChallenge = yield* Effect8.promise(
1495
+ () => createPkceCodeChallenge(codeVerifier)
1496
+ );
1497
+ const authorizationUrl = buildAuthorizationUrl({
1498
+ authorizationUrl: strategy.authorizationEndpoint,
1499
+ clientId,
1500
+ redirectUrl: input.redirectUrl,
1501
+ scopes: strategy.scopes,
1502
+ state: sessionId,
1503
+ codeChallenge,
1504
+ scopeSeparator: strategy.scopeSeparator,
1505
+ extraParams: strategy.extraAuthorizationParams
1506
+ });
1507
+ const payload = {
1508
+ kind: "authorization-code",
1509
+ identityLabel: input.identityLabel ?? null,
1510
+ codeVerifier,
1511
+ authorizationEndpoint: strategy.authorizationEndpoint,
1512
+ tokenEndpoint: strategy.tokenEndpoint,
1513
+ issuerUrl: strategy.issuerUrl ?? new URL(strategy.authorizationEndpoint).origin,
1514
+ clientIdSecretId: strategy.clientIdSecretId,
1515
+ clientSecretSecretId: strategy.clientSecretSecretId,
1516
+ scopes: [...strategy.scopes],
1517
+ scopeSeparator: strategy.scopeSeparator,
1518
+ clientAuth: strategy.clientAuth ?? "body"
1519
+ };
1520
+ yield* writeSession({
1521
+ sessionId,
1522
+ input,
1523
+ payload,
1524
+ strategyKind: "authorization-code"
1525
+ });
1526
+ return {
1527
+ sessionId,
1528
+ authorizationUrl,
1529
+ completedConnection: null
1530
+ };
1531
+ });
1532
+ const startClientCredentials = (input, strategy) => Effect8.gen(function* () {
1533
+ const clientId = yield* deps.secretsGet(strategy.clientIdSecretId);
1534
+ const clientSecret = yield* deps.secretsGet(strategy.clientSecretSecretId);
1535
+ if (clientId === null || clientSecret === null) {
1536
+ return yield* Effect8.fail(
1537
+ new OAuthStartError({
1538
+ message: "client_id / client_secret secret not found"
1539
+ })
1540
+ );
1541
+ }
1542
+ const tokens = yield* exchangeClientCredentials({
1543
+ tokenUrl: strategy.tokenEndpoint,
1544
+ clientId,
1545
+ clientSecret,
1546
+ scopes: strategy.scopes,
1547
+ scopeSeparator: strategy.scopeSeparator,
1548
+ clientAuth: strategy.clientAuth ?? "body"
1549
+ }).pipe(
1550
+ Effect8.mapError(
1551
+ (err) => new OAuthStartError({
1552
+ message: `Client credentials exchange failed: ${err.message}`
1553
+ })
1554
+ )
1555
+ );
1556
+ const expiresAt = typeof tokens.expires_in === "number" ? now() + tokens.expires_in * 1e3 : null;
1557
+ const providerState = {
1558
+ kind: "client-credentials",
1559
+ tokenEndpoint: strategy.tokenEndpoint,
1560
+ clientIdSecretId: strategy.clientIdSecretId,
1561
+ clientSecretSecretId: strategy.clientSecretSecretId,
1562
+ scopes: [...strategy.scopes ?? []],
1563
+ scopeSeparator: strategy.scopeSeparator,
1564
+ clientAuth: strategy.clientAuth ?? "body",
1565
+ scope: tokens.scope ?? null
1566
+ };
1567
+ yield* deps.connectionsCreate(
1568
+ new CreateConnectionInput({
1569
+ id: ConnectionId.make(input.connectionId),
1570
+ scope: ScopeId.make(input.tokenScope),
1571
+ provider: OAUTH2_PROVIDER_KEY,
1572
+ identityLabel: input.identityLabel ?? safeHostname(input.endpoint),
1573
+ accessToken: new TokenMaterial({
1574
+ secretId: SecretId.make(oauthSecretId(input.connectionId, "access-token")),
1575
+ name: "OAuth Access Token",
1576
+ value: tokens.access_token
1577
+ }),
1578
+ refreshToken: null,
1579
+ expiresAt,
1580
+ oauthScope: tokens.scope ?? null,
1581
+ providerState: Schema8.encodeSync(OAuthProviderState)(
1582
+ providerState
1583
+ )
1584
+ })
1585
+ ).pipe(
1586
+ Effect8.mapError(
1587
+ (err) => new OAuthStartError({
1588
+ message: `Failed to mint connection: ${err instanceof Error ? err.message : String(err)}`
1589
+ })
1590
+ )
1591
+ );
1592
+ return {
1593
+ sessionId: "",
1594
+ authorizationUrl: null,
1595
+ completedConnection: { connectionId: input.connectionId }
1596
+ };
1597
+ });
1598
+ const start = (input) => {
1599
+ switch (input.strategy.kind) {
1600
+ case "dynamic-dcr":
1601
+ return startDynamicDcr(input, input.strategy);
1602
+ case "authorization-code":
1603
+ return startAuthorizationCode(input, input.strategy);
1604
+ case "client-credentials":
1605
+ return startClientCredentials(input, input.strategy);
1606
+ }
1607
+ };
1608
+ const writeSession = (args) => deps.adapter.create({
1609
+ model: "oauth2_session",
1610
+ data: {
1611
+ id: args.sessionId,
1612
+ scope_id: args.input.tokenScope,
1613
+ plugin_id: args.input.pluginId,
1614
+ strategy: args.strategyKind,
1615
+ connection_id: args.input.connectionId,
1616
+ token_scope: args.input.tokenScope,
1617
+ redirect_url: args.input.redirectUrl,
1618
+ payload: encodeSessionPayload(args.payload),
1619
+ expires_at: now() + OAUTH2_SESSION_TTL_MS,
1620
+ created_at: /* @__PURE__ */ new Date()
1621
+ },
1622
+ forceAllowId: true
1623
+ }).pipe(Effect8.asVoid);
1624
+ const complete = (input) => Effect8.gen(function* () {
1625
+ const row = yield* deps.adapter.findOne({
1626
+ model: "oauth2_session",
1627
+ where: [{ field: "id", value: input.state }]
1628
+ });
1629
+ if (!row) {
1630
+ return yield* Effect8.fail(
1631
+ new OAuthSessionNotFoundError({ sessionId: input.state })
1632
+ );
1633
+ }
1634
+ const deleteSession = deps.adapter.delete({
1635
+ model: "oauth2_session",
1636
+ where: [
1637
+ { field: "id", value: input.state },
1638
+ { field: "scope_id", value: row.scope_id }
1639
+ ]
1640
+ });
1641
+ if (input.error) {
1642
+ yield* deleteSession;
1643
+ return yield* Effect8.fail(
1644
+ new OAuthCompleteError({
1645
+ message: `Authorization server returned error: ${input.error}`,
1646
+ code: input.error
1647
+ })
1648
+ );
1649
+ }
1650
+ if (!input.code) {
1651
+ yield* deleteSession;
1652
+ return yield* Effect8.fail(
1653
+ new OAuthCompleteError({
1654
+ message: "Missing authorization code"
1655
+ })
1656
+ );
1657
+ }
1658
+ const expiresAt = Number(row.expires_at);
1659
+ if (expiresAt <= now()) {
1660
+ yield* deleteSession;
1661
+ return yield* Effect8.fail(
1662
+ new OAuthCompleteError({
1663
+ message: "OAuth session expired"
1664
+ })
1665
+ );
1666
+ }
1667
+ const payload = decodeSessionPayload(coerceJson(row.payload));
1668
+ const endpoint = "";
1669
+ const connectionId = row.connection_id;
1670
+ const tokenScope = row.token_scope;
1671
+ const redirectUrl = row.redirect_url;
1672
+ const exchangeResult = yield* (() => {
1673
+ switch (payload.kind) {
1674
+ case "dynamic-dcr":
1675
+ return exchangeDynamicDcr(payload, input.code, redirectUrl);
1676
+ case "authorization-code":
1677
+ return exchangeAuthorizationCodeStrategy(
1678
+ payload,
1679
+ input.code,
1680
+ redirectUrl
1681
+ );
1682
+ }
1683
+ })().pipe(Effect8.tapError(() => deleteSession));
1684
+ const connectionExpiresAt = typeof exchangeResult.tokens.expires_in === "number" ? now() + exchangeResult.tokens.expires_in * 1e3 : null;
1685
+ const dynamicClientSecretSecretId = yield* (() => {
1686
+ if (payload.kind !== "dynamic-dcr") return Effect8.succeed(null);
1687
+ const clientSecret = payload.clientInformation.client_secret;
1688
+ if (typeof clientSecret !== "string" || clientSecret.length === 0) {
1689
+ return Effect8.succeed(null);
1690
+ }
1691
+ const secretId = oauthSecretId(connectionId, "client-secret");
1692
+ return deps.secretsSet(
1693
+ new SetSecretInput({
1694
+ id: SecretId.make(secretId),
1695
+ scope: ScopeId.make(tokenScope),
1696
+ name: "OAuth Client Secret",
1697
+ value: clientSecret
1698
+ })
1699
+ ).pipe(
1700
+ Effect8.as(secretId),
1701
+ Effect8.mapError(
1702
+ (err) => new OAuthCompleteError({
1703
+ message: `Failed to persist DCR client_secret: ${err instanceof Error ? err.message : String(err)}`
1704
+ })
1705
+ )
1706
+ );
1707
+ })();
1708
+ const providerState = payload.kind === "dynamic-dcr" ? {
1709
+ kind: "dynamic-dcr",
1710
+ tokenEndpoint: payload.authorizationServerMetadata.token_endpoint,
1711
+ issuerUrl: payload.authorizationServerMetadata.issuer ?? null,
1712
+ authorizationServerUrl: payload.authorizationServerUrl,
1713
+ authorizationServerMetadataUrl: payload.authorizationServerMetadataUrl,
1714
+ idTokenSigningAlgValuesSupported: payload.authorizationServerMetadata.id_token_signing_alg_values_supported,
1715
+ clientId: payload.clientInformation.client_id,
1716
+ clientSecretSecretId: dynamicClientSecretSecretId,
1717
+ clientAuth: payload.clientInformation.token_endpoint_auth_method === "client_secret_basic" ? "basic" : "body",
1718
+ scopes: [...payload.scopes],
1719
+ scope: exchangeResult.tokens.scope ?? null
1720
+ } : {
1721
+ kind: "authorization-code",
1722
+ tokenEndpoint: payload.tokenEndpoint,
1723
+ issuerUrl: payload.issuerUrl,
1724
+ clientIdSecretId: payload.clientIdSecretId,
1725
+ clientSecretSecretId: payload.clientSecretSecretId,
1726
+ clientAuth: payload.clientAuth,
1727
+ scopes: [...payload.scopes],
1728
+ scopeSeparator: payload.scopeSeparator,
1729
+ scope: exchangeResult.tokens.scope ?? null
1730
+ };
1731
+ yield* deps.connectionsCreate(
1732
+ new CreateConnectionInput({
1733
+ id: ConnectionId.make(connectionId),
1734
+ scope: ScopeId.make(tokenScope),
1735
+ provider: OAUTH2_PROVIDER_KEY,
1736
+ identityLabel: safeHostname(
1737
+ payload.identityLabel ?? exchangeResult.endpointForDisplay ?? endpoint
1738
+ ),
1739
+ accessToken: new TokenMaterial({
1740
+ secretId: SecretId.make(oauthSecretId(connectionId, "access-token")),
1741
+ name: "OAuth Access Token",
1742
+ value: exchangeResult.tokens.access_token
1743
+ }),
1744
+ refreshToken: exchangeResult.tokens.refresh_token ? new TokenMaterial({
1745
+ secretId: SecretId.make(oauthSecretId(connectionId, "refresh-token")),
1746
+ name: "OAuth Refresh Token",
1747
+ value: exchangeResult.tokens.refresh_token
1748
+ }) : null,
1749
+ expiresAt: connectionExpiresAt,
1750
+ oauthScope: exchangeResult.tokens.scope ?? null,
1751
+ providerState: Schema8.encodeSync(OAuthProviderState)(
1752
+ providerState
1753
+ )
1754
+ })
1755
+ ).pipe(
1756
+ Effect8.mapError(
1757
+ (err) => new OAuthCompleteError({
1758
+ message: `Failed to mint connection: ${err instanceof Error ? err.message : String(err)}`
1759
+ })
1760
+ )
1761
+ );
1762
+ yield* deleteSession;
1763
+ return {
1764
+ connectionId,
1765
+ expiresAt: connectionExpiresAt,
1766
+ scope: exchangeResult.tokens.scope ?? null
1767
+ };
1768
+ });
1769
+ const exchangeDynamicDcr = (payload, code, redirectUrl) => Effect8.gen(function* () {
1770
+ const md = payload.authorizationServerMetadata;
1771
+ const ci = payload.clientInformation;
1772
+ const tokens = yield* exchangeAuthorizationCode({
1773
+ tokenUrl: md.token_endpoint,
1774
+ issuerUrl: md.issuer,
1775
+ clientId: ci.client_id,
1776
+ clientSecret: ci.client_secret ?? void 0,
1777
+ redirectUrl,
1778
+ codeVerifier: payload.codeVerifier,
1779
+ code,
1780
+ idTokenSigningAlgValuesSupported: md.id_token_signing_alg_values_supported,
1781
+ clientAuth: ci.token_endpoint_auth_method === "client_secret_basic" ? "basic" : "body"
1782
+ }).pipe(
1783
+ Effect8.mapError(
1784
+ (err) => new OAuthCompleteError({
1785
+ message: `Token exchange failed: ${err.message}`,
1786
+ code: err.error
1787
+ })
1788
+ )
1789
+ );
1790
+ return {
1791
+ tokens,
1792
+ endpointForDisplay: payload.authorizationServerUrl
1793
+ };
1794
+ });
1795
+ const exchangeAuthorizationCodeStrategy = (payload, code, redirectUrl) => Effect8.gen(function* () {
1796
+ const clientId = yield* deps.secretsGet(payload.clientIdSecretId);
1797
+ if (clientId === null) {
1798
+ return yield* Effect8.fail(
1799
+ new OAuthCompleteError({
1800
+ message: `client_id secret "${payload.clientIdSecretId}" not found`
1801
+ })
1802
+ );
1803
+ }
1804
+ const clientSecret = payload.clientSecretSecretId ? yield* deps.secretsGet(payload.clientSecretSecretId) : null;
1805
+ if (payload.clientSecretSecretId && clientSecret === null) {
1806
+ return yield* Effect8.fail(
1807
+ new OAuthCompleteError({
1808
+ message: `client_secret secret "${payload.clientSecretSecretId}" not found`
1809
+ })
1810
+ );
1811
+ }
1812
+ const tokens = yield* exchangeAuthorizationCode({
1813
+ tokenUrl: payload.tokenEndpoint,
1814
+ issuerUrl: payload.issuerUrl,
1815
+ clientId,
1816
+ clientSecret: clientSecret ?? void 0,
1817
+ redirectUrl,
1818
+ codeVerifier: payload.codeVerifier,
1819
+ code,
1820
+ clientAuth: payload.clientAuth
1821
+ }).pipe(
1822
+ Effect8.mapError(
1823
+ (err) => new OAuthCompleteError({
1824
+ message: `Token exchange failed: ${err.message}`,
1825
+ code: err.error
1826
+ })
1827
+ )
1828
+ );
1829
+ return {
1830
+ tokens,
1831
+ endpointForDisplay: null
1832
+ };
1833
+ });
1834
+ const cancel = (sessionId) => Effect8.gen(function* () {
1835
+ const row = yield* deps.adapter.findOne({
1836
+ model: "oauth2_session",
1837
+ where: [{ field: "id", value: sessionId }]
1838
+ });
1839
+ if (!row) return;
1840
+ yield* deps.adapter.delete({
1841
+ model: "oauth2_session",
1842
+ where: [
1843
+ { field: "id", value: sessionId },
1844
+ { field: "scope_id", value: row.scope_id }
1845
+ ]
1846
+ });
1847
+ });
1848
+ const connectionProvider = {
1849
+ key: OAUTH2_PROVIDER_KEY,
1850
+ refresh: (input) => Effect8.gen(function* () {
1851
+ if (!input.providerState) {
1852
+ return yield* new ConnectionRefreshError({
1853
+ connectionId: input.connectionId,
1854
+ message: "oauth2 connection missing providerState"
1855
+ });
1856
+ }
1857
+ const state = yield* Effect8.try({
1858
+ try: () => decodeProviderState(input.providerState),
1859
+ catch: (cause) => new ConnectionRefreshError({
1860
+ connectionId: input.connectionId,
1861
+ message: `oauth2 providerState is malformed: ${cause instanceof Error ? cause.message : String(cause)}`,
1862
+ cause
1863
+ })
1864
+ });
1865
+ if (state.kind !== "client-credentials" && !input.refreshToken) {
1866
+ return yield* new ConnectionRefreshError({
1867
+ connectionId: input.connectionId,
1868
+ message: "oauth2 connection has no refresh token",
1869
+ reauthRequired: true
1870
+ });
1871
+ }
1872
+ const { clientId, clientSecret } = yield* (() => {
1873
+ switch (state.kind) {
1874
+ case "dynamic-dcr":
1875
+ return Effect8.gen(function* () {
1876
+ const csec = state.clientSecretSecretId ? yield* deps.secretsGet(state.clientSecretSecretId).pipe(
1877
+ Effect8.mapError(
1878
+ (cause) => new ConnectionRefreshError({
1879
+ connectionId: input.connectionId,
1880
+ message: `Failed to resolve DCR client_secret: ${cause instanceof Error ? cause.message : String(cause)}`,
1881
+ cause
1882
+ })
1883
+ )
1884
+ ) : null;
1885
+ if (state.clientSecretSecretId && csec === null) {
1886
+ return yield* new ConnectionRefreshError({
1887
+ connectionId: input.connectionId,
1888
+ message: `client_secret secret "${state.clientSecretSecretId}" not found`,
1889
+ reauthRequired: true
1890
+ });
1891
+ }
1892
+ return { clientId: state.clientId, clientSecret: csec };
1893
+ });
1894
+ case "authorization-code":
1895
+ case "client-credentials":
1896
+ return Effect8.gen(function* () {
1897
+ const cid = yield* deps.secretsGet(state.clientIdSecretId).pipe(
1898
+ Effect8.mapError(
1899
+ (cause) => new ConnectionRefreshError({
1900
+ connectionId: input.connectionId,
1901
+ message: `Failed to resolve client_id secret: ${cause instanceof Error ? cause.message : String(cause)}`,
1902
+ cause
1903
+ })
1904
+ )
1905
+ );
1906
+ if (cid === null) {
1907
+ return yield* new ConnectionRefreshError({
1908
+ connectionId: input.connectionId,
1909
+ message: `client_id secret "${state.clientIdSecretId}" not found`,
1910
+ reauthRequired: true
1911
+ });
1912
+ }
1913
+ const csec = state.clientSecretSecretId ? yield* deps.secretsGet(state.clientSecretSecretId).pipe(
1914
+ Effect8.mapError(
1915
+ (cause) => new ConnectionRefreshError({
1916
+ connectionId: input.connectionId,
1917
+ message: `Failed to resolve client_secret: ${cause instanceof Error ? cause.message : String(cause)}`,
1918
+ cause
1919
+ })
1920
+ )
1921
+ ) : null;
1922
+ if (state.clientSecretSecretId && csec === null) {
1923
+ return yield* new ConnectionRefreshError({
1924
+ connectionId: input.connectionId,
1925
+ message: `client_secret secret "${state.clientSecretSecretId}" not found`,
1926
+ reauthRequired: true
1927
+ });
1928
+ }
1929
+ return { clientId: cid, clientSecret: csec };
1930
+ });
1931
+ }
1932
+ })();
1933
+ const tokenEndpoint = yield* (() => {
1934
+ if (state.tokenEndpoint) return Effect8.succeed(state.tokenEndpoint);
1935
+ if (state.kind === "dynamic-dcr" && state.authorizationServerUrl) {
1936
+ return discoverAuthorizationServerMetadata(
1937
+ state.authorizationServerUrl
1938
+ ).pipe(
1939
+ Effect8.flatMap(
1940
+ (metadata) => metadata?.metadata.token_endpoint ? Effect8.succeed(metadata.metadata.token_endpoint) : Effect8.fail(
1941
+ new ConnectionRefreshError({
1942
+ connectionId: input.connectionId,
1943
+ message: "oauth2 legacy MCP providerState is missing token endpoint",
1944
+ reauthRequired: true
1945
+ })
1946
+ )
1947
+ ),
1948
+ Effect8.mapError(
1949
+ (cause) => cause instanceof ConnectionRefreshError ? cause : new ConnectionRefreshError({
1950
+ connectionId: input.connectionId,
1951
+ message: "Failed to discover token endpoint for legacy MCP OAuth connection",
1952
+ reauthRequired: true,
1953
+ cause
1954
+ })
1955
+ )
1956
+ );
1957
+ }
1958
+ return Effect8.fail(
1959
+ new ConnectionRefreshError({
1960
+ connectionId: input.connectionId,
1961
+ message: "oauth2 providerState is missing token endpoint",
1962
+ reauthRequired: true
1963
+ })
1964
+ );
1965
+ })();
1966
+ const tokens = yield* (state.kind === "client-credentials" ? exchangeClientCredentials({
1967
+ tokenUrl: tokenEndpoint,
1968
+ clientId,
1969
+ clientSecret: clientSecret ?? "",
1970
+ scopes: state.scopes,
1971
+ scopeSeparator: state.scopeSeparator,
1972
+ clientAuth: state.clientAuth
1973
+ }) : refreshAccessToken({
1974
+ tokenUrl: tokenEndpoint,
1975
+ issuerUrl: state.kind === "dynamic-dcr" || state.kind === "authorization-code" ? state.issuerUrl ?? void 0 : void 0,
1976
+ clientId,
1977
+ clientSecret: clientSecret ?? void 0,
1978
+ refreshToken: input.refreshToken,
1979
+ scopes: state.kind === "dynamic-dcr" || state.kind === "authorization-code" ? state.scopes : void 0,
1980
+ scopeSeparator: state.kind === "dynamic-dcr" || state.kind === "authorization-code" ? state.scopeSeparator : void 0,
1981
+ clientAuth: state.clientAuth,
1982
+ idTokenSigningAlgValuesSupported: state.kind === "dynamic-dcr" ? state.idTokenSigningAlgValuesSupported : void 0
1983
+ })).pipe(
1984
+ Effect8.mapError(
1985
+ (err) => new ConnectionRefreshError({
1986
+ connectionId: input.connectionId,
1987
+ message: `OAuth refresh failed: ${err.message}`,
1988
+ // Terminal RFC 6749 §5.2 errors mean retrying won't heal it.
1989
+ reauthRequired: err.error ? terminalRefreshErrors.has(err.error) : false
1990
+ })
1991
+ )
1992
+ );
1993
+ const expiresAt = typeof tokens.expires_in === "number" ? now() + tokens.expires_in * 1e3 : null;
1994
+ const result = {
1995
+ accessToken: tokens.access_token,
1996
+ refreshToken: tokens.refresh_token,
1997
+ expiresAt,
1998
+ oauthScope: tokens.scope ?? input.oauthScope,
1999
+ providerState: Schema8.encodeSync(OAuthProviderState)({
2000
+ ...state,
2001
+ tokenEndpoint,
2002
+ scope: tokens.scope ?? state.scope
2003
+ })
2004
+ };
2005
+ return result;
2006
+ })
2007
+ };
2008
+ const service = { probe, start, complete, cancel };
2009
+ return { service, connectionProvider };
2010
+ };
2011
+ var safeHostname = (value) => {
2012
+ if (!value) return null;
2013
+ try {
2014
+ return new URL(value).host;
2015
+ } catch {
2016
+ return value;
2017
+ }
2018
+ };
2019
+
2020
+ // src/policies.ts
2021
+ import { Schema as Schema9 } from "effect";
2022
+ var matchPattern = (pattern, toolId) => {
2023
+ if (pattern === "*") return true;
2024
+ if (pattern === toolId) return true;
2025
+ if (pattern.endsWith(".*")) {
2026
+ const prefix = pattern.slice(0, -2);
2027
+ if (prefix.length === 0) return false;
2028
+ return toolId === prefix || toolId.startsWith(`${prefix}.`);
2029
+ }
2030
+ return false;
2031
+ };
2032
+ var isValidPattern = (pattern) => {
2033
+ if (pattern.length === 0) return false;
2034
+ if (pattern === "*") return true;
2035
+ if (pattern.startsWith(".") || pattern.endsWith(".")) return false;
2036
+ if (pattern.includes("..")) return false;
2037
+ if (pattern.startsWith("*")) return false;
2038
+ const segments = pattern.split(".");
2039
+ for (let i = 0; i < segments.length; i++) {
2040
+ const seg = segments[i];
2041
+ if (seg.length === 0) return false;
2042
+ if (seg.includes("*") && seg !== "*") return false;
2043
+ if (seg === "*" && i !== segments.length - 1) return false;
2044
+ }
2045
+ return true;
2046
+ };
2047
+ var comparePolicyRow = (a, b) => {
2048
+ const pa = a.position;
2049
+ const pb = b.position;
2050
+ if (pa < pb) return -1;
2051
+ if (pa > pb) return 1;
2052
+ const ia = a.id;
2053
+ const ib = b.id;
2054
+ return ia < ib ? -1 : ia > ib ? 1 : 0;
2055
+ };
2056
+ var resolveToolPolicy = (toolId, policies, scopeRank) => {
2057
+ if (policies.length === 0) return void 0;
2058
+ const sorted = [...policies].sort((a, b) => {
2059
+ const sa = scopeRank(a);
2060
+ const sb = scopeRank(b);
2061
+ if (sa !== sb) return sa - sb;
2062
+ return comparePolicyRow(a, b);
2063
+ });
2064
+ for (const row of sorted) {
2065
+ if (matchPattern(row.pattern, toolId)) {
2066
+ return {
2067
+ action: row.action,
2068
+ pattern: row.pattern,
2069
+ policyId: row.id
2070
+ };
2071
+ }
2072
+ }
2073
+ return void 0;
2074
+ };
2075
+ var liftPlugin = (defaultRequiresApproval) => defaultRequiresApproval ? { action: "require_approval", source: "plugin-default" } : { action: "approve", source: "plugin-default" };
2076
+ var liftUser = (match) => ({
2077
+ action: match.action,
2078
+ source: "user",
2079
+ pattern: match.pattern,
2080
+ policyId: match.policyId
2081
+ });
2082
+ var resolveEffectivePolicy = (toolId, policies, scopeRank, defaultRequiresApproval) => {
2083
+ const match = resolveToolPolicy(toolId, policies, scopeRank);
2084
+ return match ? liftUser(match) : liftPlugin(defaultRequiresApproval);
2085
+ };
2086
+ var effectivePolicyFromSorted = (toolId, sortedPolicies, defaultRequiresApproval) => {
2087
+ for (const p of sortedPolicies) {
2088
+ if (matchPattern(p.pattern, toolId)) {
2089
+ return {
2090
+ action: p.action,
2091
+ source: "user",
2092
+ pattern: p.pattern,
2093
+ policyId: p.id
2094
+ };
2095
+ }
2096
+ }
2097
+ return liftPlugin(defaultRequiresApproval);
2098
+ };
2099
+ var rowToToolPolicy = (row) => ({
2100
+ id: PolicyId.make(row.id),
2101
+ scopeId: ScopeId.make(row.scope_id),
2102
+ pattern: row.pattern,
2103
+ action: row.action,
2104
+ position: row.position,
2105
+ createdAt: row.created_at,
2106
+ updatedAt: row.updated_at
2107
+ });
2108
+ var ToolPolicyActionSchema = Schema9.Literals([
2109
+ "approve",
2110
+ "require_approval",
2111
+ "block"
2112
+ ]);
2113
+
2114
+ // src/types.ts
2115
+ import { Schema as Schema10 } from "effect";
2116
+ var ToolSchema = class extends Schema10.Class("ToolSchema")({
2117
+ id: ToolId,
2118
+ name: Schema10.optional(Schema10.String),
2119
+ description: Schema10.optional(Schema10.String),
2120
+ inputSchema: Schema10.optional(Schema10.Unknown),
2121
+ outputSchema: Schema10.optional(Schema10.Unknown),
2122
+ inputTypeScript: Schema10.optional(Schema10.String),
2123
+ outputTypeScript: Schema10.optional(Schema10.String),
2124
+ typeScriptDefinitions: Schema10.optional(
2125
+ Schema10.Record(Schema10.String, Schema10.String)
2126
+ )
2127
+ }) {
2128
+ };
2129
+ var SourceDetectionResult = class extends Schema10.Class(
2130
+ "SourceDetectionResult"
2131
+ )({
2132
+ /** Plugin id that recognized the URL (e.g. "openapi", "graphql"). */
2133
+ kind: Schema10.String,
2134
+ /** Confidence tier — UI uses this to pick a winner when multiple
2135
+ * plugins claim a URL. */
2136
+ confidence: Schema10.Literals(["high", "medium", "low"]),
2137
+ /** The (possibly normalized) endpoint the plugin will use. */
2138
+ endpoint: Schema10.String,
2139
+ /** Human-readable name suggestion, typically derived from spec title
2140
+ * or URL hostname. */
2141
+ name: Schema10.String,
2142
+ /** Namespace suggestion — the plugin's recommendation for the source
2143
+ * id. UI may override. */
2144
+ namespace: Schema10.String
2145
+ }) {
2146
+ };
2147
+
2148
+ // src/schema-types.ts
2149
+ var VALID_IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2150
+ var REF_PATTERN = /^#\/(?:\$defs|definitions)\/(.+)$/;
2151
+ var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
2152
+ var asStringArray = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
2153
+ var truncate = (value, maxLength) => value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 4))} ...`;
2154
+ var formatPropertyKey = (value) => VALID_IDENTIFIER_PATTERN.test(value) ? value : JSON.stringify(value);
2155
+ var refNameFromPointer = (ref) => ref.match(REF_PATTERN)?.[1];
2156
+ var refFallbackLabel = (ref) => refNameFromPointer(ref) ?? ref.split("/").at(-1) ?? ref;
2157
+ var summarizeLargeComposite = (schema, maxCompositeMembers) => {
2158
+ for (const kind of ["oneOf", "anyOf"]) {
2159
+ const items = schema[kind];
2160
+ if (Array.isArray(items) && items.length > maxCompositeMembers) {
2161
+ return { kind, count: items.length };
2162
+ }
2163
+ }
2164
+ return null;
2165
+ };
2166
+ var primitiveTypeName = (value) => {
2167
+ switch (value) {
2168
+ case "integer":
2169
+ case "number":
2170
+ return "number";
2171
+ case "string":
2172
+ case "boolean":
2173
+ case "null":
2174
+ return value;
2175
+ case "array":
2176
+ return "unknown[]";
2177
+ case "object":
2178
+ return "Record<string, unknown>";
2179
+ default:
2180
+ return "unknown";
2181
+ }
2182
+ };
2183
+ var renderComposite = (input) => {
2184
+ const rawItems = input.schema[input.key];
2185
+ const items = Array.isArray(rawItems) ? rawItems.map((item) => asRecord(item)) : [];
2186
+ if (items.length === 0) {
2187
+ return null;
2188
+ }
2189
+ const labels = items.map((item) => input.render(item, input.depthRemaining - 1)).filter((label) => label.length > 0);
2190
+ if (labels.length === 0) {
2191
+ return null;
2192
+ }
2193
+ return labels.join(input.key === "allOf" ? " & " : " | ");
2194
+ };
2195
+ var localDefinitionsFromSchema = (schema) => {
2196
+ const root = asRecord(schema);
2197
+ const defs = /* @__PURE__ */ new Map();
2198
+ for (const [key, value] of Object.entries(asRecord(root.$defs))) {
2199
+ defs.set(key, value);
2200
+ }
2201
+ for (const [key, value] of Object.entries(asRecord(root.definitions))) {
2202
+ defs.set(key, value);
2203
+ }
2204
+ return defs;
2205
+ };
2206
+ var schemaToTypeScriptPreview = (schema, options = {}) => {
2207
+ const localDefs = localDefinitionsFromSchema(schema);
2208
+ return schemaToTypeScriptPreviewWithDefs(schema, localDefs, options);
2209
+ };
2210
+ var schemaToTypeScriptPreviewWithDefs = (schema, defs, options = {}) => {
2211
+ const maxLength = options.maxLength ?? 400;
2212
+ const maxDepth = options.maxDepth ?? 6;
2213
+ const maxProperties = options.maxProperties ?? 12;
2214
+ const maxRefDepth = options.maxRefDepth ?? 3;
2215
+ const maxCompositeMembers = options.maxCompositeMembers ?? 8;
2216
+ const render = (input) => {
2217
+ const current = asRecord(input.currentInput);
2218
+ if (input.depthRemaining <= 0) {
2219
+ if (typeof current.title === "string" && current.title.length > 0) {
2220
+ return current.title;
2221
+ }
2222
+ if (current.type === "array") {
2223
+ return "unknown[]";
2224
+ }
2225
+ if (current.type === "object" || current.properties) {
2226
+ return "Record<string, unknown>";
2227
+ }
2228
+ return "unknown";
2229
+ }
2230
+ if (typeof current.$ref === "string") {
2231
+ const refLabel = refFallbackLabel(current.$ref);
2232
+ return input.refDepthRemaining > 0 ? refLabel : `unknown /* ${refLabel} omitted */`;
2233
+ }
2234
+ if ("const" in current) {
2235
+ return JSON.stringify(current.const);
2236
+ }
2237
+ const enumValues = Array.isArray(current.enum) ? current.enum : [];
2238
+ if (enumValues.length > 0) {
2239
+ return truncate(enumValues.map((value) => JSON.stringify(value)).join(" | "), maxLength);
2240
+ }
2241
+ const largeComposite = summarizeLargeComposite(current, maxCompositeMembers);
2242
+ if (largeComposite) {
2243
+ return `unknown /* ${largeComposite.count}-way ${largeComposite.kind} omitted */`;
2244
+ }
2245
+ const renderNested = (value) => render({
2246
+ currentInput: value,
2247
+ depthRemaining: input.depthRemaining - 1,
2248
+ refDepthRemaining: input.refDepthRemaining
2249
+ });
2250
+ const composite = renderComposite({
2251
+ key: "oneOf",
2252
+ schema: current,
2253
+ render: (value) => renderNested(value),
2254
+ depthRemaining: input.depthRemaining
2255
+ }) ?? renderComposite({
2256
+ key: "anyOf",
2257
+ schema: current,
2258
+ render: (value) => renderNested(value),
2259
+ depthRemaining: input.depthRemaining
2260
+ }) ?? renderComposite({
2261
+ key: "allOf",
2262
+ schema: current,
2263
+ render: (value) => renderNested(value),
2264
+ depthRemaining: input.depthRemaining
2265
+ });
2266
+ if (composite) {
2267
+ return truncate(composite, maxLength);
2268
+ }
2269
+ if (current.nullable === true) {
2270
+ const { nullable: _nullable, ...rest } = current;
2271
+ return truncate(
2272
+ `${render({
2273
+ currentInput: rest,
2274
+ depthRemaining: input.depthRemaining,
2275
+ refDepthRemaining: input.refDepthRemaining
2276
+ })} | null`,
2277
+ maxLength
2278
+ );
2279
+ }
2280
+ if (current.type === "array") {
2281
+ const itemLabel = current.items ? render({
2282
+ currentInput: current.items,
2283
+ depthRemaining: input.depthRemaining - 1,
2284
+ refDepthRemaining: input.refDepthRemaining
2285
+ }) : "unknown";
2286
+ return truncate(`${itemLabel}[]`, maxLength);
2287
+ }
2288
+ if (current.type === "object" || current.properties) {
2289
+ const properties = asRecord(current.properties);
2290
+ const propertyKeys = Object.keys(properties);
2291
+ const required = new Set(asStringArray(current.required));
2292
+ const additionalProperties = current.additionalProperties;
2293
+ const additionalPropertiesLabel = additionalProperties && typeof additionalProperties === "object" ? render({
2294
+ currentInput: additionalProperties,
2295
+ depthRemaining: input.depthRemaining - 1,
2296
+ refDepthRemaining: input.refDepthRemaining
2297
+ }) : additionalProperties === true ? "unknown" : null;
2298
+ if (propertyKeys.length === 0) {
2299
+ if (additionalPropertiesLabel) {
2300
+ return truncate(`Record<string, ${additionalPropertiesLabel}>`, maxLength);
2301
+ }
2302
+ return "Record<string, unknown>";
2303
+ }
2304
+ const visibleKeys = propertyKeys.slice(0, maxProperties);
2305
+ const parts = visibleKeys.map(
2306
+ (key) => `${formatPropertyKey(key)}${required.has(key) ? "" : "?"}: ${render({
2307
+ currentInput: properties[key],
2308
+ depthRemaining: input.depthRemaining - 1,
2309
+ refDepthRemaining: input.refDepthRemaining
2310
+ })}`
2311
+ );
2312
+ if (visibleKeys.length < propertyKeys.length) {
2313
+ parts.push("...");
2314
+ }
2315
+ if (additionalPropertiesLabel) {
2316
+ parts.push(`[key: string]: ${additionalPropertiesLabel}`);
2317
+ }
2318
+ return truncate(`{ ${parts.join("; ")} }`, maxLength);
2319
+ }
2320
+ if (Array.isArray(current.type)) {
2321
+ return truncate(
2322
+ current.type.filter((value) => typeof value === "string").map(primitiveTypeName).join(" | "),
2323
+ maxLength
2324
+ );
2325
+ }
2326
+ if (typeof current.type === "string") {
2327
+ return primitiveTypeName(current.type);
2328
+ }
2329
+ return "unknown";
2330
+ };
2331
+ const referencedDepths = /* @__PURE__ */ new Map();
2332
+ const collectPreviewRefs = (currentInput, refDepth) => {
2333
+ const current = asRecord(currentInput);
2334
+ if (summarizeLargeComposite(current, maxCompositeMembers)) {
2335
+ return;
2336
+ }
2337
+ if (typeof current.$ref === "string") {
2338
+ const name = refNameFromPointer(current.$ref);
2339
+ if (!name) {
2340
+ return;
2341
+ }
2342
+ const existingDepth = referencedDepths.get(name);
2343
+ if (existingDepth !== void 0 && existingDepth <= refDepth) {
2344
+ return;
2345
+ }
2346
+ referencedDepths.set(name, refDepth);
2347
+ if (refDepth >= maxRefDepth) {
2348
+ return;
2349
+ }
2350
+ const target = defs.get(name);
2351
+ if (target !== void 0) {
2352
+ collectPreviewRefs(target, refDepth + 1);
2353
+ }
2354
+ return;
2355
+ }
2356
+ for (const value of Object.values(current)) {
2357
+ if (value && typeof value === "object") {
2358
+ if (Array.isArray(value)) {
2359
+ for (const item of value) {
2360
+ collectPreviewRefs(item, refDepth);
2361
+ }
2362
+ } else {
2363
+ collectPreviewRefs(value, refDepth);
2364
+ }
2365
+ }
2366
+ }
2367
+ };
2368
+ collectPreviewRefs(schema, 1);
2369
+ const definitions = Object.fromEntries(
2370
+ [...referencedDepths.entries()].sort(([left], [right]) => left.localeCompare(right)).flatMap(([name, refDepth]) => {
2371
+ const target = defs.get(name);
2372
+ if (target === void 0) {
2373
+ return [];
2374
+ }
2375
+ return [
2376
+ [
2377
+ name,
2378
+ render({
2379
+ currentInput: target,
2380
+ depthRemaining: maxDepth,
2381
+ refDepthRemaining: Math.max(0, maxRefDepth - refDepth)
2382
+ })
2383
+ ]
2384
+ ];
2385
+ })
2386
+ );
2387
+ return {
2388
+ type: render({
2389
+ currentInput: schema,
2390
+ depthRemaining: maxDepth,
2391
+ refDepthRemaining: maxRefDepth
2392
+ }),
2393
+ definitions
2394
+ };
2395
+ };
2396
+ var buildToolTypeScriptPreview = (input) => {
2397
+ const inputPreview = input.inputSchema !== void 0 ? schemaToTypeScriptPreviewWithDefs(input.inputSchema, input.defs, input.options) : null;
2398
+ const outputPreview = input.outputSchema !== void 0 ? schemaToTypeScriptPreviewWithDefs(input.outputSchema, input.defs, input.options) : null;
2399
+ const mergedDefinitions = {
2400
+ ...inputPreview?.definitions,
2401
+ ...outputPreview?.definitions
2402
+ };
2403
+ return {
2404
+ ...inputPreview ? { inputTypeScript: inputPreview.type } : {},
2405
+ ...outputPreview ? { outputTypeScript: outputPreview.type } : {},
2406
+ ...Object.keys(mergedDefinitions).length > 0 ? { typeScriptDefinitions: mergedDefinitions } : {}
2407
+ };
2408
+ };
2409
+
2410
+ // src/executor.ts
2411
+ import { Context, Deferred, Effect as Effect10, Option, Result as Result2, Schema as Schema11, Semaphore } from "effect";
2412
+ import { generateKeyBetween } from "fractional-indexing";
2413
+ import {
2414
+ StorageError as StorageError3,
2415
+ typedAdapter
2416
+ } from "@executor-js/storage-core";
2417
+
2418
+ // src/scoped-adapter.ts
2419
+ import { Effect as Effect9 } from "effect";
2420
+ import {
2421
+ StorageError as StorageError2
2422
+ } from "@executor-js/storage-core";
2423
+ var SCOPE_FIELD = "scope_id";
2424
+ var collectScopedModels = (schema) => {
2425
+ const out = /* @__PURE__ */ new Set();
2426
+ for (const [model, def] of Object.entries(schema)) {
2427
+ if (def.fields[SCOPE_FIELD]) out.add(model);
2428
+ }
2429
+ return out;
2430
+ };
2431
+ var withScopeRead = (where, ctx) => {
2432
+ const base = (where ?? []).filter((w) => w.field !== SCOPE_FIELD);
2433
+ const callerScope = (where ?? []).find((w) => w.field === SCOPE_FIELD);
2434
+ if (callerScope && typeof callerScope.value === "string" && ctx.scopes.includes(callerScope.value)) {
2435
+ return [...base, { field: SCOPE_FIELD, value: callerScope.value }];
2436
+ }
2437
+ const scope = ctx.scopes.length === 1 ? { field: SCOPE_FIELD, value: ctx.scopes[0] } : { field: SCOPE_FIELD, value: [...ctx.scopes], operator: "in" };
2438
+ return [...base, scope];
2439
+ };
2440
+ var assertScopedWrite = (model, data, ctx) => {
2441
+ const value = data[SCOPE_FIELD];
2442
+ if (typeof value !== "string" || value.length === 0) {
2443
+ return Effect9.fail(
2444
+ new StorageError2({
2445
+ message: `Write to scoped table "${model}" missing required \`scope_id\`. Callers must name the target scope explicitly.`,
2446
+ cause: void 0
2447
+ })
2448
+ );
2449
+ }
2450
+ if (!ctx.scopes.includes(value)) {
2451
+ return Effect9.fail(
2452
+ new StorageError2({
2453
+ message: `Write to scoped table "${model}" targets scope "${value}" which is not in the executor's scope stack [${ctx.scopes.join(", ")}].`,
2454
+ cause: void 0
2455
+ })
2456
+ );
2457
+ }
2458
+ return Effect9.void;
2459
+ };
2460
+ var wrapTxMethods = (inner, ctx, scopedModels) => {
2461
+ const isScoped = (model) => scopedModels.has(model);
2462
+ return {
2463
+ id: inner.id,
2464
+ create: (data) => isScoped(data.model) ? Effect9.flatMap(
2465
+ assertScopedWrite(
2466
+ data.model,
2467
+ data.data,
2468
+ ctx
2469
+ ),
2470
+ () => inner.create(data)
2471
+ ) : inner.create(data),
2472
+ createMany: (data) => isScoped(data.model) ? Effect9.flatMap(
2473
+ Effect9.all(
2474
+ data.data.map(
2475
+ (row) => assertScopedWrite(
2476
+ data.model,
2477
+ row,
2478
+ ctx
2479
+ )
2480
+ )
2481
+ ),
2482
+ () => inner.createMany(data)
2483
+ ) : inner.createMany(data),
2484
+ findOne: (data) => isScoped(data.model) ? inner.findOne({ ...data, where: withScopeRead(data.where, ctx) }) : inner.findOne(data),
2485
+ findMany: (data) => isScoped(data.model) ? inner.findMany({ ...data, where: withScopeRead(data.where, ctx) }) : inner.findMany(data),
2486
+ count: (data) => isScoped(data.model) ? inner.count({ ...data, where: withScopeRead(data.where, ctx) }) : inner.count(data),
2487
+ update: (data) => isScoped(data.model) ? Effect9.flatMap(
2488
+ // If the caller sets `scope_id` in the update payload, it
2489
+ // must be one of the allowed scopes. If they don't, we leave
2490
+ // the row's existing scope_id in place — updates are scoped
2491
+ // by the where filter's IN clause, so you can only mutate
2492
+ // rows you can read. That's sufficient for isolation; we
2493
+ // don't need to force-stamp on update.
2494
+ data.update[SCOPE_FIELD] !== void 0 ? assertScopedWrite(
2495
+ data.model,
2496
+ data.update,
2497
+ ctx
2498
+ ) : Effect9.void,
2499
+ () => inner.update({
2500
+ ...data,
2501
+ where: withScopeRead(data.where, ctx)
2502
+ })
2503
+ ) : inner.update(data),
2504
+ updateMany: (data) => isScoped(data.model) ? Effect9.flatMap(
2505
+ data.update[SCOPE_FIELD] !== void 0 ? assertScopedWrite(
2506
+ data.model,
2507
+ data.update,
2508
+ ctx
2509
+ ) : Effect9.void,
2510
+ () => inner.updateMany({
2511
+ ...data,
2512
+ where: withScopeRead(data.where, ctx)
2513
+ })
2514
+ ) : inner.updateMany(data),
2515
+ delete: (data) => isScoped(data.model) ? inner.delete({ ...data, where: withScopeRead(data.where, ctx) }) : inner.delete(data),
2516
+ deleteMany: (data) => isScoped(data.model) ? inner.deleteMany({ ...data, where: withScopeRead(data.where, ctx) }) : inner.deleteMany(data)
2517
+ };
2518
+ };
2519
+ var scopeAdapter = (inner, ctx, schema) => {
2520
+ const scopedModels = collectScopedModels(schema);
2521
+ const tx = wrapTxMethods(inner, ctx, scopedModels);
2522
+ return {
2523
+ ...tx,
2524
+ transaction: (callback) => inner.transaction((rawTrx) => {
2525
+ const scopedTrx = wrapTxMethods(
2526
+ rawTrx,
2527
+ ctx,
2528
+ scopedModels
2529
+ );
2530
+ return callback(scopedTrx);
2531
+ }),
2532
+ createSchema: inner.createSchema,
2533
+ options: inner.options
2534
+ };
2535
+ };
2536
+
2537
+ // src/executor.ts
2538
+ var acceptAllHandler = () => Effect10.succeed(new ElicitationResponse({ action: "accept" }));
2539
+ var resolveElicitationHandler = (onElicitation) => onElicitation === "accept-all" ? acceptAllHandler : onElicitation;
2540
+ var collectSchemas = (plugins) => {
2541
+ const merged = { ...coreSchema };
2542
+ for (const plugin of plugins) {
2543
+ if (!plugin.schema) continue;
2544
+ for (const [modelKey, model] of Object.entries(plugin.schema)) {
2545
+ if (merged[modelKey]) {
2546
+ throw new Error(
2547
+ `Duplicate model "${modelKey}" contributed by plugin "${plugin.id}" (reserved by core or another plugin)`
2548
+ );
2549
+ }
2550
+ merged[modelKey] = model;
2551
+ }
2552
+ }
2553
+ return merged;
2554
+ };
2555
+ var rowToSource = (row) => ({
2556
+ id: row.id,
2557
+ scopeId: row.scope_id,
2558
+ kind: row.kind,
2559
+ name: row.name,
2560
+ url: row.url ?? void 0,
2561
+ pluginId: row.plugin_id,
2562
+ canRemove: Boolean(row.can_remove),
2563
+ canRefresh: Boolean(row.can_refresh),
2564
+ canEdit: Boolean(row.can_edit),
2565
+ runtime: false
2566
+ });
2567
+ var staticDeclToSource = (decl, pluginId) => ({
2568
+ id: decl.id,
2569
+ scopeId: void 0,
2570
+ kind: decl.kind,
2571
+ name: decl.name,
2572
+ url: decl.url,
2573
+ pluginId,
2574
+ canRemove: decl.canRemove ?? false,
2575
+ canRefresh: decl.canRefresh ?? false,
2576
+ canEdit: decl.canEdit ?? false,
2577
+ runtime: true
2578
+ });
2579
+ var decodeJsonColumn = (value) => {
2580
+ if (value === null || value === void 0) return void 0;
2581
+ if (typeof value !== "string") return value;
2582
+ try {
2583
+ return JSON.parse(value);
2584
+ } catch {
2585
+ return value;
2586
+ }
2587
+ };
2588
+ var rowToTool = (row, annotations) => ({
2589
+ id: row.id,
2590
+ sourceId: row.source_id,
2591
+ pluginId: row.plugin_id,
2592
+ name: row.name,
2593
+ description: row.description,
2594
+ inputSchema: decodeJsonColumn(row.input_schema),
2595
+ outputSchema: decodeJsonColumn(row.output_schema),
2596
+ annotations
2597
+ });
2598
+ var staticDeclToTool = (source, tool, pluginId) => ({
2599
+ id: `${source.id}.${tool.name}`,
2600
+ sourceId: source.id,
2601
+ pluginId,
2602
+ name: tool.name,
2603
+ description: tool.description,
2604
+ inputSchema: tool.inputSchema,
2605
+ outputSchema: tool.outputSchema,
2606
+ annotations: tool.annotations
2607
+ });
2608
+ var writeSourceInput = (core, pluginId, input) => Effect10.gen(function* () {
2609
+ yield* deleteSourceById(core, input.id, input.scope);
2610
+ const now = /* @__PURE__ */ new Date();
2611
+ yield* core.create({
2612
+ model: "source",
2613
+ data: {
2614
+ id: input.id,
2615
+ scope_id: input.scope,
2616
+ plugin_id: pluginId,
2617
+ kind: input.kind,
2618
+ name: input.name,
2619
+ url: input.url ?? void 0,
2620
+ can_remove: input.canRemove ?? true,
2621
+ can_refresh: input.canRefresh ?? false,
2622
+ can_edit: input.canEdit ?? false,
2623
+ created_at: now,
2624
+ updated_at: now
2625
+ },
2626
+ forceAllowId: true
2627
+ });
2628
+ if (input.tools.length > 0) {
2629
+ yield* core.createMany({
2630
+ model: "tool",
2631
+ data: input.tools.map((tool) => ({
2632
+ id: `${input.id}.${tool.name}`,
2633
+ scope_id: input.scope,
2634
+ source_id: input.id,
2635
+ plugin_id: pluginId,
2636
+ name: tool.name,
2637
+ description: tool.description,
2638
+ input_schema: tool.inputSchema ?? void 0,
2639
+ output_schema: tool.outputSchema ?? void 0,
2640
+ created_at: now,
2641
+ updated_at: now
2642
+ })),
2643
+ forceAllowId: true
2644
+ });
2645
+ }
2646
+ });
2647
+ var deleteSourceById = (core, sourceId, scopeId) => Effect10.gen(function* () {
2648
+ yield* core.deleteMany({
2649
+ model: "tool",
2650
+ where: [
2651
+ { field: "source_id", value: sourceId },
2652
+ { field: "scope_id", value: scopeId }
2653
+ ]
2654
+ });
2655
+ yield* core.deleteMany({
2656
+ model: "definition",
2657
+ where: [
2658
+ { field: "source_id", value: sourceId },
2659
+ { field: "scope_id", value: scopeId }
2660
+ ]
2661
+ });
2662
+ yield* core.delete({
2663
+ model: "source",
2664
+ where: [
2665
+ { field: "id", value: sourceId },
2666
+ { field: "scope_id", value: scopeId }
2667
+ ]
2668
+ });
2669
+ });
2670
+ var writeDefinitions = (core, pluginId, input) => Effect10.gen(function* () {
2671
+ yield* core.deleteMany({
2672
+ model: "definition",
2673
+ where: [
2674
+ { field: "source_id", value: input.sourceId },
2675
+ { field: "scope_id", value: input.scope }
2676
+ ]
2677
+ });
2678
+ const entries = Object.entries(input.definitions);
2679
+ if (entries.length === 0) return;
2680
+ const now = /* @__PURE__ */ new Date();
2681
+ yield* core.createMany({
2682
+ model: "definition",
2683
+ data: entries.map(([name, schema]) => ({
2684
+ id: `${input.sourceId}.${name}`,
2685
+ scope_id: input.scope,
2686
+ source_id: input.sourceId,
2687
+ plugin_id: pluginId,
2688
+ name,
2689
+ schema,
2690
+ created_at: now
2691
+ })),
2692
+ forceAllowId: true
2693
+ });
2694
+ });
2695
+ var toolMatchesFilter = (tool, filter) => {
2696
+ if (filter.sourceId && tool.sourceId !== filter.sourceId) return false;
2697
+ if (filter.query) {
2698
+ const q = filter.query.toLowerCase();
2699
+ const hay = `${tool.name} ${tool.description}`.toLowerCase();
2700
+ if (!hay.includes(q)) return false;
2701
+ }
2702
+ return true;
2703
+ };
2704
+ var activeAdapterRef = Context.Reference(
2705
+ "executor/ActiveAdapter",
2706
+ { defaultValue: () => null }
2707
+ );
2708
+ var buildAdapterRouter = (root) => {
2709
+ const pick = (use) => Effect10.flatMap(
2710
+ Effect10.service(activeAdapterRef),
2711
+ (active) => use(active ?? root)
2712
+ );
2713
+ return {
2714
+ id: root.id,
2715
+ create: (data) => pick((a) => a.create(data)),
2716
+ createMany: (data) => pick((a) => a.createMany(data)),
2717
+ findOne: (data) => pick((a) => a.findOne(data)),
2718
+ findMany: (data) => pick((a) => a.findMany(data)),
2719
+ count: (data) => pick((a) => a.count(data)),
2720
+ update: (data) => pick((a) => a.update(data)),
2721
+ updateMany: (data) => pick((a) => a.updateMany(data)),
2722
+ delete: (data) => pick((a) => a.delete(data)),
2723
+ deleteMany: (data) => pick((a) => a.deleteMany(data)),
2724
+ // transaction() always opens a real boundary on the ROOT adapter so the
2725
+ // tx uses one real connection from the pool. If we're already inside a
2726
+ // parent tx (FiberRef set), skip opening a nested sql.begin — that's
2727
+ // the postgres.js + Hyperdrive deadlock path — and just run the
2728
+ // callback with the existing tx handle. In both cases the callback
2729
+ // sees a FiberRef-substituted adapter so further nested writes thread
2730
+ // through.
2731
+ transaction: (callback) => Effect10.flatMap(Effect10.service(activeAdapterRef), (active) => {
2732
+ if (active) return callback(active);
2733
+ return root.transaction(
2734
+ (trx) => Effect10.provideService(callback(trx), activeAdapterRef, trx)
2735
+ );
2736
+ })
2737
+ };
2738
+ };
2739
+ var createExecutor = (config) => Effect10.gen(function* () {
2740
+ const defaultPlugins = () => {
2741
+ const empty = [];
2742
+ return empty;
2743
+ };
2744
+ const {
2745
+ scopes,
2746
+ adapter: rootAdapter,
2747
+ blobs,
2748
+ plugins = defaultPlugins()
2749
+ } = config;
2750
+ if (scopes.length === 0) {
2751
+ return yield* Effect10.fail(
2752
+ new Error("createExecutor requires a non-empty scopes array")
2753
+ );
2754
+ }
2755
+ const schema = collectSchemas(plugins);
2756
+ const scopeIds = scopes.map((s) => s.id);
2757
+ const scopedRoot = scopeAdapter(
2758
+ rootAdapter,
2759
+ { scopes: scopeIds },
2760
+ schema
2761
+ );
2762
+ const adapter = buildAdapterRouter(scopedRoot);
2763
+ const core = typedAdapter(adapter);
2764
+ const staticTools = /* @__PURE__ */ new Map();
2765
+ const staticSources = /* @__PURE__ */ new Map();
2766
+ const runtimes = /* @__PURE__ */ new Map();
2767
+ const secretProviders = /* @__PURE__ */ new Map();
2768
+ const connectionProviders = /* @__PURE__ */ new Map();
2769
+ const connectionProviderAliases = /* @__PURE__ */ new Map([
2770
+ ["mcp:oauth2", "oauth2"],
2771
+ ["openapi:oauth2", "oauth2"],
2772
+ ["google-discovery:google", "oauth2"],
2773
+ ["google-discovery:oauth2", "oauth2"]
2774
+ ]);
2775
+ const resolveConnectionProvider = (key) => {
2776
+ const direct = connectionProviders.get(key);
2777
+ if (direct) return direct;
2778
+ const canonical = connectionProviderAliases.get(key);
2779
+ return canonical ? connectionProviders.get(canonical) : void 0;
2780
+ };
2781
+ const refreshInFlight = /* @__PURE__ */ new Map();
2782
+ const refreshInFlightLock = Semaphore.makeUnsafe(1);
2783
+ const extensions = {};
2784
+ const scopePrecedence = /* @__PURE__ */ new Map();
2785
+ scopeIds.forEach((s, i) => scopePrecedence.set(s, i));
2786
+ const scopeRank = (row) => scopePrecedence.get(row.scope_id) ?? Infinity;
2787
+ const findInnermost = (rows) => {
2788
+ if (rows.length === 0) return null;
2789
+ let winner;
2790
+ let best = Infinity;
2791
+ for (const row of rows) {
2792
+ const rank = scopeRank(row);
2793
+ if (rank < best) {
2794
+ best = rank;
2795
+ winner = row;
2796
+ }
2797
+ }
2798
+ return winner ?? null;
2799
+ };
2800
+ const secretRowsForId = (id) => core.findMany({
2801
+ model: "secret",
2802
+ where: [{ field: "id", value: id }]
2803
+ });
2804
+ const resolveSecretValueFromRows = (id, rows) => Effect10.gen(function* () {
2805
+ const ordered = [...rows].sort(
2806
+ (a, b) => (scopePrecedence.get(a.scope_id) ?? Infinity) - (scopePrecedence.get(b.scope_id) ?? Infinity)
2807
+ );
2808
+ for (const row of ordered) {
2809
+ const provider = secretProviders.get(row.provider);
2810
+ if (!provider) continue;
2811
+ const value = yield* provider.get(id, row.scope_id);
2812
+ if (value !== null) return value;
2813
+ }
2814
+ const fallbackScope = scopeIds[0];
2815
+ const candidates = [...secretProviders.values()].filter(
2816
+ (p) => p.list
2817
+ );
2818
+ const values = yield* Effect10.all(
2819
+ candidates.map(
2820
+ (p) => p.get(id, fallbackScope).pipe(Effect10.catch(() => Effect10.succeed(null)))
2821
+ ),
2822
+ { concurrency: "unbounded" }
2823
+ );
2824
+ for (const value of values) if (value !== null) return value;
2825
+ return null;
2826
+ });
2827
+ const secretsGet = (id) => Effect10.gen(function* () {
2828
+ const rows = yield* secretRowsForId(id);
2829
+ const owned = rows.find((row) => row.owned_by_connection_id);
2830
+ if (owned) {
2831
+ return yield* Effect10.fail(
2832
+ new SecretOwnedByConnectionError({
2833
+ secretId: SecretId.make(id),
2834
+ connectionId: ConnectionId.make(
2835
+ owned.owned_by_connection_id
2836
+ )
2837
+ })
2838
+ );
2839
+ }
2840
+ return yield* resolveSecretValueFromRows(id, rows);
2841
+ });
2842
+ const connectionSecretGet = (id) => Effect10.gen(function* () {
2843
+ const rows = yield* secretRowsForId(id);
2844
+ return yield* resolveSecretValueFromRows(id, rows);
2845
+ });
2846
+ const secretRouteHasBackingValue = (row) => {
2847
+ const provider = secretProviders.get(row.provider);
2848
+ if (!provider?.has) return Effect10.succeed(true);
2849
+ return provider.has(row.id, row.scope_id).pipe(Effect10.catch(() => Effect10.succeed(false)));
2850
+ };
2851
+ const secretsSet = (input) => Effect10.gen(function* () {
2852
+ if (!scopeIds.includes(input.scope)) {
2853
+ return yield* Effect10.fail(
2854
+ new StorageError3({
2855
+ message: `secrets.set targets scope "${input.scope}" which is not in the executor's scope stack [${scopeIds.join(", ")}].`,
2856
+ cause: void 0
2857
+ })
2858
+ );
2859
+ }
2860
+ let target;
2861
+ if (input.provider) {
2862
+ target = secretProviders.get(input.provider);
2863
+ if (!target) {
2864
+ return yield* Effect10.fail(
2865
+ new StorageError3({
2866
+ message: `Unknown secret provider: ${input.provider}`,
2867
+ cause: void 0
2868
+ })
2869
+ );
2870
+ }
2871
+ } else {
2872
+ for (const provider of secretProviders.values()) {
2873
+ if (provider.writable && provider.set) {
2874
+ target = provider;
2875
+ break;
2876
+ }
2877
+ }
2878
+ if (!target) {
2879
+ return yield* Effect10.fail(
2880
+ new StorageError3({
2881
+ message: "No writable secret providers registered",
2882
+ cause: void 0
2883
+ })
2884
+ );
2885
+ }
2886
+ }
2887
+ if (!target.writable || !target.set) {
2888
+ return yield* Effect10.fail(
2889
+ new StorageError3({
2890
+ message: `Secret provider "${target.key}" is read-only`,
2891
+ cause: void 0
2892
+ })
2893
+ );
2894
+ }
2895
+ yield* target.set(input.id, input.value, input.scope);
2896
+ const now = /* @__PURE__ */ new Date();
2897
+ yield* core.delete({
2898
+ model: "secret",
2899
+ where: [
2900
+ { field: "id", value: input.id },
2901
+ { field: "scope_id", value: input.scope }
2902
+ ]
2903
+ });
2904
+ yield* core.create({
2905
+ model: "secret",
2906
+ data: {
2907
+ id: input.id,
2908
+ scope_id: input.scope,
2909
+ name: input.name,
2910
+ provider: target.key,
2911
+ created_at: now
2912
+ },
2913
+ forceAllowId: true
2914
+ });
2915
+ return new SecretRef({
2916
+ id: input.id,
2917
+ scopeId: input.scope,
2918
+ name: input.name,
2919
+ provider: target.key,
2920
+ createdAt: now
2921
+ });
2922
+ });
2923
+ const secretsRemove = (id) => Effect10.gen(function* () {
2924
+ const rows = yield* core.findMany({
2925
+ model: "secret",
2926
+ where: [{ field: "id", value: id }]
2927
+ });
2928
+ const target = findInnermost(rows);
2929
+ if (target && target.owned_by_connection_id) {
2930
+ return yield* Effect10.fail(
2931
+ new SecretOwnedByConnectionError({
2932
+ secretId: SecretId.make(id),
2933
+ connectionId: ConnectionId.make(
2934
+ target.owned_by_connection_id
2935
+ )
2936
+ })
2937
+ );
2938
+ }
2939
+ const targetScope = target?.scope_id ?? scopeIds[0];
2940
+ const deleters = [...secretProviders.values()].filter(
2941
+ (p) => !!(p.writable && p.delete)
2942
+ );
2943
+ yield* Effect10.all(
2944
+ deleters.map((p) => p.delete(id, targetScope)),
2945
+ { concurrency: "unbounded" }
2946
+ );
2947
+ if (target) {
2948
+ yield* core.delete({
2949
+ model: "secret",
2950
+ where: [
2951
+ { field: "id", value: id },
2952
+ { field: "scope_id", value: targetScope }
2953
+ ]
2954
+ });
2955
+ }
2956
+ });
2957
+ const secretsList = () => Effect10.gen(function* () {
2958
+ const byId = /* @__PURE__ */ new Map();
2959
+ const allRows = yield* core.findMany({ model: "secret" });
2960
+ const connectionOwnedIds = new Set(
2961
+ allRows.filter((r) => r.owned_by_connection_id).map((r) => r.id)
2962
+ );
2963
+ const rows = allRows.filter((r) => !r.owned_by_connection_id);
2964
+ const precedence = /* @__PURE__ */ new Map();
2965
+ scopeIds.forEach((id, index) => precedence.set(id, index));
2966
+ const pick = (row) => {
2967
+ const existing = byId.get(row.id);
2968
+ const incomingScope = row.scope_id;
2969
+ const incomingRank = precedence.get(incomingScope) ?? Number.MAX_SAFE_INTEGER;
2970
+ if (existing) {
2971
+ const existingRank = precedence.get(existing.scopeId) ?? Number.MAX_SAFE_INTEGER;
2972
+ if (existingRank <= incomingRank) return;
2973
+ }
2974
+ byId.set(
2975
+ row.id,
2976
+ new SecretRef({
2977
+ id: SecretId.make(row.id),
2978
+ scopeId: ScopeId.make(incomingScope),
2979
+ name: row.name,
2980
+ provider: row.provider,
2981
+ createdAt: row.created_at instanceof Date ? row.created_at : new Date(row.created_at)
2982
+ })
2983
+ );
2984
+ };
2985
+ for (const row of rows) {
2986
+ const hasBackingValue = yield* secretRouteHasBackingValue(row);
2987
+ if (hasBackingValue) pick(row);
2988
+ }
2989
+ const attribution = scopes[0].id;
2990
+ const listers = [...secretProviders.entries()].filter(
2991
+ ([, p]) => p.list
2992
+ );
2993
+ const lists = yield* Effect10.all(
2994
+ listers.map(
2995
+ ([key, p]) => p.list().pipe(
2996
+ Effect10.catch(() => Effect10.succeed([])),
2997
+ Effect10.map((entries) => ({ key, entries }))
2998
+ )
2999
+ ),
3000
+ { concurrency: "unbounded" }
3001
+ );
3002
+ for (const { key, entries } of lists) {
3003
+ for (const entry of entries) {
3004
+ if (byId.has(entry.id)) continue;
3005
+ if (connectionOwnedIds.has(entry.id)) continue;
3006
+ byId.set(
3007
+ entry.id,
3008
+ new SecretRef({
3009
+ id: SecretId.make(entry.id),
3010
+ scopeId: attribution,
3011
+ name: entry.name,
3012
+ provider: key,
3013
+ createdAt: /* @__PURE__ */ new Date()
3014
+ })
3015
+ );
3016
+ }
3017
+ }
3018
+ return Array.from(byId.values());
3019
+ });
3020
+ const secretsListForCtx = () => Effect10.gen(function* () {
3021
+ const list = yield* secretsList();
3022
+ return list.map((ref) => ({
3023
+ id: String(ref.id),
3024
+ name: ref.name,
3025
+ provider: ref.provider
3026
+ }));
3027
+ });
3028
+ const CONNECTION_REFRESH_SKEW_MS = 6e4;
3029
+ const decodeProviderState2 = Schema11.decodeUnknownOption(
3030
+ ConnectionProviderState
3031
+ );
3032
+ const rowToConnection = (row) => new ConnectionRef({
3033
+ id: ConnectionId.make(row.id),
3034
+ scopeId: ScopeId.make(row.scope_id),
3035
+ provider: row.provider,
3036
+ identityLabel: row.identity_label ?? null,
3037
+ accessTokenSecretId: SecretId.make(row.access_token_secret_id),
3038
+ refreshTokenSecretId: row.refresh_token_secret_id != null ? SecretId.make(row.refresh_token_secret_id) : null,
3039
+ expiresAt: row.expires_at != null ? Number(row.expires_at) : null,
3040
+ oauthScope: row.scope ?? null,
3041
+ providerState: Option.getOrNull(
3042
+ decodeProviderState2(decodeJsonColumn(row.provider_state))
3043
+ ),
3044
+ createdAt: row.created_at instanceof Date ? row.created_at : new Date(row.created_at),
3045
+ updatedAt: row.updated_at instanceof Date ? row.updated_at : new Date(row.updated_at)
3046
+ });
3047
+ const findInnermostConnectionRow = (id) => Effect10.gen(function* () {
3048
+ const rows = yield* core.findMany({
3049
+ model: "connection",
3050
+ where: [{ field: "id", value: id }]
3051
+ });
3052
+ return findInnermost(rows);
3053
+ });
3054
+ const connectionsGet = (id) => Effect10.gen(function* () {
3055
+ const row = yield* findInnermostConnectionRow(id);
3056
+ return row ? rowToConnection(row) : null;
3057
+ });
3058
+ const connectionsList = () => Effect10.gen(function* () {
3059
+ const rows = yield* core.findMany({ model: "connection" });
3060
+ const byId = /* @__PURE__ */ new Map();
3061
+ const byIdRank = /* @__PURE__ */ new Map();
3062
+ for (const row of rows) {
3063
+ const rank = scopeRank(row);
3064
+ const existing = byIdRank.get(row.id);
3065
+ if (existing === void 0 || rank < existing) {
3066
+ byId.set(row.id, row);
3067
+ byIdRank.set(row.id, rank);
3068
+ }
3069
+ }
3070
+ return [...byId.values()].map(rowToConnection);
3071
+ });
3072
+ const writeOwnedSecret = (params) => Effect10.gen(function* () {
3073
+ const target = secretProviders.get(params.provider);
3074
+ if (!target) {
3075
+ return yield* Effect10.fail(
3076
+ new StorageError3({
3077
+ message: `Unknown secret provider: ${params.provider}`,
3078
+ cause: void 0
3079
+ })
3080
+ );
3081
+ }
3082
+ if (!target.writable || !target.set) {
3083
+ return yield* Effect10.fail(
3084
+ new StorageError3({
3085
+ message: `Secret provider "${target.key}" is read-only`,
3086
+ cause: void 0
3087
+ })
3088
+ );
3089
+ }
3090
+ yield* target.set(params.id, params.value, params.scope);
3091
+ const now = /* @__PURE__ */ new Date();
3092
+ yield* core.delete({
3093
+ model: "secret",
3094
+ where: [
3095
+ { field: "id", value: params.id },
3096
+ { field: "scope_id", value: params.scope }
3097
+ ]
3098
+ });
3099
+ yield* core.create({
3100
+ model: "secret",
3101
+ data: {
3102
+ id: params.id,
3103
+ scope_id: params.scope,
3104
+ name: params.name,
3105
+ provider: target.key,
3106
+ owned_by_connection_id: params.ownedByConnectionId,
3107
+ created_at: now
3108
+ },
3109
+ forceAllowId: true
3110
+ });
3111
+ });
3112
+ const pickWritableProvider = (requested) => Effect10.gen(function* () {
3113
+ if (requested) {
3114
+ const p = secretProviders.get(requested);
3115
+ if (!p) {
3116
+ return yield* Effect10.fail(
3117
+ new StorageError3({
3118
+ message: `Unknown secret provider: ${requested}`,
3119
+ cause: void 0
3120
+ })
3121
+ );
3122
+ }
3123
+ return p;
3124
+ }
3125
+ for (const p of secretProviders.values()) {
3126
+ if (p.writable && p.set) return p;
3127
+ }
3128
+ return yield* Effect10.fail(
3129
+ new StorageError3({
3130
+ message: "No writable secret providers registered",
3131
+ cause: void 0
3132
+ })
3133
+ );
3134
+ });
3135
+ const connectionsCreate = (input) => Effect10.gen(function* () {
3136
+ if (!scopeIds.includes(input.scope)) {
3137
+ return yield* Effect10.fail(
3138
+ new StorageError3({
3139
+ message: `connections.create targets scope "${input.scope}" which is not in the executor's scope stack [${scopeIds.join(", ")}].`,
3140
+ cause: void 0
3141
+ })
3142
+ );
3143
+ }
3144
+ if (!resolveConnectionProvider(input.provider)) {
3145
+ return yield* Effect10.fail(
3146
+ new ConnectionProviderNotRegisteredError({
3147
+ provider: input.provider,
3148
+ connectionId: input.id
3149
+ })
3150
+ );
3151
+ }
3152
+ const writable = yield* pickWritableProvider();
3153
+ const now = /* @__PURE__ */ new Date();
3154
+ return yield* adapter.transaction(
3155
+ () => Effect10.gen(function* () {
3156
+ yield* core.delete({
3157
+ model: "connection",
3158
+ where: [
3159
+ { field: "id", value: input.id },
3160
+ { field: "scope_id", value: input.scope }
3161
+ ]
3162
+ });
3163
+ yield* writeOwnedSecret({
3164
+ id: input.accessToken.secretId,
3165
+ scope: input.scope,
3166
+ name: input.accessToken.name,
3167
+ value: input.accessToken.value,
3168
+ provider: writable.key,
3169
+ ownedByConnectionId: input.id
3170
+ });
3171
+ if (input.refreshToken) {
3172
+ yield* writeOwnedSecret({
3173
+ id: input.refreshToken.secretId,
3174
+ scope: input.scope,
3175
+ name: input.refreshToken.name,
3176
+ value: input.refreshToken.value,
3177
+ provider: writable.key,
3178
+ ownedByConnectionId: input.id
3179
+ });
3180
+ }
3181
+ yield* core.create({
3182
+ model: "connection",
3183
+ data: {
3184
+ id: input.id,
3185
+ scope_id: input.scope,
3186
+ provider: input.provider,
3187
+ identity_label: input.identityLabel ?? void 0,
3188
+ access_token_secret_id: input.accessToken.secretId,
3189
+ refresh_token_secret_id: input.refreshToken?.secretId ?? void 0,
3190
+ expires_at: input.expiresAt ?? void 0,
3191
+ scope: input.oauthScope ?? void 0,
3192
+ provider_state: input.providerState ?? void 0,
3193
+ created_at: now,
3194
+ updated_at: now
3195
+ },
3196
+ forceAllowId: true
3197
+ });
3198
+ return new ConnectionRef({
3199
+ id: input.id,
3200
+ scopeId: input.scope,
3201
+ provider: input.provider,
3202
+ identityLabel: input.identityLabel,
3203
+ accessTokenSecretId: input.accessToken.secretId,
3204
+ refreshTokenSecretId: input.refreshToken?.secretId ?? null,
3205
+ expiresAt: input.expiresAt,
3206
+ oauthScope: input.oauthScope,
3207
+ providerState: input.providerState,
3208
+ createdAt: now,
3209
+ updatedAt: now
3210
+ });
3211
+ })
3212
+ );
3213
+ });
3214
+ const connectionsUpdateTokens = (input) => Effect10.gen(function* () {
3215
+ const row = yield* findInnermostConnectionRow(input.id);
3216
+ if (!row) {
3217
+ return yield* Effect10.fail(
3218
+ new ConnectionNotFoundError({ connectionId: input.id })
3219
+ );
3220
+ }
3221
+ const writable = yield* pickWritableProvider();
3222
+ const accessName = `Connection ${input.id} access token`;
3223
+ const refreshName = `Connection ${input.id} refresh token`;
3224
+ return yield* adapter.transaction(
3225
+ () => Effect10.gen(function* () {
3226
+ yield* writeOwnedSecret({
3227
+ id: row.access_token_secret_id,
3228
+ scope: row.scope_id,
3229
+ name: accessName,
3230
+ value: input.accessToken,
3231
+ provider: writable.key,
3232
+ ownedByConnectionId: row.id
3233
+ });
3234
+ const rotatedRefresh = input.refreshToken ?? void 0;
3235
+ if (rotatedRefresh && row.refresh_token_secret_id) {
3236
+ yield* writeOwnedSecret({
3237
+ id: row.refresh_token_secret_id,
3238
+ scope: row.scope_id,
3239
+ name: refreshName,
3240
+ value: rotatedRefresh,
3241
+ provider: writable.key,
3242
+ ownedByConnectionId: row.id
3243
+ });
3244
+ }
3245
+ const now = /* @__PURE__ */ new Date();
3246
+ const patch = { updated_at: now };
3247
+ if (input.expiresAt !== void 0)
3248
+ patch.expires_at = input.expiresAt ?? void 0;
3249
+ if (input.oauthScope !== void 0)
3250
+ patch.scope = input.oauthScope ?? void 0;
3251
+ if (input.providerState !== void 0)
3252
+ patch.provider_state = input.providerState ?? void 0;
3253
+ if (input.identityLabel !== void 0)
3254
+ patch.identity_label = input.identityLabel ?? void 0;
3255
+ yield* core.update({
3256
+ model: "connection",
3257
+ where: [
3258
+ { field: "id", value: row.id },
3259
+ { field: "scope_id", value: row.scope_id }
3260
+ ],
3261
+ update: patch
3262
+ });
3263
+ const updated = yield* findInnermostConnectionRow(
3264
+ row.id
3265
+ );
3266
+ if (!updated) {
3267
+ return yield* Effect10.fail(
3268
+ new ConnectionNotFoundError({ connectionId: input.id })
3269
+ );
3270
+ }
3271
+ return rowToConnection(updated);
3272
+ })
3273
+ );
3274
+ });
3275
+ const connectionsSetIdentityLabel = (id, label) => Effect10.gen(function* () {
3276
+ const row = yield* findInnermostConnectionRow(id);
3277
+ if (!row) {
3278
+ return yield* Effect10.fail(
3279
+ new ConnectionNotFoundError({
3280
+ connectionId: ConnectionId.make(id)
3281
+ })
3282
+ );
3283
+ }
3284
+ yield* core.update({
3285
+ model: "connection",
3286
+ where: [
3287
+ { field: "id", value: id },
3288
+ { field: "scope_id", value: row.scope_id }
3289
+ ],
3290
+ update: {
3291
+ identity_label: label ?? void 0,
3292
+ updated_at: /* @__PURE__ */ new Date()
3293
+ }
3294
+ });
3295
+ });
3296
+ const connectionsRemove = (id) => Effect10.gen(function* () {
3297
+ const row = yield* findInnermostConnectionRow(id);
3298
+ if (!row) return;
3299
+ const scope = row.scope_id;
3300
+ yield* adapter.transaction(
3301
+ () => Effect10.gen(function* () {
3302
+ const owned = yield* core.findMany({
3303
+ model: "secret",
3304
+ where: [
3305
+ { field: "owned_by_connection_id", value: id },
3306
+ { field: "scope_id", value: scope }
3307
+ ]
3308
+ });
3309
+ const deleters = [...secretProviders.values()].filter(
3310
+ (p) => !!(p.writable && p.delete)
3311
+ );
3312
+ for (const secret of owned) {
3313
+ yield* Effect10.all(
3314
+ deleters.map(
3315
+ (p) => p.delete(secret.id, scope).pipe(
3316
+ Effect10.catchCause(
3317
+ (cause) => Effect10.logWarning(
3318
+ `Failed to delete connection-owned secret from provider ${p.key}`,
3319
+ cause
3320
+ ).pipe(Effect10.as(false))
3321
+ )
3322
+ )
3323
+ ),
3324
+ { concurrency: "unbounded" }
3325
+ );
3326
+ }
3327
+ yield* core.deleteMany({
3328
+ model: "secret",
3329
+ where: [
3330
+ { field: "owned_by_connection_id", value: id },
3331
+ { field: "scope_id", value: scope }
3332
+ ]
3333
+ });
3334
+ yield* core.delete({
3335
+ model: "connection",
3336
+ where: [
3337
+ { field: "id", value: id },
3338
+ { field: "scope_id", value: scope }
3339
+ ]
3340
+ });
3341
+ })
3342
+ );
3343
+ });
3344
+ const performRefresh = (ref) => Effect10.gen(function* () {
3345
+ const provider = resolveConnectionProvider(ref.provider);
3346
+ if (!provider) {
3347
+ return yield* Effect10.fail(
3348
+ new ConnectionProviderNotRegisteredError({
3349
+ provider: ref.provider,
3350
+ connectionId: ref.id
3351
+ })
3352
+ );
3353
+ }
3354
+ if (!provider.refresh) {
3355
+ return yield* Effect10.fail(
3356
+ new ConnectionRefreshNotSupportedError({
3357
+ connectionId: ref.id,
3358
+ provider: ref.provider
3359
+ })
3360
+ );
3361
+ }
3362
+ const refreshTokenValue = ref.refreshTokenSecretId ? yield* connectionSecretGet(ref.refreshTokenSecretId) : null;
3363
+ const rawResult = yield* Effect10.result(
3364
+ provider.refresh({
3365
+ connectionId: ref.id,
3366
+ scopeId: ref.scopeId,
3367
+ identityLabel: ref.identityLabel,
3368
+ refreshToken: refreshTokenValue,
3369
+ providerState: ref.providerState,
3370
+ oauthScope: ref.oauthScope
3371
+ })
3372
+ );
3373
+ if (Result2.isFailure(rawResult)) {
3374
+ const err = rawResult.failure;
3375
+ if (err.reauthRequired) {
3376
+ return yield* Effect10.fail(
3377
+ new ConnectionReauthRequiredError({
3378
+ connectionId: err.connectionId,
3379
+ provider: ref.provider,
3380
+ message: err.message
3381
+ })
3382
+ );
3383
+ }
3384
+ return yield* Effect10.fail(err);
3385
+ }
3386
+ const result = rawResult.success;
3387
+ yield* connectionsUpdateTokens({
3388
+ id: ref.id,
3389
+ accessToken: result.accessToken,
3390
+ refreshToken: result.refreshToken,
3391
+ expiresAt: result.expiresAt,
3392
+ oauthScope: result.oauthScope,
3393
+ providerState: result.providerState
3394
+ });
3395
+ return result.accessToken;
3396
+ });
3397
+ const connectionsAccessToken = (id) => Effect10.gen(function* () {
3398
+ const row = yield* findInnermostConnectionRow(id);
3399
+ if (!row) {
3400
+ return yield* Effect10.fail(
3401
+ new ConnectionNotFoundError({
3402
+ connectionId: ConnectionId.make(id)
3403
+ })
3404
+ );
3405
+ }
3406
+ const ref = rowToConnection(row);
3407
+ const now = Date.now();
3408
+ const needsRefresh = ref.expiresAt !== null && ref.expiresAt - CONNECTION_REFRESH_SKEW_MS <= now;
3409
+ if (!needsRefresh) {
3410
+ const current = yield* connectionSecretGet(
3411
+ ref.accessTokenSecretId
3412
+ );
3413
+ if (current !== null) return current;
3414
+ }
3415
+ const action = yield* refreshInFlightLock.withPermits(1)(
3416
+ Effect10.gen(function* () {
3417
+ const existing = refreshInFlight.get(id);
3418
+ if (existing) {
3419
+ return {
3420
+ kind: "await",
3421
+ deferred: existing
3422
+ };
3423
+ }
3424
+ const deferred = yield* Deferred.make();
3425
+ refreshInFlight.set(id, deferred);
3426
+ return { kind: "lead", deferred };
3427
+ })
3428
+ );
3429
+ if (action.kind === "await") {
3430
+ return yield* Deferred.await(action.deferred);
3431
+ }
3432
+ return yield* performRefresh(ref).pipe(
3433
+ Effect10.onExit(
3434
+ (exit) => refreshInFlightLock.withPermits(1)(
3435
+ Effect10.gen(function* () {
3436
+ yield* Deferred.done(action.deferred, exit);
3437
+ refreshInFlight.delete(id);
3438
+ })
3439
+ )
3440
+ )
3441
+ );
3442
+ });
3443
+ const connectionsListForCtx = () => connectionsList();
3444
+ const oauthBundle = makeOAuth2Service({
3445
+ adapter: core,
3446
+ rawAdapter: adapter,
3447
+ secretsGet: (id) => secretsGet(id).pipe(
3448
+ Effect10.catchTag(
3449
+ "SecretOwnedByConnectionError",
3450
+ () => Effect10.succeed(null)
3451
+ )
3452
+ ),
3453
+ secretsSet: (input) => secretsSet(input),
3454
+ connectionsCreate: (input) => connectionsCreate(input)
3455
+ });
3456
+ connectionProviders.set(
3457
+ oauthBundle.connectionProvider.key,
3458
+ oauthBundle.connectionProvider
3459
+ );
3460
+ for (const plugin of plugins) {
3461
+ if (runtimes.has(plugin.id)) {
3462
+ return yield* Effect10.fail(
3463
+ new Error(`Duplicate plugin id: ${plugin.id}`)
3464
+ );
3465
+ }
3466
+ const storageDeps = {
3467
+ scopes,
3468
+ adapter: typedAdapter(adapter),
3469
+ // Blob keys are namespaced by `<scope>/<plugin>` so two tenants
3470
+ // sharing a backing BlobStore can't collide or leak on the
3471
+ // same `(plugin, key)` pair. The store's `get`/`has` walk the
3472
+ // scope stack (innermost first); `put`/`delete` require the
3473
+ // plugin to name a target scope explicitly.
3474
+ blobs: pluginBlobStore(blobs, scopeIds, plugin.id)
3475
+ };
3476
+ const storage = plugin.storage(storageDeps);
3477
+ const ctx = {
3478
+ scopes,
3479
+ storage,
3480
+ core: {
3481
+ sources: {
3482
+ register: (input) => Effect10.gen(function* () {
3483
+ if (staticSources.has(input.id)) {
3484
+ return yield* Effect10.fail(
3485
+ new StorageError3({
3486
+ message: `Source id "${input.id}" collides with a static source`,
3487
+ cause: void 0
3488
+ })
3489
+ );
3490
+ }
3491
+ for (const tool of input.tools) {
3492
+ const fqid = `${input.id}.${tool.name}`;
3493
+ if (staticTools.has(fqid)) {
3494
+ return yield* Effect10.fail(
3495
+ new StorageError3({
3496
+ message: `Tool id "${fqid}" collides with a static tool`,
3497
+ cause: void 0
3498
+ })
3499
+ );
3500
+ }
3501
+ }
3502
+ yield* adapter.transaction(
3503
+ () => writeSourceInput(core, plugin.id, input)
3504
+ );
3505
+ }),
3506
+ unregister: (sourceId) => (
3507
+ // `unregister` is scoped to a specific source row — look up
3508
+ // its scope before deleting so the tool/definition sweep
3509
+ // only touches rows at that scope. Walk the full stack and
3510
+ // pick the innermost-scope shadow so an inner-scope caller
3511
+ // can't accidentally (via non-deterministic findOne
3512
+ // iteration order) unregister the outer-scope source and
3513
+ // wipe a bystander's data at the same time.
3514
+ adapter.transaction(
3515
+ () => Effect10.gen(function* () {
3516
+ const rows = yield* core.findMany({
3517
+ model: "source",
3518
+ where: [{ field: "id", value: sourceId }]
3519
+ });
3520
+ const row = findInnermost(rows);
3521
+ if (!row) return;
3522
+ yield* deleteSourceById(
3523
+ core,
3524
+ sourceId,
3525
+ row.scope_id
3526
+ );
3527
+ })
3528
+ )
3529
+ ),
3530
+ update: (input) => core.update({
3531
+ model: "source",
3532
+ where: [
3533
+ { field: "id", value: input.id },
3534
+ { field: "scope_id", value: input.scope }
3535
+ ],
3536
+ update: {
3537
+ ...input.name !== void 0 ? { name: input.name } : {},
3538
+ ...input.url !== void 0 ? { url: input.url ?? void 0 } : {},
3539
+ updated_at: /* @__PURE__ */ new Date()
3540
+ }
3541
+ }).pipe(Effect10.asVoid)
3542
+ },
3543
+ definitions: {
3544
+ register: (input) => adapter.transaction(
3545
+ () => writeDefinitions(core, plugin.id, input)
3546
+ )
3547
+ }
3548
+ },
3549
+ secrets: {
3550
+ get: (id) => secretsGet(id),
3551
+ list: () => secretsListForCtx(),
3552
+ set: (input) => secretsSet(input),
3553
+ remove: (id) => secretsRemove(id)
3554
+ },
3555
+ connections: {
3556
+ get: (id) => connectionsGet(id),
3557
+ list: () => connectionsListForCtx(),
3558
+ create: (input) => connectionsCreate(input),
3559
+ updateTokens: (input) => connectionsUpdateTokens(input),
3560
+ setIdentityLabel: (id, label) => connectionsSetIdentityLabel(id, label),
3561
+ accessToken: (id) => connectionsAccessToken(id),
3562
+ remove: (id) => connectionsRemove(id)
3563
+ },
3564
+ oauth: oauthBundle.service,
3565
+ // Open one real tx boundary and route every nested write inside
3566
+ // `effect` through that same handle via the activeAdapterRef —
3567
+ // see buildAdapterRouter above. Caller-typed errors (`E`)
3568
+ // propagate unchanged; storage failures also stay typed
3569
+ // (`StorageFailure`) so the HTTP edge wrapper can translate them.
3570
+ transaction: (effect) => adapter.transaction(() => effect)
3571
+ };
3572
+ const extension = plugin.extension ? plugin.extension(ctx) : {};
3573
+ if (plugin.extension) {
3574
+ extensions[plugin.id] = extension;
3575
+ }
3576
+ const decls = plugin.staticSources ? plugin.staticSources(extension) : [];
3577
+ for (const source of decls) {
3578
+ if (staticSources.has(source.id)) {
3579
+ return yield* Effect10.fail(
3580
+ new Error(
3581
+ `Duplicate static source id: ${source.id} (plugin ${plugin.id})`
3582
+ )
3583
+ );
3584
+ }
3585
+ staticSources.set(source.id, { source, pluginId: plugin.id });
3586
+ for (const tool of source.tools) {
3587
+ const fqid = `${source.id}.${tool.name}`;
3588
+ if (staticTools.has(fqid)) {
3589
+ return yield* Effect10.fail(
3590
+ new Error(
3591
+ `Duplicate static tool id: ${fqid} (plugin ${plugin.id})`
3592
+ )
3593
+ );
3594
+ }
3595
+ staticTools.set(fqid, {
3596
+ source,
3597
+ tool,
3598
+ pluginId: plugin.id,
3599
+ ctx
3600
+ });
3601
+ }
3602
+ }
3603
+ runtimes.set(plugin.id, { plugin, storage, ctx });
3604
+ if (plugin.secretProviders) {
3605
+ const raw = typeof plugin.secretProviders === "function" ? plugin.secretProviders(ctx) : plugin.secretProviders;
3606
+ const providers = Effect10.isEffect(raw) ? yield* raw : raw;
3607
+ for (const provider of providers) {
3608
+ if (secretProviders.has(provider.key)) {
3609
+ return yield* Effect10.fail(
3610
+ new Error(
3611
+ `Duplicate secret provider key: ${provider.key} (from plugin ${plugin.id})`
3612
+ )
3613
+ );
3614
+ }
3615
+ secretProviders.set(provider.key, provider);
3616
+ }
3617
+ }
3618
+ if (plugin.connectionProviders) {
3619
+ const raw = typeof plugin.connectionProviders === "function" ? plugin.connectionProviders(ctx) : plugin.connectionProviders;
3620
+ const providers = Effect10.isEffect(raw) ? yield* raw : raw;
3621
+ for (const provider of providers) {
3622
+ if (connectionProviders.has(provider.key)) {
3623
+ return yield* Effect10.fail(
3624
+ new Error(
3625
+ `Duplicate connection provider key: ${provider.key} (from plugin ${plugin.id})`
3626
+ )
3627
+ );
3628
+ }
3629
+ connectionProviders.set(provider.key, provider);
3630
+ }
3631
+ }
3632
+ }
3633
+ const listSources = () => Effect10.gen(function* () {
3634
+ const dynamic = yield* core.findMany({ model: "source" });
3635
+ const byId = /* @__PURE__ */ new Map();
3636
+ const byIdRank = /* @__PURE__ */ new Map();
3637
+ for (const row of dynamic) {
3638
+ const rank = scopeRank(row);
3639
+ const existing = byIdRank.get(row.id);
3640
+ if (existing === void 0 || rank < existing) {
3641
+ byId.set(row.id, row);
3642
+ byIdRank.set(row.id, rank);
3643
+ }
3644
+ }
3645
+ const dynamicDeduped = [...byId.values()];
3646
+ const staticList = [];
3647
+ for (const { source, pluginId } of staticSources.values()) {
3648
+ staticList.push(staticDeclToSource(source, pluginId));
3649
+ }
3650
+ const merged = [...staticList, ...dynamicDeduped.map(rowToSource)];
3651
+ yield* Effect10.annotateCurrentSpan({
3652
+ "executor.sources.static_count": staticList.length,
3653
+ "executor.sources.dynamic_count": dynamicDeduped.length
3654
+ });
3655
+ return merged;
3656
+ }).pipe(Effect10.withSpan("executor.sources.list"));
3657
+ const resolveAnnotationsFor = (rows) => Effect10.gen(function* () {
3658
+ const result = /* @__PURE__ */ new Map();
3659
+ if (rows.length === 0) return result;
3660
+ const groups = /* @__PURE__ */ new Map();
3661
+ for (const row of rows) {
3662
+ const key = `${row.plugin_id}\0${row.source_id}`;
3663
+ const bucket = groups.get(key);
3664
+ if (bucket) bucket.push(row);
3665
+ else groups.set(key, [row]);
3666
+ }
3667
+ const maps = yield* Effect10.forEach(
3668
+ [...groups],
3669
+ ([key, groupRows]) => Effect10.gen(function* () {
3670
+ const [pluginId, sourceId] = key.split("\0");
3671
+ const runtime = runtimes.get(pluginId);
3672
+ if (!runtime?.plugin.resolveAnnotations) return void 0;
3673
+ return yield* runtime.plugin.resolveAnnotations({
3674
+ ctx: runtime.ctx,
3675
+ sourceId,
3676
+ toolRows: groupRows
3677
+ });
3678
+ }),
3679
+ { concurrency: "unbounded" }
3680
+ );
3681
+ for (const map of maps) {
3682
+ if (!map) continue;
3683
+ for (const [toolId, annotations] of Object.entries(map)) {
3684
+ result.set(toolId, annotations);
3685
+ }
3686
+ }
3687
+ return result;
3688
+ });
3689
+ const listTools = (filter) => Effect10.gen(function* () {
3690
+ const dynamic = yield* core.findMany({
3691
+ model: "tool",
3692
+ where: filter?.sourceId ? [{ field: "source_id", value: filter.sourceId }] : void 0
3693
+ });
3694
+ const byId = /* @__PURE__ */ new Map();
3695
+ const byIdRank = /* @__PURE__ */ new Map();
3696
+ for (const row of dynamic) {
3697
+ const rank = scopeRank(row);
3698
+ const existing = byIdRank.get(row.id);
3699
+ if (existing === void 0 || rank < existing) {
3700
+ byId.set(row.id, row);
3701
+ byIdRank.set(row.id, rank);
3702
+ }
3703
+ }
3704
+ const dynamicDeduped = [...byId.values()];
3705
+ const annotations = filter?.includeAnnotations === false ? /* @__PURE__ */ new Map() : yield* resolveAnnotationsFor(dynamicDeduped).pipe(
3706
+ Effect10.withSpan("executor.tools.list.annotations")
3707
+ );
3708
+ const out = [];
3709
+ for (const entry of staticTools.values()) {
3710
+ out.push(staticDeclToTool(entry.source, entry.tool, entry.pluginId));
3711
+ }
3712
+ for (const row of dynamicDeduped) {
3713
+ out.push(rowToTool(row, annotations.get(row.id)));
3714
+ }
3715
+ const filtered = filter ? out.filter((t) => toolMatchesFilter(t, filter)) : out;
3716
+ let result = filtered;
3717
+ let blockedCount = 0;
3718
+ if (filter?.includeBlocked !== true) {
3719
+ const policies = yield* loadAllPolicies();
3720
+ if (policies.length > 0) {
3721
+ const kept = [];
3722
+ for (const tool of filtered) {
3723
+ const match = resolveToolPolicy(tool.id, policies, scopeRank);
3724
+ if (match?.action === "block") {
3725
+ blockedCount++;
3726
+ continue;
3727
+ }
3728
+ kept.push(tool);
3729
+ }
3730
+ result = kept;
3731
+ }
3732
+ }
3733
+ yield* Effect10.annotateCurrentSpan({
3734
+ "executor.tools.static_count": staticTools.size,
3735
+ "executor.tools.dynamic_count": dynamicDeduped.length,
3736
+ "executor.tools.result_count": result.length,
3737
+ "executor.tools.blocked_count": blockedCount
3738
+ });
3739
+ return result;
3740
+ }).pipe(Effect10.withSpan("executor.tools.list"));
3741
+ const loadDefinitionsForSource = (sourceId) => Effect10.gen(function* () {
3742
+ const defRows = yield* core.findMany({
3743
+ model: "definition",
3744
+ where: [{ field: "source_id", value: sourceId }]
3745
+ });
3746
+ const winners = /* @__PURE__ */ new Map();
3747
+ for (const row of defRows) {
3748
+ const rank = scopeRank(row);
3749
+ const existing = winners.get(row.name);
3750
+ if (!existing || rank < existing.rank) {
3751
+ winners.set(row.name, { row, rank });
3752
+ }
3753
+ }
3754
+ const out = {};
3755
+ for (const [name, { row }] of winners) out[name] = row.schema;
3756
+ return out;
3757
+ });
3758
+ const buildToolSchemaView = (opts) => Effect10.gen(function* () {
3759
+ const defs = opts.sourceId ? yield* loadDefinitionsForSource(opts.sourceId).pipe(
3760
+ Effect10.withSpan("executor.tool.schema.load_defs")
3761
+ ) : {};
3762
+ const attachDefs = (schema2) => {
3763
+ if (schema2 == null || typeof schema2 !== "object") return schema2;
3764
+ if (Object.keys(defs).length === 0) return schema2;
3765
+ return { ...schema2, $defs: defs };
3766
+ };
3767
+ const inputSchema = attachDefs(opts.rawInput);
3768
+ const outputSchema = attachDefs(opts.rawOutput);
3769
+ const defsMap = new Map(Object.entries(defs));
3770
+ const preview = yield* Effect10.sync(
3771
+ () => buildToolTypeScriptPreview({ inputSchema, outputSchema, defs: defsMap })
3772
+ ).pipe(
3773
+ Effect10.withSpan("schema.compile.preview", {
3774
+ attributes: {
3775
+ "schema.kind": "tool.preview",
3776
+ "schema.has_input": inputSchema !== void 0,
3777
+ "schema.has_output": outputSchema !== void 0,
3778
+ "schema.def_count": defsMap.size
3779
+ }
3780
+ })
3781
+ );
3782
+ return new ToolSchema({
3783
+ id: ToolId.make(opts.toolId),
3784
+ name: opts.name,
3785
+ description: opts.description,
3786
+ inputSchema,
3787
+ outputSchema,
3788
+ inputTypeScript: preview.inputTypeScript ?? void 0,
3789
+ outputTypeScript: preview.outputTypeScript ?? void 0,
3790
+ typeScriptDefinitions: preview.typeScriptDefinitions ?? void 0
3791
+ });
3792
+ });
3793
+ const toolSchema = (toolId) => Effect10.gen(function* () {
3794
+ const staticEntry = staticTools.get(toolId);
3795
+ if (staticEntry) {
3796
+ yield* Effect10.annotateCurrentSpan({
3797
+ "executor.tool.dispatch_path": "static",
3798
+ "executor.source_id": staticEntry.source.id,
3799
+ "executor.source_kind": staticEntry.source.kind
3800
+ });
3801
+ return yield* buildToolSchemaView({
3802
+ toolId,
3803
+ name: staticEntry.tool.name,
3804
+ description: staticEntry.tool.description,
3805
+ sourceId: void 0,
3806
+ rawInput: staticEntry.tool.inputSchema,
3807
+ rawOutput: staticEntry.tool.outputSchema
3808
+ });
3809
+ }
3810
+ const rows = yield* core.findMany({
3811
+ model: "tool",
3812
+ where: [{ field: "id", value: toolId }]
3813
+ }).pipe(Effect10.withSpan("executor.tool.resolve"));
3814
+ const row = findInnermost(rows);
3815
+ if (!row) return null;
3816
+ yield* Effect10.annotateCurrentSpan({
3817
+ "executor.tool.dispatch_path": "dynamic",
3818
+ "executor.source_id": row.source_id,
3819
+ "executor.plugin_id": row.plugin_id
3820
+ });
3821
+ return yield* buildToolSchemaView({
3822
+ toolId,
3823
+ name: row.name,
3824
+ description: row.description,
3825
+ sourceId: row.source_id,
3826
+ rawInput: decodeJsonColumn(row.input_schema),
3827
+ rawOutput: decodeJsonColumn(row.output_schema)
3828
+ });
3829
+ }).pipe(
3830
+ Effect10.withSpan("executor.tool.schema", {
3831
+ attributes: { "mcp.tool.name": toolId }
3832
+ })
3833
+ );
3834
+ const toolsDefinitions = () => Effect10.gen(function* () {
3835
+ const rows = yield* core.findMany({ model: "definition" });
3836
+ const winners = /* @__PURE__ */ new Map();
3837
+ for (const row of rows) {
3838
+ const key = `${row.source_id}\0${row.name}`;
3839
+ const rank = scopeRank(row);
3840
+ const existing = winners.get(key);
3841
+ if (!existing || rank < existing.rank) {
3842
+ winners.set(key, { row, rank });
3843
+ }
3844
+ }
3845
+ const out = {};
3846
+ for (const { row } of winners.values()) {
3847
+ let bucket = out[row.source_id];
3848
+ if (!bucket) {
3849
+ bucket = {};
3850
+ out[row.source_id] = bucket;
3851
+ }
3852
+ bucket[row.name] = row.schema;
3853
+ }
3854
+ return out;
3855
+ });
3856
+ const defaultElicitationHandler = resolveElicitationHandler(
3857
+ config.onElicitation
3858
+ );
3859
+ const pickHandler = (options) => options?.onElicitation ? resolveElicitationHandler(options.onElicitation) : defaultElicitationHandler;
3860
+ const buildElicit = (toolId, args, handler) => {
3861
+ return (request) => Effect10.gen(function* () {
3862
+ const tid = ToolId.make(toolId);
3863
+ const response = yield* handler({
3864
+ toolId: tid,
3865
+ args,
3866
+ request
3867
+ });
3868
+ if (response.action !== "accept") {
3869
+ return yield* new ElicitationDeclinedError({
3870
+ toolId: tid,
3871
+ action: response.action
3872
+ });
3873
+ }
3874
+ return response;
3875
+ });
3876
+ };
3877
+ const loadAllPolicies = () => core.findMany({ model: "tool_policy" });
3878
+ const resolveToolPolicyForId = (toolId) => Effect10.gen(function* () {
3879
+ const policies = yield* loadAllPolicies();
3880
+ return resolveToolPolicy(toolId, policies, scopeRank);
3881
+ });
3882
+ const enforceApproval = (annotations, toolId, args, policy, handler) => Effect10.gen(function* () {
3883
+ if (policy?.action === "approve") return;
3884
+ const policyForcesApproval = policy?.action === "require_approval";
3885
+ if (!policyForcesApproval && !annotations?.requiresApproval) return;
3886
+ const tid = ToolId.make(toolId);
3887
+ const message = annotations?.approvalDescription ? annotations.approvalDescription : policyForcesApproval && policy ? `Approve ${toolId}? (matched policy: ${policy.pattern})` : `Approve ${toolId}?`;
3888
+ const request = new FormElicitation({
3889
+ message,
3890
+ requestedSchema: {}
3891
+ });
3892
+ const response = yield* handler({ toolId: tid, args, request });
3893
+ if (response.action !== "accept") {
3894
+ return yield* new ElicitationDeclinedError({
3895
+ toolId: tid,
3896
+ action: response.action
3897
+ });
3898
+ }
3899
+ });
3900
+ const invokeTool = (toolId, args, options) => {
3901
+ const handler = pickHandler(options);
3902
+ return Effect10.gen(function* () {
3903
+ const wrapInvocationError = (effect) => effect.pipe(
3904
+ Effect10.mapError(
3905
+ (cause) => new ToolInvocationError({
3906
+ toolId: ToolId.make(toolId),
3907
+ message: cause instanceof Error ? cause.message : String(cause),
3908
+ cause
3909
+ })
3910
+ )
3911
+ );
3912
+ const policy = yield* resolveToolPolicyForId(toolId).pipe(
3913
+ Effect10.withSpan("executor.tool.resolve_policy")
3914
+ );
3915
+ if (policy?.action === "block") {
3916
+ return yield* new ToolBlockedError({
3917
+ toolId: ToolId.make(toolId),
3918
+ pattern: policy.pattern
3919
+ });
3920
+ }
3921
+ const staticEntry = staticTools.get(toolId);
3922
+ if (staticEntry) {
3923
+ yield* Effect10.annotateCurrentSpan({
3924
+ "executor.tool.dispatch_path": "static",
3925
+ "executor.source_id": staticEntry.source.id,
3926
+ "executor.source_kind": staticEntry.source.kind,
3927
+ "executor.plugin_id": staticEntry.pluginId
3928
+ });
3929
+ yield* enforceApproval(
3930
+ staticEntry.tool.annotations,
3931
+ toolId,
3932
+ args,
3933
+ policy,
3934
+ handler
3935
+ ).pipe(Effect10.withSpan("executor.tool.enforce_approval"));
3936
+ return yield* wrapInvocationError(
3937
+ staticEntry.tool.handler({
3938
+ ctx: staticEntry.ctx,
3939
+ args,
3940
+ elicit: buildElicit(toolId, args, handler)
3941
+ })
3942
+ ).pipe(Effect10.withSpan("executor.tool.handler"));
3943
+ }
3944
+ const toolRows = yield* core.findMany({
3945
+ model: "tool",
3946
+ where: [{ field: "id", value: toolId }]
3947
+ }).pipe(Effect10.withSpan("executor.tool.resolve"));
3948
+ const row = findInnermost(toolRows);
3949
+ if (!row) {
3950
+ return yield* new ToolNotFoundError({
3951
+ toolId: ToolId.make(toolId)
3952
+ });
3953
+ }
3954
+ yield* Effect10.annotateCurrentSpan({
3955
+ "executor.tool.dispatch_path": "dynamic",
3956
+ "executor.source_id": row.source_id,
3957
+ "executor.plugin_id": row.plugin_id
3958
+ });
3959
+ const runtime = runtimes.get(row.plugin_id);
3960
+ if (!runtime) {
3961
+ return yield* new PluginNotLoadedError({
3962
+ pluginId: row.plugin_id,
3963
+ toolId: ToolId.make(toolId)
3964
+ });
3965
+ }
3966
+ if (!runtime.plugin.invokeTool) {
3967
+ return yield* new NoHandlerError({
3968
+ toolId: ToolId.make(toolId),
3969
+ pluginId: row.plugin_id
3970
+ });
3971
+ }
3972
+ let annotations;
3973
+ if (policy?.action !== "approve" && runtime.plugin.resolveAnnotations) {
3974
+ const map = yield* runtime.plugin.resolveAnnotations({
3975
+ ctx: runtime.ctx,
3976
+ sourceId: row.source_id,
3977
+ toolRows: [row]
3978
+ }).pipe(Effect10.withSpan("executor.tool.resolve_annotations"));
3979
+ annotations = map[toolId];
3980
+ }
3981
+ yield* enforceApproval(annotations, toolId, args, policy, handler).pipe(
3982
+ Effect10.withSpan("executor.tool.enforce_approval")
3983
+ );
3984
+ return yield* wrapInvocationError(
3985
+ runtime.plugin.invokeTool({
3986
+ ctx: runtime.ctx,
3987
+ toolRow: row,
3988
+ args,
3989
+ elicit: buildElicit(toolId, args, handler)
3990
+ })
3991
+ ).pipe(Effect10.withSpan("executor.tool.handler"));
3992
+ }).pipe(
3993
+ Effect10.withSpan("executor.tool.invoke", {
3994
+ attributes: {
3995
+ "mcp.tool.name": toolId
3996
+ }
3997
+ })
3998
+ );
3999
+ };
4000
+ const removeSource = (sourceId) => Effect10.gen(function* () {
4001
+ if (staticSources.has(sourceId)) {
4002
+ return yield* new SourceRemovalNotAllowedError({ sourceId });
4003
+ }
4004
+ const sourceRows = yield* core.findMany({
4005
+ model: "source",
4006
+ where: [{ field: "id", value: sourceId }]
4007
+ });
4008
+ const sourceRow = findInnermost(sourceRows);
4009
+ if (!sourceRow) return;
4010
+ if (!sourceRow.can_remove) {
4011
+ return yield* new SourceRemovalNotAllowedError({ sourceId });
4012
+ }
4013
+ const runtime = runtimes.get(sourceRow.plugin_id);
4014
+ yield* adapter.transaction(
4015
+ () => Effect10.gen(function* () {
4016
+ if (runtime?.plugin.removeSource) {
4017
+ yield* runtime.plugin.removeSource({
4018
+ ctx: runtime.ctx,
4019
+ sourceId,
4020
+ scope: sourceRow.scope_id
4021
+ });
4022
+ }
4023
+ yield* deleteSourceById(
4024
+ core,
4025
+ sourceId,
4026
+ sourceRow.scope_id
4027
+ );
4028
+ })
4029
+ );
4030
+ });
4031
+ const refreshSource = (sourceId) => Effect10.gen(function* () {
4032
+ if (staticSources.has(sourceId)) return;
4033
+ const sourceRows = yield* core.findMany({
4034
+ model: "source",
4035
+ where: [{ field: "id", value: sourceId }]
4036
+ });
4037
+ const sourceRow = findInnermost(sourceRows);
4038
+ if (!sourceRow) return;
4039
+ const runtime = runtimes.get(sourceRow.plugin_id);
4040
+ if (runtime?.plugin.refreshSource) {
4041
+ yield* runtime.plugin.refreshSource({
4042
+ ctx: runtime.ctx,
4043
+ sourceId,
4044
+ scope: sourceRow.scope_id
4045
+ });
4046
+ }
4047
+ });
4048
+ const detectionConfidenceScore = (confidence) => {
4049
+ switch (confidence) {
4050
+ case "high":
4051
+ return 3;
4052
+ case "medium":
4053
+ return 2;
4054
+ case "low":
4055
+ return 1;
4056
+ }
4057
+ };
4058
+ const detectSource = (url) => Effect10.gen(function* () {
4059
+ const results = [];
4060
+ for (const runtime of runtimes.values()) {
4061
+ if (!runtime.plugin.detect) continue;
4062
+ const result = yield* runtime.plugin.detect({ ctx: runtime.ctx, url }).pipe(Effect10.catch(() => Effect10.succeed(null)));
4063
+ if (result) results.push(result);
4064
+ }
4065
+ return results.sort(
4066
+ (a, b) => detectionConfidenceScore(b.confidence) - detectionConfidenceScore(a.confidence)
4067
+ );
4068
+ });
4069
+ const sourceDefinitions = (sourceId) => loadDefinitionsForSource(sourceId);
4070
+ const secretsStatus = (id) => Effect10.gen(function* () {
4071
+ const rows = yield* secretRowsForId(id);
4072
+ if (rows.some((row) => row.owned_by_connection_id)) return "missing";
4073
+ for (const row of rows) {
4074
+ if (yield* secretRouteHasBackingValue(row)) return "resolved";
4075
+ }
4076
+ for (const provider of secretProviders.values()) {
4077
+ if (!provider.list) continue;
4078
+ const entries = yield* provider.list().pipe(Effect10.catch(() => Effect10.succeed([])));
4079
+ if (entries.some((e) => e.id === id)) return "resolved";
4080
+ }
4081
+ return "missing";
4082
+ });
4083
+ const policiesList = () => Effect10.gen(function* () {
4084
+ const rows = yield* loadAllPolicies();
4085
+ const sorted = [...rows].sort((a, b) => {
4086
+ const sa = scopeRank(a);
4087
+ const sb = scopeRank(b);
4088
+ if (sa !== sb) return sa - sb;
4089
+ return comparePolicyRow(a, b);
4090
+ });
4091
+ return sorted.map((row) => rowToToolPolicy(row));
4092
+ }).pipe(Effect10.withSpan("executor.policies.list"));
4093
+ const policiesCreate = (input) => Effect10.gen(function* () {
4094
+ if (!isValidPattern(input.pattern)) {
4095
+ return yield* new StorageError3({
4096
+ message: `Invalid tool policy pattern "${input.pattern}". Patterns must be "*" (every tool), an exact tool id ("a.b.c"), or a trailing wildcard ("a.b.*"). Leading "*" prefixes ("*foo", "*.foo") and "**" are not supported.`,
4097
+ cause: void 0
4098
+ });
4099
+ }
4100
+ if (!isToolPolicyAction(input.action)) {
4101
+ return yield* new StorageError3({
4102
+ message: `Invalid tool policy action "${String(input.action)}". Expected "approve" | "require_approval" | "block".`,
4103
+ cause: void 0
4104
+ });
4105
+ }
4106
+ let position = input.position;
4107
+ if (position === void 0) {
4108
+ const existing = yield* core.findMany({
4109
+ model: "tool_policy",
4110
+ where: [{ field: "scope_id", value: input.scope }]
4111
+ });
4112
+ let min = null;
4113
+ for (const row of existing) {
4114
+ const p = row.position;
4115
+ if (min === null || p < min) min = p;
4116
+ }
4117
+ position = generateKeyBetween(null, min);
4118
+ }
4119
+ const id = `pol_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;
4120
+ const now = /* @__PURE__ */ new Date();
4121
+ yield* core.create({
4122
+ model: "tool_policy",
4123
+ data: {
4124
+ id,
4125
+ scope_id: input.scope,
4126
+ pattern: input.pattern,
4127
+ action: input.action,
4128
+ position,
4129
+ created_at: now,
4130
+ updated_at: now
4131
+ },
4132
+ forceAllowId: true
4133
+ });
4134
+ return rowToToolPolicy({
4135
+ id,
4136
+ scope_id: input.scope,
4137
+ pattern: input.pattern,
4138
+ action: input.action,
4139
+ position,
4140
+ created_at: now,
4141
+ updated_at: now
4142
+ });
4143
+ }).pipe(Effect10.withSpan("executor.policies.create"));
4144
+ const policiesUpdate = (input) => Effect10.gen(function* () {
4145
+ if (input.pattern !== void 0 && !isValidPattern(input.pattern)) {
4146
+ return yield* new StorageError3({
4147
+ message: `Invalid tool policy pattern "${input.pattern}".`,
4148
+ cause: void 0
4149
+ });
4150
+ }
4151
+ if (input.action !== void 0 && !isToolPolicyAction(input.action)) {
4152
+ return yield* new StorageError3({
4153
+ message: `Invalid tool policy action "${String(input.action)}".`,
4154
+ cause: void 0
4155
+ });
4156
+ }
4157
+ const rows = yield* core.findMany({
4158
+ model: "tool_policy",
4159
+ where: [{ field: "id", value: input.id }]
4160
+ });
4161
+ const row = findInnermost(rows);
4162
+ if (!row) {
4163
+ return yield* new StorageError3({
4164
+ message: `Tool policy "${input.id}" not found.`,
4165
+ cause: void 0
4166
+ });
4167
+ }
4168
+ const updated = {
4169
+ ...row,
4170
+ pattern: input.pattern ?? row.pattern,
4171
+ action: input.action ?? row.action,
4172
+ position: input.position ?? row.position,
4173
+ updated_at: /* @__PURE__ */ new Date()
4174
+ };
4175
+ yield* core.update({
4176
+ model: "tool_policy",
4177
+ where: [
4178
+ { field: "id", value: input.id },
4179
+ { field: "scope_id", value: row.scope_id }
4180
+ ],
4181
+ update: {
4182
+ pattern: updated.pattern,
4183
+ action: updated.action,
4184
+ position: updated.position,
4185
+ updated_at: updated.updated_at
4186
+ }
4187
+ });
4188
+ return rowToToolPolicy(updated);
4189
+ }).pipe(Effect10.withSpan("executor.policies.update"));
4190
+ const policiesRemove = (id) => core.deleteMany({
4191
+ model: "tool_policy",
4192
+ where: [{ field: "id", value: id }]
4193
+ }).pipe(Effect10.asVoid, Effect10.withSpan("executor.policies.remove"));
4194
+ const policiesResolve = (toolId) => resolveToolPolicyForId(toolId).pipe(
4195
+ Effect10.withSpan("executor.policies.resolve")
4196
+ );
4197
+ const close = () => Effect10.gen(function* () {
4198
+ for (const runtime of runtimes.values()) {
4199
+ if (runtime.plugin.close) {
4200
+ yield* runtime.plugin.close();
4201
+ }
4202
+ }
4203
+ });
4204
+ const base = {
4205
+ scopes,
4206
+ tools: {
4207
+ list: listTools,
4208
+ schema: toolSchema,
4209
+ definitions: toolsDefinitions,
4210
+ invoke: invokeTool
4211
+ },
4212
+ sources: {
4213
+ list: listSources,
4214
+ remove: removeSource,
4215
+ refresh: refreshSource,
4216
+ detect: detectSource,
4217
+ definitions: sourceDefinitions
4218
+ },
4219
+ secrets: {
4220
+ get: secretsGet,
4221
+ status: secretsStatus,
4222
+ set: secretsSet,
4223
+ remove: secretsRemove,
4224
+ list: secretsList,
4225
+ providers: () => Effect10.sync(
4226
+ () => Array.from(secretProviders.keys())
4227
+ )
4228
+ },
4229
+ connections: {
4230
+ get: connectionsGet,
4231
+ list: connectionsList,
4232
+ create: connectionsCreate,
4233
+ updateTokens: connectionsUpdateTokens,
4234
+ setIdentityLabel: connectionsSetIdentityLabel,
4235
+ accessToken: connectionsAccessToken,
4236
+ remove: connectionsRemove,
4237
+ providers: () => Effect10.sync(
4238
+ () => Array.from(connectionProviders.keys())
4239
+ )
4240
+ },
4241
+ oauth: oauthBundle.service,
4242
+ policies: {
4243
+ list: policiesList,
4244
+ create: policiesCreate,
4245
+ update: policiesUpdate,
4246
+ remove: policiesRemove,
4247
+ resolve: policiesResolve
4248
+ },
4249
+ close
4250
+ };
4251
+ const toExecutor = (value) => value;
4252
+ return toExecutor(Object.assign(base, extensions));
4253
+ });
4254
+
4255
+ // src/scope.ts
4256
+ import { Schema as Schema12 } from "effect";
4257
+ var Scope = class extends Schema12.Class("Scope")({
4258
+ id: ScopeId,
4259
+ name: Schema12.String,
4260
+ createdAt: Schema12.Date
4261
+ }) {
4262
+ };
4263
+
4264
+ // src/secret-backed-value.ts
4265
+ import { Effect as Effect11, Schema as Schema13 } from "effect";
4266
+ var SecretBackedValue = Schema13.Union([
4267
+ Schema13.String,
4268
+ Schema13.Struct({
4269
+ secretId: Schema13.String,
4270
+ prefix: Schema13.optional(Schema13.String)
4271
+ })
4272
+ ]);
4273
+ var SecretBackedMap = Schema13.Record(Schema13.String, SecretBackedValue);
4274
+ var isSecretBackedRef = (value) => typeof value !== "string";
4275
+ var resolveSecretBackedMap = ({
4276
+ values,
4277
+ getSecret,
4278
+ onMissing,
4279
+ onError,
4280
+ missing = "fail"
4281
+ }) => {
4282
+ const entries = Object.entries(values ?? {});
4283
+ if (entries.length === 0) return Effect11.succeed(void 0);
4284
+ return Effect11.gen(function* () {
4285
+ const resolved = {};
4286
+ for (const [name, value] of entries) {
4287
+ if (typeof value === "string") {
4288
+ resolved[name] = value;
4289
+ continue;
4290
+ }
4291
+ const secret = yield* getSecret(value.secretId).pipe(
4292
+ Effect11.mapError((error) => onError?.(error, name, value) ?? error)
4293
+ );
4294
+ if (secret === null) {
4295
+ if (missing === "drop") continue;
4296
+ return yield* Effect11.fail(onMissing(name, value));
4297
+ }
4298
+ resolved[name] = value.prefix ? `${value.prefix}${secret}` : secret;
4299
+ }
4300
+ return Object.keys(resolved).length > 0 ? resolved : void 0;
4301
+ });
4302
+ };
4303
+
4304
+ // src/config.ts
4305
+ var defineExecutorConfig = (config) => config;
4306
+
4307
+ export {
4308
+ pluginBlobStore,
4309
+ makeInMemoryBlobStore,
4310
+ ScopeId,
4311
+ ToolId,
4312
+ SecretId,
4313
+ PolicyId,
4314
+ ConnectionId,
4315
+ ConnectionProviderState,
4316
+ ConnectionRef,
4317
+ TokenMaterial,
4318
+ CreateConnectionInput,
4319
+ ConnectionRefreshError,
4320
+ UpdateConnectionTokensInput,
4321
+ coreSchema,
4322
+ TOOL_POLICY_ACTIONS,
4323
+ isToolPolicyAction,
4324
+ FormElicitation,
4325
+ UrlElicitation,
4326
+ ElicitationAction,
4327
+ ElicitationResponse,
4328
+ ElicitationDeclinedError,
4329
+ ToolNotFoundError,
4330
+ ToolInvocationError,
4331
+ PluginNotLoadedError,
4332
+ NoHandlerError,
4333
+ ToolBlockedError,
4334
+ SourceNotFoundError,
4335
+ SourceRemovalNotAllowedError,
4336
+ SecretNotFoundError,
4337
+ SecretResolutionError,
4338
+ SecretOwnedByConnectionError,
4339
+ ConnectionNotFoundError,
4340
+ ConnectionProviderNotRegisteredError,
4341
+ ConnectionRefreshNotSupportedError,
4342
+ ConnectionReauthRequiredError,
4343
+ SecretRef,
4344
+ SetSecretInput,
4345
+ OAuthDynamicDcrStrategy,
4346
+ OAuthAuthorizationCodeStrategy,
4347
+ OAuthClientCredentialsStrategy,
4348
+ OAuthStrategy,
4349
+ OAuthProviderState,
4350
+ OAUTH2_PROVIDER_KEY,
4351
+ OAuthProbeError,
4352
+ OAuthStartError,
4353
+ OAuthCompleteError,
4354
+ OAuthSessionNotFoundError,
4355
+ OAUTH2_SESSION_TTL_MS,
4356
+ OAuth2Error,
4357
+ OAUTH2_REFRESH_SKEW_MS,
4358
+ OAUTH2_DEFAULT_TIMEOUT_MS,
4359
+ createPkceCodeVerifier,
4360
+ createPkceCodeChallenge,
4361
+ buildAuthorizationUrl,
4362
+ exchangeAuthorizationCode,
4363
+ exchangeClientCredentials,
4364
+ refreshAccessToken,
4365
+ shouldRefreshToken,
4366
+ OAuthDiscoveryError,
4367
+ OAuthProtectedResourceMetadataSchema,
4368
+ OAuthAuthorizationServerMetadataSchema,
4369
+ OAuthClientInformationSchema,
4370
+ discoverProtectedResourceMetadata,
4371
+ discoverAuthorizationServerMetadata,
4372
+ registerDynamicClient,
4373
+ beginDynamicAuthorization,
4374
+ makeOAuth2Service,
4375
+ matchPattern,
4376
+ isValidPattern,
4377
+ resolveToolPolicy,
4378
+ resolveEffectivePolicy,
4379
+ effectivePolicyFromSorted,
4380
+ rowToToolPolicy,
4381
+ ToolPolicyActionSchema,
4382
+ ToolSchema,
4383
+ SourceDetectionResult,
4384
+ schemaToTypeScriptPreview,
4385
+ schemaToTypeScriptPreviewWithDefs,
4386
+ buildToolTypeScriptPreview,
4387
+ collectSchemas,
4388
+ createExecutor,
4389
+ Scope,
4390
+ SecretBackedValue,
4391
+ SecretBackedMap,
4392
+ isSecretBackedRef,
4393
+ resolveSecretBackedMap,
4394
+ defineExecutorConfig
4395
+ };
4396
+ //# sourceMappingURL=chunk-6LMMN2GP.js.map