@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.
- package/README.md +24 -23
- package/dist/api/group.d.ts +94 -37
- package/dist/api/handlers.d.ts +2 -2
- package/dist/chunk-ZZ7TQ4JC.js +2602 -0
- package/dist/chunk-ZZ7TQ4JC.js.map +1 -0
- package/dist/core.js +46 -50
- package/dist/core.js.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/react/AddOpenApiSource.d.ts +13 -0
- package/dist/react/EditOpenApiSource.d.ts +2 -2
- package/dist/react/OpenApiSourceSummary.d.ts +3 -1
- package/dist/react/atoms.d.ts +129 -0
- package/dist/react/client.d.ts +421 -3
- package/dist/react/source-plugin.d.ts +1 -1
- package/dist/sdk/client-credentials-oauth.test.d.ts +1 -0
- package/dist/sdk/credential-status.d.ts +23 -0
- package/dist/sdk/credential-status.test.d.ts +1 -0
- package/dist/sdk/errors.d.ts +11 -10
- package/dist/sdk/form-urlencoded-body.test.d.ts +1 -0
- package/dist/sdk/index.d.ts +8 -10
- package/dist/sdk/invoke.d.ts +8 -17
- package/dist/sdk/multi-scope-bearer.test.d.ts +1 -0
- package/dist/sdk/multi-scope-oauth.test.d.ts +1 -0
- package/dist/sdk/non-json-body.test.d.ts +1 -0
- package/dist/sdk/oauth-refresh.test.d.ts +1 -0
- package/dist/sdk/openapi-utils.d.ts +35 -4
- package/dist/sdk/parse.d.ts +28 -4
- package/dist/sdk/plugin.d.ts +169 -22
- package/dist/sdk/preview-oauth2.test.d.ts +1 -0
- package/dist/sdk/preview.d.ts +89 -125
- package/dist/sdk/store.d.ts +201 -0
- package/dist/sdk/types.d.ts +234 -266
- package/package.json +11 -22
- package/dist/chunk-V3D5A6HA.js +0 -1224
- package/dist/chunk-V3D5A6HA.js.map +0 -1
- package/dist/promise.d.ts +0 -6
- package/dist/sdk/config-file-store.d.ts +0 -10
- package/dist/sdk/kv-operation-store.d.ts +0 -4
- package/dist/sdk/operation-store.d.ts +0 -35
- 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
|