@rafads/plugin-openapi 1.5.38
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 +83 -0
- package/dist/AddOpenApiIntegration-JPAEXE56.js +512 -0
- package/dist/AddOpenApiIntegration-JPAEXE56.js.map +1 -0
- package/dist/OpenApiAccountsPanel-GZFDSTAU.js +106 -0
- package/dist/OpenApiAccountsPanel-GZFDSTAU.js.map +1 -0
- package/dist/UpdateSpecSection-KUSNI44Q.js +106 -0
- package/dist/UpdateSpecSection-KUSNI44Q.js.map +1 -0
- package/dist/api/group.d.ts +239 -0
- package/dist/api/handlers.d.ts +7 -0
- package/dist/api/index.d.ts +237 -0
- package/dist/chunk-6V45G2XQ.js +199 -0
- package/dist/chunk-6V45G2XQ.js.map +1 -0
- package/dist/chunk-CURJJXB3.js +132 -0
- package/dist/chunk-CURJJXB3.js.map +1 -0
- package/dist/chunk-CXFO6TLJ.js +39 -0
- package/dist/chunk-CXFO6TLJ.js.map +1 -0
- package/dist/chunk-HTHEUUJA.js +2873 -0
- package/dist/chunk-HTHEUUJA.js.map +1 -0
- package/dist/chunk-IG6QTTZB.js +79 -0
- package/dist/chunk-IG6QTTZB.js.map +1 -0
- package/dist/chunk-O2LMSZ2Z.js +120 -0
- package/dist/chunk-O2LMSZ2Z.js.map +1 -0
- package/dist/chunk-OR27BCXQ.js +956 -0
- package/dist/chunk-OR27BCXQ.js.map +1 -0
- package/dist/chunk-R7TVM3XD.js +275 -0
- package/dist/chunk-R7TVM3XD.js.map +1 -0
- package/dist/client.js +38 -0
- package/dist/client.js.map +1 -0
- package/dist/core.js +331 -0
- package/dist/core.js.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/google/config.d.ts +34 -0
- package/dist/providers/google/discovery.d.ts +49 -0
- package/dist/providers/google/discovery.test.d.ts +1 -0
- package/dist/providers/google/index.d.ts +5 -0
- package/dist/providers/google/oauth-scopes.d.ts +3 -0
- package/dist/providers/google/oauth-scopes.test.d.ts +1 -0
- package/dist/providers/google/openapi-ownership-migration.d.ts +7 -0
- package/dist/providers/google/openapi-ownership-migration.test.d.ts +1 -0
- package/dist/providers/google/presets.d.ts +31 -0
- package/dist/providers/google/presets.test.d.ts +1 -0
- package/dist/providers/google/spec-format-adapter.d.ts +3 -0
- package/dist/providers/google/spec-format-adapter.test.d.ts +1 -0
- package/dist/providers/google.js +1446 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/microsoft/graph.d.ts +76 -0
- package/dist/providers/microsoft/graph.test.d.ts +1 -0
- package/dist/providers/microsoft/index.d.ts +3 -0
- package/dist/providers/microsoft/presets.d.ts +46 -0
- package/dist/providers/microsoft/presets.test.d.ts +1 -0
- package/dist/providers/microsoft/spec-format-adapter.d.ts +2 -0
- package/dist/providers/microsoft/spec-format-adapter.test.d.ts +1 -0
- package/dist/providers/microsoft.js +1056 -0
- package/dist/providers/microsoft.js.map +1 -0
- package/dist/react/AddOpenApiIntegration.d.ts +22 -0
- package/dist/react/AddOpenApiIntegration.test.d.ts +1 -0
- package/dist/react/OpenApiAccountsPanel.d.ts +6 -0
- package/dist/react/OpenApiIntegrationDetailsFields.d.ts +25 -0
- package/dist/react/UpdateSpecSection.d.ts +2 -0
- package/dist/react/atoms.d.ts +317 -0
- package/dist/react/auth-method-config.d.ts +18 -0
- package/dist/react/auth-method-config.test.d.ts +1 -0
- package/dist/react/client.d.ts +233 -0
- package/dist/react/index.d.ts +5 -0
- package/dist/react/integration-plugin.d.ts +6 -0
- package/dist/react/plugin-client.d.ts +2 -0
- package/dist/sdk/backing.d.ts +188 -0
- package/dist/sdk/client-credentials-oauth.test.d.ts +1 -0
- package/dist/sdk/compiled-spec-cache.test.d.ts +1 -0
- package/dist/sdk/config.d.ts +81 -0
- package/dist/sdk/configure.test.d.ts +1 -0
- package/dist/sdk/definitions.d.ts +57 -0
- package/dist/sdk/derive-auth.d.ts +11 -0
- package/dist/sdk/describe-auth-methods.test.d.ts +1 -0
- package/dist/sdk/errors.d.ts +45 -0
- package/dist/sdk/extract.d.ts +157 -0
- package/dist/sdk/extract.test.d.ts +1 -0
- package/dist/sdk/form-urlencoded-body.test.d.ts +1 -0
- package/dist/sdk/index.d.ts +19 -0
- package/dist/sdk/index.test.d.ts +1 -0
- package/dist/sdk/invoke.d.ts +150 -0
- package/dist/sdk/migrate-config.d.ts +4 -0
- package/dist/sdk/migrate-config.test.d.ts +1 -0
- 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/ndjson-output-migration.d.ts +13 -0
- package/dist/sdk/ndjson-output-migration.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 +65 -0
- package/dist/sdk/output-schema-migration.d.ts +21 -0
- package/dist/sdk/output-schema-migration.test.d.ts +1 -0
- package/dist/sdk/parse.d.ts +33 -0
- package/dist/sdk/parse.test.d.ts +1 -0
- package/dist/sdk/plugin.d.ts +109 -0
- package/dist/sdk/plugin.test.d.ts +1 -0
- package/dist/sdk/presets.d.ts +15 -0
- package/dist/sdk/preview-oauth2.test.d.ts +1 -0
- package/dist/sdk/preview.d.ts +514 -0
- package/dist/sdk/query-serialization.test.d.ts +1 -0
- package/dist/sdk/real-specs.test.d.ts +1 -0
- package/dist/sdk/request-user-agent.test.d.ts +1 -0
- package/dist/sdk/response-headers-timeout.test.d.ts +1 -0
- package/dist/sdk/server-url-resolution.test.d.ts +1 -0
- package/dist/sdk/spec-blob-migration.d.ts +7 -0
- package/dist/sdk/spec-blob.test.d.ts +1 -0
- package/dist/sdk/spec-format.d.ts +36 -0
- package/dist/sdk/spec-format.test.d.ts +1 -0
- package/dist/sdk/split.d.ts +84 -0
- package/dist/sdk/store.d.ts +49 -0
- package/dist/sdk/store.test.d.ts +1 -0
- package/dist/sdk/streaming-response.test.d.ts +1 -0
- package/dist/sdk/tool-row-projection.test.d.ts +1 -0
- package/dist/sdk/types.d.ts +352 -0
- package/dist/sdk/upstream-failures.test.d.ts +1 -0
- package/dist/sdk/usage-scope-isolation.test.d.ts +1 -0
- package/dist/testing/index.d.ts +177 -0
- package/dist/testing.js +454 -0
- package/dist/testing.js.map +1 -0
- package/dist/testing.test.d.ts +1 -0
- package/package.json +103 -0
|
@@ -0,0 +1,2873 @@
|
|
|
1
|
+
import {
|
|
2
|
+
decodeOpenApiIntegrationConfig,
|
|
3
|
+
parseEntry,
|
|
4
|
+
parseHead,
|
|
5
|
+
parseSmallComponents,
|
|
6
|
+
renderAuthTemplate,
|
|
7
|
+
requiredTemplateVariables,
|
|
8
|
+
structuralSplit
|
|
9
|
+
} from "./chunk-R7TVM3XD.js";
|
|
10
|
+
import {
|
|
11
|
+
OpenApiExtractionError,
|
|
12
|
+
OpenApiInvocationError,
|
|
13
|
+
OpenApiParseError
|
|
14
|
+
} from "./chunk-CXFO6TLJ.js";
|
|
15
|
+
|
|
16
|
+
// src/sdk/parse.ts
|
|
17
|
+
import { Duration, Effect, Schema } from "effect";
|
|
18
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
19
|
+
import { JSON_SCHEMA, load as parseYamlDocument } from "js-yaml";
|
|
20
|
+
var OpenApiExtractionErrorFromParse = class extends OpenApiExtractionError {
|
|
21
|
+
};
|
|
22
|
+
var fetchSpecText = Effect.fn("OpenApi.fetchSpecText")(function* (url, credentials) {
|
|
23
|
+
const client = yield* HttpClient.HttpClient;
|
|
24
|
+
const requestUrl = new URL(url);
|
|
25
|
+
for (const [name, value] of Object.entries(credentials?.queryParams ?? {})) {
|
|
26
|
+
requestUrl.searchParams.set(name, value);
|
|
27
|
+
}
|
|
28
|
+
let request = HttpClientRequest.get(requestUrl.toString()).pipe(
|
|
29
|
+
HttpClientRequest.setHeader("Accept", "application/json, application/yaml, text/yaml, */*")
|
|
30
|
+
);
|
|
31
|
+
for (const [name, value] of Object.entries(credentials?.headers ?? {})) {
|
|
32
|
+
request = HttpClientRequest.setHeader(request, name, value);
|
|
33
|
+
}
|
|
34
|
+
const response = yield* client.execute(request).pipe(
|
|
35
|
+
Effect.timeout(Duration.seconds(60)),
|
|
36
|
+
Effect.mapError(
|
|
37
|
+
(_cause) => new OpenApiParseError({
|
|
38
|
+
message: "Failed to fetch OpenAPI document"
|
|
39
|
+
})
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
if (response.status < 200 || response.status >= 300) {
|
|
43
|
+
return yield* new OpenApiParseError({
|
|
44
|
+
message: `Failed to fetch OpenAPI document: HTTP ${response.status}`
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const specText = yield* response.text.pipe(
|
|
48
|
+
Effect.mapError(
|
|
49
|
+
(_cause) => new OpenApiParseError({
|
|
50
|
+
message: "Failed to read OpenAPI document body"
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
return specText;
|
|
55
|
+
});
|
|
56
|
+
var resolveSpecText = (input, credentials) => input.startsWith("http://") || input.startsWith("https://") ? fetchSpecText(input, credentials) : Effect.succeed(input);
|
|
57
|
+
var parse = Effect.fn("OpenApi.parse")(function* (text) {
|
|
58
|
+
const api = yield* parseTextToObject(text);
|
|
59
|
+
if (!isOpenApi3(api)) {
|
|
60
|
+
return yield* new OpenApiExtractionErrorFromParse({
|
|
61
|
+
message: "Only OpenAPI 3.x documents are supported. Swagger 2.x documents should be converted first."
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return api;
|
|
65
|
+
});
|
|
66
|
+
var isOpenApi3 = (doc) => "openapi" in doc && typeof doc.openapi === "string" && doc.openapi.startsWith("3.");
|
|
67
|
+
var parseTextToObject = (text) => Effect.gen(function* () {
|
|
68
|
+
const trimmed = text.trim();
|
|
69
|
+
if (trimmed.length === 0) {
|
|
70
|
+
return yield* new OpenApiParseError({
|
|
71
|
+
message: "OpenAPI document is empty"
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const parsed = yield* parseJsonLike(trimmed).pipe(
|
|
75
|
+
Effect.mapError(
|
|
76
|
+
() => new OpenApiParseError({
|
|
77
|
+
message: "Failed to parse OpenAPI document"
|
|
78
|
+
})
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
82
|
+
return yield* new OpenApiParseError({
|
|
83
|
+
message: "OpenAPI document must parse to an object"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return parsed;
|
|
87
|
+
});
|
|
88
|
+
var parseJsonText = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
|
|
89
|
+
var parseJsonLike = (text) => {
|
|
90
|
+
const parseYaml = Effect.try({
|
|
91
|
+
try: () => parseYamlDocument(text, { json: true, schema: JSON_SCHEMA }),
|
|
92
|
+
catch: () => "YamlParseFailed"
|
|
93
|
+
});
|
|
94
|
+
if (!text.startsWith("{") && !text.startsWith("[")) return parseYaml;
|
|
95
|
+
return parseJsonText(text).pipe(Effect.catch(() => parseYaml));
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/sdk/openapi-utils.ts
|
|
99
|
+
var DocResolver = class {
|
|
100
|
+
constructor(doc) {
|
|
101
|
+
this.doc = doc;
|
|
102
|
+
}
|
|
103
|
+
doc;
|
|
104
|
+
/** Resolve a value that might be a $ref, returning the resolved object */
|
|
105
|
+
resolve(value) {
|
|
106
|
+
if (isRef(value)) {
|
|
107
|
+
const resolved = this.resolvePointer(value.$ref);
|
|
108
|
+
return resolved;
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
resolvePointer(ref) {
|
|
113
|
+
if (!ref.startsWith("#/")) return null;
|
|
114
|
+
const segments = ref.slice(2).split("/");
|
|
115
|
+
let current = this.doc;
|
|
116
|
+
for (const segment of segments) {
|
|
117
|
+
if (typeof current !== "object" || current === null) return null;
|
|
118
|
+
current = current[segment];
|
|
119
|
+
}
|
|
120
|
+
return current;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var isRef = (value) => typeof value === "object" && value !== null && "$ref" in value;
|
|
124
|
+
var substituteUrlVariables = (url, values) => {
|
|
125
|
+
let out = url;
|
|
126
|
+
for (const [name, value] of Object.entries(values)) {
|
|
127
|
+
out = out.replaceAll(`{${name}}`, value);
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
};
|
|
131
|
+
var resolveServerUrl = (templateUrl, variables, overrides) => {
|
|
132
|
+
const values = {};
|
|
133
|
+
for (const [name, v] of Object.entries(variables ?? {})) values[name] = v.default;
|
|
134
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
135
|
+
if (value) values[name] = value;
|
|
136
|
+
}
|
|
137
|
+
return substituteUrlVariables(templateUrl, values);
|
|
138
|
+
};
|
|
139
|
+
var declaredContents = (content) => {
|
|
140
|
+
if (!content) return [];
|
|
141
|
+
return Object.entries(content).map(([mediaType, media]) => ({ mediaType, media }));
|
|
142
|
+
};
|
|
143
|
+
var preferredContent = (content) => {
|
|
144
|
+
const first = declaredContents(content)[0];
|
|
145
|
+
return first ? first : void 0;
|
|
146
|
+
};
|
|
147
|
+
var preferredResponseContent = (content) => {
|
|
148
|
+
if (!content) return void 0;
|
|
149
|
+
const entries = Object.entries(content);
|
|
150
|
+
const pick = entries.find(([mt]) => mt === "application/json") ?? entries.find(([mt]) => mt.toLowerCase().includes("+json")) ?? entries.find(([mt]) => mt.toLowerCase().includes("json")) ?? entries[0];
|
|
151
|
+
return pick ? { mediaType: pick[0], media: pick[1] } : void 0;
|
|
152
|
+
};
|
|
153
|
+
var normalizeMediaType = (mediaType) => mediaType?.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
154
|
+
var NDJSON_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
155
|
+
"application/stream+json",
|
|
156
|
+
"application/x-ndjson",
|
|
157
|
+
"application/jsonl"
|
|
158
|
+
]);
|
|
159
|
+
var isNdjsonMediaType = (mediaType) => NDJSON_MEDIA_TYPES.has(normalizeMediaType(mediaType));
|
|
160
|
+
var ndjsonArrayOutputSchema = (lineSchema) => ({
|
|
161
|
+
type: "array",
|
|
162
|
+
items: lineSchema,
|
|
163
|
+
description: "Parsed NDJSON stream: one array item per line. The stream may be truncated (`x-executor-stream: truncated` response header); a body that is not valid NDJSON is returned as the raw string instead."
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// src/sdk/types.ts
|
|
167
|
+
import { Schema as Schema2 } from "effect";
|
|
168
|
+
import { AuthTemplateSlug } from "@rafads/sdk/shared";
|
|
169
|
+
import {
|
|
170
|
+
apiKeyMethodFromAuthTemplate,
|
|
171
|
+
isApiKeyAuthTemplate
|
|
172
|
+
} from "@rafads/sdk/http-auth";
|
|
173
|
+
import { TOKEN_VARIABLE } from "@rafads/sdk/http-auth";
|
|
174
|
+
var normalizeOpenApiAuthInputs = (inputs) => inputs.map((input) => {
|
|
175
|
+
if (!isApiKeyAuthTemplate(input)) {
|
|
176
|
+
return { ...input, slug: AuthTemplateSlug.make(input.slug) };
|
|
177
|
+
}
|
|
178
|
+
const method = apiKeyMethodFromAuthTemplate(input);
|
|
179
|
+
return { ...method, slug: method.slug ?? "" };
|
|
180
|
+
});
|
|
181
|
+
var OperationId = Schema2.String.pipe(Schema2.brand("OperationId"));
|
|
182
|
+
var HttpMethod = Schema2.Literals([
|
|
183
|
+
"get",
|
|
184
|
+
"put",
|
|
185
|
+
"post",
|
|
186
|
+
"delete",
|
|
187
|
+
"patch",
|
|
188
|
+
"head",
|
|
189
|
+
"options",
|
|
190
|
+
"trace"
|
|
191
|
+
]);
|
|
192
|
+
var ParameterLocation = Schema2.Literals(["path", "query", "header", "cookie"]);
|
|
193
|
+
var OperationParameter = Schema2.Struct({
|
|
194
|
+
name: Schema2.String,
|
|
195
|
+
location: ParameterLocation,
|
|
196
|
+
required: Schema2.Boolean,
|
|
197
|
+
schema: Schema2.OptionFromOptional(Schema2.Unknown),
|
|
198
|
+
style: Schema2.OptionFromOptional(Schema2.String),
|
|
199
|
+
explode: Schema2.OptionFromOptional(Schema2.Boolean),
|
|
200
|
+
allowReserved: Schema2.OptionFromOptional(Schema2.Boolean),
|
|
201
|
+
description: Schema2.OptionFromOptional(Schema2.String)
|
|
202
|
+
});
|
|
203
|
+
var EncodingObject = Schema2.Struct({
|
|
204
|
+
contentType: Schema2.OptionFromOptional(Schema2.String),
|
|
205
|
+
style: Schema2.OptionFromOptional(Schema2.String),
|
|
206
|
+
explode: Schema2.OptionFromOptional(Schema2.Boolean),
|
|
207
|
+
allowReserved: Schema2.OptionFromOptional(Schema2.Boolean)
|
|
208
|
+
});
|
|
209
|
+
var MediaBinding = Schema2.Struct({
|
|
210
|
+
contentType: Schema2.String,
|
|
211
|
+
schema: Schema2.OptionFromOptional(Schema2.Unknown),
|
|
212
|
+
encoding: Schema2.OptionFromOptional(Schema2.Record(Schema2.String, EncodingObject))
|
|
213
|
+
});
|
|
214
|
+
var OperationRequestBody = Schema2.Struct({
|
|
215
|
+
required: Schema2.Boolean,
|
|
216
|
+
/** Default media type — first declared in spec order (not JSON-first).
|
|
217
|
+
* Used when the caller does not override via the tool's `contentType` arg. */
|
|
218
|
+
contentType: Schema2.String,
|
|
219
|
+
/** Schema of the default media type. Kept for backward compat with stored
|
|
220
|
+
* bindings from before `contents` was added. */
|
|
221
|
+
schema: Schema2.OptionFromOptional(Schema2.Unknown),
|
|
222
|
+
/** All declared media types in spec order. Populated by `extract.ts`
|
|
223
|
+
* going forward; older persisted bindings may have this unset and will
|
|
224
|
+
* fall back to `{contentType, schema}`. */
|
|
225
|
+
contents: Schema2.OptionFromOptional(Schema2.Array(MediaBinding))
|
|
226
|
+
});
|
|
227
|
+
var OperationFileHint = Schema2.Struct({
|
|
228
|
+
kind: Schema2.Literals(["binaryResponse", "byteField"]),
|
|
229
|
+
mimeType: Schema2.OptionFromOptional(Schema2.String),
|
|
230
|
+
dataField: Schema2.OptionFromOptional(Schema2.String),
|
|
231
|
+
sizeField: Schema2.OptionFromOptional(Schema2.String),
|
|
232
|
+
encoding: Schema2.OptionFromOptional(Schema2.Literals(["base64", "base64url"]))
|
|
233
|
+
});
|
|
234
|
+
var OperationResponseBody = Schema2.Struct({
|
|
235
|
+
contentType: Schema2.String,
|
|
236
|
+
schema: Schema2.OptionFromOptional(Schema2.Unknown),
|
|
237
|
+
fileHint: Schema2.OptionFromOptional(OperationFileHint)
|
|
238
|
+
});
|
|
239
|
+
var ServerVariable = Schema2.Struct({
|
|
240
|
+
default: Schema2.String,
|
|
241
|
+
enum: Schema2.OptionFromOptional(Schema2.Array(Schema2.String)),
|
|
242
|
+
description: Schema2.OptionFromOptional(Schema2.String)
|
|
243
|
+
});
|
|
244
|
+
var ServerInfo = Schema2.Struct({
|
|
245
|
+
url: Schema2.String,
|
|
246
|
+
description: Schema2.OptionFromOptional(Schema2.String),
|
|
247
|
+
variables: Schema2.OptionFromOptional(Schema2.Record(Schema2.String, ServerVariable))
|
|
248
|
+
});
|
|
249
|
+
var ExtractedOperation = Schema2.Struct({
|
|
250
|
+
operationId: OperationId,
|
|
251
|
+
toolPath: Schema2.OptionFromOptional(Schema2.String),
|
|
252
|
+
method: HttpMethod,
|
|
253
|
+
servers: Schema2.Array(ServerInfo),
|
|
254
|
+
pathTemplate: Schema2.String,
|
|
255
|
+
summary: Schema2.OptionFromOptional(Schema2.String),
|
|
256
|
+
description: Schema2.OptionFromOptional(Schema2.String),
|
|
257
|
+
tags: Schema2.Array(Schema2.String),
|
|
258
|
+
parameters: Schema2.Array(OperationParameter),
|
|
259
|
+
requestBody: Schema2.OptionFromOptional(OperationRequestBody),
|
|
260
|
+
responseBody: Schema2.OptionFromOptional(OperationResponseBody),
|
|
261
|
+
inputSchema: Schema2.OptionFromOptional(Schema2.Unknown),
|
|
262
|
+
outputSchema: Schema2.OptionFromOptional(Schema2.Unknown),
|
|
263
|
+
deprecated: Schema2.Boolean,
|
|
264
|
+
/** OAuth scope requirements from `security`, alternatives preserved: each
|
|
265
|
+
* inner array is one acceptable Security Requirement Object's scope set
|
|
266
|
+
* (sorted, deduped); the outer array is an OR across alternatives. An
|
|
267
|
+
* absent operation `security` inherits the document default; an explicit
|
|
268
|
+
* `security: []` (auth disabled) and a scope-less declaration both omit
|
|
269
|
+
* the field. */
|
|
270
|
+
requiredScopeAlternatives: Schema2.optional(Schema2.Array(Schema2.Array(Schema2.String)))
|
|
271
|
+
});
|
|
272
|
+
var ExtractionResult = Schema2.Struct({
|
|
273
|
+
title: Schema2.OptionFromOptional(Schema2.String),
|
|
274
|
+
/** The spec's `info.description` — the author's own summary of the API. */
|
|
275
|
+
description: Schema2.OptionFromOptional(Schema2.String),
|
|
276
|
+
version: Schema2.OptionFromOptional(Schema2.String),
|
|
277
|
+
servers: Schema2.Array(ServerInfo),
|
|
278
|
+
operations: Schema2.Array(ExtractedOperation)
|
|
279
|
+
});
|
|
280
|
+
var OperationBinding = Schema2.Struct({
|
|
281
|
+
method: HttpMethod,
|
|
282
|
+
servers: Schema2.optional(Schema2.Array(ServerInfo)),
|
|
283
|
+
pathTemplate: Schema2.String,
|
|
284
|
+
parameters: Schema2.Array(OperationParameter),
|
|
285
|
+
requestBody: Schema2.OptionFromOptional(OperationRequestBody),
|
|
286
|
+
responseBody: Schema2.OptionFromOptional(OperationResponseBody),
|
|
287
|
+
/** Declared OAuth scope alternatives (see
|
|
288
|
+
* ExtractedOperation.requiredScopeAlternatives), persisted with the
|
|
289
|
+
* binding so the invoke path can annotate a scope-insufficient rejection
|
|
290
|
+
* with exactly what the operation needs. Optional so bindings stored
|
|
291
|
+
* before this field existed keep decoding. */
|
|
292
|
+
requiredScopeAlternatives: Schema2.optional(Schema2.Array(Schema2.Array(Schema2.String)))
|
|
293
|
+
});
|
|
294
|
+
var InvocationResult = Schema2.Struct({
|
|
295
|
+
status: Schema2.Number,
|
|
296
|
+
headers: Schema2.Record(Schema2.String, Schema2.String),
|
|
297
|
+
data: Schema2.NullOr(Schema2.Unknown),
|
|
298
|
+
error: Schema2.NullOr(Schema2.Unknown)
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// src/sdk/extract.ts
|
|
302
|
+
import { Effect as Effect2, Option as Option2 } from "effect";
|
|
303
|
+
|
|
304
|
+
// src/sdk/definitions.ts
|
|
305
|
+
import { Option } from "effect";
|
|
306
|
+
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);
|
|
307
|
+
var normalizeWord = (value) => value.toLowerCase();
|
|
308
|
+
var toCamelCase = (value) => {
|
|
309
|
+
const words = splitWords(value).map(normalizeWord);
|
|
310
|
+
if (words.length === 0) return "tool";
|
|
311
|
+
const [first, ...rest] = words;
|
|
312
|
+
return `${first}${rest.map((p) => `${p[0]?.toUpperCase() ?? ""}${p.slice(1)}`).join("")}`;
|
|
313
|
+
};
|
|
314
|
+
var toPascalCase = (value) => {
|
|
315
|
+
const camel = toCamelCase(value);
|
|
316
|
+
return `${camel[0]?.toUpperCase() ?? ""}${camel.slice(1)}`;
|
|
317
|
+
};
|
|
318
|
+
var VERSION_SEGMENT_REGEX = /^v\d+(?:[._-]\d+)?$/i;
|
|
319
|
+
var IGNORED_PATH_SEGMENTS = /* @__PURE__ */ new Set(["api"]);
|
|
320
|
+
var pathSegmentsFromTemplate = (pathTemplate) => pathTemplate.split("/").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
321
|
+
var isPathParameterSegment = (segment) => segment.startsWith("{") && segment.endsWith("}");
|
|
322
|
+
var normalizeGroupSegment = (value) => {
|
|
323
|
+
const candidate = value?.trim();
|
|
324
|
+
if (!candidate) return null;
|
|
325
|
+
return toCamelCase(candidate);
|
|
326
|
+
};
|
|
327
|
+
var deriveVersionSegment = (pathTemplate) => pathSegmentsFromTemplate(pathTemplate).map((s) => s.toLowerCase()).find((s) => VERSION_SEGMENT_REGEX.test(s));
|
|
328
|
+
var derivePathGroup = (pathTemplate) => {
|
|
329
|
+
for (const segment of pathSegmentsFromTemplate(pathTemplate)) {
|
|
330
|
+
const lower = segment.toLowerCase();
|
|
331
|
+
if (VERSION_SEGMENT_REGEX.test(lower)) continue;
|
|
332
|
+
if (IGNORED_PATH_SEGMENTS.has(lower)) continue;
|
|
333
|
+
if (isPathParameterSegment(segment)) continue;
|
|
334
|
+
return normalizeGroupSegment(segment) ?? "root";
|
|
335
|
+
}
|
|
336
|
+
return "root";
|
|
337
|
+
};
|
|
338
|
+
var splitOperationIdSegments = (value) => value.split(/[/.]+/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
339
|
+
var deriveLeafSeed = (operationId, group) => {
|
|
340
|
+
const segments = splitOperationIdSegments(operationId);
|
|
341
|
+
if (segments.length > 1) {
|
|
342
|
+
const [first, ...rest] = segments;
|
|
343
|
+
if ((normalizeGroupSegment(first) ?? first) === group && rest.length > 0) {
|
|
344
|
+
return rest.join(" ");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return operationId;
|
|
348
|
+
};
|
|
349
|
+
var fallbackLeafSeed = (method, pathTemplate, group) => {
|
|
350
|
+
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);
|
|
351
|
+
const segmentSuffix = relevantSegments.map((s) => toPascalCase(s)).join("");
|
|
352
|
+
return `${method}${segmentSuffix || "Operation"}`;
|
|
353
|
+
};
|
|
354
|
+
var deriveLeaf = (operationId, method, pathTemplate, group) => {
|
|
355
|
+
const preferred = toCamelCase(deriveLeafSeed(operationId, group));
|
|
356
|
+
if (preferred.length > 0 && preferred !== group) return preferred;
|
|
357
|
+
return toCamelCase(fallbackLeafSeed(method, pathTemplate, group));
|
|
358
|
+
};
|
|
359
|
+
var resolveCollisions = (definitions) => {
|
|
360
|
+
const staged = definitions.map((d) => ({ ...d }));
|
|
361
|
+
const applyFactory = (items, factory) => {
|
|
362
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
363
|
+
for (const item of items) {
|
|
364
|
+
const bucket = byPath.get(item.toolPath) ?? [];
|
|
365
|
+
bucket.push(item);
|
|
366
|
+
byPath.set(item.toolPath, bucket);
|
|
367
|
+
}
|
|
368
|
+
for (const bucket of byPath.values()) {
|
|
369
|
+
if (bucket.length < 2) continue;
|
|
370
|
+
for (const d of bucket) {
|
|
371
|
+
d.toolPath = factory(d);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
applyFactory(
|
|
376
|
+
staged,
|
|
377
|
+
(d) => d.versionSegment ? `${d.group}.${d.versionSegment}.${d.leaf}` : d.toolPath
|
|
378
|
+
);
|
|
379
|
+
applyFactory(staged, (d) => {
|
|
380
|
+
const prefix = d.versionSegment ? `${d.group}.${d.versionSegment}` : d.group;
|
|
381
|
+
return `${prefix}.${d.leaf}${toPascalCase(d.method)}`;
|
|
382
|
+
});
|
|
383
|
+
applyFactory(staged, (d) => {
|
|
384
|
+
const prefix = d.versionSegment ? `${d.group}.${d.versionSegment}` : d.group;
|
|
385
|
+
return `${prefix}.${d.leaf}${toPascalCase(d.method)}${d.operationHash.slice(0, 8)}`;
|
|
386
|
+
});
|
|
387
|
+
return staged.map((d) => ({
|
|
388
|
+
toolPath: d.toolPath,
|
|
389
|
+
group: d.group,
|
|
390
|
+
leaf: d.leaf,
|
|
391
|
+
operationIndex: d.operationIndex
|
|
392
|
+
}));
|
|
393
|
+
};
|
|
394
|
+
var stableHash = (value) => {
|
|
395
|
+
const str = JSON.stringify(value, Object.keys(value).sort());
|
|
396
|
+
let hash = 0;
|
|
397
|
+
for (let i = 0; i < str.length; i++) {
|
|
398
|
+
hash = (hash << 5) - hash + str.charCodeAt(i) | 0;
|
|
399
|
+
}
|
|
400
|
+
return Math.abs(hash).toString(36).padStart(8, "0");
|
|
401
|
+
};
|
|
402
|
+
var planToolPaths = (inputs) => {
|
|
403
|
+
const raw = inputs.map((op, index) => {
|
|
404
|
+
const operationId = op.operationId;
|
|
405
|
+
const operationHash = stableHash({
|
|
406
|
+
method: op.method,
|
|
407
|
+
path: op.pathTemplate,
|
|
408
|
+
operationId
|
|
409
|
+
});
|
|
410
|
+
const versionSegment = deriveVersionSegment(op.pathTemplate);
|
|
411
|
+
if (op.explicitToolPath) {
|
|
412
|
+
const [group2 = "root", ...leafParts] = op.explicitToolPath.split(".").filter(Boolean);
|
|
413
|
+
const leaf2 = leafParts.join(".") || group2;
|
|
414
|
+
return {
|
|
415
|
+
toolPath: op.explicitToolPath,
|
|
416
|
+
group: group2,
|
|
417
|
+
leaf: leaf2,
|
|
418
|
+
versionSegment,
|
|
419
|
+
method: op.method,
|
|
420
|
+
operationHash,
|
|
421
|
+
operationIndex: index
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const group = normalizeGroupSegment(op.tag0) ?? derivePathGroup(op.pathTemplate);
|
|
425
|
+
const leaf = deriveLeaf(operationId, op.method, op.pathTemplate, group);
|
|
426
|
+
return {
|
|
427
|
+
toolPath: `${group}.${leaf}`,
|
|
428
|
+
group,
|
|
429
|
+
leaf,
|
|
430
|
+
versionSegment,
|
|
431
|
+
method: op.method,
|
|
432
|
+
operationHash,
|
|
433
|
+
operationIndex: index
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
return resolveCollisions(raw).sort((a, b) => a.toolPath.localeCompare(b.toolPath));
|
|
437
|
+
};
|
|
438
|
+
var compileToolDefinitions = (operations) => {
|
|
439
|
+
const plans = planToolPaths(
|
|
440
|
+
operations.map((op) => ({
|
|
441
|
+
operationId: op.operationId,
|
|
442
|
+
explicitToolPath: Option.getOrUndefined(op.toolPath),
|
|
443
|
+
method: op.method,
|
|
444
|
+
pathTemplate: op.pathTemplate,
|
|
445
|
+
tag0: op.tags[0]
|
|
446
|
+
}))
|
|
447
|
+
);
|
|
448
|
+
return plans.map((plan) => ({
|
|
449
|
+
toolPath: plan.toolPath,
|
|
450
|
+
group: plan.group,
|
|
451
|
+
leaf: plan.leaf,
|
|
452
|
+
operationIndex: plan.operationIndex,
|
|
453
|
+
operation: operations[plan.operationIndex]
|
|
454
|
+
}));
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// src/sdk/extract.ts
|
|
458
|
+
var HTTP_METHODS = [
|
|
459
|
+
"get",
|
|
460
|
+
"put",
|
|
461
|
+
"post",
|
|
462
|
+
"delete",
|
|
463
|
+
"patch",
|
|
464
|
+
"head",
|
|
465
|
+
"options",
|
|
466
|
+
"trace"
|
|
467
|
+
];
|
|
468
|
+
var VALID_PARAM_LOCATIONS = /* @__PURE__ */ new Set(["path", "query", "header", "cookie"]);
|
|
469
|
+
var extractParameters = (pathItem, operation, r) => {
|
|
470
|
+
const merged = /* @__PURE__ */ new Map();
|
|
471
|
+
for (const raw of pathItem.parameters ?? []) {
|
|
472
|
+
const p = r.resolve(raw);
|
|
473
|
+
if (!p) continue;
|
|
474
|
+
merged.set(`${p.in}:${p.name}`, p);
|
|
475
|
+
}
|
|
476
|
+
for (const raw of operation.parameters ?? []) {
|
|
477
|
+
const p = r.resolve(raw);
|
|
478
|
+
if (!p) continue;
|
|
479
|
+
merged.set(`${p.in}:${p.name}`, p);
|
|
480
|
+
}
|
|
481
|
+
return [...merged.values()].filter((p) => VALID_PARAM_LOCATIONS.has(p.in)).map(
|
|
482
|
+
(p) => OperationParameter.make({
|
|
483
|
+
name: p.name,
|
|
484
|
+
location: p.in,
|
|
485
|
+
required: p.in === "path" ? true : p.required === true,
|
|
486
|
+
schema: Option2.fromNullishOr(p.schema),
|
|
487
|
+
style: Option2.fromNullishOr(p.style),
|
|
488
|
+
explode: Option2.fromNullishOr(p.explode),
|
|
489
|
+
allowReserved: Option2.fromNullishOr("allowReserved" in p ? p.allowReserved : void 0),
|
|
490
|
+
description: Option2.fromNullishOr(p.description)
|
|
491
|
+
})
|
|
492
|
+
);
|
|
493
|
+
};
|
|
494
|
+
var buildEncodingRecord = (encoding) => {
|
|
495
|
+
if (!encoding) return void 0;
|
|
496
|
+
const out = {};
|
|
497
|
+
for (const [prop, raw] of Object.entries(encoding)) {
|
|
498
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
499
|
+
const e = raw;
|
|
500
|
+
out[prop] = EncodingObject.make({
|
|
501
|
+
contentType: Option2.fromNullishOr(e.contentType),
|
|
502
|
+
style: Option2.fromNullishOr(e.style),
|
|
503
|
+
explode: Option2.fromNullishOr(e.explode),
|
|
504
|
+
allowReserved: Option2.fromNullishOr(e.allowReserved)
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
508
|
+
};
|
|
509
|
+
var extractRequestBody = (operation, r) => {
|
|
510
|
+
if (!operation.requestBody) return void 0;
|
|
511
|
+
const body = r.resolve(operation.requestBody);
|
|
512
|
+
if (!body) return void 0;
|
|
513
|
+
const contents = declaredContents(body.content).map(
|
|
514
|
+
({ mediaType, media }) => MediaBinding.make({
|
|
515
|
+
contentType: mediaType,
|
|
516
|
+
schema: Option2.fromNullishOr(media.schema),
|
|
517
|
+
encoding: Option2.fromNullishOr(
|
|
518
|
+
buildEncodingRecord(media.encoding)
|
|
519
|
+
)
|
|
520
|
+
})
|
|
521
|
+
);
|
|
522
|
+
if (contents.length === 0) return void 0;
|
|
523
|
+
const defaultContent = contents[0];
|
|
524
|
+
return OperationRequestBody.make({
|
|
525
|
+
required: body.required === true,
|
|
526
|
+
contentType: defaultContent.contentType,
|
|
527
|
+
schema: defaultContent.schema,
|
|
528
|
+
contents: Option2.some(contents)
|
|
529
|
+
});
|
|
530
|
+
};
|
|
531
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
532
|
+
var stringType = (schema) => schema.type === "string" || Array.isArray(schema.type) && schema.type.includes("string");
|
|
533
|
+
var numericType = (schema) => schema.type === "integer" || schema.type === "number" || Array.isArray(schema.type) && (schema.type.includes("integer") || schema.type.includes("number"));
|
|
534
|
+
var normalizedMediaType = (mediaType) => mediaType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
535
|
+
var isJsonMediaType = (mediaType) => {
|
|
536
|
+
const normalized = normalizedMediaType(mediaType);
|
|
537
|
+
return normalized === "application/json" || normalized.includes("+json") || normalized.includes("json");
|
|
538
|
+
};
|
|
539
|
+
var binaryStringSchema = (schema) => stringType(schema) && (schema.format === "binary" || schema.format === "byte");
|
|
540
|
+
var base64EncodingFromDescription = (schema) => typeof schema.description === "string" && /base64url|base64-url|url[- ]safe/i.test(schema.description) ? "base64url" : "base64";
|
|
541
|
+
var detectFileHint = (schema, mediaType, r) => {
|
|
542
|
+
const resolved = isRecord(schema) ? r.resolve(schema) : null;
|
|
543
|
+
if (!resolved) return void 0;
|
|
544
|
+
if (!isJsonMediaType(mediaType) && binaryStringSchema(resolved)) {
|
|
545
|
+
return OperationFileHint.make({
|
|
546
|
+
kind: "binaryResponse",
|
|
547
|
+
mimeType: Option2.some(mediaType),
|
|
548
|
+
dataField: Option2.none(),
|
|
549
|
+
sizeField: Option2.none(),
|
|
550
|
+
encoding: Option2.none()
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
if (!isJsonMediaType(mediaType)) return void 0;
|
|
554
|
+
const properties = resolved.properties;
|
|
555
|
+
if (!isRecord(properties)) return void 0;
|
|
556
|
+
const data = properties.data;
|
|
557
|
+
const dataSchema = isRecord(data) ? r.resolve(data) : null;
|
|
558
|
+
if (!dataSchema || !binaryStringSchema(dataSchema)) return void 0;
|
|
559
|
+
const size = properties.size;
|
|
560
|
+
const sizeSchema = isRecord(size) ? r.resolve(size) : null;
|
|
561
|
+
const sizeField = sizeSchema && numericType(sizeSchema) ? "size" : void 0;
|
|
562
|
+
return OperationFileHint.make({
|
|
563
|
+
kind: "byteField",
|
|
564
|
+
mimeType: Option2.some("application/octet-stream"),
|
|
565
|
+
dataField: Option2.some("data"),
|
|
566
|
+
sizeField: sizeField ? Option2.some(sizeField) : Option2.none(),
|
|
567
|
+
encoding: Option2.some(base64EncodingFromDescription(dataSchema))
|
|
568
|
+
});
|
|
569
|
+
};
|
|
570
|
+
var extractResponseBody = (operation, r) => {
|
|
571
|
+
if (!operation.responses) return void 0;
|
|
572
|
+
const entries = Object.entries(operation.responses);
|
|
573
|
+
const preferred = [
|
|
574
|
+
...entries.filter(([s]) => /^2\d\d$/.test(s)).sort(([a], [b]) => a.localeCompare(b)),
|
|
575
|
+
...entries.filter(([s]) => /^2xx$/i.test(s)),
|
|
576
|
+
...entries.filter(([s]) => s === "default")
|
|
577
|
+
];
|
|
578
|
+
for (const [, ref] of preferred) {
|
|
579
|
+
const resp = r.resolve(ref);
|
|
580
|
+
if (!resp) continue;
|
|
581
|
+
const content = preferredResponseContent(resp.content);
|
|
582
|
+
if (content?.media.schema) {
|
|
583
|
+
return OperationResponseBody.make({
|
|
584
|
+
contentType: content.mediaType,
|
|
585
|
+
schema: Option2.some(content.media.schema),
|
|
586
|
+
fileHint: Option2.fromNullishOr(detectFileHint(content.media.schema, content.mediaType, r))
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return void 0;
|
|
591
|
+
};
|
|
592
|
+
var outputSchemaFromResponseBody = (responseBody) => {
|
|
593
|
+
const schema = Option2.getOrUndefined(responseBody.schema);
|
|
594
|
+
if (schema === void 0) return void 0;
|
|
595
|
+
return isNdjsonMediaType(responseBody.contentType) ? ndjsonArrayOutputSchema(schema) : schema;
|
|
596
|
+
};
|
|
597
|
+
var buildServerInputProperty = (servers) => {
|
|
598
|
+
const variableDefs = {};
|
|
599
|
+
for (const server of servers) {
|
|
600
|
+
for (const [name, v] of Object.entries(Option2.getOrUndefined(server.variables) ?? {})) {
|
|
601
|
+
if (!(name in variableDefs)) variableDefs[name] = v;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
const hasMultiple = servers.length > 1;
|
|
605
|
+
const variableNames = Object.keys(variableDefs);
|
|
606
|
+
if (!hasMultiple && variableNames.length === 0) return void 0;
|
|
607
|
+
const properties = {};
|
|
608
|
+
if (hasMultiple) {
|
|
609
|
+
properties.url = {
|
|
610
|
+
type: "string",
|
|
611
|
+
enum: servers.map((server) => server.url),
|
|
612
|
+
default: servers[0].url,
|
|
613
|
+
description: "Which of the spec's servers to send the request to."
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
if (variableNames.length > 0) {
|
|
617
|
+
properties.variables = {
|
|
618
|
+
type: "object",
|
|
619
|
+
additionalProperties: false,
|
|
620
|
+
properties: Object.fromEntries(
|
|
621
|
+
Object.entries(variableDefs).map(([name, v]) => [
|
|
622
|
+
name,
|
|
623
|
+
{
|
|
624
|
+
type: "string",
|
|
625
|
+
default: v.default,
|
|
626
|
+
...Option2.isSome(v.enum) ? { enum: v.enum.value } : {},
|
|
627
|
+
...Option2.isSome(v.description) ? { description: v.description.value } : {}
|
|
628
|
+
}
|
|
629
|
+
])
|
|
630
|
+
),
|
|
631
|
+
description: "Values for the server URL `{variables}`; spec defaults apply when omitted."
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
type: "object",
|
|
636
|
+
additionalProperties: false,
|
|
637
|
+
properties,
|
|
638
|
+
description: "Optional host selection and server-URL variables for this request."
|
|
639
|
+
};
|
|
640
|
+
};
|
|
641
|
+
var buildInputSchema = (parameters, requestBody, servers) => {
|
|
642
|
+
const properties = {};
|
|
643
|
+
const required = [];
|
|
644
|
+
let requiredBodyAlternatives;
|
|
645
|
+
for (const param of parameters) {
|
|
646
|
+
properties[param.name] = Option2.getOrElse(param.schema, () => ({ type: "string" }));
|
|
647
|
+
if (param.required) required.push(param.name);
|
|
648
|
+
}
|
|
649
|
+
const serverProperty = buildServerInputProperty(servers);
|
|
650
|
+
if (serverProperty && !("server" in properties)) properties.server = serverProperty;
|
|
651
|
+
if (requestBody) {
|
|
652
|
+
const contents = Option2.getOrUndefined(requestBody.contents);
|
|
653
|
+
const defaultIsOctetStream = requestBody.contentType.split(";")[0]?.trim().toLowerCase() === "application/octet-stream";
|
|
654
|
+
const acceptsOctetStream = defaultIsOctetStream || contents?.some(
|
|
655
|
+
(content) => content.contentType.split(";")[0]?.trim().toLowerCase() === "application/octet-stream"
|
|
656
|
+
) === true;
|
|
657
|
+
const acceptsBody = !defaultIsOctetStream || contents?.some(
|
|
658
|
+
(content) => content.contentType.split(";")[0]?.trim().toLowerCase() !== "application/octet-stream"
|
|
659
|
+
) === true;
|
|
660
|
+
if (acceptsBody) {
|
|
661
|
+
properties.body = Option2.getOrElse(requestBody.schema, () => ({ type: "object" }));
|
|
662
|
+
}
|
|
663
|
+
if (acceptsOctetStream) {
|
|
664
|
+
properties.bodyBase64 = {
|
|
665
|
+
type: "string",
|
|
666
|
+
contentEncoding: "base64",
|
|
667
|
+
contentMediaType: "application/octet-stream",
|
|
668
|
+
description: "Base64-encoded bytes for application/octet-stream request bodies. When contentType is omitted, this selects application/octet-stream."
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
if (requestBody.required) {
|
|
672
|
+
if (acceptsOctetStream && acceptsBody) {
|
|
673
|
+
requiredBodyAlternatives = [{ required: ["body"] }, { required: ["bodyBase64"] }];
|
|
674
|
+
} else {
|
|
675
|
+
required.push(acceptsOctetStream ? "bodyBase64" : "body");
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (contents && contents.length > 1) {
|
|
679
|
+
properties.contentType = {
|
|
680
|
+
type: "string",
|
|
681
|
+
enum: contents.map((c) => c.contentType),
|
|
682
|
+
default: requestBody.contentType,
|
|
683
|
+
description: "Content-Type for the request body. Declared media types for this operation, in spec order."
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (Object.keys(properties).length === 0) return void 0;
|
|
688
|
+
return {
|
|
689
|
+
type: "object",
|
|
690
|
+
properties,
|
|
691
|
+
...required.length > 0 ? { required } : {},
|
|
692
|
+
...requiredBodyAlternatives ? { anyOf: requiredBodyAlternatives } : {},
|
|
693
|
+
additionalProperties: false
|
|
694
|
+
};
|
|
695
|
+
};
|
|
696
|
+
var deriveOperationId = (method, pathTemplate, operation) => operation.operationId ?? (`${method}_${pathTemplate.replace(/[^a-zA-Z0-9]+/g, "_")}`.replace(/^_+|_+$/g, "") || `${method}_operation`);
|
|
697
|
+
var explicitToolPath = (operation) => {
|
|
698
|
+
const value = operation["x-executor-toolPath"];
|
|
699
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
700
|
+
};
|
|
701
|
+
var explicitPathTemplate = (operation) => {
|
|
702
|
+
const value = operation["x-executor-pathTemplate"];
|
|
703
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
704
|
+
};
|
|
705
|
+
var extractServerList = (servers) => (servers ?? []).flatMap((server) => {
|
|
706
|
+
if (!server.url) return [];
|
|
707
|
+
const serverVariables = server.variables;
|
|
708
|
+
const vars = serverVariables ? Object.fromEntries(
|
|
709
|
+
Object.entries(serverVariables).flatMap(([name, v]) => {
|
|
710
|
+
if (v.default === void 0 || v.default === null) return [];
|
|
711
|
+
const enumValues = Array.isArray(v.enum) ? v.enum.filter((x) => typeof x === "string") : void 0;
|
|
712
|
+
return [
|
|
713
|
+
[
|
|
714
|
+
name,
|
|
715
|
+
ServerVariable.make({
|
|
716
|
+
default: String(v.default),
|
|
717
|
+
enum: enumValues && enumValues.length > 0 ? Option2.some(enumValues) : Option2.none(),
|
|
718
|
+
description: Option2.fromNullishOr(v.description)
|
|
719
|
+
})
|
|
720
|
+
]
|
|
721
|
+
];
|
|
722
|
+
})
|
|
723
|
+
) : void 0;
|
|
724
|
+
return [
|
|
725
|
+
ServerInfo.make({
|
|
726
|
+
url: server.url,
|
|
727
|
+
description: Option2.fromNullishOr(server.description),
|
|
728
|
+
variables: vars && Object.keys(vars).length > 0 ? Option2.some(vars) : Option2.none()
|
|
729
|
+
})
|
|
730
|
+
];
|
|
731
|
+
});
|
|
732
|
+
var extractServers = (doc) => extractServerList(doc.servers);
|
|
733
|
+
var operationServers = (pathItem, operation, docServers) => {
|
|
734
|
+
const operationLevel = extractServerList(operation.servers);
|
|
735
|
+
if (operationLevel.length > 0) return operationLevel;
|
|
736
|
+
const pathLevel = extractServerList(pathItem.servers);
|
|
737
|
+
if (pathLevel.length > 0) return pathLevel;
|
|
738
|
+
return docServers;
|
|
739
|
+
};
|
|
740
|
+
var securityScopeAlternatives = (operation, documentSecurity) => {
|
|
741
|
+
const security = operation.security !== void 0 ? operation.security : documentSecurity;
|
|
742
|
+
if (!Array.isArray(security) || security.length === 0) return void 0;
|
|
743
|
+
const alternatives = [];
|
|
744
|
+
const seen = /* @__PURE__ */ new Set();
|
|
745
|
+
for (const requirement of security) {
|
|
746
|
+
if (requirement === null || typeof requirement !== "object") continue;
|
|
747
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
748
|
+
for (const schemeScopes of Object.values(requirement)) {
|
|
749
|
+
if (!Array.isArray(schemeScopes)) continue;
|
|
750
|
+
for (const scope of schemeScopes) {
|
|
751
|
+
if (typeof scope === "string" && scope.trim().length > 0) scopes.add(scope);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (scopes.size === 0) continue;
|
|
755
|
+
const alternative = [...scopes].sort();
|
|
756
|
+
const key = alternative.join(" ");
|
|
757
|
+
if (seen.has(key)) continue;
|
|
758
|
+
seen.add(key);
|
|
759
|
+
alternatives.push(alternative);
|
|
760
|
+
}
|
|
761
|
+
return alternatives.length > 0 ? alternatives : void 0;
|
|
762
|
+
};
|
|
763
|
+
var documentSecurityOf = (doc) => doc !== null && typeof doc === "object" && !Array.isArray(doc) ? doc.security : void 0;
|
|
764
|
+
var extract = Effect2.fn("OpenApi.extract")(function* (doc) {
|
|
765
|
+
const paths = doc.paths;
|
|
766
|
+
if (!paths) {
|
|
767
|
+
return yield* new OpenApiExtractionError({
|
|
768
|
+
message: "OpenAPI document has no paths defined"
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
const r = new DocResolver(doc);
|
|
772
|
+
const docServers = extractServers(doc);
|
|
773
|
+
const operations = [];
|
|
774
|
+
for (const [pathTemplate, pathItem] of Object.entries(paths).sort(
|
|
775
|
+
([a], [b]) => a.localeCompare(b)
|
|
776
|
+
)) {
|
|
777
|
+
if (!pathItem) continue;
|
|
778
|
+
for (const method of HTTP_METHODS) {
|
|
779
|
+
const operation = pathItem[method];
|
|
780
|
+
if (!operation) continue;
|
|
781
|
+
const parameters = extractParameters(pathItem, operation, r);
|
|
782
|
+
const requestBody = extractRequestBody(operation, r);
|
|
783
|
+
const responseBody = extractResponseBody(operation, r);
|
|
784
|
+
const servers = operationServers(pathItem, operation, docServers);
|
|
785
|
+
const inputSchema = buildInputSchema(parameters, requestBody, servers);
|
|
786
|
+
const outputSchema = responseBody ? outputSchemaFromResponseBody(responseBody) : void 0;
|
|
787
|
+
const tags = (operation.tags ?? []).filter((t) => t.trim().length > 0);
|
|
788
|
+
const operationPathTemplate = explicitPathTemplate(operation) ?? pathTemplate;
|
|
789
|
+
const requiredScopeAlternatives = securityScopeAlternatives(
|
|
790
|
+
operation,
|
|
791
|
+
documentSecurityOf(doc)
|
|
792
|
+
);
|
|
793
|
+
operations.push(
|
|
794
|
+
ExtractedOperation.make({
|
|
795
|
+
operationId: OperationId.make(deriveOperationId(method, pathTemplate, operation)),
|
|
796
|
+
toolPath: Option2.fromNullishOr(explicitToolPath(operation)),
|
|
797
|
+
method,
|
|
798
|
+
servers,
|
|
799
|
+
pathTemplate: operationPathTemplate,
|
|
800
|
+
summary: Option2.fromNullishOr(operation.summary),
|
|
801
|
+
description: Option2.fromNullishOr(operation.description),
|
|
802
|
+
tags,
|
|
803
|
+
parameters,
|
|
804
|
+
requestBody: Option2.fromNullishOr(requestBody),
|
|
805
|
+
responseBody: Option2.fromNullishOr(responseBody),
|
|
806
|
+
inputSchema: Option2.fromNullishOr(inputSchema),
|
|
807
|
+
outputSchema: Option2.fromNullishOr(outputSchema),
|
|
808
|
+
deprecated: operation.deprecated === true,
|
|
809
|
+
...requiredScopeAlternatives ? { requiredScopeAlternatives } : {}
|
|
810
|
+
})
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return ExtractionResult.make({
|
|
815
|
+
title: Option2.fromNullishOr(doc.info?.title),
|
|
816
|
+
description: Option2.fromNullishOr(doc.info?.description),
|
|
817
|
+
version: Option2.fromNullishOr(doc.info?.version),
|
|
818
|
+
servers: docServers,
|
|
819
|
+
operations
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
var streamOperationBindings = (doc, chunkSize, onChunk) => Effect2.gen(function* () {
|
|
823
|
+
const paths = doc.paths;
|
|
824
|
+
if (!paths) {
|
|
825
|
+
return yield* new OpenApiExtractionError({
|
|
826
|
+
message: "OpenAPI document has no paths defined"
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
const r = new DocResolver(doc);
|
|
830
|
+
const docServers = extractServers(doc);
|
|
831
|
+
const inputs = [];
|
|
832
|
+
const opRefs = [];
|
|
833
|
+
for (const [pathTemplate, pathItem] of Object.entries(paths).sort(
|
|
834
|
+
([a], [b]) => a.localeCompare(b)
|
|
835
|
+
)) {
|
|
836
|
+
if (!pathItem) continue;
|
|
837
|
+
for (const method of HTTP_METHODS) {
|
|
838
|
+
const operation = pathItem[method];
|
|
839
|
+
if (!operation) continue;
|
|
840
|
+
const resolvedPathTemplate = explicitPathTemplate(operation) ?? pathTemplate;
|
|
841
|
+
const tags = (operation.tags ?? []).filter((t) => t.trim().length > 0);
|
|
842
|
+
inputs.push({
|
|
843
|
+
operationId: deriveOperationId(method, pathTemplate, operation),
|
|
844
|
+
explicitToolPath: explicitToolPath(operation),
|
|
845
|
+
method,
|
|
846
|
+
pathTemplate: resolvedPathTemplate,
|
|
847
|
+
tag0: tags[0]
|
|
848
|
+
});
|
|
849
|
+
opRefs.push({ pathItem, operation, method, pathTemplate: resolvedPathTemplate });
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
const plans = planToolPaths(inputs);
|
|
853
|
+
let chunk = [];
|
|
854
|
+
for (const plan of plans) {
|
|
855
|
+
const ref = opRefs[plan.operationIndex];
|
|
856
|
+
const parameters = extractParameters(ref.pathItem, ref.operation, r);
|
|
857
|
+
const requestBody = extractRequestBody(ref.operation, r);
|
|
858
|
+
const responseBody = extractResponseBody(ref.operation, r);
|
|
859
|
+
const servers = operationServers(ref.pathItem, ref.operation, docServers);
|
|
860
|
+
const requiredScopeAlternatives = securityScopeAlternatives(
|
|
861
|
+
ref.operation,
|
|
862
|
+
documentSecurityOf(doc)
|
|
863
|
+
);
|
|
864
|
+
chunk.push({
|
|
865
|
+
toolName: plan.toolPath,
|
|
866
|
+
description: ref.operation.description ?? ref.operation.summary ?? `${ref.method.toUpperCase()} ${ref.pathTemplate}`,
|
|
867
|
+
binding: OperationBinding.make({
|
|
868
|
+
method: ref.method,
|
|
869
|
+
servers,
|
|
870
|
+
pathTemplate: ref.pathTemplate,
|
|
871
|
+
parameters,
|
|
872
|
+
requestBody: Option2.fromNullishOr(requestBody),
|
|
873
|
+
responseBody: Option2.fromNullishOr(responseBody),
|
|
874
|
+
...requiredScopeAlternatives ? { requiredScopeAlternatives } : {}
|
|
875
|
+
})
|
|
876
|
+
});
|
|
877
|
+
if (chunk.length >= chunkSize) {
|
|
878
|
+
yield* onChunk(chunk);
|
|
879
|
+
chunk = [];
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (chunk.length > 0) yield* onChunk(chunk);
|
|
883
|
+
return { toolCount: plans.length, toolNames: plans.map((plan) => plan.toolPath) };
|
|
884
|
+
}).pipe(Effect2.withSpan("OpenApi.streamOperationBindings"));
|
|
885
|
+
var isPathItemValue = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
886
|
+
var streamOperationBindingsFromStructure = (structure, options, onChunk) => Effect2.gen(function* () {
|
|
887
|
+
const { chunkSize, keepPathItem } = options;
|
|
888
|
+
const keptPathItem = (range) => {
|
|
889
|
+
const entry = parseEntry(structure.text, range, 2);
|
|
890
|
+
if (!entry) return null;
|
|
891
|
+
const [path, rawValue] = entry;
|
|
892
|
+
if (!isPathItemValue(rawValue)) return null;
|
|
893
|
+
if (!keepPathItem) return [path, rawValue];
|
|
894
|
+
const kept = keepPathItem(path, rawValue);
|
|
895
|
+
return kept ? [path, kept] : null;
|
|
896
|
+
};
|
|
897
|
+
const inputs = [];
|
|
898
|
+
for (const range of structure.pathItems) {
|
|
899
|
+
const kept = keptPathItem(range);
|
|
900
|
+
if (!kept) continue;
|
|
901
|
+
const [path, pathItem] = kept;
|
|
902
|
+
for (const method of HTTP_METHODS) {
|
|
903
|
+
const operation = pathItem[method];
|
|
904
|
+
if (!operation) continue;
|
|
905
|
+
const resolvedPathTemplate = explicitPathTemplate(operation) ?? path;
|
|
906
|
+
const tags = (operation.tags ?? []).filter((t) => t.trim().length > 0);
|
|
907
|
+
inputs.push({
|
|
908
|
+
operationId: deriveOperationId(method, path, operation),
|
|
909
|
+
explicitToolPath: explicitToolPath(operation),
|
|
910
|
+
method,
|
|
911
|
+
pathTemplate: resolvedPathTemplate,
|
|
912
|
+
tag0: tags[0]
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const plans = planToolPaths(inputs);
|
|
917
|
+
const planByOpIndex = new Array(inputs.length);
|
|
918
|
+
for (const plan of plans) planByOpIndex[plan.operationIndex] = plan;
|
|
919
|
+
const resolverDoc = {
|
|
920
|
+
...parseHead(structure),
|
|
921
|
+
paths: {},
|
|
922
|
+
components: parseSmallComponents(structure)
|
|
923
|
+
};
|
|
924
|
+
const r = new DocResolver(resolverDoc);
|
|
925
|
+
const docServers = extractServers(resolverDoc);
|
|
926
|
+
let opIndex = 0;
|
|
927
|
+
let chunk = [];
|
|
928
|
+
for (const range of structure.pathItems) {
|
|
929
|
+
const kept = keptPathItem(range);
|
|
930
|
+
if (!kept) continue;
|
|
931
|
+
const [path, pathItem] = kept;
|
|
932
|
+
for (const method of HTTP_METHODS) {
|
|
933
|
+
const operation = pathItem[method];
|
|
934
|
+
if (!operation) continue;
|
|
935
|
+
const plan = planByOpIndex[opIndex];
|
|
936
|
+
opIndex += 1;
|
|
937
|
+
if (!plan) continue;
|
|
938
|
+
const resolvedPathTemplate = explicitPathTemplate(operation) ?? path;
|
|
939
|
+
const parameters = extractParameters(pathItem, operation, r);
|
|
940
|
+
const requestBody = extractRequestBody(operation, r);
|
|
941
|
+
const responseBody = extractResponseBody(operation, r);
|
|
942
|
+
const servers = operationServers(pathItem, operation, docServers);
|
|
943
|
+
const requiredScopeAlternatives = securityScopeAlternatives(
|
|
944
|
+
operation,
|
|
945
|
+
documentSecurityOf(resolverDoc)
|
|
946
|
+
);
|
|
947
|
+
chunk.push({
|
|
948
|
+
toolName: plan.toolPath,
|
|
949
|
+
description: operation.description ?? operation.summary ?? `${method.toUpperCase()} ${resolvedPathTemplate}`,
|
|
950
|
+
binding: OperationBinding.make({
|
|
951
|
+
method,
|
|
952
|
+
servers,
|
|
953
|
+
pathTemplate: resolvedPathTemplate,
|
|
954
|
+
parameters,
|
|
955
|
+
requestBody: Option2.fromNullishOr(requestBody),
|
|
956
|
+
responseBody: Option2.fromNullishOr(responseBody),
|
|
957
|
+
...requiredScopeAlternatives ? { requiredScopeAlternatives } : {}
|
|
958
|
+
})
|
|
959
|
+
});
|
|
960
|
+
if (chunk.length >= chunkSize) {
|
|
961
|
+
yield* onChunk(chunk);
|
|
962
|
+
chunk = [];
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (chunk.length > 0) yield* onChunk(chunk);
|
|
967
|
+
return { toolCount: plans.length, toolNames: plans.map((plan) => plan.toolPath) };
|
|
968
|
+
}).pipe(Effect2.withSpan("OpenApi.streamOperationBindingsFromStructure"));
|
|
969
|
+
|
|
970
|
+
// src/sdk/invoke.ts
|
|
971
|
+
import { Effect as Effect3, Exit, Fiber, Layer, Option as Option3, Schema as Schema3, Stream } from "effect";
|
|
972
|
+
import { HttpClient as HttpClient2, HttpClientRequest as HttpClientRequest2 } from "effect/unstable/http";
|
|
973
|
+
var CONTAINER_KEYS = {
|
|
974
|
+
path: ["path", "pathParams", "params"],
|
|
975
|
+
query: ["query", "queryParams", "params"],
|
|
976
|
+
header: ["headers", "header"],
|
|
977
|
+
cookie: ["cookies", "cookie"]
|
|
978
|
+
};
|
|
979
|
+
var readParamValue = (args, param) => {
|
|
980
|
+
const direct = args[param.name];
|
|
981
|
+
if (direct !== void 0) return direct;
|
|
982
|
+
for (const key of CONTAINER_KEYS[param.location] ?? []) {
|
|
983
|
+
const container = args[key];
|
|
984
|
+
if (typeof container === "object" && container !== null && !Array.isArray(container)) {
|
|
985
|
+
const nested = container[param.name];
|
|
986
|
+
if (nested !== void 0) return nested;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return void 0;
|
|
990
|
+
};
|
|
991
|
+
var primitiveToString = (value) => typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
|
|
992
|
+
var RESERVED_UNENCODED_RE = /[A-Za-z0-9\-._~:/?#[\]@!$&'()*+,;=]/;
|
|
993
|
+
var encodeReservedAware = (raw, allowReserved) => {
|
|
994
|
+
if (!allowReserved) return encodeURIComponent(raw);
|
|
995
|
+
let out = "";
|
|
996
|
+
for (const ch of raw) {
|
|
997
|
+
out += RESERVED_UNENCODED_RE.test(ch) ? ch : encodeURIComponent(ch);
|
|
998
|
+
}
|
|
999
|
+
return out;
|
|
1000
|
+
};
|
|
1001
|
+
var queryParamValues = (value, param) => {
|
|
1002
|
+
if (value === void 0 || value === null) return [];
|
|
1003
|
+
if (!Array.isArray(value)) return [primitiveToString(value)];
|
|
1004
|
+
const style = Option3.getOrUndefined(param.style) ?? "form";
|
|
1005
|
+
const explode = Option3.getOrElse(param.explode, () => true);
|
|
1006
|
+
if (explode) return value.map(primitiveToString);
|
|
1007
|
+
const separator = style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ",";
|
|
1008
|
+
return [value.map(primitiveToString).join(separator)];
|
|
1009
|
+
};
|
|
1010
|
+
var resolvePath = Effect3.fn("OpenApi.resolvePath")(function* (pathTemplate, args, parameters) {
|
|
1011
|
+
let resolved = pathTemplate;
|
|
1012
|
+
for (const param of parameters) {
|
|
1013
|
+
if (param.location !== "path") continue;
|
|
1014
|
+
const value = readParamValue(args, param);
|
|
1015
|
+
if (value === void 0 || value === null) {
|
|
1016
|
+
if (param.required) {
|
|
1017
|
+
return yield* new OpenApiInvocationError({
|
|
1018
|
+
message: `Missing required path parameter: ${param.name}`,
|
|
1019
|
+
statusCode: Option3.none()
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
const encoded = encodeReservedAware(
|
|
1025
|
+
String(value),
|
|
1026
|
+
Option3.getOrElse(param.allowReserved, () => false)
|
|
1027
|
+
);
|
|
1028
|
+
resolved = resolved.replaceAll(`{${param.name}}`, encoded);
|
|
1029
|
+
resolved = resolved.replaceAll(`{+${param.name}}`, encoded);
|
|
1030
|
+
}
|
|
1031
|
+
const remaining = [...resolved.matchAll(/\{([^{}]+)\}/g)].map((m) => m[1]).filter((v) => typeof v === "string");
|
|
1032
|
+
for (const name of remaining) {
|
|
1033
|
+
const value = args[name];
|
|
1034
|
+
if (value !== void 0 && value !== null) {
|
|
1035
|
+
resolved = resolved.replaceAll(`{${name}}`, encodeURIComponent(String(value)));
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
const unresolved = [...resolved.matchAll(/\{([^{}]+)\}/g)].map((m) => m[1]).filter((v) => typeof v === "string");
|
|
1039
|
+
if (unresolved.length > 0) {
|
|
1040
|
+
return yield* new OpenApiInvocationError({
|
|
1041
|
+
message: `Unresolved path parameters: ${[...new Set(unresolved)].join(", ")}`,
|
|
1042
|
+
statusCode: Option3.none()
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
return resolved;
|
|
1046
|
+
});
|
|
1047
|
+
var DEFAULT_USER_AGENT = "executor";
|
|
1048
|
+
var applyHeaders = (request, headers) => {
|
|
1049
|
+
let req = request;
|
|
1050
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
1051
|
+
req = HttpClientRequest2.setHeader(req, name, value);
|
|
1052
|
+
}
|
|
1053
|
+
return req;
|
|
1054
|
+
};
|
|
1055
|
+
var normalizeContentType = (ct) => ct?.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
1056
|
+
var STREAM_MAX_BYTES = 1e6;
|
|
1057
|
+
var STREAM_MAX_MS = 1e4;
|
|
1058
|
+
var RESPONSE_HEADERS_TIMEOUT_MS = 11e4;
|
|
1059
|
+
var StreamCapReached = class extends Error {
|
|
1060
|
+
_tag = "StreamCapReached";
|
|
1061
|
+
};
|
|
1062
|
+
var formatTimeout = (timeoutMs) => timeoutMs % 1e3 === 0 ? `${timeoutMs / 1e3}s` : `${timeoutMs}ms`;
|
|
1063
|
+
var responseHeadersTimeoutMessage = (timeoutMs) => `Upstream returned no response headers within ${formatTimeout(timeoutMs)}. The endpoint may be a live stream with no data to send (for example, runtime logs of an idle deployment); the request was aborted. Retry when the resource has activity, or use a non-streaming endpoint.`;
|
|
1064
|
+
var STREAMING_RESPONSE_CONTENT_TYPES = /* @__PURE__ */ new Set([...NDJSON_MEDIA_TYPES, "text/event-stream"]);
|
|
1065
|
+
var isStreamingResponseContentType = (ct) => STREAMING_RESPONSE_CONTENT_TYPES.has(normalizeContentType(ct));
|
|
1066
|
+
var isJsonContentType = (ct) => {
|
|
1067
|
+
const normalized = normalizeContentType(ct);
|
|
1068
|
+
if (!normalized) return false;
|
|
1069
|
+
return normalized === "application/json" || normalized.includes("+json") || normalized.includes("json");
|
|
1070
|
+
};
|
|
1071
|
+
var isFormUrlEncoded = (ct) => normalizeContentType(ct) === "application/x-www-form-urlencoded";
|
|
1072
|
+
var isMultipartFormData = (ct) => normalizeContentType(ct).startsWith("multipart/form-data");
|
|
1073
|
+
var isXmlContentType = (ct) => {
|
|
1074
|
+
const normalized = normalizeContentType(ct);
|
|
1075
|
+
if (!normalized) return false;
|
|
1076
|
+
return normalized === "application/xml" || normalized === "text/xml" || normalized.endsWith("+xml");
|
|
1077
|
+
};
|
|
1078
|
+
var isTextContentType = (ct) => normalizeContentType(ct).startsWith("text/");
|
|
1079
|
+
var isOctetStream = (ct) => normalizeContentType(ct) === "application/octet-stream";
|
|
1080
|
+
var decodeJsonLine = Schema3.decodeUnknownExit(Schema3.fromJsonString(Schema3.Unknown));
|
|
1081
|
+
var parseStreamingJsonLines = (text, truncated) => {
|
|
1082
|
+
const lines = text.split(/\r?\n/);
|
|
1083
|
+
let lastNonEmptyIndex = -1;
|
|
1084
|
+
for (let index = lines.length - 1; index >= 0; index--) {
|
|
1085
|
+
const line = lines[index];
|
|
1086
|
+
if (line !== void 0 && line.trim() !== "") {
|
|
1087
|
+
lastNonEmptyIndex = index;
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
const rows = [];
|
|
1092
|
+
for (let index = 0; index < lines.length; index++) {
|
|
1093
|
+
const line = lines[index];
|
|
1094
|
+
if (line === void 0 || line.trim() === "") continue;
|
|
1095
|
+
const parsed = decodeJsonLine(line);
|
|
1096
|
+
if (Exit.isSuccess(parsed)) {
|
|
1097
|
+
rows.push(parsed.value);
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
if (truncated && index === lastNonEmptyIndex) continue;
|
|
1101
|
+
return text;
|
|
1102
|
+
}
|
|
1103
|
+
return rows;
|
|
1104
|
+
};
|
|
1105
|
+
var decodeStreamingResponseBody = (contentType, text, truncated) => isNdjsonMediaType(contentType) ? parseStreamingJsonLines(text, truncated) : text;
|
|
1106
|
+
var collectStreamingBody = (stream, contentType, caps = {}) => Effect3.gen(function* () {
|
|
1107
|
+
const maxBytes = caps.maxBytes ?? STREAM_MAX_BYTES;
|
|
1108
|
+
const maxMs = caps.maxMs ?? STREAM_MAX_MS;
|
|
1109
|
+
const chunks = [];
|
|
1110
|
+
let bytes = 0;
|
|
1111
|
+
let truncated = false;
|
|
1112
|
+
const startedAt = Date.now();
|
|
1113
|
+
const runFork = Effect3.runForkWith(yield* Effect3.context());
|
|
1114
|
+
const completedExitOption = yield* Effect3.callback(
|
|
1115
|
+
(resume, signal) => {
|
|
1116
|
+
let settled = false;
|
|
1117
|
+
const collectEffect = stream.pipe(
|
|
1118
|
+
Stream.timeout(maxMs),
|
|
1119
|
+
Stream.runForEach(
|
|
1120
|
+
(chunk) => Effect3.gen(function* () {
|
|
1121
|
+
const remaining = maxBytes - bytes;
|
|
1122
|
+
if (remaining <= 0) {
|
|
1123
|
+
truncated = true;
|
|
1124
|
+
return yield* Effect3.fail(new StreamCapReached());
|
|
1125
|
+
}
|
|
1126
|
+
if (chunk.byteLength > remaining) {
|
|
1127
|
+
chunks.push(chunk.subarray(0, remaining));
|
|
1128
|
+
bytes += remaining;
|
|
1129
|
+
truncated = true;
|
|
1130
|
+
return yield* Effect3.fail(new StreamCapReached());
|
|
1131
|
+
}
|
|
1132
|
+
chunks.push(chunk);
|
|
1133
|
+
bytes += chunk.byteLength;
|
|
1134
|
+
if (bytes >= maxBytes) {
|
|
1135
|
+
truncated = true;
|
|
1136
|
+
return yield* Effect3.fail(new StreamCapReached());
|
|
1137
|
+
}
|
|
1138
|
+
})
|
|
1139
|
+
),
|
|
1140
|
+
Effect3.catch(
|
|
1141
|
+
(error) => error instanceof StreamCapReached ? Effect3.void : Effect3.fail(error)
|
|
1142
|
+
),
|
|
1143
|
+
Effect3.exit,
|
|
1144
|
+
Effect3.tap(
|
|
1145
|
+
(exit) => Effect3.sync(() => {
|
|
1146
|
+
if (settled) return;
|
|
1147
|
+
settled = true;
|
|
1148
|
+
clearTimeout(timer);
|
|
1149
|
+
resume(Effect3.succeed(Option3.some(exit)));
|
|
1150
|
+
})
|
|
1151
|
+
)
|
|
1152
|
+
);
|
|
1153
|
+
const fiber = runFork(collectEffect);
|
|
1154
|
+
const interrupt = () => {
|
|
1155
|
+
runFork(Fiber.interrupt(fiber));
|
|
1156
|
+
};
|
|
1157
|
+
const timer = setTimeout(() => {
|
|
1158
|
+
if (settled) return;
|
|
1159
|
+
settled = true;
|
|
1160
|
+
truncated = true;
|
|
1161
|
+
interrupt();
|
|
1162
|
+
resume(Effect3.succeed(Option3.none()));
|
|
1163
|
+
}, maxMs + 1e3);
|
|
1164
|
+
signal.addEventListener("abort", interrupt, { once: true });
|
|
1165
|
+
return Effect3.sync(() => {
|
|
1166
|
+
clearTimeout(timer);
|
|
1167
|
+
signal.removeEventListener("abort", interrupt);
|
|
1168
|
+
if (!settled) interrupt();
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
);
|
|
1172
|
+
if (Option3.isSome(completedExitOption) && Exit.isFailure(completedExitOption.value)) {
|
|
1173
|
+
return yield* Effect3.failCause(completedExitOption.value.cause);
|
|
1174
|
+
}
|
|
1175
|
+
if (Option3.isNone(completedExitOption)) truncated = true;
|
|
1176
|
+
const durationMs = Date.now() - startedAt;
|
|
1177
|
+
if (durationMs >= maxMs) truncated = true;
|
|
1178
|
+
const collected = new Uint8Array(bytes);
|
|
1179
|
+
let offset = 0;
|
|
1180
|
+
for (const chunk of chunks) {
|
|
1181
|
+
collected.set(chunk, offset);
|
|
1182
|
+
offset += chunk.byteLength;
|
|
1183
|
+
}
|
|
1184
|
+
const text = new TextDecoder("utf-8").decode(collected);
|
|
1185
|
+
return {
|
|
1186
|
+
data: decodeStreamingResponseBody(contentType, text, truncated),
|
|
1187
|
+
headers: {
|
|
1188
|
+
"x-executor-stream": truncated ? "truncated" : "complete",
|
|
1189
|
+
"x-executor-stream-bytes": String(bytes),
|
|
1190
|
+
"x-executor-stream-duration-ms": String(durationMs)
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
});
|
|
1194
|
+
var bytesToBase64 = (bytes) => {
|
|
1195
|
+
let binary = "";
|
|
1196
|
+
const chunkSize = 32768;
|
|
1197
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
1198
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
|
|
1199
|
+
}
|
|
1200
|
+
return btoa(binary);
|
|
1201
|
+
};
|
|
1202
|
+
var normalizeBase64 = (value, encoding) => {
|
|
1203
|
+
const compact = value.replace(/\s/g, "");
|
|
1204
|
+
const alphabet = encoding === "base64url" ? compact.replace(/-/g, "+").replace(/_/g, "/") : compact;
|
|
1205
|
+
const remainder = alphabet.length % 4;
|
|
1206
|
+
return remainder === 0 ? alphabet : `${alphabet}${"=".repeat(4 - remainder)}`;
|
|
1207
|
+
};
|
|
1208
|
+
var byteLengthFromBase64 = (base64) => {
|
|
1209
|
+
const compact = base64.replace(/\s/g, "");
|
|
1210
|
+
const padding = compact.endsWith("==") ? 2 : compact.endsWith("=") ? 1 : 0;
|
|
1211
|
+
return Math.max(0, Math.floor(compact.length * 3 / 4) - padding);
|
|
1212
|
+
};
|
|
1213
|
+
var isGenericMimeType = (mimeType) => normalizeContentType(mimeType) === "application/octet-stream";
|
|
1214
|
+
var startsWithBytes = (bytes, prefix) => prefix.every((byte, index) => bytes[index] === byte);
|
|
1215
|
+
var isLikelyUtf8Text = (bytes) => {
|
|
1216
|
+
if (bytes.length === 0) return false;
|
|
1217
|
+
let text;
|
|
1218
|
+
try {
|
|
1219
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
1220
|
+
} catch {
|
|
1221
|
+
return false;
|
|
1222
|
+
}
|
|
1223
|
+
let suspicious = 0;
|
|
1224
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1225
|
+
const code = text.charCodeAt(index);
|
|
1226
|
+
const allowedControl = code === 9 || code === 10 || code === 12 || code === 13;
|
|
1227
|
+
if (code === 0) return false;
|
|
1228
|
+
if (code < 32 && !allowedControl) suspicious += 1;
|
|
1229
|
+
}
|
|
1230
|
+
return suspicious / Math.max(1, text.length) <= 0.02;
|
|
1231
|
+
};
|
|
1232
|
+
var sniffMimeType = (bytes) => {
|
|
1233
|
+
if (startsWithBytes(bytes, [255, 216, 255])) return "image/jpeg";
|
|
1234
|
+
if (startsWithBytes(bytes, [137, 80, 78, 71, 13, 10, 26, 10])) {
|
|
1235
|
+
return "image/png";
|
|
1236
|
+
}
|
|
1237
|
+
if (startsWithBytes(bytes, [71, 73, 70, 56, 55, 97]) || startsWithBytes(bytes, [71, 73, 70, 56, 57, 97])) {
|
|
1238
|
+
return "image/gif";
|
|
1239
|
+
}
|
|
1240
|
+
if (startsWithBytes(bytes, [82, 73, 70, 70]) && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) {
|
|
1241
|
+
return "image/webp";
|
|
1242
|
+
}
|
|
1243
|
+
if (startsWithBytes(bytes, [37, 80, 68, 70, 45])) return "application/pdf";
|
|
1244
|
+
if (startsWithBytes(bytes, [80, 75, 3, 4]) || startsWithBytes(bytes, [80, 75, 5, 6]) || startsWithBytes(bytes, [80, 75, 7, 8])) {
|
|
1245
|
+
return "application/zip";
|
|
1246
|
+
}
|
|
1247
|
+
if (isLikelyUtf8Text(bytes)) return "text/plain";
|
|
1248
|
+
return null;
|
|
1249
|
+
};
|
|
1250
|
+
var bytesFromBase64Prefix = (base64) => {
|
|
1251
|
+
const prefix = base64.slice(0, Math.min(base64.length, 64));
|
|
1252
|
+
const binary = atob(prefix);
|
|
1253
|
+
const bytes = new Uint8Array(binary.length);
|
|
1254
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
1255
|
+
bytes[index] = binary.charCodeAt(index);
|
|
1256
|
+
}
|
|
1257
|
+
return bytes;
|
|
1258
|
+
};
|
|
1259
|
+
var sniffMimeTypeFromBase64 = (base64) => sniffMimeType(bytesFromBase64Prefix(base64));
|
|
1260
|
+
var base64ToUint8Array = (value) => {
|
|
1261
|
+
let binary = "";
|
|
1262
|
+
try {
|
|
1263
|
+
binary = atob(normalizeBase64(value, "base64"));
|
|
1264
|
+
} catch {
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
const bytes = new Uint8Array(binary.length);
|
|
1268
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
1269
|
+
bytes[index] = binary.charCodeAt(index);
|
|
1270
|
+
}
|
|
1271
|
+
return bytes;
|
|
1272
|
+
};
|
|
1273
|
+
var decodeBase64Body = (value) => {
|
|
1274
|
+
const bytes = base64ToUint8Array(value);
|
|
1275
|
+
return bytes ? { ok: true, bytes } : { ok: false };
|
|
1276
|
+
};
|
|
1277
|
+
var toUint8Array = (value) => {
|
|
1278
|
+
if (value instanceof Uint8Array) return value;
|
|
1279
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
1280
|
+
if (ArrayBuffer.isView(value)) {
|
|
1281
|
+
const view = value;
|
|
1282
|
+
return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
1283
|
+
}
|
|
1284
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "number")) {
|
|
1285
|
+
return new Uint8Array(value);
|
|
1286
|
+
}
|
|
1287
|
+
return null;
|
|
1288
|
+
};
|
|
1289
|
+
var readNestedBodyBase64 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, "bodyBase64") ? value.bodyBase64 : void 0;
|
|
1290
|
+
var readHintString = (option, fallback) => Option3.getOrElse(option, () => fallback);
|
|
1291
|
+
var readHintMimeType = (hint, fallback) => Option3.getOrElse(hint.mimeType, () => fallback);
|
|
1292
|
+
var readHintEncoding = (hint) => Option3.getOrElse(hint.encoding, () => "base64");
|
|
1293
|
+
var fileFromByteField = (body, hint) => {
|
|
1294
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return null;
|
|
1295
|
+
const record = body;
|
|
1296
|
+
const dataField = readHintString(hint.dataField, "data");
|
|
1297
|
+
const rawData = record[dataField];
|
|
1298
|
+
if (typeof rawData !== "string") return null;
|
|
1299
|
+
const data = normalizeBase64(rawData, readHintEncoding(hint));
|
|
1300
|
+
const sizeField = Option3.getOrUndefined(hint.sizeField);
|
|
1301
|
+
const byteLength = sizeField && typeof record[sizeField] === "number" ? record[sizeField] : byteLengthFromBase64(data);
|
|
1302
|
+
const hintedMimeType = readHintMimeType(hint, "application/octet-stream");
|
|
1303
|
+
return {
|
|
1304
|
+
_tag: "ToolFile",
|
|
1305
|
+
mimeType: isGenericMimeType(hintedMimeType) ? sniffMimeTypeFromBase64(data) ?? hintedMimeType : hintedMimeType,
|
|
1306
|
+
encoding: "base64",
|
|
1307
|
+
data,
|
|
1308
|
+
byteLength
|
|
1309
|
+
};
|
|
1310
|
+
};
|
|
1311
|
+
var fileFromBinaryBytes = (bytes, hint, contentType) => {
|
|
1312
|
+
const hintedMimeType = contentType ?? readHintMimeType(hint, "application/octet-stream");
|
|
1313
|
+
return {
|
|
1314
|
+
_tag: "ToolFile",
|
|
1315
|
+
mimeType: isGenericMimeType(hintedMimeType) ? sniffMimeType(bytes) ?? hintedMimeType : hintedMimeType,
|
|
1316
|
+
encoding: "base64",
|
|
1317
|
+
data: bytesToBase64(bytes),
|
|
1318
|
+
byteLength: bytes.byteLength
|
|
1319
|
+
};
|
|
1320
|
+
};
|
|
1321
|
+
var toArrayBuffer = (bytes) => {
|
|
1322
|
+
const copy = new ArrayBuffer(bytes.byteLength);
|
|
1323
|
+
new Uint8Array(copy).set(bytes);
|
|
1324
|
+
return copy;
|
|
1325
|
+
};
|
|
1326
|
+
var DEFAULT_FORM_STYLE = {
|
|
1327
|
+
style: "form",
|
|
1328
|
+
explode: true,
|
|
1329
|
+
allowReserved: false
|
|
1330
|
+
};
|
|
1331
|
+
var resolveStyleExplode = (e) => {
|
|
1332
|
+
if (!e) return DEFAULT_FORM_STYLE;
|
|
1333
|
+
return {
|
|
1334
|
+
style: Option3.getOrElse(e.style, () => DEFAULT_FORM_STYLE.style),
|
|
1335
|
+
explode: Option3.getOrElse(e.explode, () => DEFAULT_FORM_STYLE.explode),
|
|
1336
|
+
allowReserved: Option3.getOrElse(e.allowReserved, () => DEFAULT_FORM_STYLE.allowReserved)
|
|
1337
|
+
};
|
|
1338
|
+
};
|
|
1339
|
+
var encodeFormValue = (v, allowReserved) => {
|
|
1340
|
+
const raw = typeof v === "object" && v !== null ? JSON.stringify(v) : String(v);
|
|
1341
|
+
return encodeReservedAware(raw, allowReserved);
|
|
1342
|
+
};
|
|
1343
|
+
var serializeFormUrlEncoded = (value, encoding) => {
|
|
1344
|
+
const parts = [];
|
|
1345
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
1346
|
+
if (raw === void 0 || raw === null) continue;
|
|
1347
|
+
const { style, explode, allowReserved } = resolveStyleExplode(encoding?.[key]);
|
|
1348
|
+
const encKey = encodeURIComponent(key);
|
|
1349
|
+
if (Array.isArray(raw)) {
|
|
1350
|
+
if (explode) {
|
|
1351
|
+
for (const v of raw) {
|
|
1352
|
+
parts.push(`${encKey}=${encodeFormValue(v, allowReserved)}`);
|
|
1353
|
+
}
|
|
1354
|
+
} else {
|
|
1355
|
+
const sep = style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ",";
|
|
1356
|
+
parts.push(
|
|
1357
|
+
`${encKey}=${encodeFormValue(
|
|
1358
|
+
raw.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)).join(sep),
|
|
1359
|
+
allowReserved
|
|
1360
|
+
)}`
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
if (typeof raw === "object") {
|
|
1366
|
+
const entries = Object.entries(raw).filter(
|
|
1367
|
+
([, v]) => v !== void 0 && v !== null
|
|
1368
|
+
);
|
|
1369
|
+
if (style === "deepObject") {
|
|
1370
|
+
for (const [subkey, subval] of entries) {
|
|
1371
|
+
parts.push(
|
|
1372
|
+
`${encodeURIComponent(`${key}[${subkey}]`)}=${encodeFormValue(subval, allowReserved)}`
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
} else if (explode) {
|
|
1376
|
+
for (const [subkey, subval] of entries) {
|
|
1377
|
+
parts.push(`${encodeURIComponent(subkey)}=${encodeFormValue(subval, allowReserved)}`);
|
|
1378
|
+
}
|
|
1379
|
+
} else {
|
|
1380
|
+
const flat = entries.flatMap(([k, v]) => [
|
|
1381
|
+
k,
|
|
1382
|
+
typeof v === "object" ? JSON.stringify(v) : String(v)
|
|
1383
|
+
]);
|
|
1384
|
+
parts.push(`${encKey}=${encodeFormValue(flat.join(","), allowReserved)}`);
|
|
1385
|
+
}
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
parts.push(`${encKey}=${encodeFormValue(raw, allowReserved)}`);
|
|
1389
|
+
}
|
|
1390
|
+
return parts.join("&");
|
|
1391
|
+
};
|
|
1392
|
+
var coerceFormDataRecord = (value, encoding) => {
|
|
1393
|
+
const out = {};
|
|
1394
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
1395
|
+
if (raw === void 0 || raw === null) continue;
|
|
1396
|
+
const partType = encoding?.[key] ? Option3.getOrUndefined(encoding[key].contentType) : void 0;
|
|
1397
|
+
if (partType) {
|
|
1398
|
+
const isJson = partType.startsWith("application/json") || partType.includes("+json");
|
|
1399
|
+
const serialized = typeof raw === "string" ? raw : isJson ? JSON.stringify(raw) : typeof raw === "object" ? JSON.stringify(raw) : String(raw);
|
|
1400
|
+
out[key] = new Blob([serialized], { type: partType });
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
if (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean" || raw instanceof Blob || typeof File !== "undefined" && raw instanceof File) {
|
|
1404
|
+
out[key] = raw;
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
if (Array.isArray(raw)) {
|
|
1408
|
+
out[key] = raw.map(
|
|
1409
|
+
(v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v instanceof Blob || typeof File !== "undefined" && v instanceof File ? v : JSON.stringify(v)
|
|
1410
|
+
);
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
const bytes = toUint8Array(raw);
|
|
1414
|
+
if (bytes) {
|
|
1415
|
+
out[key] = new Blob([toArrayBuffer(bytes)]);
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
out[key] = JSON.stringify(raw);
|
|
1419
|
+
}
|
|
1420
|
+
return out;
|
|
1421
|
+
};
|
|
1422
|
+
var applyRequestBody = (request, contentType, bodyValue, encoding) => {
|
|
1423
|
+
if (isJsonContentType(contentType)) {
|
|
1424
|
+
if (typeof bodyValue === "string") {
|
|
1425
|
+
return HttpClientRequest2.bodyText(request, bodyValue, contentType);
|
|
1426
|
+
}
|
|
1427
|
+
return HttpClientRequest2.bodyJsonUnsafe(request, bodyValue);
|
|
1428
|
+
}
|
|
1429
|
+
if (isFormUrlEncoded(contentType)) {
|
|
1430
|
+
if (typeof bodyValue === "string") {
|
|
1431
|
+
return HttpClientRequest2.bodyText(request, bodyValue, contentType);
|
|
1432
|
+
}
|
|
1433
|
+
if (typeof bodyValue === "object" && bodyValue !== null && !Array.isArray(bodyValue)) {
|
|
1434
|
+
const serialized = serializeFormUrlEncoded(bodyValue, encoding);
|
|
1435
|
+
return HttpClientRequest2.bodyText(request, serialized, contentType);
|
|
1436
|
+
}
|
|
1437
|
+
return HttpClientRequest2.bodyUrlParams(
|
|
1438
|
+
request,
|
|
1439
|
+
bodyValue
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
if (isMultipartFormData(contentType)) {
|
|
1443
|
+
if (bodyValue instanceof FormData) {
|
|
1444
|
+
return HttpClientRequest2.bodyFormData(request, bodyValue);
|
|
1445
|
+
}
|
|
1446
|
+
if (typeof bodyValue === "object" && bodyValue !== null) {
|
|
1447
|
+
return HttpClientRequest2.bodyFormDataRecord(
|
|
1448
|
+
request,
|
|
1449
|
+
coerceFormDataRecord(bodyValue, encoding)
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
return HttpClientRequest2.bodyText(request, String(bodyValue), contentType);
|
|
1453
|
+
}
|
|
1454
|
+
if (isOctetStream(contentType)) {
|
|
1455
|
+
const bytes2 = toUint8Array(bodyValue);
|
|
1456
|
+
if (bytes2) return HttpClientRequest2.bodyUint8Array(request, bytes2, contentType);
|
|
1457
|
+
if (typeof bodyValue === "string") {
|
|
1458
|
+
return HttpClientRequest2.bodyText(request, bodyValue, contentType);
|
|
1459
|
+
}
|
|
1460
|
+
return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
|
|
1461
|
+
}
|
|
1462
|
+
if (isXmlContentType(contentType) || isTextContentType(contentType)) {
|
|
1463
|
+
if (typeof bodyValue === "string") {
|
|
1464
|
+
return HttpClientRequest2.bodyText(request, bodyValue, contentType);
|
|
1465
|
+
}
|
|
1466
|
+
const bytes2 = toUint8Array(bodyValue);
|
|
1467
|
+
if (bytes2) return HttpClientRequest2.bodyUint8Array(request, bytes2, contentType);
|
|
1468
|
+
return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
|
|
1469
|
+
}
|
|
1470
|
+
if (typeof bodyValue === "string") {
|
|
1471
|
+
return HttpClientRequest2.bodyText(request, bodyValue, contentType);
|
|
1472
|
+
}
|
|
1473
|
+
const bytes = toUint8Array(bodyValue);
|
|
1474
|
+
if (bytes) return HttpClientRequest2.bodyUint8Array(request, bytes, contentType);
|
|
1475
|
+
return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
|
|
1476
|
+
};
|
|
1477
|
+
var acceptedArgumentNames = (operation) => {
|
|
1478
|
+
const accepted = /* @__PURE__ */ new Set();
|
|
1479
|
+
for (const param of operation.parameters) {
|
|
1480
|
+
accepted.add(param.name);
|
|
1481
|
+
for (const container of CONTAINER_KEYS[param.location] ?? []) accepted.add(container);
|
|
1482
|
+
}
|
|
1483
|
+
for (const match of operation.pathTemplate.matchAll(/\{([^{}]+)\}/g)) {
|
|
1484
|
+
if (match[1]) accepted.add(match[1]);
|
|
1485
|
+
}
|
|
1486
|
+
if (Option3.isSome(operation.requestBody)) {
|
|
1487
|
+
const requestBody = operation.requestBody.value;
|
|
1488
|
+
const contents = Option3.getOrUndefined(requestBody.contents);
|
|
1489
|
+
accepted.add("body");
|
|
1490
|
+
accepted.add("input");
|
|
1491
|
+
if (isOctetStream(requestBody.contentType) || contents?.some((content) => isOctetStream(content.contentType))) {
|
|
1492
|
+
accepted.add("bodyBase64");
|
|
1493
|
+
}
|
|
1494
|
+
if (contents && contents.length > 1) accepted.add("contentType");
|
|
1495
|
+
}
|
|
1496
|
+
const servers = operation.servers ?? [];
|
|
1497
|
+
const hasServerVariables = servers.some(
|
|
1498
|
+
(server) => Object.keys(Option3.getOrUndefined(server.variables) ?? {}).length > 0
|
|
1499
|
+
);
|
|
1500
|
+
if (servers.length > 1 || hasServerVariables) accepted.add("server");
|
|
1501
|
+
return [...accepted];
|
|
1502
|
+
};
|
|
1503
|
+
var buildRequest = Effect3.fn("OpenApi.buildRequest")(function* (operation, args, resolvedHeaders, integrationQueryParams = {}) {
|
|
1504
|
+
const accepted = acceptedArgumentNames(operation);
|
|
1505
|
+
const acceptedSet = new Set(accepted);
|
|
1506
|
+
const unknown = Object.keys(args).filter((name) => !acceptedSet.has(name));
|
|
1507
|
+
if (unknown.length > 0) {
|
|
1508
|
+
const label = unknown.length === 1 ? "Unknown argument" : "Unknown arguments";
|
|
1509
|
+
const names = unknown.map((name) => JSON.stringify(name)).join(", ");
|
|
1510
|
+
const acceptedMessage = accepted.length > 0 ? `This operation accepts: ${accepted.join(", ")}.` : "This operation accepts no arguments.";
|
|
1511
|
+
return yield* new OpenApiInvocationError({
|
|
1512
|
+
message: `${label} ${names}. ${acceptedMessage}`,
|
|
1513
|
+
statusCode: Option3.none(),
|
|
1514
|
+
reason: "unknown_arguments"
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
const resolvedPath = yield* resolvePath(operation.pathTemplate, args, operation.parameters);
|
|
1518
|
+
const path = resolvedPath.startsWith("/") ? resolvedPath : `/${resolvedPath}`;
|
|
1519
|
+
let request = HttpClientRequest2.make(operation.method.toUpperCase())(path);
|
|
1520
|
+
request = HttpClientRequest2.setHeader(request, "User-Agent", DEFAULT_USER_AGENT);
|
|
1521
|
+
for (const [name, value] of Object.entries(integrationQueryParams)) {
|
|
1522
|
+
request = HttpClientRequest2.setUrlParam(request, name, value);
|
|
1523
|
+
}
|
|
1524
|
+
for (const param of operation.parameters) {
|
|
1525
|
+
if (param.location !== "query") continue;
|
|
1526
|
+
const value = readParamValue(args, param);
|
|
1527
|
+
for (const paramValue of queryParamValues(value, param)) {
|
|
1528
|
+
request = HttpClientRequest2.appendUrlParam(request, param.name, paramValue);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
for (const param of operation.parameters) {
|
|
1532
|
+
if (param.location !== "header") continue;
|
|
1533
|
+
const value = readParamValue(args, param);
|
|
1534
|
+
if (value === void 0 || value === null) continue;
|
|
1535
|
+
request = HttpClientRequest2.setHeader(request, param.name, String(value));
|
|
1536
|
+
}
|
|
1537
|
+
const cookieValues = [];
|
|
1538
|
+
for (const param of operation.parameters) {
|
|
1539
|
+
if (param.location !== "cookie") continue;
|
|
1540
|
+
const value = readParamValue(args, param);
|
|
1541
|
+
if (value === void 0 || value === null) continue;
|
|
1542
|
+
cookieValues.push(`${param.name}=${primitiveToString(value)}`);
|
|
1543
|
+
}
|
|
1544
|
+
if (Option3.isSome(operation.requestBody)) {
|
|
1545
|
+
const rb = operation.requestBody.value;
|
|
1546
|
+
const contentsOpt = Option3.getOrUndefined(rb.contents);
|
|
1547
|
+
const requestedCt = typeof args.contentType === "string" ? args.contentType : void 0;
|
|
1548
|
+
const octetStreamContent = contentsOpt?.find((c) => isOctetStream(c.contentType));
|
|
1549
|
+
const bodyAcceptsOctetStream = Boolean(octetStreamContent) || isOctetStream(rb.contentType);
|
|
1550
|
+
const hasBodyBase64 = Object.prototype.hasOwnProperty.call(args, "bodyBase64");
|
|
1551
|
+
const bodyBase64Raw = args.bodyBase64;
|
|
1552
|
+
const bodyBase64 = typeof bodyBase64Raw === "string" ? decodeBase64Body(bodyBase64Raw) : void 0;
|
|
1553
|
+
if (hasBodyBase64 && typeof bodyBase64Raw !== "string") {
|
|
1554
|
+
return yield* new OpenApiInvocationError({
|
|
1555
|
+
message: "`bodyBase64` must be a base64 string",
|
|
1556
|
+
statusCode: Option3.none()
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
if (bodyBase64?.ok === false) {
|
|
1560
|
+
return yield* new OpenApiInvocationError({
|
|
1561
|
+
message: "`bodyBase64` is not valid base64",
|
|
1562
|
+
statusCode: Option3.none()
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
if (bodyBase64?.ok === true && !bodyAcceptsOctetStream) {
|
|
1566
|
+
return yield* new OpenApiInvocationError({
|
|
1567
|
+
message: "`bodyBase64` requires an application/octet-stream request body",
|
|
1568
|
+
statusCode: Option3.none()
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
if (bodyBase64?.ok === true && requestedCt && !isOctetStream(requestedCt)) {
|
|
1572
|
+
return yield* new OpenApiInvocationError({
|
|
1573
|
+
message: "`bodyBase64` requires an application/octet-stream contentType",
|
|
1574
|
+
statusCode: Option3.none()
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
const rawBodyValue = args.body ?? args.input;
|
|
1578
|
+
const nestedBodyBase64Raw = readNestedBodyBase64(rawBodyValue);
|
|
1579
|
+
const hasNestedBodyBase64 = nestedBodyBase64Raw !== void 0;
|
|
1580
|
+
const nestedBodyBase64 = typeof nestedBodyBase64Raw === "string" ? decodeBase64Body(nestedBodyBase64Raw) : void 0;
|
|
1581
|
+
if (hasNestedBodyBase64 && typeof nestedBodyBase64Raw !== "string") {
|
|
1582
|
+
return yield* new OpenApiInvocationError({
|
|
1583
|
+
message: "`body.bodyBase64` must be a base64 string",
|
|
1584
|
+
statusCode: Option3.none()
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
if (nestedBodyBase64?.ok === false) {
|
|
1588
|
+
return yield* new OpenApiInvocationError({
|
|
1589
|
+
message: "`body.bodyBase64` is not valid base64",
|
|
1590
|
+
statusCode: Option3.none()
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
if (nestedBodyBase64?.ok === true && !bodyAcceptsOctetStream) {
|
|
1594
|
+
return yield* new OpenApiInvocationError({
|
|
1595
|
+
message: "`body.bodyBase64` requires an application/octet-stream request body",
|
|
1596
|
+
statusCode: Option3.none()
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
if (nestedBodyBase64?.ok === true && requestedCt && !isOctetStream(requestedCt)) {
|
|
1600
|
+
return yield* new OpenApiInvocationError({
|
|
1601
|
+
message: "`body.bodyBase64` requires an application/octet-stream contentType",
|
|
1602
|
+
statusCode: Option3.none()
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
const binaryBody = bodyBase64?.ok === true ? bodyBase64 : nestedBodyBase64;
|
|
1606
|
+
const bodyValue = binaryBody?.ok === true ? binaryBody.bytes : rawBodyValue;
|
|
1607
|
+
if (rb.required && bodyValue === void 0) {
|
|
1608
|
+
return yield* new OpenApiInvocationError({
|
|
1609
|
+
message: bodyAcceptsOctetStream ? "Missing required request body: provide `bodyBase64`" : "Missing required request body",
|
|
1610
|
+
statusCode: Option3.none()
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
if (bodyValue !== void 0) {
|
|
1614
|
+
const selected = binaryBody?.ok === true && octetStreamContent ? octetStreamContent : contentsOpt && requestedCt ? contentsOpt.find((c) => c.contentType === requestedCt) : void 0;
|
|
1615
|
+
const chosenCt = binaryBody?.ok === true && !octetStreamContent && isOctetStream(rb.contentType) ? rb.contentType : selected?.contentType ?? rb.contentType;
|
|
1616
|
+
if (isOctetStream(chosenCt) && typeof bodyValue !== "string" && toUint8Array(bodyValue) === null) {
|
|
1617
|
+
return yield* new OpenApiInvocationError({
|
|
1618
|
+
message: "application/octet-stream request body must be bytes; provide `bodyBase64`",
|
|
1619
|
+
statusCode: Option3.none()
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
const chosenEncoding = selected ? Option3.getOrUndefined(selected.encoding) : contentsOpt && contentsOpt[0] ? Option3.getOrUndefined(contentsOpt[0].encoding) : void 0;
|
|
1623
|
+
request = applyRequestBody(request, chosenCt, bodyValue, chosenEncoding);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
request = applyHeaders(request, resolvedHeaders);
|
|
1627
|
+
if (cookieValues.length > 0) {
|
|
1628
|
+
const existingCookie = request.headers.cookie;
|
|
1629
|
+
const serializedCookies = cookieValues.join("; ");
|
|
1630
|
+
request = HttpClientRequest2.setHeader(
|
|
1631
|
+
request,
|
|
1632
|
+
"Cookie",
|
|
1633
|
+
existingCookie ? `${existingCookie}; ${serializedCookies}` : serializedCookies
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
return request;
|
|
1637
|
+
});
|
|
1638
|
+
var invoke = Effect3.fn("OpenApi.invoke")(function* (operation, args, resolvedHeaders, integrationQueryParams = {}, options = {}) {
|
|
1639
|
+
const client = yield* HttpClient2.HttpClient;
|
|
1640
|
+
yield* Effect3.annotateCurrentSpan({
|
|
1641
|
+
"http.method": operation.method.toUpperCase(),
|
|
1642
|
+
"http.route": operation.pathTemplate,
|
|
1643
|
+
"plugin.openapi.method": operation.method.toUpperCase(),
|
|
1644
|
+
"plugin.openapi.path_template": operation.pathTemplate,
|
|
1645
|
+
"plugin.openapi.headers.resolved_count": Object.keys(resolvedHeaders).length
|
|
1646
|
+
});
|
|
1647
|
+
const request = yield* buildRequest(operation, args, resolvedHeaders, integrationQueryParams);
|
|
1648
|
+
const responseHeadersTimeoutMs = options.responseHeadersTimeoutMs ?? RESPONSE_HEADERS_TIMEOUT_MS;
|
|
1649
|
+
const runFork = Effect3.runForkWith(yield* Effect3.context());
|
|
1650
|
+
const responseExitOption = yield* Effect3.callback((resume, signal) => {
|
|
1651
|
+
let settled = false;
|
|
1652
|
+
const responseEffect = client.execute(request).pipe(
|
|
1653
|
+
Effect3.mapError(
|
|
1654
|
+
(err) => new OpenApiInvocationError({
|
|
1655
|
+
message: "HTTP request failed",
|
|
1656
|
+
statusCode: Option3.none(),
|
|
1657
|
+
cause: err
|
|
1658
|
+
})
|
|
1659
|
+
),
|
|
1660
|
+
Effect3.exit,
|
|
1661
|
+
Effect3.tap(
|
|
1662
|
+
(exit) => Effect3.sync(() => {
|
|
1663
|
+
if (settled) return;
|
|
1664
|
+
settled = true;
|
|
1665
|
+
clearTimeout(timer);
|
|
1666
|
+
resume(Effect3.succeed(Option3.some(exit)));
|
|
1667
|
+
})
|
|
1668
|
+
)
|
|
1669
|
+
);
|
|
1670
|
+
const fiber = runFork(responseEffect);
|
|
1671
|
+
const interrupt = () => {
|
|
1672
|
+
runFork(Fiber.interrupt(fiber));
|
|
1673
|
+
};
|
|
1674
|
+
const timer = setTimeout(() => {
|
|
1675
|
+
if (settled) return;
|
|
1676
|
+
settled = true;
|
|
1677
|
+
interrupt();
|
|
1678
|
+
resume(Effect3.succeed(Option3.none()));
|
|
1679
|
+
}, responseHeadersTimeoutMs);
|
|
1680
|
+
signal.addEventListener("abort", interrupt, { once: true });
|
|
1681
|
+
return Effect3.sync(() => {
|
|
1682
|
+
clearTimeout(timer);
|
|
1683
|
+
signal.removeEventListener("abort", interrupt);
|
|
1684
|
+
if (!settled) interrupt();
|
|
1685
|
+
});
|
|
1686
|
+
});
|
|
1687
|
+
if (Option3.isNone(responseExitOption)) {
|
|
1688
|
+
return yield* new OpenApiInvocationError({
|
|
1689
|
+
message: responseHeadersTimeoutMessage(responseHeadersTimeoutMs),
|
|
1690
|
+
statusCode: Option3.none(),
|
|
1691
|
+
reason: "response_headers_timeout"
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
const responseExit = responseExitOption.value;
|
|
1695
|
+
if (Exit.isFailure(responseExit)) return yield* Effect3.failCause(responseExit.cause);
|
|
1696
|
+
const response = responseExit.value;
|
|
1697
|
+
const status = response.status;
|
|
1698
|
+
yield* Effect3.annotateCurrentSpan({
|
|
1699
|
+
"http.status_code": status
|
|
1700
|
+
});
|
|
1701
|
+
const responseHeaders = { ...response.headers };
|
|
1702
|
+
const contentType = response.headers["content-type"] ?? null;
|
|
1703
|
+
const mapBodyError = Effect3.mapError(
|
|
1704
|
+
(err) => new OpenApiInvocationError({
|
|
1705
|
+
message: "Failed to read response body",
|
|
1706
|
+
statusCode: Option3.some(status),
|
|
1707
|
+
cause: err
|
|
1708
|
+
})
|
|
1709
|
+
);
|
|
1710
|
+
const responseBodyBinding = Option3.getOrUndefined(operation.responseBody);
|
|
1711
|
+
const fileHint = responseBodyBinding ? Option3.getOrUndefined(responseBodyBinding.fileHint) : void 0;
|
|
1712
|
+
const ok = status >= 200 && status < 300;
|
|
1713
|
+
const streamingBody = ok && status !== 204 && fileHint?.kind !== "binaryResponse" && isStreamingResponseContentType(contentType) ? yield* collectStreamingBody(response.stream, contentType).pipe(mapBodyError) : null;
|
|
1714
|
+
if (streamingBody) {
|
|
1715
|
+
Object.assign(responseHeaders, streamingBody.headers);
|
|
1716
|
+
}
|
|
1717
|
+
const responseBody = status === 204 ? null : ok && fileHint?.kind === "binaryResponse" ? fileFromBinaryBytes(
|
|
1718
|
+
new Uint8Array(yield* response.arrayBuffer.pipe(mapBodyError)),
|
|
1719
|
+
fileHint,
|
|
1720
|
+
contentType
|
|
1721
|
+
) : streamingBody ? streamingBody.data : isJsonContentType(contentType) ? yield* response.json.pipe(
|
|
1722
|
+
Effect3.catch(() => response.text),
|
|
1723
|
+
mapBodyError
|
|
1724
|
+
) : yield* response.text.pipe(mapBodyError);
|
|
1725
|
+
const dataBody = ok && fileHint?.kind === "byteField" ? fileFromByteField(responseBody, fileHint) ?? responseBody : responseBody;
|
|
1726
|
+
return InvocationResult.make({
|
|
1727
|
+
status,
|
|
1728
|
+
headers: responseHeaders,
|
|
1729
|
+
data: ok ? dataBody : null,
|
|
1730
|
+
error: ok ? null : responseBody
|
|
1731
|
+
});
|
|
1732
|
+
});
|
|
1733
|
+
var resolveRequestHost = (servers, serverArg, baseUrl) => {
|
|
1734
|
+
if (baseUrl) return baseUrl;
|
|
1735
|
+
if (servers.length === 0) return "";
|
|
1736
|
+
const arg = typeof serverArg === "object" && serverArg !== null && !Array.isArray(serverArg) ? serverArg : {};
|
|
1737
|
+
const chosen = servers.find((server) => server.url === arg.url) ?? servers[0];
|
|
1738
|
+
const overrides = {};
|
|
1739
|
+
if (typeof arg.variables === "object" && arg.variables !== null) {
|
|
1740
|
+
for (const [name, value] of Object.entries(arg.variables)) {
|
|
1741
|
+
if (value != null && value !== "") overrides[name] = String(value);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
return resolveServerUrl(chosen.url, Option3.getOrUndefined(chosen.variables), overrides);
|
|
1745
|
+
};
|
|
1746
|
+
var invokeWithLayer = (operation, args, baseUrl, resolvedHeaders, integrationQueryParams, httpClientLayer, options = {}) => {
|
|
1747
|
+
const effectiveBaseUrl = resolveRequestHost(operation.servers ?? [], args.server, baseUrl);
|
|
1748
|
+
const clientWithBaseUrl = effectiveBaseUrl ? Layer.effect(
|
|
1749
|
+
HttpClient2.HttpClient,
|
|
1750
|
+
Effect3.map(
|
|
1751
|
+
Effect3.service(HttpClient2.HttpClient),
|
|
1752
|
+
HttpClient2.mapRequest(HttpClientRequest2.prependUrl(effectiveBaseUrl))
|
|
1753
|
+
)
|
|
1754
|
+
).pipe(Layer.provide(httpClientLayer)) : httpClientLayer;
|
|
1755
|
+
return invoke(operation, args, resolvedHeaders, integrationQueryParams, options).pipe(
|
|
1756
|
+
Effect3.provide(clientWithBaseUrl),
|
|
1757
|
+
// `invoke` annotates http.status_code on ITS span (`OpenApi.invoke`,
|
|
1758
|
+
// via Effect.fn) — annotateCurrentSpan inside it never reaches this
|
|
1759
|
+
// wrapper span. Stamp the status here too so queries against
|
|
1760
|
+
// `plugin.openapi.invoke` see the upstream outcome directly.
|
|
1761
|
+
Effect3.tap((result) => Effect3.annotateCurrentSpan({ "http.status_code": result.status })),
|
|
1762
|
+
Effect3.withSpan("plugin.openapi.invoke", {
|
|
1763
|
+
attributes: {
|
|
1764
|
+
"plugin.openapi.method": operation.method.toUpperCase(),
|
|
1765
|
+
"plugin.openapi.path_template": operation.pathTemplate,
|
|
1766
|
+
"plugin.openapi.base_url": effectiveBaseUrl
|
|
1767
|
+
}
|
|
1768
|
+
})
|
|
1769
|
+
);
|
|
1770
|
+
};
|
|
1771
|
+
var REQUIRE_APPROVAL = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
|
|
1772
|
+
var annotationsForOperation = (method, pathTemplate) => {
|
|
1773
|
+
const m = method.toLowerCase();
|
|
1774
|
+
if (!REQUIRE_APPROVAL.has(m)) return {};
|
|
1775
|
+
return {
|
|
1776
|
+
requiresApproval: true,
|
|
1777
|
+
approvalDescription: `${method.toUpperCase()} ${pathTemplate}`
|
|
1778
|
+
};
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
// src/sdk/backing.ts
|
|
1782
|
+
import { Effect as Effect4, Option as Option4, Schema as Schema4 } from "effect";
|
|
1783
|
+
import {
|
|
1784
|
+
ToolFileJsonSchema,
|
|
1785
|
+
ToolName,
|
|
1786
|
+
ToolResult,
|
|
1787
|
+
authToolFailure,
|
|
1788
|
+
classifyHttpStatus,
|
|
1789
|
+
detectInsufficientScope,
|
|
1790
|
+
sortHealthCheckCandidatesByIdentity,
|
|
1791
|
+
extractIdentity,
|
|
1792
|
+
extractResponseFields,
|
|
1793
|
+
projectResponseFields
|
|
1794
|
+
} from "@rafads/sdk/core";
|
|
1795
|
+
var STRINGIFIED_BODY_CAP = 1024;
|
|
1796
|
+
var UpstreamMessageBody = Schema4.Struct({ message: Schema4.String });
|
|
1797
|
+
var UpstreamErrorMessageBody = Schema4.Struct({ errorMessage: Schema4.String });
|
|
1798
|
+
var UpstreamNestedErrorBody = Schema4.Struct({ error: UpstreamMessageBody });
|
|
1799
|
+
var UpstreamErrorsArrayBody = Schema4.Struct({
|
|
1800
|
+
errors: Schema4.Array(
|
|
1801
|
+
Schema4.Struct({
|
|
1802
|
+
detail: Schema4.optional(Schema4.String),
|
|
1803
|
+
message: Schema4.optional(Schema4.String),
|
|
1804
|
+
title: Schema4.optional(Schema4.String)
|
|
1805
|
+
})
|
|
1806
|
+
)
|
|
1807
|
+
});
|
|
1808
|
+
var UpstreamDescriptionBody = Schema4.Struct({
|
|
1809
|
+
detail: Schema4.optional(Schema4.String),
|
|
1810
|
+
title: Schema4.optional(Schema4.String),
|
|
1811
|
+
description: Schema4.optional(Schema4.String)
|
|
1812
|
+
});
|
|
1813
|
+
var decodeUpstreamMessageBody = Schema4.decodeUnknownOption(UpstreamMessageBody);
|
|
1814
|
+
var decodeUpstreamErrorMessageBody = Schema4.decodeUnknownOption(UpstreamErrorMessageBody);
|
|
1815
|
+
var decodeUpstreamNestedErrorBody = Schema4.decodeUnknownOption(UpstreamNestedErrorBody);
|
|
1816
|
+
var decodeUpstreamErrorsArrayBody = Schema4.decodeUnknownOption(UpstreamErrorsArrayBody);
|
|
1817
|
+
var decodeUpstreamDescriptionBody = Schema4.decodeUnknownOption(UpstreamDescriptionBody);
|
|
1818
|
+
var clampedStringify = (value) => {
|
|
1819
|
+
let s;
|
|
1820
|
+
try {
|
|
1821
|
+
s = JSON.stringify(value);
|
|
1822
|
+
} catch {
|
|
1823
|
+
s = String(value);
|
|
1824
|
+
}
|
|
1825
|
+
return s.length > STRINGIFIED_BODY_CAP ? `${s.slice(0, STRINGIFIED_BODY_CAP)}\u2026` : s;
|
|
1826
|
+
};
|
|
1827
|
+
var firstNonEmpty = (...values) => values.find((value) => value !== void 0 && value.length > 0);
|
|
1828
|
+
var extractOpenApiUpstreamMessage = (body, status) => {
|
|
1829
|
+
if (typeof body === "string") {
|
|
1830
|
+
return body.length > 0 ? body : `Upstream returned HTTP ${status}`;
|
|
1831
|
+
}
|
|
1832
|
+
const nested = Option4.getOrUndefined(decodeUpstreamNestedErrorBody(body));
|
|
1833
|
+
const messageBody = Option4.getOrUndefined(decodeUpstreamMessageBody(body));
|
|
1834
|
+
const errorMessageBody = Option4.getOrUndefined(decodeUpstreamErrorMessageBody(body));
|
|
1835
|
+
const errorsBody = Option4.getOrUndefined(decodeUpstreamErrorsArrayBody(body));
|
|
1836
|
+
const descriptionBody = Option4.getOrUndefined(decodeUpstreamDescriptionBody(body));
|
|
1837
|
+
const arrayMessage = errorsBody?.errors.map(
|
|
1838
|
+
({
|
|
1839
|
+
detail,
|
|
1840
|
+
message: upstreamMessage,
|
|
1841
|
+
title
|
|
1842
|
+
}) => firstNonEmpty(detail, upstreamMessage, title)
|
|
1843
|
+
).find((message2) => message2 !== void 0);
|
|
1844
|
+
const message = firstNonEmpty(
|
|
1845
|
+
nested?.error.message,
|
|
1846
|
+
messageBody?.message,
|
|
1847
|
+
errorMessageBody?.errorMessage,
|
|
1848
|
+
arrayMessage,
|
|
1849
|
+
descriptionBody?.detail,
|
|
1850
|
+
descriptionBody?.title,
|
|
1851
|
+
descriptionBody?.description
|
|
1852
|
+
);
|
|
1853
|
+
if (message !== void 0) return message;
|
|
1854
|
+
if (body !== null && typeof body === "object") {
|
|
1855
|
+
return clampedStringify(body);
|
|
1856
|
+
}
|
|
1857
|
+
return `Upstream returned HTTP ${status}`;
|
|
1858
|
+
};
|
|
1859
|
+
var openApiAuthToolFailure = (failure) => authToolFailure({
|
|
1860
|
+
code: failure.code,
|
|
1861
|
+
message: failure.message,
|
|
1862
|
+
integration: { id: failure.integration, scope: failure.owner },
|
|
1863
|
+
credential: {
|
|
1864
|
+
kind: failure.credentialKind,
|
|
1865
|
+
...failure.credentialLabel ? { label: failure.credentialLabel } : {}
|
|
1866
|
+
},
|
|
1867
|
+
...failure.status !== void 0 ? { status: failure.status } : {},
|
|
1868
|
+
...failure.details !== void 0 ? {
|
|
1869
|
+
upstream: {
|
|
1870
|
+
...failure.status !== void 0 ? { status: failure.status } : {},
|
|
1871
|
+
details: failure.details
|
|
1872
|
+
}
|
|
1873
|
+
} : {}
|
|
1874
|
+
});
|
|
1875
|
+
var normalizeOpenApiRefs = (node) => {
|
|
1876
|
+
if (node == null || typeof node !== "object") return node;
|
|
1877
|
+
if (Array.isArray(node)) {
|
|
1878
|
+
let changed2 = false;
|
|
1879
|
+
const out = node.map((item) => {
|
|
1880
|
+
const n = normalizeOpenApiRefs(item);
|
|
1881
|
+
if (n !== item) changed2 = true;
|
|
1882
|
+
return n;
|
|
1883
|
+
});
|
|
1884
|
+
return changed2 ? out : node;
|
|
1885
|
+
}
|
|
1886
|
+
const obj = node;
|
|
1887
|
+
if (typeof obj.$ref === "string") {
|
|
1888
|
+
const match = obj.$ref.match(/^#\/components\/schemas\/(.+)$/);
|
|
1889
|
+
if (match) return { ...obj, $ref: `#/$defs/${match[1]}` };
|
|
1890
|
+
return obj;
|
|
1891
|
+
}
|
|
1892
|
+
let changed = false;
|
|
1893
|
+
const result = {};
|
|
1894
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1895
|
+
const n = normalizeOpenApiRefs(v);
|
|
1896
|
+
if (n !== v) changed = true;
|
|
1897
|
+
result[k] = n;
|
|
1898
|
+
}
|
|
1899
|
+
return changed ? result : obj;
|
|
1900
|
+
};
|
|
1901
|
+
var toBinding = (def) => OperationBinding.make({
|
|
1902
|
+
method: def.operation.method,
|
|
1903
|
+
servers: def.operation.servers,
|
|
1904
|
+
pathTemplate: def.operation.pathTemplate,
|
|
1905
|
+
parameters: [...def.operation.parameters],
|
|
1906
|
+
requestBody: def.operation.requestBody,
|
|
1907
|
+
responseBody: def.operation.responseBody,
|
|
1908
|
+
...def.operation.requiredScopeAlternatives ? { requiredScopeAlternatives: def.operation.requiredScopeAlternatives } : {}
|
|
1909
|
+
});
|
|
1910
|
+
var descriptionFor = (def) => {
|
|
1911
|
+
const op = def.operation;
|
|
1912
|
+
return Option4.getOrElse(
|
|
1913
|
+
op.description,
|
|
1914
|
+
() => Option4.getOrElse(op.summary, () => `${op.method.toUpperCase()} ${op.pathTemplate}`)
|
|
1915
|
+
);
|
|
1916
|
+
};
|
|
1917
|
+
var FILE_OUTPUT_HINT = `Returns a ToolFile: the file bytes already decoded into { _tag: "ToolFile", mimeType, encoding, data, byteLength }. To display or forward it, pass the result's data straight to emit(result.data). Do not rebuild the envelope or read upstream fields like size.`;
|
|
1918
|
+
var withFileEmitHint = (description, returnsFile) => returnsFile ? `${description}
|
|
1919
|
+
|
|
1920
|
+
${FILE_OUTPUT_HINT}` : description;
|
|
1921
|
+
var compileOpenApiDocument = (doc) => Effect4.gen(function* () {
|
|
1922
|
+
const result = yield* extract(doc);
|
|
1923
|
+
const hoistedDefs = {};
|
|
1924
|
+
if (doc.components?.schemas) {
|
|
1925
|
+
for (const [k, v] of Object.entries(doc.components.schemas)) {
|
|
1926
|
+
hoistedDefs[k] = normalizeOpenApiRefs(v);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
return {
|
|
1930
|
+
definitions: compileToolDefinitions(result.operations),
|
|
1931
|
+
hoistedDefs,
|
|
1932
|
+
title: Option4.getOrUndefined(result.title),
|
|
1933
|
+
description: Option4.getOrUndefined(result.description)
|
|
1934
|
+
};
|
|
1935
|
+
});
|
|
1936
|
+
var compileOpenApiSpec = (specText) => Effect4.gen(function* () {
|
|
1937
|
+
const doc = yield* parse(specText);
|
|
1938
|
+
return yield* compileOpenApiDocument(doc);
|
|
1939
|
+
});
|
|
1940
|
+
var COMPILED_SPEC_CACHE_CAPACITY = 4;
|
|
1941
|
+
var compiledSpecCache = /* @__PURE__ */ new Map();
|
|
1942
|
+
var compileOpenApiSpecCached = (specHash, specText) => Effect4.gen(function* () {
|
|
1943
|
+
if (specHash == null) return yield* compileOpenApiSpec(specText);
|
|
1944
|
+
const hit = compiledSpecCache.get(specHash);
|
|
1945
|
+
if (hit !== void 0) {
|
|
1946
|
+
compiledSpecCache.delete(specHash);
|
|
1947
|
+
compiledSpecCache.set(specHash, hit);
|
|
1948
|
+
return hit;
|
|
1949
|
+
}
|
|
1950
|
+
const compiled = yield* compileOpenApiSpec(specText);
|
|
1951
|
+
compiledSpecCache.set(specHash, compiled);
|
|
1952
|
+
while (compiledSpecCache.size > COMPILED_SPEC_CACHE_CAPACITY) {
|
|
1953
|
+
const oldest = compiledSpecCache.keys().next().value;
|
|
1954
|
+
if (oldest === void 0) break;
|
|
1955
|
+
compiledSpecCache.delete(oldest);
|
|
1956
|
+
}
|
|
1957
|
+
return compiled;
|
|
1958
|
+
});
|
|
1959
|
+
var openApiToolDefsFromCompiled = (compiled) => compiled.definitions.map((def) => {
|
|
1960
|
+
const returnsFile = Option4.match(def.operation.responseBody, {
|
|
1961
|
+
onNone: () => false,
|
|
1962
|
+
onSome: (responseBody) => Option4.isSome(responseBody.fileHint)
|
|
1963
|
+
});
|
|
1964
|
+
return {
|
|
1965
|
+
name: ToolName.make(def.toolPath),
|
|
1966
|
+
description: withFileEmitHint(descriptionFor(def), returnsFile),
|
|
1967
|
+
inputSchema: normalizeOpenApiRefs(Option4.getOrUndefined(def.operation.inputSchema)),
|
|
1968
|
+
outputSchema: returnsFile ? ToolFileJsonSchema : normalizeOpenApiRefs(Option4.getOrUndefined(def.operation.outputSchema)),
|
|
1969
|
+
annotations: annotationsForOperation(def.operation.method, def.operation.pathTemplate)
|
|
1970
|
+
};
|
|
1971
|
+
});
|
|
1972
|
+
var openApiStoredOperationsFromCompiled = (integration, compiled) => compiled.definitions.map((def) => ({
|
|
1973
|
+
integration,
|
|
1974
|
+
toolName: def.toolPath,
|
|
1975
|
+
binding: toBinding(def),
|
|
1976
|
+
description: descriptionFor(def)
|
|
1977
|
+
}));
|
|
1978
|
+
var buildDefsJson = (doc) => {
|
|
1979
|
+
const schemas = doc.components?.schemas;
|
|
1980
|
+
if (!schemas) return "{}";
|
|
1981
|
+
let json = "{";
|
|
1982
|
+
let first = true;
|
|
1983
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
1984
|
+
const serialized = JSON.stringify(normalizeOpenApiRefs(schema));
|
|
1985
|
+
if (serialized === void 0) continue;
|
|
1986
|
+
json += `${first ? "" : ","}${JSON.stringify(name)}:${serialized}`;
|
|
1987
|
+
first = false;
|
|
1988
|
+
}
|
|
1989
|
+
return `${json}}`;
|
|
1990
|
+
};
|
|
1991
|
+
var buildDefsJsonStreaming = (structure) => {
|
|
1992
|
+
let json = "{";
|
|
1993
|
+
let first = true;
|
|
1994
|
+
for (const range of structure.schemas) {
|
|
1995
|
+
const entry = parseEntry(structure.text, range, 4);
|
|
1996
|
+
if (!entry) continue;
|
|
1997
|
+
const [name, schema] = entry;
|
|
1998
|
+
const serialized = JSON.stringify(normalizeOpenApiRefs(schema));
|
|
1999
|
+
if (serialized === void 0) continue;
|
|
2000
|
+
json += `${first ? "" : ","}${JSON.stringify(name)}:${serialized}`;
|
|
2001
|
+
first = false;
|
|
2002
|
+
}
|
|
2003
|
+
return `${json}}`;
|
|
2004
|
+
};
|
|
2005
|
+
var DefsJson = Schema4.Record(Schema4.String, Schema4.Unknown);
|
|
2006
|
+
var decodeDefsJson = Schema4.decodeUnknownOption(Schema4.fromJsonString(DefsJson));
|
|
2007
|
+
var toolDefFromStoredOperation = (op) => {
|
|
2008
|
+
const binding = op.binding;
|
|
2009
|
+
const returnsFile = Option4.match(binding.responseBody, {
|
|
2010
|
+
onNone: () => false,
|
|
2011
|
+
onSome: (responseBody) => Option4.isSome(responseBody.fileHint)
|
|
2012
|
+
});
|
|
2013
|
+
return {
|
|
2014
|
+
name: ToolName.make(op.toolName),
|
|
2015
|
+
description: withFileEmitHint(
|
|
2016
|
+
op.description ?? `${binding.method.toUpperCase()} ${binding.pathTemplate}`,
|
|
2017
|
+
returnsFile
|
|
2018
|
+
),
|
|
2019
|
+
inputSchema: normalizeOpenApiRefs(
|
|
2020
|
+
buildInputSchema(
|
|
2021
|
+
binding.parameters,
|
|
2022
|
+
Option4.getOrUndefined(binding.requestBody),
|
|
2023
|
+
binding.servers ?? []
|
|
2024
|
+
)
|
|
2025
|
+
),
|
|
2026
|
+
outputSchema: returnsFile ? ToolFileJsonSchema : Option4.match(binding.responseBody, {
|
|
2027
|
+
onNone: () => void 0,
|
|
2028
|
+
onSome: (responseBody) => normalizeOpenApiRefs(outputSchemaFromResponseBody(responseBody))
|
|
2029
|
+
}),
|
|
2030
|
+
annotations: annotationsForOperation(binding.method, binding.pathTemplate)
|
|
2031
|
+
};
|
|
2032
|
+
};
|
|
2033
|
+
var compileAndPersistOpenApiOperations = ({
|
|
2034
|
+
doc,
|
|
2035
|
+
integration,
|
|
2036
|
+
storage,
|
|
2037
|
+
specHash,
|
|
2038
|
+
chunkSize
|
|
2039
|
+
}) => Effect4.gen(function* () {
|
|
2040
|
+
yield* storage.removeOperations(integration);
|
|
2041
|
+
const result = yield* streamOperationBindings(
|
|
2042
|
+
doc,
|
|
2043
|
+
chunkSize ?? 500,
|
|
2044
|
+
(chunk) => storage.appendOperations(
|
|
2045
|
+
integration,
|
|
2046
|
+
chunk.map((item) => ({
|
|
2047
|
+
integration,
|
|
2048
|
+
toolName: item.toolName,
|
|
2049
|
+
binding: item.binding,
|
|
2050
|
+
description: item.description
|
|
2051
|
+
}))
|
|
2052
|
+
)
|
|
2053
|
+
);
|
|
2054
|
+
if (specHash != null) {
|
|
2055
|
+
yield* storage.putDefs(specHash, buildDefsJson(doc));
|
|
2056
|
+
}
|
|
2057
|
+
return result;
|
|
2058
|
+
});
|
|
2059
|
+
var compileAndPersistOpenApiSpec = ({
|
|
2060
|
+
specText,
|
|
2061
|
+
integration,
|
|
2062
|
+
storage,
|
|
2063
|
+
specHash,
|
|
2064
|
+
chunkSize
|
|
2065
|
+
}) => Effect4.gen(function* () {
|
|
2066
|
+
const doc = yield* parse(specText);
|
|
2067
|
+
return yield* compileAndPersistOpenApiOperations({
|
|
2068
|
+
doc,
|
|
2069
|
+
integration,
|
|
2070
|
+
storage,
|
|
2071
|
+
specHash,
|
|
2072
|
+
chunkSize
|
|
2073
|
+
});
|
|
2074
|
+
});
|
|
2075
|
+
var compileAndPersistOpenApiSpecStreaming = ({
|
|
2076
|
+
specText,
|
|
2077
|
+
integration,
|
|
2078
|
+
storage,
|
|
2079
|
+
specHash,
|
|
2080
|
+
chunkSize,
|
|
2081
|
+
keepPathItem
|
|
2082
|
+
}) => Effect4.gen(function* () {
|
|
2083
|
+
const structure = structuralSplit(specText);
|
|
2084
|
+
if (!structure) {
|
|
2085
|
+
return yield* new OpenApiExtractionError({
|
|
2086
|
+
message: "OpenAPI spec is not in the streamable block-YAML profile (no top-level `paths:` block); cannot stream-compile a spec this large in-band."
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
yield* storage.removeOperations(integration);
|
|
2090
|
+
const result = yield* streamOperationBindingsFromStructure(
|
|
2091
|
+
structure,
|
|
2092
|
+
{ chunkSize: chunkSize ?? 500, keepPathItem },
|
|
2093
|
+
(chunk) => storage.appendOperations(
|
|
2094
|
+
integration,
|
|
2095
|
+
chunk.map((item) => ({
|
|
2096
|
+
integration,
|
|
2097
|
+
toolName: item.toolName,
|
|
2098
|
+
binding: item.binding,
|
|
2099
|
+
description: item.description
|
|
2100
|
+
}))
|
|
2101
|
+
)
|
|
2102
|
+
);
|
|
2103
|
+
if (specHash != null) {
|
|
2104
|
+
yield* storage.putDefs(specHash, buildDefsJsonStreaming(structure));
|
|
2105
|
+
}
|
|
2106
|
+
return result;
|
|
2107
|
+
});
|
|
2108
|
+
var loadOpenApiSpecText = (storage, config) => config.specHash != null ? storage.getSpec(config.specHash) : Effect4.succeed(null);
|
|
2109
|
+
var resolveOpenApiBackedTools = ({
|
|
2110
|
+
integration,
|
|
2111
|
+
config,
|
|
2112
|
+
storage
|
|
2113
|
+
}) => Effect4.gen(function* () {
|
|
2114
|
+
const incomplete = (reason) => ({
|
|
2115
|
+
tools: [],
|
|
2116
|
+
definitions: {},
|
|
2117
|
+
incomplete: true,
|
|
2118
|
+
incompleteReason: reason
|
|
2119
|
+
});
|
|
2120
|
+
const openApiConfig = decodeOpenApiIntegrationConfig(config);
|
|
2121
|
+
if (!openApiConfig) return { tools: [], definitions: {} };
|
|
2122
|
+
if (openApiConfig.specHash != null) {
|
|
2123
|
+
const defsJson = yield* storage.getDefs(openApiConfig.specHash).pipe(Effect4.catch(() => Effect4.succeed(null)));
|
|
2124
|
+
if (defsJson != null) {
|
|
2125
|
+
const definitions = Option4.getOrNull(decodeDefsJson(defsJson));
|
|
2126
|
+
if (definitions != null) {
|
|
2127
|
+
const ops = yield* storage.listOperations(String(integration.slug)).pipe(Effect4.catch(() => Effect4.succeed(null)));
|
|
2128
|
+
if (ops == null) return incomplete("OpenAPI operation bindings could not be loaded.");
|
|
2129
|
+
return { tools: ops.map(toolDefFromStoredOperation), definitions };
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
const specText = yield* loadOpenApiSpecText(storage, openApiConfig).pipe(
|
|
2134
|
+
Effect4.catch(() => Effect4.succeed(null))
|
|
2135
|
+
);
|
|
2136
|
+
if (specText == null) return incomplete("OpenAPI spec blob could not be loaded.");
|
|
2137
|
+
const compiled = yield* compileOpenApiSpecCached(openApiConfig.specHash, specText).pipe(
|
|
2138
|
+
Effect4.catch(() => Effect4.succeed(null))
|
|
2139
|
+
);
|
|
2140
|
+
if (!compiled) return incomplete("OpenAPI spec could not be parsed.");
|
|
2141
|
+
return {
|
|
2142
|
+
tools: openApiToolDefsFromCompiled(compiled),
|
|
2143
|
+
definitions: compiled.hoistedDefs
|
|
2144
|
+
};
|
|
2145
|
+
});
|
|
2146
|
+
var invokeOpenApiBackedTool = (input) => Effect4.gen(function* () {
|
|
2147
|
+
const integration = input.toolRow.integration;
|
|
2148
|
+
const config = decodeOpenApiIntegrationConfig(input.credential.config);
|
|
2149
|
+
let binding = (yield* input.ctx.storage.getOperation(integration, input.toolRow.name))?.binding;
|
|
2150
|
+
if (!binding && config) {
|
|
2151
|
+
const specText = yield* loadOpenApiSpecText(input.ctx.storage, config).pipe(
|
|
2152
|
+
Effect4.catch(() => Effect4.succeed(null))
|
|
2153
|
+
);
|
|
2154
|
+
const compiled = specText == null ? null : yield* compileOpenApiSpecCached(config.specHash, specText).pipe(
|
|
2155
|
+
Effect4.catch(() => Effect4.succeed(null))
|
|
2156
|
+
);
|
|
2157
|
+
binding = compiled ? openApiStoredOperationsFromCompiled(integration, compiled).find(
|
|
2158
|
+
(op) => op.toolName === input.toolRow.name
|
|
2159
|
+
)?.binding : void 0;
|
|
2160
|
+
}
|
|
2161
|
+
if (!binding) {
|
|
2162
|
+
return yield* new OpenApiExtractionError({
|
|
2163
|
+
message: `No OpenAPI operation found for tool "${input.toolRow.name}" on "${integration}"`
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
const headers = { ...config?.headers ?? {} };
|
|
2167
|
+
const queryParams = {
|
|
2168
|
+
...config?.queryParams ?? {}
|
|
2169
|
+
};
|
|
2170
|
+
const template = (config?.authenticationTemplate ?? []).find(
|
|
2171
|
+
(entry) => String(entry.slug) === String(input.credential.template)
|
|
2172
|
+
);
|
|
2173
|
+
if (template) {
|
|
2174
|
+
const missing = requiredTemplateVariables(template).filter((name) => {
|
|
2175
|
+
const value = input.credential.values[name];
|
|
2176
|
+
return value == null || value === "";
|
|
2177
|
+
});
|
|
2178
|
+
if (missing.length > 0) {
|
|
2179
|
+
return openApiAuthToolFailure({
|
|
2180
|
+
code: template.kind === "oauth2" ? "oauth_connection_missing" : "connection_value_missing",
|
|
2181
|
+
message: `Connection "${input.credential.connection}" for "${integration}" has no resolvable credential value. Re-authenticate or update the connection.`,
|
|
2182
|
+
owner: input.credential.owner,
|
|
2183
|
+
integration,
|
|
2184
|
+
connection: String(input.credential.connection),
|
|
2185
|
+
credentialKind: template.kind === "oauth2" ? "oauth" : "secret"
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
const rendered = renderAuthTemplate(template, input.credential.values);
|
|
2189
|
+
Object.assign(headers, rendered.headers);
|
|
2190
|
+
Object.assign(queryParams, rendered.queryParams);
|
|
2191
|
+
}
|
|
2192
|
+
const invocation = yield* invokeWithLayer(
|
|
2193
|
+
binding,
|
|
2194
|
+
input.args ?? {},
|
|
2195
|
+
config?.baseUrl ?? "",
|
|
2196
|
+
headers,
|
|
2197
|
+
queryParams,
|
|
2198
|
+
input.httpClientLayer,
|
|
2199
|
+
input.invokeOptions
|
|
2200
|
+
).pipe(
|
|
2201
|
+
Effect4.map((result2) => ({ ok: true, result: result2 })),
|
|
2202
|
+
Effect4.catchTag(
|
|
2203
|
+
"OpenApiInvocationError",
|
|
2204
|
+
(error) => error.reason === "response_headers_timeout" ? Effect4.succeed({
|
|
2205
|
+
ok: false,
|
|
2206
|
+
failure: ToolResult.fail({
|
|
2207
|
+
code: "upstream_response_headers_timeout",
|
|
2208
|
+
message: error.message,
|
|
2209
|
+
details: error.cause ?? error
|
|
2210
|
+
})
|
|
2211
|
+
}) : Effect4.fail(error)
|
|
2212
|
+
)
|
|
2213
|
+
);
|
|
2214
|
+
if (!invocation.ok) return invocation.failure;
|
|
2215
|
+
const result = invocation.result;
|
|
2216
|
+
const ok = result.status >= 200 && result.status < 300;
|
|
2217
|
+
if (!ok) {
|
|
2218
|
+
if (result.status === 401 || result.status === 403) {
|
|
2219
|
+
const insufficientScope = result.status === 403 ? detectInsufficientScope({ body: result.error, headers: result.headers }) : null;
|
|
2220
|
+
if (insufficientScope) {
|
|
2221
|
+
const required = insufficientScope.requiredScopes.length > 0 ? insufficientScope.requiredScopes.join(" ") : (binding.requiredScopeAlternatives ?? []).map((alternative) => alternative.join(" ")).join(", or ");
|
|
2222
|
+
const granted = input.credential.grantedScopes;
|
|
2223
|
+
return openApiAuthToolFailure({
|
|
2224
|
+
code: "oauth_scope_insufficient",
|
|
2225
|
+
status: result.status,
|
|
2226
|
+
message: `The connection "${input.credential.connection}" for "${integration}" is authorized, but its grant${granted && granted.length > 0 ? ` (${granted.join(" ")})` : ""} does not cover the scope this operation requires${required.length > 0 ? ` (${required})` : ""}. Re-authenticating with the same grant will return the same error; reconnect with broader access.`,
|
|
2227
|
+
owner: input.credential.owner,
|
|
2228
|
+
integration,
|
|
2229
|
+
connection: String(input.credential.connection),
|
|
2230
|
+
credentialKind: "oauth",
|
|
2231
|
+
credentialLabel: "Upstream authorization",
|
|
2232
|
+
details: result.error
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
return openApiAuthToolFailure({
|
|
2236
|
+
code: "connection_rejected",
|
|
2237
|
+
status: result.status,
|
|
2238
|
+
message: `Upstream rejected credentials for "${integration}" with HTTP ${result.status}. Re-authenticate or update the connection "${input.credential.connection}" before retrying this tool.`,
|
|
2239
|
+
owner: input.credential.owner,
|
|
2240
|
+
integration,
|
|
2241
|
+
connection: String(input.credential.connection),
|
|
2242
|
+
credentialKind: "upstream",
|
|
2243
|
+
credentialLabel: "Upstream authorization",
|
|
2244
|
+
details: result.error
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
return ToolResult.fail({
|
|
2248
|
+
code: "upstream_http_error",
|
|
2249
|
+
status: result.status,
|
|
2250
|
+
message: extractOpenApiUpstreamMessage(result.error, result.status),
|
|
2251
|
+
details: result.error
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
return ToolResult.ok(result.data, {
|
|
2255
|
+
http: { status: result.status, headers: result.headers }
|
|
2256
|
+
});
|
|
2257
|
+
});
|
|
2258
|
+
var validateOpenApiBackedToolArgs = (input) => Effect4.gen(function* () {
|
|
2259
|
+
const operation = yield* input.ctx.storage.getOperation(
|
|
2260
|
+
input.toolRow.integration,
|
|
2261
|
+
input.toolRow.name
|
|
2262
|
+
);
|
|
2263
|
+
if (!operation) return;
|
|
2264
|
+
yield* buildRequest(operation.binding, input.args ?? {}, {});
|
|
2265
|
+
});
|
|
2266
|
+
var resolveOpenApiBackedAnnotations = (input) => Effect4.gen(function* () {
|
|
2267
|
+
const out = {};
|
|
2268
|
+
for (const row of input.toolRows) {
|
|
2269
|
+
const operation = yield* input.ctx.storage.getOperation(input.integration, row.name);
|
|
2270
|
+
if (!operation) continue;
|
|
2271
|
+
out[row.name] = annotationsForOperation(
|
|
2272
|
+
operation.binding.method,
|
|
2273
|
+
operation.binding.pathTemplate
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
return out;
|
|
2277
|
+
});
|
|
2278
|
+
var resolveHealthCheckBinding = (ctx, integration, operation, config) => Effect4.gen(function* () {
|
|
2279
|
+
const stored = (yield* ctx.storage.getOperation(integration, operation))?.binding;
|
|
2280
|
+
if (stored) return stored;
|
|
2281
|
+
if (!config) return void 0;
|
|
2282
|
+
const specText = yield* loadOpenApiSpecText(ctx.storage, config).pipe(
|
|
2283
|
+
Effect4.catch(() => Effect4.succeed(null))
|
|
2284
|
+
);
|
|
2285
|
+
if (specText == null) return void 0;
|
|
2286
|
+
const compiled = yield* compileOpenApiSpecCached(config.specHash, specText).pipe(
|
|
2287
|
+
Effect4.catch(() => Effect4.succeed(null))
|
|
2288
|
+
);
|
|
2289
|
+
if (!compiled) return void 0;
|
|
2290
|
+
return openApiStoredOperationsFromCompiled(integration, compiled).find(
|
|
2291
|
+
(op) => op.toolName === operation
|
|
2292
|
+
)?.binding;
|
|
2293
|
+
});
|
|
2294
|
+
var checkHealthOpenApi = (input) => Effect4.gen(function* () {
|
|
2295
|
+
const checkedAt = Date.now();
|
|
2296
|
+
const config = decodeOpenApiIntegrationConfig(input.integration.config);
|
|
2297
|
+
const spec = input.spec;
|
|
2298
|
+
if (!spec) {
|
|
2299
|
+
return {
|
|
2300
|
+
status: "unknown",
|
|
2301
|
+
checkedAt,
|
|
2302
|
+
detail: "No health check configured."
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
const integration = String(input.integration.slug);
|
|
2306
|
+
const binding = yield* resolveHealthCheckBinding(
|
|
2307
|
+
input.ctx,
|
|
2308
|
+
integration,
|
|
2309
|
+
spec.operation,
|
|
2310
|
+
config
|
|
2311
|
+
);
|
|
2312
|
+
if (!binding) {
|
|
2313
|
+
return {
|
|
2314
|
+
status: "unknown",
|
|
2315
|
+
checkedAt,
|
|
2316
|
+
detail: `Health check operation "${spec.operation}" not found on "${integration}".`
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
if (REQUIRE_APPROVAL.has(binding.method.toLowerCase())) {
|
|
2320
|
+
return {
|
|
2321
|
+
status: "unknown",
|
|
2322
|
+
checkedAt,
|
|
2323
|
+
detail: `Health check operation "${spec.operation}" is a ${binding.method.toUpperCase()} (mutating): pick a read-only operation.`
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
const headers = { ...config?.headers ?? {} };
|
|
2327
|
+
const queryParams = { ...config?.queryParams ?? {} };
|
|
2328
|
+
const template = (config?.authenticationTemplate ?? []).find(
|
|
2329
|
+
(entry) => String(entry.slug) === String(input.credential.template)
|
|
2330
|
+
);
|
|
2331
|
+
if (template) {
|
|
2332
|
+
const missing = requiredTemplateVariables(template).filter((name) => {
|
|
2333
|
+
const value = input.credential.values[name];
|
|
2334
|
+
return value == null || value === "";
|
|
2335
|
+
});
|
|
2336
|
+
if (missing.length > 0) {
|
|
2337
|
+
return {
|
|
2338
|
+
status: "expired",
|
|
2339
|
+
checkedAt,
|
|
2340
|
+
detail: `Connection "${input.credential.connection}" has no resolvable credential value.`
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
const rendered = renderAuthTemplate(template, input.credential.values);
|
|
2344
|
+
Object.assign(headers, rendered.headers);
|
|
2345
|
+
Object.assign(queryParams, rendered.queryParams);
|
|
2346
|
+
}
|
|
2347
|
+
const probe = yield* invokeWithLayer(
|
|
2348
|
+
binding,
|
|
2349
|
+
{ ...spec.args ?? {} },
|
|
2350
|
+
config?.baseUrl ?? "",
|
|
2351
|
+
headers,
|
|
2352
|
+
queryParams,
|
|
2353
|
+
input.httpClientLayer
|
|
2354
|
+
).pipe(
|
|
2355
|
+
Effect4.map((result) => ({ ok: true, result })),
|
|
2356
|
+
Effect4.catch((failure) => Effect4.succeed({ ok: false, failure }))
|
|
2357
|
+
);
|
|
2358
|
+
const secretValues = Object.values(input.credential.values).filter(
|
|
2359
|
+
(value) => typeof value === "string" && value.length > 0
|
|
2360
|
+
);
|
|
2361
|
+
const scrubSecrets = (text) => secretValues.reduce((out, secret) => out.split(secret).join("[redacted]"), text);
|
|
2362
|
+
if (!probe.ok) {
|
|
2363
|
+
return {
|
|
2364
|
+
status: "degraded",
|
|
2365
|
+
checkedAt,
|
|
2366
|
+
detail: scrubSecrets(`Health check request failed: ${probe.failure.message}`)
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
const status = classifyHttpStatus(probe.result.status);
|
|
2370
|
+
const identity = status === "healthy" ? extractIdentity(probe.result.data, spec.identityField) : void 0;
|
|
2371
|
+
const responseSample = status === "healthy" ? extractResponseFields(probe.result.data) : [];
|
|
2372
|
+
return {
|
|
2373
|
+
status,
|
|
2374
|
+
httpStatus: probe.result.status,
|
|
2375
|
+
...identity !== void 0 ? { identity } : {},
|
|
2376
|
+
checkedAt,
|
|
2377
|
+
...responseSample.length > 0 ? { responseSample } : {},
|
|
2378
|
+
...status === "healthy" ? {} : {
|
|
2379
|
+
detail: scrubSecrets(
|
|
2380
|
+
extractOpenApiUpstreamMessage(probe.result.error, probe.result.status)
|
|
2381
|
+
)
|
|
2382
|
+
}
|
|
2383
|
+
};
|
|
2384
|
+
});
|
|
2385
|
+
var listHealthCheckCandidatesOpenApi = (input) => Effect4.gen(function* () {
|
|
2386
|
+
const integration = String(input.integration.slug);
|
|
2387
|
+
const operations = yield* input.ctx.storage.listOperations(integration);
|
|
2388
|
+
const config = decodeOpenApiIntegrationConfig(input.integration.config);
|
|
2389
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
2390
|
+
const responseFieldsByTool = /* @__PURE__ */ new Map();
|
|
2391
|
+
if (config) {
|
|
2392
|
+
const specText = yield* loadOpenApiSpecText(input.ctx.storage, config).pipe(
|
|
2393
|
+
Effect4.catch(() => Effect4.succeed(null))
|
|
2394
|
+
);
|
|
2395
|
+
const compiled = specText == null ? null : yield* compileOpenApiSpecCached(config.specHash, specText).pipe(
|
|
2396
|
+
Effect4.catch(() => Effect4.succeed(null))
|
|
2397
|
+
);
|
|
2398
|
+
if (compiled) {
|
|
2399
|
+
for (const def of compiled.definitions) {
|
|
2400
|
+
const summary = Option4.getOrUndefined(def.operation.summary) ?? Option4.getOrUndefined(def.operation.description);
|
|
2401
|
+
if (summary) summaries.set(def.toolPath, summary);
|
|
2402
|
+
const fields = projectResponseFields(
|
|
2403
|
+
normalizeOpenApiRefs(Option4.getOrUndefined(def.operation.outputSchema)),
|
|
2404
|
+
compiled.hoistedDefs
|
|
2405
|
+
);
|
|
2406
|
+
if (fields.length > 0) responseFieldsByTool.set(def.toolPath, fields);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
const candidates = operations.map((op) => {
|
|
2411
|
+
const method = op.binding.method.toLowerCase();
|
|
2412
|
+
const parameters = op.binding.parameters.map((parameter) => ({
|
|
2413
|
+
name: parameter.name,
|
|
2414
|
+
location: parameter.location,
|
|
2415
|
+
required: parameter.required,
|
|
2416
|
+
...Option4.isSome(parameter.description) ? { description: parameter.description.value } : {}
|
|
2417
|
+
}));
|
|
2418
|
+
const responseFields = responseFieldsByTool.get(op.toolName);
|
|
2419
|
+
return {
|
|
2420
|
+
operation: op.toolName,
|
|
2421
|
+
method,
|
|
2422
|
+
requiredArgCount: op.binding.parameters.filter((parameter) => parameter.required).length,
|
|
2423
|
+
destructive: REQUIRE_APPROVAL.has(method),
|
|
2424
|
+
summary: summaries.get(op.toolName) ?? `${method.toUpperCase()} ${op.binding.pathTemplate}`,
|
|
2425
|
+
...parameters.length > 0 ? { parameters } : {},
|
|
2426
|
+
...responseFields && responseFields.length > 0 ? { responseFields } : {}
|
|
2427
|
+
};
|
|
2428
|
+
});
|
|
2429
|
+
return sortHealthCheckCandidatesByIdentity(candidates);
|
|
2430
|
+
});
|
|
2431
|
+
|
|
2432
|
+
// src/sdk/preview.ts
|
|
2433
|
+
import { Effect as Effect5, Option as Option5, Predicate } from "effect";
|
|
2434
|
+
import { Schema as Schema5 } from "effect";
|
|
2435
|
+
import {
|
|
2436
|
+
HealthCheckCandidate,
|
|
2437
|
+
compareHealthCheckCandidates,
|
|
2438
|
+
projectResponseFields as projectResponseFields2
|
|
2439
|
+
} from "@rafads/sdk/core";
|
|
2440
|
+
var DESTRUCTIVE_METHODS = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
|
|
2441
|
+
var MAX_PREVIEW_CANDIDATES = 1e3;
|
|
2442
|
+
var MAX_PREVIEW_RESPONSE_FIELD_CANDIDATES = 50;
|
|
2443
|
+
var OAuth2Scopes = Schema5.Record(Schema5.String, Schema5.String);
|
|
2444
|
+
var SecuritySchemeType = Schema5.Literals(["http", "apiKey", "oauth2", "openIdConnect"]);
|
|
2445
|
+
var decodeSecuritySchemeType = Schema5.decodeUnknownOption(SecuritySchemeType);
|
|
2446
|
+
var OAuth2AuthorizationCodeFlow = Schema5.Struct({
|
|
2447
|
+
authorizationUrl: Schema5.String,
|
|
2448
|
+
tokenUrl: Schema5.String,
|
|
2449
|
+
refreshUrl: Schema5.OptionFromOptional(Schema5.String),
|
|
2450
|
+
scopes: OAuth2Scopes
|
|
2451
|
+
});
|
|
2452
|
+
var OAuth2ClientCredentialsFlow = Schema5.Struct({
|
|
2453
|
+
tokenUrl: Schema5.String,
|
|
2454
|
+
refreshUrl: Schema5.OptionFromOptional(Schema5.String),
|
|
2455
|
+
scopes: OAuth2Scopes
|
|
2456
|
+
});
|
|
2457
|
+
var OAuth2Flows = Schema5.Struct({
|
|
2458
|
+
authorizationCode: Schema5.OptionFromOptional(OAuth2AuthorizationCodeFlow),
|
|
2459
|
+
clientCredentials: Schema5.OptionFromOptional(OAuth2ClientCredentialsFlow)
|
|
2460
|
+
});
|
|
2461
|
+
var SecurityScheme = Schema5.Struct({
|
|
2462
|
+
/** Key name in components.securitySchemes (e.g. "api_token") */
|
|
2463
|
+
name: Schema5.String,
|
|
2464
|
+
/** OpenAPI security scheme type */
|
|
2465
|
+
type: SecuritySchemeType,
|
|
2466
|
+
/** For type: "http" — e.g. "bearer", "basic" */
|
|
2467
|
+
scheme: Schema5.OptionFromOptional(Schema5.String),
|
|
2468
|
+
/** For type: "http" with scheme "bearer" — e.g. "JWT" */
|
|
2469
|
+
bearerFormat: Schema5.OptionFromOptional(Schema5.String),
|
|
2470
|
+
/** For type: "apiKey" — where the key goes */
|
|
2471
|
+
in: Schema5.OptionFromOptional(Schema5.Literals(["header", "query", "cookie"])),
|
|
2472
|
+
/** For type: "apiKey" — the header/query/cookie name */
|
|
2473
|
+
headerName: Schema5.OptionFromOptional(Schema5.String),
|
|
2474
|
+
description: Schema5.OptionFromOptional(Schema5.String),
|
|
2475
|
+
/** For type: "oauth2" — declared flows (authorizationCode / clientCredentials only; implicit and password are deprecated). */
|
|
2476
|
+
flows: Schema5.OptionFromOptional(OAuth2Flows),
|
|
2477
|
+
/** For type: "openIdConnect" — the discovery URL. */
|
|
2478
|
+
openIdConnectUrl: Schema5.OptionFromOptional(Schema5.String)
|
|
2479
|
+
});
|
|
2480
|
+
var AuthStrategy = Schema5.Struct({
|
|
2481
|
+
/** The security schemes required together for this strategy */
|
|
2482
|
+
schemes: Schema5.Array(Schema5.String)
|
|
2483
|
+
});
|
|
2484
|
+
var HeaderPreset = Schema5.Struct({
|
|
2485
|
+
/** Human-readable label for the UI (e.g. "Bearer Token", "API Key + Email") */
|
|
2486
|
+
label: Schema5.String,
|
|
2487
|
+
/** Headers this strategy needs. Value is null when the user must provide it. */
|
|
2488
|
+
headers: Schema5.Record(Schema5.String, Schema5.NullOr(Schema5.String)),
|
|
2489
|
+
/** Which headers should be stored as secrets */
|
|
2490
|
+
secretHeaders: Schema5.Array(Schema5.String)
|
|
2491
|
+
});
|
|
2492
|
+
var OAuth2Preset = Schema5.Struct({
|
|
2493
|
+
/** Human-readable label for the UI (e.g. "OAuth2 (Authorization Code) — oauth_app") */
|
|
2494
|
+
label: Schema5.String,
|
|
2495
|
+
/** The source security scheme this preset came from (components.securitySchemes key). */
|
|
2496
|
+
securitySchemeName: Schema5.String,
|
|
2497
|
+
/** Which OAuth2 flow this preset uses. */
|
|
2498
|
+
flow: Schema5.Literals(["authorizationCode", "clientCredentials"]),
|
|
2499
|
+
/** For authorizationCode: user-agent redirect URL (from the spec). */
|
|
2500
|
+
authorizationUrl: Schema5.OptionFromOptional(Schema5.String),
|
|
2501
|
+
/** Token endpoint to exchange the code / refresh. */
|
|
2502
|
+
tokenUrl: Schema5.String,
|
|
2503
|
+
/** RFC 8707 resource indicator discovered from protected-resource metadata. */
|
|
2504
|
+
resource: Schema5.OptionFromOptional(Schema5.String),
|
|
2505
|
+
/** Optional refresh endpoint if the spec declares one separately. */
|
|
2506
|
+
refreshUrl: Schema5.OptionFromOptional(Schema5.String),
|
|
2507
|
+
/** Declared scopes for this flow: `{ scope: description }`. */
|
|
2508
|
+
scopes: Schema5.Record(Schema5.String, Schema5.String),
|
|
2509
|
+
/** Identity scopes to request alongside API scopes. `"auto"` discovers standard OIDC scopes. */
|
|
2510
|
+
identityScopes: Schema5.Union([
|
|
2511
|
+
Schema5.Literal("auto"),
|
|
2512
|
+
Schema5.Literal(false),
|
|
2513
|
+
Schema5.Array(Schema5.String)
|
|
2514
|
+
]),
|
|
2515
|
+
/** Provider metadata advertised Client ID Metadata Document support. */
|
|
2516
|
+
supportsClientIdMetadataDocument: Schema5.optional(Schema5.Boolean)
|
|
2517
|
+
});
|
|
2518
|
+
var PreviewOperation = Schema5.Struct({
|
|
2519
|
+
operationId: Schema5.String,
|
|
2520
|
+
method: HttpMethod,
|
|
2521
|
+
path: Schema5.String,
|
|
2522
|
+
summary: Schema5.OptionFromOptional(Schema5.String),
|
|
2523
|
+
tags: Schema5.Array(Schema5.String),
|
|
2524
|
+
deprecated: Schema5.Boolean
|
|
2525
|
+
});
|
|
2526
|
+
var SpecPreview = Schema5.Struct({
|
|
2527
|
+
title: Schema5.OptionFromOptional(Schema5.String),
|
|
2528
|
+
/** The spec's `info.description` — prefills the add form's description field. */
|
|
2529
|
+
description: Schema5.OptionFromOptional(Schema5.String),
|
|
2530
|
+
version: Schema5.OptionFromOptional(Schema5.String),
|
|
2531
|
+
/** Reuses ServerInfo from extraction */
|
|
2532
|
+
servers: Schema5.Array(ServerInfo),
|
|
2533
|
+
operationCount: Schema5.Number,
|
|
2534
|
+
/** Lightweight operation list for the add-integration UI */
|
|
2535
|
+
operations: Schema5.Array(PreviewOperation),
|
|
2536
|
+
tags: Schema5.Array(Schema5.String),
|
|
2537
|
+
securitySchemes: Schema5.Array(SecurityScheme),
|
|
2538
|
+
/** Valid auth strategies (each is a set of schemes used together) */
|
|
2539
|
+
authStrategies: Schema5.Array(AuthStrategy),
|
|
2540
|
+
/** Pre-built header presets derived from auth strategies */
|
|
2541
|
+
headerPresets: Schema5.Array(HeaderPreset),
|
|
2542
|
+
/** OAuth2 presets — one per (oauth2 scheme × supported flow) combination */
|
|
2543
|
+
oauth2Presets: Schema5.Array(OAuth2Preset),
|
|
2544
|
+
/** Top-ranked health-check candidates (bounded), so the add screen can offer a
|
|
2545
|
+
* typed operation + identity picker before the integration is registered. */
|
|
2546
|
+
healthCheckCandidates: Schema5.Array(HealthCheckCandidate)
|
|
2547
|
+
});
|
|
2548
|
+
var SpecPreviewSummary = Schema5.Struct({
|
|
2549
|
+
title: Schema5.OptionFromOptional(Schema5.String),
|
|
2550
|
+
description: Schema5.OptionFromOptional(Schema5.String),
|
|
2551
|
+
version: Schema5.OptionFromOptional(Schema5.String),
|
|
2552
|
+
servers: Schema5.Array(ServerInfo),
|
|
2553
|
+
operationCount: Schema5.Number,
|
|
2554
|
+
tags: Schema5.Array(Schema5.String),
|
|
2555
|
+
securitySchemes: Schema5.Array(SecurityScheme),
|
|
2556
|
+
authStrategies: Schema5.Array(AuthStrategy),
|
|
2557
|
+
headerPresets: Schema5.Array(HeaderPreset),
|
|
2558
|
+
oauth2Presets: Schema5.Array(OAuth2Preset),
|
|
2559
|
+
healthCheckCandidates: Schema5.Array(HealthCheckCandidate)
|
|
2560
|
+
});
|
|
2561
|
+
var stringRecord = (value) => {
|
|
2562
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
2563
|
+
const out = {};
|
|
2564
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2565
|
+
if (typeof v === "string") out[k] = v;
|
|
2566
|
+
}
|
|
2567
|
+
return out;
|
|
2568
|
+
};
|
|
2569
|
+
var extractFlows = (rawFlows) => {
|
|
2570
|
+
if (!rawFlows || typeof rawFlows !== "object") return Option5.none();
|
|
2571
|
+
const flows = rawFlows;
|
|
2572
|
+
const parseFlow = (key) => flows[key];
|
|
2573
|
+
let authorizationCode = Option5.none();
|
|
2574
|
+
const authCodeRaw = parseFlow("authorizationCode");
|
|
2575
|
+
if (authCodeRaw && typeof authCodeRaw === "object") {
|
|
2576
|
+
const f = authCodeRaw;
|
|
2577
|
+
const authUrl = typeof f.authorizationUrl === "string" ? f.authorizationUrl : null;
|
|
2578
|
+
const tokenUrl = typeof f.tokenUrl === "string" ? f.tokenUrl : null;
|
|
2579
|
+
if (authUrl && tokenUrl) {
|
|
2580
|
+
authorizationCode = Option5.some(
|
|
2581
|
+
OAuth2AuthorizationCodeFlow.make({
|
|
2582
|
+
authorizationUrl: authUrl,
|
|
2583
|
+
tokenUrl,
|
|
2584
|
+
refreshUrl: Option5.fromNullishOr(
|
|
2585
|
+
typeof f.refreshUrl === "string" ? f.refreshUrl : void 0
|
|
2586
|
+
),
|
|
2587
|
+
scopes: stringRecord(f.scopes)
|
|
2588
|
+
})
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
let clientCredentials = Option5.none();
|
|
2593
|
+
const ccRaw = parseFlow("clientCredentials");
|
|
2594
|
+
if (ccRaw && typeof ccRaw === "object") {
|
|
2595
|
+
const f = ccRaw;
|
|
2596
|
+
const tokenUrl = typeof f.tokenUrl === "string" ? f.tokenUrl : null;
|
|
2597
|
+
if (tokenUrl) {
|
|
2598
|
+
clientCredentials = Option5.some(
|
|
2599
|
+
OAuth2ClientCredentialsFlow.make({
|
|
2600
|
+
tokenUrl,
|
|
2601
|
+
refreshUrl: Option5.fromNullishOr(
|
|
2602
|
+
typeof f.refreshUrl === "string" ? f.refreshUrl : void 0
|
|
2603
|
+
),
|
|
2604
|
+
scopes: stringRecord(f.scopes)
|
|
2605
|
+
})
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
if (Option5.isNone(authorizationCode) && Option5.isNone(clientCredentials)) {
|
|
2610
|
+
return Option5.none();
|
|
2611
|
+
}
|
|
2612
|
+
return Option5.some(OAuth2Flows.make({ authorizationCode, clientCredentials }));
|
|
2613
|
+
};
|
|
2614
|
+
var extractSecuritySchemes = (rawSchemes, resolver) => Object.entries(rawSchemes).flatMap(([name, schemeOrRef]) => {
|
|
2615
|
+
if (!schemeOrRef || typeof schemeOrRef !== "object") return [];
|
|
2616
|
+
const resolved = resolver.resolve(
|
|
2617
|
+
schemeOrRef
|
|
2618
|
+
);
|
|
2619
|
+
if (!resolved || typeof resolved !== "object") return [];
|
|
2620
|
+
const scheme = resolved;
|
|
2621
|
+
const type = decodeSecuritySchemeType(scheme.type);
|
|
2622
|
+
if (Option5.isNone(type)) return [];
|
|
2623
|
+
const schemeType = type.value;
|
|
2624
|
+
return [
|
|
2625
|
+
SecurityScheme.make({
|
|
2626
|
+
name,
|
|
2627
|
+
type: schemeType,
|
|
2628
|
+
scheme: Option5.fromNullishOr(scheme.scheme),
|
|
2629
|
+
bearerFormat: Option5.fromNullishOr(scheme.bearerFormat),
|
|
2630
|
+
in: Option5.fromNullishOr(scheme.in),
|
|
2631
|
+
headerName: Option5.fromNullishOr(scheme.name),
|
|
2632
|
+
description: Option5.fromNullishOr(scheme.description),
|
|
2633
|
+
flows: schemeType === "oauth2" ? extractFlows(scheme.flows) : Option5.none(),
|
|
2634
|
+
openIdConnectUrl: Option5.fromNullishOr(scheme.openIdConnectUrl)
|
|
2635
|
+
})
|
|
2636
|
+
];
|
|
2637
|
+
});
|
|
2638
|
+
var buildHeaderPresets = (schemes, strategies) => {
|
|
2639
|
+
const schemeMap = new Map(schemes.map((s) => [s.name, s]));
|
|
2640
|
+
return strategies.flatMap((strategy) => {
|
|
2641
|
+
const resolved = strategy.schemes.map((name) => schemeMap.get(name)).filter(Predicate.isNotUndefined);
|
|
2642
|
+
if (resolved.length === 0) return [];
|
|
2643
|
+
const headers = {};
|
|
2644
|
+
const secretHeaders = [];
|
|
2645
|
+
const labelParts = [];
|
|
2646
|
+
for (const scheme of resolved) {
|
|
2647
|
+
if (scheme.type === "http" && Option5.getOrElse(scheme.scheme, () => "") === "bearer") {
|
|
2648
|
+
headers["Authorization"] = null;
|
|
2649
|
+
secretHeaders.push("Authorization");
|
|
2650
|
+
labelParts.push("Bearer Token");
|
|
2651
|
+
} else if (scheme.type === "http" && Option5.getOrElse(scheme.scheme, () => "") === "basic") {
|
|
2652
|
+
headers["Authorization"] = null;
|
|
2653
|
+
secretHeaders.push("Authorization");
|
|
2654
|
+
labelParts.push("Basic Auth");
|
|
2655
|
+
} else if (scheme.type === "apiKey" && Option5.getOrElse(scheme.in, () => "") === "header") {
|
|
2656
|
+
const headerName = Option5.getOrElse(scheme.headerName, () => scheme.name);
|
|
2657
|
+
headers[headerName] = null;
|
|
2658
|
+
secretHeaders.push(headerName);
|
|
2659
|
+
labelParts.push(scheme.name);
|
|
2660
|
+
} else if (scheme.type === "apiKey") {
|
|
2661
|
+
labelParts.push(`${scheme.name} (${Option5.getOrElse(scheme.in, () => "unknown")})`);
|
|
2662
|
+
} else if (scheme.type === "oauth2" || scheme.type === "openIdConnect") {
|
|
2663
|
+
return [];
|
|
2664
|
+
} else {
|
|
2665
|
+
labelParts.push(scheme.name);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
if (Object.keys(headers).length === 0 && resolved.length > 0) {
|
|
2669
|
+
return [
|
|
2670
|
+
HeaderPreset.make({
|
|
2671
|
+
label: labelParts.join(" + "),
|
|
2672
|
+
headers: {},
|
|
2673
|
+
secretHeaders: []
|
|
2674
|
+
})
|
|
2675
|
+
];
|
|
2676
|
+
}
|
|
2677
|
+
return [
|
|
2678
|
+
HeaderPreset.make({
|
|
2679
|
+
label: labelParts.join(" + "),
|
|
2680
|
+
headers,
|
|
2681
|
+
secretHeaders
|
|
2682
|
+
})
|
|
2683
|
+
];
|
|
2684
|
+
});
|
|
2685
|
+
};
|
|
2686
|
+
var buildOAuth2Presets = (schemes) => {
|
|
2687
|
+
const presets = [];
|
|
2688
|
+
for (const scheme of schemes) {
|
|
2689
|
+
if (scheme.type !== "oauth2") continue;
|
|
2690
|
+
if (Option5.isNone(scheme.flows)) continue;
|
|
2691
|
+
const flows = scheme.flows.value;
|
|
2692
|
+
if (Option5.isSome(flows.authorizationCode)) {
|
|
2693
|
+
const flow = flows.authorizationCode.value;
|
|
2694
|
+
presets.push(
|
|
2695
|
+
OAuth2Preset.make({
|
|
2696
|
+
label: `OAuth2 Authorization Code \xB7 ${scheme.name}`,
|
|
2697
|
+
securitySchemeName: scheme.name,
|
|
2698
|
+
flow: "authorizationCode",
|
|
2699
|
+
authorizationUrl: Option5.some(flow.authorizationUrl),
|
|
2700
|
+
tokenUrl: flow.tokenUrl,
|
|
2701
|
+
resource: Option5.none(),
|
|
2702
|
+
refreshUrl: flow.refreshUrl,
|
|
2703
|
+
scopes: flow.scopes,
|
|
2704
|
+
identityScopes: "auto"
|
|
2705
|
+
})
|
|
2706
|
+
);
|
|
2707
|
+
}
|
|
2708
|
+
if (Option5.isSome(flows.clientCredentials)) {
|
|
2709
|
+
const flow = flows.clientCredentials.value;
|
|
2710
|
+
presets.push(
|
|
2711
|
+
OAuth2Preset.make({
|
|
2712
|
+
label: `OAuth2 Client Credentials \xB7 ${scheme.name}`,
|
|
2713
|
+
securitySchemeName: scheme.name,
|
|
2714
|
+
flow: "clientCredentials",
|
|
2715
|
+
authorizationUrl: Option5.none(),
|
|
2716
|
+
tokenUrl: flow.tokenUrl,
|
|
2717
|
+
resource: Option5.none(),
|
|
2718
|
+
refreshUrl: flow.refreshUrl,
|
|
2719
|
+
scopes: flow.scopes,
|
|
2720
|
+
identityScopes: false
|
|
2721
|
+
})
|
|
2722
|
+
);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
return presets;
|
|
2726
|
+
};
|
|
2727
|
+
var collectTags = (result) => {
|
|
2728
|
+
const tagSet = /* @__PURE__ */ new Set();
|
|
2729
|
+
for (const op of result.operations) {
|
|
2730
|
+
for (const tag of op.tags) tagSet.add(tag);
|
|
2731
|
+
}
|
|
2732
|
+
return [...tagSet].sort();
|
|
2733
|
+
};
|
|
2734
|
+
var buildPreviewHealthCheckCandidates = (doc, operations) => {
|
|
2735
|
+
if (operations.length === 0) return [];
|
|
2736
|
+
const definitions = compileToolDefinitions(operations);
|
|
2737
|
+
const ranked = definitions.map((def) => {
|
|
2738
|
+
const op = def.operation;
|
|
2739
|
+
const method = op.method.toLowerCase();
|
|
2740
|
+
const parameters = op.parameters.map((parameter) => ({
|
|
2741
|
+
name: parameter.name,
|
|
2742
|
+
location: parameter.location,
|
|
2743
|
+
required: parameter.required,
|
|
2744
|
+
...Option5.isSome(parameter.description) ? { description: parameter.description.value } : {}
|
|
2745
|
+
}));
|
|
2746
|
+
return {
|
|
2747
|
+
operation: def.toolPath,
|
|
2748
|
+
method,
|
|
2749
|
+
requiredArgCount: op.parameters.filter((parameter) => parameter.required).length,
|
|
2750
|
+
destructive: DESTRUCTIVE_METHODS.has(method),
|
|
2751
|
+
summary: Option5.getOrUndefined(op.summary) ?? Option5.getOrUndefined(op.description) ?? `${method.toUpperCase()} ${op.pathTemplate}`,
|
|
2752
|
+
...parameters.length > 0 ? { parameters } : {}
|
|
2753
|
+
};
|
|
2754
|
+
}).sort(compareHealthCheckCandidates).slice(0, MAX_PREVIEW_CANDIDATES);
|
|
2755
|
+
const hoistedDefs = {};
|
|
2756
|
+
const rawSchemas = doc.components?.schemas;
|
|
2757
|
+
if (rawSchemas) {
|
|
2758
|
+
for (const [name, schema] of Object.entries(rawSchemas)) {
|
|
2759
|
+
hoistedDefs[name] = normalizeOpenApiRefs(schema);
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
const operationByToolPath = new Map(definitions.map((def) => [def.toolPath, def.operation]));
|
|
2763
|
+
return ranked.map((candidate, index) => {
|
|
2764
|
+
if (index >= MAX_PREVIEW_RESPONSE_FIELD_CANDIDATES) return candidate;
|
|
2765
|
+
const op = operationByToolPath.get(candidate.operation);
|
|
2766
|
+
if (!op) return candidate;
|
|
2767
|
+
const responseFields = projectResponseFields2(
|
|
2768
|
+
normalizeOpenApiRefs(Option5.getOrUndefined(op.outputSchema)),
|
|
2769
|
+
hoistedDefs
|
|
2770
|
+
);
|
|
2771
|
+
return responseFields.length > 0 ? { ...candidate, responseFields } : candidate;
|
|
2772
|
+
});
|
|
2773
|
+
};
|
|
2774
|
+
var previewSpecText = Effect5.fn("OpenApi.previewSpecText")(function* (specText) {
|
|
2775
|
+
const doc = yield* parse(specText);
|
|
2776
|
+
const result = yield* extract(doc);
|
|
2777
|
+
const resolver = new DocResolver(doc);
|
|
2778
|
+
const securitySchemes = extractSecuritySchemes(doc.components?.securitySchemes ?? {}, resolver);
|
|
2779
|
+
const rawSecurity = doc.security ?? [];
|
|
2780
|
+
const declaredStrategies = rawSecurity.map(
|
|
2781
|
+
(entry) => AuthStrategy.make({ schemes: Object.keys(entry) })
|
|
2782
|
+
);
|
|
2783
|
+
const authStrategies = declaredStrategies.length > 0 ? declaredStrategies : securitySchemes.map((scheme) => AuthStrategy.make({ schemes: [scheme.name] }));
|
|
2784
|
+
return SpecPreview.make({
|
|
2785
|
+
title: result.title,
|
|
2786
|
+
description: result.description,
|
|
2787
|
+
version: result.version,
|
|
2788
|
+
servers: result.servers,
|
|
2789
|
+
operationCount: result.operations.length,
|
|
2790
|
+
operations: result.operations.map(
|
|
2791
|
+
(op) => PreviewOperation.make({
|
|
2792
|
+
operationId: op.operationId,
|
|
2793
|
+
method: op.method,
|
|
2794
|
+
path: op.pathTemplate,
|
|
2795
|
+
summary: op.summary,
|
|
2796
|
+
tags: op.tags,
|
|
2797
|
+
deprecated: op.deprecated
|
|
2798
|
+
})
|
|
2799
|
+
),
|
|
2800
|
+
tags: collectTags(result),
|
|
2801
|
+
securitySchemes,
|
|
2802
|
+
authStrategies,
|
|
2803
|
+
headerPresets: buildHeaderPresets(securitySchemes, authStrategies),
|
|
2804
|
+
oauth2Presets: buildOAuth2Presets(securitySchemes),
|
|
2805
|
+
healthCheckCandidates: buildPreviewHealthCheckCandidates(doc, result.operations)
|
|
2806
|
+
});
|
|
2807
|
+
});
|
|
2808
|
+
var previewSpec = Effect5.fn("OpenApi.previewSpec")(function* (input) {
|
|
2809
|
+
const specText = yield* resolveSpecText(input);
|
|
2810
|
+
return yield* previewSpecText(specText);
|
|
2811
|
+
});
|
|
2812
|
+
|
|
2813
|
+
export {
|
|
2814
|
+
fetchSpecText,
|
|
2815
|
+
resolveSpecText,
|
|
2816
|
+
parse,
|
|
2817
|
+
DocResolver,
|
|
2818
|
+
substituteUrlVariables,
|
|
2819
|
+
resolveServerUrl,
|
|
2820
|
+
preferredContent,
|
|
2821
|
+
normalizeOpenApiAuthInputs,
|
|
2822
|
+
OperationId,
|
|
2823
|
+
HttpMethod,
|
|
2824
|
+
ParameterLocation,
|
|
2825
|
+
OperationParameter,
|
|
2826
|
+
EncodingObject,
|
|
2827
|
+
MediaBinding,
|
|
2828
|
+
OperationRequestBody,
|
|
2829
|
+
ServerVariable,
|
|
2830
|
+
ServerInfo,
|
|
2831
|
+
ExtractedOperation,
|
|
2832
|
+
ExtractionResult,
|
|
2833
|
+
OperationBinding,
|
|
2834
|
+
InvocationResult,
|
|
2835
|
+
TOKEN_VARIABLE,
|
|
2836
|
+
extract,
|
|
2837
|
+
streamOperationBindingsFromStructure,
|
|
2838
|
+
RESPONSE_HEADERS_TIMEOUT_MS,
|
|
2839
|
+
buildRequest,
|
|
2840
|
+
invoke,
|
|
2841
|
+
invokeWithLayer,
|
|
2842
|
+
annotationsForOperation,
|
|
2843
|
+
extractOpenApiUpstreamMessage,
|
|
2844
|
+
normalizeOpenApiRefs,
|
|
2845
|
+
compileOpenApiDocument,
|
|
2846
|
+
compileOpenApiSpec,
|
|
2847
|
+
openApiToolDefsFromCompiled,
|
|
2848
|
+
openApiStoredOperationsFromCompiled,
|
|
2849
|
+
buildDefsJsonStreaming,
|
|
2850
|
+
compileAndPersistOpenApiOperations,
|
|
2851
|
+
compileAndPersistOpenApiSpec,
|
|
2852
|
+
compileAndPersistOpenApiSpecStreaming,
|
|
2853
|
+
loadOpenApiSpecText,
|
|
2854
|
+
resolveOpenApiBackedTools,
|
|
2855
|
+
invokeOpenApiBackedTool,
|
|
2856
|
+
validateOpenApiBackedToolArgs,
|
|
2857
|
+
resolveOpenApiBackedAnnotations,
|
|
2858
|
+
checkHealthOpenApi,
|
|
2859
|
+
listHealthCheckCandidatesOpenApi,
|
|
2860
|
+
OAuth2AuthorizationCodeFlow,
|
|
2861
|
+
OAuth2ClientCredentialsFlow,
|
|
2862
|
+
OAuth2Flows,
|
|
2863
|
+
SecurityScheme,
|
|
2864
|
+
AuthStrategy,
|
|
2865
|
+
HeaderPreset,
|
|
2866
|
+
OAuth2Preset,
|
|
2867
|
+
PreviewOperation,
|
|
2868
|
+
SpecPreview,
|
|
2869
|
+
SpecPreviewSummary,
|
|
2870
|
+
previewSpecText,
|
|
2871
|
+
previewSpec
|
|
2872
|
+
};
|
|
2873
|
+
//# sourceMappingURL=chunk-HTHEUUJA.js.map
|