@executor-js/plugin-openapi 0.0.1-beta.6 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +24 -23
  2. package/dist/api/group.d.ts +94 -37
  3. package/dist/api/handlers.d.ts +2 -2
  4. package/dist/chunk-ZZ7TQ4JC.js +2602 -0
  5. package/dist/chunk-ZZ7TQ4JC.js.map +1 -0
  6. package/dist/core.js +46 -50
  7. package/dist/core.js.map +1 -1
  8. package/dist/index.js +2 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/react/AddOpenApiSource.d.ts +13 -0
  11. package/dist/react/EditOpenApiSource.d.ts +2 -2
  12. package/dist/react/OpenApiSourceSummary.d.ts +3 -1
  13. package/dist/react/atoms.d.ts +129 -0
  14. package/dist/react/client.d.ts +421 -3
  15. package/dist/react/source-plugin.d.ts +1 -1
  16. package/dist/sdk/client-credentials-oauth.test.d.ts +1 -0
  17. package/dist/sdk/credential-status.d.ts +23 -0
  18. package/dist/sdk/credential-status.test.d.ts +1 -0
  19. package/dist/sdk/errors.d.ts +11 -10
  20. package/dist/sdk/form-urlencoded-body.test.d.ts +1 -0
  21. package/dist/sdk/index.d.ts +8 -10
  22. package/dist/sdk/invoke.d.ts +8 -17
  23. package/dist/sdk/multi-scope-bearer.test.d.ts +1 -0
  24. package/dist/sdk/multi-scope-oauth.test.d.ts +1 -0
  25. package/dist/sdk/non-json-body.test.d.ts +1 -0
  26. package/dist/sdk/oauth-refresh.test.d.ts +1 -0
  27. package/dist/sdk/openapi-utils.d.ts +35 -4
  28. package/dist/sdk/parse.d.ts +28 -4
  29. package/dist/sdk/plugin.d.ts +169 -22
  30. package/dist/sdk/preview-oauth2.test.d.ts +1 -0
  31. package/dist/sdk/preview.d.ts +89 -125
  32. package/dist/sdk/store.d.ts +201 -0
  33. package/dist/sdk/types.d.ts +234 -266
  34. package/package.json +11 -22
  35. package/dist/chunk-V3D5A6HA.js +0 -1224
  36. package/dist/chunk-V3D5A6HA.js.map +0 -1
  37. package/dist/promise.d.ts +0 -6
  38. package/dist/sdk/config-file-store.d.ts +0 -10
  39. package/dist/sdk/kv-operation-store.d.ts +0 -4
  40. package/dist/sdk/operation-store.d.ts +0 -35
  41. package/dist/sdk/stored-source.d.ts +0 -46
