@executor-js/sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -29
- package/dist/api-errors.d.ts +10 -0
- package/dist/api-errors.d.ts.map +1 -0
- package/dist/blob.d.ts.map +1 -1
- package/dist/chunk-6SQWMOM4.js +51 -0
- package/dist/chunk-6SQWMOM4.js.map +1 -0
- package/dist/chunk-FPV6KONN.js +78 -0
- package/dist/chunk-FPV6KONN.js.map +1 -0
- package/dist/chunk-VLVPSIQ4.js +5532 -0
- package/dist/chunk-VLVPSIQ4.js.map +1 -0
- package/dist/client.d.ts +2 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +12 -18
- package/dist/client.js.map +1 -1
- package/dist/connections.d.ts +7 -0
- package/dist/connections.d.ts.map +1 -1
- package/dist/core-schema.d.ts +83 -3
- package/dist/core-schema.d.ts.map +1 -1
- package/dist/core.js +92 -52
- package/dist/core.js.map +1 -1
- package/dist/credential-bindings.d.ts +173 -0
- package/dist/credential-bindings.d.ts.map +1 -0
- package/dist/credential-bindings.test.d.ts +2 -0
- package/dist/credential-bindings.test.d.ts.map +1 -0
- package/dist/errors.d.ts +27 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/executor.d.ts +47 -13
- package/dist/executor.d.ts.map +1 -1
- package/dist/hosted-http-client.d.ts +17 -0
- package/dist/hosted-http-client.d.ts.map +1 -0
- package/dist/hosted-http-client.test.d.ts +2 -0
- package/dist/hosted-http-client.test.d.ts.map +1 -0
- package/dist/ids.d.ts +2 -0
- package/dist/ids.d.ts.map +1 -1
- package/dist/index.d.ts +14 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -18
- package/dist/index.js.map +1 -1
- package/dist/oauth-discovery.d.ts +19 -4
- package/dist/oauth-discovery.d.ts.map +1 -1
- package/dist/oauth-helpers.d.ts +21 -0
- package/dist/oauth-helpers.d.ts.map +1 -1
- package/dist/oauth-popup-types.d.ts.map +1 -1
- package/dist/oauth-service.d.ts +16 -5
- package/dist/oauth-service.d.ts.map +1 -1
- package/dist/oauth.d.ts +19 -16
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oxlint-plugin-executor.test.d.ts +2 -0
- package/dist/oxlint-plugin-executor.test.d.ts.map +1 -0
- package/dist/plugin.d.ts +59 -14
- package/dist/plugin.d.ts.map +1 -1
- package/dist/policies.d.ts +10 -15
- package/dist/policies.d.ts.map +1 -1
- package/dist/promise-executor.d.ts.map +1 -1
- package/dist/promise.d.ts +5 -3
- package/dist/promise.d.ts.map +1 -1
- package/dist/schema-types.d.ts.map +1 -1
- package/dist/scoped-adapter.d.ts +17 -2
- package/dist/scoped-adapter.d.ts.map +1 -1
- package/dist/secrets.d.ts +12 -0
- package/dist/secrets.d.ts.map +1 -1
- package/dist/test-config.d.ts +10 -0
- package/dist/test-config.d.ts.map +1 -0
- package/dist/testing.d.ts +30 -8
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +56 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +11 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/usage-visibility.test.d.ts +2 -0
- package/dist/usage-visibility.test.d.ts.map +1 -0
- package/dist/usages.d.ts +20 -0
- package/dist/usages.d.ts.map +1 -0
- package/package.json +25 -14
- package/dist/chunk-2WV7VSNL.js +0 -4440
- package/dist/chunk-2WV7VSNL.js.map +0 -1
package/dist/chunk-2WV7VSNL.js
DELETED
|
@@ -1,4440 +0,0 @@
|
|
|
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 DcrErrorBody = class extends Data4.TaggedError("DcrErrorBody") {
|
|
1037
|
-
};
|
|
1038
|
-
var DcrTransport = class extends Data4.TaggedError("DcrTransport") {
|
|
1039
|
-
};
|
|
1040
|
-
var buildDcrBody = (m) => {
|
|
1041
|
-
const body = { redirect_uris: [...m.redirect_uris] };
|
|
1042
|
-
if (m.client_name !== void 0) body.client_name = m.client_name;
|
|
1043
|
-
if (m.grant_types !== void 0) body.grant_types = [...m.grant_types];
|
|
1044
|
-
if (m.response_types !== void 0) body.response_types = [...m.response_types];
|
|
1045
|
-
if (m.token_endpoint_auth_method !== void 0) {
|
|
1046
|
-
body.token_endpoint_auth_method = m.token_endpoint_auth_method;
|
|
1047
|
-
}
|
|
1048
|
-
if (m.scope !== void 0) body.scope = m.scope;
|
|
1049
|
-
if (m.application_type !== void 0) body.application_type = m.application_type;
|
|
1050
|
-
if (m.client_uri !== void 0) body.client_uri = m.client_uri;
|
|
1051
|
-
if (m.logo_uri !== void 0) body.logo_uri = m.logo_uri;
|
|
1052
|
-
if (m.contacts !== void 0) body.contacts = [...m.contacts];
|
|
1053
|
-
if (m.software_id !== void 0) body.software_id = m.software_id;
|
|
1054
|
-
if (m.software_version !== void 0) body.software_version = m.software_version;
|
|
1055
|
-
if (m.extra) for (const [k, v] of Object.entries(m.extra)) body[k] = v;
|
|
1056
|
-
return body;
|
|
1057
|
-
};
|
|
1058
|
-
var interpretDcrFailure = (status, text) => {
|
|
1059
|
-
if (status >= 400 && status < 500) {
|
|
1060
|
-
const parsed = Result.try({
|
|
1061
|
-
try: () => text ? JSON.parse(text) : null,
|
|
1062
|
-
catch: () => null
|
|
1063
|
-
});
|
|
1064
|
-
const body = Result.isSuccess(parsed) ? parsed.success : null;
|
|
1065
|
-
if (body && typeof body === "object" && "error" in body && typeof body.error === "string" && body.error.length > 0) {
|
|
1066
|
-
const desc = "error_description" in body && typeof body.error_description === "string" ? body.error_description : void 0;
|
|
1067
|
-
return new DcrErrorBody({ status, error: body.error, error_description: desc });
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
return new DcrTransport({
|
|
1071
|
-
message: `Dynamic Client Registration endpoint returned status ${status}${text ? ` \u2014 ${text.slice(0, 200)}` : ""}`,
|
|
1072
|
-
status
|
|
1073
|
-
});
|
|
1074
|
-
};
|
|
1075
|
-
var registerDynamicClient = (input, options = {}) => Effect7.gen(function* () {
|
|
1076
|
-
const url = new URL(input.registrationEndpoint);
|
|
1077
|
-
if (url.protocol !== "https:" && !isLoopbackHttpUrl2(input.registrationEndpoint)) {
|
|
1078
|
-
return yield* new DcrTransport({
|
|
1079
|
-
message: `registration_endpoint must be HTTPS or a loopback HTTP URL (got ${url.protocol}//${url.host})`
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
const headers = {
|
|
1083
|
-
"content-type": "application/json",
|
|
1084
|
-
accept: "application/json"
|
|
1085
|
-
};
|
|
1086
|
-
if (input.initialAccessToken) {
|
|
1087
|
-
headers.authorization = `Bearer ${input.initialAccessToken}`;
|
|
1088
|
-
}
|
|
1089
|
-
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1090
|
-
const timeoutMs = options.timeoutMs ?? OAUTH2_DEFAULT_TIMEOUT_MS;
|
|
1091
|
-
const response = yield* Effect7.tryPromise({
|
|
1092
|
-
try: () => fetchImpl(input.registrationEndpoint, {
|
|
1093
|
-
method: "POST",
|
|
1094
|
-
headers,
|
|
1095
|
-
body: JSON.stringify(buildDcrBody(input.metadata)),
|
|
1096
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
1097
|
-
}),
|
|
1098
|
-
catch: (cause) => new DcrTransport({
|
|
1099
|
-
message: `Dynamic Client Registration request failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1100
|
-
cause
|
|
1101
|
-
})
|
|
1102
|
-
});
|
|
1103
|
-
if (response.status !== 200 && response.status !== 201) {
|
|
1104
|
-
const text2 = yield* Effect7.promise(
|
|
1105
|
-
() => response.text().catch(() => "")
|
|
1106
|
-
);
|
|
1107
|
-
return yield* interpretDcrFailure(response.status, text2);
|
|
1108
|
-
}
|
|
1109
|
-
const text = yield* Effect7.tryPromise({
|
|
1110
|
-
try: () => response.text(),
|
|
1111
|
-
catch: (cause) => new DcrTransport({
|
|
1112
|
-
message: "Dynamic Client Registration response could not be read",
|
|
1113
|
-
status: response.status,
|
|
1114
|
-
cause
|
|
1115
|
-
})
|
|
1116
|
-
});
|
|
1117
|
-
const json = yield* Effect7.try({
|
|
1118
|
-
try: () => JSON.parse(text),
|
|
1119
|
-
catch: (cause) => new DcrTransport({
|
|
1120
|
-
message: "Dynamic Client Registration response was not valid JSON",
|
|
1121
|
-
status: response.status,
|
|
1122
|
-
cause
|
|
1123
|
-
})
|
|
1124
|
-
});
|
|
1125
|
-
return yield* decodeClientInformation(json).pipe(
|
|
1126
|
-
Effect7.mapError(
|
|
1127
|
-
(err) => new OAuthDiscoveryError({
|
|
1128
|
-
message: `Dynamic Client Registration response is malformed: ${Schema7.isSchemaError(err) ? err.message : String(err)}`,
|
|
1129
|
-
cause: err
|
|
1130
|
-
})
|
|
1131
|
-
)
|
|
1132
|
-
);
|
|
1133
|
-
}).pipe(
|
|
1134
|
-
Effect7.catchTags({
|
|
1135
|
-
DcrErrorBody: (err) => Effect7.fail(
|
|
1136
|
-
discoveryError(
|
|
1137
|
-
`Dynamic Client Registration failed: ${err.error}${err.error_description ? ` \u2014 ${err.error_description}` : ""}`,
|
|
1138
|
-
{ status: err.status, cause: err }
|
|
1139
|
-
)
|
|
1140
|
-
),
|
|
1141
|
-
DcrTransport: (err) => Effect7.fail(
|
|
1142
|
-
discoveryError(`Dynamic Client Registration failed: ${err.message}`, {
|
|
1143
|
-
status: err.status,
|
|
1144
|
-
cause: err.cause ?? err
|
|
1145
|
-
})
|
|
1146
|
-
)
|
|
1147
|
-
})
|
|
1148
|
-
);
|
|
1149
|
-
var beginDynamicAuthorization = (input, options = {}) => Effect7.gen(function* () {
|
|
1150
|
-
const prior = input.previousState ?? {};
|
|
1151
|
-
const canSkipResourceDiscovery = prior.resourceMetadata !== void 0 || !!prior.authorizationServerUrl || !!prior.authorizationServerMetadata;
|
|
1152
|
-
const resource = canSkipResourceDiscovery ? prior.resourceMetadata ? {
|
|
1153
|
-
metadata: prior.resourceMetadata,
|
|
1154
|
-
metadataUrl: prior.resourceMetadataUrl ?? null
|
|
1155
|
-
} : null : yield* discoverProtectedResourceMetadata(input.endpoint, options);
|
|
1156
|
-
const authorizationServerUrl = (() => {
|
|
1157
|
-
if (prior.authorizationServerUrl) return prior.authorizationServerUrl;
|
|
1158
|
-
const fromResource = resource && resource.metadata.authorization_servers?.[0];
|
|
1159
|
-
if (fromResource) return fromResource;
|
|
1160
|
-
const u = new URL(input.endpoint);
|
|
1161
|
-
return `${u.protocol}//${u.host}`;
|
|
1162
|
-
})();
|
|
1163
|
-
const authServer = prior.authorizationServerMetadata && prior.authorizationServerMetadataUrl ? {
|
|
1164
|
-
metadata: prior.authorizationServerMetadata,
|
|
1165
|
-
metadataUrl: prior.authorizationServerMetadataUrl
|
|
1166
|
-
} : yield* discoverAuthorizationServerMetadata(
|
|
1167
|
-
authorizationServerUrl,
|
|
1168
|
-
options
|
|
1169
|
-
);
|
|
1170
|
-
if (!authServer) {
|
|
1171
|
-
return yield* Effect7.fail(
|
|
1172
|
-
discoveryError(
|
|
1173
|
-
`No OAuth authorization server metadata at ${authorizationServerUrl}`
|
|
1174
|
-
)
|
|
1175
|
-
);
|
|
1176
|
-
}
|
|
1177
|
-
const pkceMethods = authServer.metadata.code_challenge_methods_supported ?? [];
|
|
1178
|
-
if (pkceMethods.length > 0 && !pkceMethods.includes("S256")) {
|
|
1179
|
-
return yield* Effect7.fail(
|
|
1180
|
-
discoveryError(
|
|
1181
|
-
`Authorization server does not support PKCE S256 (advertised: ${pkceMethods.join(", ")})`
|
|
1182
|
-
)
|
|
1183
|
-
);
|
|
1184
|
-
}
|
|
1185
|
-
const responseTypes = authServer.metadata.response_types_supported ?? [];
|
|
1186
|
-
if (responseTypes.length > 0 && !responseTypes.includes("code")) {
|
|
1187
|
-
return yield* Effect7.fail(
|
|
1188
|
-
discoveryError(
|
|
1189
|
-
`Authorization server does not support response_type=code (advertised: ${responseTypes.join(", ")})`
|
|
1190
|
-
)
|
|
1191
|
-
);
|
|
1192
|
-
}
|
|
1193
|
-
const baseClientMetadata = {
|
|
1194
|
-
grant_types: ["authorization_code", "refresh_token"],
|
|
1195
|
-
response_types: ["code"],
|
|
1196
|
-
token_endpoint_auth_method: "none",
|
|
1197
|
-
client_name: "Executor",
|
|
1198
|
-
...input.clientMetadata ?? {},
|
|
1199
|
-
redirect_uris: input.clientMetadata?.redirect_uris ?? [input.redirectUrl]
|
|
1200
|
-
};
|
|
1201
|
-
const clientInformation = prior.clientInformation ?? (yield* (() => {
|
|
1202
|
-
const reg = authServer.metadata.registration_endpoint;
|
|
1203
|
-
if (!reg) {
|
|
1204
|
-
return Effect7.fail(
|
|
1205
|
-
discoveryError(
|
|
1206
|
-
"Authorization server does not advertise registration_endpoint \u2014 cannot auto-register a client"
|
|
1207
|
-
)
|
|
1208
|
-
);
|
|
1209
|
-
}
|
|
1210
|
-
return registerDynamicClient(
|
|
1211
|
-
{ registrationEndpoint: reg, metadata: baseClientMetadata },
|
|
1212
|
-
options
|
|
1213
|
-
);
|
|
1214
|
-
})());
|
|
1215
|
-
const codeVerifier = createPkceCodeVerifier();
|
|
1216
|
-
const codeChallenge = yield* Effect7.promise(
|
|
1217
|
-
() => createPkceCodeChallenge(codeVerifier)
|
|
1218
|
-
);
|
|
1219
|
-
const scopes = input.scopes ?? authServer.metadata.scopes_supported ?? [];
|
|
1220
|
-
const authorizationUrl = buildAuthorizationUrl({
|
|
1221
|
-
authorizationUrl: authServer.metadata.authorization_endpoint,
|
|
1222
|
-
clientId: clientInformation.client_id,
|
|
1223
|
-
redirectUrl: input.redirectUrl,
|
|
1224
|
-
scopes,
|
|
1225
|
-
state: input.state,
|
|
1226
|
-
codeChallenge
|
|
1227
|
-
});
|
|
1228
|
-
return {
|
|
1229
|
-
authorizationUrl,
|
|
1230
|
-
codeVerifier,
|
|
1231
|
-
state: {
|
|
1232
|
-
resourceMetadata: resource?.metadata ?? null,
|
|
1233
|
-
resourceMetadataUrl: resource?.metadataUrl ?? null,
|
|
1234
|
-
authorizationServerUrl,
|
|
1235
|
-
authorizationServerMetadataUrl: authServer.metadataUrl,
|
|
1236
|
-
authorizationServerMetadata: authServer.metadata,
|
|
1237
|
-
clientInformation
|
|
1238
|
-
}
|
|
1239
|
-
};
|
|
1240
|
-
});
|
|
1241
|
-
|
|
1242
|
-
// src/oauth-service.ts
|
|
1243
|
-
import { Effect as Effect8, Schema as Schema8 } from "effect";
|
|
1244
|
-
var OAuthAuthorizationServerMetadataJson = Schema8.Record(Schema8.String, Schema8.Unknown);
|
|
1245
|
-
var OAuthClientInformationJson = Schema8.Record(Schema8.String, Schema8.Unknown);
|
|
1246
|
-
var DynamicDcrSessionPayload = Schema8.Struct({
|
|
1247
|
-
kind: Schema8.Literal("dynamic-dcr"),
|
|
1248
|
-
identityLabel: Schema8.NullOr(Schema8.String),
|
|
1249
|
-
codeVerifier: Schema8.String,
|
|
1250
|
-
authorizationServerUrl: Schema8.String,
|
|
1251
|
-
authorizationServerMetadataUrl: Schema8.String,
|
|
1252
|
-
authorizationServerMetadata: OAuthAuthorizationServerMetadataJson,
|
|
1253
|
-
clientInformation: OAuthClientInformationJson,
|
|
1254
|
-
resourceMetadataUrl: Schema8.NullOr(Schema8.String),
|
|
1255
|
-
resourceMetadata: Schema8.NullOr(
|
|
1256
|
-
Schema8.Record(Schema8.String, Schema8.Unknown)
|
|
1257
|
-
),
|
|
1258
|
-
scopes: Schema8.Array(Schema8.String)
|
|
1259
|
-
});
|
|
1260
|
-
var AuthorizationCodeSessionPayload = Schema8.Struct({
|
|
1261
|
-
kind: Schema8.Literal("authorization-code"),
|
|
1262
|
-
identityLabel: Schema8.NullOr(Schema8.String),
|
|
1263
|
-
codeVerifier: Schema8.String,
|
|
1264
|
-
authorizationEndpoint: Schema8.String,
|
|
1265
|
-
tokenEndpoint: Schema8.String,
|
|
1266
|
-
issuerUrl: Schema8.NullOr(Schema8.String).pipe(Schema8.withDecodingDefaultType(Effect8.succeed(null))),
|
|
1267
|
-
clientIdSecretId: Schema8.String,
|
|
1268
|
-
clientSecretSecretId: Schema8.NullOr(Schema8.String),
|
|
1269
|
-
scopes: Schema8.Array(Schema8.String),
|
|
1270
|
-
scopeSeparator: Schema8.optional(Schema8.String),
|
|
1271
|
-
clientAuth: Schema8.Literals(["body", "basic"])
|
|
1272
|
-
});
|
|
1273
|
-
var OAuthSessionPayload = Schema8.Union([
|
|
1274
|
-
DynamicDcrSessionPayload,
|
|
1275
|
-
AuthorizationCodeSessionPayload
|
|
1276
|
-
]);
|
|
1277
|
-
var decodeSessionPayload = Schema8.decodeUnknownSync(OAuthSessionPayload);
|
|
1278
|
-
var encodeSessionPayload = Schema8.encodeSync(OAuthSessionPayload);
|
|
1279
|
-
var coerceJson = (value) => {
|
|
1280
|
-
if (typeof value !== "string") return value;
|
|
1281
|
-
try {
|
|
1282
|
-
return JSON.parse(value);
|
|
1283
|
-
} catch {
|
|
1284
|
-
return value;
|
|
1285
|
-
}
|
|
1286
|
-
};
|
|
1287
|
-
var stringArray = (value) => Array.isArray(value) ? value.filter((scope) => typeof scope === "string") : [];
|
|
1288
|
-
var originOrNull = (value) => {
|
|
1289
|
-
if (typeof value !== "string") return null;
|
|
1290
|
-
try {
|
|
1291
|
-
return new URL(value).origin;
|
|
1292
|
-
} catch {
|
|
1293
|
-
return null;
|
|
1294
|
-
}
|
|
1295
|
-
};
|
|
1296
|
-
var decodeProviderState = (value) => {
|
|
1297
|
-
const raw = coerceJson(value);
|
|
1298
|
-
const record = raw && typeof raw === "object" ? raw : null;
|
|
1299
|
-
if (record && !("kind" in record) && "flow" in record && "tokenUrl" in record) {
|
|
1300
|
-
const flow = record.flow;
|
|
1301
|
-
if (flow === "authorizationCode") {
|
|
1302
|
-
return Schema8.decodeUnknownSync(OAuthProviderState)({
|
|
1303
|
-
kind: "authorization-code",
|
|
1304
|
-
tokenEndpoint: record.tokenUrl,
|
|
1305
|
-
issuerUrl: originOrNull(record.authorizationEndpoint),
|
|
1306
|
-
clientIdSecretId: record.clientIdSecretId,
|
|
1307
|
-
clientSecretSecretId: record.clientSecretSecretId ?? null,
|
|
1308
|
-
clientAuth: "body",
|
|
1309
|
-
scope: stringArray(record.scopes).join(" ") || null
|
|
1310
|
-
});
|
|
1311
|
-
}
|
|
1312
|
-
if (flow === "clientCredentials") {
|
|
1313
|
-
return Schema8.decodeUnknownSync(OAuthProviderState)({
|
|
1314
|
-
kind: "client-credentials",
|
|
1315
|
-
tokenEndpoint: record.tokenUrl,
|
|
1316
|
-
clientIdSecretId: record.clientIdSecretId,
|
|
1317
|
-
clientSecretSecretId: record.clientSecretSecretId,
|
|
1318
|
-
scopes: stringArray(record.scopes),
|
|
1319
|
-
clientAuth: "body",
|
|
1320
|
-
scope: stringArray(record.scopes).join(" ") || null
|
|
1321
|
-
});
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
if (record && !("kind" in record) && "clientIdSecretId" in record && "scopes" in record) {
|
|
1325
|
-
const scopes = stringArray(record.scopes);
|
|
1326
|
-
return Schema8.decodeUnknownSync(OAuthProviderState)({
|
|
1327
|
-
kind: "authorization-code",
|
|
1328
|
-
tokenEndpoint: "https://oauth2.googleapis.com/token",
|
|
1329
|
-
issuerUrl: "https://accounts.google.com",
|
|
1330
|
-
clientIdSecretId: record.clientIdSecretId,
|
|
1331
|
-
clientSecretSecretId: record.clientSecretSecretId ?? null,
|
|
1332
|
-
clientAuth: "body",
|
|
1333
|
-
scope: scopes.join(" ") || null
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
if (record && !("kind" in record) && "clientInformation" in record && "endpoint" in record) {
|
|
1337
|
-
const clientInformation = record.clientInformation && typeof record.clientInformation === "object" ? record.clientInformation : null;
|
|
1338
|
-
return Schema8.decodeUnknownSync(OAuthProviderState)({
|
|
1339
|
-
kind: "dynamic-dcr",
|
|
1340
|
-
tokenEndpoint: typeof record.tokenEndpoint === "string" ? record.tokenEndpoint : record.authorizationServerMetadata && typeof record.authorizationServerMetadata === "object" && typeof record.authorizationServerMetadata.token_endpoint === "string" ? record.authorizationServerMetadata.token_endpoint : "",
|
|
1341
|
-
issuerUrl: record.authorizationServerMetadata && typeof record.authorizationServerMetadata === "object" && typeof record.authorizationServerMetadata.issuer === "string" ? record.authorizationServerMetadata.issuer : null,
|
|
1342
|
-
authorizationServerUrl: typeof record.authorizationServerUrl === "string" ? record.authorizationServerUrl : null,
|
|
1343
|
-
authorizationServerMetadataUrl: typeof record.authorizationServerMetadataUrl === "string" ? record.authorizationServerMetadataUrl : null,
|
|
1344
|
-
clientId: typeof clientInformation?.client_id === "string" ? clientInformation.client_id : "",
|
|
1345
|
-
clientSecretSecretId: null,
|
|
1346
|
-
clientAuth: "body",
|
|
1347
|
-
scope: null
|
|
1348
|
-
});
|
|
1349
|
-
}
|
|
1350
|
-
return Schema8.decodeUnknownSync(OAuthProviderState)(raw);
|
|
1351
|
-
};
|
|
1352
|
-
var defaultSessionId = () => {
|
|
1353
|
-
const crypto = globalThis.crypto;
|
|
1354
|
-
if (crypto?.randomUUID) return `oauth2_session_${crypto.randomUUID()}`;
|
|
1355
|
-
const bytes = new Uint8Array(16);
|
|
1356
|
-
crypto.getRandomValues(bytes);
|
|
1357
|
-
return `oauth2_session_${Array.from(
|
|
1358
|
-
bytes,
|
|
1359
|
-
(byte) => byte.toString(16).padStart(2, "0")
|
|
1360
|
-
).join("")}`;
|
|
1361
|
-
};
|
|
1362
|
-
var secretIdPart = (value) => value.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "oauth";
|
|
1363
|
-
var oauthSecretId = (connectionId, suffix) => {
|
|
1364
|
-
const base = secretIdPart(connectionId);
|
|
1365
|
-
const readable = base.length <= 48 ? base : base.slice(0, 40);
|
|
1366
|
-
return `oauth2-${readable}-${suffix}`;
|
|
1367
|
-
};
|
|
1368
|
-
var scopedSessionId = (scopeId, sessionId) => `${sessionId}_${secretIdPart(scopeId).slice(0, 24)}`;
|
|
1369
|
-
var terminalRefreshErrors = /* @__PURE__ */ new Set([
|
|
1370
|
-
"invalid_grant",
|
|
1371
|
-
"invalid_client",
|
|
1372
|
-
"unauthorized_client"
|
|
1373
|
-
]);
|
|
1374
|
-
var makeOAuth2Service = (deps) => {
|
|
1375
|
-
const now = deps.now ?? (() => Date.now());
|
|
1376
|
-
const newSessionId = deps.newSessionId ?? defaultSessionId;
|
|
1377
|
-
const probe = (input) => Effect8.gen(function* () {
|
|
1378
|
-
const resource = yield* discoverProtectedResourceMetadata(
|
|
1379
|
-
input.endpoint,
|
|
1380
|
-
{ resourceHeaders: input.headers, resourceQueryParams: input.queryParams }
|
|
1381
|
-
).pipe(
|
|
1382
|
-
Effect8.catchTag(
|
|
1383
|
-
"OAuthDiscoveryError",
|
|
1384
|
-
(err) => Effect8.fail(
|
|
1385
|
-
new OAuthProbeError({
|
|
1386
|
-
message: `Protected resource metadata probe failed: ${err.message}`
|
|
1387
|
-
})
|
|
1388
|
-
)
|
|
1389
|
-
)
|
|
1390
|
-
);
|
|
1391
|
-
const authorizationServerUrl = (() => {
|
|
1392
|
-
const fromResource = resource?.metadata.authorization_servers?.[0];
|
|
1393
|
-
if (fromResource) return fromResource;
|
|
1394
|
-
try {
|
|
1395
|
-
const u = new URL(input.endpoint);
|
|
1396
|
-
return `${u.protocol}//${u.host}`;
|
|
1397
|
-
} catch {
|
|
1398
|
-
return null;
|
|
1399
|
-
}
|
|
1400
|
-
})();
|
|
1401
|
-
const authServer = authorizationServerUrl ? yield* discoverAuthorizationServerMetadata(
|
|
1402
|
-
authorizationServerUrl
|
|
1403
|
-
).pipe(
|
|
1404
|
-
Effect8.catchTag(
|
|
1405
|
-
"OAuthDiscoveryError",
|
|
1406
|
-
() => Effect8.succeed(null)
|
|
1407
|
-
)
|
|
1408
|
-
) : null;
|
|
1409
|
-
const supportsDynamicRegistration = !!(authServer?.metadata.registration_endpoint && (authServer.metadata.token_endpoint_auth_methods_supported ?? []).includes(
|
|
1410
|
-
"none"
|
|
1411
|
-
));
|
|
1412
|
-
const isBearerChallengeEndpoint = yield* Effect8.tryPromise({
|
|
1413
|
-
try: async () => {
|
|
1414
|
-
const controller = new AbortController();
|
|
1415
|
-
const timer = setTimeout(() => controller.abort(), 6e3);
|
|
1416
|
-
try {
|
|
1417
|
-
const probeUrl = new URL(input.endpoint);
|
|
1418
|
-
for (const [key, value] of Object.entries(input.queryParams ?? {})) {
|
|
1419
|
-
probeUrl.searchParams.set(key, value);
|
|
1420
|
-
}
|
|
1421
|
-
const response = await fetch(probeUrl.toString(), {
|
|
1422
|
-
method: "POST",
|
|
1423
|
-
headers: {
|
|
1424
|
-
...input.headers ?? {},
|
|
1425
|
-
"content-type": "application/json",
|
|
1426
|
-
accept: "application/json, text/event-stream"
|
|
1427
|
-
},
|
|
1428
|
-
body: JSON.stringify({
|
|
1429
|
-
jsonrpc: "2.0",
|
|
1430
|
-
id: 1,
|
|
1431
|
-
method: "initialize",
|
|
1432
|
-
params: {
|
|
1433
|
-
protocolVersion: "2025-06-18",
|
|
1434
|
-
capabilities: {},
|
|
1435
|
-
clientInfo: { name: "executor-probe", version: "0" }
|
|
1436
|
-
}
|
|
1437
|
-
}),
|
|
1438
|
-
signal: controller.signal
|
|
1439
|
-
});
|
|
1440
|
-
if (response.status !== 401) return false;
|
|
1441
|
-
const wwwAuth = response.headers.get("www-authenticate") ?? response.headers.get("WWW-Authenticate");
|
|
1442
|
-
return !!wwwAuth && /^\s*bearer\b/i.test(wwwAuth);
|
|
1443
|
-
} finally {
|
|
1444
|
-
clearTimeout(timer);
|
|
1445
|
-
}
|
|
1446
|
-
},
|
|
1447
|
-
catch: () => null
|
|
1448
|
-
}).pipe(Effect8.catch(() => Effect8.succeed(false)));
|
|
1449
|
-
return {
|
|
1450
|
-
resourceMetadata: resource?.metadata ?? null,
|
|
1451
|
-
resourceMetadataUrl: resource?.metadataUrl ?? null,
|
|
1452
|
-
authorizationServerMetadata: authServer?.metadata ?? null,
|
|
1453
|
-
authorizationServerMetadataUrl: authServer?.metadataUrl ?? null,
|
|
1454
|
-
authorizationServerUrl: authorizationServerUrl ?? null,
|
|
1455
|
-
supportsDynamicRegistration,
|
|
1456
|
-
isBearerChallengeEndpoint
|
|
1457
|
-
};
|
|
1458
|
-
});
|
|
1459
|
-
const startDynamicDcr = (input, strategy) => Effect8.gen(function* () {
|
|
1460
|
-
const started = yield* beginDynamicAuthorization({
|
|
1461
|
-
endpoint: input.endpoint,
|
|
1462
|
-
redirectUrl: input.redirectUrl,
|
|
1463
|
-
state: "",
|
|
1464
|
-
scopes: strategy.scopes
|
|
1465
|
-
}, {
|
|
1466
|
-
resourceHeaders: input.headers,
|
|
1467
|
-
resourceQueryParams: input.queryParams
|
|
1468
|
-
}).pipe(
|
|
1469
|
-
Effect8.catchTag(
|
|
1470
|
-
"OAuthDiscoveryError",
|
|
1471
|
-
(err) => Effect8.fail(
|
|
1472
|
-
new OAuthStartError({
|
|
1473
|
-
message: `Dynamic authorization setup failed: ${err.message}`
|
|
1474
|
-
})
|
|
1475
|
-
)
|
|
1476
|
-
)
|
|
1477
|
-
);
|
|
1478
|
-
const sessionId = scopedSessionId(input.tokenScope, newSessionId());
|
|
1479
|
-
const codeChallenge = yield* Effect8.promise(
|
|
1480
|
-
() => createPkceCodeChallenge(started.codeVerifier)
|
|
1481
|
-
);
|
|
1482
|
-
const authorizationUrl = buildAuthorizationUrl({
|
|
1483
|
-
authorizationUrl: started.state.authorizationServerMetadata.authorization_endpoint,
|
|
1484
|
-
clientId: started.state.clientInformation.client_id,
|
|
1485
|
-
redirectUrl: input.redirectUrl,
|
|
1486
|
-
scopes: strategy.scopes ?? started.state.authorizationServerMetadata.scopes_supported ?? [],
|
|
1487
|
-
state: sessionId,
|
|
1488
|
-
codeChallenge
|
|
1489
|
-
});
|
|
1490
|
-
const payload = {
|
|
1491
|
-
kind: "dynamic-dcr",
|
|
1492
|
-
identityLabel: input.identityLabel ?? null,
|
|
1493
|
-
codeVerifier: started.codeVerifier,
|
|
1494
|
-
authorizationServerUrl: started.state.authorizationServerUrl,
|
|
1495
|
-
authorizationServerMetadataUrl: started.state.authorizationServerMetadataUrl,
|
|
1496
|
-
authorizationServerMetadata: started.state.authorizationServerMetadata,
|
|
1497
|
-
clientInformation: (() => {
|
|
1498
|
-
const value = started.state.clientInformation;
|
|
1499
|
-
return value;
|
|
1500
|
-
})(),
|
|
1501
|
-
resourceMetadataUrl: started.state.resourceMetadataUrl,
|
|
1502
|
-
resourceMetadata: started.state.resourceMetadata ?? null,
|
|
1503
|
-
scopes: [
|
|
1504
|
-
...strategy.scopes ?? started.state.authorizationServerMetadata.scopes_supported ?? []
|
|
1505
|
-
]
|
|
1506
|
-
};
|
|
1507
|
-
yield* writeSession({
|
|
1508
|
-
sessionId,
|
|
1509
|
-
input,
|
|
1510
|
-
payload,
|
|
1511
|
-
strategyKind: "dynamic-dcr"
|
|
1512
|
-
});
|
|
1513
|
-
return {
|
|
1514
|
-
sessionId,
|
|
1515
|
-
authorizationUrl,
|
|
1516
|
-
completedConnection: null
|
|
1517
|
-
};
|
|
1518
|
-
});
|
|
1519
|
-
const startAuthorizationCode = (input, strategy) => Effect8.gen(function* () {
|
|
1520
|
-
const clientId = yield* deps.secretsGet(strategy.clientIdSecretId).pipe(
|
|
1521
|
-
Effect8.mapError(
|
|
1522
|
-
(err) => (
|
|
1523
|
-
// Storage failure propagates; null returns aren't errors — the
|
|
1524
|
-
// branch below handles them.
|
|
1525
|
-
err
|
|
1526
|
-
)
|
|
1527
|
-
)
|
|
1528
|
-
);
|
|
1529
|
-
if (clientId === null) {
|
|
1530
|
-
return yield* Effect8.fail(
|
|
1531
|
-
new OAuthStartError({
|
|
1532
|
-
message: `client_id secret "${strategy.clientIdSecretId}" not found`
|
|
1533
|
-
})
|
|
1534
|
-
);
|
|
1535
|
-
}
|
|
1536
|
-
const sessionId = scopedSessionId(input.tokenScope, newSessionId());
|
|
1537
|
-
const codeVerifier = createPkceCodeVerifier();
|
|
1538
|
-
const codeChallenge = yield* Effect8.promise(
|
|
1539
|
-
() => createPkceCodeChallenge(codeVerifier)
|
|
1540
|
-
);
|
|
1541
|
-
const authorizationUrl = buildAuthorizationUrl({
|
|
1542
|
-
authorizationUrl: strategy.authorizationEndpoint,
|
|
1543
|
-
clientId,
|
|
1544
|
-
redirectUrl: input.redirectUrl,
|
|
1545
|
-
scopes: strategy.scopes,
|
|
1546
|
-
state: sessionId,
|
|
1547
|
-
codeChallenge,
|
|
1548
|
-
scopeSeparator: strategy.scopeSeparator,
|
|
1549
|
-
extraParams: strategy.extraAuthorizationParams
|
|
1550
|
-
});
|
|
1551
|
-
const payload = {
|
|
1552
|
-
kind: "authorization-code",
|
|
1553
|
-
identityLabel: input.identityLabel ?? null,
|
|
1554
|
-
codeVerifier,
|
|
1555
|
-
authorizationEndpoint: strategy.authorizationEndpoint,
|
|
1556
|
-
tokenEndpoint: strategy.tokenEndpoint,
|
|
1557
|
-
issuerUrl: strategy.issuerUrl ?? new URL(strategy.authorizationEndpoint).origin,
|
|
1558
|
-
clientIdSecretId: strategy.clientIdSecretId,
|
|
1559
|
-
clientSecretSecretId: strategy.clientSecretSecretId,
|
|
1560
|
-
scopes: [...strategy.scopes],
|
|
1561
|
-
scopeSeparator: strategy.scopeSeparator,
|
|
1562
|
-
clientAuth: strategy.clientAuth ?? "body"
|
|
1563
|
-
};
|
|
1564
|
-
yield* writeSession({
|
|
1565
|
-
sessionId,
|
|
1566
|
-
input,
|
|
1567
|
-
payload,
|
|
1568
|
-
strategyKind: "authorization-code"
|
|
1569
|
-
});
|
|
1570
|
-
return {
|
|
1571
|
-
sessionId,
|
|
1572
|
-
authorizationUrl,
|
|
1573
|
-
completedConnection: null
|
|
1574
|
-
};
|
|
1575
|
-
});
|
|
1576
|
-
const startClientCredentials = (input, strategy) => Effect8.gen(function* () {
|
|
1577
|
-
const clientId = yield* deps.secretsGet(strategy.clientIdSecretId);
|
|
1578
|
-
const clientSecret = yield* deps.secretsGet(strategy.clientSecretSecretId);
|
|
1579
|
-
if (clientId === null || clientSecret === null) {
|
|
1580
|
-
return yield* Effect8.fail(
|
|
1581
|
-
new OAuthStartError({
|
|
1582
|
-
message: "client_id / client_secret secret not found"
|
|
1583
|
-
})
|
|
1584
|
-
);
|
|
1585
|
-
}
|
|
1586
|
-
const tokens = yield* exchangeClientCredentials({
|
|
1587
|
-
tokenUrl: strategy.tokenEndpoint,
|
|
1588
|
-
clientId,
|
|
1589
|
-
clientSecret,
|
|
1590
|
-
scopes: strategy.scopes,
|
|
1591
|
-
scopeSeparator: strategy.scopeSeparator,
|
|
1592
|
-
clientAuth: strategy.clientAuth ?? "body"
|
|
1593
|
-
}).pipe(
|
|
1594
|
-
Effect8.mapError(
|
|
1595
|
-
(err) => new OAuthStartError({
|
|
1596
|
-
message: `Client credentials exchange failed: ${err.message}`
|
|
1597
|
-
})
|
|
1598
|
-
)
|
|
1599
|
-
);
|
|
1600
|
-
const expiresAt = typeof tokens.expires_in === "number" ? now() + tokens.expires_in * 1e3 : null;
|
|
1601
|
-
const providerState = {
|
|
1602
|
-
kind: "client-credentials",
|
|
1603
|
-
tokenEndpoint: strategy.tokenEndpoint,
|
|
1604
|
-
clientIdSecretId: strategy.clientIdSecretId,
|
|
1605
|
-
clientSecretSecretId: strategy.clientSecretSecretId,
|
|
1606
|
-
scopes: [...strategy.scopes ?? []],
|
|
1607
|
-
scopeSeparator: strategy.scopeSeparator,
|
|
1608
|
-
clientAuth: strategy.clientAuth ?? "body",
|
|
1609
|
-
scope: tokens.scope ?? null
|
|
1610
|
-
};
|
|
1611
|
-
yield* deps.connectionsCreate(
|
|
1612
|
-
new CreateConnectionInput({
|
|
1613
|
-
id: ConnectionId.make(input.connectionId),
|
|
1614
|
-
scope: ScopeId.make(input.tokenScope),
|
|
1615
|
-
provider: OAUTH2_PROVIDER_KEY,
|
|
1616
|
-
identityLabel: input.identityLabel ?? safeHostname(input.endpoint),
|
|
1617
|
-
accessToken: new TokenMaterial({
|
|
1618
|
-
secretId: SecretId.make(oauthSecretId(input.connectionId, "access-token")),
|
|
1619
|
-
name: "OAuth Access Token",
|
|
1620
|
-
value: tokens.access_token
|
|
1621
|
-
}),
|
|
1622
|
-
refreshToken: null,
|
|
1623
|
-
expiresAt,
|
|
1624
|
-
oauthScope: tokens.scope ?? null,
|
|
1625
|
-
providerState: Schema8.encodeSync(OAuthProviderState)(
|
|
1626
|
-
providerState
|
|
1627
|
-
)
|
|
1628
|
-
})
|
|
1629
|
-
).pipe(
|
|
1630
|
-
Effect8.mapError(
|
|
1631
|
-
(err) => new OAuthStartError({
|
|
1632
|
-
message: `Failed to mint connection: ${err instanceof Error ? err.message : String(err)}`
|
|
1633
|
-
})
|
|
1634
|
-
)
|
|
1635
|
-
);
|
|
1636
|
-
return {
|
|
1637
|
-
sessionId: "",
|
|
1638
|
-
authorizationUrl: null,
|
|
1639
|
-
completedConnection: { connectionId: input.connectionId }
|
|
1640
|
-
};
|
|
1641
|
-
});
|
|
1642
|
-
const start = (input) => {
|
|
1643
|
-
switch (input.strategy.kind) {
|
|
1644
|
-
case "dynamic-dcr":
|
|
1645
|
-
return startDynamicDcr(input, input.strategy);
|
|
1646
|
-
case "authorization-code":
|
|
1647
|
-
return startAuthorizationCode(input, input.strategy);
|
|
1648
|
-
case "client-credentials":
|
|
1649
|
-
return startClientCredentials(input, input.strategy);
|
|
1650
|
-
}
|
|
1651
|
-
};
|
|
1652
|
-
const writeSession = (args) => deps.adapter.create({
|
|
1653
|
-
model: "oauth2_session",
|
|
1654
|
-
data: {
|
|
1655
|
-
id: args.sessionId,
|
|
1656
|
-
scope_id: args.input.tokenScope,
|
|
1657
|
-
plugin_id: args.input.pluginId,
|
|
1658
|
-
strategy: args.strategyKind,
|
|
1659
|
-
connection_id: args.input.connectionId,
|
|
1660
|
-
token_scope: args.input.tokenScope,
|
|
1661
|
-
redirect_url: args.input.redirectUrl,
|
|
1662
|
-
payload: encodeSessionPayload(args.payload),
|
|
1663
|
-
expires_at: now() + OAUTH2_SESSION_TTL_MS,
|
|
1664
|
-
created_at: /* @__PURE__ */ new Date()
|
|
1665
|
-
},
|
|
1666
|
-
forceAllowId: true
|
|
1667
|
-
}).pipe(Effect8.asVoid);
|
|
1668
|
-
const complete = (input) => Effect8.gen(function* () {
|
|
1669
|
-
const row = yield* deps.adapter.findOne({
|
|
1670
|
-
model: "oauth2_session",
|
|
1671
|
-
where: [{ field: "id", value: input.state }]
|
|
1672
|
-
});
|
|
1673
|
-
if (!row) {
|
|
1674
|
-
return yield* Effect8.fail(
|
|
1675
|
-
new OAuthSessionNotFoundError({ sessionId: input.state })
|
|
1676
|
-
);
|
|
1677
|
-
}
|
|
1678
|
-
const deleteSession = deps.adapter.delete({
|
|
1679
|
-
model: "oauth2_session",
|
|
1680
|
-
where: [
|
|
1681
|
-
{ field: "id", value: input.state },
|
|
1682
|
-
{ field: "scope_id", value: row.scope_id }
|
|
1683
|
-
]
|
|
1684
|
-
});
|
|
1685
|
-
if (input.error) {
|
|
1686
|
-
yield* deleteSession;
|
|
1687
|
-
return yield* Effect8.fail(
|
|
1688
|
-
new OAuthCompleteError({
|
|
1689
|
-
message: `Authorization server returned error: ${input.error}`,
|
|
1690
|
-
code: input.error
|
|
1691
|
-
})
|
|
1692
|
-
);
|
|
1693
|
-
}
|
|
1694
|
-
if (!input.code) {
|
|
1695
|
-
yield* deleteSession;
|
|
1696
|
-
return yield* Effect8.fail(
|
|
1697
|
-
new OAuthCompleteError({
|
|
1698
|
-
message: "Missing authorization code"
|
|
1699
|
-
})
|
|
1700
|
-
);
|
|
1701
|
-
}
|
|
1702
|
-
const expiresAt = Number(row.expires_at);
|
|
1703
|
-
if (expiresAt <= now()) {
|
|
1704
|
-
yield* deleteSession;
|
|
1705
|
-
return yield* Effect8.fail(
|
|
1706
|
-
new OAuthCompleteError({
|
|
1707
|
-
message: "OAuth session expired"
|
|
1708
|
-
})
|
|
1709
|
-
);
|
|
1710
|
-
}
|
|
1711
|
-
const payload = decodeSessionPayload(coerceJson(row.payload));
|
|
1712
|
-
const endpoint = "";
|
|
1713
|
-
const connectionId = row.connection_id;
|
|
1714
|
-
const tokenScope = row.token_scope;
|
|
1715
|
-
const redirectUrl = row.redirect_url;
|
|
1716
|
-
const exchangeResult = yield* (() => {
|
|
1717
|
-
switch (payload.kind) {
|
|
1718
|
-
case "dynamic-dcr":
|
|
1719
|
-
return exchangeDynamicDcr(payload, input.code, redirectUrl);
|
|
1720
|
-
case "authorization-code":
|
|
1721
|
-
return exchangeAuthorizationCodeStrategy(
|
|
1722
|
-
payload,
|
|
1723
|
-
input.code,
|
|
1724
|
-
redirectUrl
|
|
1725
|
-
);
|
|
1726
|
-
}
|
|
1727
|
-
})().pipe(Effect8.tapError(() => deleteSession));
|
|
1728
|
-
const connectionExpiresAt = typeof exchangeResult.tokens.expires_in === "number" ? now() + exchangeResult.tokens.expires_in * 1e3 : null;
|
|
1729
|
-
const dynamicClientSecretSecretId = yield* (() => {
|
|
1730
|
-
if (payload.kind !== "dynamic-dcr") return Effect8.succeed(null);
|
|
1731
|
-
const clientSecret = payload.clientInformation.client_secret;
|
|
1732
|
-
if (typeof clientSecret !== "string" || clientSecret.length === 0) {
|
|
1733
|
-
return Effect8.succeed(null);
|
|
1734
|
-
}
|
|
1735
|
-
const secretId = oauthSecretId(connectionId, "client-secret");
|
|
1736
|
-
return deps.secretsSet(
|
|
1737
|
-
new SetSecretInput({
|
|
1738
|
-
id: SecretId.make(secretId),
|
|
1739
|
-
scope: ScopeId.make(tokenScope),
|
|
1740
|
-
name: "OAuth Client Secret",
|
|
1741
|
-
value: clientSecret
|
|
1742
|
-
})
|
|
1743
|
-
).pipe(
|
|
1744
|
-
Effect8.as(secretId),
|
|
1745
|
-
Effect8.mapError(
|
|
1746
|
-
(err) => new OAuthCompleteError({
|
|
1747
|
-
message: `Failed to persist DCR client_secret: ${err instanceof Error ? err.message : String(err)}`
|
|
1748
|
-
})
|
|
1749
|
-
)
|
|
1750
|
-
);
|
|
1751
|
-
})();
|
|
1752
|
-
const providerState = payload.kind === "dynamic-dcr" ? {
|
|
1753
|
-
kind: "dynamic-dcr",
|
|
1754
|
-
tokenEndpoint: payload.authorizationServerMetadata.token_endpoint,
|
|
1755
|
-
issuerUrl: payload.authorizationServerMetadata.issuer ?? null,
|
|
1756
|
-
authorizationServerUrl: payload.authorizationServerUrl,
|
|
1757
|
-
authorizationServerMetadataUrl: payload.authorizationServerMetadataUrl,
|
|
1758
|
-
idTokenSigningAlgValuesSupported: payload.authorizationServerMetadata.id_token_signing_alg_values_supported,
|
|
1759
|
-
clientId: payload.clientInformation.client_id,
|
|
1760
|
-
clientSecretSecretId: dynamicClientSecretSecretId,
|
|
1761
|
-
clientAuth: payload.clientInformation.token_endpoint_auth_method === "client_secret_basic" ? "basic" : "body",
|
|
1762
|
-
scopes: [...payload.scopes],
|
|
1763
|
-
scope: exchangeResult.tokens.scope ?? null
|
|
1764
|
-
} : {
|
|
1765
|
-
kind: "authorization-code",
|
|
1766
|
-
tokenEndpoint: payload.tokenEndpoint,
|
|
1767
|
-
issuerUrl: payload.issuerUrl,
|
|
1768
|
-
clientIdSecretId: payload.clientIdSecretId,
|
|
1769
|
-
clientSecretSecretId: payload.clientSecretSecretId,
|
|
1770
|
-
clientAuth: payload.clientAuth,
|
|
1771
|
-
scopes: [...payload.scopes],
|
|
1772
|
-
scopeSeparator: payload.scopeSeparator,
|
|
1773
|
-
scope: exchangeResult.tokens.scope ?? null
|
|
1774
|
-
};
|
|
1775
|
-
yield* deps.connectionsCreate(
|
|
1776
|
-
new CreateConnectionInput({
|
|
1777
|
-
id: ConnectionId.make(connectionId),
|
|
1778
|
-
scope: ScopeId.make(tokenScope),
|
|
1779
|
-
provider: OAUTH2_PROVIDER_KEY,
|
|
1780
|
-
identityLabel: safeHostname(
|
|
1781
|
-
payload.identityLabel ?? exchangeResult.endpointForDisplay ?? endpoint
|
|
1782
|
-
),
|
|
1783
|
-
accessToken: new TokenMaterial({
|
|
1784
|
-
secretId: SecretId.make(oauthSecretId(connectionId, "access-token")),
|
|
1785
|
-
name: "OAuth Access Token",
|
|
1786
|
-
value: exchangeResult.tokens.access_token
|
|
1787
|
-
}),
|
|
1788
|
-
refreshToken: exchangeResult.tokens.refresh_token ? new TokenMaterial({
|
|
1789
|
-
secretId: SecretId.make(oauthSecretId(connectionId, "refresh-token")),
|
|
1790
|
-
name: "OAuth Refresh Token",
|
|
1791
|
-
value: exchangeResult.tokens.refresh_token
|
|
1792
|
-
}) : null,
|
|
1793
|
-
expiresAt: connectionExpiresAt,
|
|
1794
|
-
oauthScope: exchangeResult.tokens.scope ?? null,
|
|
1795
|
-
providerState: Schema8.encodeSync(OAuthProviderState)(
|
|
1796
|
-
providerState
|
|
1797
|
-
)
|
|
1798
|
-
})
|
|
1799
|
-
).pipe(
|
|
1800
|
-
Effect8.mapError(
|
|
1801
|
-
(err) => new OAuthCompleteError({
|
|
1802
|
-
message: `Failed to mint connection: ${err instanceof Error ? err.message : String(err)}`
|
|
1803
|
-
})
|
|
1804
|
-
)
|
|
1805
|
-
);
|
|
1806
|
-
yield* deleteSession;
|
|
1807
|
-
return {
|
|
1808
|
-
connectionId,
|
|
1809
|
-
expiresAt: connectionExpiresAt,
|
|
1810
|
-
scope: exchangeResult.tokens.scope ?? null
|
|
1811
|
-
};
|
|
1812
|
-
});
|
|
1813
|
-
const exchangeDynamicDcr = (payload, code, redirectUrl) => Effect8.gen(function* () {
|
|
1814
|
-
const md = payload.authorizationServerMetadata;
|
|
1815
|
-
const ci = payload.clientInformation;
|
|
1816
|
-
const tokens = yield* exchangeAuthorizationCode({
|
|
1817
|
-
tokenUrl: md.token_endpoint,
|
|
1818
|
-
issuerUrl: md.issuer,
|
|
1819
|
-
clientId: ci.client_id,
|
|
1820
|
-
clientSecret: ci.client_secret ?? void 0,
|
|
1821
|
-
redirectUrl,
|
|
1822
|
-
codeVerifier: payload.codeVerifier,
|
|
1823
|
-
code,
|
|
1824
|
-
idTokenSigningAlgValuesSupported: md.id_token_signing_alg_values_supported,
|
|
1825
|
-
clientAuth: ci.token_endpoint_auth_method === "client_secret_basic" ? "basic" : "body"
|
|
1826
|
-
}).pipe(
|
|
1827
|
-
Effect8.mapError(
|
|
1828
|
-
(err) => new OAuthCompleteError({
|
|
1829
|
-
message: `Token exchange failed: ${err.message}`,
|
|
1830
|
-
code: err.error
|
|
1831
|
-
})
|
|
1832
|
-
)
|
|
1833
|
-
);
|
|
1834
|
-
return {
|
|
1835
|
-
tokens,
|
|
1836
|
-
endpointForDisplay: payload.authorizationServerUrl
|
|
1837
|
-
};
|
|
1838
|
-
});
|
|
1839
|
-
const exchangeAuthorizationCodeStrategy = (payload, code, redirectUrl) => Effect8.gen(function* () {
|
|
1840
|
-
const clientId = yield* deps.secretsGet(payload.clientIdSecretId);
|
|
1841
|
-
if (clientId === null) {
|
|
1842
|
-
return yield* Effect8.fail(
|
|
1843
|
-
new OAuthCompleteError({
|
|
1844
|
-
message: `client_id secret "${payload.clientIdSecretId}" not found`
|
|
1845
|
-
})
|
|
1846
|
-
);
|
|
1847
|
-
}
|
|
1848
|
-
const clientSecret = payload.clientSecretSecretId ? yield* deps.secretsGet(payload.clientSecretSecretId) : null;
|
|
1849
|
-
if (payload.clientSecretSecretId && clientSecret === null) {
|
|
1850
|
-
return yield* Effect8.fail(
|
|
1851
|
-
new OAuthCompleteError({
|
|
1852
|
-
message: `client_secret secret "${payload.clientSecretSecretId}" not found`
|
|
1853
|
-
})
|
|
1854
|
-
);
|
|
1855
|
-
}
|
|
1856
|
-
const tokens = yield* exchangeAuthorizationCode({
|
|
1857
|
-
tokenUrl: payload.tokenEndpoint,
|
|
1858
|
-
issuerUrl: payload.issuerUrl,
|
|
1859
|
-
clientId,
|
|
1860
|
-
clientSecret: clientSecret ?? void 0,
|
|
1861
|
-
redirectUrl,
|
|
1862
|
-
codeVerifier: payload.codeVerifier,
|
|
1863
|
-
code,
|
|
1864
|
-
clientAuth: payload.clientAuth
|
|
1865
|
-
}).pipe(
|
|
1866
|
-
Effect8.mapError(
|
|
1867
|
-
(err) => new OAuthCompleteError({
|
|
1868
|
-
message: `Token exchange failed: ${err.message}`,
|
|
1869
|
-
code: err.error
|
|
1870
|
-
})
|
|
1871
|
-
)
|
|
1872
|
-
);
|
|
1873
|
-
return {
|
|
1874
|
-
tokens,
|
|
1875
|
-
endpointForDisplay: null
|
|
1876
|
-
};
|
|
1877
|
-
});
|
|
1878
|
-
const cancel = (sessionId) => Effect8.gen(function* () {
|
|
1879
|
-
const row = yield* deps.adapter.findOne({
|
|
1880
|
-
model: "oauth2_session",
|
|
1881
|
-
where: [{ field: "id", value: sessionId }]
|
|
1882
|
-
});
|
|
1883
|
-
if (!row) return;
|
|
1884
|
-
yield* deps.adapter.delete({
|
|
1885
|
-
model: "oauth2_session",
|
|
1886
|
-
where: [
|
|
1887
|
-
{ field: "id", value: sessionId },
|
|
1888
|
-
{ field: "scope_id", value: row.scope_id }
|
|
1889
|
-
]
|
|
1890
|
-
});
|
|
1891
|
-
});
|
|
1892
|
-
const connectionProvider = {
|
|
1893
|
-
key: OAUTH2_PROVIDER_KEY,
|
|
1894
|
-
refresh: (input) => Effect8.gen(function* () {
|
|
1895
|
-
if (!input.providerState) {
|
|
1896
|
-
return yield* new ConnectionRefreshError({
|
|
1897
|
-
connectionId: input.connectionId,
|
|
1898
|
-
message: "oauth2 connection missing providerState"
|
|
1899
|
-
});
|
|
1900
|
-
}
|
|
1901
|
-
const state = yield* Effect8.try({
|
|
1902
|
-
try: () => decodeProviderState(input.providerState),
|
|
1903
|
-
catch: (cause) => new ConnectionRefreshError({
|
|
1904
|
-
connectionId: input.connectionId,
|
|
1905
|
-
message: `oauth2 providerState is malformed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1906
|
-
cause
|
|
1907
|
-
})
|
|
1908
|
-
});
|
|
1909
|
-
if (state.kind !== "client-credentials" && !input.refreshToken) {
|
|
1910
|
-
return yield* new ConnectionRefreshError({
|
|
1911
|
-
connectionId: input.connectionId,
|
|
1912
|
-
message: "oauth2 connection has no refresh token",
|
|
1913
|
-
reauthRequired: true
|
|
1914
|
-
});
|
|
1915
|
-
}
|
|
1916
|
-
const { clientId, clientSecret } = yield* (() => {
|
|
1917
|
-
switch (state.kind) {
|
|
1918
|
-
case "dynamic-dcr":
|
|
1919
|
-
return Effect8.gen(function* () {
|
|
1920
|
-
const csec = state.clientSecretSecretId ? yield* deps.secretsGet(state.clientSecretSecretId).pipe(
|
|
1921
|
-
Effect8.mapError(
|
|
1922
|
-
(cause) => new ConnectionRefreshError({
|
|
1923
|
-
connectionId: input.connectionId,
|
|
1924
|
-
message: `Failed to resolve DCR client_secret: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1925
|
-
cause
|
|
1926
|
-
})
|
|
1927
|
-
)
|
|
1928
|
-
) : null;
|
|
1929
|
-
if (state.clientSecretSecretId && csec === null) {
|
|
1930
|
-
return yield* new ConnectionRefreshError({
|
|
1931
|
-
connectionId: input.connectionId,
|
|
1932
|
-
message: `client_secret secret "${state.clientSecretSecretId}" not found`,
|
|
1933
|
-
reauthRequired: true
|
|
1934
|
-
});
|
|
1935
|
-
}
|
|
1936
|
-
return { clientId: state.clientId, clientSecret: csec };
|
|
1937
|
-
});
|
|
1938
|
-
case "authorization-code":
|
|
1939
|
-
case "client-credentials":
|
|
1940
|
-
return Effect8.gen(function* () {
|
|
1941
|
-
const cid = yield* deps.secretsGet(state.clientIdSecretId).pipe(
|
|
1942
|
-
Effect8.mapError(
|
|
1943
|
-
(cause) => new ConnectionRefreshError({
|
|
1944
|
-
connectionId: input.connectionId,
|
|
1945
|
-
message: `Failed to resolve client_id secret: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1946
|
-
cause
|
|
1947
|
-
})
|
|
1948
|
-
)
|
|
1949
|
-
);
|
|
1950
|
-
if (cid === null) {
|
|
1951
|
-
return yield* new ConnectionRefreshError({
|
|
1952
|
-
connectionId: input.connectionId,
|
|
1953
|
-
message: `client_id secret "${state.clientIdSecretId}" not found`,
|
|
1954
|
-
reauthRequired: true
|
|
1955
|
-
});
|
|
1956
|
-
}
|
|
1957
|
-
const csec = state.clientSecretSecretId ? yield* deps.secretsGet(state.clientSecretSecretId).pipe(
|
|
1958
|
-
Effect8.mapError(
|
|
1959
|
-
(cause) => new ConnectionRefreshError({
|
|
1960
|
-
connectionId: input.connectionId,
|
|
1961
|
-
message: `Failed to resolve client_secret: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1962
|
-
cause
|
|
1963
|
-
})
|
|
1964
|
-
)
|
|
1965
|
-
) : null;
|
|
1966
|
-
if (state.clientSecretSecretId && csec === null) {
|
|
1967
|
-
return yield* new ConnectionRefreshError({
|
|
1968
|
-
connectionId: input.connectionId,
|
|
1969
|
-
message: `client_secret secret "${state.clientSecretSecretId}" not found`,
|
|
1970
|
-
reauthRequired: true
|
|
1971
|
-
});
|
|
1972
|
-
}
|
|
1973
|
-
return { clientId: cid, clientSecret: csec };
|
|
1974
|
-
});
|
|
1975
|
-
}
|
|
1976
|
-
})();
|
|
1977
|
-
const tokenEndpoint = yield* (() => {
|
|
1978
|
-
if (state.tokenEndpoint) return Effect8.succeed(state.tokenEndpoint);
|
|
1979
|
-
if (state.kind === "dynamic-dcr" && state.authorizationServerUrl) {
|
|
1980
|
-
return discoverAuthorizationServerMetadata(
|
|
1981
|
-
state.authorizationServerUrl
|
|
1982
|
-
).pipe(
|
|
1983
|
-
Effect8.flatMap(
|
|
1984
|
-
(metadata) => metadata?.metadata.token_endpoint ? Effect8.succeed(metadata.metadata.token_endpoint) : Effect8.fail(
|
|
1985
|
-
new ConnectionRefreshError({
|
|
1986
|
-
connectionId: input.connectionId,
|
|
1987
|
-
message: "oauth2 legacy MCP providerState is missing token endpoint",
|
|
1988
|
-
reauthRequired: true
|
|
1989
|
-
})
|
|
1990
|
-
)
|
|
1991
|
-
),
|
|
1992
|
-
Effect8.mapError(
|
|
1993
|
-
(cause) => cause instanceof ConnectionRefreshError ? cause : new ConnectionRefreshError({
|
|
1994
|
-
connectionId: input.connectionId,
|
|
1995
|
-
message: "Failed to discover token endpoint for legacy MCP OAuth connection",
|
|
1996
|
-
reauthRequired: true,
|
|
1997
|
-
cause
|
|
1998
|
-
})
|
|
1999
|
-
)
|
|
2000
|
-
);
|
|
2001
|
-
}
|
|
2002
|
-
return Effect8.fail(
|
|
2003
|
-
new ConnectionRefreshError({
|
|
2004
|
-
connectionId: input.connectionId,
|
|
2005
|
-
message: "oauth2 providerState is missing token endpoint",
|
|
2006
|
-
reauthRequired: true
|
|
2007
|
-
})
|
|
2008
|
-
);
|
|
2009
|
-
})();
|
|
2010
|
-
const tokens = yield* (state.kind === "client-credentials" ? exchangeClientCredentials({
|
|
2011
|
-
tokenUrl: tokenEndpoint,
|
|
2012
|
-
clientId,
|
|
2013
|
-
clientSecret: clientSecret ?? "",
|
|
2014
|
-
scopes: state.scopes,
|
|
2015
|
-
scopeSeparator: state.scopeSeparator,
|
|
2016
|
-
clientAuth: state.clientAuth
|
|
2017
|
-
}) : refreshAccessToken({
|
|
2018
|
-
tokenUrl: tokenEndpoint,
|
|
2019
|
-
issuerUrl: state.kind === "dynamic-dcr" || state.kind === "authorization-code" ? state.issuerUrl ?? void 0 : void 0,
|
|
2020
|
-
clientId,
|
|
2021
|
-
clientSecret: clientSecret ?? void 0,
|
|
2022
|
-
refreshToken: input.refreshToken,
|
|
2023
|
-
scopes: state.kind === "dynamic-dcr" || state.kind === "authorization-code" ? state.scopes : void 0,
|
|
2024
|
-
scopeSeparator: state.kind === "dynamic-dcr" || state.kind === "authorization-code" ? state.scopeSeparator : void 0,
|
|
2025
|
-
clientAuth: state.clientAuth,
|
|
2026
|
-
idTokenSigningAlgValuesSupported: state.kind === "dynamic-dcr" ? state.idTokenSigningAlgValuesSupported : void 0
|
|
2027
|
-
})).pipe(
|
|
2028
|
-
Effect8.mapError(
|
|
2029
|
-
(err) => new ConnectionRefreshError({
|
|
2030
|
-
connectionId: input.connectionId,
|
|
2031
|
-
message: `OAuth refresh failed: ${err.message}`,
|
|
2032
|
-
// Terminal RFC 6749 §5.2 errors mean retrying won't heal it.
|
|
2033
|
-
reauthRequired: err.error ? terminalRefreshErrors.has(err.error) : false
|
|
2034
|
-
})
|
|
2035
|
-
)
|
|
2036
|
-
);
|
|
2037
|
-
const expiresAt = typeof tokens.expires_in === "number" ? now() + tokens.expires_in * 1e3 : null;
|
|
2038
|
-
const result = {
|
|
2039
|
-
accessToken: tokens.access_token,
|
|
2040
|
-
refreshToken: tokens.refresh_token,
|
|
2041
|
-
expiresAt,
|
|
2042
|
-
oauthScope: tokens.scope ?? input.oauthScope,
|
|
2043
|
-
providerState: Schema8.encodeSync(OAuthProviderState)({
|
|
2044
|
-
...state,
|
|
2045
|
-
tokenEndpoint,
|
|
2046
|
-
scope: tokens.scope ?? state.scope
|
|
2047
|
-
})
|
|
2048
|
-
};
|
|
2049
|
-
return result;
|
|
2050
|
-
})
|
|
2051
|
-
};
|
|
2052
|
-
const service = { probe, start, complete, cancel };
|
|
2053
|
-
return { service, connectionProvider };
|
|
2054
|
-
};
|
|
2055
|
-
var safeHostname = (value) => {
|
|
2056
|
-
if (!value) return null;
|
|
2057
|
-
try {
|
|
2058
|
-
return new URL(value).host;
|
|
2059
|
-
} catch {
|
|
2060
|
-
return value;
|
|
2061
|
-
}
|
|
2062
|
-
};
|
|
2063
|
-
|
|
2064
|
-
// src/policies.ts
|
|
2065
|
-
import { Schema as Schema9 } from "effect";
|
|
2066
|
-
var matchPattern = (pattern, toolId) => {
|
|
2067
|
-
if (pattern === "*") return true;
|
|
2068
|
-
if (pattern === toolId) return true;
|
|
2069
|
-
if (pattern.endsWith(".*")) {
|
|
2070
|
-
const prefix = pattern.slice(0, -2);
|
|
2071
|
-
if (prefix.length === 0) return false;
|
|
2072
|
-
return toolId === prefix || toolId.startsWith(`${prefix}.`);
|
|
2073
|
-
}
|
|
2074
|
-
return false;
|
|
2075
|
-
};
|
|
2076
|
-
var isValidPattern = (pattern) => {
|
|
2077
|
-
if (pattern.length === 0) return false;
|
|
2078
|
-
if (pattern === "*") return true;
|
|
2079
|
-
if (pattern.startsWith(".") || pattern.endsWith(".")) return false;
|
|
2080
|
-
if (pattern.includes("..")) return false;
|
|
2081
|
-
if (pattern.startsWith("*")) return false;
|
|
2082
|
-
const segments = pattern.split(".");
|
|
2083
|
-
for (let i = 0; i < segments.length; i++) {
|
|
2084
|
-
const seg = segments[i];
|
|
2085
|
-
if (seg.length === 0) return false;
|
|
2086
|
-
if (seg.includes("*") && seg !== "*") return false;
|
|
2087
|
-
if (seg === "*" && i !== segments.length - 1) return false;
|
|
2088
|
-
}
|
|
2089
|
-
return true;
|
|
2090
|
-
};
|
|
2091
|
-
var comparePolicyRow = (a, b) => {
|
|
2092
|
-
const pa = a.position;
|
|
2093
|
-
const pb = b.position;
|
|
2094
|
-
if (pa < pb) return -1;
|
|
2095
|
-
if (pa > pb) return 1;
|
|
2096
|
-
const ia = a.id;
|
|
2097
|
-
const ib = b.id;
|
|
2098
|
-
return ia < ib ? -1 : ia > ib ? 1 : 0;
|
|
2099
|
-
};
|
|
2100
|
-
var resolveToolPolicy = (toolId, policies, scopeRank) => {
|
|
2101
|
-
if (policies.length === 0) return void 0;
|
|
2102
|
-
const sorted = [...policies].sort((a, b) => {
|
|
2103
|
-
const sa = scopeRank(a);
|
|
2104
|
-
const sb = scopeRank(b);
|
|
2105
|
-
if (sa !== sb) return sa - sb;
|
|
2106
|
-
return comparePolicyRow(a, b);
|
|
2107
|
-
});
|
|
2108
|
-
for (const row of sorted) {
|
|
2109
|
-
if (matchPattern(row.pattern, toolId)) {
|
|
2110
|
-
return {
|
|
2111
|
-
action: row.action,
|
|
2112
|
-
pattern: row.pattern,
|
|
2113
|
-
policyId: row.id
|
|
2114
|
-
};
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
return void 0;
|
|
2118
|
-
};
|
|
2119
|
-
var liftPlugin = (defaultRequiresApproval) => defaultRequiresApproval ? { action: "require_approval", source: "plugin-default" } : { action: "approve", source: "plugin-default" };
|
|
2120
|
-
var liftUser = (match) => ({
|
|
2121
|
-
action: match.action,
|
|
2122
|
-
source: "user",
|
|
2123
|
-
pattern: match.pattern,
|
|
2124
|
-
policyId: match.policyId
|
|
2125
|
-
});
|
|
2126
|
-
var resolveEffectivePolicy = (toolId, policies, scopeRank, defaultRequiresApproval) => {
|
|
2127
|
-
const match = resolveToolPolicy(toolId, policies, scopeRank);
|
|
2128
|
-
return match ? liftUser(match) : liftPlugin(defaultRequiresApproval);
|
|
2129
|
-
};
|
|
2130
|
-
var effectivePolicyFromSorted = (toolId, sortedPolicies, defaultRequiresApproval) => {
|
|
2131
|
-
for (const p of sortedPolicies) {
|
|
2132
|
-
if (matchPattern(p.pattern, toolId)) {
|
|
2133
|
-
return {
|
|
2134
|
-
action: p.action,
|
|
2135
|
-
source: "user",
|
|
2136
|
-
pattern: p.pattern,
|
|
2137
|
-
policyId: p.id
|
|
2138
|
-
};
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
return liftPlugin(defaultRequiresApproval);
|
|
2142
|
-
};
|
|
2143
|
-
var rowToToolPolicy = (row) => ({
|
|
2144
|
-
id: PolicyId.make(row.id),
|
|
2145
|
-
scopeId: ScopeId.make(row.scope_id),
|
|
2146
|
-
pattern: row.pattern,
|
|
2147
|
-
action: row.action,
|
|
2148
|
-
position: row.position,
|
|
2149
|
-
createdAt: row.created_at,
|
|
2150
|
-
updatedAt: row.updated_at
|
|
2151
|
-
});
|
|
2152
|
-
var ToolPolicyActionSchema = Schema9.Literals([
|
|
2153
|
-
"approve",
|
|
2154
|
-
"require_approval",
|
|
2155
|
-
"block"
|
|
2156
|
-
]);
|
|
2157
|
-
|
|
2158
|
-
// src/types.ts
|
|
2159
|
-
import { Schema as Schema10 } from "effect";
|
|
2160
|
-
var ToolSchema = class extends Schema10.Class("ToolSchema")({
|
|
2161
|
-
id: ToolId,
|
|
2162
|
-
name: Schema10.optional(Schema10.String),
|
|
2163
|
-
description: Schema10.optional(Schema10.String),
|
|
2164
|
-
inputSchema: Schema10.optional(Schema10.Unknown),
|
|
2165
|
-
outputSchema: Schema10.optional(Schema10.Unknown),
|
|
2166
|
-
inputTypeScript: Schema10.optional(Schema10.String),
|
|
2167
|
-
outputTypeScript: Schema10.optional(Schema10.String),
|
|
2168
|
-
typeScriptDefinitions: Schema10.optional(
|
|
2169
|
-
Schema10.Record(Schema10.String, Schema10.String)
|
|
2170
|
-
)
|
|
2171
|
-
}) {
|
|
2172
|
-
};
|
|
2173
|
-
var SourceDetectionResult = class extends Schema10.Class(
|
|
2174
|
-
"SourceDetectionResult"
|
|
2175
|
-
)({
|
|
2176
|
-
/** Plugin id that recognized the URL (e.g. "openapi", "graphql"). */
|
|
2177
|
-
kind: Schema10.String,
|
|
2178
|
-
/** Confidence tier — UI uses this to pick a winner when multiple
|
|
2179
|
-
* plugins claim a URL. */
|
|
2180
|
-
confidence: Schema10.Literals(["high", "medium", "low"]),
|
|
2181
|
-
/** The (possibly normalized) endpoint the plugin will use. */
|
|
2182
|
-
endpoint: Schema10.String,
|
|
2183
|
-
/** Human-readable name suggestion, typically derived from spec title
|
|
2184
|
-
* or URL hostname. */
|
|
2185
|
-
name: Schema10.String,
|
|
2186
|
-
/** Namespace suggestion — the plugin's recommendation for the source
|
|
2187
|
-
* id. UI may override. */
|
|
2188
|
-
namespace: Schema10.String
|
|
2189
|
-
}) {
|
|
2190
|
-
};
|
|
2191
|
-
|
|
2192
|
-
// src/schema-types.ts
|
|
2193
|
-
var VALID_IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
2194
|
-
var REF_PATTERN = /^#\/(?:\$defs|definitions)\/(.+)$/;
|
|
2195
|
-
var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
2196
|
-
var asStringArray = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2197
|
-
var truncate = (value, maxLength) => value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 4))} ...`;
|
|
2198
|
-
var formatPropertyKey = (value) => VALID_IDENTIFIER_PATTERN.test(value) ? value : JSON.stringify(value);
|
|
2199
|
-
var refNameFromPointer = (ref) => ref.match(REF_PATTERN)?.[1];
|
|
2200
|
-
var refFallbackLabel = (ref) => refNameFromPointer(ref) ?? ref.split("/").at(-1) ?? ref;
|
|
2201
|
-
var summarizeLargeComposite = (schema, maxCompositeMembers) => {
|
|
2202
|
-
for (const kind of ["oneOf", "anyOf"]) {
|
|
2203
|
-
const items = schema[kind];
|
|
2204
|
-
if (Array.isArray(items) && items.length > maxCompositeMembers) {
|
|
2205
|
-
return { kind, count: items.length };
|
|
2206
|
-
}
|
|
2207
|
-
}
|
|
2208
|
-
return null;
|
|
2209
|
-
};
|
|
2210
|
-
var primitiveTypeName = (value) => {
|
|
2211
|
-
switch (value) {
|
|
2212
|
-
case "integer":
|
|
2213
|
-
case "number":
|
|
2214
|
-
return "number";
|
|
2215
|
-
case "string":
|
|
2216
|
-
case "boolean":
|
|
2217
|
-
case "null":
|
|
2218
|
-
return value;
|
|
2219
|
-
case "array":
|
|
2220
|
-
return "unknown[]";
|
|
2221
|
-
case "object":
|
|
2222
|
-
return "Record<string, unknown>";
|
|
2223
|
-
default:
|
|
2224
|
-
return "unknown";
|
|
2225
|
-
}
|
|
2226
|
-
};
|
|
2227
|
-
var renderComposite = (input) => {
|
|
2228
|
-
const rawItems = input.schema[input.key];
|
|
2229
|
-
const items = Array.isArray(rawItems) ? rawItems.map((item) => asRecord(item)) : [];
|
|
2230
|
-
if (items.length === 0) {
|
|
2231
|
-
return null;
|
|
2232
|
-
}
|
|
2233
|
-
const labels = items.map((item) => input.render(item, input.depthRemaining - 1)).filter((label) => label.length > 0);
|
|
2234
|
-
if (labels.length === 0) {
|
|
2235
|
-
return null;
|
|
2236
|
-
}
|
|
2237
|
-
return labels.join(input.key === "allOf" ? " & " : " | ");
|
|
2238
|
-
};
|
|
2239
|
-
var localDefinitionsFromSchema = (schema) => {
|
|
2240
|
-
const root = asRecord(schema);
|
|
2241
|
-
const defs = /* @__PURE__ */ new Map();
|
|
2242
|
-
for (const [key, value] of Object.entries(asRecord(root.$defs))) {
|
|
2243
|
-
defs.set(key, value);
|
|
2244
|
-
}
|
|
2245
|
-
for (const [key, value] of Object.entries(asRecord(root.definitions))) {
|
|
2246
|
-
defs.set(key, value);
|
|
2247
|
-
}
|
|
2248
|
-
return defs;
|
|
2249
|
-
};
|
|
2250
|
-
var schemaToTypeScriptPreview = (schema, options = {}) => {
|
|
2251
|
-
const localDefs = localDefinitionsFromSchema(schema);
|
|
2252
|
-
return schemaToTypeScriptPreviewWithDefs(schema, localDefs, options);
|
|
2253
|
-
};
|
|
2254
|
-
var schemaToTypeScriptPreviewWithDefs = (schema, defs, options = {}) => {
|
|
2255
|
-
const maxLength = options.maxLength ?? 400;
|
|
2256
|
-
const maxDepth = options.maxDepth ?? 6;
|
|
2257
|
-
const maxProperties = options.maxProperties ?? 12;
|
|
2258
|
-
const maxRefDepth = options.maxRefDepth ?? 3;
|
|
2259
|
-
const maxCompositeMembers = options.maxCompositeMembers ?? 8;
|
|
2260
|
-
const render = (input) => {
|
|
2261
|
-
const current = asRecord(input.currentInput);
|
|
2262
|
-
if (input.depthRemaining <= 0) {
|
|
2263
|
-
if (typeof current.title === "string" && current.title.length > 0) {
|
|
2264
|
-
return current.title;
|
|
2265
|
-
}
|
|
2266
|
-
if (current.type === "array") {
|
|
2267
|
-
return "unknown[]";
|
|
2268
|
-
}
|
|
2269
|
-
if (current.type === "object" || current.properties) {
|
|
2270
|
-
return "Record<string, unknown>";
|
|
2271
|
-
}
|
|
2272
|
-
return "unknown";
|
|
2273
|
-
}
|
|
2274
|
-
if (typeof current.$ref === "string") {
|
|
2275
|
-
const refLabel = refFallbackLabel(current.$ref);
|
|
2276
|
-
return input.refDepthRemaining > 0 ? refLabel : `unknown /* ${refLabel} omitted */`;
|
|
2277
|
-
}
|
|
2278
|
-
if ("const" in current) {
|
|
2279
|
-
return JSON.stringify(current.const);
|
|
2280
|
-
}
|
|
2281
|
-
const enumValues = Array.isArray(current.enum) ? current.enum : [];
|
|
2282
|
-
if (enumValues.length > 0) {
|
|
2283
|
-
return truncate(enumValues.map((value) => JSON.stringify(value)).join(" | "), maxLength);
|
|
2284
|
-
}
|
|
2285
|
-
const largeComposite = summarizeLargeComposite(current, maxCompositeMembers);
|
|
2286
|
-
if (largeComposite) {
|
|
2287
|
-
return `unknown /* ${largeComposite.count}-way ${largeComposite.kind} omitted */`;
|
|
2288
|
-
}
|
|
2289
|
-
const renderNested = (value) => render({
|
|
2290
|
-
currentInput: value,
|
|
2291
|
-
depthRemaining: input.depthRemaining - 1,
|
|
2292
|
-
refDepthRemaining: input.refDepthRemaining
|
|
2293
|
-
});
|
|
2294
|
-
const composite = renderComposite({
|
|
2295
|
-
key: "oneOf",
|
|
2296
|
-
schema: current,
|
|
2297
|
-
render: (value) => renderNested(value),
|
|
2298
|
-
depthRemaining: input.depthRemaining
|
|
2299
|
-
}) ?? renderComposite({
|
|
2300
|
-
key: "anyOf",
|
|
2301
|
-
schema: current,
|
|
2302
|
-
render: (value) => renderNested(value),
|
|
2303
|
-
depthRemaining: input.depthRemaining
|
|
2304
|
-
}) ?? renderComposite({
|
|
2305
|
-
key: "allOf",
|
|
2306
|
-
schema: current,
|
|
2307
|
-
render: (value) => renderNested(value),
|
|
2308
|
-
depthRemaining: input.depthRemaining
|
|
2309
|
-
});
|
|
2310
|
-
if (composite) {
|
|
2311
|
-
return truncate(composite, maxLength);
|
|
2312
|
-
}
|
|
2313
|
-
if (current.nullable === true) {
|
|
2314
|
-
const { nullable: _nullable, ...rest } = current;
|
|
2315
|
-
return truncate(
|
|
2316
|
-
`${render({
|
|
2317
|
-
currentInput: rest,
|
|
2318
|
-
depthRemaining: input.depthRemaining,
|
|
2319
|
-
refDepthRemaining: input.refDepthRemaining
|
|
2320
|
-
})} | null`,
|
|
2321
|
-
maxLength
|
|
2322
|
-
);
|
|
2323
|
-
}
|
|
2324
|
-
if (current.type === "array") {
|
|
2325
|
-
const itemLabel = current.items ? render({
|
|
2326
|
-
currentInput: current.items,
|
|
2327
|
-
depthRemaining: input.depthRemaining - 1,
|
|
2328
|
-
refDepthRemaining: input.refDepthRemaining
|
|
2329
|
-
}) : "unknown";
|
|
2330
|
-
return truncate(`${itemLabel}[]`, maxLength);
|
|
2331
|
-
}
|
|
2332
|
-
if (current.type === "object" || current.properties) {
|
|
2333
|
-
const properties = asRecord(current.properties);
|
|
2334
|
-
const propertyKeys = Object.keys(properties);
|
|
2335
|
-
const required = new Set(asStringArray(current.required));
|
|
2336
|
-
const additionalProperties = current.additionalProperties;
|
|
2337
|
-
const additionalPropertiesLabel = additionalProperties && typeof additionalProperties === "object" ? render({
|
|
2338
|
-
currentInput: additionalProperties,
|
|
2339
|
-
depthRemaining: input.depthRemaining - 1,
|
|
2340
|
-
refDepthRemaining: input.refDepthRemaining
|
|
2341
|
-
}) : additionalProperties === true ? "unknown" : null;
|
|
2342
|
-
if (propertyKeys.length === 0) {
|
|
2343
|
-
if (additionalPropertiesLabel) {
|
|
2344
|
-
return truncate(`Record<string, ${additionalPropertiesLabel}>`, maxLength);
|
|
2345
|
-
}
|
|
2346
|
-
return "Record<string, unknown>";
|
|
2347
|
-
}
|
|
2348
|
-
const visibleKeys = propertyKeys.slice(0, maxProperties);
|
|
2349
|
-
const parts = visibleKeys.map(
|
|
2350
|
-
(key) => `${formatPropertyKey(key)}${required.has(key) ? "" : "?"}: ${render({
|
|
2351
|
-
currentInput: properties[key],
|
|
2352
|
-
depthRemaining: input.depthRemaining - 1,
|
|
2353
|
-
refDepthRemaining: input.refDepthRemaining
|
|
2354
|
-
})}`
|
|
2355
|
-
);
|
|
2356
|
-
if (visibleKeys.length < propertyKeys.length) {
|
|
2357
|
-
parts.push("...");
|
|
2358
|
-
}
|
|
2359
|
-
if (additionalPropertiesLabel) {
|
|
2360
|
-
parts.push(`[key: string]: ${additionalPropertiesLabel}`);
|
|
2361
|
-
}
|
|
2362
|
-
return truncate(`{ ${parts.join("; ")} }`, maxLength);
|
|
2363
|
-
}
|
|
2364
|
-
if (Array.isArray(current.type)) {
|
|
2365
|
-
return truncate(
|
|
2366
|
-
current.type.filter((value) => typeof value === "string").map(primitiveTypeName).join(" | "),
|
|
2367
|
-
maxLength
|
|
2368
|
-
);
|
|
2369
|
-
}
|
|
2370
|
-
if (typeof current.type === "string") {
|
|
2371
|
-
return primitiveTypeName(current.type);
|
|
2372
|
-
}
|
|
2373
|
-
return "unknown";
|
|
2374
|
-
};
|
|
2375
|
-
const referencedDepths = /* @__PURE__ */ new Map();
|
|
2376
|
-
const collectPreviewRefs = (currentInput, refDepth) => {
|
|
2377
|
-
const current = asRecord(currentInput);
|
|
2378
|
-
if (summarizeLargeComposite(current, maxCompositeMembers)) {
|
|
2379
|
-
return;
|
|
2380
|
-
}
|
|
2381
|
-
if (typeof current.$ref === "string") {
|
|
2382
|
-
const name = refNameFromPointer(current.$ref);
|
|
2383
|
-
if (!name) {
|
|
2384
|
-
return;
|
|
2385
|
-
}
|
|
2386
|
-
const existingDepth = referencedDepths.get(name);
|
|
2387
|
-
if (existingDepth !== void 0 && existingDepth <= refDepth) {
|
|
2388
|
-
return;
|
|
2389
|
-
}
|
|
2390
|
-
referencedDepths.set(name, refDepth);
|
|
2391
|
-
if (refDepth >= maxRefDepth) {
|
|
2392
|
-
return;
|
|
2393
|
-
}
|
|
2394
|
-
const target = defs.get(name);
|
|
2395
|
-
if (target !== void 0) {
|
|
2396
|
-
collectPreviewRefs(target, refDepth + 1);
|
|
2397
|
-
}
|
|
2398
|
-
return;
|
|
2399
|
-
}
|
|
2400
|
-
for (const value of Object.values(current)) {
|
|
2401
|
-
if (value && typeof value === "object") {
|
|
2402
|
-
if (Array.isArray(value)) {
|
|
2403
|
-
for (const item of value) {
|
|
2404
|
-
collectPreviewRefs(item, refDepth);
|
|
2405
|
-
}
|
|
2406
|
-
} else {
|
|
2407
|
-
collectPreviewRefs(value, refDepth);
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
};
|
|
2412
|
-
collectPreviewRefs(schema, 1);
|
|
2413
|
-
const definitions = Object.fromEntries(
|
|
2414
|
-
[...referencedDepths.entries()].sort(([left], [right]) => left.localeCompare(right)).flatMap(([name, refDepth]) => {
|
|
2415
|
-
const target = defs.get(name);
|
|
2416
|
-
if (target === void 0) {
|
|
2417
|
-
return [];
|
|
2418
|
-
}
|
|
2419
|
-
return [
|
|
2420
|
-
[
|
|
2421
|
-
name,
|
|
2422
|
-
render({
|
|
2423
|
-
currentInput: target,
|
|
2424
|
-
depthRemaining: maxDepth,
|
|
2425
|
-
refDepthRemaining: Math.max(0, maxRefDepth - refDepth)
|
|
2426
|
-
})
|
|
2427
|
-
]
|
|
2428
|
-
];
|
|
2429
|
-
})
|
|
2430
|
-
);
|
|
2431
|
-
return {
|
|
2432
|
-
type: render({
|
|
2433
|
-
currentInput: schema,
|
|
2434
|
-
depthRemaining: maxDepth,
|
|
2435
|
-
refDepthRemaining: maxRefDepth
|
|
2436
|
-
}),
|
|
2437
|
-
definitions
|
|
2438
|
-
};
|
|
2439
|
-
};
|
|
2440
|
-
var buildToolTypeScriptPreview = (input) => {
|
|
2441
|
-
const inputPreview = input.inputSchema !== void 0 ? schemaToTypeScriptPreviewWithDefs(input.inputSchema, input.defs, input.options) : null;
|
|
2442
|
-
const outputPreview = input.outputSchema !== void 0 ? schemaToTypeScriptPreviewWithDefs(input.outputSchema, input.defs, input.options) : null;
|
|
2443
|
-
const mergedDefinitions = {
|
|
2444
|
-
...inputPreview?.definitions,
|
|
2445
|
-
...outputPreview?.definitions
|
|
2446
|
-
};
|
|
2447
|
-
return {
|
|
2448
|
-
...inputPreview ? { inputTypeScript: inputPreview.type } : {},
|
|
2449
|
-
...outputPreview ? { outputTypeScript: outputPreview.type } : {},
|
|
2450
|
-
...Object.keys(mergedDefinitions).length > 0 ? { typeScriptDefinitions: mergedDefinitions } : {}
|
|
2451
|
-
};
|
|
2452
|
-
};
|
|
2453
|
-
|
|
2454
|
-
// src/executor.ts
|
|
2455
|
-
import { Context, Deferred, Effect as Effect10, Option, Result as Result2, Schema as Schema11, Semaphore } from "effect";
|
|
2456
|
-
import { generateKeyBetween } from "fractional-indexing";
|
|
2457
|
-
import {
|
|
2458
|
-
StorageError as StorageError3,
|
|
2459
|
-
typedAdapter
|
|
2460
|
-
} from "@executor-js/storage-core";
|
|
2461
|
-
|
|
2462
|
-
// src/scoped-adapter.ts
|
|
2463
|
-
import { Effect as Effect9 } from "effect";
|
|
2464
|
-
import {
|
|
2465
|
-
StorageError as StorageError2
|
|
2466
|
-
} from "@executor-js/storage-core";
|
|
2467
|
-
var SCOPE_FIELD = "scope_id";
|
|
2468
|
-
var collectScopedModels = (schema) => {
|
|
2469
|
-
const out = /* @__PURE__ */ new Set();
|
|
2470
|
-
for (const [model, def] of Object.entries(schema)) {
|
|
2471
|
-
if (def.fields[SCOPE_FIELD]) out.add(model);
|
|
2472
|
-
}
|
|
2473
|
-
return out;
|
|
2474
|
-
};
|
|
2475
|
-
var withScopeRead = (where, ctx) => {
|
|
2476
|
-
const base = (where ?? []).filter((w) => w.field !== SCOPE_FIELD);
|
|
2477
|
-
const callerScope = (where ?? []).find((w) => w.field === SCOPE_FIELD);
|
|
2478
|
-
if (callerScope && typeof callerScope.value === "string" && ctx.scopes.includes(callerScope.value)) {
|
|
2479
|
-
return [...base, { field: SCOPE_FIELD, value: callerScope.value }];
|
|
2480
|
-
}
|
|
2481
|
-
const scope = ctx.scopes.length === 1 ? { field: SCOPE_FIELD, value: ctx.scopes[0] } : { field: SCOPE_FIELD, value: [...ctx.scopes], operator: "in" };
|
|
2482
|
-
return [...base, scope];
|
|
2483
|
-
};
|
|
2484
|
-
var assertScopedWrite = (model, data, ctx) => {
|
|
2485
|
-
const value = data[SCOPE_FIELD];
|
|
2486
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
2487
|
-
return Effect9.fail(
|
|
2488
|
-
new StorageError2({
|
|
2489
|
-
message: `Write to scoped table "${model}" missing required \`scope_id\`. Callers must name the target scope explicitly.`,
|
|
2490
|
-
cause: void 0
|
|
2491
|
-
})
|
|
2492
|
-
);
|
|
2493
|
-
}
|
|
2494
|
-
if (!ctx.scopes.includes(value)) {
|
|
2495
|
-
return Effect9.fail(
|
|
2496
|
-
new StorageError2({
|
|
2497
|
-
message: `Write to scoped table "${model}" targets scope "${value}" which is not in the executor's scope stack [${ctx.scopes.join(", ")}].`,
|
|
2498
|
-
cause: void 0
|
|
2499
|
-
})
|
|
2500
|
-
);
|
|
2501
|
-
}
|
|
2502
|
-
return Effect9.void;
|
|
2503
|
-
};
|
|
2504
|
-
var wrapTxMethods = (inner, ctx, scopedModels) => {
|
|
2505
|
-
const isScoped = (model) => scopedModels.has(model);
|
|
2506
|
-
return {
|
|
2507
|
-
id: inner.id,
|
|
2508
|
-
create: (data) => isScoped(data.model) ? Effect9.flatMap(
|
|
2509
|
-
assertScopedWrite(
|
|
2510
|
-
data.model,
|
|
2511
|
-
data.data,
|
|
2512
|
-
ctx
|
|
2513
|
-
),
|
|
2514
|
-
() => inner.create(data)
|
|
2515
|
-
) : inner.create(data),
|
|
2516
|
-
createMany: (data) => isScoped(data.model) ? Effect9.flatMap(
|
|
2517
|
-
Effect9.all(
|
|
2518
|
-
data.data.map(
|
|
2519
|
-
(row) => assertScopedWrite(
|
|
2520
|
-
data.model,
|
|
2521
|
-
row,
|
|
2522
|
-
ctx
|
|
2523
|
-
)
|
|
2524
|
-
)
|
|
2525
|
-
),
|
|
2526
|
-
() => inner.createMany(data)
|
|
2527
|
-
) : inner.createMany(data),
|
|
2528
|
-
findOne: (data) => isScoped(data.model) ? inner.findOne({ ...data, where: withScopeRead(data.where, ctx) }) : inner.findOne(data),
|
|
2529
|
-
findMany: (data) => isScoped(data.model) ? inner.findMany({ ...data, where: withScopeRead(data.where, ctx) }) : inner.findMany(data),
|
|
2530
|
-
count: (data) => isScoped(data.model) ? inner.count({ ...data, where: withScopeRead(data.where, ctx) }) : inner.count(data),
|
|
2531
|
-
update: (data) => isScoped(data.model) ? Effect9.flatMap(
|
|
2532
|
-
// If the caller sets `scope_id` in the update payload, it
|
|
2533
|
-
// must be one of the allowed scopes. If they don't, we leave
|
|
2534
|
-
// the row's existing scope_id in place — updates are scoped
|
|
2535
|
-
// by the where filter's IN clause, so you can only mutate
|
|
2536
|
-
// rows you can read. That's sufficient for isolation; we
|
|
2537
|
-
// don't need to force-stamp on update.
|
|
2538
|
-
data.update[SCOPE_FIELD] !== void 0 ? assertScopedWrite(
|
|
2539
|
-
data.model,
|
|
2540
|
-
data.update,
|
|
2541
|
-
ctx
|
|
2542
|
-
) : Effect9.void,
|
|
2543
|
-
() => inner.update({
|
|
2544
|
-
...data,
|
|
2545
|
-
where: withScopeRead(data.where, ctx)
|
|
2546
|
-
})
|
|
2547
|
-
) : inner.update(data),
|
|
2548
|
-
updateMany: (data) => isScoped(data.model) ? Effect9.flatMap(
|
|
2549
|
-
data.update[SCOPE_FIELD] !== void 0 ? assertScopedWrite(
|
|
2550
|
-
data.model,
|
|
2551
|
-
data.update,
|
|
2552
|
-
ctx
|
|
2553
|
-
) : Effect9.void,
|
|
2554
|
-
() => inner.updateMany({
|
|
2555
|
-
...data,
|
|
2556
|
-
where: withScopeRead(data.where, ctx)
|
|
2557
|
-
})
|
|
2558
|
-
) : inner.updateMany(data),
|
|
2559
|
-
delete: (data) => isScoped(data.model) ? inner.delete({ ...data, where: withScopeRead(data.where, ctx) }) : inner.delete(data),
|
|
2560
|
-
deleteMany: (data) => isScoped(data.model) ? inner.deleteMany({ ...data, where: withScopeRead(data.where, ctx) }) : inner.deleteMany(data)
|
|
2561
|
-
};
|
|
2562
|
-
};
|
|
2563
|
-
var scopeAdapter = (inner, ctx, schema) => {
|
|
2564
|
-
const scopedModels = collectScopedModels(schema);
|
|
2565
|
-
const tx = wrapTxMethods(inner, ctx, scopedModels);
|
|
2566
|
-
return {
|
|
2567
|
-
...tx,
|
|
2568
|
-
transaction: (callback) => inner.transaction((rawTrx) => {
|
|
2569
|
-
const scopedTrx = wrapTxMethods(
|
|
2570
|
-
rawTrx,
|
|
2571
|
-
ctx,
|
|
2572
|
-
scopedModels
|
|
2573
|
-
);
|
|
2574
|
-
return callback(scopedTrx);
|
|
2575
|
-
}),
|
|
2576
|
-
createSchema: inner.createSchema,
|
|
2577
|
-
options: inner.options
|
|
2578
|
-
};
|
|
2579
|
-
};
|
|
2580
|
-
|
|
2581
|
-
// src/executor.ts
|
|
2582
|
-
var acceptAllHandler = () => Effect10.succeed(new ElicitationResponse({ action: "accept" }));
|
|
2583
|
-
var resolveElicitationHandler = (onElicitation) => onElicitation === "accept-all" ? acceptAllHandler : onElicitation;
|
|
2584
|
-
var collectSchemas = (plugins) => {
|
|
2585
|
-
const merged = { ...coreSchema };
|
|
2586
|
-
for (const plugin of plugins) {
|
|
2587
|
-
if (!plugin.schema) continue;
|
|
2588
|
-
for (const [modelKey, model] of Object.entries(plugin.schema)) {
|
|
2589
|
-
if (merged[modelKey]) {
|
|
2590
|
-
throw new Error(
|
|
2591
|
-
`Duplicate model "${modelKey}" contributed by plugin "${plugin.id}" (reserved by core or another plugin)`
|
|
2592
|
-
);
|
|
2593
|
-
}
|
|
2594
|
-
merged[modelKey] = model;
|
|
2595
|
-
}
|
|
2596
|
-
}
|
|
2597
|
-
return merged;
|
|
2598
|
-
};
|
|
2599
|
-
var rowToSource = (row) => ({
|
|
2600
|
-
id: row.id,
|
|
2601
|
-
scopeId: row.scope_id,
|
|
2602
|
-
kind: row.kind,
|
|
2603
|
-
name: row.name,
|
|
2604
|
-
url: row.url ?? void 0,
|
|
2605
|
-
pluginId: row.plugin_id,
|
|
2606
|
-
canRemove: Boolean(row.can_remove),
|
|
2607
|
-
canRefresh: Boolean(row.can_refresh),
|
|
2608
|
-
canEdit: Boolean(row.can_edit),
|
|
2609
|
-
runtime: false
|
|
2610
|
-
});
|
|
2611
|
-
var staticDeclToSource = (decl, pluginId) => ({
|
|
2612
|
-
id: decl.id,
|
|
2613
|
-
scopeId: void 0,
|
|
2614
|
-
kind: decl.kind,
|
|
2615
|
-
name: decl.name,
|
|
2616
|
-
url: decl.url,
|
|
2617
|
-
pluginId,
|
|
2618
|
-
canRemove: decl.canRemove ?? false,
|
|
2619
|
-
canRefresh: decl.canRefresh ?? false,
|
|
2620
|
-
canEdit: decl.canEdit ?? false,
|
|
2621
|
-
runtime: true
|
|
2622
|
-
});
|
|
2623
|
-
var decodeJsonColumn = (value) => {
|
|
2624
|
-
if (value === null || value === void 0) return void 0;
|
|
2625
|
-
if (typeof value !== "string") return value;
|
|
2626
|
-
try {
|
|
2627
|
-
return JSON.parse(value);
|
|
2628
|
-
} catch {
|
|
2629
|
-
return value;
|
|
2630
|
-
}
|
|
2631
|
-
};
|
|
2632
|
-
var rowToTool = (row, annotations) => ({
|
|
2633
|
-
id: row.id,
|
|
2634
|
-
sourceId: row.source_id,
|
|
2635
|
-
pluginId: row.plugin_id,
|
|
2636
|
-
name: row.name,
|
|
2637
|
-
description: row.description,
|
|
2638
|
-
inputSchema: decodeJsonColumn(row.input_schema),
|
|
2639
|
-
outputSchema: decodeJsonColumn(row.output_schema),
|
|
2640
|
-
annotations
|
|
2641
|
-
});
|
|
2642
|
-
var staticDeclToTool = (source, tool, pluginId) => ({
|
|
2643
|
-
id: `${source.id}.${tool.name}`,
|
|
2644
|
-
sourceId: source.id,
|
|
2645
|
-
pluginId,
|
|
2646
|
-
name: tool.name,
|
|
2647
|
-
description: tool.description,
|
|
2648
|
-
inputSchema: tool.inputSchema,
|
|
2649
|
-
outputSchema: tool.outputSchema,
|
|
2650
|
-
annotations: tool.annotations
|
|
2651
|
-
});
|
|
2652
|
-
var writeSourceInput = (core, pluginId, input) => Effect10.gen(function* () {
|
|
2653
|
-
yield* deleteSourceById(core, input.id, input.scope);
|
|
2654
|
-
const now = /* @__PURE__ */ new Date();
|
|
2655
|
-
yield* core.create({
|
|
2656
|
-
model: "source",
|
|
2657
|
-
data: {
|
|
2658
|
-
id: input.id,
|
|
2659
|
-
scope_id: input.scope,
|
|
2660
|
-
plugin_id: pluginId,
|
|
2661
|
-
kind: input.kind,
|
|
2662
|
-
name: input.name,
|
|
2663
|
-
url: input.url ?? void 0,
|
|
2664
|
-
can_remove: input.canRemove ?? true,
|
|
2665
|
-
can_refresh: input.canRefresh ?? false,
|
|
2666
|
-
can_edit: input.canEdit ?? false,
|
|
2667
|
-
created_at: now,
|
|
2668
|
-
updated_at: now
|
|
2669
|
-
},
|
|
2670
|
-
forceAllowId: true
|
|
2671
|
-
});
|
|
2672
|
-
if (input.tools.length > 0) {
|
|
2673
|
-
yield* core.createMany({
|
|
2674
|
-
model: "tool",
|
|
2675
|
-
data: input.tools.map((tool) => ({
|
|
2676
|
-
id: `${input.id}.${tool.name}`,
|
|
2677
|
-
scope_id: input.scope,
|
|
2678
|
-
source_id: input.id,
|
|
2679
|
-
plugin_id: pluginId,
|
|
2680
|
-
name: tool.name,
|
|
2681
|
-
description: tool.description,
|
|
2682
|
-
input_schema: tool.inputSchema ?? void 0,
|
|
2683
|
-
output_schema: tool.outputSchema ?? void 0,
|
|
2684
|
-
created_at: now,
|
|
2685
|
-
updated_at: now
|
|
2686
|
-
})),
|
|
2687
|
-
forceAllowId: true
|
|
2688
|
-
});
|
|
2689
|
-
}
|
|
2690
|
-
});
|
|
2691
|
-
var deleteSourceById = (core, sourceId, scopeId) => Effect10.gen(function* () {
|
|
2692
|
-
yield* core.deleteMany({
|
|
2693
|
-
model: "tool",
|
|
2694
|
-
where: [
|
|
2695
|
-
{ field: "source_id", value: sourceId },
|
|
2696
|
-
{ field: "scope_id", value: scopeId }
|
|
2697
|
-
]
|
|
2698
|
-
});
|
|
2699
|
-
yield* core.deleteMany({
|
|
2700
|
-
model: "definition",
|
|
2701
|
-
where: [
|
|
2702
|
-
{ field: "source_id", value: sourceId },
|
|
2703
|
-
{ field: "scope_id", value: scopeId }
|
|
2704
|
-
]
|
|
2705
|
-
});
|
|
2706
|
-
yield* core.delete({
|
|
2707
|
-
model: "source",
|
|
2708
|
-
where: [
|
|
2709
|
-
{ field: "id", value: sourceId },
|
|
2710
|
-
{ field: "scope_id", value: scopeId }
|
|
2711
|
-
]
|
|
2712
|
-
});
|
|
2713
|
-
});
|
|
2714
|
-
var writeDefinitions = (core, pluginId, input) => Effect10.gen(function* () {
|
|
2715
|
-
yield* core.deleteMany({
|
|
2716
|
-
model: "definition",
|
|
2717
|
-
where: [
|
|
2718
|
-
{ field: "source_id", value: input.sourceId },
|
|
2719
|
-
{ field: "scope_id", value: input.scope }
|
|
2720
|
-
]
|
|
2721
|
-
});
|
|
2722
|
-
const entries = Object.entries(input.definitions);
|
|
2723
|
-
if (entries.length === 0) return;
|
|
2724
|
-
const now = /* @__PURE__ */ new Date();
|
|
2725
|
-
yield* core.createMany({
|
|
2726
|
-
model: "definition",
|
|
2727
|
-
data: entries.map(([name, schema]) => ({
|
|
2728
|
-
id: `${input.sourceId}.${name}`,
|
|
2729
|
-
scope_id: input.scope,
|
|
2730
|
-
source_id: input.sourceId,
|
|
2731
|
-
plugin_id: pluginId,
|
|
2732
|
-
name,
|
|
2733
|
-
schema,
|
|
2734
|
-
created_at: now
|
|
2735
|
-
})),
|
|
2736
|
-
forceAllowId: true
|
|
2737
|
-
});
|
|
2738
|
-
});
|
|
2739
|
-
var toolMatchesFilter = (tool, filter) => {
|
|
2740
|
-
if (filter.sourceId && tool.sourceId !== filter.sourceId) return false;
|
|
2741
|
-
if (filter.query) {
|
|
2742
|
-
const q = filter.query.toLowerCase();
|
|
2743
|
-
const hay = `${tool.name} ${tool.description}`.toLowerCase();
|
|
2744
|
-
if (!hay.includes(q)) return false;
|
|
2745
|
-
}
|
|
2746
|
-
return true;
|
|
2747
|
-
};
|
|
2748
|
-
var activeAdapterRef = Context.Reference(
|
|
2749
|
-
"executor/ActiveAdapter",
|
|
2750
|
-
{ defaultValue: () => null }
|
|
2751
|
-
);
|
|
2752
|
-
var buildAdapterRouter = (root) => {
|
|
2753
|
-
const pick = (use) => Effect10.flatMap(
|
|
2754
|
-
Effect10.service(activeAdapterRef),
|
|
2755
|
-
(active) => use(active ?? root)
|
|
2756
|
-
);
|
|
2757
|
-
return {
|
|
2758
|
-
id: root.id,
|
|
2759
|
-
create: (data) => pick((a) => a.create(data)),
|
|
2760
|
-
createMany: (data) => pick((a) => a.createMany(data)),
|
|
2761
|
-
findOne: (data) => pick((a) => a.findOne(data)),
|
|
2762
|
-
findMany: (data) => pick((a) => a.findMany(data)),
|
|
2763
|
-
count: (data) => pick((a) => a.count(data)),
|
|
2764
|
-
update: (data) => pick((a) => a.update(data)),
|
|
2765
|
-
updateMany: (data) => pick((a) => a.updateMany(data)),
|
|
2766
|
-
delete: (data) => pick((a) => a.delete(data)),
|
|
2767
|
-
deleteMany: (data) => pick((a) => a.deleteMany(data)),
|
|
2768
|
-
// transaction() always opens a real boundary on the ROOT adapter so the
|
|
2769
|
-
// tx uses one real connection from the pool. If we're already inside a
|
|
2770
|
-
// parent tx (FiberRef set), skip opening a nested sql.begin — that's
|
|
2771
|
-
// the postgres.js + Hyperdrive deadlock path — and just run the
|
|
2772
|
-
// callback with the existing tx handle. In both cases the callback
|
|
2773
|
-
// sees a FiberRef-substituted adapter so further nested writes thread
|
|
2774
|
-
// through.
|
|
2775
|
-
transaction: (callback) => Effect10.flatMap(Effect10.service(activeAdapterRef), (active) => {
|
|
2776
|
-
if (active) return callback(active);
|
|
2777
|
-
return root.transaction(
|
|
2778
|
-
(trx) => Effect10.provideService(callback(trx), activeAdapterRef, trx)
|
|
2779
|
-
);
|
|
2780
|
-
})
|
|
2781
|
-
};
|
|
2782
|
-
};
|
|
2783
|
-
var createExecutor = (config) => Effect10.gen(function* () {
|
|
2784
|
-
const defaultPlugins = () => {
|
|
2785
|
-
const empty = [];
|
|
2786
|
-
return empty;
|
|
2787
|
-
};
|
|
2788
|
-
const {
|
|
2789
|
-
scopes,
|
|
2790
|
-
adapter: rootAdapter,
|
|
2791
|
-
blobs,
|
|
2792
|
-
plugins = defaultPlugins()
|
|
2793
|
-
} = config;
|
|
2794
|
-
if (scopes.length === 0) {
|
|
2795
|
-
return yield* Effect10.fail(
|
|
2796
|
-
new Error("createExecutor requires a non-empty scopes array")
|
|
2797
|
-
);
|
|
2798
|
-
}
|
|
2799
|
-
const schema = collectSchemas(plugins);
|
|
2800
|
-
const scopeIds = scopes.map((s) => s.id);
|
|
2801
|
-
const scopedRoot = scopeAdapter(
|
|
2802
|
-
rootAdapter,
|
|
2803
|
-
{ scopes: scopeIds },
|
|
2804
|
-
schema
|
|
2805
|
-
);
|
|
2806
|
-
const adapter = buildAdapterRouter(scopedRoot);
|
|
2807
|
-
const core = typedAdapter(adapter);
|
|
2808
|
-
const staticTools = /* @__PURE__ */ new Map();
|
|
2809
|
-
const staticSources = /* @__PURE__ */ new Map();
|
|
2810
|
-
const runtimes = /* @__PURE__ */ new Map();
|
|
2811
|
-
const secretProviders = /* @__PURE__ */ new Map();
|
|
2812
|
-
const connectionProviders = /* @__PURE__ */ new Map();
|
|
2813
|
-
const connectionProviderAliases = /* @__PURE__ */ new Map([
|
|
2814
|
-
["mcp:oauth2", "oauth2"],
|
|
2815
|
-
["openapi:oauth2", "oauth2"],
|
|
2816
|
-
["google-discovery:google", "oauth2"],
|
|
2817
|
-
["google-discovery:oauth2", "oauth2"]
|
|
2818
|
-
]);
|
|
2819
|
-
const resolveConnectionProvider = (key) => {
|
|
2820
|
-
const direct = connectionProviders.get(key);
|
|
2821
|
-
if (direct) return direct;
|
|
2822
|
-
const canonical = connectionProviderAliases.get(key);
|
|
2823
|
-
return canonical ? connectionProviders.get(canonical) : void 0;
|
|
2824
|
-
};
|
|
2825
|
-
const refreshInFlight = /* @__PURE__ */ new Map();
|
|
2826
|
-
const refreshInFlightLock = Semaphore.makeUnsafe(1);
|
|
2827
|
-
const extensions = {};
|
|
2828
|
-
const scopePrecedence = /* @__PURE__ */ new Map();
|
|
2829
|
-
scopeIds.forEach((s, i) => scopePrecedence.set(s, i));
|
|
2830
|
-
const scopeRank = (row) => scopePrecedence.get(row.scope_id) ?? Infinity;
|
|
2831
|
-
const findInnermost = (rows) => {
|
|
2832
|
-
if (rows.length === 0) return null;
|
|
2833
|
-
let winner;
|
|
2834
|
-
let best = Infinity;
|
|
2835
|
-
for (const row of rows) {
|
|
2836
|
-
const rank = scopeRank(row);
|
|
2837
|
-
if (rank < best) {
|
|
2838
|
-
best = rank;
|
|
2839
|
-
winner = row;
|
|
2840
|
-
}
|
|
2841
|
-
}
|
|
2842
|
-
return winner ?? null;
|
|
2843
|
-
};
|
|
2844
|
-
const secretRowsForId = (id) => core.findMany({
|
|
2845
|
-
model: "secret",
|
|
2846
|
-
where: [{ field: "id", value: id }]
|
|
2847
|
-
});
|
|
2848
|
-
const resolveSecretValueFromRows = (id, rows) => Effect10.gen(function* () {
|
|
2849
|
-
const ordered = [...rows].sort(
|
|
2850
|
-
(a, b) => (scopePrecedence.get(a.scope_id) ?? Infinity) - (scopePrecedence.get(b.scope_id) ?? Infinity)
|
|
2851
|
-
);
|
|
2852
|
-
for (const row of ordered) {
|
|
2853
|
-
const provider = secretProviders.get(row.provider);
|
|
2854
|
-
if (!provider) continue;
|
|
2855
|
-
const value = yield* provider.get(id, row.scope_id);
|
|
2856
|
-
if (value !== null) return value;
|
|
2857
|
-
}
|
|
2858
|
-
const fallbackScope = scopeIds[0];
|
|
2859
|
-
const candidates = [...secretProviders.values()].filter(
|
|
2860
|
-
(p) => p.list
|
|
2861
|
-
);
|
|
2862
|
-
const values = yield* Effect10.all(
|
|
2863
|
-
candidates.map(
|
|
2864
|
-
(p) => p.get(id, fallbackScope).pipe(Effect10.catch(() => Effect10.succeed(null)))
|
|
2865
|
-
),
|
|
2866
|
-
{ concurrency: "unbounded" }
|
|
2867
|
-
);
|
|
2868
|
-
for (const value of values) if (value !== null) return value;
|
|
2869
|
-
return null;
|
|
2870
|
-
});
|
|
2871
|
-
const secretsGet = (id) => Effect10.gen(function* () {
|
|
2872
|
-
const rows = yield* secretRowsForId(id);
|
|
2873
|
-
const owned = rows.find((row) => row.owned_by_connection_id);
|
|
2874
|
-
if (owned) {
|
|
2875
|
-
return yield* Effect10.fail(
|
|
2876
|
-
new SecretOwnedByConnectionError({
|
|
2877
|
-
secretId: SecretId.make(id),
|
|
2878
|
-
connectionId: ConnectionId.make(
|
|
2879
|
-
owned.owned_by_connection_id
|
|
2880
|
-
)
|
|
2881
|
-
})
|
|
2882
|
-
);
|
|
2883
|
-
}
|
|
2884
|
-
return yield* resolveSecretValueFromRows(id, rows);
|
|
2885
|
-
});
|
|
2886
|
-
const connectionSecretGet = (id) => Effect10.gen(function* () {
|
|
2887
|
-
const rows = yield* secretRowsForId(id);
|
|
2888
|
-
return yield* resolveSecretValueFromRows(id, rows);
|
|
2889
|
-
});
|
|
2890
|
-
const secretRouteHasBackingValue = (row) => {
|
|
2891
|
-
const provider = secretProviders.get(row.provider);
|
|
2892
|
-
if (!provider?.has) return Effect10.succeed(true);
|
|
2893
|
-
return provider.has(row.id, row.scope_id).pipe(Effect10.catch(() => Effect10.succeed(false)));
|
|
2894
|
-
};
|
|
2895
|
-
const secretsSet = (input) => Effect10.gen(function* () {
|
|
2896
|
-
if (!scopeIds.includes(input.scope)) {
|
|
2897
|
-
return yield* Effect10.fail(
|
|
2898
|
-
new StorageError3({
|
|
2899
|
-
message: `secrets.set targets scope "${input.scope}" which is not in the executor's scope stack [${scopeIds.join(", ")}].`,
|
|
2900
|
-
cause: void 0
|
|
2901
|
-
})
|
|
2902
|
-
);
|
|
2903
|
-
}
|
|
2904
|
-
let target;
|
|
2905
|
-
if (input.provider) {
|
|
2906
|
-
target = secretProviders.get(input.provider);
|
|
2907
|
-
if (!target) {
|
|
2908
|
-
return yield* Effect10.fail(
|
|
2909
|
-
new StorageError3({
|
|
2910
|
-
message: `Unknown secret provider: ${input.provider}`,
|
|
2911
|
-
cause: void 0
|
|
2912
|
-
})
|
|
2913
|
-
);
|
|
2914
|
-
}
|
|
2915
|
-
} else {
|
|
2916
|
-
for (const provider of secretProviders.values()) {
|
|
2917
|
-
if (provider.writable && provider.set) {
|
|
2918
|
-
target = provider;
|
|
2919
|
-
break;
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
if (!target) {
|
|
2923
|
-
return yield* Effect10.fail(
|
|
2924
|
-
new StorageError3({
|
|
2925
|
-
message: "No writable secret providers registered",
|
|
2926
|
-
cause: void 0
|
|
2927
|
-
})
|
|
2928
|
-
);
|
|
2929
|
-
}
|
|
2930
|
-
}
|
|
2931
|
-
if (!target.writable || !target.set) {
|
|
2932
|
-
return yield* Effect10.fail(
|
|
2933
|
-
new StorageError3({
|
|
2934
|
-
message: `Secret provider "${target.key}" is read-only`,
|
|
2935
|
-
cause: void 0
|
|
2936
|
-
})
|
|
2937
|
-
);
|
|
2938
|
-
}
|
|
2939
|
-
yield* target.set(input.id, input.value, input.scope);
|
|
2940
|
-
const now = /* @__PURE__ */ new Date();
|
|
2941
|
-
yield* core.delete({
|
|
2942
|
-
model: "secret",
|
|
2943
|
-
where: [
|
|
2944
|
-
{ field: "id", value: input.id },
|
|
2945
|
-
{ field: "scope_id", value: input.scope }
|
|
2946
|
-
]
|
|
2947
|
-
});
|
|
2948
|
-
yield* core.create({
|
|
2949
|
-
model: "secret",
|
|
2950
|
-
data: {
|
|
2951
|
-
id: input.id,
|
|
2952
|
-
scope_id: input.scope,
|
|
2953
|
-
name: input.name,
|
|
2954
|
-
provider: target.key,
|
|
2955
|
-
created_at: now
|
|
2956
|
-
},
|
|
2957
|
-
forceAllowId: true
|
|
2958
|
-
});
|
|
2959
|
-
return new SecretRef({
|
|
2960
|
-
id: input.id,
|
|
2961
|
-
scopeId: input.scope,
|
|
2962
|
-
name: input.name,
|
|
2963
|
-
provider: target.key,
|
|
2964
|
-
createdAt: now
|
|
2965
|
-
});
|
|
2966
|
-
});
|
|
2967
|
-
const secretsRemove = (id) => Effect10.gen(function* () {
|
|
2968
|
-
const rows = yield* core.findMany({
|
|
2969
|
-
model: "secret",
|
|
2970
|
-
where: [{ field: "id", value: id }]
|
|
2971
|
-
});
|
|
2972
|
-
const target = findInnermost(rows);
|
|
2973
|
-
if (target && target.owned_by_connection_id) {
|
|
2974
|
-
return yield* Effect10.fail(
|
|
2975
|
-
new SecretOwnedByConnectionError({
|
|
2976
|
-
secretId: SecretId.make(id),
|
|
2977
|
-
connectionId: ConnectionId.make(
|
|
2978
|
-
target.owned_by_connection_id
|
|
2979
|
-
)
|
|
2980
|
-
})
|
|
2981
|
-
);
|
|
2982
|
-
}
|
|
2983
|
-
const targetScope = target?.scope_id ?? scopeIds[0];
|
|
2984
|
-
const deleters = [...secretProviders.values()].filter(
|
|
2985
|
-
(p) => !!(p.writable && p.delete)
|
|
2986
|
-
);
|
|
2987
|
-
yield* Effect10.all(
|
|
2988
|
-
deleters.map((p) => p.delete(id, targetScope)),
|
|
2989
|
-
{ concurrency: "unbounded" }
|
|
2990
|
-
);
|
|
2991
|
-
if (target) {
|
|
2992
|
-
yield* core.delete({
|
|
2993
|
-
model: "secret",
|
|
2994
|
-
where: [
|
|
2995
|
-
{ field: "id", value: id },
|
|
2996
|
-
{ field: "scope_id", value: targetScope }
|
|
2997
|
-
]
|
|
2998
|
-
});
|
|
2999
|
-
}
|
|
3000
|
-
});
|
|
3001
|
-
const secretsList = () => Effect10.gen(function* () {
|
|
3002
|
-
const byId = /* @__PURE__ */ new Map();
|
|
3003
|
-
const allRows = yield* core.findMany({ model: "secret" });
|
|
3004
|
-
const connectionOwnedIds = new Set(
|
|
3005
|
-
allRows.filter((r) => r.owned_by_connection_id).map((r) => r.id)
|
|
3006
|
-
);
|
|
3007
|
-
const rows = allRows.filter((r) => !r.owned_by_connection_id);
|
|
3008
|
-
const precedence = /* @__PURE__ */ new Map();
|
|
3009
|
-
scopeIds.forEach((id, index) => precedence.set(id, index));
|
|
3010
|
-
const pick = (row) => {
|
|
3011
|
-
const existing = byId.get(row.id);
|
|
3012
|
-
const incomingScope = row.scope_id;
|
|
3013
|
-
const incomingRank = precedence.get(incomingScope) ?? Number.MAX_SAFE_INTEGER;
|
|
3014
|
-
if (existing) {
|
|
3015
|
-
const existingRank = precedence.get(existing.scopeId) ?? Number.MAX_SAFE_INTEGER;
|
|
3016
|
-
if (existingRank <= incomingRank) return;
|
|
3017
|
-
}
|
|
3018
|
-
byId.set(
|
|
3019
|
-
row.id,
|
|
3020
|
-
new SecretRef({
|
|
3021
|
-
id: SecretId.make(row.id),
|
|
3022
|
-
scopeId: ScopeId.make(incomingScope),
|
|
3023
|
-
name: row.name,
|
|
3024
|
-
provider: row.provider,
|
|
3025
|
-
createdAt: row.created_at instanceof Date ? row.created_at : new Date(row.created_at)
|
|
3026
|
-
})
|
|
3027
|
-
);
|
|
3028
|
-
};
|
|
3029
|
-
for (const row of rows) {
|
|
3030
|
-
const hasBackingValue = yield* secretRouteHasBackingValue(row);
|
|
3031
|
-
if (hasBackingValue) pick(row);
|
|
3032
|
-
}
|
|
3033
|
-
const attribution = scopes[0].id;
|
|
3034
|
-
const listers = [...secretProviders.entries()].filter(
|
|
3035
|
-
([, p]) => p.list
|
|
3036
|
-
);
|
|
3037
|
-
const lists = yield* Effect10.all(
|
|
3038
|
-
listers.map(
|
|
3039
|
-
([key, p]) => p.list().pipe(
|
|
3040
|
-
Effect10.catch(() => Effect10.succeed([])),
|
|
3041
|
-
Effect10.map((entries) => ({ key, entries }))
|
|
3042
|
-
)
|
|
3043
|
-
),
|
|
3044
|
-
{ concurrency: "unbounded" }
|
|
3045
|
-
);
|
|
3046
|
-
for (const { key, entries } of lists) {
|
|
3047
|
-
for (const entry of entries) {
|
|
3048
|
-
if (byId.has(entry.id)) continue;
|
|
3049
|
-
if (connectionOwnedIds.has(entry.id)) continue;
|
|
3050
|
-
byId.set(
|
|
3051
|
-
entry.id,
|
|
3052
|
-
new SecretRef({
|
|
3053
|
-
id: SecretId.make(entry.id),
|
|
3054
|
-
scopeId: attribution,
|
|
3055
|
-
name: entry.name,
|
|
3056
|
-
provider: key,
|
|
3057
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
3058
|
-
})
|
|
3059
|
-
);
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
|
-
return Array.from(byId.values());
|
|
3063
|
-
});
|
|
3064
|
-
const secretsListForCtx = () => Effect10.gen(function* () {
|
|
3065
|
-
const list = yield* secretsList();
|
|
3066
|
-
return list.map((ref) => ({
|
|
3067
|
-
id: String(ref.id),
|
|
3068
|
-
name: ref.name,
|
|
3069
|
-
provider: ref.provider
|
|
3070
|
-
}));
|
|
3071
|
-
});
|
|
3072
|
-
const CONNECTION_REFRESH_SKEW_MS = 6e4;
|
|
3073
|
-
const decodeProviderState2 = Schema11.decodeUnknownOption(
|
|
3074
|
-
ConnectionProviderState
|
|
3075
|
-
);
|
|
3076
|
-
const rowToConnection = (row) => new ConnectionRef({
|
|
3077
|
-
id: ConnectionId.make(row.id),
|
|
3078
|
-
scopeId: ScopeId.make(row.scope_id),
|
|
3079
|
-
provider: row.provider,
|
|
3080
|
-
identityLabel: row.identity_label ?? null,
|
|
3081
|
-
accessTokenSecretId: SecretId.make(row.access_token_secret_id),
|
|
3082
|
-
refreshTokenSecretId: row.refresh_token_secret_id != null ? SecretId.make(row.refresh_token_secret_id) : null,
|
|
3083
|
-
expiresAt: row.expires_at != null ? Number(row.expires_at) : null,
|
|
3084
|
-
oauthScope: row.scope ?? null,
|
|
3085
|
-
providerState: Option.getOrNull(
|
|
3086
|
-
decodeProviderState2(decodeJsonColumn(row.provider_state))
|
|
3087
|
-
),
|
|
3088
|
-
createdAt: row.created_at instanceof Date ? row.created_at : new Date(row.created_at),
|
|
3089
|
-
updatedAt: row.updated_at instanceof Date ? row.updated_at : new Date(row.updated_at)
|
|
3090
|
-
});
|
|
3091
|
-
const findInnermostConnectionRow = (id) => Effect10.gen(function* () {
|
|
3092
|
-
const rows = yield* core.findMany({
|
|
3093
|
-
model: "connection",
|
|
3094
|
-
where: [{ field: "id", value: id }]
|
|
3095
|
-
});
|
|
3096
|
-
return findInnermost(rows);
|
|
3097
|
-
});
|
|
3098
|
-
const connectionsGet = (id) => Effect10.gen(function* () {
|
|
3099
|
-
const row = yield* findInnermostConnectionRow(id);
|
|
3100
|
-
return row ? rowToConnection(row) : null;
|
|
3101
|
-
});
|
|
3102
|
-
const connectionsList = () => Effect10.gen(function* () {
|
|
3103
|
-
const rows = yield* core.findMany({ model: "connection" });
|
|
3104
|
-
const byId = /* @__PURE__ */ new Map();
|
|
3105
|
-
const byIdRank = /* @__PURE__ */ new Map();
|
|
3106
|
-
for (const row of rows) {
|
|
3107
|
-
const rank = scopeRank(row);
|
|
3108
|
-
const existing = byIdRank.get(row.id);
|
|
3109
|
-
if (existing === void 0 || rank < existing) {
|
|
3110
|
-
byId.set(row.id, row);
|
|
3111
|
-
byIdRank.set(row.id, rank);
|
|
3112
|
-
}
|
|
3113
|
-
}
|
|
3114
|
-
return [...byId.values()].map(rowToConnection);
|
|
3115
|
-
});
|
|
3116
|
-
const writeOwnedSecret = (params) => Effect10.gen(function* () {
|
|
3117
|
-
const target = secretProviders.get(params.provider);
|
|
3118
|
-
if (!target) {
|
|
3119
|
-
return yield* Effect10.fail(
|
|
3120
|
-
new StorageError3({
|
|
3121
|
-
message: `Unknown secret provider: ${params.provider}`,
|
|
3122
|
-
cause: void 0
|
|
3123
|
-
})
|
|
3124
|
-
);
|
|
3125
|
-
}
|
|
3126
|
-
if (!target.writable || !target.set) {
|
|
3127
|
-
return yield* Effect10.fail(
|
|
3128
|
-
new StorageError3({
|
|
3129
|
-
message: `Secret provider "${target.key}" is read-only`,
|
|
3130
|
-
cause: void 0
|
|
3131
|
-
})
|
|
3132
|
-
);
|
|
3133
|
-
}
|
|
3134
|
-
yield* target.set(params.id, params.value, params.scope);
|
|
3135
|
-
const now = /* @__PURE__ */ new Date();
|
|
3136
|
-
yield* core.delete({
|
|
3137
|
-
model: "secret",
|
|
3138
|
-
where: [
|
|
3139
|
-
{ field: "id", value: params.id },
|
|
3140
|
-
{ field: "scope_id", value: params.scope }
|
|
3141
|
-
]
|
|
3142
|
-
});
|
|
3143
|
-
yield* core.create({
|
|
3144
|
-
model: "secret",
|
|
3145
|
-
data: {
|
|
3146
|
-
id: params.id,
|
|
3147
|
-
scope_id: params.scope,
|
|
3148
|
-
name: params.name,
|
|
3149
|
-
provider: target.key,
|
|
3150
|
-
owned_by_connection_id: params.ownedByConnectionId,
|
|
3151
|
-
created_at: now
|
|
3152
|
-
},
|
|
3153
|
-
forceAllowId: true
|
|
3154
|
-
});
|
|
3155
|
-
});
|
|
3156
|
-
const pickWritableProvider = (requested) => Effect10.gen(function* () {
|
|
3157
|
-
if (requested) {
|
|
3158
|
-
const p = secretProviders.get(requested);
|
|
3159
|
-
if (!p) {
|
|
3160
|
-
return yield* Effect10.fail(
|
|
3161
|
-
new StorageError3({
|
|
3162
|
-
message: `Unknown secret provider: ${requested}`,
|
|
3163
|
-
cause: void 0
|
|
3164
|
-
})
|
|
3165
|
-
);
|
|
3166
|
-
}
|
|
3167
|
-
return p;
|
|
3168
|
-
}
|
|
3169
|
-
for (const p of secretProviders.values()) {
|
|
3170
|
-
if (p.writable && p.set) return p;
|
|
3171
|
-
}
|
|
3172
|
-
return yield* Effect10.fail(
|
|
3173
|
-
new StorageError3({
|
|
3174
|
-
message: "No writable secret providers registered",
|
|
3175
|
-
cause: void 0
|
|
3176
|
-
})
|
|
3177
|
-
);
|
|
3178
|
-
});
|
|
3179
|
-
const connectionsCreate = (input) => Effect10.gen(function* () {
|
|
3180
|
-
if (!scopeIds.includes(input.scope)) {
|
|
3181
|
-
return yield* Effect10.fail(
|
|
3182
|
-
new StorageError3({
|
|
3183
|
-
message: `connections.create targets scope "${input.scope}" which is not in the executor's scope stack [${scopeIds.join(", ")}].`,
|
|
3184
|
-
cause: void 0
|
|
3185
|
-
})
|
|
3186
|
-
);
|
|
3187
|
-
}
|
|
3188
|
-
if (!resolveConnectionProvider(input.provider)) {
|
|
3189
|
-
return yield* Effect10.fail(
|
|
3190
|
-
new ConnectionProviderNotRegisteredError({
|
|
3191
|
-
provider: input.provider,
|
|
3192
|
-
connectionId: input.id
|
|
3193
|
-
})
|
|
3194
|
-
);
|
|
3195
|
-
}
|
|
3196
|
-
const writable = yield* pickWritableProvider();
|
|
3197
|
-
const now = /* @__PURE__ */ new Date();
|
|
3198
|
-
return yield* adapter.transaction(
|
|
3199
|
-
() => Effect10.gen(function* () {
|
|
3200
|
-
yield* core.delete({
|
|
3201
|
-
model: "connection",
|
|
3202
|
-
where: [
|
|
3203
|
-
{ field: "id", value: input.id },
|
|
3204
|
-
{ field: "scope_id", value: input.scope }
|
|
3205
|
-
]
|
|
3206
|
-
});
|
|
3207
|
-
yield* writeOwnedSecret({
|
|
3208
|
-
id: input.accessToken.secretId,
|
|
3209
|
-
scope: input.scope,
|
|
3210
|
-
name: input.accessToken.name,
|
|
3211
|
-
value: input.accessToken.value,
|
|
3212
|
-
provider: writable.key,
|
|
3213
|
-
ownedByConnectionId: input.id
|
|
3214
|
-
});
|
|
3215
|
-
if (input.refreshToken) {
|
|
3216
|
-
yield* writeOwnedSecret({
|
|
3217
|
-
id: input.refreshToken.secretId,
|
|
3218
|
-
scope: input.scope,
|
|
3219
|
-
name: input.refreshToken.name,
|
|
3220
|
-
value: input.refreshToken.value,
|
|
3221
|
-
provider: writable.key,
|
|
3222
|
-
ownedByConnectionId: input.id
|
|
3223
|
-
});
|
|
3224
|
-
}
|
|
3225
|
-
yield* core.create({
|
|
3226
|
-
model: "connection",
|
|
3227
|
-
data: {
|
|
3228
|
-
id: input.id,
|
|
3229
|
-
scope_id: input.scope,
|
|
3230
|
-
provider: input.provider,
|
|
3231
|
-
identity_label: input.identityLabel ?? void 0,
|
|
3232
|
-
access_token_secret_id: input.accessToken.secretId,
|
|
3233
|
-
refresh_token_secret_id: input.refreshToken?.secretId ?? void 0,
|
|
3234
|
-
expires_at: input.expiresAt ?? void 0,
|
|
3235
|
-
scope: input.oauthScope ?? void 0,
|
|
3236
|
-
provider_state: input.providerState ?? void 0,
|
|
3237
|
-
created_at: now,
|
|
3238
|
-
updated_at: now
|
|
3239
|
-
},
|
|
3240
|
-
forceAllowId: true
|
|
3241
|
-
});
|
|
3242
|
-
return new ConnectionRef({
|
|
3243
|
-
id: input.id,
|
|
3244
|
-
scopeId: input.scope,
|
|
3245
|
-
provider: input.provider,
|
|
3246
|
-
identityLabel: input.identityLabel,
|
|
3247
|
-
accessTokenSecretId: input.accessToken.secretId,
|
|
3248
|
-
refreshTokenSecretId: input.refreshToken?.secretId ?? null,
|
|
3249
|
-
expiresAt: input.expiresAt,
|
|
3250
|
-
oauthScope: input.oauthScope,
|
|
3251
|
-
providerState: input.providerState,
|
|
3252
|
-
createdAt: now,
|
|
3253
|
-
updatedAt: now
|
|
3254
|
-
});
|
|
3255
|
-
})
|
|
3256
|
-
);
|
|
3257
|
-
});
|
|
3258
|
-
const connectionsUpdateTokens = (input) => Effect10.gen(function* () {
|
|
3259
|
-
const row = yield* findInnermostConnectionRow(input.id);
|
|
3260
|
-
if (!row) {
|
|
3261
|
-
return yield* Effect10.fail(
|
|
3262
|
-
new ConnectionNotFoundError({ connectionId: input.id })
|
|
3263
|
-
);
|
|
3264
|
-
}
|
|
3265
|
-
const writable = yield* pickWritableProvider();
|
|
3266
|
-
const accessName = `Connection ${input.id} access token`;
|
|
3267
|
-
const refreshName = `Connection ${input.id} refresh token`;
|
|
3268
|
-
return yield* adapter.transaction(
|
|
3269
|
-
() => Effect10.gen(function* () {
|
|
3270
|
-
yield* writeOwnedSecret({
|
|
3271
|
-
id: row.access_token_secret_id,
|
|
3272
|
-
scope: row.scope_id,
|
|
3273
|
-
name: accessName,
|
|
3274
|
-
value: input.accessToken,
|
|
3275
|
-
provider: writable.key,
|
|
3276
|
-
ownedByConnectionId: row.id
|
|
3277
|
-
});
|
|
3278
|
-
const rotatedRefresh = input.refreshToken ?? void 0;
|
|
3279
|
-
if (rotatedRefresh && row.refresh_token_secret_id) {
|
|
3280
|
-
yield* writeOwnedSecret({
|
|
3281
|
-
id: row.refresh_token_secret_id,
|
|
3282
|
-
scope: row.scope_id,
|
|
3283
|
-
name: refreshName,
|
|
3284
|
-
value: rotatedRefresh,
|
|
3285
|
-
provider: writable.key,
|
|
3286
|
-
ownedByConnectionId: row.id
|
|
3287
|
-
});
|
|
3288
|
-
}
|
|
3289
|
-
const now = /* @__PURE__ */ new Date();
|
|
3290
|
-
const patch = { updated_at: now };
|
|
3291
|
-
if (input.expiresAt !== void 0)
|
|
3292
|
-
patch.expires_at = input.expiresAt ?? void 0;
|
|
3293
|
-
if (input.oauthScope !== void 0)
|
|
3294
|
-
patch.scope = input.oauthScope ?? void 0;
|
|
3295
|
-
if (input.providerState !== void 0)
|
|
3296
|
-
patch.provider_state = input.providerState ?? void 0;
|
|
3297
|
-
if (input.identityLabel !== void 0)
|
|
3298
|
-
patch.identity_label = input.identityLabel ?? void 0;
|
|
3299
|
-
yield* core.update({
|
|
3300
|
-
model: "connection",
|
|
3301
|
-
where: [
|
|
3302
|
-
{ field: "id", value: row.id },
|
|
3303
|
-
{ field: "scope_id", value: row.scope_id }
|
|
3304
|
-
],
|
|
3305
|
-
update: patch
|
|
3306
|
-
});
|
|
3307
|
-
const updated = yield* findInnermostConnectionRow(
|
|
3308
|
-
row.id
|
|
3309
|
-
);
|
|
3310
|
-
if (!updated) {
|
|
3311
|
-
return yield* Effect10.fail(
|
|
3312
|
-
new ConnectionNotFoundError({ connectionId: input.id })
|
|
3313
|
-
);
|
|
3314
|
-
}
|
|
3315
|
-
return rowToConnection(updated);
|
|
3316
|
-
})
|
|
3317
|
-
);
|
|
3318
|
-
});
|
|
3319
|
-
const connectionsSetIdentityLabel = (id, label) => Effect10.gen(function* () {
|
|
3320
|
-
const row = yield* findInnermostConnectionRow(id);
|
|
3321
|
-
if (!row) {
|
|
3322
|
-
return yield* Effect10.fail(
|
|
3323
|
-
new ConnectionNotFoundError({
|
|
3324
|
-
connectionId: ConnectionId.make(id)
|
|
3325
|
-
})
|
|
3326
|
-
);
|
|
3327
|
-
}
|
|
3328
|
-
yield* core.update({
|
|
3329
|
-
model: "connection",
|
|
3330
|
-
where: [
|
|
3331
|
-
{ field: "id", value: id },
|
|
3332
|
-
{ field: "scope_id", value: row.scope_id }
|
|
3333
|
-
],
|
|
3334
|
-
update: {
|
|
3335
|
-
identity_label: label ?? void 0,
|
|
3336
|
-
updated_at: /* @__PURE__ */ new Date()
|
|
3337
|
-
}
|
|
3338
|
-
});
|
|
3339
|
-
});
|
|
3340
|
-
const connectionsRemove = (id) => Effect10.gen(function* () {
|
|
3341
|
-
const row = yield* findInnermostConnectionRow(id);
|
|
3342
|
-
if (!row) return;
|
|
3343
|
-
const scope = row.scope_id;
|
|
3344
|
-
yield* adapter.transaction(
|
|
3345
|
-
() => Effect10.gen(function* () {
|
|
3346
|
-
const owned = yield* core.findMany({
|
|
3347
|
-
model: "secret",
|
|
3348
|
-
where: [
|
|
3349
|
-
{ field: "owned_by_connection_id", value: id },
|
|
3350
|
-
{ field: "scope_id", value: scope }
|
|
3351
|
-
]
|
|
3352
|
-
});
|
|
3353
|
-
const deleters = [...secretProviders.values()].filter(
|
|
3354
|
-
(p) => !!(p.writable && p.delete)
|
|
3355
|
-
);
|
|
3356
|
-
for (const secret of owned) {
|
|
3357
|
-
yield* Effect10.all(
|
|
3358
|
-
deleters.map(
|
|
3359
|
-
(p) => p.delete(secret.id, scope).pipe(
|
|
3360
|
-
Effect10.catchCause(
|
|
3361
|
-
(cause) => Effect10.logWarning(
|
|
3362
|
-
`Failed to delete connection-owned secret from provider ${p.key}`,
|
|
3363
|
-
cause
|
|
3364
|
-
).pipe(Effect10.as(false))
|
|
3365
|
-
)
|
|
3366
|
-
)
|
|
3367
|
-
),
|
|
3368
|
-
{ concurrency: "unbounded" }
|
|
3369
|
-
);
|
|
3370
|
-
}
|
|
3371
|
-
yield* core.deleteMany({
|
|
3372
|
-
model: "secret",
|
|
3373
|
-
where: [
|
|
3374
|
-
{ field: "owned_by_connection_id", value: id },
|
|
3375
|
-
{ field: "scope_id", value: scope }
|
|
3376
|
-
]
|
|
3377
|
-
});
|
|
3378
|
-
yield* core.delete({
|
|
3379
|
-
model: "connection",
|
|
3380
|
-
where: [
|
|
3381
|
-
{ field: "id", value: id },
|
|
3382
|
-
{ field: "scope_id", value: scope }
|
|
3383
|
-
]
|
|
3384
|
-
});
|
|
3385
|
-
})
|
|
3386
|
-
);
|
|
3387
|
-
});
|
|
3388
|
-
const performRefresh = (ref) => Effect10.gen(function* () {
|
|
3389
|
-
const provider = resolveConnectionProvider(ref.provider);
|
|
3390
|
-
if (!provider) {
|
|
3391
|
-
return yield* Effect10.fail(
|
|
3392
|
-
new ConnectionProviderNotRegisteredError({
|
|
3393
|
-
provider: ref.provider,
|
|
3394
|
-
connectionId: ref.id
|
|
3395
|
-
})
|
|
3396
|
-
);
|
|
3397
|
-
}
|
|
3398
|
-
if (!provider.refresh) {
|
|
3399
|
-
return yield* Effect10.fail(
|
|
3400
|
-
new ConnectionRefreshNotSupportedError({
|
|
3401
|
-
connectionId: ref.id,
|
|
3402
|
-
provider: ref.provider
|
|
3403
|
-
})
|
|
3404
|
-
);
|
|
3405
|
-
}
|
|
3406
|
-
const refreshTokenValue = ref.refreshTokenSecretId ? yield* connectionSecretGet(ref.refreshTokenSecretId) : null;
|
|
3407
|
-
const rawResult = yield* Effect10.result(
|
|
3408
|
-
provider.refresh({
|
|
3409
|
-
connectionId: ref.id,
|
|
3410
|
-
scopeId: ref.scopeId,
|
|
3411
|
-
identityLabel: ref.identityLabel,
|
|
3412
|
-
refreshToken: refreshTokenValue,
|
|
3413
|
-
providerState: ref.providerState,
|
|
3414
|
-
oauthScope: ref.oauthScope
|
|
3415
|
-
})
|
|
3416
|
-
);
|
|
3417
|
-
if (Result2.isFailure(rawResult)) {
|
|
3418
|
-
const err = rawResult.failure;
|
|
3419
|
-
if (err.reauthRequired) {
|
|
3420
|
-
return yield* Effect10.fail(
|
|
3421
|
-
new ConnectionReauthRequiredError({
|
|
3422
|
-
connectionId: err.connectionId,
|
|
3423
|
-
provider: ref.provider,
|
|
3424
|
-
message: err.message
|
|
3425
|
-
})
|
|
3426
|
-
);
|
|
3427
|
-
}
|
|
3428
|
-
return yield* Effect10.fail(err);
|
|
3429
|
-
}
|
|
3430
|
-
const result = rawResult.success;
|
|
3431
|
-
yield* connectionsUpdateTokens({
|
|
3432
|
-
id: ref.id,
|
|
3433
|
-
accessToken: result.accessToken,
|
|
3434
|
-
refreshToken: result.refreshToken,
|
|
3435
|
-
expiresAt: result.expiresAt,
|
|
3436
|
-
oauthScope: result.oauthScope,
|
|
3437
|
-
providerState: result.providerState
|
|
3438
|
-
});
|
|
3439
|
-
return result.accessToken;
|
|
3440
|
-
});
|
|
3441
|
-
const connectionsAccessToken = (id) => Effect10.gen(function* () {
|
|
3442
|
-
const row = yield* findInnermostConnectionRow(id);
|
|
3443
|
-
if (!row) {
|
|
3444
|
-
return yield* Effect10.fail(
|
|
3445
|
-
new ConnectionNotFoundError({
|
|
3446
|
-
connectionId: ConnectionId.make(id)
|
|
3447
|
-
})
|
|
3448
|
-
);
|
|
3449
|
-
}
|
|
3450
|
-
const ref = rowToConnection(row);
|
|
3451
|
-
const now = Date.now();
|
|
3452
|
-
const needsRefresh = ref.expiresAt !== null && ref.expiresAt - CONNECTION_REFRESH_SKEW_MS <= now;
|
|
3453
|
-
if (!needsRefresh) {
|
|
3454
|
-
const current = yield* connectionSecretGet(
|
|
3455
|
-
ref.accessTokenSecretId
|
|
3456
|
-
);
|
|
3457
|
-
if (current !== null) return current;
|
|
3458
|
-
}
|
|
3459
|
-
const action = yield* refreshInFlightLock.withPermits(1)(
|
|
3460
|
-
Effect10.gen(function* () {
|
|
3461
|
-
const existing = refreshInFlight.get(id);
|
|
3462
|
-
if (existing) {
|
|
3463
|
-
return {
|
|
3464
|
-
kind: "await",
|
|
3465
|
-
deferred: existing
|
|
3466
|
-
};
|
|
3467
|
-
}
|
|
3468
|
-
const deferred = yield* Deferred.make();
|
|
3469
|
-
refreshInFlight.set(id, deferred);
|
|
3470
|
-
return { kind: "lead", deferred };
|
|
3471
|
-
})
|
|
3472
|
-
);
|
|
3473
|
-
if (action.kind === "await") {
|
|
3474
|
-
return yield* Deferred.await(action.deferred);
|
|
3475
|
-
}
|
|
3476
|
-
return yield* performRefresh(ref).pipe(
|
|
3477
|
-
Effect10.onExit(
|
|
3478
|
-
(exit) => refreshInFlightLock.withPermits(1)(
|
|
3479
|
-
Effect10.gen(function* () {
|
|
3480
|
-
yield* Deferred.done(action.deferred, exit);
|
|
3481
|
-
refreshInFlight.delete(id);
|
|
3482
|
-
})
|
|
3483
|
-
)
|
|
3484
|
-
)
|
|
3485
|
-
);
|
|
3486
|
-
});
|
|
3487
|
-
const connectionsListForCtx = () => connectionsList();
|
|
3488
|
-
const oauthBundle = makeOAuth2Service({
|
|
3489
|
-
adapter: core,
|
|
3490
|
-
rawAdapter: adapter,
|
|
3491
|
-
secretsGet: (id) => secretsGet(id).pipe(
|
|
3492
|
-
Effect10.catchTag(
|
|
3493
|
-
"SecretOwnedByConnectionError",
|
|
3494
|
-
() => Effect10.succeed(null)
|
|
3495
|
-
)
|
|
3496
|
-
),
|
|
3497
|
-
secretsSet: (input) => secretsSet(input),
|
|
3498
|
-
connectionsCreate: (input) => connectionsCreate(input)
|
|
3499
|
-
});
|
|
3500
|
-
connectionProviders.set(
|
|
3501
|
-
oauthBundle.connectionProvider.key,
|
|
3502
|
-
oauthBundle.connectionProvider
|
|
3503
|
-
);
|
|
3504
|
-
for (const plugin of plugins) {
|
|
3505
|
-
if (runtimes.has(plugin.id)) {
|
|
3506
|
-
return yield* Effect10.fail(
|
|
3507
|
-
new Error(`Duplicate plugin id: ${plugin.id}`)
|
|
3508
|
-
);
|
|
3509
|
-
}
|
|
3510
|
-
const storageDeps = {
|
|
3511
|
-
scopes,
|
|
3512
|
-
adapter: typedAdapter(adapter),
|
|
3513
|
-
// Blob keys are namespaced by `<scope>/<plugin>` so two tenants
|
|
3514
|
-
// sharing a backing BlobStore can't collide or leak on the
|
|
3515
|
-
// same `(plugin, key)` pair. The store's `get`/`has` walk the
|
|
3516
|
-
// scope stack (innermost first); `put`/`delete` require the
|
|
3517
|
-
// plugin to name a target scope explicitly.
|
|
3518
|
-
blobs: pluginBlobStore(blobs, scopeIds, plugin.id)
|
|
3519
|
-
};
|
|
3520
|
-
const storage = plugin.storage(storageDeps);
|
|
3521
|
-
const ctx = {
|
|
3522
|
-
scopes,
|
|
3523
|
-
storage,
|
|
3524
|
-
core: {
|
|
3525
|
-
sources: {
|
|
3526
|
-
register: (input) => Effect10.gen(function* () {
|
|
3527
|
-
if (staticSources.has(input.id)) {
|
|
3528
|
-
return yield* Effect10.fail(
|
|
3529
|
-
new StorageError3({
|
|
3530
|
-
message: `Source id "${input.id}" collides with a static source`,
|
|
3531
|
-
cause: void 0
|
|
3532
|
-
})
|
|
3533
|
-
);
|
|
3534
|
-
}
|
|
3535
|
-
for (const tool of input.tools) {
|
|
3536
|
-
const fqid = `${input.id}.${tool.name}`;
|
|
3537
|
-
if (staticTools.has(fqid)) {
|
|
3538
|
-
return yield* Effect10.fail(
|
|
3539
|
-
new StorageError3({
|
|
3540
|
-
message: `Tool id "${fqid}" collides with a static tool`,
|
|
3541
|
-
cause: void 0
|
|
3542
|
-
})
|
|
3543
|
-
);
|
|
3544
|
-
}
|
|
3545
|
-
}
|
|
3546
|
-
yield* adapter.transaction(
|
|
3547
|
-
() => writeSourceInput(core, plugin.id, input)
|
|
3548
|
-
);
|
|
3549
|
-
}),
|
|
3550
|
-
unregister: (sourceId) => (
|
|
3551
|
-
// `unregister` is scoped to a specific source row — look up
|
|
3552
|
-
// its scope before deleting so the tool/definition sweep
|
|
3553
|
-
// only touches rows at that scope. Walk the full stack and
|
|
3554
|
-
// pick the innermost-scope shadow so an inner-scope caller
|
|
3555
|
-
// can't accidentally (via non-deterministic findOne
|
|
3556
|
-
// iteration order) unregister the outer-scope source and
|
|
3557
|
-
// wipe a bystander's data at the same time.
|
|
3558
|
-
adapter.transaction(
|
|
3559
|
-
() => Effect10.gen(function* () {
|
|
3560
|
-
const rows = yield* core.findMany({
|
|
3561
|
-
model: "source",
|
|
3562
|
-
where: [{ field: "id", value: sourceId }]
|
|
3563
|
-
});
|
|
3564
|
-
const row = findInnermost(rows);
|
|
3565
|
-
if (!row) return;
|
|
3566
|
-
yield* deleteSourceById(
|
|
3567
|
-
core,
|
|
3568
|
-
sourceId,
|
|
3569
|
-
row.scope_id
|
|
3570
|
-
);
|
|
3571
|
-
})
|
|
3572
|
-
)
|
|
3573
|
-
),
|
|
3574
|
-
update: (input) => core.update({
|
|
3575
|
-
model: "source",
|
|
3576
|
-
where: [
|
|
3577
|
-
{ field: "id", value: input.id },
|
|
3578
|
-
{ field: "scope_id", value: input.scope }
|
|
3579
|
-
],
|
|
3580
|
-
update: {
|
|
3581
|
-
...input.name !== void 0 ? { name: input.name } : {},
|
|
3582
|
-
...input.url !== void 0 ? { url: input.url ?? void 0 } : {},
|
|
3583
|
-
updated_at: /* @__PURE__ */ new Date()
|
|
3584
|
-
}
|
|
3585
|
-
}).pipe(Effect10.asVoid)
|
|
3586
|
-
},
|
|
3587
|
-
definitions: {
|
|
3588
|
-
register: (input) => adapter.transaction(
|
|
3589
|
-
() => writeDefinitions(core, plugin.id, input)
|
|
3590
|
-
)
|
|
3591
|
-
}
|
|
3592
|
-
},
|
|
3593
|
-
secrets: {
|
|
3594
|
-
get: (id) => secretsGet(id),
|
|
3595
|
-
list: () => secretsListForCtx(),
|
|
3596
|
-
set: (input) => secretsSet(input),
|
|
3597
|
-
remove: (id) => secretsRemove(id)
|
|
3598
|
-
},
|
|
3599
|
-
connections: {
|
|
3600
|
-
get: (id) => connectionsGet(id),
|
|
3601
|
-
list: () => connectionsListForCtx(),
|
|
3602
|
-
create: (input) => connectionsCreate(input),
|
|
3603
|
-
updateTokens: (input) => connectionsUpdateTokens(input),
|
|
3604
|
-
setIdentityLabel: (id, label) => connectionsSetIdentityLabel(id, label),
|
|
3605
|
-
accessToken: (id) => connectionsAccessToken(id),
|
|
3606
|
-
remove: (id) => connectionsRemove(id)
|
|
3607
|
-
},
|
|
3608
|
-
oauth: oauthBundle.service,
|
|
3609
|
-
// Open one real tx boundary and route every nested write inside
|
|
3610
|
-
// `effect` through that same handle via the activeAdapterRef —
|
|
3611
|
-
// see buildAdapterRouter above. Caller-typed errors (`E`)
|
|
3612
|
-
// propagate unchanged; storage failures also stay typed
|
|
3613
|
-
// (`StorageFailure`) so the HTTP edge wrapper can translate them.
|
|
3614
|
-
transaction: (effect) => adapter.transaction(() => effect)
|
|
3615
|
-
};
|
|
3616
|
-
const extension = plugin.extension ? plugin.extension(ctx) : {};
|
|
3617
|
-
if (plugin.extension) {
|
|
3618
|
-
extensions[plugin.id] = extension;
|
|
3619
|
-
}
|
|
3620
|
-
const decls = plugin.staticSources ? plugin.staticSources(extension) : [];
|
|
3621
|
-
for (const source of decls) {
|
|
3622
|
-
if (staticSources.has(source.id)) {
|
|
3623
|
-
return yield* Effect10.fail(
|
|
3624
|
-
new Error(
|
|
3625
|
-
`Duplicate static source id: ${source.id} (plugin ${plugin.id})`
|
|
3626
|
-
)
|
|
3627
|
-
);
|
|
3628
|
-
}
|
|
3629
|
-
staticSources.set(source.id, { source, pluginId: plugin.id });
|
|
3630
|
-
for (const tool of source.tools) {
|
|
3631
|
-
const fqid = `${source.id}.${tool.name}`;
|
|
3632
|
-
if (staticTools.has(fqid)) {
|
|
3633
|
-
return yield* Effect10.fail(
|
|
3634
|
-
new Error(
|
|
3635
|
-
`Duplicate static tool id: ${fqid} (plugin ${plugin.id})`
|
|
3636
|
-
)
|
|
3637
|
-
);
|
|
3638
|
-
}
|
|
3639
|
-
staticTools.set(fqid, {
|
|
3640
|
-
source,
|
|
3641
|
-
tool,
|
|
3642
|
-
pluginId: plugin.id,
|
|
3643
|
-
ctx
|
|
3644
|
-
});
|
|
3645
|
-
}
|
|
3646
|
-
}
|
|
3647
|
-
runtimes.set(plugin.id, { plugin, storage, ctx });
|
|
3648
|
-
if (plugin.secretProviders) {
|
|
3649
|
-
const raw = typeof plugin.secretProviders === "function" ? plugin.secretProviders(ctx) : plugin.secretProviders;
|
|
3650
|
-
const providers = Effect10.isEffect(raw) ? yield* raw : raw;
|
|
3651
|
-
for (const provider of providers) {
|
|
3652
|
-
if (secretProviders.has(provider.key)) {
|
|
3653
|
-
return yield* Effect10.fail(
|
|
3654
|
-
new Error(
|
|
3655
|
-
`Duplicate secret provider key: ${provider.key} (from plugin ${plugin.id})`
|
|
3656
|
-
)
|
|
3657
|
-
);
|
|
3658
|
-
}
|
|
3659
|
-
secretProviders.set(provider.key, provider);
|
|
3660
|
-
}
|
|
3661
|
-
}
|
|
3662
|
-
if (plugin.connectionProviders) {
|
|
3663
|
-
const raw = typeof plugin.connectionProviders === "function" ? plugin.connectionProviders(ctx) : plugin.connectionProviders;
|
|
3664
|
-
const providers = Effect10.isEffect(raw) ? yield* raw : raw;
|
|
3665
|
-
for (const provider of providers) {
|
|
3666
|
-
if (connectionProviders.has(provider.key)) {
|
|
3667
|
-
return yield* Effect10.fail(
|
|
3668
|
-
new Error(
|
|
3669
|
-
`Duplicate connection provider key: ${provider.key} (from plugin ${plugin.id})`
|
|
3670
|
-
)
|
|
3671
|
-
);
|
|
3672
|
-
}
|
|
3673
|
-
connectionProviders.set(provider.key, provider);
|
|
3674
|
-
}
|
|
3675
|
-
}
|
|
3676
|
-
}
|
|
3677
|
-
const listSources = () => Effect10.gen(function* () {
|
|
3678
|
-
const dynamic = yield* core.findMany({ model: "source" });
|
|
3679
|
-
const byId = /* @__PURE__ */ new Map();
|
|
3680
|
-
const byIdRank = /* @__PURE__ */ new Map();
|
|
3681
|
-
for (const row of dynamic) {
|
|
3682
|
-
const rank = scopeRank(row);
|
|
3683
|
-
const existing = byIdRank.get(row.id);
|
|
3684
|
-
if (existing === void 0 || rank < existing) {
|
|
3685
|
-
byId.set(row.id, row);
|
|
3686
|
-
byIdRank.set(row.id, rank);
|
|
3687
|
-
}
|
|
3688
|
-
}
|
|
3689
|
-
const dynamicDeduped = [...byId.values()];
|
|
3690
|
-
const staticList = [];
|
|
3691
|
-
for (const { source, pluginId } of staticSources.values()) {
|
|
3692
|
-
staticList.push(staticDeclToSource(source, pluginId));
|
|
3693
|
-
}
|
|
3694
|
-
const merged = [...staticList, ...dynamicDeduped.map(rowToSource)];
|
|
3695
|
-
yield* Effect10.annotateCurrentSpan({
|
|
3696
|
-
"executor.sources.static_count": staticList.length,
|
|
3697
|
-
"executor.sources.dynamic_count": dynamicDeduped.length
|
|
3698
|
-
});
|
|
3699
|
-
return merged;
|
|
3700
|
-
}).pipe(Effect10.withSpan("executor.sources.list"));
|
|
3701
|
-
const resolveAnnotationsFor = (rows) => Effect10.gen(function* () {
|
|
3702
|
-
const result = /* @__PURE__ */ new Map();
|
|
3703
|
-
if (rows.length === 0) return result;
|
|
3704
|
-
const groups = /* @__PURE__ */ new Map();
|
|
3705
|
-
for (const row of rows) {
|
|
3706
|
-
const key = `${row.plugin_id}\0${row.source_id}`;
|
|
3707
|
-
const bucket = groups.get(key);
|
|
3708
|
-
if (bucket) bucket.push(row);
|
|
3709
|
-
else groups.set(key, [row]);
|
|
3710
|
-
}
|
|
3711
|
-
const maps = yield* Effect10.forEach(
|
|
3712
|
-
[...groups],
|
|
3713
|
-
([key, groupRows]) => Effect10.gen(function* () {
|
|
3714
|
-
const [pluginId, sourceId] = key.split("\0");
|
|
3715
|
-
const runtime = runtimes.get(pluginId);
|
|
3716
|
-
if (!runtime?.plugin.resolveAnnotations) return void 0;
|
|
3717
|
-
return yield* runtime.plugin.resolveAnnotations({
|
|
3718
|
-
ctx: runtime.ctx,
|
|
3719
|
-
sourceId,
|
|
3720
|
-
toolRows: groupRows
|
|
3721
|
-
});
|
|
3722
|
-
}),
|
|
3723
|
-
{ concurrency: "unbounded" }
|
|
3724
|
-
);
|
|
3725
|
-
for (const map of maps) {
|
|
3726
|
-
if (!map) continue;
|
|
3727
|
-
for (const [toolId, annotations] of Object.entries(map)) {
|
|
3728
|
-
result.set(toolId, annotations);
|
|
3729
|
-
}
|
|
3730
|
-
}
|
|
3731
|
-
return result;
|
|
3732
|
-
});
|
|
3733
|
-
const listTools = (filter) => Effect10.gen(function* () {
|
|
3734
|
-
const dynamic = yield* core.findMany({
|
|
3735
|
-
model: "tool",
|
|
3736
|
-
where: filter?.sourceId ? [{ field: "source_id", value: filter.sourceId }] : void 0
|
|
3737
|
-
});
|
|
3738
|
-
const byId = /* @__PURE__ */ new Map();
|
|
3739
|
-
const byIdRank = /* @__PURE__ */ new Map();
|
|
3740
|
-
for (const row of dynamic) {
|
|
3741
|
-
const rank = scopeRank(row);
|
|
3742
|
-
const existing = byIdRank.get(row.id);
|
|
3743
|
-
if (existing === void 0 || rank < existing) {
|
|
3744
|
-
byId.set(row.id, row);
|
|
3745
|
-
byIdRank.set(row.id, rank);
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3748
|
-
const dynamicDeduped = [...byId.values()];
|
|
3749
|
-
const annotations = filter?.includeAnnotations === false ? /* @__PURE__ */ new Map() : yield* resolveAnnotationsFor(dynamicDeduped).pipe(
|
|
3750
|
-
Effect10.withSpan("executor.tools.list.annotations")
|
|
3751
|
-
);
|
|
3752
|
-
const out = [];
|
|
3753
|
-
for (const entry of staticTools.values()) {
|
|
3754
|
-
out.push(staticDeclToTool(entry.source, entry.tool, entry.pluginId));
|
|
3755
|
-
}
|
|
3756
|
-
for (const row of dynamicDeduped) {
|
|
3757
|
-
out.push(rowToTool(row, annotations.get(row.id)));
|
|
3758
|
-
}
|
|
3759
|
-
const filtered = filter ? out.filter((t) => toolMatchesFilter(t, filter)) : out;
|
|
3760
|
-
let result = filtered;
|
|
3761
|
-
let blockedCount = 0;
|
|
3762
|
-
if (filter?.includeBlocked !== true) {
|
|
3763
|
-
const policies = yield* loadAllPolicies();
|
|
3764
|
-
if (policies.length > 0) {
|
|
3765
|
-
const kept = [];
|
|
3766
|
-
for (const tool of filtered) {
|
|
3767
|
-
const match = resolveToolPolicy(tool.id, policies, scopeRank);
|
|
3768
|
-
if (match?.action === "block") {
|
|
3769
|
-
blockedCount++;
|
|
3770
|
-
continue;
|
|
3771
|
-
}
|
|
3772
|
-
kept.push(tool);
|
|
3773
|
-
}
|
|
3774
|
-
result = kept;
|
|
3775
|
-
}
|
|
3776
|
-
}
|
|
3777
|
-
yield* Effect10.annotateCurrentSpan({
|
|
3778
|
-
"executor.tools.static_count": staticTools.size,
|
|
3779
|
-
"executor.tools.dynamic_count": dynamicDeduped.length,
|
|
3780
|
-
"executor.tools.result_count": result.length,
|
|
3781
|
-
"executor.tools.blocked_count": blockedCount
|
|
3782
|
-
});
|
|
3783
|
-
return result;
|
|
3784
|
-
}).pipe(Effect10.withSpan("executor.tools.list"));
|
|
3785
|
-
const loadDefinitionsForSource = (sourceId) => Effect10.gen(function* () {
|
|
3786
|
-
const defRows = yield* core.findMany({
|
|
3787
|
-
model: "definition",
|
|
3788
|
-
where: [{ field: "source_id", value: sourceId }]
|
|
3789
|
-
});
|
|
3790
|
-
const winners = /* @__PURE__ */ new Map();
|
|
3791
|
-
for (const row of defRows) {
|
|
3792
|
-
const rank = scopeRank(row);
|
|
3793
|
-
const existing = winners.get(row.name);
|
|
3794
|
-
if (!existing || rank < existing.rank) {
|
|
3795
|
-
winners.set(row.name, { row, rank });
|
|
3796
|
-
}
|
|
3797
|
-
}
|
|
3798
|
-
const out = {};
|
|
3799
|
-
for (const [name, { row }] of winners) out[name] = row.schema;
|
|
3800
|
-
return out;
|
|
3801
|
-
});
|
|
3802
|
-
const buildToolSchemaView = (opts) => Effect10.gen(function* () {
|
|
3803
|
-
const defs = opts.sourceId ? yield* loadDefinitionsForSource(opts.sourceId).pipe(
|
|
3804
|
-
Effect10.withSpan("executor.tool.schema.load_defs")
|
|
3805
|
-
) : {};
|
|
3806
|
-
const attachDefs = (schema2) => {
|
|
3807
|
-
if (schema2 == null || typeof schema2 !== "object") return schema2;
|
|
3808
|
-
if (Object.keys(defs).length === 0) return schema2;
|
|
3809
|
-
return { ...schema2, $defs: defs };
|
|
3810
|
-
};
|
|
3811
|
-
const inputSchema = attachDefs(opts.rawInput);
|
|
3812
|
-
const outputSchema = attachDefs(opts.rawOutput);
|
|
3813
|
-
const defsMap = new Map(Object.entries(defs));
|
|
3814
|
-
const preview = yield* Effect10.sync(
|
|
3815
|
-
() => buildToolTypeScriptPreview({ inputSchema, outputSchema, defs: defsMap })
|
|
3816
|
-
).pipe(
|
|
3817
|
-
Effect10.withSpan("schema.compile.preview", {
|
|
3818
|
-
attributes: {
|
|
3819
|
-
"schema.kind": "tool.preview",
|
|
3820
|
-
"schema.has_input": inputSchema !== void 0,
|
|
3821
|
-
"schema.has_output": outputSchema !== void 0,
|
|
3822
|
-
"schema.def_count": defsMap.size
|
|
3823
|
-
}
|
|
3824
|
-
})
|
|
3825
|
-
);
|
|
3826
|
-
return new ToolSchema({
|
|
3827
|
-
id: ToolId.make(opts.toolId),
|
|
3828
|
-
name: opts.name,
|
|
3829
|
-
description: opts.description,
|
|
3830
|
-
inputSchema,
|
|
3831
|
-
outputSchema,
|
|
3832
|
-
inputTypeScript: preview.inputTypeScript ?? void 0,
|
|
3833
|
-
outputTypeScript: preview.outputTypeScript ?? void 0,
|
|
3834
|
-
typeScriptDefinitions: preview.typeScriptDefinitions ?? void 0
|
|
3835
|
-
});
|
|
3836
|
-
});
|
|
3837
|
-
const toolSchema = (toolId) => Effect10.gen(function* () {
|
|
3838
|
-
const staticEntry = staticTools.get(toolId);
|
|
3839
|
-
if (staticEntry) {
|
|
3840
|
-
yield* Effect10.annotateCurrentSpan({
|
|
3841
|
-
"executor.tool.dispatch_path": "static",
|
|
3842
|
-
"executor.source_id": staticEntry.source.id,
|
|
3843
|
-
"executor.source_kind": staticEntry.source.kind
|
|
3844
|
-
});
|
|
3845
|
-
return yield* buildToolSchemaView({
|
|
3846
|
-
toolId,
|
|
3847
|
-
name: staticEntry.tool.name,
|
|
3848
|
-
description: staticEntry.tool.description,
|
|
3849
|
-
sourceId: void 0,
|
|
3850
|
-
rawInput: staticEntry.tool.inputSchema,
|
|
3851
|
-
rawOutput: staticEntry.tool.outputSchema
|
|
3852
|
-
});
|
|
3853
|
-
}
|
|
3854
|
-
const rows = yield* core.findMany({
|
|
3855
|
-
model: "tool",
|
|
3856
|
-
where: [{ field: "id", value: toolId }]
|
|
3857
|
-
}).pipe(Effect10.withSpan("executor.tool.resolve"));
|
|
3858
|
-
const row = findInnermost(rows);
|
|
3859
|
-
if (!row) return null;
|
|
3860
|
-
yield* Effect10.annotateCurrentSpan({
|
|
3861
|
-
"executor.tool.dispatch_path": "dynamic",
|
|
3862
|
-
"executor.source_id": row.source_id,
|
|
3863
|
-
"executor.plugin_id": row.plugin_id
|
|
3864
|
-
});
|
|
3865
|
-
return yield* buildToolSchemaView({
|
|
3866
|
-
toolId,
|
|
3867
|
-
name: row.name,
|
|
3868
|
-
description: row.description,
|
|
3869
|
-
sourceId: row.source_id,
|
|
3870
|
-
rawInput: decodeJsonColumn(row.input_schema),
|
|
3871
|
-
rawOutput: decodeJsonColumn(row.output_schema)
|
|
3872
|
-
});
|
|
3873
|
-
}).pipe(
|
|
3874
|
-
Effect10.withSpan("executor.tool.schema", {
|
|
3875
|
-
attributes: { "mcp.tool.name": toolId }
|
|
3876
|
-
})
|
|
3877
|
-
);
|
|
3878
|
-
const toolsDefinitions = () => Effect10.gen(function* () {
|
|
3879
|
-
const rows = yield* core.findMany({ model: "definition" });
|
|
3880
|
-
const winners = /* @__PURE__ */ new Map();
|
|
3881
|
-
for (const row of rows) {
|
|
3882
|
-
const key = `${row.source_id}\0${row.name}`;
|
|
3883
|
-
const rank = scopeRank(row);
|
|
3884
|
-
const existing = winners.get(key);
|
|
3885
|
-
if (!existing || rank < existing.rank) {
|
|
3886
|
-
winners.set(key, { row, rank });
|
|
3887
|
-
}
|
|
3888
|
-
}
|
|
3889
|
-
const out = {};
|
|
3890
|
-
for (const { row } of winners.values()) {
|
|
3891
|
-
let bucket = out[row.source_id];
|
|
3892
|
-
if (!bucket) {
|
|
3893
|
-
bucket = {};
|
|
3894
|
-
out[row.source_id] = bucket;
|
|
3895
|
-
}
|
|
3896
|
-
bucket[row.name] = row.schema;
|
|
3897
|
-
}
|
|
3898
|
-
return out;
|
|
3899
|
-
});
|
|
3900
|
-
const defaultElicitationHandler = resolveElicitationHandler(
|
|
3901
|
-
config.onElicitation
|
|
3902
|
-
);
|
|
3903
|
-
const pickHandler = (options) => options?.onElicitation ? resolveElicitationHandler(options.onElicitation) : defaultElicitationHandler;
|
|
3904
|
-
const buildElicit = (toolId, args, handler) => {
|
|
3905
|
-
return (request) => Effect10.gen(function* () {
|
|
3906
|
-
const tid = ToolId.make(toolId);
|
|
3907
|
-
const response = yield* handler({
|
|
3908
|
-
toolId: tid,
|
|
3909
|
-
args,
|
|
3910
|
-
request
|
|
3911
|
-
});
|
|
3912
|
-
if (response.action !== "accept") {
|
|
3913
|
-
return yield* new ElicitationDeclinedError({
|
|
3914
|
-
toolId: tid,
|
|
3915
|
-
action: response.action
|
|
3916
|
-
});
|
|
3917
|
-
}
|
|
3918
|
-
return response;
|
|
3919
|
-
});
|
|
3920
|
-
};
|
|
3921
|
-
const loadAllPolicies = () => core.findMany({ model: "tool_policy" });
|
|
3922
|
-
const resolveToolPolicyForId = (toolId) => Effect10.gen(function* () {
|
|
3923
|
-
const policies = yield* loadAllPolicies();
|
|
3924
|
-
return resolveToolPolicy(toolId, policies, scopeRank);
|
|
3925
|
-
});
|
|
3926
|
-
const enforceApproval = (annotations, toolId, args, policy, handler) => Effect10.gen(function* () {
|
|
3927
|
-
if (policy?.action === "approve") return;
|
|
3928
|
-
const policyForcesApproval = policy?.action === "require_approval";
|
|
3929
|
-
if (!policyForcesApproval && !annotations?.requiresApproval) return;
|
|
3930
|
-
const tid = ToolId.make(toolId);
|
|
3931
|
-
const message = annotations?.approvalDescription ? annotations.approvalDescription : policyForcesApproval && policy ? `Approve ${toolId}? (matched policy: ${policy.pattern})` : `Approve ${toolId}?`;
|
|
3932
|
-
const request = new FormElicitation({
|
|
3933
|
-
message,
|
|
3934
|
-
requestedSchema: {}
|
|
3935
|
-
});
|
|
3936
|
-
const response = yield* handler({ toolId: tid, args, request });
|
|
3937
|
-
if (response.action !== "accept") {
|
|
3938
|
-
return yield* new ElicitationDeclinedError({
|
|
3939
|
-
toolId: tid,
|
|
3940
|
-
action: response.action
|
|
3941
|
-
});
|
|
3942
|
-
}
|
|
3943
|
-
});
|
|
3944
|
-
const invokeTool = (toolId, args, options) => {
|
|
3945
|
-
const handler = pickHandler(options);
|
|
3946
|
-
return Effect10.gen(function* () {
|
|
3947
|
-
const wrapInvocationError = (effect) => effect.pipe(
|
|
3948
|
-
Effect10.mapError(
|
|
3949
|
-
(cause) => new ToolInvocationError({
|
|
3950
|
-
toolId: ToolId.make(toolId),
|
|
3951
|
-
message: cause instanceof Error ? cause.message : String(cause),
|
|
3952
|
-
cause
|
|
3953
|
-
})
|
|
3954
|
-
)
|
|
3955
|
-
);
|
|
3956
|
-
const policy = yield* resolveToolPolicyForId(toolId).pipe(
|
|
3957
|
-
Effect10.withSpan("executor.tool.resolve_policy")
|
|
3958
|
-
);
|
|
3959
|
-
if (policy?.action === "block") {
|
|
3960
|
-
return yield* new ToolBlockedError({
|
|
3961
|
-
toolId: ToolId.make(toolId),
|
|
3962
|
-
pattern: policy.pattern
|
|
3963
|
-
});
|
|
3964
|
-
}
|
|
3965
|
-
const staticEntry = staticTools.get(toolId);
|
|
3966
|
-
if (staticEntry) {
|
|
3967
|
-
yield* Effect10.annotateCurrentSpan({
|
|
3968
|
-
"executor.tool.dispatch_path": "static",
|
|
3969
|
-
"executor.source_id": staticEntry.source.id,
|
|
3970
|
-
"executor.source_kind": staticEntry.source.kind,
|
|
3971
|
-
"executor.plugin_id": staticEntry.pluginId
|
|
3972
|
-
});
|
|
3973
|
-
yield* enforceApproval(
|
|
3974
|
-
staticEntry.tool.annotations,
|
|
3975
|
-
toolId,
|
|
3976
|
-
args,
|
|
3977
|
-
policy,
|
|
3978
|
-
handler
|
|
3979
|
-
).pipe(Effect10.withSpan("executor.tool.enforce_approval"));
|
|
3980
|
-
return yield* wrapInvocationError(
|
|
3981
|
-
staticEntry.tool.handler({
|
|
3982
|
-
ctx: staticEntry.ctx,
|
|
3983
|
-
args,
|
|
3984
|
-
elicit: buildElicit(toolId, args, handler)
|
|
3985
|
-
})
|
|
3986
|
-
).pipe(Effect10.withSpan("executor.tool.handler"));
|
|
3987
|
-
}
|
|
3988
|
-
const toolRows = yield* core.findMany({
|
|
3989
|
-
model: "tool",
|
|
3990
|
-
where: [{ field: "id", value: toolId }]
|
|
3991
|
-
}).pipe(Effect10.withSpan("executor.tool.resolve"));
|
|
3992
|
-
const row = findInnermost(toolRows);
|
|
3993
|
-
if (!row) {
|
|
3994
|
-
return yield* new ToolNotFoundError({
|
|
3995
|
-
toolId: ToolId.make(toolId)
|
|
3996
|
-
});
|
|
3997
|
-
}
|
|
3998
|
-
yield* Effect10.annotateCurrentSpan({
|
|
3999
|
-
"executor.tool.dispatch_path": "dynamic",
|
|
4000
|
-
"executor.source_id": row.source_id,
|
|
4001
|
-
"executor.plugin_id": row.plugin_id
|
|
4002
|
-
});
|
|
4003
|
-
const runtime = runtimes.get(row.plugin_id);
|
|
4004
|
-
if (!runtime) {
|
|
4005
|
-
return yield* new PluginNotLoadedError({
|
|
4006
|
-
pluginId: row.plugin_id,
|
|
4007
|
-
toolId: ToolId.make(toolId)
|
|
4008
|
-
});
|
|
4009
|
-
}
|
|
4010
|
-
if (!runtime.plugin.invokeTool) {
|
|
4011
|
-
return yield* new NoHandlerError({
|
|
4012
|
-
toolId: ToolId.make(toolId),
|
|
4013
|
-
pluginId: row.plugin_id
|
|
4014
|
-
});
|
|
4015
|
-
}
|
|
4016
|
-
let annotations;
|
|
4017
|
-
if (policy?.action !== "approve" && runtime.plugin.resolveAnnotations) {
|
|
4018
|
-
const map = yield* runtime.plugin.resolveAnnotations({
|
|
4019
|
-
ctx: runtime.ctx,
|
|
4020
|
-
sourceId: row.source_id,
|
|
4021
|
-
toolRows: [row]
|
|
4022
|
-
}).pipe(Effect10.withSpan("executor.tool.resolve_annotations"));
|
|
4023
|
-
annotations = map[toolId];
|
|
4024
|
-
}
|
|
4025
|
-
yield* enforceApproval(annotations, toolId, args, policy, handler).pipe(
|
|
4026
|
-
Effect10.withSpan("executor.tool.enforce_approval")
|
|
4027
|
-
);
|
|
4028
|
-
return yield* wrapInvocationError(
|
|
4029
|
-
runtime.plugin.invokeTool({
|
|
4030
|
-
ctx: runtime.ctx,
|
|
4031
|
-
toolRow: row,
|
|
4032
|
-
args,
|
|
4033
|
-
elicit: buildElicit(toolId, args, handler)
|
|
4034
|
-
})
|
|
4035
|
-
).pipe(Effect10.withSpan("executor.tool.handler"));
|
|
4036
|
-
}).pipe(
|
|
4037
|
-
Effect10.withSpan("executor.tool.invoke", {
|
|
4038
|
-
attributes: {
|
|
4039
|
-
"mcp.tool.name": toolId
|
|
4040
|
-
}
|
|
4041
|
-
})
|
|
4042
|
-
);
|
|
4043
|
-
};
|
|
4044
|
-
const removeSource = (sourceId) => Effect10.gen(function* () {
|
|
4045
|
-
if (staticSources.has(sourceId)) {
|
|
4046
|
-
return yield* new SourceRemovalNotAllowedError({ sourceId });
|
|
4047
|
-
}
|
|
4048
|
-
const sourceRows = yield* core.findMany({
|
|
4049
|
-
model: "source",
|
|
4050
|
-
where: [{ field: "id", value: sourceId }]
|
|
4051
|
-
});
|
|
4052
|
-
const sourceRow = findInnermost(sourceRows);
|
|
4053
|
-
if (!sourceRow) return;
|
|
4054
|
-
if (!sourceRow.can_remove) {
|
|
4055
|
-
return yield* new SourceRemovalNotAllowedError({ sourceId });
|
|
4056
|
-
}
|
|
4057
|
-
const runtime = runtimes.get(sourceRow.plugin_id);
|
|
4058
|
-
yield* adapter.transaction(
|
|
4059
|
-
() => Effect10.gen(function* () {
|
|
4060
|
-
if (runtime?.plugin.removeSource) {
|
|
4061
|
-
yield* runtime.plugin.removeSource({
|
|
4062
|
-
ctx: runtime.ctx,
|
|
4063
|
-
sourceId,
|
|
4064
|
-
scope: sourceRow.scope_id
|
|
4065
|
-
});
|
|
4066
|
-
}
|
|
4067
|
-
yield* deleteSourceById(
|
|
4068
|
-
core,
|
|
4069
|
-
sourceId,
|
|
4070
|
-
sourceRow.scope_id
|
|
4071
|
-
);
|
|
4072
|
-
})
|
|
4073
|
-
);
|
|
4074
|
-
});
|
|
4075
|
-
const refreshSource = (sourceId) => Effect10.gen(function* () {
|
|
4076
|
-
if (staticSources.has(sourceId)) return;
|
|
4077
|
-
const sourceRows = yield* core.findMany({
|
|
4078
|
-
model: "source",
|
|
4079
|
-
where: [{ field: "id", value: sourceId }]
|
|
4080
|
-
});
|
|
4081
|
-
const sourceRow = findInnermost(sourceRows);
|
|
4082
|
-
if (!sourceRow) return;
|
|
4083
|
-
const runtime = runtimes.get(sourceRow.plugin_id);
|
|
4084
|
-
if (runtime?.plugin.refreshSource) {
|
|
4085
|
-
yield* runtime.plugin.refreshSource({
|
|
4086
|
-
ctx: runtime.ctx,
|
|
4087
|
-
sourceId,
|
|
4088
|
-
scope: sourceRow.scope_id
|
|
4089
|
-
});
|
|
4090
|
-
}
|
|
4091
|
-
});
|
|
4092
|
-
const detectionConfidenceScore = (confidence) => {
|
|
4093
|
-
switch (confidence) {
|
|
4094
|
-
case "high":
|
|
4095
|
-
return 3;
|
|
4096
|
-
case "medium":
|
|
4097
|
-
return 2;
|
|
4098
|
-
case "low":
|
|
4099
|
-
return 1;
|
|
4100
|
-
}
|
|
4101
|
-
};
|
|
4102
|
-
const detectSource = (url) => Effect10.gen(function* () {
|
|
4103
|
-
const results = [];
|
|
4104
|
-
for (const runtime of runtimes.values()) {
|
|
4105
|
-
if (!runtime.plugin.detect) continue;
|
|
4106
|
-
const result = yield* runtime.plugin.detect({ ctx: runtime.ctx, url }).pipe(Effect10.catch(() => Effect10.succeed(null)));
|
|
4107
|
-
if (result) results.push(result);
|
|
4108
|
-
}
|
|
4109
|
-
return results.sort(
|
|
4110
|
-
(a, b) => detectionConfidenceScore(b.confidence) - detectionConfidenceScore(a.confidence)
|
|
4111
|
-
);
|
|
4112
|
-
});
|
|
4113
|
-
const sourceDefinitions = (sourceId) => loadDefinitionsForSource(sourceId);
|
|
4114
|
-
const secretsStatus = (id) => Effect10.gen(function* () {
|
|
4115
|
-
const rows = yield* secretRowsForId(id);
|
|
4116
|
-
if (rows.some((row) => row.owned_by_connection_id)) return "missing";
|
|
4117
|
-
for (const row of rows) {
|
|
4118
|
-
if (yield* secretRouteHasBackingValue(row)) return "resolved";
|
|
4119
|
-
}
|
|
4120
|
-
for (const provider of secretProviders.values()) {
|
|
4121
|
-
if (!provider.list) continue;
|
|
4122
|
-
const entries = yield* provider.list().pipe(Effect10.catch(() => Effect10.succeed([])));
|
|
4123
|
-
if (entries.some((e) => e.id === id)) return "resolved";
|
|
4124
|
-
}
|
|
4125
|
-
return "missing";
|
|
4126
|
-
});
|
|
4127
|
-
const policiesList = () => Effect10.gen(function* () {
|
|
4128
|
-
const rows = yield* loadAllPolicies();
|
|
4129
|
-
const sorted = [...rows].sort((a, b) => {
|
|
4130
|
-
const sa = scopeRank(a);
|
|
4131
|
-
const sb = scopeRank(b);
|
|
4132
|
-
if (sa !== sb) return sa - sb;
|
|
4133
|
-
return comparePolicyRow(a, b);
|
|
4134
|
-
});
|
|
4135
|
-
return sorted.map((row) => rowToToolPolicy(row));
|
|
4136
|
-
}).pipe(Effect10.withSpan("executor.policies.list"));
|
|
4137
|
-
const policiesCreate = (input) => Effect10.gen(function* () {
|
|
4138
|
-
if (!isValidPattern(input.pattern)) {
|
|
4139
|
-
return yield* new StorageError3({
|
|
4140
|
-
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.`,
|
|
4141
|
-
cause: void 0
|
|
4142
|
-
});
|
|
4143
|
-
}
|
|
4144
|
-
if (!isToolPolicyAction(input.action)) {
|
|
4145
|
-
return yield* new StorageError3({
|
|
4146
|
-
message: `Invalid tool policy action "${String(input.action)}". Expected "approve" | "require_approval" | "block".`,
|
|
4147
|
-
cause: void 0
|
|
4148
|
-
});
|
|
4149
|
-
}
|
|
4150
|
-
let position = input.position;
|
|
4151
|
-
if (position === void 0) {
|
|
4152
|
-
const existing = yield* core.findMany({
|
|
4153
|
-
model: "tool_policy",
|
|
4154
|
-
where: [{ field: "scope_id", value: input.scope }]
|
|
4155
|
-
});
|
|
4156
|
-
let min = null;
|
|
4157
|
-
for (const row of existing) {
|
|
4158
|
-
const p = row.position;
|
|
4159
|
-
if (min === null || p < min) min = p;
|
|
4160
|
-
}
|
|
4161
|
-
position = generateKeyBetween(null, min);
|
|
4162
|
-
}
|
|
4163
|
-
const id = `pol_${Math.random().toString(36).slice(2, 10)}_${Date.now().toString(36)}`;
|
|
4164
|
-
const now = /* @__PURE__ */ new Date();
|
|
4165
|
-
yield* core.create({
|
|
4166
|
-
model: "tool_policy",
|
|
4167
|
-
data: {
|
|
4168
|
-
id,
|
|
4169
|
-
scope_id: input.scope,
|
|
4170
|
-
pattern: input.pattern,
|
|
4171
|
-
action: input.action,
|
|
4172
|
-
position,
|
|
4173
|
-
created_at: now,
|
|
4174
|
-
updated_at: now
|
|
4175
|
-
},
|
|
4176
|
-
forceAllowId: true
|
|
4177
|
-
});
|
|
4178
|
-
return rowToToolPolicy({
|
|
4179
|
-
id,
|
|
4180
|
-
scope_id: input.scope,
|
|
4181
|
-
pattern: input.pattern,
|
|
4182
|
-
action: input.action,
|
|
4183
|
-
position,
|
|
4184
|
-
created_at: now,
|
|
4185
|
-
updated_at: now
|
|
4186
|
-
});
|
|
4187
|
-
}).pipe(Effect10.withSpan("executor.policies.create"));
|
|
4188
|
-
const policiesUpdate = (input) => Effect10.gen(function* () {
|
|
4189
|
-
if (input.pattern !== void 0 && !isValidPattern(input.pattern)) {
|
|
4190
|
-
return yield* new StorageError3({
|
|
4191
|
-
message: `Invalid tool policy pattern "${input.pattern}".`,
|
|
4192
|
-
cause: void 0
|
|
4193
|
-
});
|
|
4194
|
-
}
|
|
4195
|
-
if (input.action !== void 0 && !isToolPolicyAction(input.action)) {
|
|
4196
|
-
return yield* new StorageError3({
|
|
4197
|
-
message: `Invalid tool policy action "${String(input.action)}".`,
|
|
4198
|
-
cause: void 0
|
|
4199
|
-
});
|
|
4200
|
-
}
|
|
4201
|
-
const rows = yield* core.findMany({
|
|
4202
|
-
model: "tool_policy",
|
|
4203
|
-
where: [{ field: "id", value: input.id }]
|
|
4204
|
-
});
|
|
4205
|
-
const row = findInnermost(rows);
|
|
4206
|
-
if (!row) {
|
|
4207
|
-
return yield* new StorageError3({
|
|
4208
|
-
message: `Tool policy "${input.id}" not found.`,
|
|
4209
|
-
cause: void 0
|
|
4210
|
-
});
|
|
4211
|
-
}
|
|
4212
|
-
const updated = {
|
|
4213
|
-
...row,
|
|
4214
|
-
pattern: input.pattern ?? row.pattern,
|
|
4215
|
-
action: input.action ?? row.action,
|
|
4216
|
-
position: input.position ?? row.position,
|
|
4217
|
-
updated_at: /* @__PURE__ */ new Date()
|
|
4218
|
-
};
|
|
4219
|
-
yield* core.update({
|
|
4220
|
-
model: "tool_policy",
|
|
4221
|
-
where: [
|
|
4222
|
-
{ field: "id", value: input.id },
|
|
4223
|
-
{ field: "scope_id", value: row.scope_id }
|
|
4224
|
-
],
|
|
4225
|
-
update: {
|
|
4226
|
-
pattern: updated.pattern,
|
|
4227
|
-
action: updated.action,
|
|
4228
|
-
position: updated.position,
|
|
4229
|
-
updated_at: updated.updated_at
|
|
4230
|
-
}
|
|
4231
|
-
});
|
|
4232
|
-
return rowToToolPolicy(updated);
|
|
4233
|
-
}).pipe(Effect10.withSpan("executor.policies.update"));
|
|
4234
|
-
const policiesRemove = (id) => core.deleteMany({
|
|
4235
|
-
model: "tool_policy",
|
|
4236
|
-
where: [{ field: "id", value: id }]
|
|
4237
|
-
}).pipe(Effect10.asVoid, Effect10.withSpan("executor.policies.remove"));
|
|
4238
|
-
const policiesResolve = (toolId) => resolveToolPolicyForId(toolId).pipe(
|
|
4239
|
-
Effect10.withSpan("executor.policies.resolve")
|
|
4240
|
-
);
|
|
4241
|
-
const close = () => Effect10.gen(function* () {
|
|
4242
|
-
for (const runtime of runtimes.values()) {
|
|
4243
|
-
if (runtime.plugin.close) {
|
|
4244
|
-
yield* runtime.plugin.close();
|
|
4245
|
-
}
|
|
4246
|
-
}
|
|
4247
|
-
});
|
|
4248
|
-
const base = {
|
|
4249
|
-
scopes,
|
|
4250
|
-
tools: {
|
|
4251
|
-
list: listTools,
|
|
4252
|
-
schema: toolSchema,
|
|
4253
|
-
definitions: toolsDefinitions,
|
|
4254
|
-
invoke: invokeTool
|
|
4255
|
-
},
|
|
4256
|
-
sources: {
|
|
4257
|
-
list: listSources,
|
|
4258
|
-
remove: removeSource,
|
|
4259
|
-
refresh: refreshSource,
|
|
4260
|
-
detect: detectSource,
|
|
4261
|
-
definitions: sourceDefinitions
|
|
4262
|
-
},
|
|
4263
|
-
secrets: {
|
|
4264
|
-
get: secretsGet,
|
|
4265
|
-
status: secretsStatus,
|
|
4266
|
-
set: secretsSet,
|
|
4267
|
-
remove: secretsRemove,
|
|
4268
|
-
list: secretsList,
|
|
4269
|
-
providers: () => Effect10.sync(
|
|
4270
|
-
() => Array.from(secretProviders.keys())
|
|
4271
|
-
)
|
|
4272
|
-
},
|
|
4273
|
-
connections: {
|
|
4274
|
-
get: connectionsGet,
|
|
4275
|
-
list: connectionsList,
|
|
4276
|
-
create: connectionsCreate,
|
|
4277
|
-
updateTokens: connectionsUpdateTokens,
|
|
4278
|
-
setIdentityLabel: connectionsSetIdentityLabel,
|
|
4279
|
-
accessToken: connectionsAccessToken,
|
|
4280
|
-
remove: connectionsRemove,
|
|
4281
|
-
providers: () => Effect10.sync(
|
|
4282
|
-
() => Array.from(connectionProviders.keys())
|
|
4283
|
-
)
|
|
4284
|
-
},
|
|
4285
|
-
oauth: oauthBundle.service,
|
|
4286
|
-
policies: {
|
|
4287
|
-
list: policiesList,
|
|
4288
|
-
create: policiesCreate,
|
|
4289
|
-
update: policiesUpdate,
|
|
4290
|
-
remove: policiesRemove,
|
|
4291
|
-
resolve: policiesResolve
|
|
4292
|
-
},
|
|
4293
|
-
close
|
|
4294
|
-
};
|
|
4295
|
-
const toExecutor = (value) => value;
|
|
4296
|
-
return toExecutor(Object.assign(base, extensions));
|
|
4297
|
-
});
|
|
4298
|
-
|
|
4299
|
-
// src/scope.ts
|
|
4300
|
-
import { Schema as Schema12 } from "effect";
|
|
4301
|
-
var Scope = class extends Schema12.Class("Scope")({
|
|
4302
|
-
id: ScopeId,
|
|
4303
|
-
name: Schema12.String,
|
|
4304
|
-
createdAt: Schema12.Date
|
|
4305
|
-
}) {
|
|
4306
|
-
};
|
|
4307
|
-
|
|
4308
|
-
// src/secret-backed-value.ts
|
|
4309
|
-
import { Effect as Effect11, Schema as Schema13 } from "effect";
|
|
4310
|
-
var SecretBackedValue = Schema13.Union([
|
|
4311
|
-
Schema13.String,
|
|
4312
|
-
Schema13.Struct({
|
|
4313
|
-
secretId: Schema13.String,
|
|
4314
|
-
prefix: Schema13.optional(Schema13.String)
|
|
4315
|
-
})
|
|
4316
|
-
]);
|
|
4317
|
-
var SecretBackedMap = Schema13.Record(Schema13.String, SecretBackedValue);
|
|
4318
|
-
var isSecretBackedRef = (value) => typeof value !== "string";
|
|
4319
|
-
var resolveSecretBackedMap = ({
|
|
4320
|
-
values,
|
|
4321
|
-
getSecret,
|
|
4322
|
-
onMissing,
|
|
4323
|
-
onError,
|
|
4324
|
-
missing = "fail"
|
|
4325
|
-
}) => {
|
|
4326
|
-
const entries = Object.entries(values ?? {});
|
|
4327
|
-
if (entries.length === 0) return Effect11.succeed(void 0);
|
|
4328
|
-
return Effect11.gen(function* () {
|
|
4329
|
-
const resolved = {};
|
|
4330
|
-
for (const [name, value] of entries) {
|
|
4331
|
-
if (typeof value === "string") {
|
|
4332
|
-
resolved[name] = value;
|
|
4333
|
-
continue;
|
|
4334
|
-
}
|
|
4335
|
-
const secret = yield* getSecret(value.secretId).pipe(
|
|
4336
|
-
Effect11.mapError((error) => onError?.(error, name, value) ?? error)
|
|
4337
|
-
);
|
|
4338
|
-
if (secret === null) {
|
|
4339
|
-
if (missing === "drop") continue;
|
|
4340
|
-
return yield* Effect11.fail(onMissing(name, value));
|
|
4341
|
-
}
|
|
4342
|
-
resolved[name] = value.prefix ? `${value.prefix}${secret}` : secret;
|
|
4343
|
-
}
|
|
4344
|
-
return Object.keys(resolved).length > 0 ? resolved : void 0;
|
|
4345
|
-
});
|
|
4346
|
-
};
|
|
4347
|
-
|
|
4348
|
-
// src/config.ts
|
|
4349
|
-
var defineExecutorConfig = (config) => config;
|
|
4350
|
-
|
|
4351
|
-
export {
|
|
4352
|
-
pluginBlobStore,
|
|
4353
|
-
makeInMemoryBlobStore,
|
|
4354
|
-
ScopeId,
|
|
4355
|
-
ToolId,
|
|
4356
|
-
SecretId,
|
|
4357
|
-
PolicyId,
|
|
4358
|
-
ConnectionId,
|
|
4359
|
-
ConnectionProviderState,
|
|
4360
|
-
ConnectionRef,
|
|
4361
|
-
TokenMaterial,
|
|
4362
|
-
CreateConnectionInput,
|
|
4363
|
-
ConnectionRefreshError,
|
|
4364
|
-
UpdateConnectionTokensInput,
|
|
4365
|
-
coreSchema,
|
|
4366
|
-
TOOL_POLICY_ACTIONS,
|
|
4367
|
-
isToolPolicyAction,
|
|
4368
|
-
FormElicitation,
|
|
4369
|
-
UrlElicitation,
|
|
4370
|
-
ElicitationAction,
|
|
4371
|
-
ElicitationResponse,
|
|
4372
|
-
ElicitationDeclinedError,
|
|
4373
|
-
ToolNotFoundError,
|
|
4374
|
-
ToolInvocationError,
|
|
4375
|
-
PluginNotLoadedError,
|
|
4376
|
-
NoHandlerError,
|
|
4377
|
-
ToolBlockedError,
|
|
4378
|
-
SourceNotFoundError,
|
|
4379
|
-
SourceRemovalNotAllowedError,
|
|
4380
|
-
SecretNotFoundError,
|
|
4381
|
-
SecretResolutionError,
|
|
4382
|
-
SecretOwnedByConnectionError,
|
|
4383
|
-
ConnectionNotFoundError,
|
|
4384
|
-
ConnectionProviderNotRegisteredError,
|
|
4385
|
-
ConnectionRefreshNotSupportedError,
|
|
4386
|
-
ConnectionReauthRequiredError,
|
|
4387
|
-
SecretRef,
|
|
4388
|
-
SetSecretInput,
|
|
4389
|
-
OAuthDynamicDcrStrategy,
|
|
4390
|
-
OAuthAuthorizationCodeStrategy,
|
|
4391
|
-
OAuthClientCredentialsStrategy,
|
|
4392
|
-
OAuthStrategy,
|
|
4393
|
-
OAuthProviderState,
|
|
4394
|
-
OAUTH2_PROVIDER_KEY,
|
|
4395
|
-
OAuthProbeError,
|
|
4396
|
-
OAuthStartError,
|
|
4397
|
-
OAuthCompleteError,
|
|
4398
|
-
OAuthSessionNotFoundError,
|
|
4399
|
-
OAUTH2_SESSION_TTL_MS,
|
|
4400
|
-
OAuth2Error,
|
|
4401
|
-
OAUTH2_REFRESH_SKEW_MS,
|
|
4402
|
-
OAUTH2_DEFAULT_TIMEOUT_MS,
|
|
4403
|
-
createPkceCodeVerifier,
|
|
4404
|
-
createPkceCodeChallenge,
|
|
4405
|
-
buildAuthorizationUrl,
|
|
4406
|
-
exchangeAuthorizationCode,
|
|
4407
|
-
exchangeClientCredentials,
|
|
4408
|
-
refreshAccessToken,
|
|
4409
|
-
shouldRefreshToken,
|
|
4410
|
-
OAuthDiscoveryError,
|
|
4411
|
-
OAuthProtectedResourceMetadataSchema,
|
|
4412
|
-
OAuthAuthorizationServerMetadataSchema,
|
|
4413
|
-
OAuthClientInformationSchema,
|
|
4414
|
-
discoverProtectedResourceMetadata,
|
|
4415
|
-
discoverAuthorizationServerMetadata,
|
|
4416
|
-
registerDynamicClient,
|
|
4417
|
-
beginDynamicAuthorization,
|
|
4418
|
-
makeOAuth2Service,
|
|
4419
|
-
matchPattern,
|
|
4420
|
-
isValidPattern,
|
|
4421
|
-
resolveToolPolicy,
|
|
4422
|
-
resolveEffectivePolicy,
|
|
4423
|
-
effectivePolicyFromSorted,
|
|
4424
|
-
rowToToolPolicy,
|
|
4425
|
-
ToolPolicyActionSchema,
|
|
4426
|
-
ToolSchema,
|
|
4427
|
-
SourceDetectionResult,
|
|
4428
|
-
schemaToTypeScriptPreview,
|
|
4429
|
-
schemaToTypeScriptPreviewWithDefs,
|
|
4430
|
-
buildToolTypeScriptPreview,
|
|
4431
|
-
collectSchemas,
|
|
4432
|
-
createExecutor,
|
|
4433
|
-
Scope,
|
|
4434
|
-
SecretBackedValue,
|
|
4435
|
-
SecretBackedMap,
|
|
4436
|
-
isSecretBackedRef,
|
|
4437
|
-
resolveSecretBackedMap,
|
|
4438
|
-
defineExecutorConfig
|
|
4439
|
-
};
|
|
4440
|
-
//# sourceMappingURL=chunk-2WV7VSNL.js.map
|