History

Remove legacy source oauth and discovery flows

Rhys Sullivan committed 7 months ago

Commit f25b0911 parent 813a1c724 files · +2 −1898Browse files

Unified · Split

24 files
  1. packages/clients/react/src/index.ts +0 −177
  2. packages/platform/api/package.json +0 −2
  3. packages/platform/api/src/api.ts +0 −2
  4. packages/platform/api/src/client.ts +0 −58
  5. packages/platform/api/src/http.ts +0 −2
  6. packages/platform/api/src/index.ts +0 −18
  7. packages/platform/api/src/oauth/api.ts +0 −99
  8. packages/platform/api/src/oauth/http.ts +0 −315
  9. packages/platform/api/src/sources/api.ts +0 −75
  10. packages/platform/api/src/sources/http.ts +0 −358
  11. packages/platform/internal/src/tools/executor-admin.ts +0 −190
  12. packages/platform/sdk/src/executor-effect.ts +0 −114
  13. packages/platform/sdk/src/executor.ts +0 −21
  14. packages/platform/sdk/src/operations.ts +0 −1
  15. packages/platform/sdk/src/runtime/index.ts +0 −1
  16. packages/platform/sdk/src/runtime/sources/source-auth-service.ts +0 −80
  17. packages/platform/sdk/src/runtime/sources/source-discovery.test.ts +0 −278
  18. packages/platform/sdk/src/schema/enums.ts +0 −16
  19. packages/platform/sdk/src/schema/index.ts +0 −1
  20. packages/platform/sdk/src/schema/models/source-discovery.ts +0 −16
  21. packages/platform/sdk/src/sources/contracts.ts +0 −10
  22. packages/platform/sdk/src/sources/discovery.ts +0 −56
  23. packages/sources/core/src/discovery-models.ts +1 −4
  24. packages/sources/core/src/discovery.ts +1 −4
