// ---------------------------------------------------------------------------// Integrations HTTP API — the v2 catalog surface (was `sources`).//// An integration is the tenant-shared catalog identity (slug + description +// which plugin owns it). The executor is bound to its `{ tenant, subject }` from// the request auth, so integration routes carry no scope segment — the catalog// is tenant-level. Connections (owner-scoped credentials) live in their own// group; credential-binding endpoints are gone (folded into connections).// --------------------------------------------------------------------------- import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";import { Schema } from "effect";import { HealthCheckCandidate, HealthCheckSpec, IntegrationDetectionResult, IntegrationNotFoundError, IntegrationRemovalNotAllowedError, IntegrationSlug, InternalError,} from "@executor-js/sdk/shared"; // ---------------------------------------------------------------------------// Params// --------------------------------------------------------------------------- const IntegrationParams = { slug: IntegrationSlug }; // ---------------------------------------------------------------------------// Response / payload schemas// --------------------------------------------------------------------------- /** Where a credential value is carried — mirrors the SDK's * `AuthPlacementDescriptor`. */const PlacementDescriptor = Schema.Struct({ carrier: Schema.Literals(["header", "query", "env"]), name: Schema.String, prefix: Schema.String, /** Input variable this placement renders from (absent ⇒ `token`). Without * it the client cannot derive per-variable credential inputs for * multi-input methods. */ variable: Schema.optional(Schema.String), /** Static value rendered verbatim (no credential input). */ literal: Schema.optional(Schema.String),}); /** OAuth specifics — mirrors the SDK's `AuthMethodOAuthDescriptor`. */const OAuthDescriptor = Schema.Struct({ discoveryUrl: Schema.optional(Schema.String), authorizationUrl: Schema.optional(Schema.String), tokenUrl: Schema.optional(Schema.String), resource: Schema.optional(Schema.NullOr(Schema.String)), scopes: Schema.optional(Schema.Array(Schema.String)), registrationEndpoint: Schema.optional(Schema.String), supportsDynamicRegistration: Schema.optional(Schema.Boolean), supportsClientIdMetadataDocument: Schema.optional(Schema.Boolean),}); /** A single declared auth method — mirrors the SDK's `AuthMethodDescriptor`. */const AuthMethodDescriptorSchema = Schema.Struct({ id: Schema.String, label: Schema.String, kind: Schema.Literals(["oauth", "apikey", "header", "none"]), template: Schema.String, placements: Schema.optional(Schema.Array(PlacementDescriptor)), oauth: Schema.optional(OAuthDescriptor),}); /** Public projection of an integration — mirrors the SDK's `Integration`. */const IntegrationResponse = Schema.Struct({ slug: IntegrationSlug, /** Display name. */ name: Schema.String, description: Schema.String, /** The plugin that owns this integration kind (e.g. "openapi", "mcp"). */ kind: Schema.String, canRemove: Schema.Boolean, canRefresh: Schema.Boolean, /** Declared auth methods derived from the owning plugin's stored config. * Always present (possibly empty) so the client never handles absence. */ authMethods: Schema.Array(AuthMethodDescriptorSchema), /** Non-secret URL derived from opaque integration config for favicons. */ displayUrl: Schema.optional(Schema.String), /** Catalog family derived from opaque integration config for grouped display. */ family: Schema.optional(Schema.String),}); const UpdateIntegrationPayload = Schema.Struct({ name: Schema.optional(Schema.String), description: Schema.optional(Schema.String),}); const DetectRequest = Schema.Struct({ url: Schema.String.check(Schema.isMaxLength(2_048)),}); // Set (or clear, with null) the integration's declared health check. The spec// lives inside the owning plugin's opaque config; core only routes it.const SetHealthCheckPayload = Schema.Struct({ spec: Schema.NullOr(HealthCheckSpec),}); // ---------------------------------------------------------------------------// Error schemas with HTTP status annotations// --------------------------------------------------------------------------- const IntegrationNotFound = IntegrationNotFoundError.annotate({ httpApiStatus: 404 });const IntegrationRemovalNotAllowed = IntegrationRemovalNotAllowedError.annotate({ httpApiStatus: 409,}); // ---------------------------------------------------------------------------// Group// --------------------------------------------------------------------------- export const IntegrationsApi = HttpApiGroup.make("integrations") .add( HttpApiEndpoint.get("list", "/integrations", { success: Schema.Array(IntegrationResponse), error: InternalError, }), ) .add( HttpApiEndpoint.get("get", "/integrations/:slug", { params: IntegrationParams, success: IntegrationResponse, error: [InternalError, IntegrationNotFound], }), ) .add( HttpApiEndpoint.patch("update", "/integrations/:slug", { params: IntegrationParams, payload: UpdateIntegrationPayload, success: IntegrationResponse, error: [InternalError, IntegrationNotFound], }), ) .add( HttpApiEndpoint.delete("remove", "/integrations/:slug", { params: IntegrationParams, success: Schema.Struct({ removed: Schema.Boolean }), error: [InternalError, IntegrationRemovalNotAllowed], }), ) .add( HttpApiEndpoint.post("detect", "/integrations/detect", { payload: DetectRequest, success: Schema.Array(IntegrationDetectionResult), error: InternalError, }), ) // The integration's currently declared health check (null when none is set). .add( HttpApiEndpoint.get("healthCheckGet", "/integrations/:slug/health-check", { params: IntegrationParams, success: Schema.NullOr(HealthCheckSpec), error: InternalError, }), ) // Operations the user can pick as the health check, ranked non-destructive + // fewest-required-args first so the obvious identity endpoint floats to the top. .add( HttpApiEndpoint.get("healthCheckCandidates", "/integrations/:slug/health-check/candidates", { params: IntegrationParams, success: Schema.Array(HealthCheckCandidate), error: [InternalError, IntegrationNotFound], }), ) // Persist (or clear) the integration's health check. .add( HttpApiEndpoint.put("healthCheckSet", "/integrations/:slug/health-check", { params: IntegrationParams, payload: SetHealthCheckPayload, success: Schema.Struct({ ok: Schema.Boolean }), error: [InternalError, IntegrationNotFound], }), );
// ---------------------------------------------------------------------------
// Integrations HTTP API — the v2 catalog surface (was `sources`).
//
// An integration is the tenant-shared catalog identity (slug + description +
// which plugin owns it). The executor is bound to its `{ tenant, subject }` from
// the request auth, so integration routes carry no scope segment — the catalog
// is tenant-level. Connections (owner-scoped credentials) live in their own
// group; credential-binding endpoints are gone (folded into connections).
// ---------------------------------------------------------------------------
import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";
import { Schema } from "effect";
import {
HealthCheckCandidate,
HealthCheckSpec,
IntegrationDetectionResult,
IntegrationNotFoundError,
IntegrationRemovalNotAllowedError,
IntegrationSlug,
InternalError,
} from "@executor-js/sdk/shared";
// ---------------------------------------------------------------------------
// Params
// ---------------------------------------------------------------------------
const IntegrationParams = { slug: IntegrationSlug };
// ---------------------------------------------------------------------------
// Response / payload schemas
// ---------------------------------------------------------------------------
/** Where a credential value is carried — mirrors the SDK's
* `AuthPlacementDescriptor`. */
const PlacementDescriptor = Schema.Struct({
carrier: Schema.Literals(["header", "query", "env"]),
name: Schema.String,
prefix: Schema.String,
/** Input variable this placement renders from (absent ⇒ `token`). Without
* it the client cannot derive per-variable credential inputs for
* multi-input methods. */
variable: Schema.optional(Schema.String),
/** Static value rendered verbatim (no credential input). */
literal: Schema.optional(Schema.String),
});
/** OAuth specifics — mirrors the SDK's `AuthMethodOAuthDescriptor`. */
const OAuthDescriptor = Schema.Struct({
discoveryUrl: Schema.optional(Schema.String),
authorizationUrl: Schema.optional(Schema.String),
tokenUrl: Schema.optional(Schema.String),
resource: Schema.optional(Schema.NullOr(Schema.String)),
scopes: Schema.optional(Schema.Array(Schema.String)),
registrationEndpoint: Schema.optional(Schema.String),
supportsDynamicRegistration: Schema.optional(Schema.Boolean),
supportsClientIdMetadataDocument: Schema.optional(Schema.Boolean),
});
/** A single declared auth method — mirrors the SDK's `AuthMethodDescriptor`. */
const AuthMethodDescriptorSchema = Schema.Struct({
id: Schema.String,
label: Schema.String,
kind: Schema.Literals(["oauth", "apikey", "header", "none"]),
template: Schema.String,
placements: Schema.optional(Schema.Array(PlacementDescriptor)),
oauth: Schema.optional(OAuthDescriptor),
});
/** Public projection of an integration — mirrors the SDK's `Integration`. */
const IntegrationResponse = Schema.Struct({
slug: IntegrationSlug,
/** Display name. */
name: Schema.String,
description: Schema.String,
/** The plugin that owns this integration kind (e.g. "openapi", "mcp"). */
kind: Schema.String,
canRemove: Schema.Boolean,
canRefresh: Schema.Boolean,
/** Declared auth methods derived from the owning plugin's stored config.
* Always present (possibly empty) so the client never handles absence. */
authMethods: Schema.Array(AuthMethodDescriptorSchema),
/** Non-secret URL derived from opaque integration config for favicons. */
displayUrl: Schema.optional(Schema.String),
/** Catalog family derived from opaque integration config for grouped display. */
family: Schema.optional(Schema.String),
});
const UpdateIntegrationPayload = Schema.Struct({
name: Schema.optional(Schema.String),
description: Schema.optional(Schema.String),
});
const DetectRequest = Schema.Struct({
url: Schema.String.check(Schema.isMaxLength(2_048)),
});
// Set (or clear, with null) the integration's declared health check. The spec
// lives inside the owning plugin's opaque config; core only routes it.
const SetHealthCheckPayload = Schema.Struct({
spec: Schema.NullOr(HealthCheckSpec),
});
// ---------------------------------------------------------------------------
// Error schemas with HTTP status annotations
// ---------------------------------------------------------------------------
const IntegrationNotFound = IntegrationNotFoundError.annotate({ httpApiStatus: 404 });
const IntegrationRemovalNotAllowed = IntegrationRemovalNotAllowedError.annotate({
httpApiStatus: 409,
});
// ---------------------------------------------------------------------------
// Group
// ---------------------------------------------------------------------------
export const IntegrationsApi = HttpApiGroup.make("integrations")
.add(
HttpApiEndpoint.get("list", "/integrations", {
success: Schema.Array(IntegrationResponse),
error: InternalError,
}),
)
.add(
HttpApiEndpoint.get("get", "/integrations/:slug", {
params: IntegrationParams,
success: IntegrationResponse,
error: [InternalError, IntegrationNotFound],
}),
)
.add(
HttpApiEndpoint.patch("update", "/integrations/:slug", {
params: IntegrationParams,
payload: UpdateIntegrationPayload,
success: IntegrationResponse,
error: [InternalError, IntegrationNotFound],
}),
)
.add(
HttpApiEndpoint.delete("remove", "/integrations/:slug", {
params: IntegrationParams,
success: Schema.Struct({ removed: Schema.Boolean }),
error: [InternalError, IntegrationRemovalNotAllowed],
}),
)
.add(
HttpApiEndpoint.post("detect", "/integrations/detect", {
payload: DetectRequest,
success: Schema.Array(IntegrationDetectionResult),
error: InternalError,
}),
)
// The integration's currently declared health check (null when none is set).
.add(
HttpApiEndpoint.get("healthCheckGet", "/integrations/:slug/health-check", {
params: IntegrationParams,
success: Schema.NullOr(HealthCheckSpec),
error: InternalError,
}),
)
// Operations the user can pick as the health check, ranked non-destructive +
// fewest-required-args first so the obvious identity endpoint floats to the top.
.add(
HttpApiEndpoint.get("healthCheckCandidates", "/integrations/:slug/health-check/candidates", {
params: IntegrationParams,
success: Schema.Array(HealthCheckCandidate),
error: [InternalError, IntegrationNotFound],
}),
)
// Persist (or clear) the integration's health check.
.add(
HttpApiEndpoint.put("healthCheckSet", "/integrations/:slug/health-check", {
params: IntegrationParams,
payload: SetHealthCheckPayload,
success: Schema.Struct({ ok: Schema.Boolean }),
error: [InternalError, IntegrationNotFound],
}),
);