@executor-js/plugin-openapi 0.1.0 → 1.4.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/AddOpenApiSource-FLMNI742.js +19 -0
  2. package/dist/AddOpenApiSource-FLMNI742.js.map +1 -0
  3. package/dist/EditOpenApiSource-I4NIGIIJ.js +665 -0
  4. package/dist/EditOpenApiSource-I4NIGIIJ.js.map +1 -0
  5. package/dist/OpenApiSourceSummary-CM46DB4L.js +122 -0
  6. package/dist/OpenApiSourceSummary-CM46DB4L.js.map +1 -0
  7. package/dist/api/group.d.ts +224 -16
  8. package/dist/api/index.d.ts +634 -0
  9. package/dist/chunk-E7PZ2QGD.js +1303 -0
  10. package/dist/chunk-E7PZ2QGD.js.map +1 -0
  11. package/dist/chunk-GFQUEZUW.js +216 -0
  12. package/dist/chunk-GFQUEZUW.js.map +1 -0
  13. package/dist/chunk-OZ67JNID.js +1447 -0
  14. package/dist/chunk-OZ67JNID.js.map +1 -0
  15. package/dist/chunk-TGDT6QCH.js +1120 -0
  16. package/dist/chunk-TGDT6QCH.js.map +1 -0
  17. package/dist/client.js +165 -0
  18. package/dist/client.js.map +1 -0
  19. package/dist/core.js +8 -10
  20. package/dist/index.js +2 -1
  21. package/dist/react/AddOpenApiSource.d.ts +1 -1
  22. package/dist/react/OpenApiSourceDetailsFields.d.ts +18 -0
  23. package/dist/react/atoms.d.ts +320 -15
  24. package/dist/react/client.d.ts +226 -15
  25. package/dist/sdk/extract.d.ts +54 -3
  26. package/dist/sdk/index.d.ts +1 -1
  27. package/dist/sdk/invoke.d.ts +48 -4
  28. package/dist/sdk/openapi-utils.d.ts +4 -3
  29. package/dist/sdk/parse.d.ts +1 -1
  30. package/dist/sdk/parse.test.d.ts +1 -0
  31. package/dist/sdk/plugin.d.ts +247 -128
  32. package/dist/sdk/preview.d.ts +201 -49
  33. package/dist/sdk/store.d.ts +155 -45
  34. package/dist/sdk/types.d.ts +204 -137
  35. package/dist/sdk/usage-scope-isolation.test.d.ts +1 -0
  36. package/dist/testing/index.d.ts +34 -0
  37. package/dist/testing.js +56 -0
  38. package/dist/testing.js.map +1 -0
  39. package/package.json +16 -4
  40. package/dist/chunk-RBE3CVB4.js +0 -2837
  41. package/dist/chunk-RBE3CVB4.js.map +0 -1
