@executor-js/plugin-openapi 0.0.2 → 0.1.0

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.
@@ -561,934 +561,531 @@ var extract = Effect2.fn("OpenApi.extract")(function* (doc) {
561
561
  });
562
562
  });
563
563
 
564
- // src/sdk/invoke.ts
565
- import { Effect as Effect3, Layer, Option as Option3 } from "effect";
566
- import { HttpClient as HttpClient2, HttpClientRequest as HttpClientRequest2 } from "effect/unstable/http";
567
- var CONTAINER_KEYS = {
568
- path: ["path", "pathParams", "params"],
569
- query: ["query", "queryParams", "params"],
570
- header: ["headers", "header"],
571
- cookie: ["cookies", "cookie"]
572
- };
573
- var readParamValue = (args, param) => {
574
- const direct = args[param.name];
575
- if (direct !== void 0) return direct;
576
- for (const key of CONTAINER_KEYS[param.location] ?? []) {
577
- const container = args[key];
578
- if (typeof container === "object" && container !== null && !Array.isArray(container)) {
579
- const nested = container[param.name];
580
- if (nested !== void 0) return nested;
581
- }
582
- }
583
- return void 0;
564
+ // src/sdk/preview.ts
565
+ import { Effect as Effect3, Option as Option3 } from "effect";
566
+ import { Schema as Schema3 } from "effect";
567
+ var OAuth2Scopes = Schema3.Record(Schema3.String, Schema3.String);
568
+ var OAuth2AuthorizationCodeFlow = class extends Schema3.Class(
569
+ "OAuth2AuthorizationCodeFlow"
570
+ )({
571
+ authorizationUrl: Schema3.String,
572
+ tokenUrl: Schema3.String,
573
+ refreshUrl: Schema3.OptionFromOptional(Schema3.String),
574
+ scopes: OAuth2Scopes
575
+ }) {
584
576
  };
585
- var resolvePath = Effect3.fn("OpenApi.resolvePath")(function* (pathTemplate, args, parameters) {
586
- let resolved = pathTemplate;
587
- for (const param of parameters) {
588
- if (param.location !== "path") continue;
589
- const value = readParamValue(args, param);
590
- if (value === void 0 || value === null) {
591
- if (param.required) {
592
- return yield* new OpenApiInvocationError({
593
- message: `Missing required path parameter: ${param.name}`,
594
- statusCode: Option3.none()
595
- });
596
- }
597
- continue;
598
- }
599
- resolved = resolved.replaceAll(`{${param.name}}`, encodeURIComponent(String(value)));
600
- }
601
- const remaining = [...resolved.matchAll(/\{([^{}]+)\}/g)].map((m) => m[1]).filter((v) => typeof v === "string");
602
- for (const name of remaining) {
603
- const value = args[name];
604
- if (value !== void 0 && value !== null) {
605
- resolved = resolved.replaceAll(`{${name}}`, encodeURIComponent(String(value)));
606
- }
607
- }
608
- const unresolved = [...resolved.matchAll(/\{([^{}]+)\}/g)].map((m) => m[1]).filter((v) => typeof v === "string");
609
- if (unresolved.length > 0) {
610
- return yield* new OpenApiInvocationError({
611
- message: `Unresolved path parameters: ${[...new Set(unresolved)].join(", ")}`,
612
- statusCode: Option3.none()
613
- });
614
- }
615
- return resolved;
616
- });
617
- var resolveHeaders = (headers, secrets) => {
618
- const entries = Object.entries(headers);
619
- const secretCount = entries.reduce(
620
- (acc, [, value]) => typeof value === "string" ? acc : acc + 1,
621
- 0
622
- );
623
- return Effect3.gen(function* () {
624
- const values = yield* Effect3.all(
625
- entries.map(
626
- ([name, value]) => typeof value === "string" ? Effect3.succeed({ name, value }) : secrets.get(value.secretId).pipe(
627
- Effect3.mapError(
628
- (err) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? new OpenApiInvocationError({
629
- message: `Failed to resolve secret "${value.secretId}" for header "${name}"`,
630
- statusCode: Option3.none()
631
- }) : err
632
- ),
633
- Effect3.flatMap(
634
- (secret) => secret === null ? Effect3.fail(
635
- new OpenApiInvocationError({
636
- message: `Failed to resolve secret "${value.secretId}" for header "${name}"`,
637
- statusCode: Option3.none()
638
- })
639
- ) : Effect3.succeed({
640
- name,
641
- value: value.prefix ? `${value.prefix}${secret}` : secret
642
- })
643
- )
644
- )
645
- ),
646
- { concurrency: "unbounded" }
647
- );
648
- const resolved = {};
649
- for (const { name, value } of values) resolved[name] = value;
650
- return resolved;
651
- }).pipe(
652
- Effect3.withSpan("plugin.openapi.secret.resolve", {
653
- attributes: {
654
- "plugin.openapi.headers.total": entries.length,
655
- "plugin.openapi.headers.secret_count": secretCount
656
- }
657
- })
658
- );
577
+ var OAuth2ClientCredentialsFlow = class extends Schema3.Class(
578
+ "OAuth2ClientCredentialsFlow"
579
+ )({
580
+ tokenUrl: Schema3.String,
581
+ refreshUrl: Schema3.OptionFromOptional(Schema3.String),
582
+ scopes: OAuth2Scopes
583
+ }) {
659
584
  };
660
- var applyHeaders = (request, headers) => {
661
- let req = request;
662
- for (const [name, value] of Object.entries(headers)) {
663
- req = HttpClientRequest2.setHeader(req, name, value);
664
- }
665
- return req;
585
+ var OAuth2Flows = class extends Schema3.Class("OAuth2Flows")({
586
+ authorizationCode: Schema3.OptionFromOptional(OAuth2AuthorizationCodeFlow),
587
+ clientCredentials: Schema3.OptionFromOptional(OAuth2ClientCredentialsFlow)
588
+ }) {
666
589
  };
667
- var normalizeContentType = (ct) => ct?.split(";")[0]?.trim().toLowerCase() ?? "";
668
- var isJsonContentType = (ct) => {
669
- const normalized = normalizeContentType(ct);
670
- if (!normalized) return false;
671
- return normalized === "application/json" || normalized.includes("+json") || normalized.includes("json");
590
+ var SecurityScheme = class extends Schema3.Class("SecurityScheme")({
591
+ /** Key name in components.securitySchemes (e.g. "api_token") */
592
+ name: Schema3.String,
593
+ /** OpenAPI security scheme type */
594
+ type: Schema3.Literals(["http", "apiKey", "oauth2", "openIdConnect"]),
595
+ /** For type: "http" — e.g. "bearer", "basic" */
596
+ scheme: Schema3.OptionFromOptional(Schema3.String),
597
+ /** For type: "http" with scheme "bearer" — e.g. "JWT" */
598
+ bearerFormat: Schema3.OptionFromOptional(Schema3.String),
599
+ /** For type: "apiKey" — where the key goes */
600
+ in: Schema3.OptionFromOptional(Schema3.Literals(["header", "query", "cookie"])),
601
+ /** For type: "apiKey" — the header/query/cookie name */
602
+ headerName: Schema3.OptionFromOptional(Schema3.String),
603
+ description: Schema3.OptionFromOptional(Schema3.String),
604
+ /** For type: "oauth2" — declared flows (authorizationCode / clientCredentials only; implicit and password are deprecated). */
605
+ flows: Schema3.OptionFromOptional(OAuth2Flows),
606
+ /** For type: "openIdConnect" — the discovery URL. */
607
+ openIdConnectUrl: Schema3.OptionFromOptional(Schema3.String)
608
+ }) {
672
609
  };
673
- var isFormUrlEncoded = (ct) => normalizeContentType(ct) === "application/x-www-form-urlencoded";
674
- var isMultipartFormData = (ct) => normalizeContentType(ct).startsWith("multipart/form-data");
675
- var isXmlContentType = (ct) => {
676
- const normalized = normalizeContentType(ct);
677
- if (!normalized) return false;
678
- return normalized === "application/xml" || normalized === "text/xml" || normalized.endsWith("+xml");
610
+ var AuthStrategy = class extends Schema3.Class("AuthStrategy")({
611
+ /** The security schemes required together for this strategy */
612
+ schemes: Schema3.Array(Schema3.String)
613
+ }) {
679
614
  };
680
- var isTextContentType = (ct) => normalizeContentType(ct).startsWith("text/");
681
- var isOctetStream = (ct) => normalizeContentType(ct) === "application/octet-stream";
682
- var toUint8Array = (value) => {
683
- if (value instanceof Uint8Array) return value;
684
- if (value instanceof ArrayBuffer) return new Uint8Array(value);
685
- if (ArrayBuffer.isView(value)) {
686
- const view = value;
687
- return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
688
- }
689
- if (Array.isArray(value) && value.every((v) => typeof v === "number")) {
690
- return new Uint8Array(value);
691
- }
692
- return null;
615
+ var HeaderPreset = class extends Schema3.Class("HeaderPreset")({
616
+ /** Human-readable label for the UI (e.g. "Bearer Token", "API Key + Email") */
617
+ label: Schema3.String,
618
+ /** Headers this strategy needs. Value is null when the user must provide it. */
619
+ headers: Schema3.Record(Schema3.String, Schema3.NullOr(Schema3.String)),
620
+ /** Which headers should be stored as secrets */
621
+ secretHeaders: Schema3.Array(Schema3.String)
622
+ }) {
693
623
  };
694
- var toArrayBuffer = (bytes) => {
695
- const copy = new ArrayBuffer(bytes.byteLength);
696
- new Uint8Array(copy).set(bytes);
697
- return copy;
624
+ var OAuth2Preset = class extends Schema3.Class("OAuth2Preset")({
625
+ /** Human-readable label for the UI (e.g. "OAuth2 (Authorization Code) — oauth_app") */
626
+ label: Schema3.String,
627
+ /** The source security scheme this preset came from (components.securitySchemes key). */
628
+ securitySchemeName: Schema3.String,
629
+ /** Which OAuth2 flow this preset uses. */
630
+ flow: Schema3.Literals(["authorizationCode", "clientCredentials"]),
631
+ /** For authorizationCode: user-agent redirect URL (from the spec). */
632
+ authorizationUrl: Schema3.OptionFromOptional(Schema3.String),
633
+ /** Token endpoint to exchange the code / refresh. */
634
+ tokenUrl: Schema3.String,
635
+ /** Optional refresh endpoint if the spec declares one separately. */
636
+ refreshUrl: Schema3.OptionFromOptional(Schema3.String),
637
+ /** Declared scopes for this flow: `{ scope: description }`. */
638
+ scopes: Schema3.Record(Schema3.String, Schema3.String)
639
+ }) {
698
640
  };
699
- var DEFAULT_FORM_STYLE = {
700
- style: "form",
701
- explode: true,
702
- allowReserved: false
641
+ var PreviewOperation = class extends Schema3.Class("PreviewOperation")({
642
+ operationId: Schema3.String,
643
+ method: HttpMethod,
644
+ path: Schema3.String,
645
+ summary: Schema3.OptionFromOptional(Schema3.String),
646
+ tags: Schema3.Array(Schema3.String),
647
+ deprecated: Schema3.Boolean
648
+ }) {
703
649
  };
704
- var resolveStyleExplode = (e) => {
705
- if (!e) return DEFAULT_FORM_STYLE;
706
- return {
707
- style: Option3.getOrElse(e.style, () => DEFAULT_FORM_STYLE.style),
708
- explode: Option3.getOrElse(e.explode, () => DEFAULT_FORM_STYLE.explode),
709
- allowReserved: Option3.getOrElse(e.allowReserved, () => DEFAULT_FORM_STYLE.allowReserved)
710
- };
650
+ var SpecPreview = class extends Schema3.Class("SpecPreview")({
651
+ title: Schema3.OptionFromOptional(Schema3.String),
652
+ version: Schema3.OptionFromOptional(Schema3.String),
653
+ /** Reuses ServerInfo from extraction */
654
+ servers: Schema3.Array(ServerInfo),
655
+ operationCount: Schema3.Number,
656
+ /** Lightweight operation list for the add-source UI */
657
+ operations: Schema3.Array(PreviewOperation),
658
+ tags: Schema3.Array(Schema3.String),
659
+ securitySchemes: Schema3.Array(SecurityScheme),
660
+ /** Valid auth strategies (each is a set of schemes used together) */
661
+ authStrategies: Schema3.Array(AuthStrategy),
662
+ /** Pre-built header presets derived from auth strategies */
663
+ headerPresets: Schema3.Array(HeaderPreset),
664
+ /** OAuth2 presets — one per (oauth2 scheme × supported flow) combination */
665
+ oauth2Presets: Schema3.Array(OAuth2Preset)
666
+ }) {
711
667
  };
712
- var RESERVED_UNENCODED_RE = /[A-Za-z0-9\-._~:/?#[\]@!$&'()*+,;=]/;
713
- var encodeFormValue = (v, allowReserved) => {
714
- const raw = typeof v === "object" && v !== null ? JSON.stringify(v) : String(v);
715
- if (!allowReserved) return encodeURIComponent(raw);
716
- let out = "";
717
- for (const ch of raw) {
718
- out += RESERVED_UNENCODED_RE.test(ch) ? ch : encodeURIComponent(ch);
668
+ var stringRecord = (value) => {
669
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
670
+ const out = {};
671
+ for (const [k, v] of Object.entries(value)) {
672
+ if (typeof v === "string") out[k] = v;
719
673
  }
720
674
  return out;
721
675
  };
722
- var serializeFormUrlEncoded = (value, encoding) => {
723
- const parts = [];
724
- for (const [key, raw] of Object.entries(value)) {
725
- if (raw === void 0 || raw === null) continue;
726
- const { style, explode, allowReserved } = resolveStyleExplode(encoding?.[key]);
727
- const encKey = encodeURIComponent(key);
728
- if (Array.isArray(raw)) {
729
- if (explode) {
730
- for (const v of raw) {
731
- parts.push(`${encKey}=${encodeFormValue(v, allowReserved)}`);
732
- }
733
- } else {
734
- const sep = style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ",";
735
- parts.push(
736
- `${encKey}=${encodeFormValue(
737
- raw.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)).join(sep),
738
- allowReserved
739
- )}`
740
- );
741
- }
742
- continue;
743
- }
744
- if (typeof raw === "object") {
745
- const entries = Object.entries(raw).filter(
746
- ([, v]) => v !== void 0 && v !== null
747
- );
748
- if (style === "deepObject") {
749
- for (const [subkey, subval] of entries) {
750
- parts.push(
751
- `${encodeURIComponent(`${key}[${subkey}]`)}=${encodeFormValue(
752
- subval,
753
- allowReserved
754
- )}`
755
- );
756
- }
757
- } else if (explode) {
758
- for (const [subkey, subval] of entries) {
759
- parts.push(
760
- `${encodeURIComponent(subkey)}=${encodeFormValue(subval, allowReserved)}`
761
- );
762
- }
763
- } else {
764
- const flat = entries.flatMap(([k, v]) => [
765
- k,
766
- typeof v === "object" ? JSON.stringify(v) : String(v)
767
- ]);
768
- parts.push(`${encKey}=${encodeFormValue(flat.join(","), allowReserved)}`);
769
- }
770
- continue;
676
+ var extractFlows = (rawFlows) => {
677
+ if (!rawFlows || typeof rawFlows !== "object") return Option3.none();
678
+ const flows = rawFlows;
679
+ const parseFlow = (key) => flows[key];
680
+ let authorizationCode = Option3.none();
681
+ const authCodeRaw = parseFlow("authorizationCode");
682
+ if (authCodeRaw && typeof authCodeRaw === "object") {
683
+ const f = authCodeRaw;
684
+ const authUrl = typeof f.authorizationUrl === "string" ? f.authorizationUrl : null;
685
+ const tokenUrl = typeof f.tokenUrl === "string" ? f.tokenUrl : null;
686
+ if (authUrl && tokenUrl) {
687
+ authorizationCode = Option3.some(
688
+ new OAuth2AuthorizationCodeFlow({
689
+ authorizationUrl: authUrl,
690
+ tokenUrl,
691
+ refreshUrl: Option3.fromNullishOr(
692
+ typeof f.refreshUrl === "string" ? f.refreshUrl : void 0
693
+ ),
694
+ scopes: stringRecord(f.scopes)
695
+ })
696
+ );
771
697
  }
772
- parts.push(`${encKey}=${encodeFormValue(raw, allowReserved)}`);
773
698
  }
774
- return parts.join("&");
775
- };
776
- var coerceFormDataRecord = (value, encoding) => {
777
- const out = {};
778
- for (const [key, raw] of Object.entries(value)) {
779
- if (raw === void 0 || raw === null) continue;
780
- const partType = encoding?.[key] ? Option3.getOrUndefined(encoding[key].contentType) : void 0;
781
- if (partType) {
782
- const isJson = partType.startsWith("application/json") || partType.includes("+json");
783
- const serialized = typeof raw === "string" ? raw : isJson ? JSON.stringify(raw) : typeof raw === "object" ? JSON.stringify(raw) : String(raw);
784
- out[key] = new Blob([serialized], { type: partType });
785
- continue;
786
- }
787
- if (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean" || raw instanceof Blob || typeof File !== "undefined" && raw instanceof File) {
788
- out[key] = raw;
789
- continue;
790
- }
791
- if (Array.isArray(raw)) {
792
- out[key] = raw.map(
793
- (v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v instanceof Blob || typeof File !== "undefined" && v instanceof File ? v : JSON.stringify(v)
699
+ let clientCredentials = Option3.none();
700
+ const ccRaw = parseFlow("clientCredentials");
701
+ if (ccRaw && typeof ccRaw === "object") {
702
+ const f = ccRaw;
703
+ const tokenUrl = typeof f.tokenUrl === "string" ? f.tokenUrl : null;
704
+ if (tokenUrl) {
705
+ clientCredentials = Option3.some(
706
+ new OAuth2ClientCredentialsFlow({
707
+ tokenUrl,
708
+ refreshUrl: Option3.fromNullishOr(
709
+ typeof f.refreshUrl === "string" ? f.refreshUrl : void 0
710
+ ),
711
+ scopes: stringRecord(f.scopes)
712
+ })
794
713
  );
795
- continue;
796
- }
797
- const bytes = toUint8Array(raw);
798
- if (bytes) {
799
- out[key] = new Blob([toArrayBuffer(bytes)]);
800
- continue;
801
714
  }
802
- out[key] = JSON.stringify(raw);
803
715
  }
804
- return out;
716
+ if (Option3.isNone(authorizationCode) && Option3.isNone(clientCredentials)) {
717
+ return Option3.none();
718
+ }
719
+ return Option3.some(new OAuth2Flows({ authorizationCode, clientCredentials }));
805
720
  };
806
- var applyRequestBody = (request, contentType, bodyValue, encoding) => {
807
- if (isJsonContentType(contentType)) {
808
- if (typeof bodyValue === "string") {
809
- return HttpClientRequest2.bodyText(request, bodyValue, contentType);
721
+ var extractSecuritySchemes = (rawSchemes, resolver) => Object.entries(rawSchemes).flatMap(([name, schemeOrRef]) => {
722
+ if (!schemeOrRef || typeof schemeOrRef !== "object") return [];
723
+ const resolved = resolver.resolve(
724
+ schemeOrRef
725
+ );
726
+ if (!resolved || typeof resolved !== "object") return [];
727
+ const scheme = resolved;
728
+ const type = scheme.type;
729
+ if (!["http", "apiKey", "oauth2", "openIdConnect"].includes(type)) return [];
730
+ return [
731
+ new SecurityScheme({
732
+ name,
733
+ type,
734
+ scheme: Option3.fromNullishOr(scheme.scheme),
735
+ bearerFormat: Option3.fromNullishOr(scheme.bearerFormat),
736
+ in: Option3.fromNullishOr(scheme.in),
737
+ headerName: Option3.fromNullishOr(scheme.name),
738
+ description: Option3.fromNullishOr(scheme.description),
739
+ flows: type === "oauth2" ? extractFlows(scheme.flows) : Option3.none(),
740
+ openIdConnectUrl: Option3.fromNullishOr(
741
+ scheme.openIdConnectUrl
742
+ )
743
+ })
744
+ ];
745
+ });
746
+ var buildHeaderPresets = (schemes, strategies) => {
747
+ const schemeMap = new Map(schemes.map((s) => [s.name, s]));
748
+ return strategies.flatMap((strategy) => {
749
+ const resolved = strategy.schemes.map((name) => schemeMap.get(name)).filter((s) => s !== void 0);
750
+ if (resolved.length === 0) return [];
751
+ const headers = {};
752
+ const secretHeaders = [];
753
+ const labelParts = [];
754
+ for (const scheme of resolved) {
755
+ if (scheme.type === "http" && Option3.getOrElse(scheme.scheme, () => "") === "bearer") {
756
+ headers["Authorization"] = null;
757
+ secretHeaders.push("Authorization");
758
+ labelParts.push("Bearer Token");
759
+ } else if (scheme.type === "http" && Option3.getOrElse(scheme.scheme, () => "") === "basic") {
760
+ headers["Authorization"] = null;
761
+ secretHeaders.push("Authorization");
762
+ labelParts.push("Basic Auth");
763
+ } else if (scheme.type === "apiKey" && Option3.getOrElse(scheme.in, () => "") === "header") {
764
+ const headerName = Option3.getOrElse(scheme.headerName, () => scheme.name);
765
+ headers[headerName] = null;
766
+ secretHeaders.push(headerName);
767
+ labelParts.push(scheme.name);
768
+ } else if (scheme.type === "apiKey") {
769
+ labelParts.push(`${scheme.name} (${Option3.getOrElse(scheme.in, () => "unknown")})`);
770
+ } else {
771
+ labelParts.push(scheme.name);
772
+ }
810
773
  }
811
- return HttpClientRequest2.bodyJsonUnsafe(request, bodyValue);
812
- }
813
- if (isFormUrlEncoded(contentType)) {
814
- if (typeof bodyValue === "string") {
815
- return HttpClientRequest2.bodyText(request, bodyValue, contentType);
774
+ if (Object.keys(headers).length === 0 && resolved.length > 0) {
775
+ return [new HeaderPreset({ label: labelParts.join(" + "), headers: {}, secretHeaders: [] })];
816
776
  }
817
- if (typeof bodyValue === "object" && bodyValue !== null && !Array.isArray(bodyValue)) {
818
- const serialized = serializeFormUrlEncoded(
819
- bodyValue,
820
- encoding
777
+ return [new HeaderPreset({ label: labelParts.join(" + "), headers, secretHeaders })];
778
+ });
779
+ };
780
+ var buildOAuth2Presets = (schemes) => {
781
+ const presets = [];
782
+ for (const scheme of schemes) {
783
+ if (scheme.type !== "oauth2") continue;
784
+ if (Option3.isNone(scheme.flows)) continue;
785
+ const flows = scheme.flows.value;
786
+ if (Option3.isSome(flows.authorizationCode)) {
787
+ const flow = flows.authorizationCode.value;
788
+ presets.push(
789
+ new OAuth2Preset({
790
+ label: `OAuth2 Authorization Code \xB7 ${scheme.name}`,
791
+ securitySchemeName: scheme.name,
792
+ flow: "authorizationCode",
793
+ authorizationUrl: Option3.some(flow.authorizationUrl),
794
+ tokenUrl: flow.tokenUrl,
795
+ refreshUrl: flow.refreshUrl,
796
+ scopes: flow.scopes
797
+ })
821
798
  );
822
- return HttpClientRequest2.bodyText(request, serialized, contentType);
823
- }
824
- return HttpClientRequest2.bodyUrlParams(
825
- request,
826
- bodyValue
827
- );
828
- }
829
- if (isMultipartFormData(contentType)) {
830
- if (bodyValue instanceof FormData) {
831
- return HttpClientRequest2.bodyFormData(request, bodyValue);
832
799
  }
833
- if (typeof bodyValue === "object" && bodyValue !== null) {
834
- return HttpClientRequest2.bodyFormDataRecord(
835
- request,
836
- coerceFormDataRecord(bodyValue, encoding)
800
+ if (Option3.isSome(flows.clientCredentials)) {
801
+ const flow = flows.clientCredentials.value;
802
+ presets.push(
803
+ new OAuth2Preset({
804
+ label: `OAuth2 Client Credentials \xB7 ${scheme.name}`,
805
+ securitySchemeName: scheme.name,
806
+ flow: "clientCredentials",
807
+ authorizationUrl: Option3.none(),
808
+ tokenUrl: flow.tokenUrl,
809
+ refreshUrl: flow.refreshUrl,
810
+ scopes: flow.scopes
811
+ })
837
812
  );
838
813
  }
839
- return HttpClientRequest2.bodyText(request, String(bodyValue), contentType);
840
- }
841
- if (isOctetStream(contentType)) {
842
- const bytes2 = toUint8Array(bodyValue);
843
- if (bytes2) return HttpClientRequest2.bodyUint8Array(request, bytes2, contentType);
844
- if (typeof bodyValue === "string") {
845
- return HttpClientRequest2.bodyText(request, bodyValue, contentType);
846
- }
847
- return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
848
- }
849
- if (isXmlContentType(contentType) || isTextContentType(contentType)) {
850
- if (typeof bodyValue === "string") {
851
- return HttpClientRequest2.bodyText(request, bodyValue, contentType);
852
- }
853
- const bytes2 = toUint8Array(bodyValue);
854
- if (bytes2) return HttpClientRequest2.bodyUint8Array(request, bytes2, contentType);
855
- return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
856
814
  }
857
- if (typeof bodyValue === "string") {
858
- return HttpClientRequest2.bodyText(request, bodyValue, contentType);
859
- }
860
- const bytes = toUint8Array(bodyValue);
861
- if (bytes) return HttpClientRequest2.bodyUint8Array(request, bytes, contentType);
862
- return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
815
+ return presets;
863
816
  };
864
- var invoke = Effect3.fn("OpenApi.invoke")(function* (operation, args, resolvedHeaders, sourceQueryParams = {}) {
865
- const client = yield* HttpClient2.HttpClient;
866
- yield* Effect3.annotateCurrentSpan({
867
- "http.method": operation.method.toUpperCase(),
868
- "http.route": operation.pathTemplate,
869
- "plugin.openapi.method": operation.method.toUpperCase(),
870
- "plugin.openapi.path_template": operation.pathTemplate,
871
- "plugin.openapi.headers.resolved_count": Object.keys(resolvedHeaders).length
872
- });
873
- const resolvedPath = yield* resolvePath(operation.pathTemplate, args, operation.parameters);
874
- const path = resolvedPath.startsWith("/") ? resolvedPath : `/${resolvedPath}`;
875
- let request = HttpClientRequest2.make(operation.method.toUpperCase())(path);
876
- for (const [name, value] of Object.entries(sourceQueryParams)) {
877
- request = HttpClientRequest2.setUrlParam(request, name, value);
878
- }
879
- for (const param of operation.parameters) {
880
- if (param.location !== "query") continue;
881
- const value = readParamValue(args, param);
882
- if (value === void 0 || value === null) continue;
883
- request = HttpClientRequest2.setUrlParam(request, param.name, String(value));
884
- }
885
- for (const param of operation.parameters) {
886
- if (param.location !== "header") continue;
887
- const value = readParamValue(args, param);
888
- if (value === void 0 || value === null) continue;
889
- request = HttpClientRequest2.setHeader(request, param.name, String(value));
890
- }
891
- if (Option3.isSome(operation.requestBody)) {
892
- const rb = operation.requestBody.value;
893
- const bodyValue = args.body ?? args.input;
894
- if (bodyValue !== void 0) {
895
- const contentsOpt = Option3.getOrUndefined(rb.contents);
896
- const requestedCt = typeof args.contentType === "string" ? args.contentType : void 0;
897
- const selected = contentsOpt && requestedCt ? contentsOpt.find((c) => c.contentType === requestedCt) : void 0;
898
- const chosenCt = selected?.contentType ?? rb.contentType;
899
- const chosenEncoding = selected ? Option3.getOrUndefined(selected.encoding) : contentsOpt && contentsOpt[0] ? Option3.getOrUndefined(contentsOpt[0].encoding) : void 0;
900
- request = applyRequestBody(request, chosenCt, bodyValue, chosenEncoding);
901
- }
817
+ var collectTags = (result) => {
818
+ const tagSet = /* @__PURE__ */ new Set();
819
+ for (const op of result.operations) {
820
+ for (const tag of op.tags) tagSet.add(tag);
902
821
  }
903
- request = applyHeaders(request, resolvedHeaders);
904
- const response = yield* client.execute(request).pipe(
905
- Effect3.mapError(
906
- (err) => new OpenApiInvocationError({
907
- message: `HTTP request failed: ${err.message}`,
908
- statusCode: Option3.none(),
909
- cause: err
910
- })
911
- )
822
+ return [...tagSet].sort();
823
+ };
824
+ var previewSpec = Effect3.fn("OpenApi.previewSpec")(function* (input) {
825
+ const specText = yield* resolveSpecText(input);
826
+ const doc = yield* parse(specText);
827
+ const result = yield* extract(doc);
828
+ const resolver = new DocResolver(doc);
829
+ const securitySchemes = extractSecuritySchemes(
830
+ doc.components?.securitySchemes ?? {},
831
+ resolver
912
832
  );
913
- const status = response.status;
914
- yield* Effect3.annotateCurrentSpan({
915
- "http.status_code": status
916
- });
917
- const responseHeaders = { ...response.headers };
918
- const contentType = response.headers["content-type"] ?? null;
919
- const mapBodyError = Effect3.mapError(
920
- (err) => new OpenApiInvocationError({
921
- message: `Failed to read response body: ${err.message ?? String(err)}`,
922
- statusCode: Option3.some(status),
923
- cause: err
924
- })
833
+ const rawSecurity = doc.security ?? [];
834
+ const declaredStrategies = rawSecurity.map(
835
+ (entry) => new AuthStrategy({ schemes: Object.keys(entry) })
925
836
  );
926
- const responseBody = status === 204 ? null : isJsonContentType(contentType) ? yield* response.json.pipe(
927
- Effect3.catch(() => response.text),
928
- mapBodyError
929
- ) : yield* response.text.pipe(mapBodyError);
930
- const ok = status >= 200 && status < 300;
931
- return new InvocationResult({
932
- status,
933
- headers: responseHeaders,
934
- data: ok ? responseBody : null,
935
- error: ok ? null : responseBody
837
+ const authStrategies = declaredStrategies.length > 0 ? declaredStrategies : securitySchemes.map((scheme) => new AuthStrategy({ schemes: [scheme.name] }));
838
+ return new SpecPreview({
839
+ title: result.title,
840
+ version: result.version,
841
+ servers: result.servers,
842
+ operationCount: result.operations.length,
843
+ operations: result.operations.map(
844
+ (op) => new PreviewOperation({
845
+ operationId: op.operationId,
846
+ method: op.method,
847
+ path: op.pathTemplate,
848
+ summary: op.summary,
849
+ tags: op.tags,
850
+ deprecated: op.deprecated
851
+ })
852
+ ),
853
+ tags: collectTags(result),
854
+ securitySchemes,
855
+ authStrategies,
856
+ headerPresets: buildHeaderPresets(securitySchemes, authStrategies),
857
+ oauth2Presets: buildOAuth2Presets(securitySchemes)
936
858
  });
937
859
  });
938
- var invokeWithLayer = (operation, args, baseUrl, resolvedHeaders, sourceQueryParams, httpClientLayer) => {
939
- const clientWithBaseUrl = baseUrl ? Layer.effect(
940
- HttpClient2.HttpClient,
941
- Effect3.map(
942
- Effect3.service(HttpClient2.HttpClient),
943
- HttpClient2.mapRequest(HttpClientRequest2.prependUrl(baseUrl))
944
- )
945
- ).pipe(Layer.provide(httpClientLayer)) : httpClientLayer;
946
- return invoke(operation, args, resolvedHeaders, sourceQueryParams).pipe(
947
- Effect3.provide(clientWithBaseUrl),
948
- Effect3.withSpan("plugin.openapi.invoke", {
949
- attributes: {
950
- "plugin.openapi.method": operation.method.toUpperCase(),
951
- "plugin.openapi.path_template": operation.pathTemplate,
952
- "plugin.openapi.base_url": baseUrl
953
- }
954
- })
955
- );
956
- };
957
- var REQUIRE_APPROVAL = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
958
- var annotationsForOperation = (method, pathTemplate) => {
959
- const m = method.toLowerCase();
960
- if (!REQUIRE_APPROVAL.has(m)) return {};
961
- return {
962
- requiresApproval: true,
963
- approvalDescription: `${method.toUpperCase()} ${pathTemplate}`
964
- };
965
- };
966
860
 
967
- // src/sdk/preview.ts
968
- import { Effect as Effect4, Option as Option4 } from "effect";
969
- import { Schema as Schema3 } from "effect";
970
- var OAuth2Scopes = Schema3.Record(Schema3.String, Schema3.String);
971
- var OAuth2AuthorizationCodeFlow = class extends Schema3.Class(
972
- "OAuth2AuthorizationCodeFlow"
973
- )({
974
- authorizationUrl: Schema3.String,
975
- tokenUrl: Schema3.String,
976
- refreshUrl: Schema3.OptionFromOptional(Schema3.String),
977
- scopes: OAuth2Scopes
978
- }) {
979
- };
980
- var OAuth2ClientCredentialsFlow = class extends Schema3.Class(
981
- "OAuth2ClientCredentialsFlow"
861
+ // src/sdk/store.ts
862
+ import { Effect as Effect4, Schema as Schema4 } from "effect";
863
+ import {
864
+ defineSchema,
865
+ ScopeId as ScopeId2,
866
+ StorageError
867
+ } from "@executor-js/sdk/core";
868
+ var openapiSchema = defineSchema({
869
+ openapi_source: {
870
+ fields: {
871
+ id: { type: "string", required: true },
872
+ scope_id: { type: "string", required: true, index: true },
873
+ name: { type: "string", required: true },
874
+ spec: { type: "string", required: true },
875
+ // Origin URL the spec was fetched from. Set when `addSpec` was
876
+ // invoked with an http(s) URL; null when the caller passed raw
877
+ // spec text. Drives `canRefresh` on the core source row and
878
+ // is the address re-fetched on `refreshSource`.
879
+ source_url: { type: "string", required: false },
880
+ base_url: { type: "string", required: false },
881
+ headers: { type: "json", required: false },
882
+ query_params: { type: "json", required: false },
883
+ oauth2: { type: "json", required: false },
884
+ invocation_config: { type: "json", required: true }
885
+ }
886
+ },
887
+ openapi_operation: {
888
+ fields: {
889
+ id: { type: "string", required: true },
890
+ scope_id: { type: "string", required: true, index: true },
891
+ source_id: { type: "string", required: true, index: true },
892
+ binding: { type: "json", required: true }
893
+ }
894
+ },
895
+ openapi_source_binding: {
896
+ fields: {
897
+ id: { type: "string", required: true },
898
+ source_id: { type: "string", required: true, index: true },
899
+ source_scope_id: { type: "string", required: true, index: true },
900
+ // Intentionally NOT named `scope_id`: this row is visible across
901
+ // scope stacks and is filtered manually by source/target scope.
902
+ // The target scope is credential ownership data, not adapter row
903
+ // ownership. Source owners must be able to delete all descendant
904
+ // bindings when a shared source is removed.
905
+ target_scope_id: { type: "string", required: true, index: true },
906
+ slot: { type: "string", required: true, index: true },
907
+ value: { type: "json", required: true },
908
+ created_at: { type: "date", required: true },
909
+ updated_at: { type: "date", required: true }
910
+ }
911
+ }
912
+ });
913
+ var StoredSourceSchema = class extends Schema4.Class(
914
+ "OpenApiStoredSource"
982
915
  )({
983
- tokenUrl: Schema3.String,
984
- refreshUrl: Schema3.OptionFromOptional(Schema3.String),
985
- scopes: OAuth2Scopes
986
- }) {
987
- };
988
- var OAuth2Flows = class extends Schema3.Class("OAuth2Flows")({
989
- authorizationCode: Schema3.OptionFromOptional(OAuth2AuthorizationCodeFlow),
990
- clientCredentials: Schema3.OptionFromOptional(OAuth2ClientCredentialsFlow)
916
+ namespace: Schema4.String,
917
+ name: Schema4.String,
918
+ config: Schema4.Struct({
919
+ spec: Schema4.String,
920
+ sourceUrl: Schema4.optional(Schema4.String),
921
+ baseUrl: Schema4.optional(Schema4.String),
922
+ namespace: Schema4.optional(Schema4.String),
923
+ headers: Schema4.optional(
924
+ Schema4.Record(Schema4.String, ConfiguredHeaderValue)
925
+ ),
926
+ queryParams: Schema4.optional(
927
+ Schema4.Record(Schema4.String, HeaderValue)
928
+ ),
929
+ specFetchCredentials: Schema4.optional(
930
+ Schema4.Struct({
931
+ headers: Schema4.optional(
932
+ Schema4.Record(Schema4.String, HeaderValue)
933
+ ),
934
+ queryParams: Schema4.optional(
935
+ Schema4.Record(Schema4.String, HeaderValue)
936
+ )
937
+ })
938
+ ),
939
+ // Canonical source-owned OAuth config. Concrete client credentials
940
+ // and connection ids live in OpenAPI-owned scoped binding rows.
941
+ oauth2: Schema4.optional(OAuth2SourceConfig)
942
+ })
991
943
  }) {
992
944
  };
993
- var SecurityScheme = class extends Schema3.Class("SecurityScheme")({
994
- /** Key name in components.securitySchemes (e.g. "api_token") */
995
- name: Schema3.String,
996
- /** OpenAPI security scheme type */
997
- type: Schema3.Literals(["http", "apiKey", "oauth2", "openIdConnect"]),
998
- /** For type: "http" — e.g. "bearer", "basic" */
999
- scheme: Schema3.OptionFromOptional(Schema3.String),
1000
- /** For type: "http" with scheme "bearer" — e.g. "JWT" */
1001
- bearerFormat: Schema3.OptionFromOptional(Schema3.String),
1002
- /** For type: "apiKey" where the key goes */
1003
- in: Schema3.OptionFromOptional(Schema3.Literals(["header", "query", "cookie"])),
1004
- /** For type: "apiKey" — the header/query/cookie name */
1005
- headerName: Schema3.OptionFromOptional(Schema3.String),
1006
- description: Schema3.OptionFromOptional(Schema3.String),
1007
- /** For type: "oauth2" — declared flows (authorizationCode / clientCredentials only; implicit and password are deprecated). */
1008
- flows: Schema3.OptionFromOptional(OAuth2Flows),
1009
- /** For type: "openIdConnect" — the discovery URL. */
1010
- openIdConnectUrl: Schema3.OptionFromOptional(Schema3.String)
1011
- }) {
945
+ var encodeBinding = Schema4.encodeSync(OperationBinding);
946
+ var decodeBinding = Schema4.decodeUnknownSync(OperationBinding);
947
+ var decodeOAuth2 = Schema4.decodeUnknownSync(OAuth2Auth);
948
+ var encodeOAuth2SourceConfig = Schema4.encodeSync(OAuth2SourceConfig);
949
+ var encodeSourceBindingValue = Schema4.encodeSync(OpenApiSourceBindingValue);
950
+ var decodeSourceBindingValue = Schema4.decodeUnknownSync(
951
+ OpenApiSourceBindingValue
952
+ );
953
+ var asJsonObject = (value) => {
954
+ if (value == null) return {};
955
+ if (typeof value === "string")
956
+ return JSON.parse(value);
957
+ return value;
1012
958
  };
1013
- var AuthStrategy = class extends Schema3.Class("AuthStrategy")({
1014
- /** The security schemes required together for this strategy */
1015
- schemes: Schema3.Array(Schema3.String)
1016
- }) {
959
+ var toJsonRecord = (value) => value;
960
+ var toConfiguredHeaderBinding = (value) => new ConfiguredHeaderBinding({
961
+ kind: "binding",
962
+ slot: String(value.slot ?? ""),
963
+ ...typeof value.prefix === "string" ? { prefix: value.prefix } : {}
964
+ });
965
+ var decodeHeaders = (value) => {
966
+ if (value == null) return {};
967
+ if (typeof value === "string")
968
+ return JSON.parse(value);
969
+ return value;
1017
970
  };
1018
- var HeaderPreset = class extends Schema3.Class("HeaderPreset")({
1019
- /** Human-readable label for the UI (e.g. "Bearer Token", "API Key + Email") */
1020
- label: Schema3.String,
1021
- /** Headers this strategy needs. Value is null when the user must provide it. */
1022
- headers: Schema3.Record(Schema3.String, Schema3.NullOr(Schema3.String)),
1023
- /** Which headers should be stored as secrets */
1024
- secretHeaders: Schema3.Array(Schema3.String)
1025
- }) {
971
+ var slugifySlotPart = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "default";
972
+ var headerBindingSlot = (headerName) => `header:${slugifySlotPart(headerName)}`;
973
+ var oauth2ClientIdSlot = (securitySchemeName) => `oauth2:${slugifySlotPart(securitySchemeName)}:client-id`;
974
+ var oauth2ClientSecretSlot = (securitySchemeName) => `oauth2:${slugifySlotPart(securitySchemeName)}:client-secret`;
975
+ var oauth2ConnectionSlot = (securitySchemeName) => `oauth2:${slugifySlotPart(securitySchemeName)}:connection`;
976
+ var normalizeStoredHeaders = (value) => {
977
+ const raw = decodeHeaders(value);
978
+ const headers = {};
979
+ const legacy = {};
980
+ for (const [name, header] of Object.entries(raw)) {
981
+ if (typeof header === "string") {
982
+ headers[name] = header;
983
+ legacy[name] = header;
984
+ continue;
985
+ }
986
+ if (header && typeof header === "object" && "kind" in header && header.kind === "binding") {
987
+ headers[name] = toConfiguredHeaderBinding(header);
988
+ continue;
989
+ }
990
+ legacy[name] = header;
991
+ headers[name] = new ConfiguredHeaderBinding({
992
+ kind: "binding",
993
+ slot: headerBindingSlot(name),
994
+ prefix: header.prefix
995
+ });
996
+ }
997
+ return { headers, legacy };
1026
998
  };
1027
- var OAuth2Preset = class extends Schema3.Class("OAuth2Preset")({
1028
- /** Human-readable label for the UI (e.g. "OAuth2 (Authorization Code) oauth_app") */
1029
- label: Schema3.String,
1030
- /** The source security scheme this preset came from (components.securitySchemes key). */
1031
- securitySchemeName: Schema3.String,
1032
- /** Which OAuth2 flow this preset uses. */
1033
- flow: Schema3.Literals(["authorizationCode", "clientCredentials"]),
1034
- /** For authorizationCode: user-agent redirect URL (from the spec). */
1035
- authorizationUrl: Schema3.OptionFromOptional(Schema3.String),
1036
- /** Token endpoint to exchange the code / refresh. */
1037
- tokenUrl: Schema3.String,
1038
- /** Optional refresh endpoint if the spec declares one separately. */
1039
- refreshUrl: Schema3.OptionFromOptional(Schema3.String),
1040
- /** Declared scopes for this flow: `{ scope: description }`. */
1041
- scopes: Schema3.Record(Schema3.String, Schema3.String)
1042
- }) {
1043
- };
1044
- var PreviewOperation = class extends Schema3.Class("PreviewOperation")({
1045
- operationId: Schema3.String,
1046
- method: HttpMethod,
1047
- path: Schema3.String,
1048
- summary: Schema3.OptionFromOptional(Schema3.String),
1049
- tags: Schema3.Array(Schema3.String),
1050
- deprecated: Schema3.Boolean
1051
- }) {
1052
- };
1053
- var SpecPreview = class extends Schema3.Class("SpecPreview")({
1054
- title: Schema3.OptionFromOptional(Schema3.String),
1055
- version: Schema3.OptionFromOptional(Schema3.String),
1056
- /** Reuses ServerInfo from extraction */
1057
- servers: Schema3.Array(ServerInfo),
1058
- operationCount: Schema3.Number,
1059
- /** Lightweight operation list for the add-source UI */
1060
- operations: Schema3.Array(PreviewOperation),
1061
- tags: Schema3.Array(Schema3.String),
1062
- securitySchemes: Schema3.Array(SecurityScheme),
1063
- /** Valid auth strategies (each is a set of schemes used together) */
1064
- authStrategies: Schema3.Array(AuthStrategy),
1065
- /** Pre-built header presets derived from auth strategies */
1066
- headerPresets: Schema3.Array(HeaderPreset),
1067
- /** OAuth2 presets — one per (oauth2 scheme × supported flow) combination */
1068
- oauth2Presets: Schema3.Array(OAuth2Preset)
1069
- }) {
1070
- };
1071
- var stringRecord = (value) => {
1072
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
1073
- const out = {};
1074
- for (const [k, v] of Object.entries(value)) {
1075
- if (typeof v === "string") out[k] = v;
999
+ var normalizeStoredOAuth2 = (value) => {
1000
+ if (value == null) return {};
1001
+ const parsed = typeof value === "string" ? JSON.parse(value) : value;
1002
+ if (parsed && typeof parsed === "object" && "connectionSlot" in parsed) {
1003
+ return {
1004
+ oauth2: Schema4.decodeUnknownSync(OAuth2SourceConfig)(parsed)
1005
+ };
1076
1006
  }
1077
- return out;
1007
+ const legacy = decodeOAuth2(parsed);
1008
+ return {
1009
+ legacy,
1010
+ oauth2: new OAuth2SourceConfig({
1011
+ kind: "oauth2",
1012
+ securitySchemeName: legacy.securitySchemeName,
1013
+ flow: legacy.flow,
1014
+ tokenUrl: legacy.tokenUrl,
1015
+ authorizationUrl: legacy.authorizationUrl,
1016
+ clientIdSlot: oauth2ClientIdSlot(legacy.securitySchemeName),
1017
+ clientSecretSlot: legacy.clientSecretSecretId ? oauth2ClientSecretSlot(legacy.securitySchemeName) : null,
1018
+ connectionSlot: oauth2ConnectionSlot(legacy.securitySchemeName),
1019
+ scopes: [...legacy.scopes]
1020
+ })
1021
+ };
1078
1022
  };
1079
- var extractFlows = (rawFlows) => {
1080
- if (!rawFlows || typeof rawFlows !== "object") return Option4.none();
1081
- const flows = rawFlows;
1082
- const parseFlow = (key) => flows[key];
1083
- let authorizationCode = Option4.none();
1084
- const authCodeRaw = parseFlow("authorizationCode");
1085
- if (authCodeRaw && typeof authCodeRaw === "object") {
1086
- const f = authCodeRaw;
1087
- const authUrl = typeof f.authorizationUrl === "string" ? f.authorizationUrl : null;
1088
- const tokenUrl = typeof f.tokenUrl === "string" ? f.tokenUrl : null;
1089
- if (authUrl && tokenUrl) {
1090
- authorizationCode = Option4.some(
1091
- new OAuth2AuthorizationCodeFlow({
1092
- authorizationUrl: authUrl,
1093
- tokenUrl,
1094
- refreshUrl: Option4.fromNullishOr(
1095
- typeof f.refreshUrl === "string" ? f.refreshUrl : void 0
1096
- ),
1097
- scopes: stringRecord(f.scopes)
1023
+ var makeDefaultOpenapiStore = ({
1024
+ adapter,
1025
+ scopes
1026
+ }) => {
1027
+ const scopeIds = scopes.map((scope) => scope.id);
1028
+ const scopePrecedence = /* @__PURE__ */ new Map();
1029
+ scopeIds.forEach((scope, index) => scopePrecedence.set(scope, index));
1030
+ const scopeRank = (scopeId) => scopePrecedence.get(scopeId) ?? Infinity;
1031
+ const encodeSyntheticRowIdPart = (value) => encodeURIComponent(value);
1032
+ const sourceBindingRowId = (sourceId, sourceScopeId, slot, scopeId) => [
1033
+ "openapi-source-binding",
1034
+ encodeSyntheticRowIdPart(sourceScopeId),
1035
+ encodeSyntheticRowIdPart(sourceId),
1036
+ encodeSyntheticRowIdPart(slot),
1037
+ encodeSyntheticRowIdPart(scopeId)
1038
+ ].join("::");
1039
+ const rowToSourceBinding = (row) => new OpenApiSourceBindingRef({
1040
+ sourceId: row.source_id,
1041
+ sourceScopeId: ScopeId2.make(row.source_scope_id),
1042
+ scopeId: ScopeId2.make(row.target_scope_id),
1043
+ slot: row.slot,
1044
+ value: decodeSourceBindingValue(asJsonObject(row.value)),
1045
+ createdAt: row.created_at instanceof Date ? row.created_at : new Date(row.created_at),
1046
+ updatedAt: row.updated_at instanceof Date ? row.updated_at : new Date(row.updated_at)
1047
+ });
1048
+ const validateBindingTarget = (params) => Effect4.gen(function* () {
1049
+ if (!scopeIds.includes(params.sourceScope)) {
1050
+ return yield* Effect4.fail(
1051
+ new StorageError({
1052
+ message: `OpenAPI source binding references source scope "${params.sourceScope}" which is not in the executor's scope stack [${scopeIds.join(", ")}].`,
1053
+ cause: void 0
1098
1054
  })
1099
1055
  );
1100
1056
  }
1101
- }
1102
- let clientCredentials = Option4.none();
1103
- const ccRaw = parseFlow("clientCredentials");
1104
- if (ccRaw && typeof ccRaw === "object") {
1105
- const f = ccRaw;
1106
- const tokenUrl = typeof f.tokenUrl === "string" ? f.tokenUrl : null;
1107
- if (tokenUrl) {
1108
- clientCredentials = Option4.some(
1109
- new OAuth2ClientCredentialsFlow({
1110
- tokenUrl,
1111
- refreshUrl: Option4.fromNullishOr(
1112
- typeof f.refreshUrl === "string" ? f.refreshUrl : void 0
1113
- ),
1114
- scopes: stringRecord(f.scopes)
1057
+ if (!scopeIds.includes(params.targetScope)) {
1058
+ return yield* Effect4.fail(
1059
+ new StorageError({
1060
+ message: `OpenAPI source binding targets scope "${params.targetScope}" which is not in the executor's scope stack [${scopeIds.join(", ")}].`,
1061
+ cause: void 0
1115
1062
  })
1116
1063
  );
1117
1064
  }
1118
- }
1119
- if (Option4.isNone(authorizationCode) && Option4.isNone(clientCredentials)) {
1120
- return Option4.none();
1121
- }
1122
- return Option4.some(new OAuth2Flows({ authorizationCode, clientCredentials }));
1123
- };
1124
- var extractSecuritySchemes = (rawSchemes, resolver) => Object.entries(rawSchemes).flatMap(([name, schemeOrRef]) => {
1125
- if (!schemeOrRef || typeof schemeOrRef !== "object") return [];
1126
- const resolved = resolver.resolve(
1127
- schemeOrRef
1128
- );
1129
- if (!resolved || typeof resolved !== "object") return [];
1130
- const scheme = resolved;
1131
- const type = scheme.type;
1132
- if (!["http", "apiKey", "oauth2", "openIdConnect"].includes(type)) return [];
1133
- return [
1134
- new SecurityScheme({
1135
- name,
1136
- type,
1137
- scheme: Option4.fromNullishOr(scheme.scheme),
1138
- bearerFormat: Option4.fromNullishOr(scheme.bearerFormat),
1139
- in: Option4.fromNullishOr(scheme.in),
1140
- headerName: Option4.fromNullishOr(scheme.name),
1141
- description: Option4.fromNullishOr(scheme.description),
1142
- flows: type === "oauth2" ? extractFlows(scheme.flows) : Option4.none(),
1143
- openIdConnectUrl: Option4.fromNullishOr(
1144
- scheme.openIdConnectUrl
1145
- )
1146
- })
1147
- ];
1148
- });
1149
- var buildHeaderPresets = (schemes, strategies) => {
1150
- const schemeMap = new Map(schemes.map((s) => [s.name, s]));
1151
- return strategies.flatMap((strategy) => {
1152
- const resolved = strategy.schemes.map((name) => schemeMap.get(name)).filter((s) => s !== void 0);
1153
- if (resolved.length === 0) return [];
1154
- const headers = {};
1155
- const secretHeaders = [];
1156
- const labelParts = [];
1157
- for (const scheme of resolved) {
1158
- if (scheme.type === "http" && Option4.getOrElse(scheme.scheme, () => "") === "bearer") {
1159
- headers["Authorization"] = null;
1160
- secretHeaders.push("Authorization");
1161
- labelParts.push("Bearer Token");
1162
- } else if (scheme.type === "http" && Option4.getOrElse(scheme.scheme, () => "") === "basic") {
1163
- headers["Authorization"] = null;
1164
- secretHeaders.push("Authorization");
1165
- labelParts.push("Basic Auth");
1166
- } else if (scheme.type === "apiKey" && Option4.getOrElse(scheme.in, () => "") === "header") {
1167
- const headerName = Option4.getOrElse(scheme.headerName, () => scheme.name);
1168
- headers[headerName] = null;
1169
- secretHeaders.push(headerName);
1170
- labelParts.push(scheme.name);
1171
- } else if (scheme.type === "apiKey") {
1172
- labelParts.push(`${scheme.name} (${Option4.getOrElse(scheme.in, () => "unknown")})`);
1173
- } else {
1174
- labelParts.push(scheme.name);
1175
- }
1176
- }
1177
- if (Object.keys(headers).length === 0 && resolved.length > 0) {
1178
- return [new HeaderPreset({ label: labelParts.join(" + "), headers: {}, secretHeaders: [] })];
1179
- }
1180
- return [new HeaderPreset({ label: labelParts.join(" + "), headers, secretHeaders })];
1181
- });
1182
- };
1183
- var buildOAuth2Presets = (schemes) => {
1184
- const presets = [];
1185
- for (const scheme of schemes) {
1186
- if (scheme.type !== "oauth2") continue;
1187
- if (Option4.isNone(scheme.flows)) continue;
1188
- const flows = scheme.flows.value;
1189
- if (Option4.isSome(flows.authorizationCode)) {
1190
- const flow = flows.authorizationCode.value;
1191
- presets.push(
1192
- new OAuth2Preset({
1193
- label: `OAuth2 Authorization Code \xB7 ${scheme.name}`,
1194
- securitySchemeName: scheme.name,
1195
- flow: "authorizationCode",
1196
- authorizationUrl: Option4.some(flow.authorizationUrl),
1197
- tokenUrl: flow.tokenUrl,
1198
- refreshUrl: flow.refreshUrl,
1199
- scopes: flow.scopes
1065
+ const source = yield* adapter.findOne({
1066
+ model: "openapi_source",
1067
+ where: [
1068
+ { field: "id", value: params.sourceId },
1069
+ { field: "scope_id", value: params.sourceScope }
1070
+ ]
1071
+ });
1072
+ if (!source) {
1073
+ return yield* Effect4.fail(
1074
+ new StorageError({
1075
+ message: `OpenAPI source "${params.sourceId}" does not exist at scope "${params.sourceScope}"`,
1076
+ cause: void 0
1200
1077
  })
1201
1078
  );
1202
1079
  }
1203
- if (Option4.isSome(flows.clientCredentials)) {
1204
- const flow = flows.clientCredentials.value;
1205
- presets.push(
1206
- new OAuth2Preset({
1207
- label: `OAuth2 Client Credentials \xB7 ${scheme.name}`,
1208
- securitySchemeName: scheme.name,
1209
- flow: "clientCredentials",
1210
- authorizationUrl: Option4.none(),
1211
- tokenUrl: flow.tokenUrl,
1212
- refreshUrl: flow.refreshUrl,
1213
- scopes: flow.scopes
1080
+ if (scopeRank(params.targetScope) > scopeRank(params.sourceScope)) {
1081
+ return yield* Effect4.fail(
1082
+ new StorageError({
1083
+ message: `OpenAPI source bindings for "${params.sourceId}" cannot be written at outer scope "${params.targetScope}" because the base source lives at "${params.sourceScope}"`,
1084
+ cause: void 0
1214
1085
  })
1215
1086
  );
1216
1087
  }
1217
- }
1218
- return presets;
1219
- };
1220
- var collectTags = (result) => {
1221
- const tagSet = /* @__PURE__ */ new Set();
1222
- for (const op of result.operations) {
1223
- for (const tag of op.tags) tagSet.add(tag);
1224
- }
1225
- return [...tagSet].sort();
1226
- };
1227
- var previewSpec = Effect4.fn("OpenApi.previewSpec")(function* (input) {
1228
- const specText = yield* resolveSpecText(input);
1229
- const doc = yield* parse(specText);
1230
- const result = yield* extract(doc);
1231
- const resolver = new DocResolver(doc);
1232
- const securitySchemes = extractSecuritySchemes(
1233
- doc.components?.securitySchemes ?? {},
1234
- resolver
1235
- );
1236
- const rawSecurity = doc.security ?? [];
1237
- const declaredStrategies = rawSecurity.map(
1238
- (entry) => new AuthStrategy({ schemes: Object.keys(entry) })
1239
- );
1240
- const authStrategies = declaredStrategies.length > 0 ? declaredStrategies : securitySchemes.map((scheme) => new AuthStrategy({ schemes: [scheme.name] }));
1241
- return new SpecPreview({
1242
- title: result.title,
1243
- version: result.version,
1244
- servers: result.servers,
1245
- operationCount: result.operations.length,
1246
- operations: result.operations.map(
1247
- (op) => new PreviewOperation({
1248
- operationId: op.operationId,
1249
- method: op.method,
1250
- path: op.pathTemplate,
1251
- summary: op.summary,
1252
- tags: op.tags,
1253
- deprecated: op.deprecated
1254
- })
1255
- ),
1256
- tags: collectTags(result),
1257
- securitySchemes,
1258
- authStrategies,
1259
- headerPresets: buildHeaderPresets(securitySchemes, authStrategies),
1260
- oauth2Presets: buildOAuth2Presets(securitySchemes)
1261
- });
1262
- });
1263
-
1264
- // src/sdk/store.ts
1265
- import { Effect as Effect5, Schema as Schema4 } from "effect";
1266
- import {
1267
- defineSchema,
1268
- ScopeId as ScopeId2,
1269
- StorageError
1270
- } from "@executor-js/sdk/core";
1271
- var openapiSchema = defineSchema({
1272
- openapi_source: {
1273
- fields: {
1274
- id: { type: "string", required: true },
1275
- scope_id: { type: "string", required: true, index: true },
1276
- name: { type: "string", required: true },
1277
- spec: { type: "string", required: true },
1278
- // Origin URL the spec was fetched from. Set when `addSpec` was
1279
- // invoked with an http(s) URL; null when the caller passed raw
1280
- // spec text. Drives `canRefresh` on the core source row and
1281
- // is the address re-fetched on `refreshSource`.
1282
- source_url: { type: "string", required: false },
1283
- base_url: { type: "string", required: false },
1284
- headers: { type: "json", required: false },
1285
- query_params: { type: "json", required: false },
1286
- oauth2: { type: "json", required: false },
1287
- invocation_config: { type: "json", required: true }
1288
- }
1289
- },
1290
- openapi_operation: {
1291
- fields: {
1292
- id: { type: "string", required: true },
1293
- scope_id: { type: "string", required: true, index: true },
1294
- source_id: { type: "string", required: true, index: true },
1295
- binding: { type: "json", required: true }
1296
- }
1297
- },
1298
- openapi_source_binding: {
1299
- fields: {
1300
- id: { type: "string", required: true },
1301
- source_id: { type: "string", required: true, index: true },
1302
- source_scope_id: { type: "string", required: true, index: true },
1303
- // Intentionally NOT named `scope_id`: this row is visible across
1304
- // scope stacks and is filtered manually by source/target scope.
1305
- // The target scope is credential ownership data, not adapter row
1306
- // ownership. Source owners must be able to delete all descendant
1307
- // bindings when a shared source is removed.
1308
- target_scope_id: { type: "string", required: true, index: true },
1309
- slot: { type: "string", required: true, index: true },
1310
- value: { type: "json", required: true },
1311
- created_at: { type: "date", required: true },
1312
- updated_at: { type: "date", required: true }
1313
- }
1314
- }
1315
- });
1316
- var StoredSourceSchema = class extends Schema4.Class(
1317
- "OpenApiStoredSource"
1318
- )({
1319
- namespace: Schema4.String,
1320
- name: Schema4.String,
1321
- config: Schema4.Struct({
1322
- spec: Schema4.String,
1323
- sourceUrl: Schema4.optional(Schema4.String),
1324
- baseUrl: Schema4.optional(Schema4.String),
1325
- namespace: Schema4.optional(Schema4.String),
1326
- headers: Schema4.optional(
1327
- Schema4.Record(Schema4.String, ConfiguredHeaderValue)
1328
- ),
1329
- queryParams: Schema4.optional(
1330
- Schema4.Record(Schema4.String, HeaderValue)
1331
- ),
1332
- specFetchCredentials: Schema4.optional(
1333
- Schema4.Struct({
1334
- headers: Schema4.optional(
1335
- Schema4.Record(Schema4.String, HeaderValue)
1336
- ),
1337
- queryParams: Schema4.optional(
1338
- Schema4.Record(Schema4.String, HeaderValue)
1339
- )
1340
- })
1341
- ),
1342
- // Canonical source-owned OAuth config. Concrete client credentials
1343
- // and connection ids live in OpenAPI-owned scoped binding rows.
1344
- oauth2: Schema4.optional(OAuth2SourceConfig)
1345
- })
1346
- }) {
1347
- };
1348
- var encodeBinding = Schema4.encodeSync(OperationBinding);
1349
- var decodeBinding = Schema4.decodeUnknownSync(OperationBinding);
1350
- var decodeOAuth2 = Schema4.decodeUnknownSync(OAuth2Auth);
1351
- var encodeOAuth2SourceConfig = Schema4.encodeSync(OAuth2SourceConfig);
1352
- var encodeSourceBindingValue = Schema4.encodeSync(OpenApiSourceBindingValue);
1353
- var decodeSourceBindingValue = Schema4.decodeUnknownSync(
1354
- OpenApiSourceBindingValue
1355
- );
1356
- var asJsonObject = (value) => {
1357
- if (value == null) return {};
1358
- if (typeof value === "string")
1359
- return JSON.parse(value);
1360
- return value;
1361
- };
1362
- var toJsonRecord = (value) => value;
1363
- var toConfiguredHeaderBinding = (value) => new ConfiguredHeaderBinding({
1364
- kind: "binding",
1365
- slot: String(value.slot ?? ""),
1366
- ...typeof value.prefix === "string" ? { prefix: value.prefix } : {}
1367
- });
1368
- var decodeHeaders = (value) => {
1369
- if (value == null) return {};
1370
- if (typeof value === "string")
1371
- return JSON.parse(value);
1372
- return value;
1373
- };
1374
- var slugifySlotPart = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "default";
1375
- var headerBindingSlot = (headerName) => `header:${slugifySlotPart(headerName)}`;
1376
- var oauth2ClientIdSlot = (securitySchemeName) => `oauth2:${slugifySlotPart(securitySchemeName)}:client-id`;
1377
- var oauth2ClientSecretSlot = (securitySchemeName) => `oauth2:${slugifySlotPart(securitySchemeName)}:client-secret`;
1378
- var oauth2ConnectionSlot = (securitySchemeName) => `oauth2:${slugifySlotPart(securitySchemeName)}:connection`;
1379
- var normalizeStoredHeaders = (value) => {
1380
- const raw = decodeHeaders(value);
1381
- const headers = {};
1382
- const legacy = {};
1383
- for (const [name, header] of Object.entries(raw)) {
1384
- if (typeof header === "string") {
1385
- headers[name] = header;
1386
- legacy[name] = header;
1387
- continue;
1388
- }
1389
- if (header && typeof header === "object" && "kind" in header && header.kind === "binding") {
1390
- headers[name] = toConfiguredHeaderBinding(header);
1391
- continue;
1392
- }
1393
- legacy[name] = header;
1394
- headers[name] = new ConfiguredHeaderBinding({
1395
- kind: "binding",
1396
- slot: headerBindingSlot(name),
1397
- prefix: header.prefix
1398
- });
1399
- }
1400
- return { headers, legacy };
1401
- };
1402
- var normalizeStoredOAuth2 = (value) => {
1403
- if (value == null) return {};
1404
- const parsed = typeof value === "string" ? JSON.parse(value) : value;
1405
- if (parsed && typeof parsed === "object" && "connectionSlot" in parsed) {
1406
- return {
1407
- oauth2: Schema4.decodeUnknownSync(OAuth2SourceConfig)(parsed)
1408
- };
1409
- }
1410
- const legacy = decodeOAuth2(parsed);
1411
- return {
1412
- legacy,
1413
- oauth2: new OAuth2SourceConfig({
1414
- kind: "oauth2",
1415
- securitySchemeName: legacy.securitySchemeName,
1416
- flow: legacy.flow,
1417
- tokenUrl: legacy.tokenUrl,
1418
- authorizationUrl: legacy.authorizationUrl,
1419
- clientIdSlot: oauth2ClientIdSlot(legacy.securitySchemeName),
1420
- clientSecretSlot: legacy.clientSecretSecretId ? oauth2ClientSecretSlot(legacy.securitySchemeName) : null,
1421
- connectionSlot: oauth2ConnectionSlot(legacy.securitySchemeName),
1422
- scopes: [...legacy.scopes]
1423
- })
1424
- };
1425
- };
1426
- var makeDefaultOpenapiStore = ({
1427
- adapter,
1428
- scopes
1429
- }) => {
1430
- const scopeIds = scopes.map((scope) => scope.id);
1431
- const scopePrecedence = /* @__PURE__ */ new Map();
1432
- scopeIds.forEach((scope, index) => scopePrecedence.set(scope, index));
1433
- const scopeRank = (scopeId) => scopePrecedence.get(scopeId) ?? Infinity;
1434
- const encodeSyntheticRowIdPart = (value) => encodeURIComponent(value);
1435
- const sourceBindingRowId = (sourceId, sourceScopeId, slot, scopeId) => [
1436
- "openapi-source-binding",
1437
- encodeSyntheticRowIdPart(sourceScopeId),
1438
- encodeSyntheticRowIdPart(sourceId),
1439
- encodeSyntheticRowIdPart(slot),
1440
- encodeSyntheticRowIdPart(scopeId)
1441
- ].join("::");
1442
- const rowToSourceBinding = (row) => new OpenApiSourceBindingRef({
1443
- sourceId: row.source_id,
1444
- sourceScopeId: ScopeId2.make(row.source_scope_id),
1445
- scopeId: ScopeId2.make(row.target_scope_id),
1446
- slot: row.slot,
1447
- value: decodeSourceBindingValue(asJsonObject(row.value)),
1448
- createdAt: row.created_at instanceof Date ? row.created_at : new Date(row.created_at),
1449
- updatedAt: row.updated_at instanceof Date ? row.updated_at : new Date(row.updated_at)
1450
- });
1451
- const validateBindingTarget = (params) => Effect5.gen(function* () {
1452
- if (!scopeIds.includes(params.sourceScope)) {
1453
- return yield* Effect5.fail(
1454
- new StorageError({
1455
- message: `OpenAPI source binding references source scope "${params.sourceScope}" which is not in the executor's scope stack [${scopeIds.join(", ")}].`,
1456
- cause: void 0
1457
- })
1458
- );
1459
- }
1460
- if (!scopeIds.includes(params.targetScope)) {
1461
- return yield* Effect5.fail(
1462
- new StorageError({
1463
- message: `OpenAPI source binding targets scope "${params.targetScope}" which is not in the executor's scope stack [${scopeIds.join(", ")}].`,
1464
- cause: void 0
1465
- })
1466
- );
1467
- }
1468
- const source = yield* adapter.findOne({
1469
- model: "openapi_source",
1470
- where: [
1471
- { field: "id", value: params.sourceId },
1472
- { field: "scope_id", value: params.sourceScope }
1473
- ]
1474
- });
1475
- if (!source) {
1476
- return yield* Effect5.fail(
1477
- new StorageError({
1478
- message: `OpenAPI source "${params.sourceId}" does not exist at scope "${params.sourceScope}"`,
1479
- cause: void 0
1480
- })
1481
- );
1482
- }
1483
- if (scopeRank(params.targetScope) > scopeRank(params.sourceScope)) {
1484
- return yield* Effect5.fail(
1485
- new StorageError({
1486
- message: `OpenAPI source bindings for "${params.sourceId}" cannot be written at outer scope "${params.targetScope}" because the base source lives at "${params.sourceScope}"`,
1487
- cause: void 0
1488
- })
1489
- );
1490
- }
1491
- return source;
1088
+ return source;
1492
1089
  });
1493
1090
  const rowToSource = (row) => {
1494
1091
  const normalizedHeaders = normalizeStoredHeaders(row.headers);
@@ -1520,7 +1117,7 @@ var makeDefaultOpenapiStore = ({
1520
1117
  typeof row.binding === "string" ? JSON.parse(row.binding) : row.binding
1521
1118
  )
1522
1119
  });
1523
- const deleteSource = (namespace, scope, options) => Effect5.gen(function* () {
1120
+ const deleteSource = (namespace, scope, options) => Effect4.gen(function* () {
1524
1121
  yield* adapter.deleteMany({
1525
1122
  model: "openapi_operation",
1526
1123
  where: [
@@ -1546,7 +1143,7 @@ var makeDefaultOpenapiStore = ({
1546
1143
  }
1547
1144
  });
1548
1145
  return {
1549
- upsertSource: (input, operations) => Effect5.gen(function* () {
1146
+ upsertSource: (input, operations) => Effect4.gen(function* () {
1550
1147
  yield* deleteSource(input.namespace, input.scope);
1551
1148
  yield* adapter.create({
1552
1149
  model: "openapi_source",
@@ -1590,7 +1187,7 @@ var makeDefaultOpenapiStore = ({
1590
1187
  });
1591
1188
  }
1592
1189
  }),
1593
- updateSourceMeta: (namespace, scope, patch) => Effect5.gen(function* () {
1190
+ updateSourceMeta: (namespace, scope, patch) => Effect4.gen(function* () {
1594
1191
  const existingRow = yield* adapter.findOne({
1595
1192
  model: "openapi_source",
1596
1193
  where: [
@@ -1636,24 +1233,24 @@ var makeDefaultOpenapiStore = ({
1636
1233
  { field: "id", value: namespace },
1637
1234
  { field: "scope_id", value: scope }
1638
1235
  ]
1639
- }).pipe(Effect5.map((row) => row ? rowToSource(row) : null)),
1640
- listSources: () => adapter.findMany({ model: "openapi_source" }).pipe(Effect5.map((rows) => rows.map(rowToSource))),
1236
+ }).pipe(Effect4.map((row) => row ? rowToSource(row) : null)),
1237
+ listSources: () => adapter.findMany({ model: "openapi_source" }).pipe(Effect4.map((rows) => rows.map(rowToSource))),
1641
1238
  getOperationByToolId: (toolId, scope) => adapter.findOne({
1642
1239
  model: "openapi_operation",
1643
1240
  where: [
1644
1241
  { field: "id", value: toolId },
1645
1242
  { field: "scope_id", value: scope }
1646
1243
  ]
1647
- }).pipe(Effect5.map((row) => row ? rowToOperation(row) : null)),
1244
+ }).pipe(Effect4.map((row) => row ? rowToOperation(row) : null)),
1648
1245
  listOperationsBySource: (sourceId, scope) => adapter.findMany({
1649
1246
  model: "openapi_operation",
1650
1247
  where: [
1651
1248
  { field: "source_id", value: sourceId },
1652
1249
  { field: "scope_id", value: scope }
1653
1250
  ]
1654
- }).pipe(Effect5.map((rows) => rows.map(rowToOperation))),
1251
+ }).pipe(Effect4.map((rows) => rows.map(rowToOperation))),
1655
1252
  removeSource: (namespace, scope) => deleteSource(namespace, scope, { includeBindings: true }),
1656
- listSourceBindings: (sourceId, sourceScope) => Effect5.gen(function* () {
1253
+ listSourceBindings: (sourceId, sourceScope) => Effect4.gen(function* () {
1657
1254
  yield* validateBindingTarget({
1658
1255
  sourceId,
1659
1256
  sourceScope,
@@ -1673,7 +1270,7 @@ var makeDefaultOpenapiStore = ({
1673
1270
  (a, b) => scopeRank(a.target_scope_id) - scopeRank(b.target_scope_id)
1674
1271
  ).map(rowToSourceBinding);
1675
1272
  }),
1676
- resolveSourceBinding: (sourceId, sourceScope, slot) => Effect5.gen(function* () {
1273
+ resolveSourceBinding: (sourceId, sourceScope, slot) => Effect4.gen(function* () {
1677
1274
  yield* validateBindingTarget({
1678
1275
  sourceId,
1679
1276
  sourceScope,
@@ -1695,7 +1292,7 @@ var makeDefaultOpenapiStore = ({
1695
1292
  )[0];
1696
1293
  return row ? rowToSourceBinding(row) : null;
1697
1294
  }),
1698
- setSourceBinding: (input) => Effect5.gen(function* () {
1295
+ setSourceBinding: (input) => Effect4.gen(function* () {
1699
1296
  yield* validateBindingTarget({
1700
1297
  sourceId: input.sourceId,
1701
1298
  sourceScope: input.sourceScope,
@@ -1707,60 +1304,689 @@ var makeDefaultOpenapiStore = ({
1707
1304
  input.slot,
1708
1305
  input.scope
1709
1306
  );
1710
- const now = /* @__PURE__ */ new Date();
1711
- yield* adapter.delete({
1712
- model: "openapi_source_binding",
1713
- where: [{ field: "id", value: id }]
1714
- });
1715
- yield* adapter.create({
1716
- model: "openapi_source_binding",
1717
- data: {
1718
- id,
1719
- source_id: input.sourceId,
1720
- source_scope_id: input.sourceScope,
1721
- target_scope_id: input.scope,
1722
- slot: input.slot,
1723
- value: toJsonRecord(encodeSourceBindingValue(input.value)),
1724
- created_at: now,
1725
- updated_at: now
1726
- },
1727
- forceAllowId: true
1728
- });
1729
- return new OpenApiSourceBindingRef({
1730
- sourceId: input.sourceId,
1731
- sourceScopeId: input.sourceScope,
1732
- scopeId: input.scope,
1733
- slot: input.slot,
1734
- value: input.value,
1735
- createdAt: now,
1736
- updatedAt: now
1737
- });
1738
- }),
1739
- removeSourceBinding: (sourceId, sourceScope, slot, scope) => Effect5.gen(function* () {
1740
- yield* validateBindingTarget({
1741
- sourceId,
1742
- sourceScope,
1743
- targetScope: scope
1744
- });
1745
- yield* adapter.delete({
1746
- model: "openapi_source_binding",
1747
- where: [
1748
- {
1749
- field: "id",
1750
- value: sourceBindingRowId(sourceId, sourceScope, slot, scope)
1751
- }
1752
- ]
1753
- });
1307
+ const now = /* @__PURE__ */ new Date();
1308
+ yield* adapter.delete({
1309
+ model: "openapi_source_binding",
1310
+ where: [{ field: "id", value: id }]
1311
+ });
1312
+ yield* adapter.create({
1313
+ model: "openapi_source_binding",
1314
+ data: {
1315
+ id,
1316
+ source_id: input.sourceId,
1317
+ source_scope_id: input.sourceScope,
1318
+ target_scope_id: input.scope,
1319
+ slot: input.slot,
1320
+ value: toJsonRecord(encodeSourceBindingValue(input.value)),
1321
+ created_at: now,
1322
+ updated_at: now
1323
+ },
1324
+ forceAllowId: true
1325
+ });
1326
+ return new OpenApiSourceBindingRef({
1327
+ sourceId: input.sourceId,
1328
+ sourceScopeId: input.sourceScope,
1329
+ scopeId: input.scope,
1330
+ slot: input.slot,
1331
+ value: input.value,
1332
+ createdAt: now,
1333
+ updatedAt: now
1334
+ });
1335
+ }),
1336
+ removeSourceBinding: (sourceId, sourceScope, slot, scope) => Effect4.gen(function* () {
1337
+ yield* validateBindingTarget({
1338
+ sourceId,
1339
+ sourceScope,
1340
+ targetScope: scope
1341
+ });
1342
+ yield* adapter.delete({
1343
+ model: "openapi_source_binding",
1344
+ where: [
1345
+ {
1346
+ field: "id",
1347
+ value: sourceBindingRowId(sourceId, sourceScope, slot, scope)
1348
+ }
1349
+ ]
1350
+ });
1351
+ })
1352
+ };
1353
+ };
1354
+
1355
+ // src/sdk/invoke.ts
1356
+ import { Effect as Effect5, Layer, Option as Option4 } from "effect";
1357
+ import { HttpClient as HttpClient2, HttpClientRequest as HttpClientRequest2 } from "effect/unstable/http";
1358
+ var CONTAINER_KEYS = {
1359
+ path: ["path", "pathParams", "params"],
1360
+ query: ["query", "queryParams", "params"],
1361
+ header: ["headers", "header"],
1362
+ cookie: ["cookies", "cookie"]
1363
+ };
1364
+ var readParamValue = (args, param) => {
1365
+ const direct = args[param.name];
1366
+ if (direct !== void 0) return direct;
1367
+ for (const key of CONTAINER_KEYS[param.location] ?? []) {
1368
+ const container = args[key];
1369
+ if (typeof container === "object" && container !== null && !Array.isArray(container)) {
1370
+ const nested = container[param.name];
1371
+ if (nested !== void 0) return nested;
1372
+ }
1373
+ }
1374
+ return void 0;
1375
+ };
1376
+ var resolvePath = Effect5.fn("OpenApi.resolvePath")(function* (pathTemplate, args, parameters) {
1377
+ let resolved = pathTemplate;
1378
+ for (const param of parameters) {
1379
+ if (param.location !== "path") continue;
1380
+ const value = readParamValue(args, param);
1381
+ if (value === void 0 || value === null) {
1382
+ if (param.required) {
1383
+ return yield* new OpenApiInvocationError({
1384
+ message: `Missing required path parameter: ${param.name}`,
1385
+ statusCode: Option4.none()
1386
+ });
1387
+ }
1388
+ continue;
1389
+ }
1390
+ resolved = resolved.replaceAll(`{${param.name}}`, encodeURIComponent(String(value)));
1391
+ }
1392
+ const remaining = [...resolved.matchAll(/\{([^{}]+)\}/g)].map((m) => m[1]).filter((v) => typeof v === "string");
1393
+ for (const name of remaining) {
1394
+ const value = args[name];
1395
+ if (value !== void 0 && value !== null) {
1396
+ resolved = resolved.replaceAll(`{${name}}`, encodeURIComponent(String(value)));
1397
+ }
1398
+ }
1399
+ const unresolved = [...resolved.matchAll(/\{([^{}]+)\}/g)].map((m) => m[1]).filter((v) => typeof v === "string");
1400
+ if (unresolved.length > 0) {
1401
+ return yield* new OpenApiInvocationError({
1402
+ message: `Unresolved path parameters: ${[...new Set(unresolved)].join(", ")}`,
1403
+ statusCode: Option4.none()
1404
+ });
1405
+ }
1406
+ return resolved;
1407
+ });
1408
+ var resolveHeaders = (headers, secrets) => {
1409
+ const entries = Object.entries(headers);
1410
+ const secretCount = entries.reduce(
1411
+ (acc, [, value]) => typeof value === "string" ? acc : acc + 1,
1412
+ 0
1413
+ );
1414
+ return Effect5.gen(function* () {
1415
+ const values = yield* Effect5.all(
1416
+ entries.map(
1417
+ ([name, value]) => typeof value === "string" ? Effect5.succeed({ name, value }) : secrets.get(value.secretId).pipe(
1418
+ Effect5.mapError(
1419
+ (err) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? new OpenApiInvocationError({
1420
+ message: `Failed to resolve secret "${value.secretId}" for header "${name}"`,
1421
+ statusCode: Option4.none()
1422
+ }) : err
1423
+ ),
1424
+ Effect5.flatMap(
1425
+ (secret) => secret === null ? Effect5.fail(
1426
+ new OpenApiInvocationError({
1427
+ message: `Failed to resolve secret "${value.secretId}" for header "${name}"`,
1428
+ statusCode: Option4.none()
1429
+ })
1430
+ ) : Effect5.succeed({
1431
+ name,
1432
+ value: value.prefix ? `${value.prefix}${secret}` : secret
1433
+ })
1434
+ )
1435
+ )
1436
+ ),
1437
+ { concurrency: "unbounded" }
1438
+ );
1439
+ const resolved = {};
1440
+ for (const { name, value } of values) resolved[name] = value;
1441
+ return resolved;
1442
+ }).pipe(
1443
+ Effect5.withSpan("plugin.openapi.secret.resolve", {
1444
+ attributes: {
1445
+ "plugin.openapi.headers.total": entries.length,
1446
+ "plugin.openapi.headers.secret_count": secretCount
1447
+ }
1448
+ })
1449
+ );
1450
+ };
1451
+ var applyHeaders = (request, headers) => {
1452
+ let req = request;
1453
+ for (const [name, value] of Object.entries(headers)) {
1454
+ req = HttpClientRequest2.setHeader(req, name, value);
1455
+ }
1456
+ return req;
1457
+ };
1458
+ var normalizeContentType = (ct) => ct?.split(";")[0]?.trim().toLowerCase() ?? "";
1459
+ var isJsonContentType = (ct) => {
1460
+ const normalized = normalizeContentType(ct);
1461
+ if (!normalized) return false;
1462
+ return normalized === "application/json" || normalized.includes("+json") || normalized.includes("json");
1463
+ };
1464
+ var isFormUrlEncoded = (ct) => normalizeContentType(ct) === "application/x-www-form-urlencoded";
1465
+ var isMultipartFormData = (ct) => normalizeContentType(ct).startsWith("multipart/form-data");
1466
+ var isXmlContentType = (ct) => {
1467
+ const normalized = normalizeContentType(ct);
1468
+ if (!normalized) return false;
1469
+ return normalized === "application/xml" || normalized === "text/xml" || normalized.endsWith("+xml");
1470
+ };
1471
+ var isTextContentType = (ct) => normalizeContentType(ct).startsWith("text/");
1472
+ var isOctetStream = (ct) => normalizeContentType(ct) === "application/octet-stream";
1473
+ var toUint8Array = (value) => {
1474
+ if (value instanceof Uint8Array) return value;
1475
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
1476
+ if (ArrayBuffer.isView(value)) {
1477
+ const view = value;
1478
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
1479
+ }
1480
+ if (Array.isArray(value) && value.every((v) => typeof v === "number")) {
1481
+ return new Uint8Array(value);
1482
+ }
1483
+ return null;
1484
+ };
1485
+ var toArrayBuffer = (bytes) => {
1486
+ const copy = new ArrayBuffer(bytes.byteLength);
1487
+ new Uint8Array(copy).set(bytes);
1488
+ return copy;
1489
+ };
1490
+ var DEFAULT_FORM_STYLE = {
1491
+ style: "form",
1492
+ explode: true,
1493
+ allowReserved: false
1494
+ };
1495
+ var resolveStyleExplode = (e) => {
1496
+ if (!e) return DEFAULT_FORM_STYLE;
1497
+ return {
1498
+ style: Option4.getOrElse(e.style, () => DEFAULT_FORM_STYLE.style),
1499
+ explode: Option4.getOrElse(e.explode, () => DEFAULT_FORM_STYLE.explode),
1500
+ allowReserved: Option4.getOrElse(e.allowReserved, () => DEFAULT_FORM_STYLE.allowReserved)
1501
+ };
1502
+ };
1503
+ var RESERVED_UNENCODED_RE = /[A-Za-z0-9\-._~:/?#[\]@!$&'()*+,;=]/;
1504
+ var encodeFormValue = (v, allowReserved) => {
1505
+ const raw = typeof v === "object" && v !== null ? JSON.stringify(v) : String(v);
1506
+ if (!allowReserved) return encodeURIComponent(raw);
1507
+ let out = "";
1508
+ for (const ch of raw) {
1509
+ out += RESERVED_UNENCODED_RE.test(ch) ? ch : encodeURIComponent(ch);
1510
+ }
1511
+ return out;
1512
+ };
1513
+ var serializeFormUrlEncoded = (value, encoding) => {
1514
+ const parts = [];
1515
+ for (const [key, raw] of Object.entries(value)) {
1516
+ if (raw === void 0 || raw === null) continue;
1517
+ const { style, explode, allowReserved } = resolveStyleExplode(encoding?.[key]);
1518
+ const encKey = encodeURIComponent(key);
1519
+ if (Array.isArray(raw)) {
1520
+ if (explode) {
1521
+ for (const v of raw) {
1522
+ parts.push(`${encKey}=${encodeFormValue(v, allowReserved)}`);
1523
+ }
1524
+ } else {
1525
+ const sep = style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ",";
1526
+ parts.push(
1527
+ `${encKey}=${encodeFormValue(
1528
+ raw.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)).join(sep),
1529
+ allowReserved
1530
+ )}`
1531
+ );
1532
+ }
1533
+ continue;
1534
+ }
1535
+ if (typeof raw === "object") {
1536
+ const entries = Object.entries(raw).filter(
1537
+ ([, v]) => v !== void 0 && v !== null
1538
+ );
1539
+ if (style === "deepObject") {
1540
+ for (const [subkey, subval] of entries) {
1541
+ parts.push(
1542
+ `${encodeURIComponent(`${key}[${subkey}]`)}=${encodeFormValue(
1543
+ subval,
1544
+ allowReserved
1545
+ )}`
1546
+ );
1547
+ }
1548
+ } else if (explode) {
1549
+ for (const [subkey, subval] of entries) {
1550
+ parts.push(
1551
+ `${encodeURIComponent(subkey)}=${encodeFormValue(subval, allowReserved)}`
1552
+ );
1553
+ }
1554
+ } else {
1555
+ const flat = entries.flatMap(([k, v]) => [
1556
+ k,
1557
+ typeof v === "object" ? JSON.stringify(v) : String(v)
1558
+ ]);
1559
+ parts.push(`${encKey}=${encodeFormValue(flat.join(","), allowReserved)}`);
1560
+ }
1561
+ continue;
1562
+ }
1563
+ parts.push(`${encKey}=${encodeFormValue(raw, allowReserved)}`);
1564
+ }
1565
+ return parts.join("&");
1566
+ };
1567
+ var coerceFormDataRecord = (value, encoding) => {
1568
+ const out = {};
1569
+ for (const [key, raw] of Object.entries(value)) {
1570
+ if (raw === void 0 || raw === null) continue;
1571
+ const partType = encoding?.[key] ? Option4.getOrUndefined(encoding[key].contentType) : void 0;
1572
+ if (partType) {
1573
+ const isJson = partType.startsWith("application/json") || partType.includes("+json");
1574
+ const serialized = typeof raw === "string" ? raw : isJson ? JSON.stringify(raw) : typeof raw === "object" ? JSON.stringify(raw) : String(raw);
1575
+ out[key] = new Blob([serialized], { type: partType });
1576
+ continue;
1577
+ }
1578
+ if (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean" || raw instanceof Blob || typeof File !== "undefined" && raw instanceof File) {
1579
+ out[key] = raw;
1580
+ continue;
1581
+ }
1582
+ if (Array.isArray(raw)) {
1583
+ out[key] = raw.map(
1584
+ (v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v instanceof Blob || typeof File !== "undefined" && v instanceof File ? v : JSON.stringify(v)
1585
+ );
1586
+ continue;
1587
+ }
1588
+ const bytes = toUint8Array(raw);
1589
+ if (bytes) {
1590
+ out[key] = new Blob([toArrayBuffer(bytes)]);
1591
+ continue;
1592
+ }
1593
+ out[key] = JSON.stringify(raw);
1594
+ }
1595
+ return out;
1596
+ };
1597
+ var applyRequestBody = (request, contentType, bodyValue, encoding) => {
1598
+ if (isJsonContentType(contentType)) {
1599
+ if (typeof bodyValue === "string") {
1600
+ return HttpClientRequest2.bodyText(request, bodyValue, contentType);
1601
+ }
1602
+ return HttpClientRequest2.bodyJsonUnsafe(request, bodyValue);
1603
+ }
1604
+ if (isFormUrlEncoded(contentType)) {
1605
+ if (typeof bodyValue === "string") {
1606
+ return HttpClientRequest2.bodyText(request, bodyValue, contentType);
1607
+ }
1608
+ if (typeof bodyValue === "object" && bodyValue !== null && !Array.isArray(bodyValue)) {
1609
+ const serialized = serializeFormUrlEncoded(
1610
+ bodyValue,
1611
+ encoding
1612
+ );
1613
+ return HttpClientRequest2.bodyText(request, serialized, contentType);
1614
+ }
1615
+ return HttpClientRequest2.bodyUrlParams(
1616
+ request,
1617
+ bodyValue
1618
+ );
1619
+ }
1620
+ if (isMultipartFormData(contentType)) {
1621
+ if (bodyValue instanceof FormData) {
1622
+ return HttpClientRequest2.bodyFormData(request, bodyValue);
1623
+ }
1624
+ if (typeof bodyValue === "object" && bodyValue !== null) {
1625
+ return HttpClientRequest2.bodyFormDataRecord(
1626
+ request,
1627
+ coerceFormDataRecord(bodyValue, encoding)
1628
+ );
1629
+ }
1630
+ return HttpClientRequest2.bodyText(request, String(bodyValue), contentType);
1631
+ }
1632
+ if (isOctetStream(contentType)) {
1633
+ const bytes2 = toUint8Array(bodyValue);
1634
+ if (bytes2) return HttpClientRequest2.bodyUint8Array(request, bytes2, contentType);
1635
+ if (typeof bodyValue === "string") {
1636
+ return HttpClientRequest2.bodyText(request, bodyValue, contentType);
1637
+ }
1638
+ return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
1639
+ }
1640
+ if (isXmlContentType(contentType) || isTextContentType(contentType)) {
1641
+ if (typeof bodyValue === "string") {
1642
+ return HttpClientRequest2.bodyText(request, bodyValue, contentType);
1643
+ }
1644
+ const bytes2 = toUint8Array(bodyValue);
1645
+ if (bytes2) return HttpClientRequest2.bodyUint8Array(request, bytes2, contentType);
1646
+ return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
1647
+ }
1648
+ if (typeof bodyValue === "string") {
1649
+ return HttpClientRequest2.bodyText(request, bodyValue, contentType);
1650
+ }
1651
+ const bytes = toUint8Array(bodyValue);
1652
+ if (bytes) return HttpClientRequest2.bodyUint8Array(request, bytes, contentType);
1653
+ return HttpClientRequest2.bodyText(request, JSON.stringify(bodyValue), contentType);
1654
+ };
1655
+ var invoke = Effect5.fn("OpenApi.invoke")(function* (operation, args, resolvedHeaders, sourceQueryParams = {}) {
1656
+ const client = yield* HttpClient2.HttpClient;
1657
+ yield* Effect5.annotateCurrentSpan({
1658
+ "http.method": operation.method.toUpperCase(),
1659
+ "http.route": operation.pathTemplate,
1660
+ "plugin.openapi.method": operation.method.toUpperCase(),
1661
+ "plugin.openapi.path_template": operation.pathTemplate,
1662
+ "plugin.openapi.headers.resolved_count": Object.keys(resolvedHeaders).length
1663
+ });
1664
+ const resolvedPath = yield* resolvePath(operation.pathTemplate, args, operation.parameters);
1665
+ const path = resolvedPath.startsWith("/") ? resolvedPath : `/${resolvedPath}`;
1666
+ let request = HttpClientRequest2.make(operation.method.toUpperCase())(path);
1667
+ for (const [name, value] of Object.entries(sourceQueryParams)) {
1668
+ request = HttpClientRequest2.setUrlParam(request, name, value);
1669
+ }
1670
+ for (const param of operation.parameters) {
1671
+ if (param.location !== "query") continue;
1672
+ const value = readParamValue(args, param);
1673
+ if (value === void 0 || value === null) continue;
1674
+ request = HttpClientRequest2.setUrlParam(request, param.name, String(value));
1675
+ }
1676
+ for (const param of operation.parameters) {
1677
+ if (param.location !== "header") continue;
1678
+ const value = readParamValue(args, param);
1679
+ if (value === void 0 || value === null) continue;
1680
+ request = HttpClientRequest2.setHeader(request, param.name, String(value));
1681
+ }
1682
+ if (Option4.isSome(operation.requestBody)) {
1683
+ const rb = operation.requestBody.value;
1684
+ const bodyValue = args.body ?? args.input;
1685
+ if (bodyValue !== void 0) {
1686
+ const contentsOpt = Option4.getOrUndefined(rb.contents);
1687
+ const requestedCt = typeof args.contentType === "string" ? args.contentType : void 0;
1688
+ const selected = contentsOpt && requestedCt ? contentsOpt.find((c) => c.contentType === requestedCt) : void 0;
1689
+ const chosenCt = selected?.contentType ?? rb.contentType;
1690
+ const chosenEncoding = selected ? Option4.getOrUndefined(selected.encoding) : contentsOpt && contentsOpt[0] ? Option4.getOrUndefined(contentsOpt[0].encoding) : void 0;
1691
+ request = applyRequestBody(request, chosenCt, bodyValue, chosenEncoding);
1692
+ }
1693
+ }
1694
+ request = applyHeaders(request, resolvedHeaders);
1695
+ const response = yield* client.execute(request).pipe(
1696
+ Effect5.mapError(
1697
+ (err) => new OpenApiInvocationError({
1698
+ message: `HTTP request failed: ${err.message}`,
1699
+ statusCode: Option4.none(),
1700
+ cause: err
1701
+ })
1702
+ )
1703
+ );
1704
+ const status = response.status;
1705
+ yield* Effect5.annotateCurrentSpan({
1706
+ "http.status_code": status
1707
+ });
1708
+ const responseHeaders = { ...response.headers };
1709
+ const contentType = response.headers["content-type"] ?? null;
1710
+ const mapBodyError = Effect5.mapError(
1711
+ (err) => new OpenApiInvocationError({
1712
+ message: `Failed to read response body: ${err.message ?? String(err)}`,
1713
+ statusCode: Option4.some(status),
1714
+ cause: err
1715
+ })
1716
+ );
1717
+ const responseBody = status === 204 ? null : isJsonContentType(contentType) ? yield* response.json.pipe(
1718
+ Effect5.catch(() => response.text),
1719
+ mapBodyError
1720
+ ) : yield* response.text.pipe(mapBodyError);
1721
+ const ok = status >= 200 && status < 300;
1722
+ return new InvocationResult({
1723
+ status,
1724
+ headers: responseHeaders,
1725
+ data: ok ? responseBody : null,
1726
+ error: ok ? null : responseBody
1727
+ });
1728
+ });
1729
+ var invokeWithLayer = (operation, args, baseUrl, resolvedHeaders, sourceQueryParams, httpClientLayer) => {
1730
+ const clientWithBaseUrl = baseUrl ? Layer.effect(
1731
+ HttpClient2.HttpClient,
1732
+ Effect5.map(
1733
+ Effect5.service(HttpClient2.HttpClient),
1734
+ HttpClient2.mapRequest(HttpClientRequest2.prependUrl(baseUrl))
1735
+ )
1736
+ ).pipe(Layer.provide(httpClientLayer)) : httpClientLayer;
1737
+ return invoke(operation, args, resolvedHeaders, sourceQueryParams).pipe(
1738
+ Effect5.provide(clientWithBaseUrl),
1739
+ Effect5.withSpan("plugin.openapi.invoke", {
1740
+ attributes: {
1741
+ "plugin.openapi.method": operation.method.toUpperCase(),
1742
+ "plugin.openapi.path_template": operation.pathTemplate,
1743
+ "plugin.openapi.base_url": baseUrl
1744
+ }
1754
1745
  })
1746
+ );
1747
+ };
1748
+ var REQUIRE_APPROVAL = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
1749
+ var annotationsForOperation = (method, pathTemplate) => {
1750
+ const m = method.toLowerCase();
1751
+ if (!REQUIRE_APPROVAL.has(m)) return {};
1752
+ return {
1753
+ requiresApproval: true,
1754
+ approvalDescription: `${method.toUpperCase()} ${pathTemplate}`
1755
1755
  };
1756
1756
  };
1757
1757
 
1758
1758
  // src/sdk/plugin.ts
1759
- import { Effect as Effect6, Option as Option5, Schema as Schema5 } from "effect";
1759
+ import { Effect as Effect7, Option as Option5, Schema as Schema6 } from "effect";
1760
1760
  import { FetchHttpClient } from "effect/unstable/http";
1761
+
1762
+ // src/api/group.ts
1763
+ import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";
1764
+ import { Schema as Schema5 } from "effect";
1765
+ import { ScopeId as ScopeId3, SecretBackedValue as SecretBackedValue2 } from "@executor-js/sdk/core";
1766
+ import { InternalError } from "@executor-js/api";
1767
+ var DomainErrors = [
1768
+ InternalError,
1769
+ OpenApiParseError,
1770
+ OpenApiExtractionError,
1771
+ OpenApiOAuthError
1772
+ ];
1773
+ var ScopeIdParam = {
1774
+ scopeId: ScopeId3
1775
+ };
1776
+ var SourceParams = {
1777
+ scopeId: ScopeId3,
1778
+ namespace: Schema5.String
1779
+ };
1780
+ var SourceBindingParams = {
1781
+ scopeId: ScopeId3,
1782
+ namespace: Schema5.String,
1783
+ sourceScopeId: ScopeId3
1784
+ };
1785
+ var SpecFetchCredentialsPayload = Schema5.Struct({
1786
+ headers: Schema5.optional(Schema5.Record(Schema5.String, SecretBackedValue2)),
1787
+ queryParams: Schema5.optional(Schema5.Record(Schema5.String, SecretBackedValue2))
1788
+ });
1789
+ var AddSpecPayload = Schema5.Struct({
1790
+ spec: Schema5.String,
1791
+ specFetchCredentials: Schema5.optional(SpecFetchCredentialsPayload),
1792
+ name: Schema5.optional(Schema5.String),
1793
+ baseUrl: Schema5.optional(Schema5.String),
1794
+ namespace: Schema5.optional(Schema5.String),
1795
+ headers: Schema5.optional(Schema5.Record(Schema5.String, Schema5.Unknown)),
1796
+ queryParams: Schema5.optional(Schema5.Record(Schema5.String, SecretBackedValue2)),
1797
+ oauth2: Schema5.optional(Schema5.Union([OAuth2Auth, OAuth2SourceConfig]))
1798
+ });
1799
+ var PreviewSpecPayload = Schema5.Struct({
1800
+ spec: Schema5.String,
1801
+ specFetchCredentials: Schema5.optional(SpecFetchCredentialsPayload)
1802
+ });
1803
+ var UpdateSourcePayload = Schema5.Struct({
1804
+ name: Schema5.optional(Schema5.String),
1805
+ baseUrl: Schema5.optional(Schema5.String),
1806
+ headers: Schema5.optional(Schema5.Record(Schema5.String, Schema5.Unknown)),
1807
+ queryParams: Schema5.optional(Schema5.Record(Schema5.String, SecretBackedValue2)),
1808
+ // Set after a successful re-authenticate to refresh the source's
1809
+ // stored OAuth2 metadata.
1810
+ oauth2: Schema5.optional(Schema5.Union([OAuth2Auth, OAuth2SourceConfig]))
1811
+ });
1812
+ var UpdateSourceResponse = Schema5.Struct({
1813
+ updated: Schema5.Boolean
1814
+ });
1815
+ var RemoveBindingPayload = Schema5.Struct({
1816
+ sourceId: Schema5.String,
1817
+ sourceScope: ScopeId3,
1818
+ slot: Schema5.String,
1819
+ scope: ScopeId3
1820
+ });
1821
+ var AddSpecResponse = Schema5.Struct({
1822
+ toolCount: Schema5.Number,
1823
+ namespace: Schema5.String
1824
+ });
1825
+ var OpenApiGroup = HttpApiGroup.make("openapi").add(
1826
+ HttpApiEndpoint.post("previewSpec", "/scopes/:scopeId/openapi/preview", {
1827
+ params: ScopeIdParam,
1828
+ payload: PreviewSpecPayload,
1829
+ success: SpecPreview,
1830
+ error: DomainErrors
1831
+ })
1832
+ ).add(
1833
+ HttpApiEndpoint.post("addSpec", "/scopes/:scopeId/openapi/specs", {
1834
+ params: ScopeIdParam,
1835
+ payload: AddSpecPayload,
1836
+ success: AddSpecResponse,
1837
+ error: DomainErrors
1838
+ })
1839
+ ).add(
1840
+ HttpApiEndpoint.get("getSource", "/scopes/:scopeId/openapi/sources/:namespace", {
1841
+ params: SourceParams,
1842
+ success: Schema5.NullOr(StoredSourceSchema),
1843
+ error: DomainErrors
1844
+ })
1845
+ ).add(
1846
+ HttpApiEndpoint.patch("updateSource", "/scopes/:scopeId/openapi/sources/:namespace", {
1847
+ params: SourceParams,
1848
+ payload: UpdateSourcePayload,
1849
+ success: UpdateSourceResponse,
1850
+ error: DomainErrors
1851
+ })
1852
+ ).add(
1853
+ HttpApiEndpoint.get(
1854
+ "listSourceBindings",
1855
+ "/scopes/:scopeId/openapi/sources/:namespace/base/:sourceScopeId/bindings",
1856
+ {
1857
+ params: SourceBindingParams,
1858
+ success: Schema5.Array(OpenApiSourceBindingRef),
1859
+ error: DomainErrors
1860
+ }
1861
+ )
1862
+ ).add(
1863
+ HttpApiEndpoint.post("setSourceBinding", "/scopes/:scopeId/openapi/source-bindings", {
1864
+ params: ScopeIdParam,
1865
+ payload: OpenApiSourceBindingInputSchema,
1866
+ success: OpenApiSourceBindingRef,
1867
+ error: DomainErrors
1868
+ })
1869
+ ).add(
1870
+ HttpApiEndpoint.post(
1871
+ "removeSourceBinding",
1872
+ "/scopes/:scopeId/openapi/source-bindings/remove",
1873
+ {
1874
+ params: ScopeIdParam,
1875
+ payload: RemoveBindingPayload,
1876
+ success: Schema5.Struct({ removed: Schema5.Boolean }),
1877
+ error: DomainErrors
1878
+ }
1879
+ )
1880
+ );
1881
+
1882
+ // src/api/handlers.ts
1883
+ import { HttpApiBuilder } from "effect/unstable/httpapi";
1884
+ import { Context, Effect as Effect6 } from "effect";
1885
+ import { addGroup, capture } from "@executor-js/api";
1886
+ var OpenApiExtensionService = class extends Context.Service()("OpenApiExtensionService") {
1887
+ };
1888
+ var ExecutorApiWithOpenApi = addGroup(OpenApiGroup);
1889
+ var OpenApiHandlers = HttpApiBuilder.group(
1890
+ ExecutorApiWithOpenApi,
1891
+ "openapi",
1892
+ (handlers) => handlers.handle(
1893
+ "previewSpec",
1894
+ ({ payload }) => capture(
1895
+ Effect6.gen(function* () {
1896
+ const ext = yield* OpenApiExtensionService;
1897
+ return yield* ext.previewSpec({
1898
+ spec: payload.spec,
1899
+ specFetchCredentials: payload.specFetchCredentials
1900
+ });
1901
+ })
1902
+ )
1903
+ ).handle(
1904
+ "addSpec",
1905
+ ({ params: path, payload }) => capture(
1906
+ Effect6.gen(function* () {
1907
+ const ext = yield* OpenApiExtensionService;
1908
+ const result = yield* ext.addSpec({
1909
+ spec: payload.spec,
1910
+ specFetchCredentials: payload.specFetchCredentials,
1911
+ scope: path.scopeId,
1912
+ name: payload.name,
1913
+ baseUrl: payload.baseUrl,
1914
+ namespace: payload.namespace,
1915
+ headers: payload.headers,
1916
+ queryParams: payload.queryParams,
1917
+ oauth2: payload.oauth2
1918
+ });
1919
+ return {
1920
+ toolCount: result.toolCount,
1921
+ namespace: result.sourceId
1922
+ };
1923
+ })
1924
+ )
1925
+ ).handle(
1926
+ "getSource",
1927
+ ({ params: path }) => capture(
1928
+ Effect6.gen(function* () {
1929
+ const ext = yield* OpenApiExtensionService;
1930
+ const source = yield* ext.getSource(path.namespace, path.scopeId);
1931
+ return source ? new StoredSourceSchema({
1932
+ namespace: source.namespace,
1933
+ name: source.name,
1934
+ config: source.config
1935
+ }) : null;
1936
+ })
1937
+ )
1938
+ ).handle(
1939
+ "updateSource",
1940
+ ({ params: path, payload }) => capture(
1941
+ Effect6.gen(function* () {
1942
+ const ext = yield* OpenApiExtensionService;
1943
+ yield* ext.updateSource(path.namespace, path.scopeId, {
1944
+ name: payload.name,
1945
+ baseUrl: payload.baseUrl,
1946
+ headers: payload.headers,
1947
+ queryParams: payload.queryParams,
1948
+ oauth2: payload.oauth2
1949
+ });
1950
+ return { updated: true };
1951
+ })
1952
+ )
1953
+ ).handle(
1954
+ "listSourceBindings",
1955
+ ({ params: path }) => capture(
1956
+ Effect6.gen(function* () {
1957
+ const ext = yield* OpenApiExtensionService;
1958
+ return yield* ext.listSourceBindings(path.namespace, path.sourceScopeId);
1959
+ })
1960
+ )
1961
+ ).handle(
1962
+ "setSourceBinding",
1963
+ ({ payload }) => capture(
1964
+ Effect6.gen(function* () {
1965
+ const ext = yield* OpenApiExtensionService;
1966
+ return yield* ext.setSourceBinding(new OpenApiSourceBindingInput(payload));
1967
+ })
1968
+ )
1969
+ ).handle(
1970
+ "removeSourceBinding",
1971
+ ({ payload }) => capture(
1972
+ Effect6.gen(function* () {
1973
+ const ext = yield* OpenApiExtensionService;
1974
+ yield* ext.removeSourceBinding(
1975
+ payload.sourceId,
1976
+ payload.sourceScope,
1977
+ payload.slot,
1978
+ payload.scope
1979
+ );
1980
+ return { removed: true };
1981
+ })
1982
+ )
1983
+ )
1984
+ );
1985
+
1986
+ // src/sdk/plugin.ts
1761
1987
  import {
1762
1988
  ConnectionId as ConnectionId2,
1763
- ScopeId as ScopeId3,
1989
+ ScopeId as ScopeId4,
1764
1990
  SecretId as SecretId2,
1765
1991
  SourceDetectionResult,
1766
1992
  definePlugin,
@@ -1894,25 +2120,25 @@ var compileToolDefinitions = (operations) => {
1894
2120
  };
1895
2121
 
1896
2122
  // src/sdk/plugin.ts
1897
- var PreviewSpecInputSchema = Schema5.Struct({
1898
- spec: Schema5.String,
1899
- specFetchCredentials: Schema5.optional(
1900
- Schema5.Struct({
1901
- headers: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue)),
1902
- queryParams: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue))
2123
+ var PreviewSpecInputSchema = Schema6.Struct({
2124
+ spec: Schema6.String,
2125
+ specFetchCredentials: Schema6.optional(
2126
+ Schema6.Struct({
2127
+ headers: Schema6.optional(Schema6.Record(Schema6.String, HeaderValue)),
2128
+ queryParams: Schema6.optional(Schema6.Record(Schema6.String, HeaderValue))
1903
2129
  })
1904
2130
  )
1905
2131
  });
1906
- var AddSourceInputSchema = Schema5.Struct({
1907
- spec: Schema5.String,
1908
- baseUrl: Schema5.optional(Schema5.String),
1909
- namespace: Schema5.optional(Schema5.String),
1910
- headers: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue)),
1911
- queryParams: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue)),
1912
- specFetchCredentials: Schema5.optional(
1913
- Schema5.Struct({
1914
- headers: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue)),
1915
- queryParams: Schema5.optional(Schema5.Record(Schema5.String, HeaderValue))
2132
+ var AddSourceInputSchema = Schema6.Struct({
2133
+ spec: Schema6.String,
2134
+ baseUrl: Schema6.optional(Schema6.String),
2135
+ namespace: Schema6.optional(Schema6.String),
2136
+ headers: Schema6.optional(Schema6.Record(Schema6.String, HeaderValue)),
2137
+ queryParams: Schema6.optional(Schema6.Record(Schema6.String, HeaderValue)),
2138
+ specFetchCredentials: Schema6.optional(
2139
+ Schema6.Struct({
2140
+ headers: Schema6.optional(Schema6.Record(Schema6.String, HeaderValue)),
2141
+ queryParams: Schema6.optional(Schema6.Record(Schema6.String, HeaderValue))
1916
2142
  })
1917
2143
  )
1918
2144
  });
@@ -2034,7 +2260,7 @@ var canonicalizeOAuth2 = (oauth2) => {
2034
2260
  bindings
2035
2261
  };
2036
2262
  };
2037
- var resolveEffectiveSourceConfig = (ctx, base) => Effect6.gen(function* () {
2263
+ var resolveEffectiveSourceConfig = (ctx, base) => Effect7.gen(function* () {
2038
2264
  const rank = new Map(ctx.scopes.map((scope, index) => [scope.id, index]));
2039
2265
  const baseRank = rank.get(base.scope) ?? Infinity;
2040
2266
  let fallback = null;
@@ -2068,7 +2294,7 @@ var resolveEffectiveSourceConfig = (ctx, base) => Effect6.gen(function* () {
2068
2294
  oauth2Source: base.config.oauth2 ? base : fallback
2069
2295
  };
2070
2296
  });
2071
- var resolveConfiguredHeaders = (ctx, params) => Effect6.gen(function* () {
2297
+ var resolveConfiguredHeaders = (ctx, params) => Effect7.gen(function* () {
2072
2298
  const resolved = {};
2073
2299
  for (const [name, value] of Object.entries(params.headers)) {
2074
2300
  if (typeof value === "string") {
@@ -2082,7 +2308,7 @@ var resolveConfiguredHeaders = (ctx, params) => Effect6.gen(function* () {
2082
2308
  );
2083
2309
  if (binding?.value.kind === "secret") {
2084
2310
  const secret = yield* ctx.secrets.get(binding.value.secretId).pipe(
2085
- Effect6.mapError(
2311
+ Effect7.mapError(
2086
2312
  (err) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? new OpenApiOAuthError({
2087
2313
  message: `Secret not found for header "${name}"`
2088
2314
  }) : err
@@ -2103,8 +2329,8 @@ var resolveConfiguredHeaders = (ctx, params) => Effect6.gen(function* () {
2103
2329
  const legacy = params.legacyHeaders?.[name];
2104
2330
  if (legacy) {
2105
2331
  const fallback = yield* resolveHeaders({ [name]: legacy }, ctx.secrets).pipe(
2106
- Effect6.map((headers) => headers[name]),
2107
- Effect6.mapError(
2332
+ Effect7.map((headers) => headers[name]),
2333
+ Effect7.mapError(
2108
2334
  (err) => err instanceof OpenApiOAuthError ? err : new OpenApiOAuthError({ message: err.message })
2109
2335
  )
2110
2336
  );
@@ -2127,12 +2353,12 @@ var resolveHeaderValues = (ctx, values) => resolveSecretBackedMap({
2127
2353
  message: `Secret not found for "${name}"`
2128
2354
  }) : err
2129
2355
  }).pipe(
2130
- Effect6.mapError(
2356
+ Effect7.mapError(
2131
2357
  (err) => "_tag" in err && err._tag === "SecretOwnedByConnectionError" ? new OpenApiOAuthError({ message: "Secret resolution failed" }) : err
2132
2358
  ),
2133
- Effect6.map((resolved) => resolved ?? {})
2359
+ Effect7.map((resolved) => resolved ?? {})
2134
2360
  );
2135
- var resolveOAuthConnectionId = (ctx, params) => Effect6.gen(function* () {
2361
+ var resolveOAuthConnectionId = (ctx, params) => Effect7.gen(function* () {
2136
2362
  const binding = yield* ctx.storage.resolveSourceBinding(
2137
2363
  params.sourceId,
2138
2364
  params.sourceScope,
@@ -2147,7 +2373,7 @@ var resolveOAuthConnectionId = (ctx, params) => Effect6.gen(function* () {
2147
2373
  const legacyConnection = yield* ctx.connections.get(params.legacyOAuth2.connectionId);
2148
2374
  return legacyConnection ? params.legacyOAuth2.connectionId : null;
2149
2375
  });
2150
- var resolveSpecFetchCredentials = (ctx, credentials) => Effect6.gen(function* () {
2376
+ var resolveSpecFetchCredentials = (ctx, credentials) => Effect7.gen(function* () {
2151
2377
  if (!credentials) return void 0;
2152
2378
  return {
2153
2379
  headers: yield* resolveHeaderValues(ctx, credentials.headers),
@@ -2174,7 +2400,7 @@ var toOpenApiSourceConfig = (namespace, config) => {
2174
2400
  var isHttpUrl = (s) => s.startsWith("http://") || s.startsWith("https://");
2175
2401
  var openApiPlugin = definePlugin((options) => {
2176
2402
  const httpClientLayer = options?.httpClientLayer ?? FetchHttpClient.layer;
2177
- const rebuildSource = (ctx, input) => Effect6.gen(function* () {
2403
+ const rebuildSource = (ctx, input) => Effect7.gen(function* () {
2178
2404
  const doc = yield* parse(input.specText);
2179
2405
  const result = yield* extract(doc);
2180
2406
  const namespace = input.namespace ?? Option5.getOrElse(result.title, () => "api").toLowerCase().replace(/[^a-z0-9]+/g, "_");
@@ -2211,7 +2437,7 @@ var openApiPlugin = definePlugin((options) => {
2211
2437
  binding: toBinding(def)
2212
2438
  }));
2213
2439
  yield* ctx.transaction(
2214
- Effect6.gen(function* () {
2440
+ Effect7.gen(function* () {
2215
2441
  yield* ctx.storage.upsertSource(storedSource, storedOps);
2216
2442
  yield* ctx.core.sources.register({
2217
2443
  id: namespace,
@@ -2236,8 +2462,8 @@ var openApiPlugin = definePlugin((options) => {
2236
2462
  yield* ctx.storage.setSourceBinding(
2237
2463
  new OpenApiSourceBindingInput({
2238
2464
  sourceId: namespace,
2239
- sourceScope: ScopeId3.make(input.scope),
2240
- scope: ScopeId3.make(input.scope),
2465
+ sourceScope: ScopeId4.make(input.scope),
2466
+ scope: ScopeId4.make(input.scope),
2241
2467
  slot: binding.slot,
2242
2468
  value: binding.value
2243
2469
  })
@@ -2254,7 +2480,7 @@ var openApiPlugin = definePlugin((options) => {
2254
2480
  );
2255
2481
  return { sourceId: namespace, toolCount: definitions.length };
2256
2482
  });
2257
- const refreshSourceInternal = (ctx, sourceId, scope) => Effect6.gen(function* () {
2483
+ const refreshSourceInternal = (ctx, sourceId, scope) => Effect7.gen(function* () {
2258
2484
  const existing = yield* ctx.storage.getSource(sourceId, scope);
2259
2485
  if (!existing) return;
2260
2486
  const effective = yield* resolveEffectiveSourceConfig(ctx, existing);
@@ -2266,7 +2492,7 @@ var openApiPlugin = definePlugin((options) => {
2266
2492
  resolvedConfig.specFetchCredentials
2267
2493
  );
2268
2494
  const specText = yield* resolveSpecText(sourceUrl, credentials).pipe(
2269
- Effect6.provide(httpClientLayer)
2495
+ Effect7.provide(httpClientLayer)
2270
2496
  );
2271
2497
  yield* rebuildSource(ctx, {
2272
2498
  specText,
@@ -2283,13 +2509,14 @@ var openApiPlugin = definePlugin((options) => {
2283
2509
  });
2284
2510
  return {
2285
2511
  id: "openapi",
2512
+ packageName: "@executor-js/plugin-openapi",
2286
2513
  schema: openapiSchema,
2287
2514
  storage: (deps) => makeDefaultOpenapiStore(deps),
2288
2515
  extension: (ctx) => {
2289
- const addSpecInternal = (config) => Effect6.gen(function* () {
2516
+ const addSpecInternal = (config) => Effect7.gen(function* () {
2290
2517
  const credentials = yield* resolveSpecFetchCredentials(ctx, config.specFetchCredentials);
2291
2518
  const specText = yield* resolveSpecText(config.spec, credentials).pipe(
2292
- Effect6.provide(httpClientLayer)
2519
+ Effect7.provide(httpClientLayer)
2293
2520
  );
2294
2521
  return yield* rebuildSource(ctx, {
2295
2522
  specText,
@@ -2306,27 +2533,27 @@ var openApiPlugin = definePlugin((options) => {
2306
2533
  });
2307
2534
  const configFile = options?.configFile;
2308
2535
  return {
2309
- previewSpec: (input) => Effect6.gen(function* () {
2536
+ previewSpec: (input) => Effect7.gen(function* () {
2310
2537
  const previewInput = typeof input === "string" ? { spec: input } : input;
2311
2538
  const credentials = yield* resolveSpecFetchCredentials(
2312
2539
  ctx,
2313
2540
  previewInput.specFetchCredentials
2314
2541
  );
2315
2542
  const specText = yield* resolveSpecText(previewInput.spec, credentials).pipe(
2316
- Effect6.provide(httpClientLayer)
2543
+ Effect7.provide(httpClientLayer)
2317
2544
  );
2318
- return yield* previewSpec(specText).pipe(Effect6.provide(httpClientLayer));
2545
+ return yield* previewSpec(specText).pipe(Effect7.provide(httpClientLayer));
2319
2546
  }),
2320
- addSpec: (config) => Effect6.gen(function* () {
2547
+ addSpec: (config) => Effect7.gen(function* () {
2321
2548
  const result = yield* addSpecInternal(config);
2322
2549
  if (configFile) {
2323
2550
  yield* configFile.upsertSource(toOpenApiSourceConfig(result.sourceId, config));
2324
2551
  }
2325
2552
  return result;
2326
2553
  }),
2327
- removeSpec: (namespace, scope) => Effect6.gen(function* () {
2554
+ removeSpec: (namespace, scope) => Effect7.gen(function* () {
2328
2555
  yield* ctx.transaction(
2329
- Effect6.gen(function* () {
2556
+ Effect7.gen(function* () {
2330
2557
  yield* ctx.storage.removeSource(namespace, scope);
2331
2558
  yield* ctx.core.sources.unregister(namespace);
2332
2559
  })
@@ -2335,7 +2562,7 @@ var openApiPlugin = definePlugin((options) => {
2335
2562
  yield* configFile.removeSource(namespace);
2336
2563
  }
2337
2564
  }),
2338
- getSource: (namespace, scope) => Effect6.gen(function* () {
2565
+ getSource: (namespace, scope) => Effect7.gen(function* () {
2339
2566
  const source = yield* ctx.storage.getSource(namespace, scope);
2340
2567
  if (!source) return null;
2341
2568
  const effective = yield* resolveEffectiveSourceConfig(ctx, source);
@@ -2344,7 +2571,7 @@ var openApiPlugin = definePlugin((options) => {
2344
2571
  config: effective.config
2345
2572
  };
2346
2573
  }),
2347
- updateSource: (namespace, scope, input) => Effect6.gen(function* () {
2574
+ updateSource: (namespace, scope, input) => Effect7.gen(function* () {
2348
2575
  const existing = yield* ctx.storage.getSource(namespace, scope);
2349
2576
  if (!existing) return;
2350
2577
  const canonicalHeaders = input.headers !== void 0 ? canonicalizeHeaders(input.headers) : existing.legacy?.headers ? canonicalizeHeaders(existing.legacy.headers) : null;
@@ -2361,8 +2588,8 @@ var openApiPlugin = definePlugin((options) => {
2361
2588
  yield* ctx.storage.setSourceBinding(
2362
2589
  new OpenApiSourceBindingInput({
2363
2590
  sourceId: namespace,
2364
- sourceScope: ScopeId3.make(scope),
2365
- scope: ScopeId3.make(scope),
2591
+ sourceScope: ScopeId4.make(scope),
2592
+ scope: ScopeId4.make(scope),
2366
2593
  slot: binding.slot,
2367
2594
  value: binding.value
2368
2595
  })
@@ -2432,17 +2659,17 @@ var openApiPlugin = definePlugin((options) => {
2432
2659
  ]
2433
2660
  }
2434
2661
  ],
2435
- invokeTool: ({ ctx, toolRow, args }) => Effect6.gen(function* () {
2662
+ invokeTool: ({ ctx, toolRow, args }) => Effect7.gen(function* () {
2436
2663
  const toolScope = toolRow.scope_id;
2437
2664
  const op = yield* ctx.storage.getOperationByToolId(toolRow.id, toolScope);
2438
2665
  if (!op) {
2439
- return yield* Effect6.fail(
2666
+ return yield* Effect7.fail(
2440
2667
  new Error(`No OpenAPI operation found for tool "${toolRow.id}"`)
2441
2668
  );
2442
2669
  }
2443
2670
  const source = yield* ctx.storage.getSource(op.sourceId, toolScope);
2444
2671
  if (!source) {
2445
- return yield* Effect6.fail(new Error(`No OpenAPI source found for "${op.sourceId}"`));
2672
+ return yield* Effect7.fail(new Error(`No OpenAPI source found for "${op.sourceId}"`));
2446
2673
  }
2447
2674
  const effective = yield* resolveEffectiveSourceConfig(ctx, source);
2448
2675
  const config = effective.config;
@@ -2451,9 +2678,9 @@ var openApiPlugin = definePlugin((options) => {
2451
2678
  sourceScope: effective.headersSource.scope,
2452
2679
  headers: config.headers ?? {},
2453
2680
  legacyHeaders: effective.headersSource.legacy?.headers
2454
- }).pipe(Effect6.mapError((err) => new Error(err.message)));
2681
+ }).pipe(Effect7.mapError((err) => new Error(err.message)));
2455
2682
  const resolvedQueryParams = yield* resolveHeaderValues(ctx, config.queryParams).pipe(
2456
- Effect6.mapError((err) => new Error(err.message))
2683
+ Effect7.mapError((err) => new Error(err.message))
2457
2684
  );
2458
2685
  if (config.oauth2) {
2459
2686
  const connectionId = yield* resolveOAuthConnectionId(ctx, {
@@ -2463,12 +2690,12 @@ var openApiPlugin = definePlugin((options) => {
2463
2690
  legacyOAuth2: effective.oauth2Source.legacy?.oauth2
2464
2691
  });
2465
2692
  if (!connectionId) {
2466
- return yield* Effect6.fail(
2693
+ return yield* Effect7.fail(
2467
2694
  new Error(`OAuth configuration for "${op.sourceId}" is missing a connection binding`)
2468
2695
  );
2469
2696
  }
2470
2697
  const accessToken = yield* ctx.connections.accessToken(connectionId).pipe(
2471
- Effect6.mapError(
2698
+ Effect7.mapError(
2472
2699
  (err) => new Error(
2473
2700
  `OAuth connection resolution failed: ${"message" in err ? err.message : String(err)}`
2474
2701
  )
@@ -2486,14 +2713,14 @@ var openApiPlugin = definePlugin((options) => {
2486
2713
  );
2487
2714
  return result;
2488
2715
  }),
2489
- resolveAnnotations: ({ ctx, sourceId, toolRows }) => Effect6.gen(function* () {
2716
+ resolveAnnotations: ({ ctx, sourceId, toolRows }) => Effect7.gen(function* () {
2490
2717
  const scopes = /* @__PURE__ */ new Set();
2491
2718
  for (const row of toolRows) {
2492
2719
  scopes.add(row.scope_id);
2493
2720
  }
2494
- const entries = yield* Effect6.forEach(
2721
+ const entries = yield* Effect7.forEach(
2495
2722
  [...scopes],
2496
- (scope) => Effect6.gen(function* () {
2723
+ (scope) => Effect7.gen(function* () {
2497
2724
  const ops = yield* ctx.storage.listOperationsBySource(sourceId, scope);
2498
2725
  const byId = /* @__PURE__ */ new Map();
2499
2726
  for (const op of ops) byId.set(op.toolId, op.binding);
@@ -2519,22 +2746,22 @@ var openApiPlugin = definePlugin((options) => {
2519
2746
  // when `canRefresh: true`, so a raw-text source reaching here
2520
2747
  // means stale UI state, which is worth surfacing to the caller.
2521
2748
  refreshSource: ({ ctx, sourceId, scope }) => refreshSourceInternal(ctx, sourceId, scope),
2522
- detect: ({ url }) => Effect6.gen(function* () {
2749
+ detect: ({ url }) => Effect7.gen(function* () {
2523
2750
  const trimmed = url.trim();
2524
2751
  if (!trimmed) return null;
2525
- const parsed = yield* Effect6.try({
2752
+ const parsed = yield* Effect7.try({
2526
2753
  try: () => new URL(trimmed),
2527
2754
  catch: (error) => error
2528
- }).pipe(Effect6.option);
2755
+ }).pipe(Effect7.option);
2529
2756
  if (Option5.isNone(parsed)) return null;
2530
2757
  const specText = yield* resolveSpecText(trimmed).pipe(
2531
- Effect6.provide(httpClientLayer),
2532
- Effect6.catch(() => Effect6.succeed(null))
2758
+ Effect7.provide(httpClientLayer),
2759
+ Effect7.catch(() => Effect7.succeed(null))
2533
2760
  );
2534
2761
  if (specText === null) return null;
2535
- const doc = yield* parse(specText).pipe(Effect6.catch(() => Effect6.succeed(null)));
2762
+ const doc = yield* parse(specText).pipe(Effect7.catch(() => Effect7.succeed(null)));
2536
2763
  if (!doc) return null;
2537
- const result = yield* extract(doc).pipe(Effect6.catch(() => Effect6.succeed(null)));
2764
+ const result = yield* extract(doc).pipe(Effect7.catch(() => Effect7.succeed(null)));
2538
2765
  if (!result) return null;
2539
2766
  const namespace = Option5.getOrElse(result.title, () => "api").toLowerCase().replace(/[^a-z0-9]+/g, "_");
2540
2767
  const name = Option5.getOrElse(result.title, () => namespace);
@@ -2545,7 +2772,15 @@ var openApiPlugin = definePlugin((options) => {
2545
2772
  name,
2546
2773
  namespace
2547
2774
  });
2548
- })
2775
+ }),
2776
+ // HTTP transport. `OpenApiHandlers` is the existing late-binding
2777
+ // Layer that requires `OpenApiExtensionService`; the host satisfies
2778
+ // it via the spec's `extensionService` Tag — at boot for local
2779
+ // (`composePluginHandlers(plugins, executor)`), per-request for
2780
+ // cloud (`providePluginExtensions(plugins)(executor)`).
2781
+ routes: () => OpenApiGroup,
2782
+ handlers: () => OpenApiHandlers,
2783
+ extensionService: OpenApiExtensionService
2549
2784
  };
2550
2785
  });
2551
2786
 
@@ -2581,10 +2816,6 @@ export {
2581
2816
  InvocationConfig,
2582
2817
  InvocationResult,
2583
2818
  extract,
2584
- resolveHeaders,
2585
- invoke,
2586
- invokeWithLayer,
2587
- annotationsForOperation,
2588
2819
  OAuth2AuthorizationCodeFlow,
2589
2820
  OAuth2ClientCredentialsFlow,
2590
2821
  OAuth2Flows,
@@ -2597,6 +2828,10 @@ export {
2597
2828
  previewSpec,
2598
2829
  openapiSchema,
2599
2830
  makeDefaultOpenapiStore,
2831
+ resolveHeaders,
2832
+ invoke,
2833
+ invokeWithLayer,
2834
+ annotationsForOperation,
2600
2835
  openApiPlugin
2601
2836
  };
2602
- //# sourceMappingURL=chunk-ZZ7TQ4JC.js.map
2837
+ //# sourceMappingURL=chunk-RBE3CVB4.js.map