packages/clients/react/src/index.ts+0−177
@@ -9,9 +9,7 @@ import type { · 8 unmodified lines
9 CreateSecretPayload,9 CreateSecretPayload,
10 CreateSecretResult,10 CreateSecretResult,
11 CreateSourcePayload,11 CreateSourcePayload,
12 CreateWorkspaceOauthClientPayload,
13 DeleteSecretResult,12 DeleteSecretResult,
14 DiscoverSourcePayload,
15 InstanceConfig,13 InstanceConfig,
16 LocalInstallation,14 LocalInstallation,
17 SecretListItem,15 SecretListItem,
@@ -21,11 +19,9 @@ import type { · 3 unmodified lines
21} from "@executor/platform-api";19} from "@executor/platform-api";
22import type {20import type {
23 Source,21 Source,
24 SourceDiscoveryResult,
25 SourceInspection,22 SourceInspection,
26 SourceInspectionDiscoverResult,23 SourceInspectionDiscoverResult,
27 SourceInspectionToolDetail,24 SourceInspectionToolDetail,
28 ScopeOauthClient as WorkspaceOauthClient,
29} from "@executor/platform-sdk/schema";25} from "@executor/platform-sdk/schema";
30import * as Cause from "effect/Cause";26import * as Cause from "effect/Cause";
31import * as Effect from "effect/Effect";27import * as Effect from "effect/Effect";
@@ -67,13 +63,6 @@ type SourceDiscoveryKeyParts = readonly [ · 35 unmodified lines
67 string,63 string,
68 number | null,64 number | null,
69];65];
70type WorkspaceOauthClientsKeyParts = readonly [
71 boolean,
72 Source["scopeId"],
73 string,
74 string,
75];
76
77type InvalidationTarget = {66type InvalidationTarget = {
78 workspaceId?: Source["scopeId"];67 workspaceId?: Source["scopeId"];
79 accountId?: string;68 accountId?: string;
@@ -83,7 +72,6 @@ type InvalidationTarget = { · 3 unmodified lines
83type ActiveQueryCollections = {72type ActiveQueryCollections = {
84 sourceLists: Set<string>;73 sourceLists: Set<string>;
85 sources: Set<string>;74 sources: Set<string>;
86 workspaceOauthClients: Set<string>;
87 inspections: Set<string>;75 inspections: Set<string>;
88 toolDetails: Set<string>;76 toolDetails: Set<string>;
89 discoveries: Set<string>;77 discoveries: Set<string>;
@@ -171,16 +159,6 @@ const encodeDiscoveryKey = ( · 81 unmodified lines
171): string =>159): string =>
172 encodeAtomKey([enabled, workspaceId, accountId, sourceId, query, limit] satisfies SourceDiscoveryKeyParts);160 encodeAtomKey([enabled, workspaceId, accountId, sourceId, query, limit] satisfies SourceDiscoveryKeyParts);
173 161
174const encodeWorkspaceOauthClientsKey = (
175 enabled: boolean,
176 workspaceId: Source["scopeId"],
177 accountId: string,
178 providerKey: string,
179): string =>
180 encodeAtomKey(
181 [enabled, workspaceId, accountId, providerKey] satisfies WorkspaceOauthClientsKeyParts,
182 );
183
184const toError = (cause: unknown): Error =>162const toError = (cause: unknown): Error =>
185 cause instanceof Error ? cause : new Error(String(cause));163 cause instanceof Error ? cause : new Error(String(cause));
186 164
@@ -391,26 +369,6 @@ const sourceDiscoveryAtom = Atom.family((key: string) => { · 204 unmodified lines
391 ).pipe(Atom.keepAlive);369 ).pipe(Atom.keepAlive);
392});370});
393 371
394const workspaceOauthClientsAtom = Atom.family((key: string) => {
395 const [enabled, workspaceId, accountId, providerKey] = decodeAtomKey<WorkspaceOauthClientsKeyParts>(key);
396
397 return Atom.make(
398 enabled
399 ? controlPlaneRequest({
400 accountId,
401 execute: (client) => client.sources.listWorkspaceOauthClients({
402 path: {
403 workspaceId,
404 },
405 urlParams: {
406 providerKey,
407 },
408 }),
409 })
410 : Effect.never,
411 ).pipe(Atom.keepAlive);
412});
413
414export type Loadable<T> =372export type Loadable<T> =
415 | { status: "loading" }373 | { status: "loading" }
416 | { status: "error"; error: Error }374 | { status: "error"; error: Error }
@@ -539,7 +497,6 @@ const setCachedAtomValue = <A>( · 122 unmodified lines
539const createActiveQueryCollections = (): ActiveQueryCollections => ({497const createActiveQueryCollections = (): ActiveQueryCollections => ({
540 sourceLists: new Set(),498 sourceLists: new Set(),
541 sources: new Set(),499 sources: new Set(),
542 workspaceOauthClients: new Set(),
543 inspections: new Set(),500 inspections: new Set(),
544 toolDetails: new Set(),501 toolDetails: new Set(),
545 discoveries: new Set(),502 discoveries: new Set(),
@@ -582,13 +539,6 @@ const invalidateTrackedQueries = ( · 36 unmodified lines
582 }539 }
583 });540 });
584 541
585 activeQueries.workspaceOauthClients.forEach((key) => {
586 const [enabled, workspaceId, accountId] = decodeAtomKey<WorkspaceOauthClientsKeyParts>(key);
587 if (enabled && targetMatches(target, workspaceId, accountId)) {
588 registry.refresh(workspaceOauthClientsAtom(key));
589 }
590 });
591
592 activeQueries.inspections.forEach((key) => {542 activeQueries.inspections.forEach((key) => {
593 const [enabled, workspaceId, accountId, sourceId] = decodeAtomKey<SourceKeyParts>(key);543 const [enabled, workspaceId, accountId, sourceId] = decodeAtomKey<SourceKeyParts>(key);
594 if (enabled && targetMatches(target, workspaceId, accountId, sourceId)) {544 if (enabled && targetMatches(target, workspaceId, accountId, sourceId)) {
@@ -946,37 +896,6 @@ export const useSource = (sourceId: string): Loadable<Source> => { · 351 unmodified lines
946 return workspace.enabled ? source : pendingLoadable(workspace.workspace);896 return workspace.enabled ? source : pendingLoadable(workspace.workspace);
947};897};
948 898
949export const useWorkspaceOauthClients = (
950 providerKey: string | null,
951): Loadable<ReadonlyArray<WorkspaceOauthClient>> => {
952 const workspace = useWorkspaceRequestContext();
953 const key = encodeWorkspaceOauthClientsKey(
954 workspace.enabled && providerKey !== null,
955 workspace.workspaceId,
956 workspace.accountId,
957 providerKey ?? "",
958 );
959 useTrackActiveKey(
960 "workspaceOauthClients",
961 key,
962 workspace.enabled && providerKey !== null,
963 );
964 const oauthClients = useLoadableAtom(workspaceOauthClientsAtom(key));
965
966 if (!workspace.enabled) {
967 return pendingLoadable(workspace.workspace);
968 }
969
970 if (providerKey === null) {
971 return {
972 status: "ready",
973 data: [],
974 };
975 }
976
977 return oauthClients;
978};
979
980export const useSourceInspection = (sourceId: string): Loadable<SourceInspection> => {899export const useSourceInspection = (sourceId: string): Loadable<SourceInspection> => {
981 const workspace = useWorkspaceRequestContext();900 const workspace = useWorkspaceRequestContext();
982 const requestedSourceId = workspace.enabled901 const requestedSourceId = workspace.enabled
@@ -1214,116 +1133,20 @@ export const useRemoveSource = () => · 231 unmodified lines
1214 },1133 },
1215 );1134 );
1216 1135
1217export const useDiscoverSource = () =>
1218 useSourceMutation<DiscoverSourcePayload, SourceDiscoveryResult>(
1219 React.useCallback(
1220 ({ accountId, payload }) =>
1221 runControlPlane({
1222 accountId,
1223 execute: (client) => client.sources.discover({
1224 payload,
1225 }),
1226 }),
1227 [],
1228 ),
1229 );
1230
1231export const useCreateWorkspaceOauthClient = () =>
1232 useSourceMutation<CreateWorkspaceOauthClientPayload, WorkspaceOauthClient>(
1233 React.useCallback(
1234 ({ workspaceId, accountId, payload }) =>
1235 runControlPlane({
1236 accountId,
1237 execute: (client) => client.sources.createWorkspaceOauthClient({
1238 path: {
1239 workspaceId,
1240 },
1241 payload,
1242 }),
1243 }),
1244 [],
1245 ),
1246 {
1247 onSuccess: (context) => {
1248 context.invalidateQueries({
1249 workspaceId: context.workspaceId,
1250 accountId: context.accountId,
1251 });
1252 },
1253 },
1254 );
1255
1256export const useRemoveWorkspaceOauthClient = () =>
1257 useSourceMutation<WorkspaceOauthClient["id"], { removed: boolean }>(
1258 React.useCallback(
1259 ({ workspaceId, accountId, payload }) =>
1260 runControlPlane({
1261 accountId,
1262 execute: (client) => client.sources.removeWorkspaceOauthClient({
1263 path: {
1264 workspaceId,
1265 oauthClientId: payload,
1266 },
1267 }),
1268 }),
1269 [],
1270 ),
1271 {
1272 onSuccess: (context) => {
1273 context.invalidateQueries({
1274 workspaceId: context.workspaceId,
1275 accountId: context.accountId,
1276 });
1277 },
1278 },
1279 );
1280
1281export const useRemoveProviderAuthGrant = () =>
1282 useSourceMutation<
1283 Extract<Source["auth"], { kind: "provider_grant_ref" }>["grantId"],
1284 { removed: boolean }
1285 >(
1286 React.useCallback(
1287 ({ workspaceId, accountId, payload }) =>
1288 runControlPlane({
1289 accountId,
1290 execute: (client) => client.sources.removeProviderAuthGrant({
1291 path: {
1292 workspaceId,
1293 grantId: payload,
1294 },
1295 }),
1296 }),
1297 [],
1298 ),
1299 {
1300 onSuccess: (context) => {
1301 context.invalidateQueries({
1302 workspaceId: context.workspaceId,
1303 accountId: context.accountId,
1304 });
1305 },
1306 },
1307 );
1308
1309export type {1136export type {
1310 CreateSecretPayload,1137 CreateSecretPayload,
1311 CreateSecretResult,1138 CreateSecretResult,
1312 CreateSourcePayload,1139 CreateSourcePayload,
1313 CreateWorkspaceOauthClientPayload,
1314 DeleteSecretResult,1140 DeleteSecretResult,
1315 DiscoverSourcePayload,
1316 InstanceConfig,1141 InstanceConfig,
1317 LocalInstallation,1142 LocalInstallation,
1318 1143
1319 SecretListItem,1144 SecretListItem,
1320 Source,1145 Source,
1321 SourceDiscoveryResult,
1322 SourceInspection,1146 SourceInspection,
1323 SourceInspectionDiscoverResult,1147 SourceInspectionDiscoverResult,
1324 SourceInspectionToolDetail,1148 SourceInspectionToolDetail,
1325 UpdateSecretPayload,1149 UpdateSecretPayload,
1326 UpdateSecretResult,1150 UpdateSecretResult,
1327 UpdateSourcePayload,1151 UpdateSourcePayload,
1328 WorkspaceOauthClient,
1329};1152};
packages/platform/api/package.json+0−2
@@ -12,8 +12,6 @@ · 11 unmodified lines
12 "./local/api": "./src/local/api.ts",12 "./local/api": "./src/local/api.ts",
13 "./local/http": "./src/local/http.ts",13 "./local/http": "./src/local/http.ts",
14 "./local-context": "./src/local-context.ts",14 "./local-context": "./src/local-context.ts",
15 "./oauth/api": "./src/oauth/api.ts",
16 "./oauth/http": "./src/oauth/http.ts",
17 "./policies/api": "./src/policies/api.ts",15 "./policies/api": "./src/policies/api.ts",
18 "./policies/http": "./src/policies/http.ts",16 "./policies/http": "./src/policies/http.ts",
19 "./sources/api": "./src/sources/api.ts",17 "./sources/api": "./src/sources/api.ts",
packages/platform/api/src/api.ts+0−2
@@ -2,13 +2,11 @@ import { HttpApi, OpenApi } from "@effect/platform"; · 1 unmodified line
2 2
3import { ExecutionsApi } from "./executions/api";3import { ExecutionsApi } from "./executions/api";
4import { LocalApi } from "./local/api";4import { LocalApi } from "./local/api";
5import { OAuthApi } from "./oauth/api";
6import { PoliciesApi } from "./policies/api";5import { PoliciesApi } from "./policies/api";
7import { SourcesApi } from "./sources/api";6import { SourcesApi } from "./sources/api";
8 7
9export class ExecutorApi extends HttpApi.make("executor")8export class ExecutorApi extends HttpApi.make("executor")
10 .add(LocalApi)9 .add(LocalApi)
11 .add(OAuthApi)
12 .add(SourcesApi)10 .add(SourcesApi)
13 .add(PoliciesApi)11 .add(PoliciesApi)
14 .add(ExecutionsApi)12 .add(ExecutionsApi)
packages/platform/api/src/client.ts+0−58
@@ -34,19 +34,6 @@ export type ExecutorApiClient = { · 33 unmodified lines
34 remove: PromiseMethod<ExecutorApiEffectClient["policies"]["remove"]>;34 remove: PromiseMethod<ExecutorApiEffectClient["policies"]["remove"]>;
35 };35 };
36 sources: {36 sources: {
37 discover: PromiseMethod<ExecutorApiEffectClient["sources"]["discover"]>;
38 listWorkspaceOauthClients: PromiseMethod<
39 ExecutorApiEffectClient["sources"]["listWorkspaceOauthClients"]
40 >;
41 createWorkspaceOauthClient: PromiseMethod<
42 ExecutorApiEffectClient["sources"]["createWorkspaceOauthClient"]
43 >;
44 removeWorkspaceOauthClient: PromiseMethod<
45 ExecutorApiEffectClient["sources"]["removeWorkspaceOauthClient"]
46 >;
47 removeProviderAuthGrant: PromiseMethod<
48 ExecutorApiEffectClient["sources"]["removeProviderAuthGrant"]
49 >;
50 list: PromiseMethod<ExecutorApiEffectClient["sources"]["list"]>;37 list: PromiseMethod<ExecutorApiEffectClient["sources"]["list"]>;
51 create: PromiseMethod<ExecutorApiEffectClient["sources"]["create"]>;38 create: PromiseMethod<ExecutorApiEffectClient["sources"]["create"]>;
52 get: PromiseMethod<ExecutorApiEffectClient["sources"]["get"]>;39 get: PromiseMethod<ExecutorApiEffectClient["sources"]["get"]>;
@@ -59,16 +46,6 @@ export type ExecutorApiClient = { · 6 unmodified lines
59 >;46 >;
60 credentialPage: PromiseMethod<ExecutorApiEffectClient["sources"]["credentialPage"]>;47 credentialPage: PromiseMethod<ExecutorApiEffectClient["sources"]["credentialPage"]>;
61 credentialSubmit: PromiseMethod<ExecutorApiEffectClient["sources"]["credentialSubmit"]>;48 credentialSubmit: PromiseMethod<ExecutorApiEffectClient["sources"]["credentialSubmit"]>;
62 credentialComplete: PromiseMethod<
63 ExecutorApiEffectClient["sources"]["credentialComplete"]
64 >;
65 providerOauthComplete: PromiseMethod<
66 ExecutorApiEffectClient["sources"]["providerOauthComplete"]
67 >;
68 };
69 oauth: {
70 startSourceAuth: PromiseMethod<ExecutorApiEffectClient["oauth"]["startSourceAuth"]>;
71 sourceAuthCallback: PromiseMethod<ExecutorApiEffectClient["oauth"]["sourceAuthCallback"]>;
72 };49 };
73 executions: {50 executions: {
74 create: PromiseMethod<ExecutorApiEffectClient["executions"]["create"]>;51 create: PromiseMethod<ExecutorApiEffectClient["executions"]["create"]>;
@@ -125,23 +102,6 @@ export const createExecutorApiClient = async (input: { · 50 unmodified lines
125 remove: wrapMethod(effectClientPromise, (client) => client.policies.remove),102 remove: wrapMethod(effectClientPromise, (client) => client.policies.remove),
126 },103 },
127 sources: {104 sources: {
128 discover: wrapMethod(effectClientPromise, (client) => client.sources.discover),
129 listWorkspaceOauthClients: wrapMethod(
130 effectClientPromise,
131 (client) => client.sources.listWorkspaceOauthClients,
132 ),
133 createWorkspaceOauthClient: wrapMethod(
134 effectClientPromise,
135 (client) => client.sources.createWorkspaceOauthClient,
136 ),
137 removeWorkspaceOauthClient: wrapMethod(
138 effectClientPromise,
139 (client) => client.sources.removeWorkspaceOauthClient,
140 ),
141 removeProviderAuthGrant: wrapMethod(
142 effectClientPromise,
143 (client) => client.sources.removeProviderAuthGrant,
144 ),
145 list: wrapMethod(effectClientPromise, (client) => client.sources.list),105 list: wrapMethod(effectClientPromise, (client) => client.sources.list),
146 create: wrapMethod(effectClientPromise, (client) => client.sources.create),106 create: wrapMethod(effectClientPromise, (client) => client.sources.create),
147 get: wrapMethod(effectClientPromise, (client) => client.sources.get),107 get: wrapMethod(effectClientPromise, (client) => client.sources.get),
@@ -164,24 +124,6 @@ export const createExecutorApiClient = async (input: { · 16 unmodified lines
164 effectClientPromise,124 effectClientPromise,
165 (client) => client.sources.credentialSubmit,125 (client) => client.sources.credentialSubmit,
166 ),126 ),
167 credentialComplete: wrapMethod(
168 effectClientPromise,
169 (client) => client.sources.credentialComplete,
170 ),
171 providerOauthComplete: wrapMethod(
172 effectClientPromise,
173 (client) => client.sources.providerOauthComplete,
174 ),
175 },
176 oauth: {
177 startSourceAuth: wrapMethod(
178 effectClientPromise,
179 (client) => client.oauth.startSourceAuth,
180 ),
181 sourceAuthCallback: wrapMethod(
182 effectClientPromise,
183 (client) => client.oauth.sourceAuthCallback,
184 ),
185 },127 },
186 executions: {128 executions: {
187 create: wrapMethod(effectClientPromise, (client) => client.executions.create),129 create: wrapMethod(effectClientPromise, (client) => client.executions.create),
packages/platform/api/src/http.ts+0−2
@@ -6,13 +6,11 @@ import { ExecutorApi } from "./api"; · 5 unmodified lines
6import { createControlPlaneExecutorLayer } from "./executor-context";6import { createControlPlaneExecutorLayer } from "./executor-context";
7import { ExecutorExecutionsLive } from "./executions/http";7import { ExecutorExecutionsLive } from "./executions/http";
8import { ExecutorLocalLive } from "./local/http";8import { ExecutorLocalLive } from "./local/http";
9import { ExecutorOAuthLive } from "./oauth/http";
10import { ExecutorPoliciesLive } from "./policies/http";9import { ExecutorPoliciesLive } from "./policies/http";
11import { ExecutorSourcesLive } from "./sources/http";10import { ExecutorSourcesLive } from "./sources/http";
12 11
13export const ExecutorApiLive = HttpApiBuilder.api(ExecutorApi).pipe(12export const ExecutorApiLive = HttpApiBuilder.api(ExecutorApi).pipe(
14 Layer.provide(ExecutorLocalLive),13 Layer.provide(ExecutorLocalLive),
15 Layer.provide(ExecutorOAuthLive),
16 Layer.provide(ExecutorSourcesLive),14 Layer.provide(ExecutorSourcesLive),
17 Layer.provide(ExecutorPoliciesLive),15 Layer.provide(ExecutorPoliciesLive),
18 Layer.provide(ExecutorExecutionsLive),16 Layer.provide(ExecutorExecutionsLive),
packages/platform/api/src/index.ts+0−18
@@ -38,28 +38,10 @@ export { · 37 unmodified lines
38} from "./local/api";38} from "./local/api";
39 39
40export {40export {
41 OAuthApi,
42 StartSourceOAuthPayloadSchema,
43 StartSourceOAuthResultSchema,
44 CompleteSourceOAuthResultSchema,
45 SourceOAuthPopupFailureResultSchema,
46 SourceOAuthPopupResultSchema,
47 SourceOAuthPopupSuccessResultSchema,
48 type StartSourceOAuthPayload,
49 type StartSourceOAuthResult,
50 type CompleteSourceOAuthResult,
51 type SourceOAuthPopupResult,
52} from "./oauth/api";
53
54export {
55 CreateWorkspaceOauthClientPayloadSchema,
56 CreateSourcePayloadSchema,41 CreateSourcePayloadSchema,
57 DiscoverSourcePayloadSchema,
58 SourcesApi,42 SourcesApi,
59 UpdateSourcePayloadSchema,43 UpdateSourcePayloadSchema,
60 type CreateWorkspaceOauthClientPayload,
61 type CreateSourcePayload,44 type CreateSourcePayload,
62 type DiscoverSourcePayload,
63 type UpdateSourcePayload,45 type UpdateSourcePayload,
64} from "./sources/api";46} from "./sources/api";
65 47
packages/platform/api/src/oauth/api.ts+0−99
@@ -1,99 +0,0 @@
1import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform";
2import {
3 JsonObjectSchema,
4 SecretRefSchema,
5 SourceAuthSessionIdSchema,
6 ScopeIdSchema as WorkspaceIdSchema,
7} from "@executor/platform-sdk/schema";
8import * as Schema from "effect/Schema";
9
10import {
11 ControlPlaneBadRequestError,
12 ControlPlaneForbiddenError,
13 ControlPlaneStorageError,
14 ControlPlaneUnauthorizedError,
15} from "@executor/platform-sdk/errors";
16import { TrimmedNonEmptyStringSchema } from "@executor/platform-sdk/string-schemas";
17
18const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema);
19
20const HtmlSchema = HttpApiSchema.Text({
21 contentType: "text/html",
22});
23
24export const StartSourceOAuthPayloadSchema = Schema.Struct({
25 providerKey: TrimmedNonEmptyStringSchema,
26 name: Schema.optional(Schema.NullOr(Schema.String)),
27 config: Schema.optional(Schema.NullOr(JsonObjectSchema)),
28});
29
30export type StartSourceOAuthPayload = typeof StartSourceOAuthPayloadSchema.Type;
31
32export const StartSourceOAuthResultSchema = Schema.Struct({
33 sessionId: SourceAuthSessionIdSchema,
34 authorizationUrl: Schema.String,
35});
36
37export type StartSourceOAuthResult = typeof StartSourceOAuthResultSchema.Type;
38
39export const SourceOAuthAuthSchema = Schema.Struct({
40 kind: Schema.Literal("oauth2"),
41 headerName: Schema.String,
42 prefix: Schema.String,
43 accessToken: SecretRefSchema,
44 refreshToken: Schema.NullOr(SecretRefSchema),
45});
46
47export const CompleteSourceOAuthResultSchema = Schema.Struct({
48 sessionId: SourceAuthSessionIdSchema,
49 auth: SourceOAuthAuthSchema,
50});
51
52export type CompleteSourceOAuthResult = typeof CompleteSourceOAuthResultSchema.Type;
53
54export const SourceOAuthPopupSuccessResultSchema = Schema.Struct({
55 type: Schema.Literal("executor:oauth-result"),
56 ok: Schema.Literal(true),
57 sessionId: SourceAuthSessionIdSchema,
58 auth: SourceOAuthAuthSchema,
59});
60
61export const SourceOAuthPopupFailureResultSchema = Schema.Struct({
62 type: Schema.Literal("executor:oauth-result"),
63 ok: Schema.Literal(false),
64 sessionId: Schema.Null,
65 error: Schema.String,
66});
67
68export const SourceOAuthPopupResultSchema = Schema.Union(
69 SourceOAuthPopupSuccessResultSchema,
70 SourceOAuthPopupFailureResultSchema,
71);
72
73export type SourceOAuthPopupResult = typeof SourceOAuthPopupResultSchema.Type;
74
75const OAuthCallbackUrlParamsSchema = Schema.Struct({
76 state: Schema.String,
77 code: Schema.optional(Schema.String),
78 error: Schema.optional(Schema.String),
79 error_description: Schema.optional(Schema.String),
80});
81
82export class OAuthApi extends HttpApiGroup.make("oauth")
83 .add(
84 HttpApiEndpoint.post("startSourceAuth")`/workspaces/${workspaceIdParam}/oauth/source-auth/start`
85 .setPayload(StartSourceOAuthPayloadSchema)
86 .addSuccess(StartSourceOAuthResultSchema)
87 .addError(ControlPlaneBadRequestError)
88 .addError(ControlPlaneUnauthorizedError)
89 .addError(ControlPlaneForbiddenError)
90 .addError(ControlPlaneStorageError),
91 )
92 .add(
93 HttpApiEndpoint.get("sourceAuthCallback")`/oauth/source-auth/callback`
94 .setUrlParams(OAuthCallbackUrlParamsSchema)
95 .addSuccess(HtmlSchema)
96 .addError(ControlPlaneBadRequestError)
97 .addError(ControlPlaneStorageError),
98 )
99 .prefix("/v1") {}
packages/platform/api/src/oauth/http.ts+0−315
@@ -1,315 +0,0 @@
1import {
2 HttpApiBuilder,
3 HttpServerRequest,
4 HttpServerResponse,
5} from "@effect/platform";
6import type { ScopeId as WorkspaceId } from "@executor/platform-sdk/schema";
7import * as Cause from "effect/Cause";
8import * as Either from "effect/Either";
9import * as Effect from "effect/Effect";
10import * as Schema from "effect/Schema";
11
12import { ExecutorApi } from "../api";
13import {
14 ControlPlaneBadRequestError,
15 ControlPlaneStorageError,
16} from "../errors";
17import {
18 getControlPlaneExecutor,
19 resolveRequestedLocalWorkspace,
20} from "../local-context";
21import {
22 SourceOAuthPopupResultSchema,
23 type SourceOAuthPopupResult,
24} from "./api";
25
26const OAUTH_RESULT_STORAGE_KEY_PREFIX = "executor:oauth-result:";
27
28const escapeHtml = (value: string): string =>
29 value
30 .replaceAll("&", "&amp;")
31 .replaceAll("<", "&lt;")
32 .replaceAll(">", "&gt;")
33 .replaceAll('"', "&quot;")
34 .replaceAll("'", "&#39;");
35
36const serializeValueForScript = (value: unknown): string =>
37 JSON.stringify(value)
38 .replaceAll("<", "\\u003c")
39 .replaceAll(">", "\\u003e")
40 .replaceAll("&", "\\u0026");
41
42const encodeSourceOAuthPopupResultJson = Schema.encodeSync(
43 Schema.parseJson(SourceOAuthPopupResultSchema),
44);
45
46const serializeJsonLiteralForScript = (value: string): string =>
47 value
48 .replaceAll("<", "\\u003c")
49 .replaceAll(">", "\\u003e")
50 .replaceAll("&", "\\u0026");
51
52const htmlResponse = (html: string, status = 200) =>
53 HttpServerResponse.html(html).pipe(
54 HttpServerResponse.setStatus(status),
55 );
56
57const readHeader = (headers: unknown, name: string): string | null => {
58 if (headers instanceof Headers) {
59 return headers.get(name);
60 }
61
62 if (headers && typeof headers === "object") {
63 const record = headers as Record<string, unknown>;
64 const direct = record[name];
65 if (typeof direct === "string" && direct.length > 0) {
66 return direct;
67 }
68
69 const lower = record[name.toLowerCase()];
70 if (typeof lower === "string" && lower.length > 0) {
71 return lower;
72 }
73 }
74
75 return null;
76};
77
78const resolveRequestOrigin = (request: { url: string; headers: unknown }): string | null => {
79 try {
80 return new URL(request.url).origin;
81 } catch {
82 const forwardedHost = readHeader(request.headers, "x-forwarded-host");
83 const host = forwardedHost ?? readHeader(request.headers, "host");
84 if (!host) {
85 return null;
86 }
87
88 const forwardedProto = readHeader(request.headers, "x-forwarded-proto");
89 const protocol = forwardedProto && forwardedProto.length > 0 ? forwardedProto : "http";
90 return `${protocol}://${host}`;
91 }
92};
93
94const toStartSourceOAuthError = (input: {
95 workspaceId: WorkspaceId;
96 providerKey: string;
97 cause: Cause.Cause<unknown>;
98}) => {
99 const pretty = Cause.pretty(input.cause);
100 console.error("oauth.start_source_auth failed", {
101 workspaceId: input.workspaceId,
102 providerKey: input.providerKey,
103 pretty,
104 });
105
106 if (pretty.includes("Invalid URL") || pretty.includes("required")) {
107 return new ControlPlaneBadRequestError({
108 operation: "oauth.start_source_auth",
109 message: pretty,
110 details: pretty,
111 });
112 }
113
114 return new ControlPlaneStorageError({
115 operation: "oauth.start_source_auth",
116 message: pretty,
117 details: pretty,
118 });
119};
120
121const sourceOAuthPopupResultDocument = (input: {
122 title: string;
123 message: string;
124 status: "connected" | "failed";
125 sessionId: string;
126 payload: SourceOAuthPopupResult;
127}) => `<!doctype html>
128<html lang="en">
129 <head>
130 <meta charset="utf-8" />
131 <meta name="viewport" content="width=device-width, initial-scale=1" />
132 <title>${escapeHtml(input.title)}</title>
133 <style>
134 :root {
135 --bg: #fafaf9;
136 --surface: #ffffff;
137 --text: #1c1917;
138 --text-secondary: #78716c;
139 --border: #e7e5e4;
140 --green: #16a34a;
141 --green-light: #f0fdf4;
142 --green-border: #bbf7d0;
143 --red: #dc2626;
144 --red-light: #fef2f2;
145 --red-border: #fecaca;
146 }
147
148 * { box-sizing: border-box; margin: 0; padding: 0; }
149
150 body {
151 min-height: 100vh;
152 display: flex;
153 align-items: center;
154 justify-content: center;
155 background: var(--bg);
156 color: var(--text);
157 font-family: system-ui, -apple-system, sans-serif;
158 padding: 24px;
159 }
160
161 .card {
162 width: 100%;
163 max-width: 420px;
164 background: var(--surface);
165 border: 1px solid var(--border);
166 border-radius: 16px;
167 padding: 28px;
168 text-align: center;
169 }
170
171 .status {
172 display: inline-flex;
173 align-items: center;
174 justify-content: center;
175 padding: 6px 12px;
176 border-radius: 999px;
177 font-size: 12px;
178 font-weight: 600;
179 margin-bottom: 14px;
180 }
181
182 .status[data-status='connected'] {
183 background: var(--green-light);
184 border: 1px solid var(--green-border);
185 color: var(--green);
186 }
187
188 .status[data-status='failed'] {
189 background: var(--red-light);
190 border: 1px solid var(--red-border);
191 color: var(--red);
192 }
193
194 h1 {
195 font-size: 22px;
196 line-height: 1.2;
197 margin-bottom: 10px;
198 }
199
200 p {
201 color: var(--text-secondary);
202 font-size: 14px;
203 line-height: 1.6;
204 }
205 </style>
206 </head>
207 <body>
208 <main class="card">
209 <div class="status" data-status="${escapeHtml(input.status)}">${escapeHtml(input.status)}</div>
210 <h1>${escapeHtml(input.title)}</h1>
211 <p>${escapeHtml(input.message)}</p>
212 </main>
213 <script>
214 (() => {
215 const payload = ${serializeJsonLiteralForScript(encodeSourceOAuthPopupResultJson(input.payload))};
216 const storageKey = ${serializeValueForScript(`${OAUTH_RESULT_STORAGE_KEY_PREFIX}${input.sessionId}`)};
217 try {
218 window.localStorage.setItem(storageKey, JSON.stringify(payload));
219 } catch {}
220 try {
221 if (window.opener) {
222 window.opener.postMessage(payload, window.location.origin);
223 }
224 } finally {
225 window.setTimeout(() => window.close(), 120);
226 }
227 })();
228 </script>
229 </body>
230</html>`;
231
232export const ExecutorOAuthLive = HttpApiBuilder.group(
233 ExecutorApi,
234 "oauth",
235 (handlers) =>
236 handlers
237 .handle("startSourceAuth", ({ path, payload }) =>
238 resolveRequestedLocalWorkspace("oauth.start_source_auth", path.workspaceId).pipe(
239 Effect.flatMap((executor) =>
240 Effect.gen(function* () {
241 const request = yield* HttpServerRequest.HttpServerRequest;
242 return yield* executor.oauth.startSourceAuth({
243 baseUrl: resolveRequestOrigin(request),
244 displayName: payload.name,
245 provider: {
246 providerKey: payload.providerKey,
247 config: payload.config ?? null,
248 },
249 });
250 }).pipe(
251 Effect.catchAllCause((cause) =>
252 Effect.fail(
253 toStartSourceOAuthError({
254 workspaceId: path.workspaceId,
255 providerKey: payload.providerKey,
256 cause,
257 }),
258 ),
259 ),
260 )
261 ),
262 ),
263 )
264 .handle("sourceAuthCallback", ({ urlParams }) =>
265 Effect.flatMap(getControlPlaneExecutor(), (executor) =>
266 Effect.gen(function* () {
267 const completed = yield* Effect.either(
268 executor.oauth.completeSourceAuth({
269 state: urlParams.state,
270 code: urlParams.code,
271 error: urlParams.error,
272 errorDescription: urlParams.error_description,
273 }),
274 );
275
276 if (Either.isRight(completed)) {
277 return htmlResponse(
278 sourceOAuthPopupResultDocument({
279 title: "OAuth connected",
280 message: "OAuth credentials are ready. Return to the source form to finish saving.",
281 status: "connected",
282 sessionId: completed.right.sessionId,
283 payload: {
284 type: "executor:oauth-result",
285 ok: true,
286 sessionId: completed.right.sessionId,
287 auth: completed.right.auth,
288 },
289 }),
290 );
291 }
292
293 const message = completed.left instanceof Error
294 ? completed.left.message
295 : "Failed completing OAuth";
296
297 return htmlResponse(
298 sourceOAuthPopupResultDocument({
299 title: "OAuth failed",
300 message,
301 status: "failed",
302 sessionId: "failed",
303 payload: {
304 type: "executor:oauth-result",
305 ok: false,
306 sessionId: null,
307 error: message,
308 },
309 }),
310 500,
311 );
312 }),
313 ),
314 ),
315);
packages/platform/api/src/sources/api.ts+0−75
@@ -1,13 +1,9 @@
1import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform";1import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform";
2import {2import {
3 CreateSourcePayloadSchema,3 CreateSourcePayloadSchema,
4 CreateScopeOauthClientPayloadSchema as CreateWorkspaceOauthClientPayloadSchema,
5 CredentialOauthCompleteUrlParamsSchema,
6 CredentialPageUrlParamsSchema,4 CredentialPageUrlParamsSchema,
7 CredentialSubmitPayloadSchema,5 CredentialSubmitPayloadSchema,
8 DiscoverSourcePayloadSchema,
9 UpdateSourcePayloadSchema,6 UpdateSourcePayloadSchema,
10 ScopeOauthClientQuerySchema as WorkspaceOauthClientQuerySchema,
11} from "@executor/platform-sdk/contracts";7} from "@executor/platform-sdk/contracts";
12import {8import {
13 ControlPlaneBadRequestError,9 ControlPlaneBadRequestError,
@@ -17,90 +13,35 @@ import { · 3 unmodified lines
17 ControlPlaneUnauthorizedError,13 ControlPlaneUnauthorizedError,
18} from "@executor/platform-sdk/errors";14} from "@executor/platform-sdk/errors";
19import {15import {
20 ProviderAuthGrantIdSchema,
21 SourceDiscoveryResultSchema,
22 SourceIdSchema,16 SourceIdSchema,
23 SourceInspectionDiscoverPayloadSchema,17 SourceInspectionDiscoverPayloadSchema,
24 SourceInspectionDiscoverResultSchema,18 SourceInspectionDiscoverResultSchema,
25 SourceInspectionSchema,19 SourceInspectionSchema,
26 SourceInspectionToolDetailSchema,20 SourceInspectionToolDetailSchema,
27 SourceSchema,21 SourceSchema,
28 ScopeOauthClientIdSchema as WorkspaceOauthClientIdSchema,
29 ScopeOauthClientSchema as WorkspaceOauthClientSchema,
30 ScopeIdSchema as WorkspaceIdSchema,22 ScopeIdSchema as WorkspaceIdSchema,
31} from "@executor/platform-sdk/schema";23} from "@executor/platform-sdk/schema";
32import * as Schema from "effect/Schema";24import * as Schema from "effect/Schema";
33 25
34export type {26export type {
35 CreateSourcePayload,27 CreateSourcePayload,
36 CreateScopeOauthClientPayload as CreateWorkspaceOauthClientPayload,
37 DiscoverSourcePayload,
38 UpdateSourcePayload,28 UpdateSourcePayload,
39} from "@executor/platform-sdk/contracts";29} from "@executor/platform-sdk/contracts";
40 30
41export {31export {
42 CreateSourcePayloadSchema,32 CreateSourcePayloadSchema,
43 CreateWorkspaceOauthClientPayloadSchema,
44 DiscoverSourcePayloadSchema,
45 UpdateSourcePayloadSchema,33 UpdateSourcePayloadSchema,
46};34};
47 35
48const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema);36const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema);
49const sourceIdParam = HttpApiSchema.param("sourceId", SourceIdSchema);37const sourceIdParam = HttpApiSchema.param("sourceId", SourceIdSchema);
50const toolPathParam = HttpApiSchema.param("toolPath", Schema.String);38const toolPathParam = HttpApiSchema.param("toolPath", Schema.String);
51const oauthClientIdParam = HttpApiSchema.param(
52 "oauthClientId",
53 WorkspaceOauthClientIdSchema,
54);
55const grantIdParam = HttpApiSchema.param("grantId", ProviderAuthGrantIdSchema);
56 39
57const HtmlSchema = HttpApiSchema.Text({40const HtmlSchema = HttpApiSchema.Text({
58 contentType: "text/html",41 contentType: "text/html",
59});42});
60 43
61export class SourcesApi extends HttpApiGroup.make("sources")44export class SourcesApi extends HttpApiGroup.make("sources")
62 .add(
63 HttpApiEndpoint.post("discover")`/sources/discover`
64 .setPayload(DiscoverSourcePayloadSchema)
65 .addSuccess(SourceDiscoveryResultSchema)
66 .addError(ControlPlaneBadRequestError)
67 .addError(ControlPlaneUnauthorizedError)
68 .addError(ControlPlaneForbiddenError),
69 )
70 .add(
71 HttpApiEndpoint.get("listWorkspaceOauthClients")`/workspaces/${workspaceIdParam}/oauth-clients`
72 .setUrlParams(WorkspaceOauthClientQuerySchema)
73 .addSuccess(Schema.Array(WorkspaceOauthClientSchema))
74 .addError(ControlPlaneBadRequestError)
75 .addError(ControlPlaneUnauthorizedError)
76 .addError(ControlPlaneForbiddenError)
77 .addError(ControlPlaneStorageError),
78 )
79 .add(
80 HttpApiEndpoint.post("createWorkspaceOauthClient")`/workspaces/${workspaceIdParam}/oauth-clients`
81 .setPayload(CreateWorkspaceOauthClientPayloadSchema)
82 .addSuccess(WorkspaceOauthClientSchema)
83 .addError(ControlPlaneBadRequestError)
84 .addError(ControlPlaneUnauthorizedError)
85 .addError(ControlPlaneForbiddenError)
86 .addError(ControlPlaneStorageError),
87 )
88 .add(
89 HttpApiEndpoint.del("removeWorkspaceOauthClient")`/workspaces/${workspaceIdParam}/oauth-clients/${oauthClientIdParam}`
90 .addSuccess(Schema.Struct({ removed: Schema.Boolean }))
91 .addError(ControlPlaneBadRequestError)
92 .addError(ControlPlaneUnauthorizedError)
93 .addError(ControlPlaneForbiddenError)
94 .addError(ControlPlaneStorageError),
95 )
96 .add(
97 HttpApiEndpoint.del("removeProviderAuthGrant")`/workspaces/${workspaceIdParam}/provider-grants/${grantIdParam}`
98 .addSuccess(Schema.Struct({ removed: Schema.Boolean }))
99 .addError(ControlPlaneBadRequestError)
100 .addError(ControlPlaneUnauthorizedError)
101 .addError(ControlPlaneForbiddenError)
102 .addError(ControlPlaneStorageError),
103 )
104 .add(45 .add(
105 HttpApiEndpoint.get("list")`/workspaces/${workspaceIdParam}/sources`46 HttpApiEndpoint.get("list")`/workspaces/${workspaceIdParam}/sources`
106 .addSuccess(Schema.Array(SourceSchema))47 .addSuccess(Schema.Array(SourceSchema))
@@ -162,22 +103,6 @@ export class SourcesApi extends HttpApiGroup.make("sources") · 55 unmodified lines
162 .addError(ControlPlaneNotFoundError)103 .addError(ControlPlaneNotFoundError)
163 .addError(ControlPlaneStorageError),104 .addError(ControlPlaneStorageError),
164 )105 )
165 .add(
166 HttpApiEndpoint.get("credentialComplete")`/workspaces/${workspaceIdParam}/sources/${sourceIdParam}/credentials/oauth/complete`
167 .setUrlParams(CredentialOauthCompleteUrlParamsSchema)
168 .addSuccess(HtmlSchema)
169 .addError(ControlPlaneBadRequestError)
170 .addError(ControlPlaneNotFoundError)
171 .addError(ControlPlaneStorageError),
172 )
173 .add(
174 HttpApiEndpoint.get("providerOauthComplete")`/workspaces/${workspaceIdParam}/oauth/provider/callback`
175 .setUrlParams(CredentialOauthCompleteUrlParamsSchema)
176 .addSuccess(HtmlSchema)
177 .addError(ControlPlaneBadRequestError)
178 .addError(ControlPlaneNotFoundError)
179 .addError(ControlPlaneStorageError),
180 )
181 .add(106 .add(
182 HttpApiEndpoint.get("inspection")`/workspaces/${workspaceIdParam}/sources/${sourceIdParam}/inspection`107 HttpApiEndpoint.get("inspection")`/workspaces/${workspaceIdParam}/sources/${sourceIdParam}/inspection`
183 .addSuccess(SourceInspectionSchema)108 .addSuccess(SourceInspectionSchema)
packages/platform/api/src/sources/http.ts+0−358
@@ -37,14 +37,6 @@ const escapeHtml = (value: string): string => · 36 unmodified lines
37 .replaceAll('"', "&quot;")37 .replaceAll('"', "&quot;")
38 .replaceAll("'", "&#39;");38 .replaceAll("'", "&#39;");
39 39
40const serializeJsonForScript = (value: unknown): string =>
41 JSON.stringify(value)
42 .replaceAll("<", "\\u003c")
43 .replaceAll(">", "\\u003e")
44 .replaceAll("&", "\\u0026");
45
46const SOURCE_OAUTH_RESULT_STORAGE_KEY_PREFIX = "executor:oauth-result:";
47
48const credentialPageDocument = (input: {40const credentialPageDocument = (input: {
49 title: string;41 title: string;
50 eyebrow: string;42 eyebrow: string;
@@ -404,133 +396,6 @@ const credentialPageDocument = (input: { · 353 unmodified lines
404 </body>396 </body>
405</html>`;397</html>`;
406 398
407const sourceOAuthPopupResultDocument = (input: {
408 title: string;
409 message: string;
410 state: "stored" | "cancelled";
411 sessionId?: string | null;
412 payload:
413 | {
414 type: "executor:source-oauth-result";
415 ok: true;
416 sourceId: Source["id"];
417 }
418 | {
419 type: "executor:source-oauth-result";
420 ok: false;
421 error: string;
422 };
423}) => `<!doctype html>
424<html lang="en">
425 <head>
426 <meta charset="utf-8" />
427 <meta name="viewport" content="width=device-width, initial-scale=1" />
428 <title>${escapeHtml(input.title)}</title>
429 <style>
430 :root {
431 --bg: #fafaf9;
432 --surface: #ffffff;
433 --text: #1c1917;
434 --text-secondary: #78716c;
435 --border: #e7e5e4;
436 --green: #16a34a;
437 --green-light: #f0fdf4;
438 --green-border: #bbf7d0;
439 --red: #dc2626;
440 --red-light: #fef2f2;
441 --red-border: #fecaca;
442 }
443
444 * { box-sizing: border-box; margin: 0; padding: 0; }
445
446 body {
447 min-height: 100vh;
448 display: flex;
449 align-items: center;
450 justify-content: center;
451 background: var(--bg);
452 color: var(--text);
453 font-family: system-ui, -apple-system, sans-serif;
454 padding: 24px;
455 }
456
457 .card {
458 width: 100%;
459 max-width: 420px;
460 background: var(--surface);
461 border: 1px solid var(--border);
462 border-radius: 16px;
463 padding: 28px;
464 text-align: center;
465 }
466
467 .status {
468 display: inline-flex;
469 align-items: center;
470 justify-content: center;
471 padding: 6px 12px;
472 border-radius: 999px;
473 font-size: 12px;
474 font-weight: 600;
475 margin-bottom: 14px;
476 }
477
478 .status[data-state='stored'] {
479 background: var(--green-light);
480 border: 1px solid var(--green-border);
481 color: var(--green);
482 }
483
484 .status[data-state='cancelled'] {
485 background: var(--red-light);
486 border: 1px solid var(--red-border);
487 color: var(--red);
488 }
489
490 h1 {
491 font-size: 22px;
492 line-height: 1.2;
493 margin-bottom: 10px;
494 }
495
496 p {
497 color: var(--text-secondary);
498 font-size: 14px;
499 line-height: 1.6;
500 }
501 </style>
502 </head>
503 <body>
504 <main class="card">
505 <div class="status" data-state="${escapeHtml(input.state)}">${escapeHtml(input.state === "stored" ? "connected" : "failed")}</div>
506 <h1>${escapeHtml(input.title)}</h1>
507 <p>${escapeHtml(input.message)}</p>
508 </main>
509 <script>
510 (() => {
511 const payload = ${serializeJsonForScript(input.payload)};
512 const storageKey = ${serializeJsonForScript(
513 input.sessionId
514 ? `${SOURCE_OAUTH_RESULT_STORAGE_KEY_PREFIX}${input.sessionId}`
515 : null,
516 )};
517 try {
518 if (storageKey) {
519 window.localStorage.setItem(storageKey, JSON.stringify(payload));
520 }
521 } catch {}
522 try {
523 if (window.opener) {
524 window.opener.postMessage(payload, window.location.origin);
525 }
526 } finally {
527 window.setTimeout(() => window.close(), 120);
528 }
529 })();
530 </script>
531 </body>
532</html>`;
533
534const htmlResponse = (399const htmlResponse = (
535 html: string,400 html: string,
536 status = 200,401 status = 200,
@@ -616,93 +481,6 @@ export const ExecutorSourcesLive = HttpApiBuilder.group( · 79 unmodified lines
616 "sources",481 "sources",
617 (handlers) =>482 (handlers) =>
618 handlers483 handlers
619 .handle("discover", ({ payload }) =>
620 Effect.flatMap(getControlPlaneExecutor(), (executor) =>
621 executor.sources.discover({
622 url: payload.url,
623 probeAuth: payload.probeAuth,
624 }).pipe(
625 Effect.catchAll((cause) =>
626 Effect.fail(toBadRequestError("sources.discover", cause)),
627 ),
628 )
629 )
630 )
631 .handle("listWorkspaceOauthClients", ({ path, urlParams }) =>
632 resolveRequestedLocalWorkspace(
633 "sources.listWorkspaceOauthClients",
634 path.workspaceId,
635 ).pipe(
636 Effect.flatMap((executor) =>
637 executor.sources.oauthClients.list(urlParams.providerKey).pipe(
638 Effect.catchAll((cause) =>
639 Effect.fail(
640 toBadRequestError("sources.listWorkspaceOauthClients", cause),
641 ),
642 ),
643 )
644 ),
645 ),
646 )
647 .handle("createWorkspaceOauthClient", ({ path, payload }) =>
648 resolveRequestedLocalWorkspace(
649 "sources.createWorkspaceOauthClient",
650 path.workspaceId,
651 ).pipe(
652 Effect.flatMap((executor) =>
653 executor.sources.oauthClients.create(payload).pipe(
654 Effect.catchAll((cause) =>
655 Effect.fail(
656 toBadRequestError(
657 "sources.createWorkspaceOauthClient",
658 cause,
659 ),
660 ),
661 ),
662 )
663 ),
664 ),
665 )
666 .handle("removeWorkspaceOauthClient", ({ path }) =>
667 resolveRequestedLocalWorkspace(
668 "sources.removeWorkspaceOauthClient",
669 path.workspaceId,
670 ).pipe(
671 Effect.flatMap((executor) =>
672 executor.sources.oauthClients.remove(path.oauthClientId).pipe(
673 Effect.map((result) => ({ removed: result })),
674 Effect.catchAll((cause) =>
675 Effect.fail(
676 toBadRequestError(
677 "sources.removeWorkspaceOauthClient",
678 cause,
679 ),
680 ),
681 ),
682 )
683 ),
684 ),
685 )
686 .handle("removeProviderAuthGrant", ({ path }) =>
687 resolveRequestedLocalWorkspace(
688 "sources.removeProviderAuthGrant",
689 path.workspaceId,
690 ).pipe(
691 Effect.flatMap((executor) =>
692 executor.sources.providerGrants.remove(path.grantId).pipe(
693 Effect.map((result) => ({ removed: result })),
694 Effect.catchAll((cause) =>
695 Effect.fail(
696 toBadRequestError(
697 "sources.removeProviderAuthGrant",
698 cause,
699 ),
700 ),
701 ),
702 )
703 ),
704 ),
705 )
706 .handle("list", ({ path }) =>484 .handle("list", ({ path }) =>
707 resolveRequestedLocalWorkspace("sources.list", path.workspaceId).pipe(485 resolveRequestedLocalWorkspace("sources.list", path.workspaceId).pipe(
708 Effect.flatMap((executor) => executor.sources.list()),486 Effect.flatMap((executor) => executor.sources.list()),
@@ -883,140 +661,4 @@ export const ExecutorSourcesLive = HttpApiBuilder.group( · 174 unmodified lines
883 }),661 }),
884 ),662 ),
885 )663 )
886 .handle("credentialComplete", ({ path, urlParams }) =>
887 resolveRequestedLocalWorkspace(
888 "sources.credentialComplete",
889 path.workspaceId,
890 ).pipe(
891 Effect.flatMap((executor) =>
892 executor.local.credentials.complete({
893 sourceId: path.sourceId,
894 state: urlParams.state,
895 code: urlParams.code,
896 error: urlParams.error,
897 errorDescription: urlParams.error_description,
898 }),
899 ),
900 Effect.map((completed) =>
901 htmlResponse(
902 sourceOAuthPopupResultDocument({
903 title: "Source connected",
904 message: `Source connected: ${completed.source.id}. You can close this window.`,
905 state: "stored",
906 sessionId: completed.sessionId,
907 payload: {
908 type: "executor:source-oauth-result",
909 ok: true,
910 sourceId: completed.source.id,
911 },
912 }),
913 ),
914 ),
915 Effect.catchAll((error) =>
916 Effect.succeed(
917 htmlResponse(
918 sourceOAuthPopupResultDocument({
919 title: "OAuth failed",
920 message:
921 error instanceof Error
922 ? error.message
923 : "Failed completing OAuth",
924 state: "cancelled",
925 payload: {
926 type: "executor:source-oauth-result",
927 ok: false,
928 error:
929 error instanceof Error
930 ? error.message
931 : "Failed completing OAuth",
932 },
933 }),
934 500,
935 ),
936 ),
937 ),
938 ),
939 )
940 .handle("providerOauthComplete", ({ path, urlParams }) =>
941 resolveRequestedLocalWorkspace(
942 "sources.providerOauthComplete",
943 path.workspaceId,
944 ).pipe(
945 Effect.flatMap((executor) =>
946 Effect.gen(function* () {
947 const completed = yield* executor.oauth.completeProviderCallback({
948 scopeId: path.workspaceId,
949 state: urlParams.state,
950 code: urlParams.code,
951 error: urlParams.error,
952 errorDescription: urlParams.error_description,
953 });
954 const primarySource = completed.sources[0] ?? null;
955
956 return htmlResponse(
957 sourceOAuthPopupResultDocument({
958 title: "Sources connected",
959 message: `Connected ${completed.sources.length} source${completed.sources.length === 1 ? "" : "s"}. You can close this window.`,
960 state: "stored",
961 sessionId: completed.sessionId,
962 payload: {
963 type: "executor:source-oauth-result",
964 ok: true,
965 sourceId:
966 primarySource?.id ??
967 ("src_oauth_complete" as Source["id"]),
968 },
969 }),
970 );
971 }).pipe(
972 Effect.catchAll((error) =>
973 Effect.succeed(
974 htmlResponse(
975 sourceOAuthPopupResultDocument({
976 title: "OAuth failed",
977 message:
978 error instanceof Error
979 ? error.message
980 : "Failed completing OAuth",
981 state: "cancelled",
982 payload: {
983 type: "executor:source-oauth-result",
984 ok: false,
985 error:
986 error instanceof Error
987 ? error.message
988 : "Failed completing OAuth",
989 },
990 }),
991 500,
992 ),
993 ),
994 ),
995 ),
996 ),
997 Effect.catchAll((error) =>
998 Effect.succeed(
999 htmlResponse(
1000 sourceOAuthPopupResultDocument({
1001 title: "OAuth failed",
1002 message:
1003 error instanceof Error
1004 ? error.message
1005 : "Failed completing OAuth",
1006 state: "cancelled",
1007 payload: {
1008 type: "executor:source-oauth-result",
1009 ok: false,
1010 error:
1011 error instanceof Error
1012 ? error.message
1013 : "Failed completing OAuth",
1014 },
1015 }),
1016 500,
1017 ),
1018 ),
1019 ),
1020 ),
1021 ),
1022);664);
packages/platform/internal/src/tools/executor-admin.ts+0−190
@@ -4,7 +4,6 @@ import { · 3 unmodified lines
4 type ScopeId as AccountId,4 type ScopeId as AccountId,
5 LocalInstallationSchema,5 LocalInstallationSchema,
6 LocalScopePolicySchema,6 LocalScopePolicySchema,
7 SourceDiscoveryResultSchema,
8 SourceIdSchema,7 SourceIdSchema,
9 SourceInspectionDiscoverPayloadSchema,8 SourceInspectionDiscoverPayloadSchema,
10 SourceInspectionDiscoverResultSchema,9 SourceInspectionDiscoverResultSchema,
@@ -12,7 +11,6 @@ import { · 1 unmodified line
12 SourceInspectionToolDetailSchema,11 SourceInspectionToolDetailSchema,
13 SourceSchema,12 SourceSchema,
14 type ScopeId as WorkspaceId,13 type ScopeId as WorkspaceId,
15 ScopeOauthClientSchema,
16} from "@executor/platform-sdk/schema";14} from "@executor/platform-sdk/schema";
17import * as Effect from "effect/Effect";15import * as Effect from "effect/Effect";
18import * as Layer from "effect/Layer";16import * as Layer from "effect/Layer";
@@ -59,7 +57,6 @@ import { · 40 unmodified lines
59 createLocalSecret,57 createLocalSecret,
60 createPolicy,58 createPolicy,
61 deleteLocalSecret,59 deleteLocalSecret,
62 discoverSource,
63 discoverSourceInspectionTools,60 discoverSourceInspectionTools,
64 getPolicy,61 getPolicy,
65 getSource,62 getSource,
@@ -81,9 +78,6 @@ import { · 15 unmodified lines
81 UpdatePolicyPayloadSchema,78 UpdatePolicyPayloadSchema,
82} from "@executor/platform-sdk/contracts";79} from "@executor/platform-sdk/contracts";
83import {80import {
84 CreateScopeOauthClientPayloadSchema,
85 DiscoverSourcePayloadSchema,
86 type CreateScopeOauthClientPayload as CreateWorkspaceOauthClientPayload,
87 UpdateSourcePayloadSchema,81 UpdateSourcePayloadSchema,
88} from "@executor/platform-sdk/contracts";82} from "@executor/platform-sdk/contracts";
89 83
@@ -129,9 +123,6 @@ const updateSourceInputSchema = Schema.standardSchemaV1( · 39 unmodified lines
129 payload: UpdateSourcePayloadSchema,123 payload: UpdateSourcePayloadSchema,
130 }),124 }),
131);125);
132const discoverSourceInputSchema = Schema.standardSchemaV1(
133 DiscoverSourcePayloadSchema,
134);
135const inspectToolInputSchema = Schema.standardSchemaV1(126const inspectToolInputSchema = Schema.standardSchemaV1(
136 Schema.Struct({127 Schema.Struct({
137 sourceId: SourceIdSchema,128 sourceId: SourceIdSchema,
@@ -161,27 +152,6 @@ const updatePolicyInputSchema = Schema.standardSchemaV1( · 23 unmodified lines
161 payload: UpdatePolicyPayloadSchema,152 payload: UpdatePolicyPayloadSchema,
162 }),153 }),
163);154);
164const workspaceOauthClientListInputSchema = Schema.standardSchemaV1(
165 Schema.Struct({
166 providerKey: Schema.String,
167 }),
168);
169const workspaceOauthClientListOutputSchema = Schema.standardSchemaV1(
170 Schema.Array(ScopeOauthClientSchema),
171);
172const createWorkspaceOauthClientInputSchema = Schema.standardSchemaV1(
173 CreateScopeOauthClientPayloadSchema,
174);
175const removeWorkspaceOauthClientInputSchema = Schema.standardSchemaV1(
176 Schema.Struct({
177 oauthClientId: Schema.String,
178 }),
179);
180const removeProviderGrantInputSchema = Schema.standardSchemaV1(
181 Schema.Struct({
182 grantId: Schema.String,
183 }),
184);
185const sourceOutputSchema = Schema.standardSchemaV1(SourceSchema);155const sourceOutputSchema = Schema.standardSchemaV1(SourceSchema);
186const sourceInspectionOutputSchema = Schema.standardSchemaV1(156const sourceInspectionOutputSchema = Schema.standardSchemaV1(
187 SourceInspectionSchema,157 SourceInspectionSchema,
@@ -192,9 +162,6 @@ const sourceInspectionToolOutputSchema = Schema.standardSchemaV1( · 4 unmodified lines
192const sourceInspectionDiscoverOutputSchema = Schema.standardSchemaV1(162const sourceInspectionDiscoverOutputSchema = Schema.standardSchemaV1(
193 SourceInspectionDiscoverResultSchema,163 SourceInspectionDiscoverResultSchema,
194);164);
195const sourceDiscoveryOutputSchema = Schema.standardSchemaV1(
196 SourceDiscoveryResultSchema,
197);
198const localScopePolicyOutputSchema = Schema.standardSchemaV1(165const localScopePolicyOutputSchema = Schema.standardSchemaV1(
199 LocalScopePolicySchema,166 LocalScopePolicySchema,
200);167);
@@ -384,22 +351,6 @@ export const createWorkspaceExecutorAdminToolMap = ( · 183 unmodified lines
384 },351 },
385 metadata,352 metadata,
386 }),353 }),
387 "executor.sources.discover": toTool({
388 tool: {
389 description:
390 "Probe a URL and infer whether it looks like MCP, OpenAPI, GraphQL, or another supported source.",
391 inputSchema: discoverSourceInputSchema,
392 outputSchema: sourceDiscoveryOutputSchema,
393 execute: (payload: { url: string; probeAuth?: unknown }) =>
394 Effect.runPromise(
395 discoverSource({
396 url: payload.url,
397 probeAuth: payload.probeAuth as never,
398 }),
399 ),
400 },
401 metadata,
402 }),
403 "executor.sources.list": toTool({354 "executor.sources.list": toTool({
404 tool: {355 tool: {
405 description: "List sources connected in the current workspace.",356 description: "List sources connected in the current workspace.",
@@ -539,82 +490,6 @@ export const createWorkspaceExecutorAdminToolMap = ( · 133 unmodified lines
539 },490 },
540 metadata,491 metadata,
541 }),492 }),
542 "executor.sources.oauthClients.list": toTool({
543 tool: {
544 description: "List workspace OAuth clients for a provider key.",
545 inputSchema: workspaceOauthClientListInputSchema,
546 outputSchema: workspaceOauthClientListOutputSchema,
547 execute: ({ providerKey }: { providerKey: string }) =>
548 runScopeStorageEffect({
549 effect: input.sourceAuthService.listScopeOauthClients({
550 scopeId: input.scopeId,
551 providerKey,
552 }),
553 scopeStorageLayer,
554 runtimeLocalScope: input.runtimeLocalScope,
555 }),
556 },
557 metadata,
558 }),
559 "executor.sources.oauthClients.create": toTool({
560 tool: {
561 description:
562 "Create a workspace OAuth client used for shared provider auth flows.",
563 inputSchema: createWorkspaceOauthClientInputSchema,
564 outputSchema: Schema.standardSchemaV1(ScopeOauthClientSchema),
565 execute: (payload: CreateWorkspaceOauthClientPayload) =>
566 runScopeStorageEffect({
567 effect: input.sourceAuthService.createScopeOauthClient({
568 scopeId: input.scopeId,
569 providerKey: payload.providerKey,
570 label: payload.label,
571 oauthClient: payload.oauthClient,
572 }),
573 scopeStorageLayer,
574 runtimeLocalScope: input.runtimeLocalScope,
575 }),
576 },
577 metadata,
578 }),
579 "executor.sources.oauthClients.remove": toTool({
580 tool: {
581 description: "Remove a workspace OAuth client.",
582 inputSchema: removeWorkspaceOauthClientInputSchema,
583 outputSchema: removeResultSchema,
584 execute: ({ oauthClientId }: { oauthClientId: string }) =>
585 runScopeStorageEffect({
586 effect: input.sourceAuthService
587 .removeScopeOauthClient({
588 scopeId: input.scopeId,
589 oauthClientId: oauthClientId as never,
590 })
591 .pipe(Effect.map((removed) => ({ removed }))),
592 scopeStorageLayer,
593 runtimeLocalScope: input.runtimeLocalScope,
594 }),
595 },
596 metadata,
597 }),
598 "executor.sources.providerGrants.remove": toTool({
599 tool: {
600 description:
601 "Revoke a shared provider grant reference from the local workspace.",
602 inputSchema: removeProviderGrantInputSchema,
603 outputSchema: removeResultSchema,
604 execute: ({ grantId }: { grantId: string }) =>
605 runScopeStorageEffect({
606 effect: input.sourceAuthService
607 .removeProviderAuthGrant({
608 scopeId: input.scopeId,
609 grantId: grantId as never,
610 })
611 .pipe(Effect.map((removed) => ({ removed }))),
612 scopeStorageLayer,
613 runtimeLocalScope: input.runtimeLocalScope,
614 }),
615 },
616 metadata,
617 }),
618 "executor.policies.list": toTool({493 "executor.policies.list": toTool({
619 tool: {494 tool: {
620 description: "List local workspace policies.",495 description: "List local workspace policies.",
@@ -780,20 +655,6 @@ export const createExecutorAdminToolMap = (input: { · 159 unmodified lines
780 },655 },
781 metadata,656 metadata,
782 }),657 }),
783 "executor.sources.discover": toTool({
784 tool: {
785 description:
786 "Probe a URL and infer whether it looks like MCP, OpenAPI, GraphQL, or another supported source.",
787 inputSchema: discoverSourceInputSchema,
788 outputSchema: sourceDiscoveryOutputSchema,
789 execute: (payload: { url: string; probeAuth?: unknown }) =>
790 input.executor.sources.discover({
791 url: payload.url,
792 probeAuth: payload.probeAuth as never,
793 }),
794 },
795 metadata,
796 }),
797 "executor.sources.list": toTool({658 "executor.sources.list": toTool({
798 tool: {659 tool: {
799 description: "List sources connected in the current workspace.",660 description: "List sources connected in the current workspace.",
@@ -888,57 +749,6 @@ export const createExecutorAdminToolMap = (input: { · 88 unmodified lines
888 },749 },
889 metadata,750 metadata,
890 }),751 }),
891 "executor.sources.oauthClients.list": toTool({
892 tool: {
893 description: "List stored workspace OAuth clients for one provider.",
894 inputSchema: workspaceOauthClientListInputSchema,
895 outputSchema: workspaceOauthClientListOutputSchema,
896 execute: ({ providerKey }: { providerKey: string }) =>
897 input.executor.sources.oauthClients.list(providerKey),
898 },
899 metadata,
900 }),
901 "executor.sources.oauthClients.create": toTool({
902 tool: {
903 description:
904 "Create a workspace OAuth client for a provider-backed source flow.",
905 inputSchema: createWorkspaceOauthClientInputSchema,
906 outputSchema: Schema.standardSchemaV1(ScopeOauthClientSchema),
907 execute: (payload: CreateWorkspaceOauthClientPayload) =>
908 input.executor.sources.oauthClients.create(payload),
909 },
910 metadata,
911 }),
912 "executor.sources.oauthClients.remove": toTool({
913 tool: {
914 description: "Remove a stored workspace OAuth client.",
915 inputSchema: removeWorkspaceOauthClientInputSchema,
916 outputSchema: removeResultSchema,
917 execute: async ({
918 oauthClientId,
919 }: {
920 oauthClientId: string;
921 }) => ({
922 removed: await input.executor.sources.oauthClients.remove(
923 oauthClientId as never,
924 ),
925 }),
926 },
927 metadata,
928 }),
929 "executor.sources.providerGrants.remove": toTool({
930 tool: {
931 description: "Remove one stored provider auth grant.",
932 inputSchema: removeProviderGrantInputSchema,
933 outputSchema: removeResultSchema,
934 execute: async ({ grantId }: { grantId: string }) => ({
935 removed: await input.executor.sources.providerGrants.remove(
936 grantId as never,
937 ),
938 }),
939 },
940 metadata,
941 }),
942 "executor.policies.list": toTool({752 "executor.policies.list": toTool({
943 tool: {753 tool: {
944 description: "List local workspace policies.",754 description: "List local workspace policies.",
packages/platform/sdk/src/executor-effect.ts+0−114
@@ -7,8 +7,6 @@ import type { · 6 unmodified lines
7 ExecutionInteraction,7 ExecutionInteraction,
8 LocalInstallation,8 LocalInstallation,
9 LocalScopePolicy,9 LocalScopePolicy,
10 ProviderAuthGrant,
11 ScopeOauthClient,
12 Source,10 Source,
13} from "./schema";11} from "./schema";
14import { ExecutionIdSchema } from "./schema";12import { ExecutionIdSchema } from "./schema";
@@ -50,11 +48,9 @@ import { · 35 unmodified lines
50 updatePolicy,48 updatePolicy,
51} from "./policies/operations";49} from "./policies/operations";
52import type {50import type {
53 CreateScopeOauthClientPayload,
54 CreateSourcePayload,51 CreateSourcePayload,
55 UpdateSourcePayload,52 UpdateSourcePayload,
56} from "./sources/contracts";53} from "./sources/contracts";
57import { discoverSource } from "./sources/discovery";
58import {54import {
59 discoverSourceInspectionTools,55 discoverSourceInspectionTools,
60 getSourceInspection,56 getSourceInspection,
@@ -83,13 +79,9 @@ import { · 22 unmodified lines
83 resumeExecution,79 resumeExecution,
84} from "./runtime/execution/service";80} from "./runtime/execution/service";
85import type {81import type {
86 CompleteProviderOauthCallbackResult,
87 CompleteSourceCredentialSetupResult,82 CompleteSourceCredentialSetupResult,
88 CompleteSourceOAuthSessionResult,
89 ExecutorAddSourceInput,83 ExecutorAddSourceInput,
90 ExecutorSourceAddResult,84 ExecutorSourceAddResult,
91 StartSourceOAuthSessionInput,
92 StartSourceOAuthSessionResult,
93} from "./runtime/sources/source-auth-service";85} from "./runtime/sources/source-auth-service";
94 86
95type DistributiveOmit<T, Keys extends PropertyKey> = T extends unknown87type DistributiveOmit<T, Keys extends PropertyKey> = T extends unknown
@@ -110,11 +102,6 @@ export type ExecutorSourceInput = DistributiveOmit< · 14 unmodified lines
110 "scopeId" | "actorScopeId" | "executionId" | "interactionId"102 "scopeId" | "actorScopeId" | "executionId" | "interactionId"
111>;103>;
112 104
113export type ExecutorSourceOAuthInput = DistributiveOmit<
114 StartSourceOAuthSessionInput,
115 "scopeId" | "actorScopeId"
116>;
117
118export type ExecutorEffect = {105export type ExecutorEffect = {
119 runtime: ExecutorRuntime;106 runtime: ExecutorRuntime;
120 installation: LocalInstallation;107 installation: LocalInstallation;
@@ -187,10 +174,6 @@ export type ExecutorEffect = { · 66 unmodified lines
187 baseUrl?: string | null;174 baseUrl?: string | null;
188 },175 },
189 ) => Effect.Effect<ExecutorSourceAddResult, Error, never>;176 ) => Effect.Effect<ExecutorSourceAddResult, Error, never>;
190 discover: (input: {
191 url: string;
192 probeAuth?: Parameters<typeof discoverSource>[0]["probeAuth"];
193 }) => ProvidedEffect<ReturnType<typeof discoverSource>>;
194 list: () => ProvidedEffect<ReturnType<typeof listSources>>;177 list: () => ProvidedEffect<ReturnType<typeof listSources>>;
195 create: (178 create: (
196 payload: CreateSourcePayload,179 payload: CreateSourcePayload,
@@ -218,41 +201,6 @@ export type ExecutorEffect = { · 21 unmodified lines
218 ReturnType<typeof discoverSourceInspectionTools>201 ReturnType<typeof discoverSourceInspectionTools>
219 >;202 >;
220 };203 };
221 oauthClients: {
222 list: (
223 providerKey: string,
224 ) => Effect.Effect<ReadonlyArray<ScopeOauthClient>, Error, never>;
225 create: (
226 payload: CreateScopeOauthClientPayload,
227 ) => Effect.Effect<ScopeOauthClient, Error, never>;
228 remove: (
229 oauthClientId: ScopeOauthClient["id"],
230 ) => Effect.Effect<boolean, Error, never>;
231 };
232 providerGrants: {
233 remove: (
234 grantId: ProviderAuthGrant["id"],
235 ) => Effect.Effect<boolean, Error, never>;
236 };
237 };
238 oauth: {
239 startSourceAuth: (
240 input: ExecutorSourceOAuthInput,
241 ) => Effect.Effect<StartSourceOAuthSessionResult, Error, never>;
242 completeSourceAuth: (input: {
243 state: string;
244 code?: string | null;
245 error?: string | null;
246 errorDescription?: string | null;
247 }) => Effect.Effect<CompleteSourceOAuthSessionResult, Error, never>;
248 completeProviderCallback: (input: {
249 scopeId?: ScopeId;
250 actorScopeId?: ScopeId | null;
251 state: string;
252 code?: string | null;
253 error?: string | null;
254 errorDescription?: string | null;
255 }) => Effect.Effect<CompleteProviderOauthCallbackResult, Error, never>;
256 };204 };
257 executions: {205 executions: {
258 create: (206 create: (
@@ -366,7 +314,6 @@ const fromRuntime = (runtime: ExecutorRuntime): ExecutorEffect => { · 107 unmodified lines
366 options,314 options,
367 );315 );
368 }),316 }),
369 discover: (input) => provide(discoverSource(input)),
370 list: () => provide(listSources({ scopeId, actorScopeId })),317 list: () => provide(listSources({ scopeId, actorScopeId })),
371 create: (payload) => provide(createSource({ scopeId, actorScopeId, payload })),318 create: (payload) => provide(createSource({ scopeId, actorScopeId, payload })),
372 get: (sourceId) => provide(getSource({ scopeId, sourceId, actorScopeId })),319 get: (sourceId) => provide(getSource({ scopeId, sourceId, actorScopeId })),
@@ -395,67 +342,6 @@ const fromRuntime = (runtime: ExecutorRuntime): ExecutorEffect => { · 22 unmodified lines
395 }),342 }),
396 ),343 ),
397 },344 },
398 oauthClients: {
399 list: (providerKey) =>
400 provideSourceAuth((service) =>
401 service.listScopeOauthClients({
402 scopeId,
403 providerKey,
404 }),
405 ),
406 create: (payload) =>
407 provideSourceAuth((service) =>
408 service.createScopeOauthClient({
409 scopeId,
410 providerKey: payload.providerKey,
411 label: payload.label,
412 oauthClient: payload.oauthClient,
413 }),
414 ),
415 remove: (oauthClientId) =>
416 provideSourceAuth((service) =>
417 service.removeScopeOauthClient({
418 scopeId,
419 oauthClientId,
420 }),
421 ),
422 },
423 providerGrants: {
424 remove: (grantId) =>
425 provideSourceAuth((service) =>
426 service.removeProviderAuthGrant({
427 scopeId,
428 grantId,
429 }),
430 ),
431 },
432 },
433 oauth: {
434 startSourceAuth: (input) =>
435 provideSourceAuth((service) =>
436 service.startSourceOAuthSession({
437 ...input,
438 scopeId,
439 actorScopeId,
440 }),
441 ),
442 completeSourceAuth: ({ state, code, error, errorDescription }) =>
443 provideSourceAuth((service) =>
444 service.completeSourceOAuthSession({
445 state,
446 code,
447 error,
448 errorDescription,
449 }),
450 ),
451 completeProviderCallback: (input) =>
452 provideSourceAuth((service) =>
453 service.completeProviderOauthCallback({
454 ...input,
455 scopeId: input.scopeId ?? scopeId,
456 actorScopeId: input.actorScopeId ?? actorScopeId,
457 }),
458 ),
459 },345 },
460 executions: {346 executions: {
461 create: (payload) =>347 create: (payload) =>
packages/platform/sdk/src/executor.ts+0−21
@@ -5,7 +5,6 @@ import { · 4 unmodified lines
5 type CreateExecutorEffectOptions,5 type CreateExecutorEffectOptions,
6 type ExecutorEffect,6 type ExecutorEffect,
7 type ExecutorSourceInput,7 type ExecutorSourceInput,
8 type ExecutorSourceOAuthInput,
9} from "./executor-effect";8} from "./executor-effect";
10 9
11type Promiseify<T> = T extends Effect.Effect<infer A, any, any>10type Promiseify<T> = T extends Effect.Effect<infer A, any, any>
@@ -22,7 +21,6 @@ export type Executor = Omit<Promiseify<ExecutorEffect>, "runtime">; · 10 unmodified lines
22export type CreateExecutorOptions = CreateExecutorEffectOptions;21export type CreateExecutorOptions = CreateExecutorEffectOptions;
23export type {22export type {
24 ExecutorSourceInput,23 ExecutorSourceInput,
25 ExecutorSourceOAuthInput,
26};24};
27 25
28const toPromiseExecutor = (executor: ExecutorEffect): Executor => {26const toPromiseExecutor = (executor: ExecutorEffect): Executor => {
@@ -60,7 +58,6 @@ const toPromiseExecutor = (executor: ExecutorEffect): Executor => { · 31 unmodified lines
60 },58 },
61 sources: {59 sources: {
62 add: (input, options) => run(executor.sources.add(input, options)),60 add: (input, options) => run(executor.sources.add(input, options)),
63 discover: (input) => run(executor.sources.discover(input)),
64 list: () => run(executor.sources.list()),61 list: () => run(executor.sources.list()),
65 create: (payload) => run(executor.sources.create(payload)),62 create: (payload) => run(executor.sources.create(payload)),
66 get: (sourceId) => run(executor.sources.get(sourceId)),63 get: (sourceId) => run(executor.sources.get(sourceId)),
@@ -72,24 +69,6 @@ const toPromiseExecutor = (executor: ExecutorEffect): Executor => { · 5 unmodified lines
72 tool: (input) => run(executor.sources.inspection.tool(input)),69 tool: (input) => run(executor.sources.inspection.tool(input)),
73 discover: (input) => run(executor.sources.inspection.discover(input)),70 discover: (input) => run(executor.sources.inspection.discover(input)),
74 },71 },
75 oauthClients: {
76 list: (providerKey) =>
77 run(executor.sources.oauthClients.list(providerKey)),
78 create: (payload) => run(executor.sources.oauthClients.create(payload)),
79 remove: (oauthClientId) =>
80 run(executor.sources.oauthClients.remove(oauthClientId)),
81 },
82 providerGrants: {
83 remove: (grantId) =>
84 run(executor.sources.providerGrants.remove(grantId)),
85 },
86 },
87 oauth: {
88 startSourceAuth: (input) => run(executor.oauth.startSourceAuth(input)),
89 completeSourceAuth: (input) =>
90 run(executor.oauth.completeSourceAuth(input)),
91 completeProviderCallback: (input) =>
92 run(executor.oauth.completeProviderCallback(input)),
93 },72 },
94 executions: {73 executions: {
95 create: (payload) => run(executor.executions.create(payload)),74 create: (payload) => run(executor.executions.create(payload)),
packages/platform/sdk/src/operations.ts+0−1
@@ -1,6 +1,5 @@
1export * from "./local/operations";1export * from "./local/operations";
2export * from "./local/secrets";2export * from "./local/secrets";
3export * from "./policies/operations";3export * from "./policies/operations";
4export * from "./sources/discovery";
5export * from "./sources/inspection";4export * from "./sources/inspection";
6export * from "./sources/operations";5export * from "./sources/operations";
packages/platform/sdk/src/runtime/index.ts+0−1
@@ -111,7 +111,6 @@ export * from "./sources/source-store"; · 110 unmodified lines
111export * from "./executor-state-store";111export * from "./executor-state-store";
112export * from "./execution/scope/environment";112export * from "./execution/scope/environment";
113export * from "../sources/inspection";113export * from "../sources/inspection";
114export * from "../sources/discovery";
115export * from "./execution/service";114export * from "./execution/service";
116export type {115export type {
117 CreateScopeInternalToolMap,116 CreateScopeInternalToolMap,
packages/platform/sdk/src/runtime/sources/source-auth-service.ts+0−80
@@ -3,14 +3,11 @@ import type { · 2 unmodified lines
3 SecretRef,3 SecretRef,
4} from "@executor/source-core";4} from "@executor/source-core";
5import {5import {
6 type ProviderAuthGrant,
7 type ScopeId,6 type ScopeId,
8 type ScopeOauthClient,
9 type Source,7 type Source,
10 type SourceAuthSession,8 type SourceAuthSession,
11 SourceAuthSessionIdSchema,9 SourceAuthSessionIdSchema,
12 SourceSchema,10 SourceSchema,
13 type SourceOauthClientInput,
14} from "#schema";11} from "#schema";
15import * as Context from "effect/Context";12import * as Context from "effect/Context";
16import * as Effect from "effect/Effect";13import * as Effect from "effect/Effect";
@@ -92,46 +89,11 @@ export type ExecutorAddSourceInput = RegisteredExecutorAddSourceInput & { · 75 unmodified lines
92 interactionId: SourceAuthSession["interactionId"];89 interactionId: SourceAuthSession["interactionId"];
93};90};
94 91
95export type CreateScopeOauthClientInput = {
96 scopeId: ScopeId;
97 providerKey: string;
98 label?: string | null;
99 oauthClient: SourceOauthClientInput;
100};
101
102export type SourceOAuthProviderInput = {
103 providerKey: string;
104 config?: Record<string, unknown> | null;
105};
106
107export type StartSourceOAuthSessionInput = {
108 scopeId: ScopeId;
109 actorScopeId?: ScopeId | null;
110 provider: SourceOAuthProviderInput;
111 baseUrl?: string | null;
112 displayName?: string | null;
113};
114
115export type StartSourceOAuthSessionResult = {
116 sessionId: SourceAuthSession["id"];
117 authorizationUrl: string;
118};
119
120export type CompleteSourceOAuthSessionResult = {
121 sessionId: SourceAuthSession["id"];
122 auth: Extract<Source["auth"], { kind: "oauth2" }>;
123};
124
125export type CompleteSourceCredentialSetupResult = {92export type CompleteSourceCredentialSetupResult = {
126 sessionId: SourceAuthSession["id"];93 sessionId: SourceAuthSession["id"];
127 source: Source;94 source: Source;
128};95};
129 96
130export type CompleteProviderOauthCallbackResult = {
131 sessionId: SourceAuthSession["id"];
132 sources: ReadonlyArray<Source>;
133};
134
135type RuntimeSourceAuthServiceShape = {97type RuntimeSourceAuthServiceShape = {
136 getSourceById: (input: {98 getSourceById: (input: {
137 scopeId: ScopeId;99 scopeId: ScopeId;
@@ -150,38 +112,6 @@ type RuntimeSourceAuthServiceShape = { · 12 unmodified lines
150 baseUrl?: string | null;112 baseUrl?: string | null;
151 },113 },
152 ) => Effect.Effect<ExecutorSourceAddResult, Error, ScopeStorageServices>;114 ) => Effect.Effect<ExecutorSourceAddResult, Error, ScopeStorageServices>;
153 listScopeOauthClients: (input: {
154 scopeId: ScopeId;
155 providerKey: string;
156 }) => Effect.Effect<readonly ScopeOauthClient[], Error, ScopeStorageServices>;
157 createScopeOauthClient: (
158 input: CreateScopeOauthClientInput,
159 ) => Effect.Effect<ScopeOauthClient, Error, ScopeStorageServices>;
160 removeScopeOauthClient: (input: {
161 scopeId: ScopeId;
162 oauthClientId: ScopeOauthClient["id"];
163 }) => Effect.Effect<boolean, Error, ScopeStorageServices>;
164 removeProviderAuthGrant: (input: {
165 scopeId: ScopeId;
166 grantId: ProviderAuthGrant["id"];
167 }) => Effect.Effect<boolean, Error, ScopeStorageServices>;
168 startSourceOAuthSession: (
169 input: StartSourceOAuthSessionInput,
170 ) => Effect.Effect<StartSourceOAuthSessionResult, Error, never>;
171 completeSourceOAuthSession: (input: {
172 state: string;
173 code?: string | null;
174 error?: string | null;
175 errorDescription?: string | null;
176 }) => Effect.Effect<CompleteSourceOAuthSessionResult, Error, ScopeStorageServices>;
177 completeProviderOauthCallback: (input: {
178 scopeId: ScopeId;
179 actorScopeId?: ScopeId | null;
180 state: string;
181 code?: string | null;
182 error?: string | null;
183 errorDescription?: string | null;
184 }) => Effect.Effect<CompleteProviderOauthCallbackResult, Error, ScopeStorageServices>;
185 completeSourceCredentialSetup: (input: {115 completeSourceCredentialSetup: (input: {
186 scopeId: ScopeId;116 scopeId: ScopeId;
187 sourceId: Source["id"];117 sourceId: Source["id"];
@@ -228,16 +158,6 @@ export const createRuntimeSourceAuthService = ( · 40 unmodified lines
228 }),158 }),
229 addExecutorSource: () =>159 addExecutorSource: () =>
230 provideLocalWorkspace(disabledSourcePlugins("addExecutorSource")),160 provideLocalWorkspace(disabledSourcePlugins("addExecutorSource")),
231 listScopeOauthClients: () => provideLocalWorkspace(Effect.succeed([])),
232 createScopeOauthClient: () =>
233 provideLocalWorkspace(disabledSourcePlugins("createScopeOauthClient")),
234 removeScopeOauthClient: () => provideLocalWorkspace(Effect.succeed(false)),
235 removeProviderAuthGrant: () => provideLocalWorkspace(Effect.succeed(false)),
236 startSourceOAuthSession: () => disabledSourcePlugins("startSourceOAuthSession"),
237 completeSourceOAuthSession: () =>
238 provideLocalWorkspace(disabledSourcePlugins("completeSourceOAuthSession")),
239 completeProviderOauthCallback: () =>
240 provideLocalWorkspace(disabledSourcePlugins("completeProviderOauthCallback")),
241 completeSourceCredentialSetup: () =>161 completeSourceCredentialSetup: () =>
242 provideLocalWorkspace(disabledSourcePlugins("completeSourceCredentialSetup")),162 provideLocalWorkspace(disabledSourcePlugins("completeSourceCredentialSetup")),
243 };163 };
packages/platform/sdk/src/runtime/sources/source-discovery.test.ts+0−278
@@ -1,278 +0,0 @@
1import {
2 createServer,
3 type IncomingMessage,
4 type ServerResponse,
5} from "node:http";
6
7import {
8 describe,
9 expect,
10 it,
11} from "@effect/vitest";
12import * as Effect from "effect/Effect";
13
14import {
15 discoverSource,
16} from "../../sources/discovery";
17
18type TestServer = {
19 url: string;
20};
21
22const toError = (cause: unknown): Error =>
23 cause instanceof Error ? cause : new Error(String(cause));
24
25const makeServer = (
26 handler: (input: {
27 request: IncomingMessage;
28 response: ServerResponse<IncomingMessage>;
29 url: string;
30 }) => void,
31) =>
32 Effect.acquireRelease(
33 Effect.tryPromise({
34 try: () =>
35 new Promise<{
36 server: ReturnType<typeof createServer>;
37 sockets: Set<import("node:net").Socket>;
38 url: string;
39 }>((resolve, reject) => {
40 let serverUrl = "";
41 const server = createServer((request, response) =>
42 handler({
43 request,
44 response,
45 url: serverUrl,
46 }),
47 );
48 const sockets = new Set<import("node:net").Socket>();
49
50 server.on("connection", (socket) => {
51 sockets.add(socket);
52 socket.on("close", () => {
53 sockets.delete(socket);
54 });
55 });
56
57 server.once("error", reject);
58 server.listen(0, "127.0.0.1", () => {
59 const address = server.address();
60 if (!address || typeof address === "string") {
61 reject(new Error("Failed to resolve test server address"));
62 return;
63 }
64
65 serverUrl = `http://127.0.0.1:${address.port}`;
66 resolve({
67 server,
68 sockets,
69 url: serverUrl,
70 });
71 });
72 }),
73 catch: toError,
74 }),
75 ({ server, sockets }) =>
76 Effect.tryPromise({
77 try: () =>
78 new Promise<void>((resolve, reject) => {
79 server.closeAllConnections?.();
80 sockets.forEach((socket) => socket.destroy());
81 server.close((error) => {
82 if (error) {
83 reject(error);
84 return;
85 }
86 resolve();
87 });
88 }),
89 catch: toError,
90 }),
91 ).pipe(Effect.map(({ url }) => ({ url }) satisfies TestServer));
92
93describe("source-discovery", () => {
94 it.scoped(
95 "detects OpenAPI and infers bearer auth from security schemes",
96 () =>
97 Effect.gen(function* () {
98 const server = yield* makeServer(({ request, response, url }) => {
99 if (request.url !== "/openapi.json") {
100 response.statusCode = 404;
101 response.end();
102 return;
103 }
104
105 if (request.headers.authorization !== "Bearer top-secret") {
106 response.statusCode = 401;
107 response.setHeader("www-authenticate", 'Bearer realm="spec"');
108 response.end("Unauthorized");
109 return;
110 }
111
112 response.statusCode = 200;
113 response.setHeader("content-type", "application/json");
114 response.end(
115 JSON.stringify({
116 openapi: "3.0.3",
117 info: {
118 title: "Secure Example API",
119 version: "1.0.0",
120 },
121 servers: [{ url: `${url}/api` }],
122 components: {
123 securitySchemes: {
124 bearerAuth: {
125 type: "http",
126 scheme: "bearer",
127 },
128 },
129 },
130 security: [{ bearerAuth: [] }],
131 paths: {
132 "/widgets": {
133 get: {
134 operationId: "widgets/list",
135 responses: {
136 200: {
137 description: "ok",
138 },
139 },
140 },
141 },
142 },
143 }),
144 );
145 });
146
147 const result = yield* discoverSource({
148 url: `${server.url}/openapi.json`,
149 probeAuth: {
150 kind: "bearer",
151 token: "top-secret",
152 },
153 });
154
155 expect(result.detectedKind).toBe("openapi");
156 expect(result.authInference.suggestedKind).toBe("bearer");
157 expect(result.authInference.supported).toBe(true);
158 expect(result.authInference.headerName).toBe("Authorization");
159 expect(result.specUrl).toBe(`${server.url}/openapi.json`);
160 expect(result.endpoint).toBe(`${server.url}/api`);
161 expect(result.toolCount).toBe(1);
162 }),
163 );
164
165 it.scoped("detects GraphQL from successful introspection", () =>
166 Effect.gen(function* () {
167 const server = yield* makeServer(({ request, response }) => {
168 if (request.url !== "/graphql" || request.method !== "POST") {
169 response.statusCode = 404;
170 response.end();
171 return;
172 }
173
174 response.statusCode = 200;
175 response.setHeader("content-type", "application/json");
176 response.end(
177 JSON.stringify({
178 data: {
179 __schema: {
180 queryType: {
181 name: "Query",
182 },
183 },
184 },
185 }),
186 );
187 });
188
189 const result = yield* discoverSource({
190 url: `${server.url}/graphql`,
191 });
192
193 expect(result.detectedKind).toBe("graphql");
194 expect(result.confidence).toBe("high");
195 expect(result.authInference.suggestedKind).toBe("none");
196 expect(result.specUrl).toBeNull();
197 }),
198 );
199
200 it.scoped("detects Google Discovery documents", () =>
201 Effect.gen(function* () {
202 const server = yield* makeServer(({ request, response, url }) => {
203 if (request.url !== "/gmail/$discovery/rest?version=v1") {
204 response.statusCode = 404;
205 response.end();
206 return;
207 }
208
209 response.statusCode = 200;
210 response.setHeader("content-type", "application/json");
211 response.end(
212 JSON.stringify({
213 name: "gmail",
214 version: "v1",
215 title: "Gmail API",
216 description: "Access Gmail mailboxes and settings.",
217 rootUrl: `${url}/`,
218 servicePath: "gmail/v1/users/",
219 auth: {
220 oauth2: {
221 scopes: {
222 "https://www.googleapis.com/auth/gmail.readonly": {
223 description: "View your email messages and settings",
224 },
225 },
226 },
227 },
228 methods: {
229 getProfile: {
230 id: "gmail.users.getProfile",
231 path: "{userId}/profile",
232 httpMethod: "GET",
233 parameters: {
234 userId: {
235 type: "string",
236 required: true,
237 location: "path",
238 },
239 },
240 response: {
241 $ref: "Profile",
242 },
243 },
244 },
245 schemas: {
246 Profile: {
247 id: "Profile",
248 type: "object",
249 properties: {
250 emailAddress: {
251 type: "string",
252 },
253 },
254 },
255 },
256 }),
257 );
258 });
259
260 const result = yield* discoverSource({
261 url: `${server.url}/gmail/$discovery/rest?version=v1`,
262 });
263
264 expect(result.detectedKind).toBe("google_discovery");
265 expect(result.specUrl).toBe(
266 `${server.url}/gmail/$discovery/rest?version=v1`,
267 );
268 expect(result.endpoint).toBe(`${server.url}/gmail/v1/users/`);
269 expect(result.name).toBe("Gmail API");
270 expect(result.namespace).toBe("gmail");
271 expect(result.authInference.suggestedKind).toBe("oauth2");
272 expect(result.authInference.oauthScopes).toEqual([
273 "https://www.googleapis.com/auth/gmail.readonly",
274 ]);
275 expect(result.toolCount).toBe(1);
276 }),
277 );
278});
packages/platform/sdk/src/schema/enums.ts+0−16
@@ -23,22 +23,6 @@ export { · 22 unmodified lines
23 type SourceCatalogKind,23 type SourceCatalogKind,
24 type SourceCatalogVisibility,24 type SourceCatalogVisibility,
25} from "./models/source-catalog";25} from "./models/source-catalog";
26export {
27 SourceAuthInferenceSchema,
28 SourceDiscoveryAuthKindSchema,
29 SourceDiscoveryAuthParameterLocationSchema,
30 SourceDiscoveryConfidenceSchema,
31 SourceDiscoveryKindSchema,
32 SourceDiscoveryResultSchema,
33 SourceProbeAuthSchema,
34 type SourceAuthInference,
35 type SourceDiscoveryAuthKind,
36 type SourceDiscoveryAuthParameterLocation,
37 type SourceDiscoveryConfidence,
38 type SourceDiscoveryKind,
39 type SourceDiscoveryResult,
40 type SourceProbeAuth,
41} from "./models/source-discovery";
42export {26export {
43 AuthArtifactKindSchema,27 AuthArtifactKindSchema,
44 AuthArtifactSlotSchema,28 AuthArtifactSlotSchema,
packages/platform/sdk/src/schema/index.ts+0−1
@@ -16,5 +16,4 @@ export * from "./models/local-installation"; · 15 unmodified lines
16export * from "./models/local-config";16export * from "./models/local-config";
17export * from "./models/execution";17export * from "./models/execution";
18export * from "./models/source-inspection";18export * from "./models/source-inspection";
19export * from "./models/source-discovery";
20export * from "./models/code-migration";19export * from "./models/code-migration";
packages/platform/sdk/src/schema/models/source-discovery.ts+0−16
@@ -1,16 +0,0 @@
1export {
2 SourceAuthInferenceSchema,
3 SourceDiscoveryAuthKindSchema,
4 SourceDiscoveryAuthParameterLocationSchema,
5 SourceDiscoveryConfidenceSchema,
6 SourceDiscoveryKindSchema,
7 SourceDiscoveryResultSchema,
8 SourceProbeAuthSchema,
9 type SourceAuthInference,
10 type SourceDiscoveryAuthKind,
11 type SourceDiscoveryAuthParameterLocation,
12 type SourceDiscoveryConfidence,
13 type SourceDiscoveryKind,
14 type SourceDiscoveryResult,
15 type SourceProbeAuth,
16} from "@executor/source-core";
packages/platform/sdk/src/sources/contracts.ts+0−10
@@ -3,10 +3,8 @@ import { · 2 unmodified lines
3 JsonObjectSchema,3 JsonObjectSchema,
4 ProviderAuthGrantIdSchema,4 ProviderAuthGrantIdSchema,
5 SourceAuthSchema,5 SourceAuthSchema,
6 SourceDiscoveryResultSchema,
7 SourceImportAuthPolicySchema,6 SourceImportAuthPolicySchema,
8 SourceKindSchema,7 SourceKindSchema,
9 SourceProbeAuthSchema,
10 SourceSchema,8 SourceSchema,
11 SourceStatusSchema,9 SourceStatusSchema,
12 SourceOauthClientInputSchema,10 SourceOauthClientInputSchema,
@@ -93,15 +91,7 @@ export type CreateScopeOauthClientPayload = · 80 unmodified lines
93export const oauthClientIdParam = ScopeOauthClientIdSchema;91export const oauthClientIdParam = ScopeOauthClientIdSchema;
94export const grantIdParam = ProviderAuthGrantIdSchema;92export const grantIdParam = ProviderAuthGrantIdSchema;
95 93
96export const DiscoverSourcePayloadSchema = Schema.Struct({
97 url: TrimmedNonEmptyStringSchema,
98 probeAuth: Schema.optional(SourceProbeAuthSchema),
99});
100
101export type DiscoverSourcePayload = typeof DiscoverSourcePayloadSchema.Type;
102
103export {94export {
104 SourceDiscoveryResultSchema,
105 ScopeIdSchema,95 ScopeIdSchema,
106 ScopeOauthClientSchema,96 ScopeOauthClientSchema,
107};97};
packages/platform/sdk/src/sources/discovery.ts+0−56
@@ -1,56 +0,0 @@
1import * as Effect from "effect/Effect";
2
3import {
4 fallbackSourceDiscoveryResult,
5 normalizeSourceDiscoveryUrl,
6 probeHeadersFromAuth,
7 type SourceAdapter,
8 type SourceProbeAuth,
9 type SourceDiscoveryResult,
10} from "@executor/source-core";
11
12import {
13 builtInSourceAdapters,
14} from "../runtime/sources/source-adapters";
15
16type DiscoverableSourceAdapter = SourceAdapter & {
17 detectSource: NonNullable<SourceAdapter["detectSource"]>;
18};
19
20const sourceAdapters: ReadonlyArray<SourceAdapter> = builtInSourceAdapters;
21
22const discoverableSourceAdapters = sourceAdapters.filter(
23 (adapter): adapter is DiscoverableSourceAdapter =>
24 adapter.detectSource !== undefined,
25);
26
27export const discoverSource = (input: {
28 url: string;
29 probeAuth?: SourceProbeAuth | null;
30}): Effect.Effect<SourceDiscoveryResult, Error, never> =>
31 Effect.gen(function* () {
32 const normalizedUrl = yield* Effect.try({
33 try: () => normalizeSourceDiscoveryUrl(input.url),
34 catch: (cause) =>
35 cause instanceof Error ? cause : new Error(String(cause)),
36 });
37 const headers = probeHeadersFromAuth(input.probeAuth);
38
39 const adaptersByPriority = [...discoverableSourceAdapters].sort(
40 (left, right) =>
41 (right.discoveryPriority?.({ normalizedUrl }) ?? 0) -
42 (left.discoveryPriority?.({ normalizedUrl }) ?? 0),
43 );
44
45 for (const adapter of adaptersByPriority) {
46 const detected = yield* adapter.detectSource({
47 normalizedUrl,
48 headers,
49 });
50 if (detected) {
51 return detected;
52 }
53 }
54
55 return fallbackSourceDiscoveryResult(normalizedUrl);
56 });
packages/sources/core/src/discovery-models.ts+1−4
@@ -6,10 +6,7 @@ import { · 5 unmodified lines
6} from "./source-models";6} from "./source-models";
7 7
8export const SourceDiscoveryKindSchema = Schema.Literal(8export const SourceDiscoveryKindSchema = Schema.Literal(
9 "mcp",9 "plugin",
10 "openapi",
11 "google_discovery",
12 "graphql",
13 "unknown",10 "unknown",
14);11);
15 12
packages/sources/core/src/discovery.ts+1−4
@@ -215,9 +215,6 @@ export const executeHttpProbe = (input: { · 214 unmodified lines
215 catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)),215 catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)),
216 });216 });
217 217
218export const looksLikeGraphqlEndpoint = (normalizedUrl: string): boolean =>
219 /graphql/i.test(new URL(normalizedUrl).pathname);
220
221export const tryParseJson = (value: string): unknown => {218export const tryParseJson = (value: string): unknown => {
222 try {219 try {
223 return JSON.parse(value) as unknown;220 return JSON.parse(value) as unknown;
@@ -244,7 +241,7 @@ export const fallbackSourceDiscoveryResult = ( · 20 unmodified lines
244 ),241 ),
245 toolCount: null,242 toolCount: null,
246 warnings: [243 warnings: [
247 "Could not confirm whether the URL is Google Discovery, OpenAPI, GraphQL, or MCP.",244 "Could not confirm whether the URL matches any registered source plugin.",
248 ],245 ],
249 };246 };
250};247};