@@ -0,0 +1,2602 @@
1
+ // src/sdk/errors.ts
2
+ import { Data, Schema } from "effect";
3
+ var OpenApiParseError = class extends Schema.TaggedErrorClass()(
4
+ "OpenApiParseError",
5
+ {
6
+ message: Schema.String
7
+ },
8
+ { httpApiStatus: 400 }
9
+ ) {
10
+ };
11
+ var OpenApiExtractionError = class extends Schema.TaggedErrorClass()(
12
+ "OpenApiExtractionError",
13
+ {
14
+ message: Schema.String
15
+ },
16
+ { httpApiStatus: 400 }
17
+ ) {
18
+ };
19
+ var OpenApiInvocationError = class extends Data.TaggedError("OpenApiInvocationError") {
20
+ };
21
+ var OpenApiOAuthError = class extends Schema.TaggedErrorClass()(
22
+ "OpenApiOAuthError",
23
+ {
24
+ message: Schema.String
25
+ },
26
+ { httpApiStatus: 400 }
27
+ ) {
28
+ };
29
+
30
+ // src/sdk/parse.ts
31
+ import { Duration, Effect } from "effect";
32
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
33
+ import YAML from "yaml";
34
+ var OpenApiExtractionErrorFromParse = class extends OpenApiExtractionError {
35
+ };
36
+ var fetchSpecText = Effect.fn("OpenApi.fetchSpecText")(function* (url, credentials) {
37
+ const client = yield* HttpClient.HttpClient;
38
+ const requestUrl = new URL(url);
39
+ for (const [name, value] of Object.entries(credentials?.queryParams ?? {})) {
40
+ requestUrl.searchParams.set(name, value);
41
+ }
42
+ let request = HttpClientRequest.get(requestUrl.toString()).pipe(
43
+ HttpClientRequest.setHeader("Accept", "application/json, application/yaml, text/yaml, */*")
44
+ );
45
+ for (const [name, value] of Object.entries(credentials?.headers ?? {})) {
46
+ request = HttpClientRequest.setHeader(request, name, value);
47
+ }
48
+ const response = yield* client.execute(request).pipe(
49
+ Effect.timeout(Duration.seconds(20)),
50
+ Effect.mapError(
51
+ (cause) => new OpenApiParseError({
52
+ message: `Failed to fetch OpenAPI document: ${cause instanceof Error ? cause.message : String(cause)}`
53
+ })
54
+ )
55
+ );
56
+ if (response.status < 200 || response.status >= 300) {
57
+ return yield* new OpenApiParseError({
58
+ message: `Failed to fetch OpenAPI document: HTTP ${response.status}`
59
+ });
60
+ }
61
+ return yield* response.text.pipe(
62
+ Effect.mapError(
63
+ (cause) => new OpenApiParseError({
64
+ message: `Failed to read OpenAPI document body: ${cause instanceof Error ? cause.message : String(cause)}`
65
+ })
66
+ )
67
+ );
68
+ });
69
+ var resolveSpecText = (input, credentials) => input.startsWith("http://") || input.startsWith("https://") ? fetchSpecText(input, credentials) : Effect.succeed(input);
70
+ var parse = Effect.fn("OpenApi.parse")(function* (text) {
71
+ const api = yield* Effect.try({
72
+ try: () => parseTextToObject(text),
73
+ catch: (error) => new OpenApiParseError({
74
+ message: `Failed to parse OpenAPI document: ${error instanceof Error ? error.message : String(error)}`
75
+ })
76
+ });
77
+ if (!isOpenApi3(api)) {
78
+ return yield* new OpenApiExtractionErrorFromParse({
79
+ message: "Only OpenAPI 3.x documents are supported. Swagger 2.x documents should be converted first."
80
+ });
81
+ }
82
+ return api;
83
+ });
84
+ var isOpenApi3 = (doc) => "openapi" in doc && typeof doc.openapi === "string" && doc.openapi.startsWith("3.");
85
+ var parseTextToObject = (text) => {
86
+ const trimmed = text.trim();
87
+ if (trimmed.length === 0) throw new Error("OpenAPI document is empty");
88
+ let parsed;
89
+ try {
90
+ parsed = JSON.parse(trimmed);
91
+ } catch {
92
+ parsed = YAML.parse(trimmed);
93
+ }
94
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
95
+ throw new Error("OpenAPI document must parse to an object");
96
+ }
97
+ return parsed;
98
+ };
99
+
100
+ // src/sdk/openapi-utils.ts
101
+ import { Option } from "effect";
102
+ var DocResolver = class {
103
+ constructor(doc) {
104
+ this.doc = doc;
105
+ }
106
+ doc;
107
+ /** Resolve a value that might be a $ref, returning the resolved object */
108
+ resolve(value) {
109
+ if (isRef(value)) {
110
+ const resolved = this.resolvePointer(value.$ref);
111
+ return resolved;
112
+ }
113
+ return value;
114
+ }
115
+ resolvePointer(ref) {
116
+ if (!ref.startsWith("#/")) return null;
117
+ const segments = ref.slice(2).split("/");
118
+ let current = this.doc;
119
+ for (const segment of segments) {
120
+ if (typeof current !== "object" || current === null) return null;
121
+ current = current[segment];
122
+ }
123
+ return current;
124
+ }
125
+ };
126
+ var isRef = (value) => typeof value === "object" && value !== null && "$ref" in value;
127
+ var substituteUrlVariables = (url, values) => {
128
+ let out = url;
129
+ for (const [name, value] of Object.entries(values)) {
130
+ out = out.replaceAll(`{${name}}`, value);
131
+ }
132
+ return out;
133
+ };
134
+ var resolveBaseUrl = (servers) => {
135
+ const server = servers[0];
136
+ if (!server) return "";
137
+ if (!Option.isSome(server.variables)) return server.url;
138
+ const values = {};
139
+ for (const [name, v] of Object.entries(server.variables.value)) {
140
+ values[name] = typeof v === "string" ? v : v.default;
141
+ }
142
+ return substituteUrlVariables(server.url, values);
143
+ };
144
+ var declaredContents = (content) => {
145
+ if (!content) return [];
146
+ return Object.entries(content).map(([mediaType, media]) => ({ mediaType, media }));
147
+ };
148
+ var preferredContent = (content) => {
149
+ const first = declaredContents(content)[0];
150
+ return first ? first : void 0;
151
+ };
152
+ var preferredResponseContent = (content) => {
153
+ if (!content) return void 0;
154
+ const entries = Object.entries(content);
155
+ const pick = entries.find(([mt]) => mt === "application/json") ?? entries.find(([mt]) => mt.toLowerCase().includes("+json")) ?? entries.find(([mt]) => mt.toLowerCase().includes("json")) ?? entries[0];
156
+ return pick ? { mediaType: pick[0], media: pick[1] } : void 0;
157
+ };
158
+
159
+ // src/sdk/types.ts
160
+ import { Schema as Schema2 } from "effect";
161
+ import { ConnectionId, ScopeId, SecretBackedValue, SecretId } from "@executor-js/sdk/core";
162
+ var OperationId = Schema2.String.pipe(Schema2.brand("OperationId"));
163
+ var HttpMethod = Schema2.Literals([
164
+ "get",
165
+ "put",
166
+ "post",
167
+ "delete",
168
+ "patch",
169
+ "head",
170
+ "options",
171
+ "trace"
172
+ ]);
173
+ var ParameterLocation = Schema2.Literals(["path", "query", "header", "cookie"]);
174
+ var OperationParameter = class extends Schema2.Class("OperationParameter")({
175
+ name: Schema2.String,
176
+ location: ParameterLocation,
177
+ required: Schema2.Boolean,
178
+ schema: Schema2.OptionFromOptional(Schema2.Unknown),
179
+ style: Schema2.OptionFromOptional(Schema2.String),
180
+ explode: Schema2.OptionFromOptional(Schema2.Boolean),
181
+ allowReserved: Schema2.OptionFromOptional(Schema2.Boolean),
182
+ description: Schema2.OptionFromOptional(Schema2.String)
183
+ }) {
184
+ };
185
+ var EncodingObject = class extends Schema2.Class("EncodingObject")({
186
+ contentType: Schema2.OptionFromOptional(Schema2.String),
187
+ style: Schema2.OptionFromOptional(Schema2.String),
188
+ explode: Schema2.OptionFromOptional(Schema2.Boolean),
189
+ allowReserved: Schema2.OptionFromOptional(Schema2.Boolean)
190
+ }) {
191
+ };
192
+ var MediaBinding = class extends Schema2.Class("MediaBinding")({
193
+ contentType: Schema2.String,
194
+ schema: Schema2.OptionFromOptional(Schema2.Unknown),
195
+ encoding: Schema2.OptionFromOptional(Schema2.Record(Schema2.String, EncodingObject))
196
+ }) {
197
+ };
198
+ var OperationRequestBody = class extends Schema2.Class(
199
+ "OperationRequestBody"
200
+ )({
201
+ required: Schema2.Boolean,
202
+ /** Default media type — first declared in spec order (not JSON-first).
203
+ * Used when the caller does not override via the tool's `contentType` arg. */
204
+ contentType: Schema2.String,
205
+ /** Schema of the default media type. Kept for backward compat with stored
206
+ * bindings from before `contents` was added. */
207
+ schema: Schema2.OptionFromOptional(Schema2.Unknown),
208
+ /** All declared media types in spec order. Populated by `extract.ts`
209
+ * going forward; older persisted bindings may have this unset and will
210
+ * fall back to `{contentType, schema}`. */
211
+ contents: Schema2.OptionFromOptional(Schema2.Array(MediaBinding))
212
+ }) {
213
+ };
214
+ var ExtractedOperation = class extends Schema2.Class("ExtractedOperation")({
215
+ operationId: OperationId,
216
+ method: HttpMethod,
217
+ pathTemplate: Schema2.String,
218
+ summary: Schema2.OptionFromOptional(Schema2.String),
219
+ description: Schema2.OptionFromOptional(Schema2.String),
220
+ tags: Schema2.Array(Schema2.String),
221
+ parameters: Schema2.Array(OperationParameter),
222
+ requestBody: Schema2.OptionFromOptional(OperationRequestBody),
223
+ inputSchema: Schema2.OptionFromOptional(Schema2.Unknown),
224
+ outputSchema: Schema2.OptionFromOptional(Schema2.Unknown),
225
+ deprecated: Schema2.Boolean
226
+ }) {
227
+ };
228
+ var ServerVariable = class extends Schema2.Class("ServerVariable")({
229
+ default: Schema2.String,
230
+ enum: Schema2.OptionFromOptional(Schema2.Array(Schema2.String)),
231
+ description: Schema2.OptionFromOptional(Schema2.String)
232
+ }) {
233
+ };
234
+ var ServerInfo = class extends Schema2.Class("ServerInfo")({
235
+ url: Schema2.String,
236
+ description: Schema2.OptionFromOptional(Schema2.String),
237
+ variables: Schema2.OptionFromOptional(Schema2.Record(Schema2.String, ServerVariable))
238
+ }) {
239
+ };
240
+ var ExtractionResult = class extends Schema2.Class("ExtractionResult")({
241
+ title: Schema2.OptionFromOptional(Schema2.String),
242
+ version: Schema2.OptionFromOptional(Schema2.String),
243
+ servers: Schema2.Array(ServerInfo),
244
+ operations: Schema2.Array(ExtractedOperation)
245
+ }) {
246
+ };
247
+ var OperationBinding = class extends Schema2.Class("OperationBinding")({
248
+ method: HttpMethod,
249
+ pathTemplate: Schema2.String,
250
+ parameters: Schema2.Array(OperationParameter),
251
+ requestBody: Schema2.OptionFromOptional(OperationRequestBody)
252
+ }) {
253
+ };
254
+ var HeaderValue = SecretBackedValue;
255
+ var ConfiguredHeaderBinding = class extends Schema2.Class(
256
+ "OpenApiConfiguredHeaderBinding"
257
+ )({
258
+ kind: Schema2.Literal("binding"),
259
+ slot: Schema2.String,
260
+ prefix: Schema2.optional(Schema2.String)
261
+ }) {
262
+ };
263
+ var ConfiguredHeaderValue = Schema2.Union([Schema2.String, ConfiguredHeaderBinding]);
264
+ var OpenApiSourceBindingValue = Schema2.Union([
265
+ Schema2.Struct({
266
+ kind: Schema2.Literal("secret"),
267
+ secretId: SecretId
268
+ }),
269
+ Schema2.Struct({
270
+ kind: Schema2.Literal("connection"),
271
+ connectionId: ConnectionId
272
+ }),
273
+ Schema2.Struct({
274
+ kind: Schema2.Literal("text"),
275
+ text: Schema2.String
276
+ })
277
+ ]);
278
+ var OpenApiSourceBindingInputSchema = Schema2.Struct({
279
+ sourceId: Schema2.String,
280
+ sourceScope: ScopeId,
281
+ scope: ScopeId,
282
+ slot: Schema2.String,
283
+ value: OpenApiSourceBindingValue
284
+ });
285
+ var OpenApiSourceBindingInput = class extends Schema2.Class(
286
+ "OpenApiSourceBindingInput"
287
+ )(OpenApiSourceBindingInputSchema.fields) {
288
+ };
289
+ var OpenApiSourceBindingRef = class extends Schema2.Class(
290
+ "OpenApiSourceBindingRef"
291
+ )({
292
+ sourceId: Schema2.String,
293
+ sourceScopeId: ScopeId,
294
+ scopeId: ScopeId,
295
+ slot: Schema2.String,
296
+ value: OpenApiSourceBindingValue,
297
+ createdAt: Schema2.Date,
298
+ updatedAt: Schema2.Date
299
+ }) {
300
+ };
301
+ var OAuth2Flow = Schema2.Literals(["authorizationCode", "clientCredentials"]);
302
+ var OAuth2Auth = class extends Schema2.Class("OpenApiOAuth2Auth")({
303
+ kind: Schema2.Literal("oauth2"),
304
+ /** Id of the Connection that owns this sign-in. Points at the core
305
+ * `connection` table; resolve via `ctx.connections.get(id)` or
306
+ * `ctx.connections.accessToken(id)`. Updated when the user signs in
307
+ * again from the source detail UI (a fresh connection is minted and
308
+ * this pointer is rewritten). */
309
+ connectionId: Schema2.String,
310
+ /** Key into `components.securitySchemes` this auth came from. Kept here
311
+ * so a spec with multiple OAuth2 schemes can wire each one to its own
312
+ * connection. */
313
+ securitySchemeName: Schema2.String,
314
+ /** OAuth2 grant type used for this source. Determines which flow the
315
+ * sign-in button runs (authorizationCode opens a browser popup;
316
+ * clientCredentials is server-to-server). */
317
+ flow: OAuth2Flow,
318
+ /** Absolute token endpoint URL. */
319
+ tokenUrl: Schema2.String,
320
+ /** Absolute authorization endpoint URL. Only used for authorizationCode
321
+ * flows; clientCredentials has no user consent step. */
322
+ authorizationUrl: Schema2.NullOr(Schema2.String),
323
+ /** Expected issuer for ID token validation. Defaults to authorization origin. */
324
+ issuerUrl: Schema2.optional(Schema2.NullOr(Schema2.String)),
325
+ /** Secret id holding the OAuth client_id. */
326
+ clientIdSecretId: Schema2.String,
327
+ /** Secret id holding the OAuth client_secret. Optional for public
328
+ * clients (PKCE-only authorizationCode). */
329
+ clientSecretSecretId: Schema2.NullOr(Schema2.String),
330
+ /** OAuth scopes requested on sign-in. Stored as a static list so the
331
+ * sign-in button can re-request the same capabilities without having
332
+ * to re-derive them from the OpenAPI spec. */
333
+ scopes: Schema2.Array(Schema2.String)
334
+ }) {
335
+ };
336
+ var OAuth2SourceConfig = class extends Schema2.Class(
337
+ "OpenApiOAuth2SourceConfig"
338
+ )({
339
+ kind: Schema2.Literal("oauth2"),
340
+ securitySchemeName: Schema2.String,
341
+ flow: OAuth2Flow,
342
+ tokenUrl: Schema2.String,
343
+ authorizationUrl: Schema2.NullOr(Schema2.String),
344
+ issuerUrl: Schema2.optional(Schema2.NullOr(Schema2.String)),
345
+ clientIdSlot: Schema2.String,
346
+ clientSecretSlot: Schema2.NullOr(Schema2.String),
347
+ connectionSlot: Schema2.String,
348
+ scopes: Schema2.Array(Schema2.String)
349
+ }) {
350
+ };
351
+ var InvocationConfig = class extends Schema2.Class("InvocationConfig")({
352
+ baseUrl: Schema2.String,
353
+ /** Headers applied to every request. Values can reference secrets. */
354
+ headers: Schema2.optional(Schema2.Record(Schema2.String, HeaderValue)),
355
+ /**
356
+ * Optional OAuth2 auth — if set, the invoker resolves/refreshes the
357
+ * access token and injects `Authorization: Bearer <token>` on every
358
+ * request. Coexists with `headers` but wins for the Authorization header.
359
+ */
360
+ oauth2: Schema2.OptionFromOptional(OAuth2Auth)
361
+ }) {
362
+ };
363
+ var InvocationResult = class extends Schema2.Class("InvocationResult")({
364
+ status: Schema2.Number,
365
+ headers: Schema2.Record(Schema2.String, Schema2.String),
366
+ data: Schema2.NullOr(Schema2.Unknown),
367
+ error: Schema2.NullOr(Schema2.Unknown)
368
+ }) {
369
+ };
370
+
371
+ // src/sdk/extract.ts
372
+ import { Effect as Effect2, Option as Option2 } from "effect";
373
+ var HTTP_METHODS = [
374
+ "get",
375
+ "put",
376
+ "post",
377
+ "delete",
378
+ "patch",
379
+ "head",
380
+ "options",
381
+ "trace"
382
+ ];
383
+ var VALID_PARAM_LOCATIONS = /* @__PURE__ */ new Set(["path", "query", "header", "cookie"]);
384
+ var extractParameters = (pathItem, operation, r) => {
385
+ const merged = /* @__PURE__ */ new Map();
386
+ for (const raw of pathItem.parameters ?? []) {
387
+ const p = r.resolve(raw);
388
+ if (!p) continue;
389
+ merged.set(`${p.in}:${p.name}`, p);
390
+ }
391
+ for (const raw of operation.parameters ?? []) {
392
+ const p = r.resolve(raw);
393
+ if (!p) continue;
394
+ merged.set(`${p.in}:${p.name}`, p);
395
+ }
396
+ return [...merged.values()].filter((p) => VALID_PARAM_LOCATIONS.has(p.in)).map(
397
+ (p) => new OperationParameter({
398
+ name: p.name,
399
+ location: p.in,
400
+ required: p.in === "path" ? true : p.required === true,
401
+ schema: Option2.fromNullishOr(p.schema),
402
+ style: Option2.fromNullishOr(p.style),
403
+ explode: Option2.fromNullishOr(p.explode),
404
+ allowReserved: Option2.fromNullishOr("allowReserved" in p ? p.allowReserved : void 0),
405
+ description: Option2.fromNullishOr(p.description)
406
+ })
407
+ );
408
+ };
409
+ var buildEncodingRecord = (encoding) => {
410
+ if (!encoding) return void 0;
411
+ const out = {};
412
+ for (const [prop, raw] of Object.entries(encoding)) {
413
+ if (typeof raw !== "object" || raw === null) continue;
414
+ const e = raw;
415
+ out[prop] = new EncodingObject({
416
+ contentType: Option2.fromNullishOr(e.contentType),
417
+ style: Option2.fromNullishOr(e.style),
418
+ explode: Option2.fromNullishOr(e.explode),
419
+ allowReserved: Option2.fromNullishOr(e.allowReserved)
420
+ });
421
+ }
422
+ return Object.keys(out).length > 0 ? out : void 0;
423
+ };
424
+ var extractRequestBody = (operation, r) => {
425
+ if (!operation.requestBody) return void 0;
426
+ const body = r.resolve(operation.requestBody);
427
+ if (!body) return void 0;
428
+ const contents = declaredContents(body.content).map(
429
+ ({ mediaType, media }) => new MediaBinding({
430
+ contentType: mediaType,
431
+ schema: Option2.fromNullishOr(media.schema),
432
+ encoding: Option2.fromNullishOr(
433
+ buildEncodingRecord(
434
+ media.encoding
435
+ )
436
+ )
437
+ })
438
+ );
439
+ if (contents.length === 0) return void 0;
440
+ const defaultContent = contents[0];
441
+ return new OperationRequestBody({
442
+ required: body.required === true,
443
+ contentType: defaultContent.contentType,
444
+ schema: defaultContent.schema,
445
+ contents: Option2.some(contents)
446
+ });
447
+ };
448
+ var extractOutputSchema = (operation, r) => {
449
+ if (!operation.responses) return void 0;
450
+ const entries = Object.entries(operation.responses);
451
+ const preferred = [
452
+ ...entries.filter(([s]) => /^2\d\d$/.test(s)).sort(([a], [b]) => a.localeCompare(b)),
453
+ ...entries.filter(([s]) => s === "default")
454
+ ];
455
+ for (const [, ref] of preferred) {
456
+ const resp = r.resolve(ref);
457
+ if (!resp) continue;
458
+ const content = preferredResponseContent(resp.content);
459
+ if (content?.media.schema) return content.media.schema;
460
+ }
461
+ return void 0;
462
+ };
463
+ var buildInputSchema = (parameters, requestBody) => {
464
+ const properties = {};
465
+ const required = [];
466
+ for (const param of parameters) {
467
+ properties[param.name] = Option2.getOrElse(param.schema, () => ({ type: "string" }));
468
+ if (param.required) required.push(param.name);
469
+ }
470
+ if (requestBody) {
471
+ properties.body = Option2.getOrElse(requestBody.schema, () => ({ type: "object" }));
472
+ if (requestBody.required) required.push("body");
473
+ const contents = Option2.getOrUndefined(requestBody.contents);
474
+ if (contents && contents.length > 1) {
475
+ properties.contentType = {
476
+ type: "string",
477
+ enum: contents.map((c) => c.contentType),
478
+ default: requestBody.contentType,
479
+ description: "Content-Type for the request body. Declared media types for this operation, in spec order."
480
+ };
481
+ }
482
+ }
483
+ if (Object.keys(properties).length === 0) return void 0;
484
+ return {
485
+ type: "object",
486
+ properties,
487
+ ...required.length > 0 ? { required } : {},
488
+ additionalProperties: false
489
+ };
490
+ };
491
+ var deriveOperationId = (method, pathTemplate, operation) => operation.operationId ?? (`${method}_${pathTemplate.replace(/[^a-zA-Z0-9]+/g, "_")}`.replace(/^_+|_+$/g, "") || `${method}_operation`);
492
+ var extractServers = (doc) => (doc.servers ?? []).flatMap((server) => {
493
+ if (!server.url) return [];
494
+ const vars = server.variables ? Object.fromEntries(
495
+ Object.entries(server.variables).flatMap(([name, v]) => {
496
+ if (v.default === void 0 || v.default === null) return [];
497
+ const enumValues = Array.isArray(v.enum) ? v.enum.filter((x) => typeof x === "string") : void 0;
498
+ return [
499
+ [
500
+ name,
501
+ new ServerVariable({
502
+ default: String(v.default),
503
+ enum: enumValues && enumValues.length > 0 ? Option2.some(enumValues) : Option2.none(),
504
+ description: Option2.fromNullishOr(v.description)
505
+ })
506
+ ]
507
+ ];
508
+ })
509
+ ) : void 0;
510
+ return [
511
+ new ServerInfo({
512
+ url: server.url,
513
+ description: Option2.fromNullishOr(server.description),
514
+ variables: vars && Object.keys(vars).length > 0 ? Option2.some(vars) : Option2.none()
515
+ })
516
+ ];
517
+ });
518
+ var extract = Effect2.fn("OpenApi.extract")(function* (doc) {
519
+ const paths = doc.paths;
520
+ if (!paths) {
521
+ return yield* new OpenApiExtractionError({
522
+ message: "OpenAPI document has no paths defined"
523
+ });
524
+ }
525
+ const r = new DocResolver(doc);
526
+ const operations = [];
527
+ for (const [pathTemplate, pathItem] of Object.entries(paths).sort(
528
+ ([a], [b]) => a.localeCompare(b)
529
+ )) {
530
+ if (!pathItem) continue;
531
+ for (const method of HTTP_METHODS) {
532
+ const operation = pathItem[method];
533
+ if (!operation) continue;
534
+ const parameters = extractParameters(pathItem, operation, r);
535
+ const requestBody = extractRequestBody(operation, r);
536
+ const inputSchema = buildInputSchema(parameters, requestBody);
537
+ const outputSchema = extractOutputSchema(operation, r);
538
+ const tags = (operation.tags ?? []).filter((t) => t.trim().length > 0);
539
+ operations.push(
540
+ new ExtractedOperation({
541
+ operationId: OperationId.make(deriveOperationId(method, pathTemplate, operation)),
542
+ method,
543
+ pathTemplate,
544
+ summary: Option2.fromNullishOr(operation.summary),
545
+ description: Option2.fromNullishOr(operation.description),
546
+ tags,
547
+ parameters,
548
+ requestBody: Option2.fromNullishOr(requestBody),
549
+ inputSchema: Option2.fromNullishOr(inputSchema),
550
+ outputSchema: Option2.fromNullishOr(outputSchema),
551
+ deprecated: operation.deprecated === true
552
+ })
553
+ );
554
+ }
555
+ }
556
+ return new ExtractionResult({
557
+ title: Option2.fromNullishOr(doc.info?.title),
558
+ version: Option2.fromNullishOr(doc.info?.version),
559
+ servers: extractServers(doc),
560
+ operations
561
+ });
562
+ });
563
+
564
+ // src/sdk/invoke.ts
565
+ import { Effect as Effect3, Layer, Option as Option3 } from "effect";
566
+ import { HttpClient as HttpClient2, HttpClientRequest as HttpClientRequest2 } from "effect/unstable/http";
567
+ var CONTAINER_KEYS = {
568
+ path: ["path", "pathParams", "params"],
569
+ query: ["query", "queryParams", "params"],
570
+ header: ["headers", "header"],
571
+ cookie: ["cookies", "cookie"]
572
+ };
573
+ var readParamValue = (args, param) => {
574
+ const direct = args[param.name];
575
+ if (direct !== void 0) return direct;
576
+ for (const key of CONTAINER_KEYS[param.location] ?? []) {
577
+ const container = args[key];
578
+ if (typeof container === "object" && container !== null && !Array.isArray(container)) {
579
+ const nested = container[param.name];
580
+ if (nested !== void 0) return nested;
581
+ }
582
+ }
583
+ return void 0;
584
+ };
585
+ var resolvePath = Effect3.fn("OpenApi.resolvePath")(function* (pathTemplate, args, parameters) {
586
+ let resolved = pathTemplate;
587
+ for (const param of parameters) {
588
+ if (param.location !== "path") continue;
589
+ const value = readParamValue(args, param);
590
+ if (value === void 0 || value === null) {
591
+ if (param.required) {
592
+ return yield* new OpenApiInvocationError({
593
+ message: `Missing required path parameter: ${param.name}`,
594
+ statusCode: Option3.none()
595
+ });
596
+ }
597
+ continue;
598
+ }
599
+ resolved = resolved.replaceAll(`{${param.name}}`, encodeURIComponent(String(value)));
600
+ }
601
+ const remaining = [...resolved.matchAll(/\{([^{}]+)\}/g)].map((m) => m[1]).filter((v) => typeof v === "string");
602
+ for (const name of remaining) {
603
+ const value = args[name];
604
+ if (value !== void 0 && value !== null) {
605
+ resolved = resolved.replaceAll(`{${name}}`, encodeURIComponent(String(value)));
606
+ }
607
+ }
608
+ const unresolved = [...resolved.matchAll(/\{([^{}]+)\}/g)].map((m) => m[1]).filter((v) => typeof v === "string");
609
+ if (unresolved.length > 0) {
610
+ return yield* new OpenApiInvocationError({
611
+ message: `Unresolved path parameters: ${[...new Set(unresolved)].join(", ")}`,
612
+ statusCode: Option3.none()
613
+ });
614
+ }
615
+ return resolved;
616
+ });
617
+ var resolveHeaders = (headers, secrets) => {
618
+ const entries = Object.entries(headers);
619
+ const secretCount = entries.reduce(
620
+ (acc, [, value]) => typeof value === "string" ? acc : acc + 1,
621
+ 0
622
+ );
623
+ return Effect3.gen(function* () {
624
+ const values = yield* Effect3.all(
625
+ entries.map(
626
+ ([name, value]) => typeof value === "string" ? Effect3.succeed({ name, value }) : secrets.get(value.secretId).pipe(
627
+ Effect3.mapError(
628
+ (err) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? new OpenApiInvocationError({
629
+ message: `Failed to resolve secret "${value.secretId}" for header "${name}"`,
630
+ statusCode: Option3.none()
631
+ }) : err
632
+ ),
633
+ Effect3.flatMap(
634
+ (secret) => secret === null ? Effect3.fail(
635
+ new OpenApiInvocationError({
636
+ message: `Failed to resolve secret "${value.secretId}" for header "${name}"`,
637
+ statusCode: Option3.none()
638
+ })
639
+ ) : Effect3.succeed({
640
+ name,
641
+ value: value.prefix ? `${value.prefix}${secret}` : secret
642
+ })
643
+ )
644
+ )
645
+ ),
646
+ { concurrency: "unbounded" }
647
+ );
648
+ const resolved = {};
649
+ for (const { name, value } of values) resolved[name] = value;
650
+ return resolved;
651
+ }).pipe(
652
+ Effect3.withSpan("plugin.openapi.secret.resolve", {
653
+ attributes: {
654
+ "plugin.openapi.headers.total": entries.length,
655
+ "plugin.openapi.headers.secret_count": secretCount
656
+ }
657
+ })
658
+ );
659
+ };
660
+ var applyHeaders = (request, headers) => {
661
+ let req = request;
662
+ for (const [name, value] of Object.entries(headers)) {
663
+ req = HttpClientRequest2.setHeader(req, name, value);
664
+ }
665
+ return req;
666
+ };
667
+ var normalizeContentType = (ct) => ct?.split(";")[0]?.trim().toLowerCase() ?? "";
668
+ var isJsonContentType = (ct) => {
669
+ const normalized = normalizeContentType(ct);
670
+ if (!normalized) return false;
671
+ return normalized === "application/json" || normalized.includes("+json") || normalized.includes("json");
672
+ };
673
+ var isFormUrlEncoded = (ct) => normalizeContentType(ct) === "application/x-www-form-urlencoded";
674
+ var isMultipartFormData = (ct) => normalizeContentType(ct).startsWith("multipart/form-data");
675
+ var isXmlContentType = (ct) => {
676
+ const normalized = normalizeContentType(ct);
677
+ if (!normalized) return false;
678
+ return normalized === "application/xml" || normalized === "text/xml" || normalized.endsWith("+xml");
679
+ };
680
+ var isTextContentType = (ct) => normalizeContentType(ct).startsWith("text/");
681
+ var isOctetStream = (ct) => normalizeContentType(ct) === "application/octet-stream";
682
+ var toUint8Array = (value) => {
683
+ if (value instanceof Uint8Array) return value;
684
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
685
+ if (ArrayBuffer.isView(value)) {
686
+ const view = value;
687
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
688
+ }
689
+ if (Array.isArray(value) && value.every((v) => typeof v === "number")) {
690
+ return new Uint8Array(value);
691
+ }
692
+ return null;
693
+ };
694
+ var toArrayBuffer = (bytes) => {
695
+ const copy = new ArrayBuffer(bytes.byteLength);
696
+ new Uint8Array(copy).set(bytes);
697
+ return copy;
698
+ };
699
+ var DEFAULT_FORM_STYLE = {
700
+ style: "form",
701
+ explode: true,
702
+ allowReserved: false
703
+ };
704
+ var resolveStyleExplode = (e) => {
705
+ if (!e) return DEFAULT_FORM_STYLE;
706
+ return {
707
+ style: Option3.getOrElse(e.style, () => DEFAULT_FORM_STYLE.style),
708
+ explode: Option3.getOrElse(e.explode, () => DEFAULT_FORM_STYLE.explode),
709
+ allowReserved: Option3.getOrElse(e.allowReserved, () => DEFAULT_FORM_STYLE.allowReserved)
710
+ };
711
+ };
712
+ var RESERVED_UNENCODED_RE = /[A-Za-z0-9\-._~:/?#[\]@!$&'()*+,;=]/;
713
+ var encodeFormValue = (v, allowReserved) => {
714
+ const raw = typeof v === "object" && v !== null ? JSON.stringify(v) : String(v);
715
+ if (!allowReserved) return encodeURIComponent(raw);
716
+ let out = "";
717
+ for (const ch of raw) {
718
+ out += RESERVED_UNENCODED_RE.test(ch) ? ch : encodeURIComponent(ch);
719
+ }
720
+ return out;
721
+ };
722
+ var serializeFormUrlEncoded = (value, encoding) => {
723
+ const parts = [];
724
+ for (const [key, raw] of Object.entries(value)) {
725
+ if (raw === void 0 || raw === null) continue;
726
+ const { style, explode, allowReserved } = resolveStyleExplode(encoding?.[key]);
727
+ const encKey = encodeURIComponent(key);
728
+ if (Array.isArray(raw)) {
729
+ if (explode) {
730
+ for (const v of raw) {
731
+ parts.push(`${encKey}=${encodeFormValue(v, allowReserved)}`);
732
+ }
733
+ } else {
734
+ const sep = style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ",";
735
+ parts.push(
736
+ `${encKey}=${encodeFormValue(
737
+ raw.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)).join(sep),
738
+ allowReserved
739
+ )}`
740
+ );
741
+ }
742
+ continue;
743
+ }
744
+ if (typeof raw === "object") {
745
+ const entries = Object.entries(raw).filter(
746
+ ([, v]) => v !== void 0 && v !== null
747
+ );
748
+ if (style === "deepObject") {
749
+ for (const [subkey, subval] of entries) {
750
+ parts.push(
751
+ `${encodeURIComponent(`${key}[${subkey}]`)}=${encodeFormValue(
752
+ subval,
753
+ allowReserved
754
+ )}`
755
+ );
756
+ }
757
+ } else if (explode) {
758
+ for (const [subkey, subval] of entries) {
759
+ parts.push(
760
+ `${encodeURIComponent(subkey)}=${encodeFormValue(subval, allowReserved)}`
761
+ );
762
+ }
763
+ } else {
764
+ const flat = entries.flatMap(([k, v]) => [
765
+ k,
766
+ typeof v === "object" ? JSON.stringify(v) : String(v)
767
+ ]);
768
+ parts.push(`${encKey}=${encodeFormValue(flat.join(","), allowReserved)}`);
769
+ }
770
+ continue;
771
+ }
772
+ parts.push(`${encKey}=${encodeFormValue(raw, allowReserved)}`);
773
+ }
774
+ return parts.join("&");
775
+ };
776
+ var coerceFormDataRecord = (value, encoding) => {
777
+ const out = {};
778
+ for (const [key, raw] of Object.entries(value)) {
779
+ if (raw === void 0 || raw === null) continue;
780
+ const partType = encoding?.[key] ? Option3.getOrUndefined(encoding[key].contentType) : void 0;
781
+ if (partType) {
782
+ const isJson = partType.startsWith("application/json") || partType.includes("+json");
783
+ const serialized = typeof raw === "string" ? raw : isJson ? JSON.stringify(raw) : typeof raw === "object" ? JSON.stringify(raw) : String(raw);
784
+ out[key] = new Blob([serialized], { type: partType });
785
+ continue;
786
+ }
787
+ if (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean" || raw instanceof Blob || typeof File !== "undefined" && raw instanceof File) {
788
+ out[key] = raw;
789
+ continue;
790
+ }
791
+ if (Array.isArray(raw)) {
792
+ out[key] = raw.map(
793
+ (v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v instanceof Blob || typeof File !== "undefined" && v instanceof File ? v : JSON.stringify(v)
794
+ );
795
+ continue;
796
+ }
797
+ const bytes = toUint8Array(raw);
798
+ if (bytes) {
799
+ out[key] = new Blob([toArrayBuffer(bytes)]);
800
+ continue;
801
+ }
802
+ out[key] = JSON.stringify(raw);
803
+ }
804
+ return out;
805
+ };
806
+ var applyRequestBody = (request, contentType, bodyValue, encoding) => {
807
+ if (isJsonContentType(contentType)) {
808
+ if (typeof bodyValue === "string") {
809
+ return HttpClientRequest2.bodyText(request, bodyValue, contentType);
810
+ }
811
+ return HttpClientRequest2.bodyJsonUnsafe(request, bodyValue);
812
+ }
813
+ if (isFormUrlEncoded(contentType)) {
814
+ if (typeof bodyValue === "string") {
815
+ return HttpClientRequest2.bodyText(request, bodyValue, contentType);
816
+ }
817
+ if (typeof bodyValue === "object" && bodyValue !== null && !Array.isArray(bodyValue)) {
818
+ const serialized = serializeFormUrlEncoded(
819
+ bodyValue,
820
+ encoding
821
+ );
822
+ return HttpClientRequest2.bodyText(request, serialized, contentType);
823
+ }
824
+ return HttpClientRequest2.bodyUrlParams(
825
+ request,
826
+ bodyValue
827
+ );
828
+ }
829
+ if (isMultipartFormData(contentType)) {
830
+ if (bodyValue instanceof FormData) {
831
+ return HttpClientRequest2.bodyFormData(request, bodyValue);
832
+ }
833
+ if (typeof bodyValue === "object" && bodyValue !== null) {
834
+ return HttpClientRequest2.bodyFormDataRecord(
835
+ request,
836
+ coerceFormDataRecord(bodyValue, encoding)
837
+ );
838
+ }
839
+ return HttpClientRequest2.bodyText(request, String(bodyValue), contentType);
840
+ }
841
+ if (isOctetStream(contentType)) {
842
+ const bytes2 = toUint8Array(bodyValue);
843
+ if (bytes2) return HttpClientRequest2.bodyUint8Array(request, bytes2, contentType);
844
+ if (typeof bodyValue === "string") {
845
+ return HttpClientRequest2.bodyText(request, bodyValue, contentType);
846
+ }
847
+ return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
848
+ }
849
+ if (isXmlContentType(contentType) || isTextContentType(contentType)) {
850
+ if (typeof bodyValue === "string") {
851
+ return HttpClientRequest2.bodyText(request, bodyValue, contentType);
852
+ }
853
+ const bytes2 = toUint8Array(bodyValue);
854
+ if (bytes2) return HttpClientRequest2.bodyUint8Array(request, bytes2, contentType);
855
+ return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
856
+ }
857
+ if (typeof bodyValue === "string") {
858
+ return HttpClientRequest2.bodyText(request, bodyValue, contentType);
859
+ }
860
+ const bytes = toUint8Array(bodyValue);
861
+ if (bytes) return HttpClientRequest2.bodyUint8Array(request, bytes, contentType);
862
+ return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
863
+ };
864
+ var invoke = Effect3.fn("OpenApi.invoke")(function* (operation, args, resolvedHeaders, sourceQueryParams = {}) {
865
+ const client = yield* HttpClient2.HttpClient;
866
+ yield* Effect3.annotateCurrentSpan({
867
+ "http.method": operation.method.toUpperCase(),
868
+ "http.route": operation.pathTemplate,
869
+ "plugin.openapi.method": operation.method.toUpperCase(),
870
+ "plugin.openapi.path_template": operation.pathTemplate,
871
+ "plugin.openapi.headers.resolved_count": Object.keys(resolvedHeaders).length
872
+ });
873
+ const resolvedPath = yield* resolvePath(operation.pathTemplate, args, operation.parameters);
874
+ const path = resolvedPath.startsWith("/") ? resolvedPath : `/${resolvedPath}`;
875
+ let request = HttpClientRequest2.make(operation.method.toUpperCase())(path);
876
+ for (const [name, value] of Object.entries(sourceQueryParams)) {
877
+ request = HttpClientRequest2.setUrlParam(request, name, value);
878
+ }
879
+ for (const param of operation.parameters) {
880
+ if (param.location !== "query") continue;
881
+ const value = readParamValue(args, param);
882
+ if (value === void 0 || value === null) continue;
883
+ request = HttpClientRequest2.setUrlParam(request, param.name, String(value));
884
+ }
885
+ for (const param of operation.parameters) {
886
+ if (param.location !== "header") continue;
887
+ const value = readParamValue(args, param);
888
+ if (value === void 0 || value === null) continue;
889
+ request = HttpClientRequest2.setHeader(request, param.name, String(value));
890
+ }
891
+ if (Option3.isSome(operation.requestBody)) {
892
+ const rb = operation.requestBody.value;
893
+ const bodyValue = args.body ?? args.input;
894
+ if (bodyValue !== void 0) {
895
+ const contentsOpt = Option3.getOrUndefined(rb.contents);
896
+ const requestedCt = typeof args.contentType === "string" ? args.contentType : void 0;
897
+ const selected = contentsOpt && requestedCt ? contentsOpt.find((c) => c.contentType === requestedCt) : void 0;
898
+ const chosenCt = selected?.contentType ?? rb.contentType;
899
+ const chosenEncoding = selected ? Option3.getOrUndefined(selected.encoding) : contentsOpt && contentsOpt[0] ? Option3.getOrUndefined(contentsOpt[0].encoding) : void 0;
900
+ request = applyRequestBody(request, chosenCt, bodyValue, chosenEncoding);
901
+ }
902
+ }
903
+ request = applyHeaders(request, resolvedHeaders);
904
+ const response = yield* client.execute(request).pipe(
905
+ Effect3.mapError(
906
+ (err) => new OpenApiInvocationError({
907
+ message: `HTTP request failed: ${err.message}`,
908
+ statusCode: Option3.none(),
909
+ cause: err
910
+ })
911
+ )
912
+ );
913
+ const status = response.status;
914
+ yield* Effect3.annotateCurrentSpan({
915
+ "http.status_code": status
916
+ });
917
+ const responseHeaders = { ...response.headers };
918
+ const contentType = response.headers["content-type"] ?? null;
919
+ const mapBodyError = Effect3.mapError(
920
+ (err) => new OpenApiInvocationError({
921
+ message: `Failed to read response body: ${err.message ?? String(err)}`,
922
+ statusCode: Option3.some(status),
923
+ cause: err
924
+ })
925
+ );
926
+ const responseBody = status === 204 ? null : isJsonContentType(contentType) ? yield* response.json.pipe(
927
+ Effect3.catch(() => response.text),
928
+ mapBodyError
929
+ ) : yield* response.text.pipe(mapBodyError);
930
+ const ok = status >= 200 && status < 300;
931
+ return new InvocationResult({
932
+ status,
933
+ headers: responseHeaders,
934
+ data: ok ? responseBody : null,
935
+ error: ok ? null : responseBody
936
+ });
937
+ });
938
+ var invokeWithLayer = (operation, args, baseUrl, resolvedHeaders, sourceQueryParams, httpClientLayer) => {
939
+ const clientWithBaseUrl = baseUrl ? Layer.effect(
940
+ HttpClient2.HttpClient,
941
+ Effect3.map(
942
+ Effect3.service(HttpClient2.HttpClient),
943
+ HttpClient2.mapRequest(HttpClientRequest2.prependUrl(baseUrl))
944
+ )
945
+ ).pipe(Layer.provide(httpClientLayer)) : httpClientLayer;
946
+ return invoke(operation, args, resolvedHeaders, sourceQueryParams).pipe(
947
+ Effect3.provide(clientWithBaseUrl),
948
+ Effect3.withSpan("plugin.openapi.invoke", {
949
+ attributes: {
950
+ "plugin.openapi.method": operation.method.toUpperCase(),
951
+ "plugin.openapi.path_template": operation.pathTemplate,
952
+ "plugin.openapi.base_url": baseUrl
953
+ }
954
+ })
955
+ );
956
+ };
957
+ var REQUIRE_APPROVAL = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
958
+ var annotationsForOperation = (method, pathTemplate) => {
959
+ const m = method.toLowerCase();
960
+ if (!REQUIRE_APPROVAL.has(m)) return {};
961
+ return {
962
+ requiresApproval: true,
963
+ approvalDescription: `${method.toUpperCase()} ${pathTemplate}`
964
+ };
965
+ };
966
+
967
+ // src/sdk/preview.ts
968
+ import { Effect as Effect4, Option as Option4 } from "effect";
969
+ import { Schema as Schema3 } from "effect";
970
+ var OAuth2Scopes = Schema3.Record(Schema3.String, Schema3.String);
971
+ var OAuth2AuthorizationCodeFlow = class extends Schema3.Class(
972
+ "OAuth2AuthorizationCodeFlow"
973
+ )({
974
+ authorizationUrl: Schema3.String,
975
+ tokenUrl: Schema3.String,
976
+ refreshUrl: Schema3.OptionFromOptional(Schema3.String),
977
+ scopes: OAuth2Scopes
978
+ }) {
979
+ };
980
+ var OAuth2ClientCredentialsFlow = class extends Schema3.Class(
981
+ "OAuth2ClientCredentialsFlow"
982
+ )({
983
+ tokenUrl: Schema3.String,
984
+ refreshUrl: Schema3.OptionFromOptional(Schema3.String),
985
+ scopes: OAuth2Scopes
986
+ }) {
987
+ };
988
+ var OAuth2Flows = class extends Schema3.Class("OAuth2Flows")({
989
+ authorizationCode: Schema3.OptionFromOptional(OAuth2AuthorizationCodeFlow),
990
+ clientCredentials: Schema3.OptionFromOptional(OAuth2ClientCredentialsFlow)
991
+ }) {
992
+ };
993
+ var SecurityScheme = class extends Schema3.Class("SecurityScheme")({
994
+ /** Key name in components.securitySchemes (e.g. "api_token") */
995
+ name: Schema3.String,
996
+ /** OpenAPI security scheme type */
997
+ type: Schema3.Literals(["http", "apiKey", "oauth2", "openIdConnect"]),
998
+ /** For type: "http" — e.g. "bearer", "basic" */
999
+ scheme: Schema3.OptionFromOptional(Schema3.String),
1000
+ /** For type: "http" with scheme "bearer" — e.g. "JWT" */
1001
+ bearerFormat: Schema3.OptionFromOptional(Schema3.String),
1002
+ /** For type: "apiKey" — where the key goes */
1003
+ in: Schema3.OptionFromOptional(Schema3.Literals(["header", "query", "cookie"])),
1004
+ /** For type: "apiKey" — the header/query/cookie name */
1005
+ headerName: Schema3.OptionFromOptional(Schema3.String),
1006
+ description: Schema3.OptionFromOptional(Schema3.String),
1007
+ /** For type: "oauth2" — declared flows (authorizationCode / clientCredentials only; implicit and password are deprecated). */
1008
+ flows: Schema3.OptionFromOptional(OAuth2Flows),
1009
+ /** For type: "openIdConnect" — the discovery URL. */
1010
+ openIdConnectUrl: Schema3.OptionFromOptional(Schema3.String)
1011
+ }) {
1012
+ };
1013
+ var AuthStrategy = class extends Schema3.Class("AuthStrategy")({
1014
+ /** The security schemes required together for this strategy */
1015
+ schemes: Schema3.Array(Schema3.String)
1016
+ }) {
1017
+ };
1018
+ var HeaderPreset = class extends Schema3.Class("HeaderPreset")({
1019
+ /** Human-readable label for the UI (e.g. "Bearer Token", "API Key + Email") */
1020
+ label: Schema3.String,
1021
+ /** Headers this strategy needs. Value is null when the user must provide it. */
1022
+ headers: Schema3.Record(Schema3.String, Schema3.NullOr(Schema3.String)),
1023
+ /** Which headers should be stored as secrets */
1024
+ secretHeaders: Schema3.Array(Schema3.String)
1025
+ }) {
1026
+ };
1027
+ var OAuth2Preset = class extends Schema3.Class("OAuth2Preset")({
1028
+ /** Human-readable label for the UI (e.g. "OAuth2 (Authorization Code) — oauth_app") */
1029
+ label: Schema3.String,
1030
+ /** The source security scheme this preset came from (components.securitySchemes key). */
1031
+ securitySchemeName: Schema3.String,
1032
+ /** Which OAuth2 flow this preset uses. */
1033
+ flow: Schema3.Literals(["authorizationCode", "clientCredentials"]),
1034
+ /** For authorizationCode: user-agent redirect URL (from the spec). */
1035
+ authorizationUrl: Schema3.OptionFromOptional(Schema3.String),
1036
+ /** Token endpoint to exchange the code / refresh. */
1037
+ tokenUrl: Schema3.String,
1038
+ /** Optional refresh endpoint if the spec declares one separately. */
1039
+ refreshUrl: Schema3.OptionFromOptional(Schema3.String),
1040
+ /** Declared scopes for this flow: `{ scope: description }`. */
1041
+ scopes: Schema3.Record(Schema3.String, Schema3.String)
1042
+ }) {
1043
+ };
1044
+ var PreviewOperation = class extends Schema3.Class("PreviewOperation")({
1045
+ operationId: Schema3.String,
1046
+ method: HttpMethod,
1047
+ path: Schema3.String,
1048
+ summary: Schema3.OptionFromOptional(Schema3.String),
1049
+ tags: Schema3.Array(Schema3.String),
1050
+ deprecated: Schema3.Boolean
1051
+ }) {
1052
+ };
1053
+ var SpecPreview = class extends Schema3.Class("SpecPreview")({
1054
+ title: Schema3.OptionFromOptional(Schema3.String),
1055
+ version: Schema3.OptionFromOptional(Schema3.String),
1056
+ /** Reuses ServerInfo from extraction */
1057
+ servers: Schema3.Array(ServerInfo),
1058
+ operationCount: Schema3.Number,
1059
+ /** Lightweight operation list for the add-source UI */
1060
+ operations: Schema3.Array(PreviewOperation),
1061
+ tags: Schema3.Array(Schema3.String),
1062
+ securitySchemes: Schema3.Array(SecurityScheme),
1063
+ /** Valid auth strategies (each is a set of schemes used together) */
1064
+ authStrategies: Schema3.Array(AuthStrategy),
1065
+ /** Pre-built header presets derived from auth strategies */
1066
+ headerPresets: Schema3.Array(HeaderPreset),
1067
+ /** OAuth2 presets — one per (oauth2 scheme × supported flow) combination */
1068
+ oauth2Presets: Schema3.Array(OAuth2Preset)
1069
+ }) {
1070
+ };
1071
+ var stringRecord = (value) => {
1072
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
1073
+ const out = {};
1074
+ for (const [k, v] of Object.entries(value)) {
1075
+ if (typeof v === "string") out[k] = v;
1076
+ }
1077
+ return out;
1078
+ };
1079
+ var extractFlows = (rawFlows) => {
1080
+ if (!rawFlows || typeof rawFlows !== "object") return Option4.none();
1081
+ const flows = rawFlows;
1082
+ const parseFlow = (key) => flows[key];
1083
+ let authorizationCode = Option4.none();
1084
+ const authCodeRaw = parseFlow("authorizationCode");
1085
+ if (authCodeRaw && typeof authCodeRaw === "object") {
1086
+ const f = authCodeRaw;
1087
+ const authUrl = typeof f.authorizationUrl === "string" ? f.authorizationUrl : null;
1088
+ const tokenUrl = typeof f.tokenUrl === "string" ? f.tokenUrl : null;
1089
+ if (authUrl && tokenUrl) {
1090
+ authorizationCode = Option4.some(
1091
+ new OAuth2AuthorizationCodeFlow({
1092
+ authorizationUrl: authUrl,
1093
+ tokenUrl,
1094
+ refreshUrl: Option4.fromNullishOr(
1095
+ typeof f.refreshUrl === "string" ? f.refreshUrl : void 0
1096
+ ),
1097
+ scopes: stringRecord(f.scopes)
1098
+ })
1099
+ );
1100
+ }
1101
+ }
1102
+ let clientCredentials = Option4.none();
1103
+ const ccRaw = parseFlow("clientCredentials");
1104
+ if (ccRaw && typeof ccRaw === "object") {
1105
+ const f = ccRaw;
1106
+ const tokenUrl = typeof f.tokenUrl === "string" ? f.tokenUrl : null;
1107
+ if (tokenUrl) {
1108
+ clientCredentials = Option4.some(
1109
+ new OAuth2ClientCredentialsFlow({
1110
+ tokenUrl,
1111
+ refreshUrl: Option4.fromNullishOr(
1112
+ typeof f.refreshUrl === "string" ? f.refreshUrl : void 0
1113
+ ),
1114
+ scopes: stringRecord(f.scopes)
1115
+ })
1116
+ );
1117
+ }
1118
+ }
1119
+ if (Option4.isNone(authorizationCode) && Option4.isNone(clientCredentials)) {
1120
+ return Option4.none();
1121
+ }
1122
+ return Option4.some(new OAuth2Flows({ authorizationCode, clientCredentials }));
1123
+ };
1124
+ var extractSecuritySchemes = (rawSchemes, resolver) => Object.entries(rawSchemes).flatMap(([name, schemeOrRef]) => {
1125
+ if (!schemeOrRef || typeof schemeOrRef !== "object") return [];
1126
+ const resolved = resolver.resolve(
1127
+ schemeOrRef
1128
+ );
1129
+ if (!resolved || typeof resolved !== "object") return [];
1130
+ const scheme = resolved;
1131
+ const type = scheme.type;
1132
+ if (!["http", "apiKey", "oauth2", "openIdConnect"].includes(type)) return [];
1133
+ return [
1134
+ new SecurityScheme({
1135
+ name,
1136
+ type,
1137
+ scheme: Option4.fromNullishOr(scheme.scheme),
1138
+ bearerFormat: Option4.fromNullishOr(scheme.bearerFormat),
1139
+ in: Option4.fromNullishOr(scheme.in),
1140
+ headerName: Option4.fromNullishOr(scheme.name),
1141
+ description: Option4.fromNullishOr(scheme.description),
1142
+ flows: type === "oauth2" ? extractFlows(scheme.flows) : Option4.none(),
1143
+ openIdConnectUrl: Option4.fromNullishOr(
1144
+ scheme.openIdConnectUrl
1145
+ )
1146
+ })
1147
+ ];
1148
+ });
1149
+ var buildHeaderPresets = (schemes, strategies) => {
1150
+ const schemeMap = new Map(schemes.map((s) => [s.name, s]));
1151
+ return strategies.flatMap((strategy) => {
1152
+ const resolved = strategy.schemes.map((name) => schemeMap.get(name)).filter((s) => s !== void 0);
1153
+ if (resolved.length === 0) return [];
1154
+ const headers = {};
1155
+ const secretHeaders = [];
1156
+ const labelParts = [];
1157
+ for (const scheme of resolved) {
1158
+ if (scheme.type === "http" && Option4.getOrElse(scheme.scheme, () => "") === "bearer") {
1159
+ headers["Authorization"] = null;
1160
+ secretHeaders.push("Authorization");
1161
+ labelParts.push("Bearer Token");
1162
+ } else if (scheme.type === "http" && Option4.getOrElse(scheme.scheme, () => "") === "basic") {
1163
+ headers["Authorization"] = null;
1164
+ secretHeaders.push("Authorization");
1165
+ labelParts.push("Basic Auth");
1166
+ } else if (scheme.type === "apiKey" && Option4.getOrElse(scheme.in, () => "") === "header") {
1167
+ const headerName = Option4.getOrElse(scheme.headerName, () => scheme.name);
1168
+ headers[headerName] = null;
1169
+ secretHeaders.push(headerName);
1170
+ labelParts.push(scheme.name);
1171
+ } else if (scheme.type === "apiKey") {
1172
+ labelParts.push(`${scheme.name} (${Option4.getOrElse(scheme.in, () => "unknown")})`);
1173
+ } else {
1174
+ labelParts.push(scheme.name);
1175
+ }
1176
+ }
1177
+ if (Object.keys(headers).length === 0 && resolved.length > 0) {
1178
+ return [new HeaderPreset({ label: labelParts.join(" + "), headers: {}, secretHeaders: [] })];
1179
+ }
1180
+ return [new HeaderPreset({ label: labelParts.join(" + "), headers, secretHeaders })];
1181
+ });
1182
+ };
1183
+ var buildOAuth2Presets = (schemes) => {
1184
+ const presets = [];
1185
+ for (const scheme of schemes) {
1186
+ if (scheme.type !== "oauth2") continue;
1187
+ if (Option4.isNone(scheme.flows)) continue;
1188
+ const flows = scheme.flows.value;
1189
+ if (Option4.isSome(flows.authorizationCode)) {
1190
+ const flow = flows.authorizationCode.value;
1191
+ presets.push(
1192
+ new OAuth2Preset({
1193
+ label: `OAuth2 Authorization Code \xB7 ${scheme.name}`,
1194
+ securitySchemeName: scheme.name,
1195
+ flow: "authorizationCode",
1196
+ authorizationUrl: Option4.some(flow.authorizationUrl),
1197
+ tokenUrl: flow.tokenUrl,
1198
+ refreshUrl: flow.refreshUrl,
1199
+ scopes: flow.scopes
1200
+ })
1201
+ );
1202
+ }
1203
+ if (Option4.isSome(flows.clientCredentials)) {
1204
+ const flow = flows.clientCredentials.value;
1205
+ presets.push(
1206
+ new OAuth2Preset({
1207
+ label: `OAuth2 Client Credentials \xB7 ${scheme.name}`,
1208
+ securitySchemeName: scheme.name,
1209
+ flow: "clientCredentials",
1210
+ authorizationUrl: Option4.none(),
1211
+ tokenUrl: flow.tokenUrl,
1212
+ refreshUrl: flow.refreshUrl,
1213
+ scopes: flow.scopes
1214
+ })
1215
+ );
1216
+ }
1217
+ }
1218
+ return presets;
1219
+ };
1220
+ var collectTags = (result) => {
1221
+ const tagSet = /* @__PURE__ */ new Set();
1222
+ for (const op of result.operations) {
1223
+ for (const tag of op.tags) tagSet.add(tag);
1224
+ }
1225
+ return [...tagSet].sort();
1226
+ };
1227
+ var previewSpec = Effect4.fn("OpenApi.previewSpec")(function* (input) {
1228
+ const specText = yield* resolveSpecText(input);
1229
+ const doc = yield* parse(specText);
1230
+ const result = yield* extract(doc);
1231
+ const resolver = new DocResolver(doc);
1232
+ const securitySchemes = extractSecuritySchemes(
1233
+ doc.components?.securitySchemes ?? {},
1234
+ resolver
1235
+ );
1236
+ const rawSecurity = doc.security ?? [];
1237
+ const declaredStrategies = rawSecurity.map(
1238
+ (entry) => new AuthStrategy({ schemes: Object.keys(entry) })
1239
+ );
1240
+ const authStrategies = declaredStrategies.length > 0 ? declaredStrategies : securitySchemes.map((scheme) => new AuthStrategy({ schemes: [scheme.name] }));
1241
+ return new SpecPreview({
1242
+ title: result.title,
1243
+ version: result.version,
1244
+ servers: result.servers,
1245
+ operationCount: result.operations.length,
1246
+ operations: result.operations.map(
1247
+ (op) => new PreviewOperation({
1248
+ operationId: op.operationId,
1249
+ method: op.method,
1250
+ path: op.pathTemplate,
1251
+ summary: op.summary,
1252
+ tags: op.tags,
1253
+ deprecated: op.deprecated
1254
+ })
1255
+ ),
1256
+ tags: collectTags(result),
1257
+ securitySchemes,
1258
+ authStrategies,
1259
+ headerPresets: buildHeaderPresets(securitySchemes, authStrategies),
1260
+ oauth2Presets: buildOAuth2Presets(securitySchemes)
1261
+ });
1262
+ });
1263
+
1264
+ // src/sdk/store.ts
1265
+ import { Effect as Effect5, Schema as Schema4 } from "effect";
1266
+ import {
1267
+ defineSchema,
1268
+ ScopeId as ScopeId2,
1269
+ StorageError
1270
+ } from "@executor-js/sdk/core";
1271
+ var openapiSchema = defineSchema({
1272
+ openapi_source: {
1273
+ fields: {
1274
+ id: { type: "string", required: true },
1275
+ scope_id: { type: "string", required: true, index: true },
1276
+ name: { type: "string", required: true },
1277
+ spec: { type: "string", required: true },
1278
+ // Origin URL the spec was fetched from. Set when `addSpec` was
1279
+ // invoked with an http(s) URL; null when the caller passed raw
1280
+ // spec text. Drives `canRefresh` on the core source row and
1281
+ // is the address re-fetched on `refreshSource`.
1282
+ source_url: { type: "string", required: false },
1283
+ base_url: { type: "string", required: false },
1284
+ headers: { type: "json", required: false },
1285
+ query_params: { type: "json", required: false },
1286
+ oauth2: { type: "json", required: false },
1287
+ invocation_config: { type: "json", required: true }
1288
+ }
1289
+ },
1290
+ openapi_operation: {
1291
+ fields: {
1292
+ id: { type: "string", required: true },
1293
+ scope_id: { type: "string", required: true, index: true },
1294
+ source_id: { type: "string", required: true, index: true },
1295
+ binding: { type: "json", required: true }
1296
+ }
1297
+ },
1298
+ openapi_source_binding: {
1299
+ fields: {
1300
+ id: { type: "string", required: true },
1301
+ source_id: { type: "string", required: true, index: true },
1302
+ source_scope_id: { type: "string", required: true, index: true },
1303
+ // Intentionally NOT named `scope_id`: this row is visible across
1304
+ // scope stacks and is filtered manually by source/target scope.
1305
+ // The target scope is credential ownership data, not adapter row
1306
+ // ownership. Source owners must be able to delete all descendant
1307
+ // bindings when a shared source is removed.
1308
+ target_scope_id: { type: "string", required: true, index: true },
1309
+ slot: { type: "string", required: true, index: true },
1310
+ value: { type: "json", required: true },
1311
+ created_at: { type: "date", required: true },
1312
+ updated_at: { type: "date", required: true }
1313
+ }
1314
+ }
1315
+ });
1316
+ var StoredSourceSchema = class extends Schema4.Class(
1317
+ "OpenApiStoredSource"
1318
+ )({
1319
+ namespace: Schema4.String,
1320
+ name: Schema4.String,
1321
+ config: Schema4.Struct({
1322
+ spec: Schema4.String,
1323
+ sourceUrl: Schema4.optional(Schema4.String),
1324
+ baseUrl: Schema4.optional(Schema4.String),
1325
+ namespace: Schema4.optional(Schema4.String),
1326
+ headers: Schema4.optional(
1327
+ Schema4.Record(Schema4.String, ConfiguredHeaderValue)
1328
+ ),
1329
+ queryParams: Schema4.optional(
1330
+ Schema4.Record(Schema4.String, HeaderValue)
1331
+ ),
1332
+ specFetchCredentials: Schema4.optional(
1333
+ Schema4.Struct({
1334
+ headers: Schema4.optional(
1335
+ Schema4.Record(Schema4.String, HeaderValue)
1336
+ ),
1337
+ queryParams: Schema4.optional(
1338
+ Schema4.Record(Schema4.String, HeaderValue)
1339
+ )
1340
+ })
1341
+ ),
1342
+ // Canonical source-owned OAuth config. Concrete client credentials
1343
+ // and connection ids live in OpenAPI-owned scoped binding rows.
1344
+ oauth2: Schema4.optional(OAuth2SourceConfig)
1345
+ })
1346
+ }) {
1347
+ };
1348
+ var encodeBinding = Schema4.encodeSync(OperationBinding);
1349
+ var decodeBinding = Schema4.decodeUnknownSync(OperationBinding);
1350
+ var decodeOAuth2 = Schema4.decodeUnknownSync(OAuth2Auth);
1351
+ var encodeOAuth2SourceConfig = Schema4.encodeSync(OAuth2SourceConfig);
1352
+ var encodeSourceBindingValue = Schema4.encodeSync(OpenApiSourceBindingValue);
1353
+ var decodeSourceBindingValue = Schema4.decodeUnknownSync(
1354
+ OpenApiSourceBindingValue
1355
+ );
1356
+ var asJsonObject = (value) => {
1357
+ if (value == null) return {};
1358
+ if (typeof value === "string")
1359
+ return JSON.parse(value);
1360
+ return value;
1361
+ };
1362
+ var toJsonRecord = (value) => value;
1363
+ var toConfiguredHeaderBinding = (value) => new ConfiguredHeaderBinding({
1364
+ kind: "binding",
1365
+ slot: String(value.slot ?? ""),
1366
+ ...typeof value.prefix === "string" ? { prefix: value.prefix } : {}
1367
+ });
1368
+ var decodeHeaders = (value) => {
1369
+ if (value == null) return {};
1370
+ if (typeof value === "string")
1371
+ return JSON.parse(value);
1372
+ return value;
1373
+ };
1374
+ var slugifySlotPart = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "default";
1375
+ var headerBindingSlot = (headerName) => `header:${slugifySlotPart(headerName)}`;
1376
+ var oauth2ClientIdSlot = (securitySchemeName) => `oauth2:${slugifySlotPart(securitySchemeName)}:client-id`;
1377
+ var oauth2ClientSecretSlot = (securitySchemeName) => `oauth2:${slugifySlotPart(securitySchemeName)}:client-secret`;
1378
+ var oauth2ConnectionSlot = (securitySchemeName) => `oauth2:${slugifySlotPart(securitySchemeName)}:connection`;
1379
+ var normalizeStoredHeaders = (value) => {
1380
+ const raw = decodeHeaders(value);
1381
+ const headers = {};
1382
+ const legacy = {};
1383
+ for (const [name, header] of Object.entries(raw)) {
1384
+ if (typeof header === "string") {
1385
+ headers[name] = header;
1386
+ legacy[name] = header;
1387
+ continue;
1388
+ }
1389
+ if (header && typeof header === "object" && "kind" in header && header.kind === "binding") {
1390
+ headers[name] = toConfiguredHeaderBinding(header);
1391
+ continue;
1392
+ }
1393
+ legacy[name] = header;
1394
+ headers[name] = new ConfiguredHeaderBinding({
1395
+ kind: "binding",
1396
+ slot: headerBindingSlot(name),
1397
+ prefix: header.prefix
1398
+ });
1399
+ }
1400
+ return { headers, legacy };
1401
+ };
1402
+ var normalizeStoredOAuth2 = (value) => {
1403
+ if (value == null) return {};
1404
+ const parsed = typeof value === "string" ? JSON.parse(value) : value;
1405
+ if (parsed && typeof parsed === "object" && "connectionSlot" in parsed) {
1406
+ return {
1407
+ oauth2: Schema4.decodeUnknownSync(OAuth2SourceConfig)(parsed)
1408
+ };
1409
+ }
1410
+ const legacy = decodeOAuth2(parsed);
1411
+ return {
1412
+ legacy,
1413
+ oauth2: new OAuth2SourceConfig({
1414
+ kind: "oauth2",
1415
+ securitySchemeName: legacy.securitySchemeName,
1416
+ flow: legacy.flow,
1417
+ tokenUrl: legacy.tokenUrl,
1418
+ authorizationUrl: legacy.authorizationUrl,
1419
+ clientIdSlot: oauth2ClientIdSlot(legacy.securitySchemeName),
1420
+ clientSecretSlot: legacy.clientSecretSecretId ? oauth2ClientSecretSlot(legacy.securitySchemeName) : null,
1421
+ connectionSlot: oauth2ConnectionSlot(legacy.securitySchemeName),
1422
+ scopes: [...legacy.scopes]
1423
+ })
1424
+ };
1425
+ };
1426
+ var makeDefaultOpenapiStore = ({
1427
+ adapter,
1428
+ scopes
1429
+ }) => {
1430
+ const scopeIds = scopes.map((scope) => scope.id);
1431
+ const scopePrecedence = /* @__PURE__ */ new Map();
1432
+ scopeIds.forEach((scope, index) => scopePrecedence.set(scope, index));
1433
+ const scopeRank = (scopeId) => scopePrecedence.get(scopeId) ?? Infinity;
1434
+ const encodeSyntheticRowIdPart = (value) => encodeURIComponent(value);
1435
+ const sourceBindingRowId = (sourceId, sourceScopeId, slot, scopeId) => [
1436
+ "openapi-source-binding",
1437
+ encodeSyntheticRowIdPart(sourceScopeId),
1438
+ encodeSyntheticRowIdPart(sourceId),
1439
+ encodeSyntheticRowIdPart(slot),
1440
+ encodeSyntheticRowIdPart(scopeId)
1441
+ ].join("::");
1442
+ const rowToSourceBinding = (row) => new OpenApiSourceBindingRef({
1443
+ sourceId: row.source_id,
1444
+ sourceScopeId: ScopeId2.make(row.source_scope_id),
1445
+ scopeId: ScopeId2.make(row.target_scope_id),
1446
+ slot: row.slot,
1447
+ value: decodeSourceBindingValue(asJsonObject(row.value)),
1448
+ createdAt: row.created_at instanceof Date ? row.created_at : new Date(row.created_at),
1449
+ updatedAt: row.updated_at instanceof Date ? row.updated_at : new Date(row.updated_at)
1450
+ });
1451
+ const validateBindingTarget = (params) => Effect5.gen(function* () {
1452
+ if (!scopeIds.includes(params.sourceScope)) {
1453
+ return yield* Effect5.fail(
1454
+ new StorageError({
1455
+ message: `OpenAPI source binding references source scope "${params.sourceScope}" which is not in the executor's scope stack [${scopeIds.join(", ")}].`,
1456
+ cause: void 0
1457
+ })
1458
+ );
1459
+ }
1460
+ if (!scopeIds.includes(params.targetScope)) {
1461
+ return yield* Effect5.fail(
1462
+ new StorageError({
1463
+ message: `OpenAPI source binding targets scope "${params.targetScope}" which is not in the executor's scope stack [${scopeIds.join(", ")}].`,
1464
+ cause: void 0
1465
+ })
1466
+ );
1467
+ }
1468
+ const source = yield* adapter.findOne({
1469
+ model: "openapi_source",
1470
+ where: [
1471
+ { field: "id", value: params.sourceId },
1472
+ { field: "scope_id", value: params.sourceScope }
1473
+ ]
1474
+ });
1475
+ if (!source) {
1476
+ return yield* Effect5.fail(
1477
+ new StorageError({
1478
+ message: `OpenAPI source "${params.sourceId}" does not exist at scope "${params.sourceScope}"`,
1479
+ cause: void 0
1480
+ })
1481
+ );
1482
+ }
1483
+ if (scopeRank(params.targetScope) > scopeRank(params.sourceScope)) {
1484
+ return yield* Effect5.fail(
1485
+ new StorageError({
1486
+ message: `OpenAPI source bindings for "${params.sourceId}" cannot be written at outer scope "${params.targetScope}" because the base source lives at "${params.sourceScope}"`,
1487
+ cause: void 0
1488
+ })
1489
+ );
1490
+ }
1491
+ return source;
1492
+ });
1493
+ const rowToSource = (row) => {
1494
+ const normalizedHeaders = normalizeStoredHeaders(row.headers);
1495
+ const normalizedOAuth2 = normalizeStoredOAuth2(row.oauth2);
1496
+ const invocationConfig = asJsonObject(row.invocation_config);
1497
+ return {
1498
+ namespace: row.id,
1499
+ scope: row.scope_id,
1500
+ name: row.name,
1501
+ config: {
1502
+ spec: row.spec,
1503
+ sourceUrl: row.source_url ?? void 0,
1504
+ baseUrl: row.base_url ?? void 0,
1505
+ headers: normalizedHeaders.headers,
1506
+ queryParams: decodeHeaders(row.query_params),
1507
+ specFetchCredentials: invocationConfig.specFetchCredentials,
1508
+ oauth2: normalizedOAuth2.oauth2
1509
+ },
1510
+ legacy: Object.keys(normalizedHeaders.legacy).length > 0 || normalizedOAuth2.legacy ? {
1511
+ ...Object.keys(normalizedHeaders.legacy).length > 0 ? { headers: normalizedHeaders.legacy } : {},
1512
+ ...normalizedOAuth2.legacy ? { oauth2: normalizedOAuth2.legacy } : {}
1513
+ } : void 0
1514
+ };
1515
+ };
1516
+ const rowToOperation = (row) => ({
1517
+ toolId: row.id,
1518
+ sourceId: row.source_id,
1519
+ binding: decodeBinding(
1520
+ typeof row.binding === "string" ? JSON.parse(row.binding) : row.binding
1521
+ )
1522
+ });
1523
+ const deleteSource = (namespace, scope, options) => Effect5.gen(function* () {
1524
+ yield* adapter.deleteMany({
1525
+ model: "openapi_operation",
1526
+ where: [
1527
+ { field: "source_id", value: namespace },
1528
+ { field: "scope_id", value: scope }
1529
+ ]
1530
+ });
1531
+ yield* adapter.delete({
1532
+ model: "openapi_source",
1533
+ where: [
1534
+ { field: "id", value: namespace },
1535
+ { field: "scope_id", value: scope }
1536
+ ]
1537
+ });
1538
+ if (options?.includeBindings) {
1539
+ yield* adapter.deleteMany({
1540
+ model: "openapi_source_binding",
1541
+ where: [
1542
+ { field: "source_id", value: namespace },
1543
+ { field: "source_scope_id", value: scope }
1544
+ ]
1545
+ });
1546
+ }
1547
+ });
1548
+ return {
1549
+ upsertSource: (input, operations) => Effect5.gen(function* () {
1550
+ yield* deleteSource(input.namespace, input.scope);
1551
+ yield* adapter.create({
1552
+ model: "openapi_source",
1553
+ data: {
1554
+ id: input.namespace,
1555
+ scope_id: input.scope,
1556
+ name: input.name,
1557
+ spec: input.config.spec,
1558
+ source_url: input.config.sourceUrl ?? void 0,
1559
+ base_url: input.config.baseUrl ?? void 0,
1560
+ headers: Object.fromEntries(
1561
+ Object.entries(input.config.headers ?? {}).map(
1562
+ ([name, value]) => [
1563
+ name,
1564
+ typeof value === "string" ? value : value.kind === "binding" ? {
1565
+ kind: value.kind,
1566
+ slot: value.slot,
1567
+ ...value.prefix ? { prefix: value.prefix } : {}
1568
+ } : value
1569
+ ]
1570
+ )
1571
+ ),
1572
+ query_params: input.config.queryParams,
1573
+ oauth2: input.config.oauth2 ? toJsonRecord(encodeOAuth2SourceConfig(input.config.oauth2)) : void 0,
1574
+ invocation_config: {
1575
+ ...input.config.specFetchCredentials ? { specFetchCredentials: input.config.specFetchCredentials } : {}
1576
+ }
1577
+ },
1578
+ forceAllowId: true
1579
+ });
1580
+ if (operations.length > 0) {
1581
+ yield* adapter.createMany({
1582
+ model: "openapi_operation",
1583
+ data: operations.map((op) => ({
1584
+ id: op.toolId,
1585
+ scope_id: input.scope,
1586
+ source_id: op.sourceId,
1587
+ binding: toJsonRecord(encodeBinding(op.binding))
1588
+ })),
1589
+ forceAllowId: true
1590
+ });
1591
+ }
1592
+ }),
1593
+ updateSourceMeta: (namespace, scope, patch) => Effect5.gen(function* () {
1594
+ const existingRow = yield* adapter.findOne({
1595
+ model: "openapi_source",
1596
+ where: [
1597
+ { field: "id", value: namespace },
1598
+ { field: "scope_id", value: scope }
1599
+ ]
1600
+ });
1601
+ if (!existingRow) return;
1602
+ const existing = rowToSource(existingRow);
1603
+ const nextName = patch.name?.trim() || existing.name;
1604
+ const nextBaseUrl = patch.baseUrl !== void 0 ? patch.baseUrl : existing.config.baseUrl;
1605
+ const nextHeaders = patch.headers !== void 0 ? patch.headers : existing.config.headers ?? {};
1606
+ const nextQueryParams = patch.queryParams !== void 0 ? patch.queryParams : existing.config.queryParams ?? {};
1607
+ const nextOAuth2 = patch.oauth2 !== void 0 ? patch.oauth2 : existing.config.oauth2;
1608
+ yield* adapter.update({
1609
+ model: "openapi_source",
1610
+ where: [
1611
+ { field: "id", value: namespace },
1612
+ { field: "scope_id", value: scope }
1613
+ ],
1614
+ update: {
1615
+ name: nextName,
1616
+ base_url: nextBaseUrl ?? void 0,
1617
+ headers: Object.fromEntries(
1618
+ Object.entries(nextHeaders).map(([name, value]) => [
1619
+ name,
1620
+ typeof value === "string" ? value : {
1621
+ kind: value.kind,
1622
+ slot: value.slot,
1623
+ ...value.prefix ? { prefix: value.prefix } : {}
1624
+ }
1625
+ ])
1626
+ ),
1627
+ query_params: nextQueryParams,
1628
+ oauth2: nextOAuth2 ? toJsonRecord(encodeOAuth2SourceConfig(nextOAuth2)) : void 0,
1629
+ invocation_config: asJsonObject(existingRow.invocation_config)
1630
+ }
1631
+ });
1632
+ }),
1633
+ getSource: (namespace, scope) => adapter.findOne({
1634
+ model: "openapi_source",
1635
+ where: [
1636
+ { field: "id", value: namespace },
1637
+ { field: "scope_id", value: scope }
1638
+ ]
1639
+ }).pipe(Effect5.map((row) => row ? rowToSource(row) : null)),
1640
+ listSources: () => adapter.findMany({ model: "openapi_source" }).pipe(Effect5.map((rows) => rows.map(rowToSource))),
1641
+ getOperationByToolId: (toolId, scope) => adapter.findOne({
1642
+ model: "openapi_operation",
1643
+ where: [
1644
+ { field: "id", value: toolId },
1645
+ { field: "scope_id", value: scope }
1646
+ ]
1647
+ }).pipe(Effect5.map((row) => row ? rowToOperation(row) : null)),
1648
+ listOperationsBySource: (sourceId, scope) => adapter.findMany({
1649
+ model: "openapi_operation",
1650
+ where: [
1651
+ { field: "source_id", value: sourceId },
1652
+ { field: "scope_id", value: scope }
1653
+ ]
1654
+ }).pipe(Effect5.map((rows) => rows.map(rowToOperation))),
1655
+ removeSource: (namespace, scope) => deleteSource(namespace, scope, { includeBindings: true }),
1656
+ listSourceBindings: (sourceId, sourceScope) => Effect5.gen(function* () {
1657
+ yield* validateBindingTarget({
1658
+ sourceId,
1659
+ sourceScope,
1660
+ targetScope: sourceScope
1661
+ });
1662
+ const sourceScopeRank = scopeRank(sourceScope);
1663
+ const rows = yield* adapter.findMany({
1664
+ model: "openapi_source_binding",
1665
+ where: [
1666
+ { field: "source_id", value: sourceId },
1667
+ { field: "source_scope_id", value: sourceScope }
1668
+ ]
1669
+ });
1670
+ return rows.filter(
1671
+ (row) => scopeRank(row.target_scope_id) <= sourceScopeRank
1672
+ ).sort(
1673
+ (a, b) => scopeRank(a.target_scope_id) - scopeRank(b.target_scope_id)
1674
+ ).map(rowToSourceBinding);
1675
+ }),
1676
+ resolveSourceBinding: (sourceId, sourceScope, slot) => Effect5.gen(function* () {
1677
+ yield* validateBindingTarget({
1678
+ sourceId,
1679
+ sourceScope,
1680
+ targetScope: sourceScope
1681
+ });
1682
+ const rows = yield* adapter.findMany({
1683
+ model: "openapi_source_binding",
1684
+ where: [
1685
+ { field: "source_id", value: sourceId },
1686
+ { field: "source_scope_id", value: sourceScope },
1687
+ { field: "slot", value: slot }
1688
+ ]
1689
+ });
1690
+ const sourceScopeRank = scopeRank(sourceScope);
1691
+ const row = rows.filter(
1692
+ (candidate) => scopeRank(candidate.target_scope_id) <= sourceScopeRank
1693
+ ).sort(
1694
+ (a, b) => scopeRank(a.target_scope_id) - scopeRank(b.target_scope_id)
1695
+ )[0];
1696
+ return row ? rowToSourceBinding(row) : null;
1697
+ }),
1698
+ setSourceBinding: (input) => Effect5.gen(function* () {
1699
+ yield* validateBindingTarget({
1700
+ sourceId: input.sourceId,
1701
+ sourceScope: input.sourceScope,
1702
+ targetScope: input.scope
1703
+ });
1704
+ const id = sourceBindingRowId(
1705
+ input.sourceId,
1706
+ input.sourceScope,
1707
+ input.slot,
1708
+ input.scope
1709
+ );
1710
+ const now = /* @__PURE__ */ new Date();
1711
+ yield* adapter.delete({
1712
+ model: "openapi_source_binding",
1713
+ where: [{ field: "id", value: id }]
1714
+ });
1715
+ yield* adapter.create({
1716
+ model: "openapi_source_binding",
1717
+ data: {
1718
+ id,
1719
+ source_id: input.sourceId,
1720
+ source_scope_id: input.sourceScope,
1721
+ target_scope_id: input.scope,
1722
+ slot: input.slot,
1723
+ value: toJsonRecord(encodeSourceBindingValue(input.value)),
1724
+ created_at: now,
1725
+ updated_at: now
1726
+ },
1727
+ forceAllowId: true
1728
+ });
1729
+ return new OpenApiSourceBindingRef({
1730
+ sourceId: input.sourceId,
1731
+ sourceScopeId: input.sourceScope,
1732
+ scopeId: input.scope,
1733
+ slot: input.slot,
1734
+ value: input.value,
1735
+ createdAt: now,
1736
+ updatedAt: now
1737
+ });
1738
+ }),
1739
+ removeSourceBinding: (sourceId, sourceScope, slot, scope) => Effect5.gen(function* () {
1740
+ yield* validateBindingTarget({
1741
+ sourceId,
1742
+ sourceScope,
1743
+ targetScope: scope
1744
+ });
1745
+ yield* adapter.delete({
1746
+ model: "openapi_source_binding",
1747
+ where: [
1748
+ {
1749
+ field: "id",
1750
+ value: sourceBindingRowId(sourceId, sourceScope, slot, scope)
1751
+ }
1752
+ ]
1753
+ });
1754
+ })
1755
+ };
1756
+ };
1757
+
1758
+ // src/sdk/plugin.ts
1759
+ import { Effect as Effect6, Option as Option5, Schema as Schema5 } from "effect";
1760
+ import { FetchHttpClient } from "effect/unstable/http";
1761
+ import {
1762
+ ConnectionId as ConnectionId2,
1763
+ ScopeId as ScopeId3,
1764
+ SecretId as SecretId2,
1765
+ SourceDetectionResult,
1766
+ definePlugin,
1767
+ resolveSecretBackedMap
1768
+ } from "@executor-js/sdk/core";
1769
+ import {
1770
+ headersToConfigValues
1771
+ } from "@executor-js/config";
1772
+
1773
+ // src/sdk/definitions.ts
1774
+ var splitWords = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z0-9]+)/g, "$1 $2").replace(/[^a-zA-Z0-9]+/g, " ").trim().split(/\s+/).filter((part) => part.length > 0);
1775
+ var normalizeWord = (value) => value.toLowerCase();
1776
+ var toCamelCase = (value) => {
1777
+ const words = splitWords(value).map(normalizeWord);
1778
+ if (words.length === 0) return "tool";
1779
+ const [first, ...rest] = words;
1780
+ return `${first}${rest.map((p) => `${p[0]?.toUpperCase() ?? ""}${p.slice(1)}`).join("")}`;
1781
+ };
1782
+ var toPascalCase = (value) => {
1783
+ const camel = toCamelCase(value);
1784
+ return `${camel[0]?.toUpperCase() ?? ""}${camel.slice(1)}`;
1785
+ };
1786
+ var VERSION_SEGMENT_REGEX = /^v\d+(?:[._-]\d+)?$/i;
1787
+ var IGNORED_PATH_SEGMENTS = /* @__PURE__ */ new Set(["api"]);
1788
+ var pathSegmentsFromTemplate = (pathTemplate) => pathTemplate.split("/").map((s) => s.trim()).filter((s) => s.length > 0);
1789
+ var isPathParameterSegment = (segment) => segment.startsWith("{") && segment.endsWith("}");
1790
+ var normalizeGroupSegment = (value) => {
1791
+ const candidate = value?.trim();
1792
+ if (!candidate) return null;
1793
+ return toCamelCase(candidate);
1794
+ };
1795
+ var deriveVersionSegment = (pathTemplate) => pathSegmentsFromTemplate(pathTemplate).map((s) => s.toLowerCase()).find((s) => VERSION_SEGMENT_REGEX.test(s));
1796
+ var derivePathGroup = (pathTemplate) => {
1797
+ for (const segment of pathSegmentsFromTemplate(pathTemplate)) {
1798
+ const lower = segment.toLowerCase();
1799
+ if (VERSION_SEGMENT_REGEX.test(lower)) continue;
1800
+ if (IGNORED_PATH_SEGMENTS.has(lower)) continue;
1801
+ if (isPathParameterSegment(segment)) continue;
1802
+ return normalizeGroupSegment(segment) ?? "root";
1803
+ }
1804
+ return "root";
1805
+ };
1806
+ var splitOperationIdSegments = (value) => value.split(/[/.]+/).map((s) => s.trim()).filter((s) => s.length > 0);
1807
+ var deriveLeafSeed = (operationId, group) => {
1808
+ const segments = splitOperationIdSegments(operationId);
1809
+ if (segments.length > 1) {
1810
+ const [first, ...rest] = segments;
1811
+ if ((normalizeGroupSegment(first) ?? first) === group && rest.length > 0) {
1812
+ return rest.join(" ");
1813
+ }
1814
+ }
1815
+ return operationId;
1816
+ };
1817
+ var fallbackLeafSeed = (method, pathTemplate, group) => {
1818
+ const relevantSegments = pathSegmentsFromTemplate(pathTemplate).filter((s) => !VERSION_SEGMENT_REGEX.test(s.toLowerCase())).filter((s) => !IGNORED_PATH_SEGMENTS.has(s.toLowerCase())).filter((s) => !isPathParameterSegment(s)).map((s) => normalizeGroupSegment(s) ?? s).filter((s) => s !== group);
1819
+ const segmentSuffix = relevantSegments.map((s) => toPascalCase(s)).join("");
1820
+ return `${method}${segmentSuffix || "Operation"}`;
1821
+ };
1822
+ var deriveLeaf = (operationId, method, pathTemplate, group) => {
1823
+ const preferred = toCamelCase(deriveLeafSeed(operationId, group));
1824
+ if (preferred.length > 0 && preferred !== group) return preferred;
1825
+ return toCamelCase(fallbackLeafSeed(method, pathTemplate, group));
1826
+ };
1827
+ var resolveCollisions = (definitions) => {
1828
+ const staged = definitions.map((d) => ({ ...d }));
1829
+ const applyFactory = (items, factory) => {
1830
+ const byPath = /* @__PURE__ */ new Map();
1831
+ for (const item of items) {
1832
+ const bucket = byPath.get(item.toolPath) ?? [];
1833
+ bucket.push(item);
1834
+ byPath.set(item.toolPath, bucket);
1835
+ }
1836
+ for (const bucket of byPath.values()) {
1837
+ if (bucket.length < 2) continue;
1838
+ for (const d of bucket) {
1839
+ d.toolPath = factory(d);
1840
+ }
1841
+ }
1842
+ };
1843
+ applyFactory(
1844
+ staged,
1845
+ (d) => d.versionSegment ? `${d.group}.${d.versionSegment}.${d.leaf}` : d.toolPath
1846
+ );
1847
+ applyFactory(staged, (d) => {
1848
+ const prefix = d.versionSegment ? `${d.group}.${d.versionSegment}` : d.group;
1849
+ return `${prefix}.${d.leaf}${toPascalCase(d.method)}`;
1850
+ });
1851
+ applyFactory(staged, (d) => {
1852
+ const prefix = d.versionSegment ? `${d.group}.${d.versionSegment}` : d.group;
1853
+ return `${prefix}.${d.leaf}${toPascalCase(d.method)}${d.operationHash.slice(0, 8)}`;
1854
+ });
1855
+ return staged.map((d) => ({
1856
+ toolPath: d.toolPath,
1857
+ group: d.group,
1858
+ leaf: d.leaf,
1859
+ operationIndex: d.operationIndex,
1860
+ operation: d.operation
1861
+ }));
1862
+ };
1863
+ var stableHash = (value) => {
1864
+ const str = JSON.stringify(value, Object.keys(value).sort());
1865
+ let hash = 0;
1866
+ for (let i = 0; i < str.length; i++) {
1867
+ hash = (hash << 5) - hash + str.charCodeAt(i) | 0;
1868
+ }
1869
+ return Math.abs(hash).toString(36).padStart(8, "0");
1870
+ };
1871
+ var compileToolDefinitions = (operations) => {
1872
+ const raw = operations.map((op, index) => {
1873
+ const operationId = op.operationId;
1874
+ const group = normalizeGroupSegment(op.tags[0]) ?? derivePathGroup(op.pathTemplate);
1875
+ const leaf = deriveLeaf(operationId, op.method, op.pathTemplate, group);
1876
+ const versionSegment = deriveVersionSegment(op.pathTemplate);
1877
+ const operationHash = stableHash({
1878
+ method: op.method,
1879
+ path: op.pathTemplate,
1880
+ operationId
1881
+ });
1882
+ return {
1883
+ toolPath: `${group}.${leaf}`,
1884
+ group,
1885
+ leaf,
1886
+ versionSegment,
1887
+ method: op.method,
1888
+ operationHash,
1889
+ operationIndex: index,
1890
+ operation: op
1891
+ };
1892
+ });
1893
+ return resolveCollisions(raw).sort((a, b) => a.toolPath.localeCompare(b.toolPath));
1894
+ };
1895
+
1896
+ // src/sdk/plugin.ts
1897
+ var PreviewSpecInputSchema = Schema5.Struct({
1898
+ spec: Schema5.String,
1899
+ specFetchCredentials: Schema5.optional(
1900
+ Schema5.Struct({
1901
+ headers: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue)),
1902
+ queryParams: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue))
1903
+ })
1904
+ )
1905
+ });
1906
+ var AddSourceInputSchema = Schema5.Struct({
1907
+ spec: Schema5.String,
1908
+ baseUrl: Schema5.optional(Schema5.String),
1909
+ namespace: Schema5.optional(Schema5.String),
1910
+ headers: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue)),
1911
+ queryParams: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue)),
1912
+ specFetchCredentials: Schema5.optional(
1913
+ Schema5.Struct({
1914
+ headers: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue)),
1915
+ queryParams: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue))
1916
+ })
1917
+ )
1918
+ });
1919
+ var normalizeOpenApiRefs = (node) => {
1920
+ if (node == null || typeof node !== "object") return node;
1921
+ if (Array.isArray(node)) {
1922
+ let changed2 = false;
1923
+ const out = node.map((item) => {
1924
+ const n = normalizeOpenApiRefs(item);
1925
+ if (n !== item) changed2 = true;
1926
+ return n;
1927
+ });
1928
+ return changed2 ? out : node;
1929
+ }
1930
+ const obj = node;
1931
+ if (typeof obj.$ref === "string") {
1932
+ const match = obj.$ref.match(/^#\/components\/schemas\/(.+)$/);
1933
+ if (match) return { ...obj, $ref: `#/$defs/${match[1]}` };
1934
+ return obj;
1935
+ }
1936
+ let changed = false;
1937
+ const result = {};
1938
+ for (const [k, v] of Object.entries(obj)) {
1939
+ const n = normalizeOpenApiRefs(v);
1940
+ if (n !== v) changed = true;
1941
+ result[k] = n;
1942
+ }
1943
+ return changed ? result : obj;
1944
+ };
1945
+ var toBinding = (def) => new OperationBinding({
1946
+ method: def.operation.method,
1947
+ pathTemplate: def.operation.pathTemplate,
1948
+ parameters: [...def.operation.parameters],
1949
+ requestBody: def.operation.requestBody
1950
+ });
1951
+ var descriptionFor = (def) => {
1952
+ const op = def.operation;
1953
+ return Option5.getOrElse(
1954
+ op.description,
1955
+ () => Option5.getOrElse(op.summary, () => `${op.method.toUpperCase()} ${op.pathTemplate}`)
1956
+ );
1957
+ };
1958
+ var headerSlotFromName = (name) => `header:${name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "default"}`;
1959
+ var oauthClientIdSlot = (securitySchemeName) => `oauth2:${securitySchemeName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "default"}:client-id`;
1960
+ var oauthClientSecretSlot = (securitySchemeName) => `oauth2:${securitySchemeName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "default"}:client-secret`;
1961
+ var oauthConnectionSlot = (securitySchemeName) => `oauth2:${securitySchemeName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "default"}:connection`;
1962
+ var canonicalizeHeaders = (headers) => {
1963
+ const nextHeaders = {};
1964
+ const bindings = [];
1965
+ for (const [name, value] of Object.entries(headers ?? {})) {
1966
+ if (typeof value === "string") {
1967
+ nextHeaders[name] = value;
1968
+ continue;
1969
+ }
1970
+ if ("kind" in value) {
1971
+ nextHeaders[name] = value;
1972
+ continue;
1973
+ }
1974
+ const slot = headerSlotFromName(name);
1975
+ nextHeaders[name] = new ConfiguredHeaderBinding({
1976
+ kind: "binding",
1977
+ slot,
1978
+ prefix: value.prefix
1979
+ });
1980
+ bindings.push({
1981
+ slot,
1982
+ value: {
1983
+ kind: "secret",
1984
+ secretId: SecretId2.make(value.secretId)
1985
+ }
1986
+ });
1987
+ }
1988
+ return { headers: nextHeaders, bindings };
1989
+ };
1990
+ var canonicalizeOAuth2 = (oauth2) => {
1991
+ if (!oauth2) return { bindings: [] };
1992
+ if ("connectionSlot" in oauth2) {
1993
+ return { oauth2, bindings: [] };
1994
+ }
1995
+ const bindings = [
1996
+ {
1997
+ slot: oauthClientIdSlot(oauth2.securitySchemeName),
1998
+ value: {
1999
+ kind: "secret",
2000
+ secretId: SecretId2.make(oauth2.clientIdSecretId)
2001
+ }
2002
+ }
2003
+ ];
2004
+ if (oauth2.clientSecretSecretId) {
2005
+ bindings.push({
2006
+ slot: oauthClientSecretSlot(oauth2.securitySchemeName),
2007
+ value: {
2008
+ kind: "secret",
2009
+ secretId: SecretId2.make(oauth2.clientSecretSecretId)
2010
+ }
2011
+ });
2012
+ }
2013
+ if (oauth2.connectionId) {
2014
+ bindings.push({
2015
+ slot: oauthConnectionSlot(oauth2.securitySchemeName),
2016
+ value: {
2017
+ kind: "connection",
2018
+ connectionId: ConnectionId2.make(oauth2.connectionId)
2019
+ }
2020
+ });
2021
+ }
2022
+ return {
2023
+ oauth2: new OAuth2SourceConfig({
2024
+ kind: "oauth2",
2025
+ securitySchemeName: oauth2.securitySchemeName,
2026
+ flow: oauth2.flow,
2027
+ tokenUrl: oauth2.tokenUrl,
2028
+ authorizationUrl: oauth2.authorizationUrl,
2029
+ clientIdSlot: oauthClientIdSlot(oauth2.securitySchemeName),
2030
+ clientSecretSlot: oauth2.clientSecretSecretId ? oauthClientSecretSlot(oauth2.securitySchemeName) : null,
2031
+ connectionSlot: oauthConnectionSlot(oauth2.securitySchemeName),
2032
+ scopes: [...oauth2.scopes]
2033
+ }),
2034
+ bindings
2035
+ };
2036
+ };
2037
+ var resolveEffectiveSourceConfig = (ctx, base) => Effect6.gen(function* () {
2038
+ const rank = new Map(ctx.scopes.map((scope, index) => [scope.id, index]));
2039
+ const baseRank = rank.get(base.scope) ?? Infinity;
2040
+ let fallback = null;
2041
+ for (let index = baseRank + 1; index < ctx.scopes.length; index++) {
2042
+ const scope = ctx.scopes[index];
2043
+ if (!scope) continue;
2044
+ fallback = yield* ctx.storage.getSource(base.namespace, scope.id);
2045
+ if (fallback) break;
2046
+ }
2047
+ if (!fallback) {
2048
+ return {
2049
+ config: base.config,
2050
+ headersSource: base,
2051
+ oauth2Source: base
2052
+ };
2053
+ }
2054
+ const hasBaseHeaders = Object.keys(base.config.headers ?? {}).length > 0;
2055
+ const hasBaseQueryParams = Object.keys(base.config.queryParams ?? {}).length > 0;
2056
+ return {
2057
+ config: {
2058
+ ...base.config,
2059
+ sourceUrl: base.config.sourceUrl ?? fallback.config.sourceUrl,
2060
+ baseUrl: base.config.baseUrl || fallback.config.baseUrl,
2061
+ namespace: base.config.namespace ?? fallback.config.namespace,
2062
+ headers: hasBaseHeaders ? base.config.headers : fallback.config.headers,
2063
+ queryParams: hasBaseQueryParams ? base.config.queryParams : fallback.config.queryParams,
2064
+ specFetchCredentials: base.config.specFetchCredentials ?? fallback.config.specFetchCredentials,
2065
+ oauth2: base.config.oauth2 ?? fallback.config.oauth2
2066
+ },
2067
+ headersSource: hasBaseHeaders ? base : fallback,
2068
+ oauth2Source: base.config.oauth2 ? base : fallback
2069
+ };
2070
+ });
2071
+ var resolveConfiguredHeaders = (ctx, params) => Effect6.gen(function* () {
2072
+ const resolved = {};
2073
+ for (const [name, value] of Object.entries(params.headers)) {
2074
+ if (typeof value === "string") {
2075
+ resolved[name] = value;
2076
+ continue;
2077
+ }
2078
+ const binding = yield* ctx.storage.resolveSourceBinding(
2079
+ params.sourceId,
2080
+ params.sourceScope,
2081
+ value.slot
2082
+ );
2083
+ if (binding?.value.kind === "secret") {
2084
+ const secret = yield* ctx.secrets.get(binding.value.secretId).pipe(
2085
+ Effect6.mapError(
2086
+ (err) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? new OpenApiOAuthError({
2087
+ message: `Secret not found for header "${name}"`
2088
+ }) : err
2089
+ )
2090
+ );
2091
+ if (secret === null) {
2092
+ return yield* new OpenApiOAuthError({
2093
+ message: `Missing secret "${binding.value.secretId}" for header "${name}"`
2094
+ });
2095
+ }
2096
+ resolved[name] = value.prefix ? `${value.prefix}${secret}` : secret;
2097
+ continue;
2098
+ }
2099
+ if (binding?.value.kind === "text") {
2100
+ resolved[name] = value.prefix ? `${value.prefix}${binding.value.text}` : binding.value.text;
2101
+ continue;
2102
+ }
2103
+ const legacy = params.legacyHeaders?.[name];
2104
+ if (legacy) {
2105
+ const fallback = yield* resolveHeaders({ [name]: legacy }, ctx.secrets).pipe(
2106
+ Effect6.map((headers) => headers[name]),
2107
+ Effect6.mapError(
2108
+ (err) => err instanceof OpenApiOAuthError ? err : new OpenApiOAuthError({ message: err.message })
2109
+ )
2110
+ );
2111
+ resolved[name] = fallback;
2112
+ continue;
2113
+ }
2114
+ return yield* new OpenApiOAuthError({
2115
+ message: `Missing binding for header "${name}"`
2116
+ });
2117
+ }
2118
+ return resolved;
2119
+ });
2120
+ var resolveHeaderValues = (ctx, values) => resolveSecretBackedMap({
2121
+ values,
2122
+ getSecret: ctx.secrets.get,
2123
+ onMissing: (name) => new OpenApiOAuthError({
2124
+ message: `Secret not found for "${name}"`
2125
+ }),
2126
+ onError: (err, name) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? new OpenApiOAuthError({
2127
+ message: `Secret not found for "${name}"`
2128
+ }) : err
2129
+ }).pipe(
2130
+ Effect6.mapError(
2131
+ (err) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? new OpenApiOAuthError({ message: "Secret resolution failed" }) : err
2132
+ ),
2133
+ Effect6.map((resolved) => resolved ?? {})
2134
+ );
2135
+ var resolveOAuthConnectionId = (ctx, params) => Effect6.gen(function* () {
2136
+ const binding = yield* ctx.storage.resolveSourceBinding(
2137
+ params.sourceId,
2138
+ params.sourceScope,
2139
+ params.oauth2.connectionSlot
2140
+ );
2141
+ if (binding?.value.kind === "connection") {
2142
+ const connectionId = binding.value.connectionId;
2143
+ const connection = yield* ctx.connections.get(connectionId);
2144
+ return connection ? connectionId : null;
2145
+ }
2146
+ if (!params.legacyOAuth2?.connectionId) return null;
2147
+ const legacyConnection = yield* ctx.connections.get(params.legacyOAuth2.connectionId);
2148
+ return legacyConnection ? params.legacyOAuth2.connectionId : null;
2149
+ });
2150
+ var resolveSpecFetchCredentials = (ctx, credentials) => Effect6.gen(function* () {
2151
+ if (!credentials) return void 0;
2152
+ return {
2153
+ headers: yield* resolveHeaderValues(ctx, credentials.headers),
2154
+ queryParams: yield* resolveHeaderValues(ctx, credentials.queryParams)
2155
+ };
2156
+ });
2157
+ var toOpenApiSourceConfig = (namespace, config) => {
2158
+ const legacyHeaders = {};
2159
+ for (const [name, value] of Object.entries(config.headers ?? {})) {
2160
+ if (typeof value === "string" || !("kind" in value)) {
2161
+ legacyHeaders[name] = value;
2162
+ }
2163
+ }
2164
+ return {
2165
+ kind: "openapi",
2166
+ spec: config.spec,
2167
+ baseUrl: config.baseUrl,
2168
+ namespace,
2169
+ headers: headersToConfigValues(
2170
+ Object.keys(legacyHeaders).length > 0 ? legacyHeaders : void 0
2171
+ )
2172
+ };
2173
+ };
2174
+ var isHttpUrl = (s) => s.startsWith("http://") || s.startsWith("https://");
2175
+ var openApiPlugin = definePlugin((options) => {
2176
+ const httpClientLayer = options?.httpClientLayer ?? FetchHttpClient.layer;
2177
+ const rebuildSource = (ctx, input) => Effect6.gen(function* () {
2178
+ const doc = yield* parse(input.specText);
2179
+ const result = yield* extract(doc);
2180
+ const namespace = input.namespace ?? Option5.getOrElse(result.title, () => "api").toLowerCase().replace(/[^a-z0-9]+/g, "_");
2181
+ const hoistedDefs = {};
2182
+ if (doc.components?.schemas) {
2183
+ for (const [k, v] of Object.entries(doc.components.schemas)) {
2184
+ hoistedDefs[k] = normalizeOpenApiRefs(v);
2185
+ }
2186
+ }
2187
+ const baseUrl = input.baseUrl ?? resolveBaseUrl(result.servers);
2188
+ const canonicalHeaders = canonicalizeHeaders(input.headers);
2189
+ const canonicalOAuth2 = canonicalizeOAuth2(input.oauth2);
2190
+ const definitions = compileToolDefinitions(result.operations);
2191
+ const sourceName = input.name ?? Option5.getOrElse(result.title, () => namespace);
2192
+ const sourceConfig = {
2193
+ spec: input.specText,
2194
+ sourceUrl: input.sourceUrl,
2195
+ baseUrl,
2196
+ namespace: input.namespace,
2197
+ headers: canonicalHeaders.headers,
2198
+ queryParams: input.queryParams,
2199
+ specFetchCredentials: input.specFetchCredentials,
2200
+ oauth2: canonicalOAuth2.oauth2
2201
+ };
2202
+ const storedSource = {
2203
+ namespace,
2204
+ scope: input.scope,
2205
+ name: sourceName,
2206
+ config: sourceConfig
2207
+ };
2208
+ const storedOps = definitions.map((def) => ({
2209
+ toolId: `${namespace}.${def.toolPath}`,
2210
+ sourceId: namespace,
2211
+ binding: toBinding(def)
2212
+ }));
2213
+ yield* ctx.transaction(
2214
+ Effect6.gen(function* () {
2215
+ yield* ctx.storage.upsertSource(storedSource, storedOps);
2216
+ yield* ctx.core.sources.register({
2217
+ id: namespace,
2218
+ scope: input.scope,
2219
+ kind: "openapi",
2220
+ name: sourceName,
2221
+ url: baseUrl || void 0,
2222
+ canRemove: true,
2223
+ // `canRefresh` reflects whether we still know the
2224
+ // origin URL — sources added from raw spec text have
2225
+ // nothing to re-fetch, so refresh stays disabled.
2226
+ canRefresh: input.sourceUrl != null,
2227
+ canEdit: true,
2228
+ tools: definitions.map((def) => ({
2229
+ name: def.toolPath,
2230
+ description: descriptionFor(def),
2231
+ inputSchema: normalizeOpenApiRefs(Option5.getOrUndefined(def.operation.inputSchema)),
2232
+ outputSchema: normalizeOpenApiRefs(Option5.getOrUndefined(def.operation.outputSchema))
2233
+ }))
2234
+ });
2235
+ for (const binding of [...canonicalHeaders.bindings, ...canonicalOAuth2.bindings]) {
2236
+ yield* ctx.storage.setSourceBinding(
2237
+ new OpenApiSourceBindingInput({
2238
+ sourceId: namespace,
2239
+ sourceScope: ScopeId3.make(input.scope),
2240
+ scope: ScopeId3.make(input.scope),
2241
+ slot: binding.slot,
2242
+ value: binding.value
2243
+ })
2244
+ );
2245
+ }
2246
+ if (Object.keys(hoistedDefs).length > 0) {
2247
+ yield* ctx.core.definitions.register({
2248
+ sourceId: namespace,
2249
+ scope: input.scope,
2250
+ definitions: hoistedDefs
2251
+ });
2252
+ }
2253
+ })
2254
+ );
2255
+ return { sourceId: namespace, toolCount: definitions.length };
2256
+ });
2257
+ const refreshSourceInternal = (ctx, sourceId, scope) => Effect6.gen(function* () {
2258
+ const existing = yield* ctx.storage.getSource(sourceId, scope);
2259
+ if (!existing) return;
2260
+ const effective = yield* resolveEffectiveSourceConfig(ctx, existing);
2261
+ const resolvedConfig = effective.config;
2262
+ const sourceUrl = resolvedConfig.sourceUrl;
2263
+ if (!sourceUrl) return;
2264
+ const credentials = yield* resolveSpecFetchCredentials(
2265
+ ctx,
2266
+ resolvedConfig.specFetchCredentials
2267
+ );
2268
+ const specText = yield* resolveSpecText(sourceUrl, credentials).pipe(
2269
+ Effect6.provide(httpClientLayer)
2270
+ );
2271
+ yield* rebuildSource(ctx, {
2272
+ specText,
2273
+ scope,
2274
+ sourceUrl,
2275
+ name: existing.name,
2276
+ baseUrl: resolvedConfig.baseUrl,
2277
+ namespace: existing.namespace,
2278
+ headers: existing.legacy?.headers ?? existing.config.headers,
2279
+ queryParams: existing.config.queryParams,
2280
+ specFetchCredentials: resolvedConfig.specFetchCredentials,
2281
+ oauth2: existing.legacy?.oauth2 ?? existing.config.oauth2
2282
+ });
2283
+ });
2284
+ return {
2285
+ id: "openapi",
2286
+ schema: openapiSchema,
2287
+ storage: (deps) => makeDefaultOpenapiStore(deps),
2288
+ extension: (ctx) => {
2289
+ const addSpecInternal = (config) => Effect6.gen(function* () {
2290
+ const credentials = yield* resolveSpecFetchCredentials(ctx, config.specFetchCredentials);
2291
+ const specText = yield* resolveSpecText(config.spec, credentials).pipe(
2292
+ Effect6.provide(httpClientLayer)
2293
+ );
2294
+ return yield* rebuildSource(ctx, {
2295
+ specText,
2296
+ scope: config.scope,
2297
+ sourceUrl: isHttpUrl(config.spec) ? config.spec : void 0,
2298
+ name: config.name,
2299
+ baseUrl: config.baseUrl,
2300
+ namespace: config.namespace,
2301
+ headers: config.headers,
2302
+ queryParams: config.queryParams,
2303
+ specFetchCredentials: config.specFetchCredentials,
2304
+ oauth2: config.oauth2
2305
+ });
2306
+ });
2307
+ const configFile = options?.configFile;
2308
+ return {
2309
+ previewSpec: (input) => Effect6.gen(function* () {
2310
+ const previewInput = typeof input === "string" ? { spec: input } : input;
2311
+ const credentials = yield* resolveSpecFetchCredentials(
2312
+ ctx,
2313
+ previewInput.specFetchCredentials
2314
+ );
2315
+ const specText = yield* resolveSpecText(previewInput.spec, credentials).pipe(
2316
+ Effect6.provide(httpClientLayer)
2317
+ );
2318
+ return yield* previewSpec(specText).pipe(Effect6.provide(httpClientLayer));
2319
+ }),
2320
+ addSpec: (config) => Effect6.gen(function* () {
2321
+ const result = yield* addSpecInternal(config);
2322
+ if (configFile) {
2323
+ yield* configFile.upsertSource(toOpenApiSourceConfig(result.sourceId, config));
2324
+ }
2325
+ return result;
2326
+ }),
2327
+ removeSpec: (namespace, scope) => Effect6.gen(function* () {
2328
+ yield* ctx.transaction(
2329
+ Effect6.gen(function* () {
2330
+ yield* ctx.storage.removeSource(namespace, scope);
2331
+ yield* ctx.core.sources.unregister(namespace);
2332
+ })
2333
+ );
2334
+ if (configFile) {
2335
+ yield* configFile.removeSource(namespace);
2336
+ }
2337
+ }),
2338
+ getSource: (namespace, scope) => Effect6.gen(function* () {
2339
+ const source = yield* ctx.storage.getSource(namespace, scope);
2340
+ if (!source) return null;
2341
+ const effective = yield* resolveEffectiveSourceConfig(ctx, source);
2342
+ return {
2343
+ ...source,
2344
+ config: effective.config
2345
+ };
2346
+ }),
2347
+ updateSource: (namespace, scope, input) => Effect6.gen(function* () {
2348
+ const existing = yield* ctx.storage.getSource(namespace, scope);
2349
+ if (!existing) return;
2350
+ const canonicalHeaders = input.headers !== void 0 ? canonicalizeHeaders(input.headers) : existing.legacy?.headers ? canonicalizeHeaders(existing.legacy.headers) : null;
2351
+ const canonicalOAuth2 = input.oauth2 !== void 0 ? canonicalizeOAuth2(input.oauth2) : existing.legacy?.oauth2 ? canonicalizeOAuth2(existing.legacy.oauth2) : null;
2352
+ yield* ctx.storage.updateSourceMeta(namespace, scope, {
2353
+ name: input.name?.trim() || void 0,
2354
+ baseUrl: input.baseUrl,
2355
+ headers: canonicalHeaders?.headers,
2356
+ queryParams: input.queryParams,
2357
+ oauth2: canonicalOAuth2?.oauth2
2358
+ });
2359
+ for (const set of [canonicalHeaders?.bindings, canonicalOAuth2?.bindings]) {
2360
+ for (const binding of set ?? []) {
2361
+ yield* ctx.storage.setSourceBinding(
2362
+ new OpenApiSourceBindingInput({
2363
+ sourceId: namespace,
2364
+ sourceScope: ScopeId3.make(scope),
2365
+ scope: ScopeId3.make(scope),
2366
+ slot: binding.slot,
2367
+ value: binding.value
2368
+ })
2369
+ );
2370
+ }
2371
+ }
2372
+ }),
2373
+ listSourceBindings: (sourceId, sourceScope) => ctx.storage.listSourceBindings(sourceId, sourceScope),
2374
+ setSourceBinding: (input) => ctx.storage.setSourceBinding(input),
2375
+ removeSourceBinding: (sourceId, sourceScope, slot, scope) => ctx.storage.removeSourceBinding(sourceId, sourceScope, slot, scope)
2376
+ };
2377
+ },
2378
+ staticSources: (self) => [
2379
+ {
2380
+ id: "openapi",
2381
+ kind: "control",
2382
+ name: "OpenAPI",
2383
+ tools: [
2384
+ {
2385
+ name: "previewSpec",
2386
+ description: "Preview an OpenAPI document before adding it as a source",
2387
+ inputSchema: {
2388
+ type: "object",
2389
+ properties: {
2390
+ spec: { type: "string" },
2391
+ specFetchCredentials: { type: "object" }
2392
+ },
2393
+ required: ["spec"]
2394
+ },
2395
+ handler: ({ args }) => self.previewSpec(args)
2396
+ },
2397
+ {
2398
+ name: "addSource",
2399
+ description: "Add an OpenAPI source and register its operations as tools",
2400
+ inputSchema: {
2401
+ type: "object",
2402
+ properties: {
2403
+ spec: { type: "string" },
2404
+ name: { type: "string" },
2405
+ baseUrl: { type: "string" },
2406
+ namespace: { type: "string" },
2407
+ headers: { type: "object" },
2408
+ queryParams: { type: "object" },
2409
+ oauth2: { type: "object" },
2410
+ specFetchCredentials: { type: "object" }
2411
+ },
2412
+ required: ["spec"]
2413
+ },
2414
+ outputSchema: {
2415
+ type: "object",
2416
+ properties: {
2417
+ sourceId: { type: "string" },
2418
+ toolCount: { type: "number" }
2419
+ },
2420
+ required: ["sourceId", "toolCount"]
2421
+ },
2422
+ // Static-tool callers don't name a scope. Default to the
2423
+ // outermost scope in the executor's stack — for a single-
2424
+ // scope executor that's the only scope; for a per-user
2425
+ // stack `[user, org]` it writes at `org` so the source is
2426
+ // visible across every user.
2427
+ handler: ({ ctx, args }) => self.addSpec({
2428
+ ...args,
2429
+ scope: ctx.scopes.at(-1).id
2430
+ })
2431
+ }
2432
+ ]
2433
+ }
2434
+ ],
2435
+ invokeTool: ({ ctx, toolRow, args }) => Effect6.gen(function* () {
2436
+ const toolScope = toolRow.scope_id;
2437
+ const op = yield* ctx.storage.getOperationByToolId(toolRow.id, toolScope);
2438
+ if (!op) {
2439
+ return yield* Effect6.fail(
2440
+ new Error(`No OpenAPI operation found for tool "${toolRow.id}"`)
2441
+ );
2442
+ }
2443
+ const source = yield* ctx.storage.getSource(op.sourceId, toolScope);
2444
+ if (!source) {
2445
+ return yield* Effect6.fail(new Error(`No OpenAPI source found for "${op.sourceId}"`));
2446
+ }
2447
+ const effective = yield* resolveEffectiveSourceConfig(ctx, source);
2448
+ const config = effective.config;
2449
+ const resolvedHeaders = yield* resolveConfiguredHeaders(ctx, {
2450
+ sourceId: op.sourceId,
2451
+ sourceScope: effective.headersSource.scope,
2452
+ headers: config.headers ?? {},
2453
+ legacyHeaders: effective.headersSource.legacy?.headers
2454
+ }).pipe(Effect6.mapError((err) => new Error(err.message)));
2455
+ const resolvedQueryParams = yield* resolveHeaderValues(ctx, config.queryParams).pipe(
2456
+ Effect6.mapError((err) => new Error(err.message))
2457
+ );
2458
+ if (config.oauth2) {
2459
+ const connectionId = yield* resolveOAuthConnectionId(ctx, {
2460
+ sourceId: op.sourceId,
2461
+ sourceScope: effective.oauth2Source.scope,
2462
+ oauth2: config.oauth2,
2463
+ legacyOAuth2: effective.oauth2Source.legacy?.oauth2
2464
+ });
2465
+ if (!connectionId) {
2466
+ return yield* Effect6.fail(
2467
+ new Error(`OAuth configuration for "${op.sourceId}" is missing a connection binding`)
2468
+ );
2469
+ }
2470
+ const accessToken = yield* ctx.connections.accessToken(connectionId).pipe(
2471
+ Effect6.mapError(
2472
+ (err) => new Error(
2473
+ `OAuth connection resolution failed: ${"message" in err ? err.message : String(err)}`
2474
+ )
2475
+ )
2476
+ );
2477
+ resolvedHeaders.authorization = `Bearer ${accessToken}`;
2478
+ }
2479
+ const result = yield* invokeWithLayer(
2480
+ op.binding,
2481
+ args ?? {},
2482
+ config.baseUrl ?? "",
2483
+ resolvedHeaders,
2484
+ resolvedQueryParams,
2485
+ httpClientLayer
2486
+ );
2487
+ return result;
2488
+ }),
2489
+ resolveAnnotations: ({ ctx, sourceId, toolRows }) => Effect6.gen(function* () {
2490
+ const scopes = /* @__PURE__ */ new Set();
2491
+ for (const row of toolRows) {
2492
+ scopes.add(row.scope_id);
2493
+ }
2494
+ const entries = yield* Effect6.forEach(
2495
+ [...scopes],
2496
+ (scope) => Effect6.gen(function* () {
2497
+ const ops = yield* ctx.storage.listOperationsBySource(sourceId, scope);
2498
+ const byId = /* @__PURE__ */ new Map();
2499
+ for (const op of ops) byId.set(op.toolId, op.binding);
2500
+ return [scope, byId];
2501
+ }),
2502
+ { concurrency: "unbounded" }
2503
+ );
2504
+ const byScope = new Map(entries);
2505
+ const out = {};
2506
+ for (const row of toolRows) {
2507
+ const binding = byScope.get(row.scope_id)?.get(row.id);
2508
+ if (binding) {
2509
+ out[row.id] = annotationsForOperation(binding.method, binding.pathTemplate);
2510
+ }
2511
+ }
2512
+ return out;
2513
+ }),
2514
+ removeSource: ({ ctx, sourceId, scope }) => ctx.storage.removeSource(sourceId, scope),
2515
+ // Re-fetch the spec from its origin URL (captured at addSpec time)
2516
+ // and replay the same parse → extract → upsertSource → register
2517
+ // path used by addSpec. Sources without a stored URL surface a
2518
+ // typed `OpenApiParseError` — the executor only dispatches refresh
2519
+ // when `canRefresh: true`, so a raw-text source reaching here
2520
+ // means stale UI state, which is worth surfacing to the caller.
2521
+ refreshSource: ({ ctx, sourceId, scope }) => refreshSourceInternal(ctx, sourceId, scope),
2522
+ detect: ({ url }) => Effect6.gen(function* () {
2523
+ const trimmed = url.trim();
2524
+ if (!trimmed) return null;
2525
+ const parsed = yield* Effect6.try({
2526
+ try: () => new URL(trimmed),
2527
+ catch: (error) => error
2528
+ }).pipe(Effect6.option);
2529
+ if (Option5.isNone(parsed)) return null;
2530
+ const specText = yield* resolveSpecText(trimmed).pipe(
2531
+ Effect6.provide(httpClientLayer),
2532
+ Effect6.catch(() => Effect6.succeed(null))
2533
+ );
2534
+ if (specText === null) return null;
2535
+ const doc = yield* parse(specText).pipe(Effect6.catch(() => Effect6.succeed(null)));
2536
+ if (!doc) return null;
2537
+ const result = yield* extract(doc).pipe(Effect6.catch(() => Effect6.succeed(null)));
2538
+ if (!result) return null;
2539
+ const namespace = Option5.getOrElse(result.title, () => "api").toLowerCase().replace(/[^a-z0-9]+/g, "_");
2540
+ const name = Option5.getOrElse(result.title, () => namespace);
2541
+ return new SourceDetectionResult({
2542
+ kind: "openapi",
2543
+ confidence: "high",
2544
+ endpoint: trimmed,
2545
+ name,
2546
+ namespace
2547
+ });
2548
+ })
2549
+ };
2550
+ });
2551
+
2552
+ export {
2553
+ OpenApiParseError,
2554
+ OpenApiExtractionError,
2555
+ OpenApiInvocationError,
2556
+ OpenApiOAuthError,
2557
+ fetchSpecText,
2558
+ resolveSpecText,
2559
+ parse,
2560
+ DocResolver,
2561
+ substituteUrlVariables,
2562
+ resolveBaseUrl,
2563
+ preferredContent,
2564
+ OperationId,
2565
+ HttpMethod,
2566
+ ParameterLocation,
2567
+ OperationParameter,
2568
+ EncodingObject,
2569
+ MediaBinding,
2570
+ OperationRequestBody,
2571
+ ExtractedOperation,
2572
+ ServerVariable,
2573
+ ServerInfo,
2574
+ ExtractionResult,
2575
+ OperationBinding,
2576
+ OpenApiSourceBindingValue,
2577
+ OpenApiSourceBindingInput,
2578
+ OpenApiSourceBindingRef,
2579
+ OAuth2Auth,
2580
+ OAuth2SourceConfig,
2581
+ InvocationConfig,
2582
+ InvocationResult,
2583
+ extract,
2584
+ resolveHeaders,
2585
+ invoke,
2586
+ invokeWithLayer,
2587
+ annotationsForOperation,
2588
+ OAuth2AuthorizationCodeFlow,
2589
+ OAuth2ClientCredentialsFlow,
2590
+ OAuth2Flows,
2591
+ SecurityScheme,
2592
+ AuthStrategy,
2593
+ HeaderPreset,
2594
+ OAuth2Preset,
2595
+ PreviewOperation,
2596
+ SpecPreview,
2597
+ previewSpec,
2598
+ openapiSchema,
2599
+ makeDefaultOpenapiStore,
2600
+ openApiPlugin
2601
+ };
2602
+ //# sourceMappingURL=chunk-ZZ7TQ4JC.js.map