@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,956 @@
|
|
|
1
|
+
import {
|
|
2
|
+
openApiPresets
|
|
3
|
+
} from "./chunk-CURJJXB3.js";
|
|
4
|
+
import {
|
|
5
|
+
deriveAuthenticationTemplateFromPreview,
|
|
6
|
+
firstBaseUrlForPreview
|
|
7
|
+
} from "./chunk-O2LMSZ2Z.js";
|
|
8
|
+
import {
|
|
9
|
+
OAuth2AuthorizationCodeFlow,
|
|
10
|
+
OAuth2Flows,
|
|
11
|
+
OAuth2Preset,
|
|
12
|
+
OperationBinding,
|
|
13
|
+
SecurityScheme,
|
|
14
|
+
checkHealthOpenApi,
|
|
15
|
+
compileAndPersistOpenApiSpecStreaming,
|
|
16
|
+
compileOpenApiSpec,
|
|
17
|
+
extract,
|
|
18
|
+
invokeOpenApiBackedTool,
|
|
19
|
+
listHealthCheckCandidatesOpenApi,
|
|
20
|
+
normalizeOpenApiAuthInputs,
|
|
21
|
+
openApiStoredOperationsFromCompiled,
|
|
22
|
+
parse,
|
|
23
|
+
previewSpecText,
|
|
24
|
+
resolveOpenApiBackedAnnotations,
|
|
25
|
+
resolveOpenApiBackedTools,
|
|
26
|
+
resolveServerUrl,
|
|
27
|
+
resolveSpecText,
|
|
28
|
+
validateOpenApiBackedToolArgs
|
|
29
|
+
} from "./chunk-HTHEUUJA.js";
|
|
30
|
+
import {
|
|
31
|
+
decodeOpenApiIntegrationConfig
|
|
32
|
+
} from "./chunk-R7TVM3XD.js";
|
|
33
|
+
import {
|
|
34
|
+
OpenApiParseError
|
|
35
|
+
} from "./chunk-CXFO6TLJ.js";
|
|
36
|
+
|
|
37
|
+
// src/sdk/store.ts
|
|
38
|
+
import { Effect, Option, Predicate, Schema } from "effect";
|
|
39
|
+
var OPERATION_COLLECTION = "operation";
|
|
40
|
+
var STORE_OWNER = "org";
|
|
41
|
+
var OPERATION_KEY_VERSION = "op";
|
|
42
|
+
var encodeBinding = Schema.encodeSync(OperationBinding);
|
|
43
|
+
var decodeBinding = Schema.decodeUnknownSync(OperationBinding);
|
|
44
|
+
var decodeBindingJson = Schema.decodeUnknownSync(Schema.fromJsonString(OperationBinding));
|
|
45
|
+
var toJsonRecord = (value) => value;
|
|
46
|
+
var OperationStorage = Schema.Struct({
|
|
47
|
+
integration: Schema.String,
|
|
48
|
+
toolName: Schema.String,
|
|
49
|
+
binding: Schema.Unknown,
|
|
50
|
+
// Resolved tool description (operation description / summary / method+path
|
|
51
|
+
// fallback), persisted so the serve path can rebuild the tool def without
|
|
52
|
+
// re-parsing the spec. Optional: legacy rows predate it and resolve via the
|
|
53
|
+
// parse fallback.
|
|
54
|
+
description: Schema.optional(Schema.String)
|
|
55
|
+
});
|
|
56
|
+
var decodeOperationStorage = Schema.decodeUnknownOption(OperationStorage);
|
|
57
|
+
var rowToOperation = (row) => {
|
|
58
|
+
const decoded = decodeOperationStorage(row.data);
|
|
59
|
+
if (Option.isNone(decoded)) return null;
|
|
60
|
+
const operation = decoded.value;
|
|
61
|
+
return {
|
|
62
|
+
integration: operation.integration,
|
|
63
|
+
toolName: operation.toolName,
|
|
64
|
+
binding: decodeBinding(
|
|
65
|
+
typeof operation.binding === "string" ? decodeBindingJson(operation.binding) : operation.binding
|
|
66
|
+
),
|
|
67
|
+
...operation.description !== void 0 ? { description: operation.description } : {}
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
var stableKeyHash = (value) => {
|
|
71
|
+
let hash = 0xcbf29ce484222325n;
|
|
72
|
+
const prime = 0x100000001b3n;
|
|
73
|
+
const mask = 0xffffffffffffffffn;
|
|
74
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
75
|
+
hash ^= BigInt(value.charCodeAt(index));
|
|
76
|
+
hash = hash * prime & mask;
|
|
77
|
+
}
|
|
78
|
+
return hash.toString(36).padStart(13, "0");
|
|
79
|
+
};
|
|
80
|
+
var operationKey = (integration, toolName) => `${OPERATION_KEY_VERSION}.${stableKeyHash(integration)}.${stableKeyHash(toolName)}`;
|
|
81
|
+
var legacyOperationKey = (integration, toolName) => `${integration}.${toolName}`;
|
|
82
|
+
var specBlobKey = (specHash) => `spec/${specHash}`;
|
|
83
|
+
var defsBlobKey = (specHash) => `defs/${specHash}`;
|
|
84
|
+
var makeDefaultOpenapiStore = ({ pluginStorage, blobs }) => {
|
|
85
|
+
const operationData = (operation) => ({
|
|
86
|
+
integration: operation.integration,
|
|
87
|
+
toolName: operation.toolName,
|
|
88
|
+
binding: toJsonRecord(encodeBinding(operation.binding)),
|
|
89
|
+
...operation.description !== void 0 ? { description: operation.description } : {}
|
|
90
|
+
});
|
|
91
|
+
const listRows = (integration) => pluginStorage.list({ collection: OPERATION_COLLECTION }).pipe(
|
|
92
|
+
Effect.map(
|
|
93
|
+
(rows) => rows.filter((row) => rowToOperation(row)?.integration === integration)
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
const removeOperations = (integration) => Effect.gen(function* () {
|
|
97
|
+
const rows = yield* listRows(integration);
|
|
98
|
+
yield* pluginStorage.removeMany({
|
|
99
|
+
owner: STORE_OWNER,
|
|
100
|
+
entries: rows.map((row) => ({ collection: OPERATION_COLLECTION, key: row.key }))
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
const appendOperations = (integration, operations) => pluginStorage.putMany({
|
|
104
|
+
owner: STORE_OWNER,
|
|
105
|
+
entries: operations.map((operation) => ({
|
|
106
|
+
collection: OPERATION_COLLECTION,
|
|
107
|
+
key: operationKey(integration, operation.toolName),
|
|
108
|
+
data: operationData(operation)
|
|
109
|
+
}))
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
putOperations: (integration, operations) => Effect.gen(function* () {
|
|
113
|
+
yield* removeOperations(integration);
|
|
114
|
+
yield* appendOperations(integration, operations);
|
|
115
|
+
}),
|
|
116
|
+
appendOperations,
|
|
117
|
+
getOperation: (integration, toolName) => Effect.gen(function* () {
|
|
118
|
+
const row = yield* pluginStorage.get({
|
|
119
|
+
collection: OPERATION_COLLECTION,
|
|
120
|
+
key: operationKey(integration, toolName)
|
|
121
|
+
});
|
|
122
|
+
if (row) return rowToOperation(row);
|
|
123
|
+
const legacyKey = legacyOperationKey(integration, toolName);
|
|
124
|
+
if (legacyKey.length > 255) return null;
|
|
125
|
+
const legacyRow = yield* pluginStorage.get({
|
|
126
|
+
collection: OPERATION_COLLECTION,
|
|
127
|
+
key: legacyKey
|
|
128
|
+
});
|
|
129
|
+
return legacyRow ? rowToOperation(legacyRow) : null;
|
|
130
|
+
}),
|
|
131
|
+
listOperations: (integration) => listRows(integration).pipe(
|
|
132
|
+
Effect.map((rows) => rows.map(rowToOperation).filter(Predicate.isNotNull))
|
|
133
|
+
),
|
|
134
|
+
removeOperations,
|
|
135
|
+
putSpec: (specHash, specText) => blobs.put(specBlobKey(specHash), specText, { owner: STORE_OWNER }),
|
|
136
|
+
getSpec: (specHash) => blobs.get(specBlobKey(specHash)),
|
|
137
|
+
putDefs: (specHash, defsJson) => blobs.put(defsBlobKey(specHash), defsJson, { owner: STORE_OWNER }),
|
|
138
|
+
getDefs: (specHash) => blobs.get(defsBlobKey(specHash))
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// src/sdk/spec-format.ts
|
|
143
|
+
import { Effect as Effect2 } from "effect";
|
|
144
|
+
var resolveSpecFormatAdapter = (adapters, id) => {
|
|
145
|
+
if (!id) return Effect2.succeed(null);
|
|
146
|
+
const adapter = adapters.find((candidate) => candidate.id === id);
|
|
147
|
+
if (adapter) return Effect2.succeed(adapter);
|
|
148
|
+
return Effect2.fail(new OpenApiParseError({ message: `Unknown OpenAPI spec format: ${id}` }));
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/sdk/plugin.ts
|
|
152
|
+
import { Effect as Effect3, Option as Option2, Schema as Schema2 } from "effect";
|
|
153
|
+
import {
|
|
154
|
+
IntegrationAlreadyExistsError,
|
|
155
|
+
IntegrationDetectionResult,
|
|
156
|
+
IntegrationNotFoundError,
|
|
157
|
+
IntegrationSlug,
|
|
158
|
+
ToolResult,
|
|
159
|
+
definePlugin,
|
|
160
|
+
HealthCheckSpec,
|
|
161
|
+
mergeAuthTemplates,
|
|
162
|
+
sha256Hex,
|
|
163
|
+
tool
|
|
164
|
+
} from "@rafads/sdk/core";
|
|
165
|
+
import { ApiKeyAuthTemplate, describeApiKeyAuthMethod } from "@rafads/sdk/http-auth";
|
|
166
|
+
var PreviewSpecInputSchema = Schema2.Struct({
|
|
167
|
+
spec: Schema2.String,
|
|
168
|
+
specFormat: Schema2.optional(Schema2.String)
|
|
169
|
+
});
|
|
170
|
+
var StaticPreviewServerVariableSchema = Schema2.Struct({
|
|
171
|
+
default: Schema2.String,
|
|
172
|
+
enum: Schema2.NullOr(Schema2.Array(Schema2.String)),
|
|
173
|
+
description: Schema2.NullOr(Schema2.String)
|
|
174
|
+
});
|
|
175
|
+
var StaticPreviewServerSchema = Schema2.Struct({
|
|
176
|
+
url: Schema2.String,
|
|
177
|
+
description: Schema2.NullOr(Schema2.String),
|
|
178
|
+
variables: Schema2.NullOr(Schema2.Record(Schema2.String, StaticPreviewServerVariableSchema))
|
|
179
|
+
});
|
|
180
|
+
var StaticPreviewOAuthAuthorizationCodeFlowSchema = Schema2.Struct({
|
|
181
|
+
authorizationUrl: Schema2.String,
|
|
182
|
+
tokenUrl: Schema2.String,
|
|
183
|
+
refreshUrl: Schema2.NullOr(Schema2.String),
|
|
184
|
+
scopes: Schema2.Record(Schema2.String, Schema2.String)
|
|
185
|
+
});
|
|
186
|
+
var StaticPreviewOAuthClientCredentialsFlowSchema = Schema2.Struct({
|
|
187
|
+
tokenUrl: Schema2.String,
|
|
188
|
+
refreshUrl: Schema2.NullOr(Schema2.String),
|
|
189
|
+
scopes: Schema2.Record(Schema2.String, Schema2.String)
|
|
190
|
+
});
|
|
191
|
+
var StaticPreviewOAuthFlowsSchema = Schema2.Struct({
|
|
192
|
+
authorizationCode: Schema2.NullOr(StaticPreviewOAuthAuthorizationCodeFlowSchema),
|
|
193
|
+
clientCredentials: Schema2.NullOr(StaticPreviewOAuthClientCredentialsFlowSchema)
|
|
194
|
+
});
|
|
195
|
+
var StaticPreviewSecuritySchemeSchema = Schema2.Struct({
|
|
196
|
+
name: Schema2.String,
|
|
197
|
+
type: Schema2.Literals(["http", "apiKey", "oauth2", "openIdConnect"]),
|
|
198
|
+
scheme: Schema2.NullOr(Schema2.String),
|
|
199
|
+
bearerFormat: Schema2.NullOr(Schema2.String),
|
|
200
|
+
in: Schema2.NullOr(Schema2.Literals(["header", "query", "cookie"])),
|
|
201
|
+
headerName: Schema2.NullOr(Schema2.String),
|
|
202
|
+
description: Schema2.NullOr(Schema2.String),
|
|
203
|
+
flows: Schema2.NullOr(StaticPreviewOAuthFlowsSchema),
|
|
204
|
+
openIdConnectUrl: Schema2.NullOr(Schema2.String)
|
|
205
|
+
});
|
|
206
|
+
var StaticPreviewOAuth2PresetSchema = Schema2.Struct({
|
|
207
|
+
label: Schema2.String,
|
|
208
|
+
securitySchemeName: Schema2.String,
|
|
209
|
+
flow: Schema2.Literals(["authorizationCode", "clientCredentials"]),
|
|
210
|
+
authorizationUrl: Schema2.NullOr(Schema2.String),
|
|
211
|
+
tokenUrl: Schema2.String,
|
|
212
|
+
resource: Schema2.NullOr(Schema2.String),
|
|
213
|
+
refreshUrl: Schema2.NullOr(Schema2.String),
|
|
214
|
+
scopes: Schema2.Record(Schema2.String, Schema2.String),
|
|
215
|
+
identityScopes: Schema2.Union([
|
|
216
|
+
Schema2.Literal("auto"),
|
|
217
|
+
Schema2.Literal(false),
|
|
218
|
+
Schema2.Array(Schema2.String)
|
|
219
|
+
]),
|
|
220
|
+
supportsClientIdMetadataDocument: Schema2.optional(Schema2.Boolean)
|
|
221
|
+
});
|
|
222
|
+
var StaticPreviewSpecOutputSchema = Schema2.Struct({
|
|
223
|
+
title: Schema2.NullOr(Schema2.String),
|
|
224
|
+
version: Schema2.NullOr(Schema2.String),
|
|
225
|
+
servers: Schema2.Array(StaticPreviewServerSchema),
|
|
226
|
+
operationCount: Schema2.Number,
|
|
227
|
+
tags: Schema2.Array(Schema2.String),
|
|
228
|
+
securitySchemes: Schema2.Array(StaticPreviewSecuritySchemeSchema),
|
|
229
|
+
authStrategies: Schema2.Array(Schema2.Struct({ schemes: Schema2.Array(Schema2.String) })),
|
|
230
|
+
headerPresets: Schema2.Array(
|
|
231
|
+
Schema2.Struct({
|
|
232
|
+
label: Schema2.String,
|
|
233
|
+
headers: Schema2.Record(Schema2.String, Schema2.NullOr(Schema2.String)),
|
|
234
|
+
secretHeaders: Schema2.Array(Schema2.String)
|
|
235
|
+
})
|
|
236
|
+
),
|
|
237
|
+
oauth2Presets: Schema2.Array(StaticPreviewOAuth2PresetSchema)
|
|
238
|
+
});
|
|
239
|
+
var OpenApiSpecInputSchema = Schema2.Union([
|
|
240
|
+
Schema2.Struct({ kind: Schema2.Literal("url"), url: Schema2.String }),
|
|
241
|
+
Schema2.Struct({ kind: Schema2.Literal("blob"), value: Schema2.String })
|
|
242
|
+
]);
|
|
243
|
+
var AuthenticationSchema = Schema2.Union([
|
|
244
|
+
Schema2.Struct({
|
|
245
|
+
slug: Schema2.String,
|
|
246
|
+
kind: Schema2.Literal("oauth2"),
|
|
247
|
+
authorizationUrl: Schema2.String,
|
|
248
|
+
tokenUrl: Schema2.String,
|
|
249
|
+
resource: Schema2.optional(Schema2.NullOr(Schema2.String)),
|
|
250
|
+
scopes: Schema2.Array(Schema2.String),
|
|
251
|
+
supportsClientIdMetadataDocument: Schema2.optional(Schema2.Boolean)
|
|
252
|
+
}),
|
|
253
|
+
// Credential methods are authored request-shaped - the ONE apikey input
|
|
254
|
+
// dialect: `{ type: "apiKey", headers: { Authorization: ["Bearer ",
|
|
255
|
+
// variable("token")] }, queryParams: { … } }`.
|
|
256
|
+
ApiKeyAuthTemplate
|
|
257
|
+
]);
|
|
258
|
+
var AddIntegrationInputSchema = Schema2.Struct({
|
|
259
|
+
spec: OpenApiSpecInputSchema,
|
|
260
|
+
slug: Schema2.optional(Schema2.String),
|
|
261
|
+
name: Schema2.optional(Schema2.String),
|
|
262
|
+
description: Schema2.optional(Schema2.String),
|
|
263
|
+
baseUrl: Schema2.optional(Schema2.String),
|
|
264
|
+
headers: Schema2.optional(Schema2.Record(Schema2.String, Schema2.String)),
|
|
265
|
+
queryParams: Schema2.optional(Schema2.Record(Schema2.String, Schema2.String)),
|
|
266
|
+
specFormat: Schema2.optional(Schema2.String),
|
|
267
|
+
family: Schema2.optional(Schema2.String),
|
|
268
|
+
healthCheck: Schema2.optional(HealthCheckSpec),
|
|
269
|
+
authenticationTemplate: Schema2.optional(Schema2.Array(AuthenticationSchema))
|
|
270
|
+
});
|
|
271
|
+
var AddIntegrationOutputSchema = Schema2.Struct({
|
|
272
|
+
slug: Schema2.String,
|
|
273
|
+
toolCount: Schema2.Number
|
|
274
|
+
});
|
|
275
|
+
var PreviewSpecInputStandardSchema = Schema2.toStandardSchemaV1(
|
|
276
|
+
Schema2.toStandardJSONSchemaV1(PreviewSpecInputSchema)
|
|
277
|
+
);
|
|
278
|
+
var PreviewSpecOutputStandardSchema = Schema2.toStandardSchemaV1(
|
|
279
|
+
Schema2.toStandardJSONSchemaV1(StaticPreviewSpecOutputSchema)
|
|
280
|
+
);
|
|
281
|
+
var AddIntegrationInputStandardSchema = Schema2.toStandardSchemaV1(
|
|
282
|
+
Schema2.toStandardJSONSchemaV1(AddIntegrationInputSchema)
|
|
283
|
+
);
|
|
284
|
+
var AddIntegrationOutputStandardSchema = Schema2.toStandardSchemaV1(
|
|
285
|
+
Schema2.toStandardJSONSchemaV1(AddIntegrationOutputSchema)
|
|
286
|
+
);
|
|
287
|
+
var openApiToolFailure = (code, message, details) => ToolResult.fail({
|
|
288
|
+
code,
|
|
289
|
+
message,
|
|
290
|
+
...details === void 0 ? {} : { details }
|
|
291
|
+
});
|
|
292
|
+
var staticPreviewOutput = (preview) => ({
|
|
293
|
+
title: Option2.getOrNull(preview.title),
|
|
294
|
+
version: Option2.getOrNull(preview.version),
|
|
295
|
+
servers: preview.servers.map((server) => ({
|
|
296
|
+
url: server.url,
|
|
297
|
+
description: Option2.getOrNull(server.description),
|
|
298
|
+
variables: Option2.getOrNull(server.variables) ? Object.fromEntries(
|
|
299
|
+
Object.entries(Option2.getOrNull(server.variables) ?? {}).map(([name, variable]) => [
|
|
300
|
+
name,
|
|
301
|
+
{
|
|
302
|
+
default: variable.default,
|
|
303
|
+
enum: Option2.getOrNull(variable.enum),
|
|
304
|
+
description: Option2.getOrNull(variable.description)
|
|
305
|
+
}
|
|
306
|
+
])
|
|
307
|
+
) : null
|
|
308
|
+
})),
|
|
309
|
+
operationCount: preview.operationCount,
|
|
310
|
+
tags: preview.tags,
|
|
311
|
+
securitySchemes: preview.securitySchemes.map((scheme) => ({
|
|
312
|
+
name: scheme.name,
|
|
313
|
+
type: scheme.type,
|
|
314
|
+
scheme: Option2.getOrNull(scheme.scheme),
|
|
315
|
+
bearerFormat: Option2.getOrNull(scheme.bearerFormat),
|
|
316
|
+
in: Option2.getOrNull(scheme.in),
|
|
317
|
+
headerName: Option2.getOrNull(scheme.headerName),
|
|
318
|
+
description: Option2.getOrNull(scheme.description),
|
|
319
|
+
flows: Option2.isSome(scheme.flows) ? {
|
|
320
|
+
authorizationCode: Option2.isSome(scheme.flows.value.authorizationCode) ? {
|
|
321
|
+
authorizationUrl: scheme.flows.value.authorizationCode.value.authorizationUrl,
|
|
322
|
+
tokenUrl: scheme.flows.value.authorizationCode.value.tokenUrl,
|
|
323
|
+
refreshUrl: Option2.getOrNull(scheme.flows.value.authorizationCode.value.refreshUrl),
|
|
324
|
+
scopes: scheme.flows.value.authorizationCode.value.scopes
|
|
325
|
+
} : null,
|
|
326
|
+
clientCredentials: Option2.isSome(scheme.flows.value.clientCredentials) ? {
|
|
327
|
+
tokenUrl: scheme.flows.value.clientCredentials.value.tokenUrl,
|
|
328
|
+
refreshUrl: Option2.getOrNull(scheme.flows.value.clientCredentials.value.refreshUrl),
|
|
329
|
+
scopes: scheme.flows.value.clientCredentials.value.scopes
|
|
330
|
+
} : null
|
|
331
|
+
} : null,
|
|
332
|
+
openIdConnectUrl: Option2.getOrNull(scheme.openIdConnectUrl)
|
|
333
|
+
})),
|
|
334
|
+
authStrategies: preview.authStrategies,
|
|
335
|
+
headerPresets: preview.headerPresets,
|
|
336
|
+
oauth2Presets: preview.oauth2Presets.map((preset) => ({
|
|
337
|
+
label: preset.label,
|
|
338
|
+
securitySchemeName: preset.securitySchemeName,
|
|
339
|
+
flow: preset.flow,
|
|
340
|
+
authorizationUrl: Option2.getOrNull(preset.authorizationUrl),
|
|
341
|
+
tokenUrl: preset.tokenUrl,
|
|
342
|
+
resource: Option2.getOrNull(preset.resource),
|
|
343
|
+
refreshUrl: Option2.getOrNull(preset.refreshUrl),
|
|
344
|
+
scopes: preset.scopes,
|
|
345
|
+
identityScopes: preset.identityScopes,
|
|
346
|
+
supportsClientIdMetadataDocument: preset.supportsClientIdMetadataDocument
|
|
347
|
+
}))
|
|
348
|
+
});
|
|
349
|
+
var specInputToSpecUrl = (spec) => spec.kind === "url" ? spec.url : void 0;
|
|
350
|
+
var OAUTH_DISCOVERED_SCHEME_NAME = "DiscoveredOAuth2";
|
|
351
|
+
var OPENAPI_HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
352
|
+
"get",
|
|
353
|
+
"put",
|
|
354
|
+
"post",
|
|
355
|
+
"delete",
|
|
356
|
+
"patch",
|
|
357
|
+
"head",
|
|
358
|
+
"options",
|
|
359
|
+
"trace"
|
|
360
|
+
]);
|
|
361
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
362
|
+
var maybeUrl = (value) => {
|
|
363
|
+
try {
|
|
364
|
+
return new URL(value);
|
|
365
|
+
} catch {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
var addProbeCandidate = (candidates, value) => {
|
|
370
|
+
const trimmed = value?.trim();
|
|
371
|
+
if (!trimmed) return;
|
|
372
|
+
const parsed = maybeUrl(trimmed);
|
|
373
|
+
if (!parsed || parsed.protocol !== "https:" && parsed.protocol !== "http:") return;
|
|
374
|
+
const normalized = parsed.toString();
|
|
375
|
+
const origin = parsed.origin;
|
|
376
|
+
if (!candidates.includes(normalized)) candidates.push(normalized);
|
|
377
|
+
if (!candidates.includes(origin)) candidates.push(origin);
|
|
378
|
+
};
|
|
379
|
+
var oauthProbeCandidates = (preview, specUrl, baseUrl) => {
|
|
380
|
+
const candidates = [];
|
|
381
|
+
addProbeCandidate(candidates, baseUrl);
|
|
382
|
+
for (const server of preview.servers) {
|
|
383
|
+
addProbeCandidate(
|
|
384
|
+
candidates,
|
|
385
|
+
resolveServerUrl(server.url, Option2.getOrUndefined(server.variables), {})
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
addProbeCandidate(candidates, specUrl);
|
|
389
|
+
return candidates;
|
|
390
|
+
};
|
|
391
|
+
var securityRequirementScopes = (security, targetSchemes) => {
|
|
392
|
+
if (!Array.isArray(security)) return [];
|
|
393
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
394
|
+
for (const requirement of security) {
|
|
395
|
+
if (!isRecord(requirement)) continue;
|
|
396
|
+
for (const [scheme, rawScopes] of Object.entries(requirement)) {
|
|
397
|
+
if (targetSchemes.size > 0 && !targetSchemes.has(scheme)) continue;
|
|
398
|
+
if (!Array.isArray(rawScopes)) continue;
|
|
399
|
+
for (const scope of rawScopes) {
|
|
400
|
+
if (typeof scope === "string" && scope.trim().length > 0) scopes.add(scope.trim());
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return [...scopes];
|
|
405
|
+
};
|
|
406
|
+
var collectDeclaredSecurityScopes = (doc, targetSchemes) => {
|
|
407
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
408
|
+
if (!isRecord(doc)) return [];
|
|
409
|
+
for (const scope of securityRequirementScopes(doc.security, targetSchemes)) scopes.add(scope);
|
|
410
|
+
const paths = doc.paths;
|
|
411
|
+
if (!isRecord(paths)) return [...scopes].sort();
|
|
412
|
+
for (const pathItem of Object.values(paths)) {
|
|
413
|
+
if (!isRecord(pathItem)) continue;
|
|
414
|
+
for (const [method, operation] of Object.entries(pathItem)) {
|
|
415
|
+
if (!OPENAPI_HTTP_METHODS.has(method.toLowerCase()) || !isRecord(operation)) continue;
|
|
416
|
+
for (const scope of securityRequirementScopes(operation.security, targetSchemes)) {
|
|
417
|
+
scopes.add(scope);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return [...scopes].sort();
|
|
422
|
+
};
|
|
423
|
+
var nonOAuthSecuritySchemeNames = (preview) => new Set(
|
|
424
|
+
preview.securitySchemes.filter((scheme) => scheme.type === "http" || scheme.type === "apiKey").map((scheme) => scheme.name)
|
|
425
|
+
);
|
|
426
|
+
var discoveredOAuthPreview = (input) => {
|
|
427
|
+
const scopes = Object.fromEntries(input.scopes.map((scope) => [scope, ""]));
|
|
428
|
+
const flow = OAuth2AuthorizationCodeFlow.make({
|
|
429
|
+
authorizationUrl: input.authorizationUrl,
|
|
430
|
+
tokenUrl: input.tokenUrl,
|
|
431
|
+
refreshUrl: Option2.none(),
|
|
432
|
+
scopes
|
|
433
|
+
});
|
|
434
|
+
const flows = OAuth2Flows.make({
|
|
435
|
+
authorizationCode: Option2.some(flow),
|
|
436
|
+
clientCredentials: Option2.none()
|
|
437
|
+
});
|
|
438
|
+
return {
|
|
439
|
+
...input.preview,
|
|
440
|
+
securitySchemes: [
|
|
441
|
+
...input.preview.securitySchemes,
|
|
442
|
+
SecurityScheme.make({
|
|
443
|
+
name: OAUTH_DISCOVERED_SCHEME_NAME,
|
|
444
|
+
type: "oauth2",
|
|
445
|
+
scheme: Option2.none(),
|
|
446
|
+
bearerFormat: Option2.none(),
|
|
447
|
+
in: Option2.none(),
|
|
448
|
+
headerName: Option2.none(),
|
|
449
|
+
description: Option2.some("Discovered from OAuth authorization-server metadata"),
|
|
450
|
+
flows: Option2.some(flows),
|
|
451
|
+
openIdConnectUrl: Option2.none()
|
|
452
|
+
})
|
|
453
|
+
],
|
|
454
|
+
oauth2Presets: [
|
|
455
|
+
...input.preview.oauth2Presets,
|
|
456
|
+
OAuth2Preset.make({
|
|
457
|
+
label: `OAuth2 Authorization Code \xB7 ${OAUTH_DISCOVERED_SCHEME_NAME}`,
|
|
458
|
+
securitySchemeName: OAUTH_DISCOVERED_SCHEME_NAME,
|
|
459
|
+
flow: "authorizationCode",
|
|
460
|
+
authorizationUrl: Option2.some(input.authorizationUrl),
|
|
461
|
+
tokenUrl: input.tokenUrl,
|
|
462
|
+
resource: input.resource ? Option2.some(input.resource) : Option2.none(),
|
|
463
|
+
refreshUrl: Option2.none(),
|
|
464
|
+
scopes,
|
|
465
|
+
identityScopes: "auto",
|
|
466
|
+
...input.supportsClientIdMetadataDocument === true ? { supportsClientIdMetadataDocument: true } : {}
|
|
467
|
+
})
|
|
468
|
+
]
|
|
469
|
+
};
|
|
470
|
+
};
|
|
471
|
+
var describeOpenApiAuthMethods = (record) => {
|
|
472
|
+
const config = decodeOpenApiIntegrationConfig(record.config);
|
|
473
|
+
if (!config) return [];
|
|
474
|
+
return (config.authenticationTemplate ?? []).map(
|
|
475
|
+
(template) => {
|
|
476
|
+
if (template.kind === "oauth2") {
|
|
477
|
+
return {
|
|
478
|
+
id: String(template.slug),
|
|
479
|
+
label: "OAuth2",
|
|
480
|
+
kind: "oauth",
|
|
481
|
+
template: String(template.slug),
|
|
482
|
+
oauth: {
|
|
483
|
+
authorizationUrl: template.authorizationUrl,
|
|
484
|
+
tokenUrl: template.tokenUrl,
|
|
485
|
+
resource: template.resource ?? null,
|
|
486
|
+
scopes: template.scopes,
|
|
487
|
+
supportsClientIdMetadataDocument: template.supportsClientIdMetadataDocument
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
return describeApiKeyAuthMethod(template);
|
|
492
|
+
}
|
|
493
|
+
);
|
|
494
|
+
};
|
|
495
|
+
var describeOpenApiIntegrationDisplay = (record) => {
|
|
496
|
+
const config = decodeOpenApiIntegrationConfig(record.config);
|
|
497
|
+
return {
|
|
498
|
+
url: config?.baseUrl ?? config?.specUrl,
|
|
499
|
+
...config?.family ? { family: config.family } : {}
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
var openApiPlugin = definePlugin((options) => {
|
|
503
|
+
const resolveSpecForInput = (config, httpClientLayer) => Effect3.gen(function* () {
|
|
504
|
+
const adapter = yield* resolveSpecFormatAdapter(
|
|
505
|
+
options?.specFormats ?? [],
|
|
506
|
+
config.specFormat
|
|
507
|
+
);
|
|
508
|
+
if (adapter) {
|
|
509
|
+
if (config.spec.kind !== "url") {
|
|
510
|
+
return yield* new OpenApiParseError({
|
|
511
|
+
message: "Spec format adapters require a URL spec input"
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
return yield* adapter.fetch({
|
|
515
|
+
urls: [config.spec.url],
|
|
516
|
+
credentials: {
|
|
517
|
+
...config.headers ? { headers: config.headers } : {},
|
|
518
|
+
...config.queryParams ? { queryParams: config.queryParams } : {}
|
|
519
|
+
},
|
|
520
|
+
httpClientLayer
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
if (config.spec.kind === "url") {
|
|
524
|
+
const specText = yield* resolveSpecText(config.spec.url).pipe(
|
|
525
|
+
Effect3.provide(httpClientLayer)
|
|
526
|
+
);
|
|
527
|
+
return { specText, specUrl: config.spec.url };
|
|
528
|
+
}
|
|
529
|
+
return { specText: config.spec.value };
|
|
530
|
+
});
|
|
531
|
+
return {
|
|
532
|
+
id: "openapi",
|
|
533
|
+
packageName: "@rafads/plugin-openapi",
|
|
534
|
+
clientConfig: options?.presets ? { presets: options.presets } : void 0,
|
|
535
|
+
integrationPresets: [...openApiPresets, ...options?.presets ?? []].map((preset) => ({
|
|
536
|
+
id: preset.id,
|
|
537
|
+
name: preset.name,
|
|
538
|
+
summary: preset.summary,
|
|
539
|
+
...preset.url ? { url: preset.url } : {},
|
|
540
|
+
...preset.icon ? { icon: preset.icon } : {},
|
|
541
|
+
...preset.featured ? { featured: preset.featured } : {},
|
|
542
|
+
...preset.family ? { family: preset.family } : {},
|
|
543
|
+
...preset.specFormat ? { specFormat: preset.specFormat } : {},
|
|
544
|
+
...preset.defaultSlug ? { defaultSlug: preset.defaultSlug } : {},
|
|
545
|
+
...preset.authTemplate ? { authTemplate: preset.authTemplate } : {},
|
|
546
|
+
...preset.healthCheck ? { healthCheck: preset.healthCheck } : {}
|
|
547
|
+
})),
|
|
548
|
+
storage: (deps) => makeDefaultOpenapiStore(deps),
|
|
549
|
+
extension: (ctx) => {
|
|
550
|
+
const httpClientLayer = options?.httpClientLayer ?? ctx.httpClientLayer;
|
|
551
|
+
const enrichPreviewWithDiscoveredOAuth = (input) => Effect3.gen(function* () {
|
|
552
|
+
if (input.preview.oauth2Presets.length > 0) return input.preview;
|
|
553
|
+
const candidates = oauthProbeCandidates(input.preview, input.specUrl, input.baseUrl);
|
|
554
|
+
if (candidates.length === 0) return input.preview;
|
|
555
|
+
for (const candidate of candidates) {
|
|
556
|
+
const oauth = yield* ctx.oauth.probe({ url: candidate }).pipe(
|
|
557
|
+
Effect3.map((result) => ({ ok: true, result })),
|
|
558
|
+
Effect3.catch(() => Effect3.succeed({ ok: false, result: null }))
|
|
559
|
+
);
|
|
560
|
+
if (!oauth.ok) continue;
|
|
561
|
+
const doc = yield* parse(input.specText);
|
|
562
|
+
const declaredScopes = collectDeclaredSecurityScopes(
|
|
563
|
+
doc,
|
|
564
|
+
nonOAuthSecuritySchemeNames(input.preview)
|
|
565
|
+
);
|
|
566
|
+
const supportedScopes = oauth.result.scopesSupported && oauth.result.scopesSupported.length > 0 ? new Set(oauth.result.scopesSupported) : null;
|
|
567
|
+
const scopes = supportedScopes ? declaredScopes.filter((scope) => supportedScopes.has(scope)) : declaredScopes;
|
|
568
|
+
return discoveredOAuthPreview({
|
|
569
|
+
preview: input.preview,
|
|
570
|
+
authorizationUrl: oauth.result.authorizationUrl,
|
|
571
|
+
tokenUrl: oauth.result.tokenUrl,
|
|
572
|
+
resource: oauth.result.resource ?? null,
|
|
573
|
+
scopes,
|
|
574
|
+
supportsClientIdMetadataDocument: oauth.result.clientIdMetadataDocumentSupported === true
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
return input.preview;
|
|
578
|
+
});
|
|
579
|
+
const addSpec = (config) => Effect3.gen(function* () {
|
|
580
|
+
const resolved = yield* resolveSpecForInput(config, httpClientLayer);
|
|
581
|
+
const compiled = resolved.keepPathItem ? void 0 : yield* compileOpenApiSpec(resolved.specText);
|
|
582
|
+
const adapter = yield* resolveSpecFormatAdapter(
|
|
583
|
+
options?.specFormats ?? [],
|
|
584
|
+
config.specFormat
|
|
585
|
+
);
|
|
586
|
+
const derivedIdentity = adapter?.deriveIdentity && resolved.document ? adapter.deriveIdentity(resolved.document) : null;
|
|
587
|
+
const resolvedSlug = config.slug?.trim() || derivedIdentity?.slug;
|
|
588
|
+
if (!resolvedSlug) {
|
|
589
|
+
return yield* new OpenApiParseError({
|
|
590
|
+
message: "OpenAPI integration slug is required"
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
const explicitBaseUrl = config.baseUrl ?? resolved.baseUrl;
|
|
594
|
+
const needsDerivedBaseUrl = explicitBaseUrl == null;
|
|
595
|
+
const needsDerivedAuth = config.authenticationTemplate == null;
|
|
596
|
+
const preview = needsDerivedBaseUrl || needsDerivedAuth ? yield* previewSpecText(resolved.specText).pipe(
|
|
597
|
+
Effect3.flatMap(
|
|
598
|
+
(rawPreview) => enrichPreviewWithDiscoveredOAuth({
|
|
599
|
+
specText: resolved.specText,
|
|
600
|
+
preview: rawPreview,
|
|
601
|
+
specUrl: resolved.specUrl ?? specInputToSpecUrl(config.spec),
|
|
602
|
+
baseUrl: explicitBaseUrl
|
|
603
|
+
})
|
|
604
|
+
)
|
|
605
|
+
) : void 0;
|
|
606
|
+
const derivedBaseUrl = needsDerivedBaseUrl && preview ? firstBaseUrlForPreview(preview) : void 0;
|
|
607
|
+
const effectiveBaseUrl = explicitBaseUrl ?? (derivedBaseUrl || void 0);
|
|
608
|
+
const derivedAuthenticationTemplate = needsDerivedAuth && preview ? deriveAuthenticationTemplateFromPreview(preview, effectiveBaseUrl) : void 0;
|
|
609
|
+
const slug = IntegrationSlug.make(resolvedSlug);
|
|
610
|
+
const existing = yield* ctx.core.integrations.get(slug);
|
|
611
|
+
if (existing) {
|
|
612
|
+
return yield* new IntegrationAlreadyExistsError({ slug });
|
|
613
|
+
}
|
|
614
|
+
const specHash = yield* sha256Hex(resolved.specText);
|
|
615
|
+
const integrationConfig = {
|
|
616
|
+
...resolved.config ?? {},
|
|
617
|
+
specHash,
|
|
618
|
+
...(resolved.specUrl ?? specInputToSpecUrl(config.spec)) !== void 0 ? { specUrl: resolved.specUrl ?? specInputToSpecUrl(config.spec) } : {},
|
|
619
|
+
// baseUrl is an optional override only. The host is otherwise
|
|
620
|
+
// resolved per call from the operation's `servers` (extracted from
|
|
621
|
+
// the spec), so we never bake a derived base URL into the config.
|
|
622
|
+
...config.baseUrl ? { baseUrl: config.baseUrl } : {},
|
|
623
|
+
...config.headers ? { headers: config.headers } : {},
|
|
624
|
+
...config.queryParams ? { queryParams: config.queryParams } : {},
|
|
625
|
+
...config.specFormat ? { specFormat: config.specFormat } : {},
|
|
626
|
+
...config.family ? { family: config.family } : {},
|
|
627
|
+
// Prefer the caller's explicit template; otherwise derive from the
|
|
628
|
+
// spec's declared security schemes.
|
|
629
|
+
...config.authenticationTemplate ? {
|
|
630
|
+
authenticationTemplate: normalizeOpenApiAuthInputs(config.authenticationTemplate)
|
|
631
|
+
} : resolved.authenticationTemplate && resolved.authenticationTemplate.length > 0 ? { authenticationTemplate: resolved.authenticationTemplate } : derivedAuthenticationTemplate && derivedAuthenticationTemplate.length > 0 ? { authenticationTemplate: derivedAuthenticationTemplate } : {}
|
|
632
|
+
};
|
|
633
|
+
yield* ctx.storage.putSpec(specHash, resolved.specText);
|
|
634
|
+
if (compiled) {
|
|
635
|
+
yield* ctx.storage.putDefs(specHash, JSON.stringify(compiled.hoistedDefs));
|
|
636
|
+
}
|
|
637
|
+
yield* ctx.transaction(
|
|
638
|
+
Effect3.gen(function* () {
|
|
639
|
+
yield* ctx.core.integrations.register({
|
|
640
|
+
slug,
|
|
641
|
+
name: config.name?.trim() || derivedIdentity?.name || compiled?.title || resolvedSlug,
|
|
642
|
+
description: config.description ?? derivedIdentity?.description ?? compiled?.description ?? compiled?.title ?? resolvedSlug,
|
|
643
|
+
config: integrationConfig,
|
|
644
|
+
canRemove: true,
|
|
645
|
+
canRefresh: integrationConfig.specUrl != null
|
|
646
|
+
});
|
|
647
|
+
if (config.healthCheck) {
|
|
648
|
+
yield* ctx.core.integrations.setHealthCheck(slug, config.healthCheck);
|
|
649
|
+
}
|
|
650
|
+
if (compiled) {
|
|
651
|
+
yield* ctx.storage.putOperations(
|
|
652
|
+
resolvedSlug,
|
|
653
|
+
openApiStoredOperationsFromCompiled(resolvedSlug, compiled)
|
|
654
|
+
);
|
|
655
|
+
return compiled.definitions.length;
|
|
656
|
+
}
|
|
657
|
+
const persisted = yield* compileAndPersistOpenApiSpecStreaming({
|
|
658
|
+
specText: resolved.specText,
|
|
659
|
+
integration: resolvedSlug,
|
|
660
|
+
storage: ctx.storage,
|
|
661
|
+
specHash,
|
|
662
|
+
keepPathItem: resolved.keepPathItem
|
|
663
|
+
});
|
|
664
|
+
return persisted.toolCount;
|
|
665
|
+
})
|
|
666
|
+
);
|
|
667
|
+
const toolCount = compiled ? compiled.definitions.length : yield* ctx.storage.listOperations(resolvedSlug).pipe(Effect3.map((operations) => operations.length));
|
|
668
|
+
return { slug, toolCount };
|
|
669
|
+
});
|
|
670
|
+
const updateSpec = (rawSlug, input) => Effect3.gen(function* () {
|
|
671
|
+
const slug = IntegrationSlug.make(rawSlug);
|
|
672
|
+
const record = yield* ctx.core.integrations.get(slug);
|
|
673
|
+
const current = record ? decodeOpenApiIntegrationConfig(record.config) : null;
|
|
674
|
+
if (!record || !current) {
|
|
675
|
+
return yield* new IntegrationNotFoundError({ slug });
|
|
676
|
+
}
|
|
677
|
+
const specInput = input?.spec ?? (current.specUrl ? { kind: "url", url: current.specUrl } : null);
|
|
678
|
+
if (specInput === null) {
|
|
679
|
+
return yield* new OpenApiParseError({
|
|
680
|
+
message: "This integration's spec was pasted inline and has no source URL to re-fetch. Provide the updated spec content."
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
const resolved = yield* resolveSpecForInput(
|
|
684
|
+
{
|
|
685
|
+
spec: specInput,
|
|
686
|
+
specFormat: current.specFormat,
|
|
687
|
+
headers: current.headers,
|
|
688
|
+
queryParams: current.queryParams,
|
|
689
|
+
baseUrl: current.baseUrl
|
|
690
|
+
},
|
|
691
|
+
httpClientLayer
|
|
692
|
+
);
|
|
693
|
+
const compiled = resolved.keepPathItem ? void 0 : yield* compileOpenApiSpec(resolved.specText);
|
|
694
|
+
const previousOperations = yield* ctx.storage.listOperations(rawSlug);
|
|
695
|
+
const previousNames = new Set(previousOperations.map((op) => op.toolName));
|
|
696
|
+
const specHash = yield* sha256Hex(resolved.specText);
|
|
697
|
+
yield* ctx.storage.putSpec(specHash, resolved.specText);
|
|
698
|
+
if (compiled) {
|
|
699
|
+
yield* ctx.storage.putDefs(specHash, JSON.stringify(compiled.hoistedDefs));
|
|
700
|
+
}
|
|
701
|
+
const nextConfig = {
|
|
702
|
+
...current,
|
|
703
|
+
specHash,
|
|
704
|
+
...(resolved.specUrl ?? specInputToSpecUrl(specInput)) !== void 0 ? { specUrl: resolved.specUrl ?? specInputToSpecUrl(specInput) } : {}
|
|
705
|
+
};
|
|
706
|
+
yield* ctx.transaction(
|
|
707
|
+
Effect3.gen(function* () {
|
|
708
|
+
yield* ctx.core.integrations.update(slug, {
|
|
709
|
+
config: nextConfig
|
|
710
|
+
});
|
|
711
|
+
if (compiled) {
|
|
712
|
+
yield* ctx.storage.putOperations(
|
|
713
|
+
rawSlug,
|
|
714
|
+
openApiStoredOperationsFromCompiled(rawSlug, compiled)
|
|
715
|
+
);
|
|
716
|
+
} else {
|
|
717
|
+
yield* compileAndPersistOpenApiSpecStreaming({
|
|
718
|
+
specText: resolved.specText,
|
|
719
|
+
integration: rawSlug,
|
|
720
|
+
storage: ctx.storage,
|
|
721
|
+
specHash,
|
|
722
|
+
keepPathItem: resolved.keepPathItem
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
})
|
|
726
|
+
);
|
|
727
|
+
const nextOperations = compiled ? openApiStoredOperationsFromCompiled(rawSlug, compiled) : yield* ctx.storage.listOperations(rawSlug);
|
|
728
|
+
const nextNames = new Set(nextOperations.map((op) => op.toolName));
|
|
729
|
+
const connections = yield* ctx.connections.list({ integration: slug });
|
|
730
|
+
yield* Effect3.forEach(
|
|
731
|
+
connections,
|
|
732
|
+
(connection) => ctx.connections.refresh({
|
|
733
|
+
owner: connection.owner,
|
|
734
|
+
integration: connection.integration,
|
|
735
|
+
name: connection.name
|
|
736
|
+
}).pipe(Effect3.catchTag("ConnectionNotFoundError", () => Effect3.succeed([]))),
|
|
737
|
+
{ discard: true }
|
|
738
|
+
).pipe(
|
|
739
|
+
Effect3.catchTag("IntegrationNotFoundError", () => Effect3.void),
|
|
740
|
+
Effect3.withSpan("openapi.plugin.update_spec.refresh_connections", {
|
|
741
|
+
attributes: { "openapi.connection_count": connections.length }
|
|
742
|
+
})
|
|
743
|
+
);
|
|
744
|
+
return {
|
|
745
|
+
slug,
|
|
746
|
+
toolCount: nextNames.size,
|
|
747
|
+
addedTools: [...nextNames].filter((name) => !previousNames.has(name)).sort(),
|
|
748
|
+
removedTools: [...previousNames].filter((name) => !nextNames.has(name)).sort()
|
|
749
|
+
};
|
|
750
|
+
}).pipe(
|
|
751
|
+
Effect3.withSpan("openapi.plugin.update_spec", {
|
|
752
|
+
attributes: { "openapi.integration.slug": rawSlug }
|
|
753
|
+
})
|
|
754
|
+
);
|
|
755
|
+
return {
|
|
756
|
+
previewSpec: (input) => Effect3.gen(function* () {
|
|
757
|
+
const previewInput = typeof input === "string" ? { spec: input } : input;
|
|
758
|
+
const spec = maybeUrl(previewInput.spec.trim()) ? { kind: "url", url: previewInput.spec.trim() } : { kind: "blob", value: previewInput.spec };
|
|
759
|
+
const resolved = yield* resolveSpecForInput(
|
|
760
|
+
{ spec, specFormat: previewInput.specFormat },
|
|
761
|
+
httpClientLayer
|
|
762
|
+
);
|
|
763
|
+
const preview = yield* previewSpecText(resolved.specText);
|
|
764
|
+
return yield* enrichPreviewWithDiscoveredOAuth({
|
|
765
|
+
specText: resolved.specText,
|
|
766
|
+
preview,
|
|
767
|
+
specUrl: resolved.specUrl ?? (spec.kind === "url" ? spec.url : void 0)
|
|
768
|
+
});
|
|
769
|
+
}),
|
|
770
|
+
addSpec,
|
|
771
|
+
updateSpec,
|
|
772
|
+
removeSpec: (slug) => ctx.transaction(
|
|
773
|
+
Effect3.gen(function* () {
|
|
774
|
+
yield* ctx.storage.removeOperations(slug);
|
|
775
|
+
yield* ctx.core.integrations.remove(IntegrationSlug.make(slug)).pipe(Effect3.catchTag("IntegrationRemovalNotAllowedError", () => Effect3.void));
|
|
776
|
+
})
|
|
777
|
+
),
|
|
778
|
+
getIntegration: (slug) => ctx.core.integrations.get(IntegrationSlug.make(slug)).pipe(
|
|
779
|
+
Effect3.map(
|
|
780
|
+
(record) => record ? {
|
|
781
|
+
slug: record.slug,
|
|
782
|
+
description: record.description,
|
|
783
|
+
kind: record.kind,
|
|
784
|
+
canRemove: record.canRemove,
|
|
785
|
+
canRefresh: record.canRefresh
|
|
786
|
+
} : null
|
|
787
|
+
)
|
|
788
|
+
),
|
|
789
|
+
getConfig: (slug) => ctx.core.integrations.get(IntegrationSlug.make(slug)).pipe(
|
|
790
|
+
Effect3.map(
|
|
791
|
+
(record) => record ? decodeOpenApiIntegrationConfig(record.config) : null
|
|
792
|
+
)
|
|
793
|
+
),
|
|
794
|
+
configure: (slug, input) => ctx.transaction(
|
|
795
|
+
Effect3.gen(function* () {
|
|
796
|
+
const record = yield* ctx.core.integrations.get(IntegrationSlug.make(slug));
|
|
797
|
+
if (!record) return [];
|
|
798
|
+
const current = decodeOpenApiIntegrationConfig(record.config);
|
|
799
|
+
if (!current) return [];
|
|
800
|
+
const merged = input.authenticationTemplate === void 0 ? current.authenticationTemplate ?? [] : input.mode === "replace" ? normalizeOpenApiAuthInputs(input.authenticationTemplate) : mergeAuthTemplates(
|
|
801
|
+
current.authenticationTemplate ?? [],
|
|
802
|
+
normalizeOpenApiAuthInputs(input.authenticationTemplate)
|
|
803
|
+
);
|
|
804
|
+
const next = {
|
|
805
|
+
...current,
|
|
806
|
+
...input.baseUrl !== void 0 ? { baseUrl: input.baseUrl } : {},
|
|
807
|
+
authenticationTemplate: merged
|
|
808
|
+
};
|
|
809
|
+
yield* ctx.core.integrations.update(IntegrationSlug.make(slug), {
|
|
810
|
+
config: next
|
|
811
|
+
});
|
|
812
|
+
return merged;
|
|
813
|
+
})
|
|
814
|
+
)
|
|
815
|
+
};
|
|
816
|
+
},
|
|
817
|
+
staticIntegrations: (self) => [
|
|
818
|
+
{
|
|
819
|
+
id: "openapi",
|
|
820
|
+
kind: "executor",
|
|
821
|
+
name: "OpenAPI",
|
|
822
|
+
tools: [
|
|
823
|
+
tool({
|
|
824
|
+
name: "previewSpec",
|
|
825
|
+
description: "Preview an OpenAPI document before adding it as an integration. Call this first when the user provides a spec URL/blob so you can inspect servers, auth schemes, operation count, and tags before `addSpec`. Do not collect API keys or OAuth client secrets in chat; use the connections tools for those values.",
|
|
826
|
+
inputSchema: PreviewSpecInputStandardSchema,
|
|
827
|
+
outputSchema: PreviewSpecOutputStandardSchema,
|
|
828
|
+
execute: (input) => self.previewSpec(input).pipe(
|
|
829
|
+
Effect3.map((preview) => ToolResult.ok(staticPreviewOutput(preview))),
|
|
830
|
+
Effect3.catchTags({
|
|
831
|
+
OpenApiParseError: ({ message }) => Effect3.succeed(openApiToolFailure("openapi_parse_failed", message)),
|
|
832
|
+
OpenApiExtractionError: ({ message }) => Effect3.succeed(openApiToolFailure("openapi_extraction_failed", message)),
|
|
833
|
+
OpenApiOAuthError: ({ message }) => Effect3.succeed(openApiToolFailure("openapi_oauth_failed", message))
|
|
834
|
+
})
|
|
835
|
+
)
|
|
836
|
+
}),
|
|
837
|
+
tool({
|
|
838
|
+
name: "addSpec",
|
|
839
|
+
description: "Add an OpenAPI integration to the catalog and persist its operations as tools. Recommended flow: call `previewSpec`, choose a `slug`, then create a connection for that integration with the user's API key or via `oauth.start`. When `baseUrl` is omitted it defaults to the spec's first server; when `authenticationTemplate` is omitted the auth methods are derived from the spec's declared security schemes (pass an explicit template to override how a credential is applied - apiKey header/query, or oauth bearer - or an empty array for no auth methods).",
|
|
840
|
+
annotations: {
|
|
841
|
+
requiresApproval: true,
|
|
842
|
+
approvalDescription: "Add an OpenAPI integration"
|
|
843
|
+
},
|
|
844
|
+
inputSchema: AddIntegrationInputStandardSchema,
|
|
845
|
+
outputSchema: AddIntegrationOutputStandardSchema,
|
|
846
|
+
execute: (input) => self.addSpec({
|
|
847
|
+
spec: input.spec,
|
|
848
|
+
slug: input.slug,
|
|
849
|
+
name: input.name,
|
|
850
|
+
description: input.description,
|
|
851
|
+
baseUrl: input.baseUrl,
|
|
852
|
+
headers: input.headers,
|
|
853
|
+
queryParams: input.queryParams,
|
|
854
|
+
specFormat: input.specFormat,
|
|
855
|
+
family: input.family,
|
|
856
|
+
healthCheck: input.healthCheck,
|
|
857
|
+
authenticationTemplate: input.authenticationTemplate
|
|
858
|
+
}).pipe(
|
|
859
|
+
Effect3.map(
|
|
860
|
+
(result) => ToolResult.ok({
|
|
861
|
+
slug: String(result.slug),
|
|
862
|
+
toolCount: result.toolCount
|
|
863
|
+
})
|
|
864
|
+
),
|
|
865
|
+
Effect3.catchTags({
|
|
866
|
+
OpenApiParseError: ({ message }) => Effect3.succeed(openApiToolFailure("openapi_parse_failed", message)),
|
|
867
|
+
OpenApiExtractionError: ({ message }) => Effect3.succeed(openApiToolFailure("openapi_extraction_failed", message)),
|
|
868
|
+
OpenApiOAuthError: ({ message }) => Effect3.succeed(openApiToolFailure("openapi_oauth_failed", message)),
|
|
869
|
+
IntegrationAlreadyExistsError: ({ slug }) => Effect3.succeed(
|
|
870
|
+
openApiToolFailure(
|
|
871
|
+
"integration_already_exists",
|
|
872
|
+
`Integration ${slug} already exists; update it instead of re-adding.`
|
|
873
|
+
)
|
|
874
|
+
)
|
|
875
|
+
})
|
|
876
|
+
)
|
|
877
|
+
})
|
|
878
|
+
]
|
|
879
|
+
}
|
|
880
|
+
],
|
|
881
|
+
describeAuthMethods: describeOpenApiAuthMethods,
|
|
882
|
+
describeIntegrationDisplay: describeOpenApiIntegrationDisplay,
|
|
883
|
+
// Health checks: the declared liveness/identity probe. Core owns the spec
|
|
884
|
+
// storage; the plugin only enumerates candidates and runs probes.
|
|
885
|
+
listHealthCheckCandidates: (input) => listHealthCheckCandidatesOpenApi({ ctx: input.ctx, integration: input.integration }),
|
|
886
|
+
checkHealth: (input) => checkHealthOpenApi({
|
|
887
|
+
ctx: input.ctx,
|
|
888
|
+
integration: input.integration,
|
|
889
|
+
credential: input.credential,
|
|
890
|
+
spec: input.spec,
|
|
891
|
+
httpClientLayer: options?.httpClientLayer ?? input.ctx.httpClientLayer
|
|
892
|
+
}),
|
|
893
|
+
// Produce one tool per spec operation. Spec-derived, identical for every
|
|
894
|
+
// connection on the integration - so `getValue` is never called here. The
|
|
895
|
+
// operation bindings invokeTool needs are persisted at addSpec time; this
|
|
896
|
+
// hook only shapes the per-connection ToolDefs from the spec blob the
|
|
897
|
+
// catalog config points at.
|
|
898
|
+
resolveTools: ({ integration, config, storage }) => resolveOpenApiBackedTools({ integration, config, storage }),
|
|
899
|
+
invokeTool: ({ ctx: invokeCtx, toolRow, credential, args }) => {
|
|
900
|
+
const httpClientLayer = options?.httpClientLayer ?? invokeCtx.httpClientLayer;
|
|
901
|
+
return invokeOpenApiBackedTool({
|
|
902
|
+
ctx: invokeCtx,
|
|
903
|
+
toolRow,
|
|
904
|
+
credential,
|
|
905
|
+
args,
|
|
906
|
+
httpClientLayer,
|
|
907
|
+
invokeOptions: options?.invokeOptions
|
|
908
|
+
});
|
|
909
|
+
},
|
|
910
|
+
validateToolArgs: ({ ctx: validateCtx, toolRow, args }) => validateOpenApiBackedToolArgs({ ctx: validateCtx, toolRow, args }),
|
|
911
|
+
resolveAnnotations: ({ ctx: annotationsCtx, integration, toolRows }) => resolveOpenApiBackedAnnotations({
|
|
912
|
+
ctx: annotationsCtx,
|
|
913
|
+
integration: String(integration),
|
|
914
|
+
toolRows
|
|
915
|
+
}),
|
|
916
|
+
removeConnection: () => Effect3.void,
|
|
917
|
+
detect: ({
|
|
918
|
+
ctx: detectCtx,
|
|
919
|
+
url
|
|
920
|
+
}) => Effect3.gen(function* () {
|
|
921
|
+
const httpClientLayer = options?.httpClientLayer ?? detectCtx.httpClientLayer;
|
|
922
|
+
const trimmed = url.trim();
|
|
923
|
+
if (!trimmed) return null;
|
|
924
|
+
const parsed = yield* Effect3.try({
|
|
925
|
+
try: () => new URL(trimmed),
|
|
926
|
+
catch: (error) => error
|
|
927
|
+
}).pipe(Effect3.option);
|
|
928
|
+
if (Option2.isNone(parsed)) return null;
|
|
929
|
+
const specText = yield* resolveSpecText(trimmed).pipe(
|
|
930
|
+
Effect3.provide(httpClientLayer),
|
|
931
|
+
Effect3.catch(() => Effect3.succeed(null))
|
|
932
|
+
);
|
|
933
|
+
if (specText === null) return null;
|
|
934
|
+
const doc = yield* parse(specText).pipe(Effect3.catch(() => Effect3.succeed(null)));
|
|
935
|
+
if (!doc) return null;
|
|
936
|
+
const result = yield* extract(doc).pipe(Effect3.catch(() => Effect3.succeed(null)));
|
|
937
|
+
if (!result) return null;
|
|
938
|
+
const slug = Option2.getOrElse(result.title, () => "api").toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
939
|
+
const name = Option2.getOrElse(result.title, () => slug);
|
|
940
|
+
return IntegrationDetectionResult.make({
|
|
941
|
+
kind: "openapi",
|
|
942
|
+
confidence: "high",
|
|
943
|
+
endpoint: trimmed,
|
|
944
|
+
name,
|
|
945
|
+
slug
|
|
946
|
+
});
|
|
947
|
+
})
|
|
948
|
+
};
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
export {
|
|
952
|
+
makeDefaultOpenapiStore,
|
|
953
|
+
resolveSpecFormatAdapter,
|
|
954
|
+
openApiPlugin
|
|
955
|
+
};
|
|
956
|
+
//# sourceMappingURL=chunk-OR27BCXQ.js.map
|