@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
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @rafads/plugin-openapi
|
|
2
|
+
|
|
3
|
+
Load [OpenAPI](https://www.openapis.org/) specifications into an executor. Every operation in the spec becomes an invokable tool with a JSON-Schema input, automatic request building, and optional secret-backed auth.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bun add @rafads/sdk @rafads/plugin-openapi
|
|
9
|
+
# or
|
|
10
|
+
npm install @rafads/sdk @rafads/plugin-openapi
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createExecutor } from "@rafads/sdk";
|
|
17
|
+
import { openApiPlugin } from "@rafads/plugin-openapi";
|
|
18
|
+
|
|
19
|
+
const executor = await createExecutor({
|
|
20
|
+
onElicitation: "accept-all",
|
|
21
|
+
plugins: [openApiPlugin()] as const,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Load a spec by URL (JSON or YAML, remote or file://)
|
|
25
|
+
await executor.openapi.addSpec({
|
|
26
|
+
scope: executor.scopes[0]!.id,
|
|
27
|
+
spec: "https://petstore3.swagger.io/api/v3/openapi.json",
|
|
28
|
+
namespace: "petstore",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// List and invoke tools like any other plugin
|
|
32
|
+
const tools = await executor.tools.list();
|
|
33
|
+
const result = await executor.tools.invoke("petstore.listPets", {});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Secret-backed auth headers
|
|
37
|
+
|
|
38
|
+
Wire API keys or bearer tokens through the executor's secret store — never hard-code them in integration configs:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { createExecutor } from "@rafads/sdk";
|
|
42
|
+
import { openApiPlugin } from "@rafads/plugin-openapi";
|
|
43
|
+
import { fileSecretsPlugin } from "@rafads/plugin-file-secrets";
|
|
44
|
+
|
|
45
|
+
const executor = await createExecutor({
|
|
46
|
+
onElicitation: "accept-all",
|
|
47
|
+
plugins: [fileSecretsPlugin(), openApiPlugin()] as const,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const scope = executor.scopes[0]!.id;
|
|
51
|
+
|
|
52
|
+
await executor.secrets.set({
|
|
53
|
+
id: "stripe-key",
|
|
54
|
+
name: "Stripe Key",
|
|
55
|
+
value: "sk_live_...",
|
|
56
|
+
scope,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await executor.openapi.addSpec({
|
|
60
|
+
scope,
|
|
61
|
+
spec: "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json",
|
|
62
|
+
namespace: "stripe",
|
|
63
|
+
headers: {
|
|
64
|
+
Authorization: { secretId: "stripe-key", prefix: "Bearer " },
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Using with Effect
|
|
70
|
+
|
|
71
|
+
If you're building on `@rafads/sdk/core` (the raw Effect entry), import this plugin from its `/core` subpath instead — it returns the Effect-shaped plugin with `Effect.Effect<...>`-returning methods rather than promisified wrappers:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { openApiPlugin } from "@rafads/plugin-openapi/core";
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Status
|
|
78
|
+
|
|
79
|
+
Pre-`1.0`. APIs may still change between beta releases. Part of the [executor monorepo](https://github.com/UsefulSoftwareCo/executor).
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import {
|
|
2
|
+
authenticationFromEditorValue,
|
|
3
|
+
editorValueFromAuthentication,
|
|
4
|
+
openApiWireAuthInput
|
|
5
|
+
} from "./chunk-IG6QTTZB.js";
|
|
6
|
+
import {
|
|
7
|
+
detectedAuthenticationTemplates
|
|
8
|
+
} from "./chunk-O2LMSZ2Z.js";
|
|
9
|
+
import {
|
|
10
|
+
addOpenApiSpec,
|
|
11
|
+
previewOpenApiSpec
|
|
12
|
+
} from "./chunk-6V45G2XQ.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveServerUrl
|
|
15
|
+
} from "./chunk-HTHEUUJA.js";
|
|
16
|
+
import "./chunk-R7TVM3XD.js";
|
|
17
|
+
import "./chunk-CXFO6TLJ.js";
|
|
18
|
+
|
|
19
|
+
// src/react/AddOpenApiIntegration.tsx
|
|
20
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
21
|
+
import { useAtomSet } from "@effect/atom-react";
|
|
22
|
+
import * as Effect from "effect/Effect";
|
|
23
|
+
import * as Exit from "effect/Exit";
|
|
24
|
+
import * as Option from "effect/Option";
|
|
25
|
+
import {
|
|
26
|
+
AuthTemplateSlug
|
|
27
|
+
} from "@rafads/sdk/shared";
|
|
28
|
+
import { useIntegrationPlugins } from "@rafads/sdk/client";
|
|
29
|
+
import { integrationWriteKeys, healthCheckWriteKeys } from "@rafads/react/api/reactivity-keys";
|
|
30
|
+
import { setIntegrationHealthCheck } from "@rafads/react/api/atoms";
|
|
31
|
+
import {
|
|
32
|
+
slugifyNamespace,
|
|
33
|
+
useIntegrationIdentity
|
|
34
|
+
} from "@rafads/react/plugins/integration-identity";
|
|
35
|
+
import { Button } from "@rafads/react/components/button";
|
|
36
|
+
import { HealthCheckConfigFields } from "@rafads/react/components/health-check-editor";
|
|
37
|
+
import {
|
|
38
|
+
AuthMethodListEditor,
|
|
39
|
+
useAuthMethodList
|
|
40
|
+
} from "@rafads/react/components/auth-method-list-editor";
|
|
41
|
+
import { CardStack as CardStack2, CardStackContent as CardStackContent2 } from "@rafads/react/components/card-stack";
|
|
42
|
+
import { FieldLabel } from "@rafads/react/components/field";
|
|
43
|
+
import { FloatActions } from "@rafads/react/components/float-actions";
|
|
44
|
+
import { Textarea as Textarea2 } from "@rafads/react/components/textarea";
|
|
45
|
+
import { IOSSpinner } from "@rafads/react/components/spinner";
|
|
46
|
+
import {
|
|
47
|
+
addIntegrationErrorMessage,
|
|
48
|
+
errorMessageFromExit,
|
|
49
|
+
FormErrorAlert,
|
|
50
|
+
SlugCollisionAlert,
|
|
51
|
+
useSlugAlreadyExists
|
|
52
|
+
} from "@rafads/react/lib/integration-add";
|
|
53
|
+
|
|
54
|
+
// src/react/OpenApiIntegrationDetailsFields.tsx
|
|
55
|
+
import {
|
|
56
|
+
CardStack,
|
|
57
|
+
CardStackContent,
|
|
58
|
+
CardStackEntry,
|
|
59
|
+
CardStackEntryContent,
|
|
60
|
+
CardStackEntryDescription,
|
|
61
|
+
CardStackEntryField,
|
|
62
|
+
CardStackEntryTitle
|
|
63
|
+
} from "@rafads/react/components/card-stack";
|
|
64
|
+
import { FreeformCombobox } from "@rafads/react/components/combobox";
|
|
65
|
+
import { Input } from "@rafads/react/components/input";
|
|
66
|
+
import { Textarea } from "@rafads/react/components/textarea";
|
|
67
|
+
import { IntegrationFavicon } from "@rafads/react/components/integration-favicon";
|
|
68
|
+
import {
|
|
69
|
+
IntegrationIdentityFieldRows
|
|
70
|
+
} from "@rafads/react/plugins/integration-identity";
|
|
71
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
72
|
+
var isUrlInput = (value) => URL.canParse(value.trim());
|
|
73
|
+
function OpenApiIntegrationDetailsFields(props) {
|
|
74
|
+
const specIsUrl = props.specUrl !== void 0 && isUrlInput(props.specUrl);
|
|
75
|
+
return /* @__PURE__ */ jsx(CardStack, { children: /* @__PURE__ */ jsxs(CardStackContent, { className: "border-t-0", children: [
|
|
76
|
+
/* @__PURE__ */ jsxs(CardStackEntry, { children: [
|
|
77
|
+
(props.faviconIcon || props.faviconUrl) && /* @__PURE__ */ jsx(IntegrationFavicon, { icon: props.faviconIcon, url: props.faviconUrl, size: 16 }),
|
|
78
|
+
/* @__PURE__ */ jsxs(CardStackEntryContent, { children: [
|
|
79
|
+
/* @__PURE__ */ jsx(CardStackEntryTitle, { children: props.title }),
|
|
80
|
+
props.subtitle && /* @__PURE__ */ jsx(CardStackEntryDescription, { children: props.subtitle })
|
|
81
|
+
] }),
|
|
82
|
+
props.saveState && props.saveState !== "idle" && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: props.saveState === "saving" ? "Saving\u2026" : "Saved" })
|
|
83
|
+
] }),
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
IntegrationIdentityFieldRows,
|
|
86
|
+
{
|
|
87
|
+
identity: props.identity,
|
|
88
|
+
namespaceReadOnly: props.namespaceReadOnly
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
props.onDescriptionChange && /* @__PURE__ */ jsx(CardStackEntryField, { label: "Description", children: /* @__PURE__ */ jsx(
|
|
92
|
+
Textarea,
|
|
93
|
+
{
|
|
94
|
+
value: props.description ?? "",
|
|
95
|
+
onChange: (e) => props.onDescriptionChange?.(e.target.value),
|
|
96
|
+
placeholder: "What this API is and when to reach for it",
|
|
97
|
+
rows: 2,
|
|
98
|
+
maxRows: 6,
|
|
99
|
+
className: "text-sm"
|
|
100
|
+
}
|
|
101
|
+
) }),
|
|
102
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2", children: [
|
|
103
|
+
/* @__PURE__ */ jsxs(CardStackEntryField, { label: props.baseUrlLabel ?? "Base URL", children: [
|
|
104
|
+
props.baseUrlOptions && props.baseUrlOptions.length > 0 ? /* @__PURE__ */ jsx(
|
|
105
|
+
FreeformCombobox,
|
|
106
|
+
{
|
|
107
|
+
value: props.baseUrl,
|
|
108
|
+
onValueChange: props.onBaseUrlChange,
|
|
109
|
+
options: props.baseUrlOptions,
|
|
110
|
+
placeholder: props.baseUrlPlaceholder ?? "https://api.example.com",
|
|
111
|
+
className: "w-full",
|
|
112
|
+
inputClassName: "font-mono text-sm"
|
|
113
|
+
}
|
|
114
|
+
) : /* @__PURE__ */ jsx(
|
|
115
|
+
Input,
|
|
116
|
+
{
|
|
117
|
+
value: props.baseUrl,
|
|
118
|
+
onChange: (e) => props.onBaseUrlChange(e.target.value),
|
|
119
|
+
placeholder: props.baseUrlPlaceholder ?? "https://api.example.com",
|
|
120
|
+
className: "font-mono text-sm"
|
|
121
|
+
}
|
|
122
|
+
),
|
|
123
|
+
props.baseUrlMissingMessage && !props.baseUrl && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: props.baseUrlMissingMessage }),
|
|
124
|
+
props.baseUrlHint && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: props.baseUrlHint })
|
|
125
|
+
] }),
|
|
126
|
+
specIsUrl && props.onSpecUrlChange && /* @__PURE__ */ jsx(CardStackEntryField, { label: "Spec URL", children: /* @__PURE__ */ jsx(
|
|
127
|
+
Input,
|
|
128
|
+
{
|
|
129
|
+
value: props.specUrl,
|
|
130
|
+
onChange: (e) => props.onSpecUrlChange?.(e.target.value),
|
|
131
|
+
placeholder: "https://api.example.com/openapi.json",
|
|
132
|
+
className: "font-mono text-sm",
|
|
133
|
+
disabled: props.specUrlDisabled
|
|
134
|
+
}
|
|
135
|
+
) })
|
|
136
|
+
] }),
|
|
137
|
+
props.specUrl !== void 0 && !specIsUrl && props.onSpecUrlChange && /* @__PURE__ */ jsx(CardStackEntryField, { label: "Spec", children: /* @__PURE__ */ jsx(
|
|
138
|
+
Textarea,
|
|
139
|
+
{
|
|
140
|
+
value: props.specUrl,
|
|
141
|
+
onChange: (e) => props.onSpecUrlChange?.(e.target.value),
|
|
142
|
+
placeholder: "Pasted OpenAPI JSON/YAML",
|
|
143
|
+
rows: 4,
|
|
144
|
+
maxRows: 12,
|
|
145
|
+
className: "font-mono text-xs",
|
|
146
|
+
disabled: props.specUrlDisabled
|
|
147
|
+
}
|
|
148
|
+
) }),
|
|
149
|
+
props.footer && /* @__PURE__ */ jsx(CardStackEntry, { children: /* @__PURE__ */ jsx(CardStackEntryContent, { children: /* @__PURE__ */ jsx(CardStackEntryTitle, { children: props.footer }) }) })
|
|
150
|
+
] }) });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/react/AddOpenApiIntegration.tsx
|
|
154
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
155
|
+
var normalizePresetUrl = (url) => {
|
|
156
|
+
const trimmed = url.trim();
|
|
157
|
+
if (!URL.canParse(trimmed)) return trimmed.replace(/\/$/, "");
|
|
158
|
+
const parsed = new URL(trimmed);
|
|
159
|
+
parsed.hash = "";
|
|
160
|
+
parsed.searchParams.sort();
|
|
161
|
+
return parsed.toString().replace(/\/$/, "");
|
|
162
|
+
};
|
|
163
|
+
var specInputForAdd = (input) => {
|
|
164
|
+
const value = input.trim();
|
|
165
|
+
const parsed = Effect.runSyncExit(
|
|
166
|
+
Effect.try({
|
|
167
|
+
try: () => new URL(value),
|
|
168
|
+
catch: () => null
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
return Exit.isSuccess(parsed) ? { kind: "url", url: value } : { kind: "blob", value };
|
|
172
|
+
};
|
|
173
|
+
var baseUrlFromSpecInput = (input) => {
|
|
174
|
+
const value = input.trim();
|
|
175
|
+
if (!URL.canParse(value)) return "";
|
|
176
|
+
const parsed = new URL(value);
|
|
177
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return "";
|
|
178
|
+
return parsed.origin;
|
|
179
|
+
};
|
|
180
|
+
var openApiPreviewFailureMessage = (message) => `Couldn't load or parse this spec: ${message && message.trim().length > 0 ? message : "unknown error"}`;
|
|
181
|
+
function AddOpenApiHealthCheckSection(props) {
|
|
182
|
+
if (props.presetHealthCheck) {
|
|
183
|
+
return /* @__PURE__ */ jsxs2("section", { className: "space-y-2", children: [
|
|
184
|
+
/* @__PURE__ */ jsx2("h3", { className: "text-sm font-medium text-foreground", children: "Health check" }),
|
|
185
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-[11px] text-muted-foreground", children: [
|
|
186
|
+
/* @__PURE__ */ jsx2("span", { className: "font-mono text-foreground", children: props.presetHealthCheck.operation }),
|
|
187
|
+
" ",
|
|
188
|
+
"From the catalog. Change it later from the integration page."
|
|
189
|
+
] })
|
|
190
|
+
] });
|
|
191
|
+
}
|
|
192
|
+
if (props.candidates.length === 0) return null;
|
|
193
|
+
return /* @__PURE__ */ jsxs2("section", { className: "space-y-4", children: [
|
|
194
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
|
|
195
|
+
/* @__PURE__ */ jsx2("h3", { className: "text-sm font-medium text-foreground", children: "Health check (optional)" }),
|
|
196
|
+
/* @__PURE__ */ jsx2("p", { className: "text-[11px] text-muted-foreground", children: "An optional read-only probe adds live upstream depth. For OAuth connections, validity and identity come from the OAuth grant itself. For non-OAuth methods, the probe can validate the credential and identify the account." })
|
|
197
|
+
] }),
|
|
198
|
+
/* @__PURE__ */ jsx2(
|
|
199
|
+
HealthCheckConfigFields,
|
|
200
|
+
{
|
|
201
|
+
candidates: props.candidates,
|
|
202
|
+
selected: props.selected,
|
|
203
|
+
operation: props.operation,
|
|
204
|
+
onOperationChange: props.onOperationChange,
|
|
205
|
+
identityField: props.identityField,
|
|
206
|
+
onIdentityFieldChange: props.onIdentityFieldChange,
|
|
207
|
+
args: props.args,
|
|
208
|
+
onArgChange: props.onArgChange,
|
|
209
|
+
disabled: props.disabled,
|
|
210
|
+
idPrefix: "add-health-check"
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
] });
|
|
214
|
+
}
|
|
215
|
+
function AddOpenApiIntegration(props) {
|
|
216
|
+
const integrationPlugins = useIntegrationPlugins();
|
|
217
|
+
const openApiPlugin = integrationPlugins.find((plugin) => plugin.key === "openapi");
|
|
218
|
+
const openApiPresets = openApiPlugin?.presets;
|
|
219
|
+
const initialPreset = openApiPresets?.find((preset) => preset.id === props.initialPreset) ?? null;
|
|
220
|
+
const [specUrl, setSpecUrl] = useState(props.initialUrl ?? "");
|
|
221
|
+
const [analyzing, setAnalyzing] = useState(false);
|
|
222
|
+
const [analyzeError, setAnalyzeError] = useState(null);
|
|
223
|
+
const [preview, setPreview] = useState(null);
|
|
224
|
+
const [baseUrl, setBaseUrl] = useState("");
|
|
225
|
+
const [descriptionDraft, setDescriptionDraft] = useState(null);
|
|
226
|
+
const identityFallbackName = initialPreset?.name ?? (preview ? Option.getOrElse(preview.title, () => "") : "");
|
|
227
|
+
const identity = useIntegrationIdentity({
|
|
228
|
+
fallbackName: identityFallbackName,
|
|
229
|
+
fallbackNamespace: props.initialNamespace
|
|
230
|
+
});
|
|
231
|
+
const [adding, setAdding] = useState(false);
|
|
232
|
+
const [addError, setAddError] = useState(null);
|
|
233
|
+
const [hcOperation, setHcOperation] = useState("");
|
|
234
|
+
const [hcIdentityField, setHcIdentityField] = useState("");
|
|
235
|
+
const [hcArgs, setHcArgs] = useState({});
|
|
236
|
+
const doPreview = useAtomSet(previewOpenApiSpec, { mode: "promiseExit" });
|
|
237
|
+
const doAdd = useAtomSet(addOpenApiSpec, { mode: "promiseExit" });
|
|
238
|
+
const doSetHealthCheck = useAtomSet(setIntegrationHealthCheck, { mode: "promiseExit" });
|
|
239
|
+
const handleAnalyzeRef = useRef(() => {
|
|
240
|
+
});
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
const trimmed = specUrl.trim();
|
|
243
|
+
if (!trimmed) return;
|
|
244
|
+
if (preview) return;
|
|
245
|
+
const handle = setTimeout(() => {
|
|
246
|
+
handleAnalyzeRef.current();
|
|
247
|
+
}, 400);
|
|
248
|
+
return () => clearTimeout(handle);
|
|
249
|
+
}, [specUrl, preview]);
|
|
250
|
+
const previewHasNoServers = preview !== null && preview.servers.length === 0;
|
|
251
|
+
const baseUrlOptions = preview && preview.servers.length > 1 ? preview.servers.map((server) => {
|
|
252
|
+
const url = resolveServerUrl(server.url, Option.getOrUndefined(server.variables), {});
|
|
253
|
+
return { value: url, label: url };
|
|
254
|
+
}) : void 0;
|
|
255
|
+
const firstServer = preview?.servers[0];
|
|
256
|
+
const firstServerUrl = firstServer ? resolveServerUrl(firstServer.url, Option.getOrUndefined(firstServer.variables), {}) : "";
|
|
257
|
+
const previewPresetIcon = openApiPresets?.find(
|
|
258
|
+
(preset) => preset.url && normalizePresetUrl(preset.url) === normalizePresetUrl(specUrl)
|
|
259
|
+
)?.icon ?? null;
|
|
260
|
+
const resolvedBaseUrl = baseUrl.trim();
|
|
261
|
+
const resolvedIntegrationId = initialPreset?.defaultSlug || slugifyNamespace(identity.namespace) || (preview ? Option.getOrElse(preview.title, () => "openapi") : "openapi");
|
|
262
|
+
const resolvedDisplayName = identity.name.trim() || (preview ? Option.getOrElse(preview.title, () => resolvedIntegrationId) : resolvedIntegrationId);
|
|
263
|
+
const resolvedDescription = descriptionDraft ?? (preview ? Option.getOrElse(preview.description, () => "") : "");
|
|
264
|
+
const authenticationTemplate = useMemo(() => {
|
|
265
|
+
if (initialPreset?.authTemplate) {
|
|
266
|
+
return initialPreset.authTemplate.flatMap(
|
|
267
|
+
(template) => template.kind === "oauth2" ? [
|
|
268
|
+
{
|
|
269
|
+
...template,
|
|
270
|
+
slug: AuthTemplateSlug.make(template.slug),
|
|
271
|
+
resource: template.resource ?? void 0
|
|
272
|
+
}
|
|
273
|
+
] : []
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return detectedAuthenticationTemplates(
|
|
277
|
+
preview?.headerPresets ?? [],
|
|
278
|
+
preview?.oauth2Presets ?? [],
|
|
279
|
+
resolvedBaseUrl
|
|
280
|
+
);
|
|
281
|
+
}, [initialPreset?.authTemplate, preview, resolvedBaseUrl]);
|
|
282
|
+
const authMethodSeeds = useMemo(() => {
|
|
283
|
+
const labels = [
|
|
284
|
+
...(preview?.headerPresets ?? []).map((preset) => preset.label),
|
|
285
|
+
...(preview?.oauth2Presets ?? []).map((preset) => preset.label)
|
|
286
|
+
];
|
|
287
|
+
return authenticationTemplate.map(
|
|
288
|
+
(template, index) => ({
|
|
289
|
+
value: editorValueFromAuthentication(template),
|
|
290
|
+
slug: String(template.slug),
|
|
291
|
+
...labels[index] !== void 0 ? { label: labels[index] } : {}
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
}, [preview, authenticationTemplate]);
|
|
295
|
+
const authMethodList = useAuthMethodList(authMethodSeeds);
|
|
296
|
+
const editedAuthenticationTemplate = useMemo(() => {
|
|
297
|
+
const templates = [];
|
|
298
|
+
authMethodList.rows.forEach((row, index) => {
|
|
299
|
+
const slug = row.seedSlug ?? (row.value.kind === "oauth" ? `oauth-${index}` : `apikey-${index}`);
|
|
300
|
+
const template = authenticationFromEditorValue(row.value, slug);
|
|
301
|
+
if (template !== null) templates.push(template);
|
|
302
|
+
});
|
|
303
|
+
return templates;
|
|
304
|
+
}, [authMethodList.rows]);
|
|
305
|
+
const healthCheckCandidates = useMemo(
|
|
306
|
+
() => preview?.healthCheckCandidates ?? [],
|
|
307
|
+
[preview?.healthCheckCandidates]
|
|
308
|
+
);
|
|
309
|
+
const hcSelected = useMemo(
|
|
310
|
+
() => healthCheckCandidates.find((c) => c.operation === hcOperation) ?? null,
|
|
311
|
+
[healthCheckCandidates, hcOperation]
|
|
312
|
+
);
|
|
313
|
+
const hcRequiredParams = useMemo(
|
|
314
|
+
() => (hcSelected?.parameters ?? []).filter((p) => p.required),
|
|
315
|
+
[hcSelected]
|
|
316
|
+
);
|
|
317
|
+
const hcMissingRequired = hcRequiredParams.some(
|
|
318
|
+
(p) => (hcArgs[p.name] ?? "").trim().length === 0
|
|
319
|
+
);
|
|
320
|
+
const onHcOperationChange = (next) => {
|
|
321
|
+
setHcOperation(next);
|
|
322
|
+
setHcArgs({});
|
|
323
|
+
setHcIdentityField("");
|
|
324
|
+
};
|
|
325
|
+
const onHcArgChange = (name, value) => setHcArgs((prev) => ({ ...prev, [name]: value }));
|
|
326
|
+
const slugAlreadyExists = useSlugAlreadyExists(resolvedIntegrationId);
|
|
327
|
+
const canAdd = preview !== null && !slugAlreadyExists && (!previewHasNoServers || resolvedBaseUrl.length > 0) && !(hcOperation.length > 0 && hcMissingRequired);
|
|
328
|
+
const handleAnalyze = async () => {
|
|
329
|
+
setAnalyzing(true);
|
|
330
|
+
setAnalyzeError(null);
|
|
331
|
+
setAddError(null);
|
|
332
|
+
const exit = await doPreview({
|
|
333
|
+
payload: { spec: specUrl, specFormat: initialPreset?.specFormat }
|
|
334
|
+
});
|
|
335
|
+
if (Exit.isFailure(exit)) {
|
|
336
|
+
setAnalyzeError(errorMessageFromExit(exit, "Failed to parse spec"));
|
|
337
|
+
setAnalyzing(false);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const result = exit.value;
|
|
341
|
+
setPreview(result);
|
|
342
|
+
setBaseUrl(result.servers.length === 0 ? baseUrlFromSpecInput(specUrl) : "");
|
|
343
|
+
setAnalyzing(false);
|
|
344
|
+
};
|
|
345
|
+
handleAnalyzeRef.current = handleAnalyze;
|
|
346
|
+
const ensureIntegration = useCallback(async () => {
|
|
347
|
+
const exit = await doAdd({
|
|
348
|
+
payload: {
|
|
349
|
+
spec: specInputForAdd(specUrl),
|
|
350
|
+
slug: resolvedIntegrationId,
|
|
351
|
+
name: resolvedDisplayName,
|
|
352
|
+
...resolvedDescription.trim().length > 0 ? { description: resolvedDescription.trim() } : {},
|
|
353
|
+
baseUrl: resolvedBaseUrl,
|
|
354
|
+
specFormat: initialPreset?.specFormat,
|
|
355
|
+
family: initialPreset?.family,
|
|
356
|
+
healthCheck: initialPreset?.healthCheck,
|
|
357
|
+
// Always send the edited method list (even empty) when the user has
|
|
358
|
+
// inspected a preview: an explicit [] means "no auth methods", while
|
|
359
|
+
// OMITTING the field tells the server to derive defaults from the
|
|
360
|
+
// spec, which would silently resurrect methods the user deleted.
|
|
361
|
+
// Serialize to the wire input dialect (apikey -> request-shaped).
|
|
362
|
+
authenticationTemplate: editedAuthenticationTemplate.map(openApiWireAuthInput)
|
|
363
|
+
},
|
|
364
|
+
reactivityKeys: integrationWriteKeys
|
|
365
|
+
});
|
|
366
|
+
if (Exit.isFailure(exit)) {
|
|
367
|
+
setAddError(
|
|
368
|
+
addIntegrationErrorMessage(exit, resolvedIntegrationId, "Failed to add integration")
|
|
369
|
+
);
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
return exit.value.slug;
|
|
373
|
+
}, [
|
|
374
|
+
specUrl,
|
|
375
|
+
doAdd,
|
|
376
|
+
resolvedIntegrationId,
|
|
377
|
+
resolvedDisplayName,
|
|
378
|
+
resolvedDescription,
|
|
379
|
+
resolvedBaseUrl,
|
|
380
|
+
editedAuthenticationTemplate,
|
|
381
|
+
initialPreset?.family,
|
|
382
|
+
initialPreset?.healthCheck,
|
|
383
|
+
initialPreset?.specFormat
|
|
384
|
+
]);
|
|
385
|
+
const handleAdd = async () => {
|
|
386
|
+
setAdding(true);
|
|
387
|
+
setAddError(null);
|
|
388
|
+
const integration = await ensureIntegration();
|
|
389
|
+
if (!integration) {
|
|
390
|
+
setAdding(false);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (hcOperation.length > 0) {
|
|
394
|
+
const identity2 = hcIdentityField.trim();
|
|
395
|
+
const argEntries = Object.entries(hcArgs).map(([key, value]) => [key, value.trim()]).filter(([, value]) => value.length > 0);
|
|
396
|
+
const spec = {
|
|
397
|
+
operation: hcOperation,
|
|
398
|
+
...argEntries.length > 0 ? { args: Object.fromEntries(argEntries) } : {},
|
|
399
|
+
...identity2.length > 0 ? { identityField: identity2 } : {}
|
|
400
|
+
};
|
|
401
|
+
await doSetHealthCheck({
|
|
402
|
+
params: { slug: integration },
|
|
403
|
+
payload: { spec },
|
|
404
|
+
reactivityKeys: healthCheckWriteKeys
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
props.onComplete(String(integration));
|
|
408
|
+
};
|
|
409
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-6", children: [
|
|
410
|
+
/* @__PURE__ */ jsx2("div", { children: /* @__PURE__ */ jsx2("h1", { className: "text-xl font-semibold text-foreground", children: "Add OpenAPI integration" }) }),
|
|
411
|
+
!preview ? /* @__PURE__ */ jsx2(CardStack2, { children: /* @__PURE__ */ jsx2(CardStackContent2, { className: "border-t-0", children: /* @__PURE__ */ jsxs2("div", { className: "space-y-2 p-3", children: [
|
|
412
|
+
/* @__PURE__ */ jsx2(FieldLabel, { children: "OpenAPI Spec" }),
|
|
413
|
+
/* @__PURE__ */ jsxs2("div", { className: "relative", children: [
|
|
414
|
+
/* @__PURE__ */ jsx2(
|
|
415
|
+
Textarea2,
|
|
416
|
+
{
|
|
417
|
+
value: specUrl,
|
|
418
|
+
onChange: (e) => {
|
|
419
|
+
setSpecUrl(e.target.value);
|
|
420
|
+
setAnalyzeError(null);
|
|
421
|
+
},
|
|
422
|
+
placeholder: "https://api.example.com/openapi.json",
|
|
423
|
+
rows: 3,
|
|
424
|
+
maxRows: 10,
|
|
425
|
+
className: "font-mono text-sm"
|
|
426
|
+
}
|
|
427
|
+
),
|
|
428
|
+
analyzing && /* @__PURE__ */ jsx2("div", { className: "pointer-events-none absolute right-2 top-2", children: /* @__PURE__ */ jsx2(IOSSpinner, { className: "size-4" }) })
|
|
429
|
+
] }),
|
|
430
|
+
/* @__PURE__ */ jsx2("p", { className: "text-[11px] text-muted-foreground", children: "Paste a URL or raw JSON/YAML content." }),
|
|
431
|
+
analyzing && /* @__PURE__ */ jsxs2("p", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
432
|
+
/* @__PURE__ */ jsx2(IOSSpinner, { className: "size-3.5" }),
|
|
433
|
+
"Checking spec\u2026"
|
|
434
|
+
] }),
|
|
435
|
+
analyzeError && !analyzing && /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-2 text-xs text-destructive", children: [
|
|
436
|
+
/* @__PURE__ */ jsx2("span", { children: openApiPreviewFailureMessage(analyzeError) }),
|
|
437
|
+
/* @__PURE__ */ jsx2(
|
|
438
|
+
Button,
|
|
439
|
+
{
|
|
440
|
+
type: "button",
|
|
441
|
+
variant: "ghost",
|
|
442
|
+
size: "sm",
|
|
443
|
+
className: "h-7 px-2",
|
|
444
|
+
onClick: () => void handleAnalyze(),
|
|
445
|
+
children: "Retry"
|
|
446
|
+
}
|
|
447
|
+
)
|
|
448
|
+
] })
|
|
449
|
+
] }) }) }) : null,
|
|
450
|
+
preview ? /* @__PURE__ */ jsx2(
|
|
451
|
+
OpenApiIntegrationDetailsFields,
|
|
452
|
+
{
|
|
453
|
+
title: Option.getOrElse(preview.title, () => "API"),
|
|
454
|
+
subtitle: `${Option.getOrElse(preview.version, () => "")}${Option.isSome(preview.version) ? " \xB7 " : ""}${preview.operationCount} operation${preview.operationCount !== 1 ? "s" : ""}${preview.tags.length > 0 ? ` \xB7 ${preview.tags.length} tag${preview.tags.length !== 1 ? "s" : ""}` : ""}`,
|
|
455
|
+
identity,
|
|
456
|
+
description: resolvedDescription,
|
|
457
|
+
onDescriptionChange: setDescriptionDraft,
|
|
458
|
+
baseUrl: resolvedBaseUrl,
|
|
459
|
+
onBaseUrlChange: setBaseUrl,
|
|
460
|
+
baseUrlOptions,
|
|
461
|
+
baseUrlLabel: previewHasNoServers ? "Base URL" : "Base URL override (optional)",
|
|
462
|
+
baseUrlPlaceholder: firstServerUrl || "https://api.example.com",
|
|
463
|
+
baseUrlHint: previewHasNoServers ? void 0 : "Overrides the spec's servers; leave empty to choose the server (and variables) per tool call.",
|
|
464
|
+
baseUrlMissingMessage: previewHasNoServers ? "This spec declares no servers, enter a base URL." : void 0,
|
|
465
|
+
specUrl,
|
|
466
|
+
onSpecUrlChange: (value) => {
|
|
467
|
+
setSpecUrl(value);
|
|
468
|
+
setPreview(null);
|
|
469
|
+
setBaseUrl("");
|
|
470
|
+
},
|
|
471
|
+
faviconIcon: previewPresetIcon,
|
|
472
|
+
faviconUrl: resolvedBaseUrl || firstServerUrl
|
|
473
|
+
}
|
|
474
|
+
) : null,
|
|
475
|
+
preview && /* @__PURE__ */ jsx2(
|
|
476
|
+
AuthMethodListEditor,
|
|
477
|
+
{
|
|
478
|
+
list: authMethodList,
|
|
479
|
+
emptyHint: "No authentication detected. Add a method, or add the integration without auth and connect an account from the integration page later.",
|
|
480
|
+
footerHint: "Every method here is registered with the integration. Connect an account from the integration page after adding."
|
|
481
|
+
}
|
|
482
|
+
),
|
|
483
|
+
preview ? /* @__PURE__ */ jsx2(
|
|
484
|
+
AddOpenApiHealthCheckSection,
|
|
485
|
+
{
|
|
486
|
+
presetHealthCheck: initialPreset?.healthCheck,
|
|
487
|
+
candidates: healthCheckCandidates,
|
|
488
|
+
selected: hcSelected,
|
|
489
|
+
operation: hcOperation,
|
|
490
|
+
onOperationChange: onHcOperationChange,
|
|
491
|
+
identityField: hcIdentityField,
|
|
492
|
+
onIdentityFieldChange: setHcIdentityField,
|
|
493
|
+
args: hcArgs,
|
|
494
|
+
onArgChange: onHcArgChange,
|
|
495
|
+
disabled: adding
|
|
496
|
+
}
|
|
497
|
+
) : null,
|
|
498
|
+
preview && slugAlreadyExists && !adding && /* @__PURE__ */ jsx2(SlugCollisionAlert, { slug: resolvedIntegrationId }),
|
|
499
|
+
addError && /* @__PURE__ */ jsx2(FormErrorAlert, { message: addError }),
|
|
500
|
+
/* @__PURE__ */ jsxs2(FloatActions, { children: [
|
|
501
|
+
/* @__PURE__ */ jsx2(Button, { variant: "ghost", onClick: () => props.onCancel(), disabled: adding, children: "Cancel" }),
|
|
502
|
+
/* @__PURE__ */ jsx2(Button, { onClick: () => void handleAdd(), disabled: !canAdd || analyzing, loading: adding, children: "Add integration" })
|
|
503
|
+
] })
|
|
504
|
+
] });
|
|
505
|
+
}
|
|
506
|
+
export {
|
|
507
|
+
AddOpenApiHealthCheckSection,
|
|
508
|
+
baseUrlFromSpecInput,
|
|
509
|
+
AddOpenApiIntegration as default,
|
|
510
|
+
openApiPreviewFailureMessage
|
|
511
|
+
};
|
|
512
|
+
//# sourceMappingURL=AddOpenApiIntegration-JPAEXE56.js.map
|