@@ -0,0 +1,1120 @@
1
+ import {
2
+ addOpenApiSpecOptimistic,
3
+ previewOpenApiSpec,
4
+ setOpenApiSourceBinding
5
+ } from "./chunk-GFQUEZUW.js";
6
+ import {
7
+ ConfiguredHeaderBinding,
8
+ OAuth2SourceConfig,
9
+ OpenApiSourceBindingInput,
10
+ expandServerUrlOptions,
11
+ headerBindingSlot,
12
+ oauth2ClientIdSlot,
13
+ oauth2ClientSecretSlot,
14
+ oauth2ConnectionSlot,
15
+ queryParamBindingSlot
16
+ } from "./chunk-E7PZ2QGD.js";
17
+
18
+ // src/react/AddOpenApiSource.tsx
19
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
20
+ import { useAtomSet } from "@effect/atom-react";
21
+ import * as Exit from "effect/Exit";
22
+ import * as Match from "effect/Match";
23
+ import * as Option from "effect/Option";
24
+ import * as Schema from "effect/Schema";
25
+ import { ConnectionId, ScopeId, SecretId } from "@executor-js/sdk/core";
26
+ import { startOAuth } from "@executor-js/react/api/atoms";
27
+ import { useScope, useScopeStack } from "@executor-js/react/api/scope-context";
28
+ import { connectionWriteKeys, sourceWriteKeys } from "@executor-js/react/api/reactivity-keys";
29
+ import { HeadersList } from "@executor-js/react/plugins/headers-list";
30
+ import {
31
+ HttpCredentialsEditor,
32
+ emptyHttpCredentials,
33
+ serializeHttpCredentials
34
+ } from "@executor-js/react/plugins/http-credentials";
35
+ import {
36
+ oauthCallbackUrl,
37
+ useOAuthPopupFlow
38
+ } from "@executor-js/react/plugins/oauth-sign-in";
39
+ import {
40
+ CreatableSecretPicker,
41
+ matchPresetKey
42
+ } from "@executor-js/react/plugins/secret-header-auth";
43
+ import { CredentialScopeDropdown } from "@executor-js/react/plugins/credential-target-scope";
44
+ import { slugifyNamespace, useSourceIdentity } from "@executor-js/react/plugins/source-identity";
45
+ import { useSecretPickerSecrets } from "@executor-js/react/plugins/use-secret-picker-secrets";
46
+ import { Button } from "@executor-js/react/components/button";
47
+ import { CopyButton } from "@executor-js/react/components/copy-button";
48
+ import {
49
+ Collapsible,
50
+ CollapsibleContent,
51
+ CollapsibleTrigger
52
+ } from "@executor-js/react/components/collapsible";
53
+ import {
54
+ CardStack as CardStack2,
55
+ CardStackContent as CardStackContent2,
56
+ CardStackEntryField as CardStackEntryField2
57
+ } from "@executor-js/react/components/card-stack";
58
+ import { FieldLabel } from "@executor-js/react/components/field";
59
+ import { FloatActions } from "@executor-js/react/components/float-actions";
60
+ import { HelpTooltip } from "@executor-js/react/components/help-tooltip";
61
+ import { Label } from "@executor-js/react/components/label";
62
+ import { Textarea } from "@executor-js/react/components/textarea";
63
+ import { Checkbox } from "@executor-js/react/components/checkbox";
64
+ import { RadioGroup, RadioGroupItem } from "@executor-js/react/components/radio-group";
65
+ import { IOSSpinner, Spinner } from "@executor-js/react/components/spinner";
66
+
67
+ // src/react/OpenApiSourceDetailsFields.tsx
68
+ import {
69
+ CardStack,
70
+ CardStackContent,
71
+ CardStackEntry,
72
+ CardStackEntryContent,
73
+ CardStackEntryDescription,
74
+ CardStackEntryField,
75
+ CardStackEntryTitle
76
+ } from "@executor-js/react/components/card-stack";
77
+ import {
78
+ FreeformCombobox
79
+ } from "@executor-js/react/components/combobox";
80
+ import { Input } from "@executor-js/react/components/input";
81
+ import { SourceFavicon } from "@executor-js/react/components/source-favicon";
82
+ import {
83
+ SourceIdentityFieldRows
84
+ } from "@executor-js/react/plugins/source-identity";
85
+ import { jsx, jsxs } from "react/jsx-runtime";
86
+ function OpenApiSourceDetailsFields(props) {
87
+ const baseUrlOptions = props.baseUrlOptions ?? [];
88
+ return /* @__PURE__ */ jsx(CardStack, { children: /* @__PURE__ */ jsxs(CardStackContent, { className: "border-t-0", children: [
89
+ /* @__PURE__ */ jsxs(CardStackEntry, { children: [
90
+ props.faviconUrl && /* @__PURE__ */ jsx(SourceFavicon, { url: props.faviconUrl, size: 16 }),
91
+ /* @__PURE__ */ jsxs(CardStackEntryContent, { children: [
92
+ /* @__PURE__ */ jsx(CardStackEntryTitle, { children: props.title }),
93
+ props.description && /* @__PURE__ */ jsx(CardStackEntryDescription, { children: props.description })
94
+ ] }),
95
+ props.saveState && props.saveState !== "idle" && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: props.saveState === "saving" ? "Saving\u2026" : "Saved" })
96
+ ] }),
97
+ /* @__PURE__ */ jsx(
98
+ SourceIdentityFieldRows,
99
+ {
100
+ identity: props.identity,
101
+ namespaceReadOnly: props.namespaceReadOnly
102
+ }
103
+ ),
104
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2", children: [
105
+ /* @__PURE__ */ jsxs(CardStackEntryField, { label: "Base URL", children: [
106
+ baseUrlOptions.length > 0 ? /* @__PURE__ */ jsx(
107
+ FreeformCombobox,
108
+ {
109
+ value: props.baseUrl,
110
+ onValueChange: props.onBaseUrlChange,
111
+ options: baseUrlOptions,
112
+ placeholder: "https://api.example.com",
113
+ className: "w-full",
114
+ inputClassName: "font-mono text-sm"
115
+ }
116
+ ) : /* @__PURE__ */ jsx(
117
+ Input,
118
+ {
119
+ value: props.baseUrl,
120
+ onChange: (e) => props.onBaseUrlChange(e.target.value),
121
+ placeholder: "https://api.example.com",
122
+ className: "font-mono text-sm"
123
+ }
124
+ ),
125
+ props.baseUrlMissingMessage && !props.baseUrl && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-amber-600 dark:text-amber-400", children: props.baseUrlMissingMessage })
126
+ ] }),
127
+ props.specUrl !== void 0 && props.onSpecUrlChange && /* @__PURE__ */ jsx(CardStackEntryField, { label: "Spec URL", children: /* @__PURE__ */ jsx(
128
+ Input,
129
+ {
130
+ value: props.specUrl,
131
+ onChange: (e) => props.onSpecUrlChange?.(e.target.value),
132
+ placeholder: "https://api.example.com/openapi.json",
133
+ className: "font-mono text-sm",
134
+ disabled: props.specUrlDisabled
135
+ }
136
+ ) })
137
+ ] }),
138
+ props.footer && /* @__PURE__ */ jsx(CardStackEntry, { children: /* @__PURE__ */ jsx(CardStackEntryContent, { children: /* @__PURE__ */ jsx(CardStackEntryTitle, { children: props.footer }) }) })
139
+ ] }) });
140
+ }
141
+
142
+ // src/react/AddOpenApiSource.tsx
143
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
144
+ var addSpecWriteKeys = [...sourceWriteKeys, ...connectionWriteKeys];
145
+ var bindingWriteKeys = [...sourceWriteKeys, ...connectionWriteKeys];
146
+ var OPENAPI_OAUTH_POPUP_NAME = "openapi-oauth";
147
+ var OPENAPI_OAUTH_CALLBACK_PATH = "/api/oauth/callback";
148
+ var ErrorMessage = Schema.Struct({ message: Schema.String });
149
+ var decodeErrorMessage = Schema.decodeUnknownOption(ErrorMessage);
150
+ var errorMessageFromExit = (exit, fallback) => Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeErrorMessage), {
151
+ onNone: () => fallback,
152
+ onSome: ({ message }) => message
153
+ });
154
+ var openApiOAuthConnectionId = (namespaceSlug, flow) => flow === "clientCredentials" ? `openapi-oauth2-app-${namespaceSlug || "default"}` : `openapi-oauth2-user-${namespaceSlug || "default"}`;
155
+ function resolveOAuthUrl(url, baseUrl) {
156
+ if (!url) return url;
157
+ try {
158
+ new URL(url);
159
+ return url;
160
+ } catch {
161
+ if (!baseUrl) return url;
162
+ try {
163
+ return new URL(url, baseUrl).toString();
164
+ } catch {
165
+ return url;
166
+ }
167
+ }
168
+ }
169
+ function inferOAuthIssuerUrl(authorizationUrl) {
170
+ try {
171
+ return new URL(authorizationUrl).origin;
172
+ } catch {
173
+ return null;
174
+ }
175
+ }
176
+ var serializeStrategy = (s) => Match.value(s).pipe(
177
+ Match.when({ kind: "none" }, () => "none"),
178
+ Match.when({ kind: "custom" }, () => "custom"),
179
+ Match.when({ kind: "header" }, (sel) => `header:${sel.presetIndex}`),
180
+ Match.when({ kind: "oauth2" }, (sel) => `oauth2:${sel.presetIndex}`),
181
+ Match.exhaustive
182
+ );
183
+ var parseStrategy = (value2) => {
184
+ if (value2 === "none") return { kind: "none" };
185
+ if (value2 === "custom") return { kind: "custom" };
186
+ if (value2.startsWith("header:")) {
187
+ return {
188
+ kind: "header",
189
+ presetIndex: Number(value2.slice("header:".length))
190
+ };
191
+ }
192
+ if (value2.startsWith("oauth2:")) {
193
+ return {
194
+ kind: "oauth2",
195
+ presetIndex: Number(value2.slice("oauth2:".length))
196
+ };
197
+ }
198
+ return { kind: "none" };
199
+ };
200
+ function prefixForHeader(preset, headerName) {
201
+ const label = preset.label.toLowerCase();
202
+ if (headerName.toLowerCase() === "authorization") {
203
+ if (label.includes("bearer")) return "Bearer ";
204
+ if (label.includes("basic")) return "Basic ";
205
+ }
206
+ return void 0;
207
+ }
208
+ function entriesFromSpecPreset(preset) {
209
+ return preset.secretHeaders.map((headerName) => {
210
+ const prefix = prefixForHeader(preset, headerName);
211
+ return {
212
+ name: headerName,
213
+ secretId: null,
214
+ prefix,
215
+ presetKey: matchPresetKey(headerName, prefix),
216
+ fromPreset: true
217
+ };
218
+ });
219
+ }
220
+ var secretStorageDescription = (label) => label === "Personal" ? "Only you can use this secret." : "Everyone in the organization can use this secret.";
221
+ function AddOpenApiSource(props) {
222
+ const [specUrl, setSpecUrl] = useState(props.initialUrl ?? "");
223
+ const [analyzing, setAnalyzing] = useState(false);
224
+ const [analyzeError, setAnalyzeError] = useState(null);
225
+ const [preview, setPreview] = useState(null);
226
+ const [baseUrl, setBaseUrl] = useState("");
227
+ const identity = useSourceIdentity({
228
+ fallbackName: preview ? Option.getOrElse(preview.title, () => "") : "",
229
+ fallbackNamespace: props.initialNamespace
230
+ });
231
+ const [strategy, setStrategy] = useState({ kind: "none" });
232
+ const [customHeaders, setCustomHeaders] = useState([]);
233
+ const [specFetchCredentials, setSpecFetchCredentials] = useState(
234
+ () => emptyHttpCredentials()
235
+ );
236
+ const [specFetchCredentialsOpen, setSpecFetchCredentialsOpen] = useState(false);
237
+ const [runtimeCredentials, setRuntimeCredentials] = useState(
238
+ () => emptyHttpCredentials()
239
+ );
240
+ const [oauth2ClientIdSecretId, setOauth2ClientIdSecretId] = useState(null);
241
+ const [oauth2ClientSecretSecretId, setOauth2ClientSecretSecretId] = useState(null);
242
+ const [oauth2ClientIdScope, setOauth2ClientIdScope] = useState(null);
243
+ const [oauth2ClientSecretScope, setOauth2ClientSecretScope] = useState(null);
244
+ const [oauth2SelectedScopes, setOauth2SelectedScopes] = useState(/* @__PURE__ */ new Set());
245
+ const [oauth2AuthState, setOauth2AuthState] = useState(null);
246
+ const [startingOAuth, setStartingOAuth] = useState(false);
247
+ const [oauth2Error, setOauth2Error] = useState(null);
248
+ const [adding, setAdding] = useState(false);
249
+ const [addError, setAddError] = useState(null);
250
+ const scopeId = useScope();
251
+ const scopeStack = useScopeStack();
252
+ const credentialScopeOptions = useMemo(
253
+ () => scopeStack.map((entry, index) => ({
254
+ scopeId: entry.id,
255
+ label: index === 0 ? "Personal" : entry.name || "Organization",
256
+ description: secretStorageDescription(
257
+ index === 0 ? "Personal" : entry.name || "Organization"
258
+ )
259
+ })),
260
+ [scopeStack]
261
+ );
262
+ const defaultCredentialTargetScope = credentialScopeOptions[credentialScopeOptions.length - 1]?.scopeId ?? scopeId;
263
+ const defaultOAuthTokenTargetScope = credentialScopeOptions[0]?.scopeId ?? scopeId;
264
+ const [credentialTargetScope, setCredentialTargetScope] = useState(
265
+ defaultCredentialTargetScope
266
+ );
267
+ const [oauthTokenTargetScope, setOAuthTokenTargetScope] = useState(
268
+ defaultOAuthTokenTargetScope
269
+ );
270
+ const bindingScopeOptions = useMemo(
271
+ () => credentialScopeOptions.map((option) => ({ ...option })),
272
+ [credentialScopeOptions]
273
+ );
274
+ useEffect(() => {
275
+ if (!credentialScopeOptions.some((option) => option.scopeId === credentialTargetScope)) {
276
+ setCredentialTargetScope(defaultCredentialTargetScope);
277
+ }
278
+ }, [credentialScopeOptions, credentialTargetScope, defaultCredentialTargetScope]);
279
+ useEffect(() => {
280
+ if (!credentialScopeOptions.some((option) => option.scopeId === oauthTokenTargetScope)) {
281
+ setOAuthTokenTargetScope(defaultOAuthTokenTargetScope);
282
+ }
283
+ }, [credentialScopeOptions, defaultOAuthTokenTargetScope, oauthTokenTargetScope]);
284
+ const initialCredentialScopeOption = credentialScopeOptions.find((option) => option.scopeId === credentialTargetScope) ?? credentialScopeOptions[0];
285
+ const initialCredentialScopeOptions = initialCredentialScopeOption ? [initialCredentialScopeOption] : [];
286
+ const doPreview = useAtomSet(previewOpenApiSpec, { mode: "promiseExit" });
287
+ const doAdd = useAtomSet(addOpenApiSpecOptimistic(scopeId), {
288
+ mode: "promiseExit"
289
+ });
290
+ const doStartOAuth = useAtomSet(startOAuth, { mode: "promiseExit" });
291
+ const doSetBinding = useAtomSet(setOpenApiSourceBinding, {
292
+ mode: "promiseExit"
293
+ });
294
+ const secretList = useSecretPickerSecrets();
295
+ const initialCredentialSecrets = useMemo(
296
+ () => secretList.filter((secret) => secret.scopeId === String(credentialTargetScope)),
297
+ [credentialTargetScope, secretList]
298
+ );
299
+ const oauth = useOAuthPopupFlow({
300
+ popupName: OPENAPI_OAUTH_POPUP_NAME,
301
+ popupBlockedMessage: "OAuth popup was blocked by the browser",
302
+ popupClosedMessage: "OAuth cancelled - popup was closed before completing the flow.",
303
+ startErrorMessage: "Failed to start OAuth"
304
+ });
305
+ const handleAnalyzeRef = useRef(() => {
306
+ });
307
+ useEffect(() => {
308
+ const trimmed = specUrl.trim();
309
+ if (!trimmed) return;
310
+ if (preview) return;
311
+ const handle = setTimeout(() => {
312
+ handleAnalyzeRef.current();
313
+ }, 400);
314
+ return () => clearTimeout(handle);
315
+ }, [specUrl, preview]);
316
+ const expandServerOptions = (server) => {
317
+ return expandServerUrlOptions(server).map((value2) => ({
318
+ value: value2,
319
+ label: value2
320
+ }));
321
+ };
322
+ const servers = preview?.servers ?? [];
323
+ const baseUrlOptions = Array.from(
324
+ new Map(servers.flatMap(expandServerOptions).map((option) => [option.value, option])).values()
325
+ );
326
+ const resolvedBaseUrl = baseUrl.trim();
327
+ const configuredHeaders = {};
328
+ const headerBindings = [];
329
+ const configuredQueryParams = {};
330
+ const queryParamBindings = [];
331
+ for (const ch of customHeaders) {
332
+ if (!ch.name.trim()) continue;
333
+ const slot = headerBindingSlot(ch.name.trim());
334
+ configuredHeaders[ch.name.trim()] = ConfiguredHeaderBinding.make({
335
+ kind: "binding",
336
+ slot,
337
+ prefix: ch.prefix
338
+ });
339
+ if (ch.secretId) {
340
+ headerBindings.push({
341
+ slot,
342
+ secretId: ch.secretId,
343
+ scope: ch.targetScope ?? credentialTargetScope,
344
+ secretScope: ch.secretScope ?? ch.targetScope ?? credentialTargetScope
345
+ });
346
+ }
347
+ }
348
+ for (const param of runtimeCredentials.queryParams) {
349
+ const name = param.name.trim();
350
+ if (!name) continue;
351
+ if (param.secretId) {
352
+ const slot = queryParamBindingSlot(name);
353
+ configuredQueryParams[name] = ConfiguredHeaderBinding.make({
354
+ kind: "binding",
355
+ slot,
356
+ prefix: param.prefix
357
+ });
358
+ queryParamBindings.push({
359
+ slot,
360
+ secretId: param.secretId,
361
+ scope: param.targetScope ?? credentialTargetScope,
362
+ secretScope: param.secretScope ?? param.targetScope ?? credentialTargetScope
363
+ });
364
+ continue;
365
+ }
366
+ if (param.literalValue?.trim()) {
367
+ configuredQueryParams[name] = param.literalValue.trim();
368
+ }
369
+ }
370
+ const oauth2Presets = preview?.oauth2Presets ?? [];
371
+ const oauth2RedirectUrl = oauthCallbackUrl(OPENAPI_OAUTH_CALLBACK_PATH);
372
+ const resolvedSourceId = slugifyNamespace(identity.namespace) || (preview ? Option.getOrElse(preview.title, () => "openapi") : "openapi");
373
+ const selectedOAuth2Preset = strategy.kind === "oauth2" ? oauth2Presets[strategy.presetIndex] ?? null : null;
374
+ const selectedOAuth2Fingerprint = selectedOAuth2Preset ? [
375
+ resolvedSourceId,
376
+ resolvedBaseUrl,
377
+ selectedOAuth2Preset.securitySchemeName,
378
+ selectedOAuth2Preset.flow,
379
+ selectedOAuth2Preset.tokenUrl,
380
+ Option.getOrElse(selectedOAuth2Preset.authorizationUrl, () => "")
381
+ ].join("\n") : "";
382
+ const oauth2Auth = oauth2AuthState?.fingerprint === selectedOAuth2Fingerprint ? oauth2AuthState.auth : null;
383
+ const configuredOAuth2 = strategy.kind === "oauth2" && selectedOAuth2Preset ? OAuth2SourceConfig.make({
384
+ kind: "oauth2",
385
+ securitySchemeName: selectedOAuth2Preset.securitySchemeName,
386
+ flow: selectedOAuth2Preset.flow,
387
+ tokenUrl: resolveOAuthUrl(selectedOAuth2Preset.tokenUrl, resolvedBaseUrl),
388
+ authorizationUrl: selectedOAuth2Preset.flow === "authorizationCode" ? resolveOAuthUrl(
389
+ Option.getOrElse(selectedOAuth2Preset.authorizationUrl, () => ""),
390
+ resolvedBaseUrl
391
+ ) || null : null,
392
+ clientIdSlot: oauth2ClientIdSlot(selectedOAuth2Preset.securitySchemeName),
393
+ // Authorization-code specs can still be confidential clients
394
+ // (Spotify is one example). Persist the slot even when the value is
395
+ // deferred so the edit screen can collect the secret later.
396
+ clientSecretSlot: oauth2ClientSecretSlot(selectedOAuth2Preset.securitySchemeName),
397
+ connectionSlot: oauth2ConnectionSlot(selectedOAuth2Preset.securitySchemeName),
398
+ scopes: [...oauth2SelectedScopes]
399
+ }) : null;
400
+ const hasHeaders = Object.keys(configuredHeaders).length > 0;
401
+ const oauth2Busy = startingOAuth || oauth.busy;
402
+ const canConnectOAuth2 = Boolean(oauth2ClientIdSecretId) && resolvedBaseUrl.length > 0;
403
+ const hasIncompleteHeaderCredentials = strategy.kind !== "none" && strategy.kind !== "oauth2" && customHeaders.some((header) => header.name.trim() && !header.secretId);
404
+ const hasIncompleteQueryCredentials = runtimeCredentials.queryParams.some(
405
+ (param) => param.name.trim() && !param.secretId && !param.literalValue?.trim()
406
+ );
407
+ const willAddWithoutInitialCredentials = Boolean(selectedOAuth2Preset && !oauth2Auth) || hasIncompleteHeaderCredentials || hasIncompleteQueryCredentials;
408
+ const canAdd = preview !== null && resolvedBaseUrl.length > 0;
409
+ const handleAnalyze = async () => {
410
+ setAnalyzing(true);
411
+ setAnalyzeError(null);
412
+ setAddError(null);
413
+ const credentials = serializeHttpCredentials(specFetchCredentials);
414
+ const exit = await doPreview({
415
+ params: { scopeId },
416
+ payload: {
417
+ spec: specUrl,
418
+ specFetchCredentials: credentials
419
+ }
420
+ });
421
+ if (Exit.isFailure(exit)) {
422
+ setAnalyzeError(errorMessageFromExit(exit, "Failed to parse spec"));
423
+ setAnalyzing(false);
424
+ return;
425
+ }
426
+ const result = exit.value;
427
+ setPreview(result);
428
+ const firstServer = result.servers[0];
429
+ setBaseUrl(firstServer ? expandServerOptions(firstServer)[0]?.value ?? "" : "");
430
+ const firstPreset = result.headerPresets[0];
431
+ if (firstPreset) {
432
+ setStrategy({ kind: "header", presetIndex: 0 });
433
+ setCustomHeaders(entriesFromSpecPreset(firstPreset));
434
+ } else if (result.oauth2Presets[0]) {
435
+ setStrategy({ kind: "oauth2", presetIndex: 0 });
436
+ setCustomHeaders([]);
437
+ setOauth2SelectedScopes(new Set(Object.keys(result.oauth2Presets[0].scopes)));
438
+ } else {
439
+ setStrategy({ kind: "custom" });
440
+ setCustomHeaders([]);
441
+ }
442
+ setAnalyzing(false);
443
+ };
444
+ handleAnalyzeRef.current = handleAnalyze;
445
+ const selectStrategy = (next) => {
446
+ setStrategy(next);
447
+ if (next.kind !== "oauth2") {
448
+ setOauth2AuthState(null);
449
+ setOauth2Error(null);
450
+ }
451
+ Match.value(next).pipe(
452
+ Match.when({ kind: "none" }, () => {
453
+ setCustomHeaders([]);
454
+ }),
455
+ Match.when({ kind: "custom" }, () => {
456
+ const userHeaders = customHeaders.filter((h) => !h.fromPreset);
457
+ setCustomHeaders(userHeaders.length > 0 ? userHeaders : []);
458
+ }),
459
+ Match.when({ kind: "header" }, (n) => {
460
+ const preset = preview?.headerPresets[n.presetIndex];
461
+ if (!preset) return;
462
+ const userHeaders = customHeaders.filter((h) => !h.fromPreset);
463
+ setCustomHeaders([...entriesFromSpecPreset(preset), ...userHeaders]);
464
+ }),
465
+ Match.when({ kind: "oauth2" }, (n) => {
466
+ setCustomHeaders([]);
467
+ const preset = preview?.oauth2Presets[n.presetIndex];
468
+ if (preset) {
469
+ setOauth2SelectedScopes(new Set(Object.keys(preset.scopes)));
470
+ }
471
+ }),
472
+ Match.exhaustive
473
+ );
474
+ };
475
+ const handleHeadersChange = (next) => {
476
+ setCustomHeaders(next);
477
+ if (strategy.kind === "header" && next.every((h) => !h.fromPreset)) {
478
+ setStrategy(next.length === 0 ? { kind: "none" } : { kind: "custom" });
479
+ }
480
+ };
481
+ const setInitialCredentialScope = (targetScope) => {
482
+ setCredentialTargetScope(targetScope);
483
+ setCustomHeaders(
484
+ (headers) => headers.map((header) => ({
485
+ ...header,
486
+ targetScope,
487
+ ...header.secretScope && header.secretScope !== targetScope ? { secretId: null, secretScope: void 0 } : {}
488
+ }))
489
+ );
490
+ setOauth2ClientIdSecretId(null);
491
+ setOauth2ClientSecretSecretId(null);
492
+ setOauth2ClientIdScope(null);
493
+ setOauth2ClientSecretScope(null);
494
+ setOauth2AuthState(null);
495
+ };
496
+ const toggleOAuth2Scope = (scope) => {
497
+ setOauth2SelectedScopes((prev) => {
498
+ const copy = new Set(prev);
499
+ if (copy.has(scope)) copy.delete(scope);
500
+ else copy.add(scope);
501
+ return copy;
502
+ });
503
+ setOauth2AuthState(null);
504
+ };
505
+ const handleConnectOAuth2 = useCallback(async () => {
506
+ if (!selectedOAuth2Preset || !oauth2ClientIdSecretId || !preview) return;
507
+ oauth.cancel();
508
+ setOauth2Error(null);
509
+ const displayName = identity.name.trim() || selectedOAuth2Preset.securitySchemeName;
510
+ const tokenUrl = resolveOAuthUrl(selectedOAuth2Preset.tokenUrl, resolvedBaseUrl);
511
+ if (selectedOAuth2Preset.flow === "clientCredentials") {
512
+ if (!oauth2ClientSecretSecretId) {
513
+ setOauth2Error("client_credentials requires a client secret");
514
+ return;
515
+ }
516
+ setStartingOAuth(true);
517
+ const connectionId = openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow);
518
+ const exit = await doStartOAuth({
519
+ params: { scopeId: oauthTokenTargetScope },
520
+ payload: {
521
+ endpoint: tokenUrl,
522
+ redirectUrl: tokenUrl,
523
+ connectionId,
524
+ tokenScope: oauthTokenTargetScope,
525
+ strategy: {
526
+ kind: "client-credentials",
527
+ tokenEndpoint: tokenUrl,
528
+ clientIdSecretId: oauth2ClientIdSecretId,
529
+ clientSecretSecretId: oauth2ClientSecretSecretId,
530
+ scopes: [...oauth2SelectedScopes]
531
+ },
532
+ pluginId: "openapi",
533
+ identityLabel: `${displayName} OAuth`
534
+ }
535
+ });
536
+ setStartingOAuth(false);
537
+ if (Exit.isFailure(exit)) {
538
+ setOauth2Error(errorMessageFromExit(exit, "Failed to start OAuth"));
539
+ return;
540
+ }
541
+ const response = exit.value;
542
+ if (!response.completedConnection) {
543
+ setOauth2Error("client_credentials flow did not mint a connection");
544
+ return;
545
+ }
546
+ setOauth2AuthState({
547
+ fingerprint: selectedOAuth2Fingerprint,
548
+ auth: { connectionId: response.completedConnection.connectionId }
549
+ });
550
+ setOauth2Error(null);
551
+ return;
552
+ }
553
+ const authorizationUrl = resolveOAuthUrl(
554
+ Option.getOrElse(selectedOAuth2Preset.authorizationUrl, () => ""),
555
+ resolvedBaseUrl
556
+ );
557
+ const issuerUrl = inferOAuthIssuerUrl(authorizationUrl);
558
+ await oauth.openAuthorization({
559
+ tokenScope: oauthTokenTargetScope,
560
+ run: async () => {
561
+ const exit = await doStartOAuth({
562
+ params: { scopeId: oauthTokenTargetScope },
563
+ payload: {
564
+ endpoint: authorizationUrl,
565
+ connectionId: openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow),
566
+ tokenScope: oauthTokenTargetScope,
567
+ redirectUrl: oauth2RedirectUrl,
568
+ strategy: {
569
+ kind: "authorization-code",
570
+ authorizationEndpoint: authorizationUrl,
571
+ tokenEndpoint: tokenUrl,
572
+ issuerUrl,
573
+ clientIdSecretId: oauth2ClientIdSecretId,
574
+ clientSecretSecretId: oauth2ClientSecretSecretId ?? null,
575
+ scopes: [...oauth2SelectedScopes]
576
+ },
577
+ pluginId: "openapi",
578
+ identityLabel: `${displayName} OAuth`
579
+ }
580
+ });
581
+ if (Exit.isFailure(exit)) {
582
+ throw new Error(errorMessageFromExit(exit, "Failed to start OAuth"));
583
+ }
584
+ const response = exit.value;
585
+ if (response.authorizationUrl === null) {
586
+ throw new Error("Unexpected response flow from server");
587
+ }
588
+ return {
589
+ sessionId: response.sessionId,
590
+ authorizationUrl: response.authorizationUrl
591
+ };
592
+ },
593
+ onSuccess: (result) => {
594
+ setOauth2AuthState({
595
+ fingerprint: selectedOAuth2Fingerprint,
596
+ auth: { connectionId: result.connectionId }
597
+ });
598
+ setOauth2Error(null);
599
+ },
600
+ onError: (message) => {
601
+ setStartingOAuth(false);
602
+ setOauth2Error(message);
603
+ }
604
+ });
605
+ }, [
606
+ selectedOAuth2Preset,
607
+ oauth2ClientIdSecretId,
608
+ oauth2ClientSecretSecretId,
609
+ oauth2SelectedScopes,
610
+ oauth2RedirectUrl,
611
+ resolvedBaseUrl,
612
+ preview,
613
+ doStartOAuth,
614
+ identity.name,
615
+ resolvedSourceId,
616
+ selectedOAuth2Fingerprint,
617
+ oauth,
618
+ oauthTokenTargetScope
619
+ ]);
620
+ const handleCancelOAuth2 = useCallback(() => {
621
+ oauth.cancel();
622
+ setStartingOAuth(false);
623
+ setOauth2Error(null);
624
+ }, [oauth]);
625
+ const handleAdd = async () => {
626
+ setAdding(true);
627
+ setAddError(null);
628
+ const namespace = resolvedSourceId;
629
+ const displayName = identity.name.trim() || (preview ? Option.getOrElse(preview.title, () => namespace) : namespace);
630
+ const exit = await doAdd({
631
+ params: { scopeId },
632
+ payload: {
633
+ targetScope: scopeId,
634
+ credentialTargetScope,
635
+ spec: specUrl,
636
+ specFetchCredentials: serializeHttpCredentials(specFetchCredentials),
637
+ name: displayName,
638
+ namespace,
639
+ baseUrl: resolvedBaseUrl || void 0,
640
+ ...hasHeaders ? { headers: configuredHeaders } : {},
641
+ ...Object.keys(configuredQueryParams).length > 0 ? { queryParams: configuredQueryParams } : {},
642
+ ...configuredOAuth2 ? { oauth2: configuredOAuth2 } : {}
643
+ },
644
+ reactivityKeys: addSpecWriteKeys
645
+ });
646
+ if (Exit.isFailure(exit)) {
647
+ setAddError(errorMessageFromExit(exit, "Failed to add source"));
648
+ setAdding(false);
649
+ return;
650
+ }
651
+ const sourceId = exit.value.namespace;
652
+ const sourceScope = ScopeId.make(scopeId);
653
+ const bindingScope = ScopeId.make(credentialTargetScope);
654
+ const oauthTokenBindingScope = ScopeId.make(oauthTokenTargetScope);
655
+ const clientIdSecretScope = oauth2ClientIdScope ?? bindingScope;
656
+ const clientSecretSecretScope = oauth2ClientSecretScope ?? bindingScope;
657
+ for (const binding of headerBindings) {
658
+ const bindingExit = await doSetBinding({
659
+ params: { scopeId },
660
+ payload: OpenApiSourceBindingInput.make({
661
+ sourceId,
662
+ sourceScope,
663
+ scope: binding.scope,
664
+ slot: binding.slot,
665
+ value: {
666
+ kind: "secret",
667
+ secretId: SecretId.make(binding.secretId),
668
+ secretScopeId: binding.secretScope
669
+ }
670
+ }),
671
+ reactivityKeys: bindingWriteKeys
672
+ });
673
+ if (Exit.isFailure(bindingExit)) {
674
+ setAddError(errorMessageFromExit(bindingExit, "Failed to add source"));
675
+ setAdding(false);
676
+ return;
677
+ }
678
+ }
679
+ for (const binding of queryParamBindings) {
680
+ const bindingExit = await doSetBinding({
681
+ params: { scopeId },
682
+ payload: OpenApiSourceBindingInput.make({
683
+ sourceId,
684
+ sourceScope,
685
+ scope: binding.scope,
686
+ slot: binding.slot,
687
+ value: {
688
+ kind: "secret",
689
+ secretId: SecretId.make(binding.secretId),
690
+ secretScopeId: binding.secretScope
691
+ }
692
+ }),
693
+ reactivityKeys: bindingWriteKeys
694
+ });
695
+ if (Exit.isFailure(bindingExit)) {
696
+ setAddError(errorMessageFromExit(bindingExit, "Failed to add source"));
697
+ setAdding(false);
698
+ return;
699
+ }
700
+ }
701
+ if (configuredOAuth2 && oauth2ClientIdSecretId) {
702
+ const bindingExit = await doSetBinding({
703
+ params: { scopeId },
704
+ payload: OpenApiSourceBindingInput.make({
705
+ sourceId,
706
+ sourceScope,
707
+ scope: bindingScope,
708
+ slot: configuredOAuth2.clientIdSlot,
709
+ value: {
710
+ kind: "secret",
711
+ secretId: SecretId.make(oauth2ClientIdSecretId),
712
+ secretScopeId: clientIdSecretScope
713
+ }
714
+ }),
715
+ reactivityKeys: bindingWriteKeys
716
+ });
717
+ if (Exit.isFailure(bindingExit)) {
718
+ setAddError(errorMessageFromExit(bindingExit, "Failed to add source"));
719
+ setAdding(false);
720
+ return;
721
+ }
722
+ }
723
+ if (configuredOAuth2?.clientSecretSlot && oauth2ClientSecretSecretId) {
724
+ const bindingExit = await doSetBinding({
725
+ params: { scopeId },
726
+ payload: OpenApiSourceBindingInput.make({
727
+ sourceId,
728
+ sourceScope,
729
+ scope: bindingScope,
730
+ slot: configuredOAuth2.clientSecretSlot,
731
+ value: {
732
+ kind: "secret",
733
+ secretId: SecretId.make(oauth2ClientSecretSecretId),
734
+ secretScopeId: clientSecretSecretScope
735
+ }
736
+ }),
737
+ reactivityKeys: bindingWriteKeys
738
+ });
739
+ if (Exit.isFailure(bindingExit)) {
740
+ setAddError(errorMessageFromExit(bindingExit, "Failed to add source"));
741
+ setAdding(false);
742
+ return;
743
+ }
744
+ }
745
+ if (configuredOAuth2 && oauth2Auth) {
746
+ const bindingExit = await doSetBinding({
747
+ params: { scopeId },
748
+ payload: OpenApiSourceBindingInput.make({
749
+ sourceId,
750
+ sourceScope,
751
+ scope: oauthTokenBindingScope,
752
+ slot: configuredOAuth2.connectionSlot,
753
+ value: {
754
+ kind: "connection",
755
+ connectionId: ConnectionId.make(oauth2Auth.connectionId)
756
+ }
757
+ }),
758
+ reactivityKeys: bindingWriteKeys
759
+ });
760
+ if (Exit.isFailure(bindingExit)) {
761
+ setAddError(errorMessageFromExit(bindingExit, "Failed to add source"));
762
+ setAdding(false);
763
+ return;
764
+ }
765
+ }
766
+ props.onComplete();
767
+ };
768
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-6", children: [
769
+ /* @__PURE__ */ jsx2("div", { children: /* @__PURE__ */ jsx2("h1", { className: "text-xl font-semibold text-foreground", children: "Add OpenAPI Source" }) }),
770
+ !preview && /* @__PURE__ */ jsxs2(Fragment, { children: [
771
+ /* @__PURE__ */ jsx2(CardStack2, { children: /* @__PURE__ */ jsx2(CardStackContent2, { className: "border-t-0", children: /* @__PURE__ */ jsx2(
772
+ CardStackEntryField2,
773
+ {
774
+ label: "OpenAPI Spec",
775
+ hint: "Paste a URL or raw JSON/YAML content.",
776
+ children: /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
777
+ /* @__PURE__ */ jsx2(
778
+ Textarea,
779
+ {
780
+ value: specUrl,
781
+ onChange: (e) => {
782
+ setSpecUrl(e.target.value);
783
+ },
784
+ placeholder: "https://api.example.com/openapi.json",
785
+ rows: 3,
786
+ maxRows: 10,
787
+ className: "font-mono text-sm"
788
+ }
789
+ ),
790
+ analyzing && /* @__PURE__ */ jsx2("div", { className: "pointer-events-none absolute right-2 top-2", children: /* @__PURE__ */ jsx2(IOSSpinner, { className: "size-4" }) })
791
+ ] })
792
+ }
793
+ ) }) }),
794
+ /* @__PURE__ */ jsxs2(
795
+ Collapsible,
796
+ {
797
+ open: specFetchCredentialsOpen,
798
+ onOpenChange: setSpecFetchCredentialsOpen,
799
+ className: "space-y-3",
800
+ children: [
801
+ /* @__PURE__ */ jsx2(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsx2(Button, { variant: "outline", size: "sm", className: "self-start", children: specFetchCredentialsOpen ? "Hide spec credentials" : "Add spec credentials" }) }),
802
+ /* @__PURE__ */ jsx2(CollapsibleContent, { children: /* @__PURE__ */ jsx2(
803
+ HttpCredentialsEditor,
804
+ {
805
+ credentials: specFetchCredentials,
806
+ onChange: setSpecFetchCredentials,
807
+ existingSecrets: secretList,
808
+ sourceName: identity.name,
809
+ targetScope: credentialTargetScope,
810
+ labels: {
811
+ headers: "Spec fetch headers",
812
+ queryParams: "Spec fetch query parameters"
813
+ }
814
+ }
815
+ ) })
816
+ ]
817
+ }
818
+ )
819
+ ] }),
820
+ preview ? /* @__PURE__ */ jsx2(
821
+ OpenApiSourceDetailsFields,
822
+ {
823
+ title: Option.getOrElse(preview.title, () => "API"),
824
+ description: `${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" : ""}` : ""}`,
825
+ identity,
826
+ baseUrl: resolvedBaseUrl,
827
+ onBaseUrlChange: setBaseUrl,
828
+ baseUrlOptions,
829
+ specUrl,
830
+ onSpecUrlChange: (value2) => {
831
+ setSpecUrl(value2);
832
+ setPreview(null);
833
+ setBaseUrl("");
834
+ setCustomHeaders([]);
835
+ setStrategy({ kind: "none" });
836
+ setOauth2AuthState(null);
837
+ setOauth2Error(null);
838
+ },
839
+ faviconUrl: resolvedBaseUrl,
840
+ baseUrlMissingMessage: "A base URL is required to make requests."
841
+ }
842
+ ) : null,
843
+ analyzeError && /* @__PURE__ */ jsx2("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx2("p", { className: "text-[12px] text-destructive", children: analyzeError }) }),
844
+ preview && /* @__PURE__ */ jsxs2(Fragment, { children: [
845
+ /* @__PURE__ */ jsxs2("section", { className: "space-y-2.5", children: [
846
+ /* @__PURE__ */ jsx2(FieldLabel, { children: "Authentication method" }),
847
+ /* @__PURE__ */ jsxs2(
848
+ RadioGroup,
849
+ {
850
+ value: serializeStrategy(strategy),
851
+ onValueChange: (value2) => selectStrategy(parseStrategy(value2)),
852
+ className: "gap-1.5",
853
+ children: [
854
+ preview.headerPresets.map((preset, i) => {
855
+ const selected = strategy.kind === "header" && strategy.presetIndex === i;
856
+ return /* @__PURE__ */ jsxs2(
857
+ Label,
858
+ {
859
+ className: `flex items-start gap-2.5 rounded-lg border px-3 py-2 cursor-pointer transition-colors ${selected ? "border-primary/50 bg-primary/[0.03]" : "border-border hover:bg-accent/50"}`,
860
+ children: [
861
+ /* @__PURE__ */ jsx2(RadioGroupItem, { value: `header:${i}`, className: "mt-0.5" }),
862
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
863
+ /* @__PURE__ */ jsx2("div", { className: "text-xs font-medium text-foreground", children: preset.label }),
864
+ preset.secretHeaders.length > 0 && /* @__PURE__ */ jsx2("div", { className: "mt-0.5 font-mono text-[10px] text-muted-foreground", children: preset.secretHeaders.join(" \xB7 ") })
865
+ ] })
866
+ ]
867
+ },
868
+ `header-${i}`
869
+ );
870
+ }),
871
+ oauth2Presets.map((preset, i) => {
872
+ const selected = strategy.kind === "oauth2" && strategy.presetIndex === i;
873
+ const scopeCount = Object.keys(preset.scopes).length;
874
+ return /* @__PURE__ */ jsxs2(
875
+ Label,
876
+ {
877
+ className: `flex items-start gap-2.5 rounded-lg border px-3 py-2 cursor-pointer transition-colors ${selected ? "border-primary/50 bg-primary/[0.03]" : "border-border hover:bg-accent/50"}`,
878
+ children: [
879
+ /* @__PURE__ */ jsx2(RadioGroupItem, { value: `oauth2:${i}`, className: "mt-0.5" }),
880
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
881
+ /* @__PURE__ */ jsx2("div", { className: "text-xs font-medium text-foreground", children: preset.label }),
882
+ /* @__PURE__ */ jsxs2("div", { className: "mt-0.5 text-[10px] text-muted-foreground", children: [
883
+ scopeCount,
884
+ " scope",
885
+ scopeCount === 1 ? "" : "s"
886
+ ] })
887
+ ] })
888
+ ]
889
+ },
890
+ `oauth2-${i}`
891
+ );
892
+ }),
893
+ /* @__PURE__ */ jsxs2(
894
+ Label,
895
+ {
896
+ className: `flex items-center gap-2.5 rounded-lg border px-3 py-2 cursor-pointer transition-colors ${strategy.kind === "custom" ? "border-primary/50 bg-primary/[0.03]" : "border-border hover:bg-accent/50"}`,
897
+ children: [
898
+ /* @__PURE__ */ jsx2(RadioGroupItem, { value: "custom" }),
899
+ /* @__PURE__ */ jsx2("span", { className: "text-xs font-medium text-foreground", children: "Custom" })
900
+ ]
901
+ }
902
+ ),
903
+ /* @__PURE__ */ jsxs2(
904
+ Label,
905
+ {
906
+ className: `flex items-center gap-2.5 rounded-lg border px-3 py-2 cursor-pointer transition-colors ${strategy.kind === "none" ? "border-primary/50 bg-primary/[0.03]" : "border-border hover:bg-accent/50"}`,
907
+ children: [
908
+ /* @__PURE__ */ jsx2(RadioGroupItem, { value: "none" }),
909
+ /* @__PURE__ */ jsx2("span", { className: "text-xs font-medium text-foreground", children: "None" })
910
+ ]
911
+ }
912
+ )
913
+ ]
914
+ }
915
+ ),
916
+ strategy.kind !== "none" && strategy.kind !== "oauth2" && /* @__PURE__ */ jsx2("div", { className: "space-y-3", children: /* @__PURE__ */ jsx2(
917
+ HeadersList,
918
+ {
919
+ headers: customHeaders,
920
+ onHeadersChange: handleHeadersChange,
921
+ existingSecrets: secretList,
922
+ sourceName: identity.name,
923
+ targetScope: credentialTargetScope,
924
+ credentialScopeOptions: initialCredentialScopeOptions,
925
+ bindingScopeOptions,
926
+ restrictSecretsToTargetScope: true,
927
+ emptyLabel: "No credentials yet. Add the header value this method should use."
928
+ }
929
+ ) }),
930
+ /* @__PURE__ */ jsx2(
931
+ HttpCredentialsEditor,
932
+ {
933
+ credentials: runtimeCredentials,
934
+ onChange: setRuntimeCredentials,
935
+ existingSecrets: secretList,
936
+ sourceName: identity.name,
937
+ targetScope: credentialTargetScope,
938
+ credentialScopeOptions: initialCredentialScopeOptions,
939
+ bindingScopeOptions,
940
+ restrictSecretsToTargetScope: true,
941
+ sections: { headers: false, queryParams: true },
942
+ labels: { queryParams: "Runtime query parameters" }
943
+ }
944
+ ),
945
+ selectedOAuth2Preset && /* @__PURE__ */ jsx2("div", { className: "space-y-3 rounded-lg border border-border/60 bg-muted/10 p-3", children: /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
946
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5", children: [
947
+ /* @__PURE__ */ jsxs2(FieldLabel, { className: "text-[11px]", children: [
948
+ "Redirect URL",
949
+ " ",
950
+ /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground", children: "\xB7 add this to your OAuth app's allowed redirects" })
951
+ ] }),
952
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 rounded-md border border-border bg-background/50 px-2.5 py-1.5 font-mono text-[11px]", children: [
953
+ /* @__PURE__ */ jsx2("span", { className: "truncate flex-1 text-foreground", children: oauth2RedirectUrl }),
954
+ /* @__PURE__ */ jsx2(CopyButton, { value: oauth2RedirectUrl })
955
+ ] })
956
+ ] }),
957
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5", children: [
958
+ /* @__PURE__ */ jsx2(FieldLabel, { className: "text-[11px]", children: "Client ID secret" }),
959
+ /* @__PURE__ */ jsxs2("div", { className: "grid gap-2 md:grid-cols-2", children: [
960
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5", children: [
961
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5", children: [
962
+ /* @__PURE__ */ jsx2(FieldLabel, { className: "text-[11px]", children: "Secret" }),
963
+ /* @__PURE__ */ jsx2(HelpTooltip, { label: "Client ID secret", children: "Select or create the OAuth client ID secret." })
964
+ ] }),
965
+ /* @__PURE__ */ jsx2(
966
+ CreatableSecretPicker,
967
+ {
968
+ value: oauth2ClientIdSecretId,
969
+ onSelect: (id, secretScopeId) => {
970
+ setOauth2ClientIdSecretId(id);
971
+ setOauth2ClientIdScope(secretScopeId ?? credentialTargetScope);
972
+ setOauth2AuthState(null);
973
+ },
974
+ secrets: initialCredentialSecrets,
975
+ sourceName: identity.name,
976
+ secretLabel: "Client ID",
977
+ targetScope: oauth2ClientIdScope ?? credentialTargetScope,
978
+ credentialScopeOptions: initialCredentialScopeOptions,
979
+ onCreatedScope: setOauth2ClientIdScope
980
+ }
981
+ )
982
+ ] }),
983
+ /* @__PURE__ */ jsx2(
984
+ CredentialScopeDropdown,
985
+ {
986
+ value: credentialTargetScope,
987
+ options: bindingScopeOptions,
988
+ onChange: setInitialCredentialScope
989
+ }
990
+ )
991
+ ] })
992
+ ] }),
993
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5", children: [
994
+ /* @__PURE__ */ jsxs2(FieldLabel, { className: "text-[11px]", children: [
995
+ "Client secret",
996
+ " ",
997
+ /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground", children: "\xB7 optional for public clients with PKCE" })
998
+ ] }),
999
+ /* @__PURE__ */ jsxs2("div", { className: "grid gap-2 md:grid-cols-2", children: [
1000
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5", children: [
1001
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5", children: [
1002
+ /* @__PURE__ */ jsx2(FieldLabel, { className: "text-[11px]", children: "Secret" }),
1003
+ /* @__PURE__ */ jsx2(HelpTooltip, { label: "Client secret", children: "Select or create the OAuth client secret." })
1004
+ ] }),
1005
+ /* @__PURE__ */ jsx2(
1006
+ CreatableSecretPicker,
1007
+ {
1008
+ value: oauth2ClientSecretSecretId,
1009
+ onSelect: (id, secretScopeId) => {
1010
+ setOauth2ClientSecretSecretId(id);
1011
+ setOauth2ClientSecretScope(secretScopeId ?? credentialTargetScope);
1012
+ setOauth2AuthState(null);
1013
+ },
1014
+ secrets: initialCredentialSecrets,
1015
+ sourceName: identity.name,
1016
+ secretLabel: "Client Secret",
1017
+ targetScope: oauth2ClientSecretScope ?? credentialTargetScope,
1018
+ credentialScopeOptions: initialCredentialScopeOptions,
1019
+ onCreatedScope: setOauth2ClientSecretScope
1020
+ }
1021
+ )
1022
+ ] }),
1023
+ /* @__PURE__ */ jsx2(
1024
+ CredentialScopeDropdown,
1025
+ {
1026
+ value: credentialTargetScope,
1027
+ options: bindingScopeOptions,
1028
+ onChange: setInitialCredentialScope
1029
+ }
1030
+ )
1031
+ ] })
1032
+ ] }),
1033
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5", children: [
1034
+ /* @__PURE__ */ jsx2(FieldLabel, { className: "text-[11px]", children: "Scopes" }),
1035
+ /* @__PURE__ */ jsx2("div", { className: "space-y-1 rounded-md border border-border/50 bg-background/50 p-2", children: Object.keys(selectedOAuth2Preset.scopes).length === 0 ? /* @__PURE__ */ jsx2("div", { className: "text-[11px] italic text-muted-foreground", children: "No scopes declared by the spec." }) : Object.entries(selectedOAuth2Preset.scopes).map(([scope, description]) => /* @__PURE__ */ jsxs2(Label, { className: "flex items-start gap-2 cursor-pointer py-1", children: [
1036
+ /* @__PURE__ */ jsx2(
1037
+ Checkbox,
1038
+ {
1039
+ checked: oauth2SelectedScopes.has(scope),
1040
+ onCheckedChange: () => toggleOAuth2Scope(scope)
1041
+ }
1042
+ ),
1043
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
1044
+ /* @__PURE__ */ jsx2("div", { className: "font-mono text-[11px] text-foreground", children: scope }),
1045
+ description && /* @__PURE__ */ jsx2("div", { className: "text-[10px] text-muted-foreground", children: description })
1046
+ ] })
1047
+ ] }, scope)) })
1048
+ ] }),
1049
+ oauth2Auth ? /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between rounded-md border border-green-500/30 bg-green-500/5 px-3 py-2", children: [
1050
+ /* @__PURE__ */ jsxs2("div", { className: "text-[11px] text-green-700 dark:text-green-400", children: [
1051
+ "Connected \xB7 ",
1052
+ oauth2SelectedScopes.size,
1053
+ " scope",
1054
+ oauth2SelectedScopes.size === 1 ? "" : "s",
1055
+ " granted"
1056
+ ] }),
1057
+ /* @__PURE__ */ jsx2(Button, { variant: "ghost", size: "sm", onClick: () => setOauth2AuthState(null), children: "Disconnect" })
1058
+ ] }) : oauth2Busy ? /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
1059
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 items-center gap-2 rounded-md border border-border/60 bg-background/50 px-3 py-2 text-[11px] text-muted-foreground", children: [
1060
+ /* @__PURE__ */ jsx2(Spinner, { className: "size-3.5" }),
1061
+ "Waiting for OAuth\u2026 complete the flow in the popup, or cancel to retry."
1062
+ ] }),
1063
+ /* @__PURE__ */ jsx2(Button, { variant: "ghost", size: "sm", onClick: handleCancelOAuth2, children: "Cancel" }),
1064
+ /* @__PURE__ */ jsx2(Button, { variant: "secondary", size: "sm", onClick: handleConnectOAuth2, children: "Retry" })
1065
+ ] }) : /* @__PURE__ */ jsx2("div", { className: "flex flex-col gap-1.5", children: /* @__PURE__ */ jsxs2("div", { className: "grid gap-2 md:grid-cols-2", children: [
1066
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5", children: [
1067
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5", children: [
1068
+ /* @__PURE__ */ jsx2(FieldLabel, { className: "text-[11px]", children: "OAuth sign-in" }),
1069
+ /* @__PURE__ */ jsx2(HelpTooltip, { label: "OAuth sign-in", children: "Start the provider OAuth flow." })
1070
+ ] }),
1071
+ /* @__PURE__ */ jsx2(
1072
+ Button,
1073
+ {
1074
+ variant: "secondary",
1075
+ onClick: handleConnectOAuth2,
1076
+ disabled: !canConnectOAuth2,
1077
+ className: canConnectOAuth2 ? "w-full border border-green-500/30 bg-green-600 text-white hover:bg-green-700 focus-visible:ring-green-500/30 dark:bg-green-500 dark:text-white dark:hover:bg-green-600" : "w-full",
1078
+ children: "Connect via OAuth"
1079
+ }
1080
+ )
1081
+ ] }),
1082
+ /* @__PURE__ */ jsx2(
1083
+ CredentialScopeDropdown,
1084
+ {
1085
+ value: oauthTokenTargetScope,
1086
+ options: credentialScopeOptions,
1087
+ onChange: (targetScope) => {
1088
+ setOAuthTokenTargetScope(targetScope);
1089
+ setOauth2AuthState(null);
1090
+ },
1091
+ label: "Token saved to",
1092
+ help: "Choose who can use the signed-in OAuth token."
1093
+ }
1094
+ )
1095
+ ] }) }),
1096
+ oauth2Error && /* @__PURE__ */ jsx2("div", { className: "rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx2("p", { className: "text-[11px] text-destructive", children: oauth2Error }) })
1097
+ ] }) })
1098
+ ] }),
1099
+ addError && /* @__PURE__ */ jsx2("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx2("p", { className: "text-[12px] text-destructive", children: addError }) })
1100
+ ] }),
1101
+ /* @__PURE__ */ jsxs2(FloatActions, { children: [
1102
+ /* @__PURE__ */ jsx2(Button, { variant: "ghost", onClick: props.onCancel, disabled: adding, children: "Cancel" }),
1103
+ preview && /* @__PURE__ */ jsxs2(Button, { onClick: handleAdd, disabled: !canAdd || adding, children: [
1104
+ adding && /* @__PURE__ */ jsx2(Spinner, { className: "size-3.5" }),
1105
+ adding ? "Adding\u2026" : willAddWithoutInitialCredentials ? "Add without credentials" : "Add source"
1106
+ ] })
1107
+ ] })
1108
+ ] });
1109
+ }
1110
+
1111
+ export {
1112
+ OpenApiSourceDetailsFields,
1113
+ OPENAPI_OAUTH_POPUP_NAME,
1114
+ OPENAPI_OAUTH_CALLBACK_PATH,
1115
+ openApiOAuthConnectionId,
1116
+ resolveOAuthUrl,
1117
+ inferOAuthIssuerUrl,
1118
+ AddOpenApiSource
1119
+ };
1120
+ //# sourceMappingURL=chunk-TGDT6QCH.js.map