Unified · Split
| @@ -9,9 +9,7 @@ import type { · 8 unmodified lines | ||
| 9 | 9 | CreateSecretPayload, |
| 10 | 10 | CreateSecretResult, |
| 11 | 11 | CreateSourcePayload, |
| 12 | − CreateWorkspaceOauthClientPayload, | |
| 13 | 12 | DeleteSecretResult, |
| 14 | − DiscoverSourcePayload, | |
| 15 | 13 | InstanceConfig, |
| 16 | 14 | LocalInstallation, |
| 17 | 15 | SecretListItem, |
| @@ -21,11 +19,9 @@ import type { · 3 unmodified lines | ||
| 21 | 19 | } from "@executor/platform-api"; |
| 22 | 20 | import type { |
| 23 | 21 | Source, |
| 24 | − SourceDiscoveryResult, | |
| 25 | 22 | SourceInspection, |
| 26 | 23 | SourceInspectionDiscoverResult, |
| 27 | 24 | SourceInspectionToolDetail, |
| 28 | − ScopeOauthClient as WorkspaceOauthClient, | |
| 29 | 25 | } from "@executor/platform-sdk/schema"; |
| 30 | 26 | import * as Cause from "effect/Cause"; |
| 31 | 27 | import * as Effect from "effect/Effect"; |
| @@ -67,13 +63,6 @@ type SourceDiscoveryKeyParts = readonly [ · 35 unmodified lines | ||
| 67 | 63 | string, |
| 68 | 64 | number | null, |
| 69 | 65 | ]; |
| 70 | −type WorkspaceOauthClientsKeyParts = readonly [ | |
| 71 | − boolean, | |
| 72 | − Source["scopeId"], | |
| 73 | − string, | |
| 74 | − string, | |
| 75 | −]; | |
| 76 | − | |
| 77 | 66 | type InvalidationTarget = { |
| 78 | 67 | workspaceId?: Source["scopeId"]; |
| 79 | 68 | accountId?: string; |
| @@ -83,7 +72,6 @@ type InvalidationTarget = { · 3 unmodified lines | ||
| 83 | 72 | type ActiveQueryCollections = { |
| 84 | 73 | sourceLists: Set<string>; |
| 85 | 74 | sources: Set<string>; |
| 86 | − workspaceOauthClients: Set<string>; | |
| 87 | 75 | inspections: Set<string>; |
| 88 | 76 | toolDetails: Set<string>; |
| 89 | 77 | discoveries: Set<string>; |
| @@ -171,16 +159,6 @@ const encodeDiscoveryKey = ( · 81 unmodified lines | ||
| 171 | 159 | ): string => |
| 172 | 160 | encodeAtomKey([enabled, workspaceId, accountId, sourceId, query, limit] satisfies SourceDiscoveryKeyParts); |
| 173 | 161 | |
| 174 | −const 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 | − | |
| 184 | 162 | const toError = (cause: unknown): Error => |
| 185 | 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 | 369 | ).pipe(Atom.keepAlive); |
| 392 | 370 | }); |
| 393 | 371 | |
| 394 | −const 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 | − | |
| 414 | 372 | export type Loadable<T> = |
| 415 | 373 | | { status: "loading" } |
| 416 | 374 | | { status: "error"; error: Error } |
| @@ -539,7 +497,6 @@ const setCachedAtomValue = <A>( · 122 unmodified lines | ||
| 539 | 497 | const createActiveQueryCollections = (): ActiveQueryCollections => ({ |
| 540 | 498 | sourceLists: new Set(), |
| 541 | 499 | sources: new Set(), |
| 542 | − workspaceOauthClients: new Set(), | |
| 543 | 500 | inspections: new Set(), |
| 544 | 501 | toolDetails: new Set(), |
| 545 | 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 | 542 | activeQueries.inspections.forEach((key) => { |
| 593 | 543 | const [enabled, workspaceId, accountId, sourceId] = decodeAtomKey<SourceKeyParts>(key); |
| 594 | 544 | if (enabled && targetMatches(target, workspaceId, accountId, sourceId)) { |
| @@ -946,37 +896,6 @@ export const useSource = (sourceId: string): Loadable<Source> => { · 351 unmodified lines | ||
| 946 | 896 | return workspace.enabled ? source : pendingLoadable(workspace.workspace); |
| 947 | 897 | }; |
| 948 | 898 | |
| 949 | −export 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 | − | |
| 980 | 899 | export const useSourceInspection = (sourceId: string): Loadable<SourceInspection> => { |
| 981 | 900 | const workspace = useWorkspaceRequestContext(); |
| 982 | 901 | const requestedSourceId = workspace.enabled |
| @@ -1214,116 +1133,20 @@ export const useRemoveSource = () => · 231 unmodified lines | ||
| 1214 | 1133 | }, |
| 1215 | 1134 | ); |
| 1216 | 1135 | |
| 1217 | −export 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 | − | |
| 1231 | −export 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 | − | |
| 1256 | −export 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 | − | |
| 1281 | −export 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 | − | |
| 1309 | 1136 | export type { |
| 1310 | 1137 | CreateSecretPayload, |
| 1311 | 1138 | CreateSecretResult, |
| 1312 | 1139 | CreateSourcePayload, |
| 1313 | − CreateWorkspaceOauthClientPayload, | |
| 1314 | 1140 | DeleteSecretResult, |
| 1315 | − DiscoverSourcePayload, | |
| 1316 | 1141 | InstanceConfig, |
| 1317 | 1142 | LocalInstallation, |
| 1318 | 1143 | |
| 1319 | 1144 | SecretListItem, |
| 1320 | 1145 | Source, |
| 1321 | − SourceDiscoveryResult, | |
| 1322 | 1146 | SourceInspection, |
| 1323 | 1147 | SourceInspectionDiscoverResult, |
| 1324 | 1148 | SourceInspectionToolDetail, |
| 1325 | 1149 | UpdateSecretPayload, |
| 1326 | 1150 | UpdateSecretResult, |
| 1327 | 1151 | UpdateSourcePayload, |
| 1328 | − WorkspaceOauthClient, | |
| 1329 | 1152 | }; |
| @@ -12,8 +12,6 @@ · 11 unmodified lines | ||
| 12 | 12 | "./local/api": "./src/local/api.ts", |
| 13 | 13 | "./local/http": "./src/local/http.ts", |
| 14 | 14 | "./local-context": "./src/local-context.ts", |
| 15 | − "./oauth/api": "./src/oauth/api.ts", | |
| 16 | − "./oauth/http": "./src/oauth/http.ts", | |
| 17 | 15 | "./policies/api": "./src/policies/api.ts", |
| 18 | 16 | "./policies/http": "./src/policies/http.ts", |
| 19 | 17 | "./sources/api": "./src/sources/api.ts", |
| @@ -2,13 +2,11 @@ import { HttpApi, OpenApi } from "@effect/platform"; · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 3 | import { ExecutionsApi } from "./executions/api"; |
| 4 | 4 | import { LocalApi } from "./local/api"; |
| 5 | −import { OAuthApi } from "./oauth/api"; | |
| 6 | 5 | import { PoliciesApi } from "./policies/api"; |
| 7 | 6 | import { SourcesApi } from "./sources/api"; |
| 8 | 7 | |
| 9 | 8 | export class ExecutorApi extends HttpApi.make("executor") |
| 10 | 9 | .add(LocalApi) |
| 11 | − .add(OAuthApi) | |
| 12 | 10 | .add(SourcesApi) |
| 13 | 11 | .add(PoliciesApi) |
| 14 | 12 | .add(ExecutionsApi) |
| @@ -34,19 +34,6 @@ export type ExecutorApiClient = { · 33 unmodified lines | ||
| 34 | 34 | remove: PromiseMethod<ExecutorApiEffectClient["policies"]["remove"]>; |
| 35 | 35 | }; |
| 36 | 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 | 37 | list: PromiseMethod<ExecutorApiEffectClient["sources"]["list"]>; |
| 51 | 38 | create: PromiseMethod<ExecutorApiEffectClient["sources"]["create"]>; |
| 52 | 39 | get: PromiseMethod<ExecutorApiEffectClient["sources"]["get"]>; |
| @@ -59,16 +46,6 @@ export type ExecutorApiClient = { · 6 unmodified lines | ||
| 59 | 46 | >; |
| 60 | 47 | credentialPage: PromiseMethod<ExecutorApiEffectClient["sources"]["credentialPage"]>; |
| 61 | 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 | 50 | executions: { |
| 74 | 51 | create: PromiseMethod<ExecutorApiEffectClient["executions"]["create"]>; |
| @@ -125,23 +102,6 @@ export const createExecutorApiClient = async (input: { · 50 unmodified lines | ||
| 125 | 102 | remove: wrapMethod(effectClientPromise, (client) => client.policies.remove), |
| 126 | 103 | }, |
| 127 | 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 | 105 | list: wrapMethod(effectClientPromise, (client) => client.sources.list), |
| 146 | 106 | create: wrapMethod(effectClientPromise, (client) => client.sources.create), |
| 147 | 107 | get: wrapMethod(effectClientPromise, (client) => client.sources.get), |
| @@ -164,24 +124,6 @@ export const createExecutorApiClient = async (input: { · 16 unmodified lines | ||
| 164 | 124 | effectClientPromise, |
| 165 | 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 | 128 | executions: { |
| 187 | 129 | create: wrapMethod(effectClientPromise, (client) => client.executions.create), |
| @@ -6,13 +6,11 @@ import { ExecutorApi } from "./api"; · 5 unmodified lines | ||
| 6 | 6 | import { createControlPlaneExecutorLayer } from "./executor-context"; |
| 7 | 7 | import { ExecutorExecutionsLive } from "./executions/http"; |
| 8 | 8 | import { ExecutorLocalLive } from "./local/http"; |
| 9 | −import { ExecutorOAuthLive } from "./oauth/http"; | |
| 10 | 9 | import { ExecutorPoliciesLive } from "./policies/http"; |
| 11 | 10 | import { ExecutorSourcesLive } from "./sources/http"; |
| 12 | 11 | |
| 13 | 12 | export const ExecutorApiLive = HttpApiBuilder.api(ExecutorApi).pipe( |
| 14 | 13 | Layer.provide(ExecutorLocalLive), |
| 15 | − Layer.provide(ExecutorOAuthLive), | |
| 16 | 14 | Layer.provide(ExecutorSourcesLive), |
| 17 | 15 | Layer.provide(ExecutorPoliciesLive), |
| 18 | 16 | Layer.provide(ExecutorExecutionsLive), |
| @@ -38,28 +38,10 @@ export { · 37 unmodified lines | ||
| 38 | 38 | } from "./local/api"; |
| 39 | 39 | |
| 40 | 40 | export { |
| 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 | − | |
| 54 | −export { | |
| 55 | − CreateWorkspaceOauthClientPayloadSchema, | |
| 56 | 41 | CreateSourcePayloadSchema, |
| 57 | − DiscoverSourcePayloadSchema, | |
| 58 | 42 | SourcesApi, |
| 59 | 43 | UpdateSourcePayloadSchema, |
| 60 | − type CreateWorkspaceOauthClientPayload, | |
| 61 | 44 | type CreateSourcePayload, |
| 62 | − type DiscoverSourcePayload, | |
| 63 | 45 | type UpdateSourcePayload, |
| 64 | 46 | } from "./sources/api"; |
| 65 | 47 | |
| @@ -1,99 +0,0 @@ | ||
| 1 | −import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform"; | |
| 2 | −import { | |
| 3 | − JsonObjectSchema, | |
| 4 | − SecretRefSchema, | |
| 5 | − SourceAuthSessionIdSchema, | |
| 6 | − ScopeIdSchema as WorkspaceIdSchema, | |
| 7 | −} from "@executor/platform-sdk/schema"; | |
| 8 | −import * as Schema from "effect/Schema"; | |
| 9 | − | |
| 10 | −import { | |
| 11 | − ControlPlaneBadRequestError, | |
| 12 | − ControlPlaneForbiddenError, | |
| 13 | − ControlPlaneStorageError, | |
| 14 | − ControlPlaneUnauthorizedError, | |
| 15 | −} from "@executor/platform-sdk/errors"; | |
| 16 | −import { TrimmedNonEmptyStringSchema } from "@executor/platform-sdk/string-schemas"; | |
| 17 | − | |
| 18 | −const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema); | |
| 19 | − | |
| 20 | −const HtmlSchema = HttpApiSchema.Text({ | |
| 21 | − contentType: "text/html", | |
| 22 | −}); | |
| 23 | − | |
| 24 | −export const StartSourceOAuthPayloadSchema = Schema.Struct({ | |
| 25 | − providerKey: TrimmedNonEmptyStringSchema, | |
| 26 | − name: Schema.optional(Schema.NullOr(Schema.String)), | |
| 27 | − config: Schema.optional(Schema.NullOr(JsonObjectSchema)), | |
| 28 | −}); | |
| 29 | − | |
| 30 | −export type StartSourceOAuthPayload = typeof StartSourceOAuthPayloadSchema.Type; | |
| 31 | − | |
| 32 | −export const StartSourceOAuthResultSchema = Schema.Struct({ | |
| 33 | − sessionId: SourceAuthSessionIdSchema, | |
| 34 | − authorizationUrl: Schema.String, | |
| 35 | −}); | |
| 36 | − | |
| 37 | −export type StartSourceOAuthResult = typeof StartSourceOAuthResultSchema.Type; | |
| 38 | − | |
| 39 | −export 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 | − | |
| 47 | −export const CompleteSourceOAuthResultSchema = Schema.Struct({ | |
| 48 | − sessionId: SourceAuthSessionIdSchema, | |
| 49 | − auth: SourceOAuthAuthSchema, | |
| 50 | −}); | |
| 51 | − | |
| 52 | −export type CompleteSourceOAuthResult = typeof CompleteSourceOAuthResultSchema.Type; | |
| 53 | − | |
| 54 | −export const SourceOAuthPopupSuccessResultSchema = Schema.Struct({ | |
| 55 | − type: Schema.Literal("executor:oauth-result"), | |
| 56 | − ok: Schema.Literal(true), | |
| 57 | − sessionId: SourceAuthSessionIdSchema, | |
| 58 | − auth: SourceOAuthAuthSchema, | |
| 59 | −}); | |
| 60 | − | |
| 61 | −export 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 | − | |
| 68 | −export const SourceOAuthPopupResultSchema = Schema.Union( | |
| 69 | − SourceOAuthPopupSuccessResultSchema, | |
| 70 | − SourceOAuthPopupFailureResultSchema, | |
| 71 | −); | |
| 72 | − | |
| 73 | −export type SourceOAuthPopupResult = typeof SourceOAuthPopupResultSchema.Type; | |
| 74 | − | |
| 75 | −const 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 | − | |
| 82 | −export 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") {} | |
| @@ -1,315 +0,0 @@ | ||
| 1 | −import { | |
| 2 | − HttpApiBuilder, | |
| 3 | − HttpServerRequest, | |
| 4 | − HttpServerResponse, | |
| 5 | −} from "@effect/platform"; | |
| 6 | −import type { ScopeId as WorkspaceId } from "@executor/platform-sdk/schema"; | |
| 7 | −import * as Cause from "effect/Cause"; | |
| 8 | −import * as Either from "effect/Either"; | |
| 9 | −import * as Effect from "effect/Effect"; | |
| 10 | −import * as Schema from "effect/Schema"; | |
| 11 | − | |
| 12 | −import { ExecutorApi } from "../api"; | |
| 13 | −import { | |
| 14 | − ControlPlaneBadRequestError, | |
| 15 | − ControlPlaneStorageError, | |
| 16 | −} from "../errors"; | |
| 17 | −import { | |
| 18 | − getControlPlaneExecutor, | |
| 19 | − resolveRequestedLocalWorkspace, | |
| 20 | −} from "../local-context"; | |
| 21 | −import { | |
| 22 | − SourceOAuthPopupResultSchema, | |
| 23 | − type SourceOAuthPopupResult, | |
| 24 | −} from "./api"; | |
| 25 | − | |
| 26 | −const OAUTH_RESULT_STORAGE_KEY_PREFIX = "executor:oauth-result:"; | |
| 27 | − | |
| 28 | −const escapeHtml = (value: string): string => | |
| 29 | − value | |
| 30 | − .replaceAll("&", "&") | |
| 31 | − .replaceAll("<", "<") | |
| 32 | − .replaceAll(">", ">") | |
| 33 | − .replaceAll('"', """) | |
| 34 | − .replaceAll("'", "'"); | |
| 35 | − | |
| 36 | −const serializeValueForScript = (value: unknown): string => | |
| 37 | − JSON.stringify(value) | |
| 38 | − .replaceAll("<", "\\u003c") | |
| 39 | − .replaceAll(">", "\\u003e") | |
| 40 | − .replaceAll("&", "\\u0026"); | |
| 41 | − | |
| 42 | −const encodeSourceOAuthPopupResultJson = Schema.encodeSync( | |
| 43 | − Schema.parseJson(SourceOAuthPopupResultSchema), | |
| 44 | −); | |
| 45 | − | |
| 46 | −const serializeJsonLiteralForScript = (value: string): string => | |
| 47 | − value | |
| 48 | − .replaceAll("<", "\\u003c") | |
| 49 | − .replaceAll(">", "\\u003e") | |
| 50 | − .replaceAll("&", "\\u0026"); | |
| 51 | − | |
| 52 | −const htmlResponse = (html: string, status = 200) => | |
| 53 | − HttpServerResponse.html(html).pipe( | |
| 54 | − HttpServerResponse.setStatus(status), | |
| 55 | − ); | |
| 56 | − | |
| 57 | −const 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 | − | |
| 78 | −const 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 | − | |
| 94 | −const 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 | − | |
| 121 | −const 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 | − | |
| 232 | −export 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 | −); | |
| @@ -1,13 +1,9 @@ | ||
| 1 | 1 | import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform"; |
| 2 | 2 | import { |
| 3 | 3 | CreateSourcePayloadSchema, |
| 4 | − CreateScopeOauthClientPayloadSchema as CreateWorkspaceOauthClientPayloadSchema, | |
| 5 | − CredentialOauthCompleteUrlParamsSchema, | |
| 6 | 4 | CredentialPageUrlParamsSchema, |
| 7 | 5 | CredentialSubmitPayloadSchema, |
| 8 | − DiscoverSourcePayloadSchema, | |
| 9 | 6 | UpdateSourcePayloadSchema, |
| 10 | − ScopeOauthClientQuerySchema as WorkspaceOauthClientQuerySchema, | |
| 11 | 7 | } from "@executor/platform-sdk/contracts"; |
| 12 | 8 | import { |
| 13 | 9 | ControlPlaneBadRequestError, |
| @@ -17,90 +13,35 @@ import { · 3 unmodified lines | ||
| 17 | 13 | ControlPlaneUnauthorizedError, |
| 18 | 14 | } from "@executor/platform-sdk/errors"; |
| 19 | 15 | import { |
| 20 | − ProviderAuthGrantIdSchema, | |
| 21 | − SourceDiscoveryResultSchema, | |
| 22 | 16 | SourceIdSchema, |
| 23 | 17 | SourceInspectionDiscoverPayloadSchema, |
| 24 | 18 | SourceInspectionDiscoverResultSchema, |
| 25 | 19 | SourceInspectionSchema, |
| 26 | 20 | SourceInspectionToolDetailSchema, |
| 27 | 21 | SourceSchema, |
| 28 | − ScopeOauthClientIdSchema as WorkspaceOauthClientIdSchema, | |
| 29 | − ScopeOauthClientSchema as WorkspaceOauthClientSchema, | |
| 30 | 22 | ScopeIdSchema as WorkspaceIdSchema, |
| 31 | 23 | } from "@executor/platform-sdk/schema"; |
| 32 | 24 | import * as Schema from "effect/Schema"; |
| 33 | 25 | |
| 34 | 26 | export type { |
| 35 | 27 | CreateSourcePayload, |
| 36 | − CreateScopeOauthClientPayload as CreateWorkspaceOauthClientPayload, | |
| 37 | − DiscoverSourcePayload, | |
| 38 | 28 | UpdateSourcePayload, |
| 39 | 29 | } from "@executor/platform-sdk/contracts"; |
| 40 | 30 | |
| 41 | 31 | export { |
| 42 | 32 | CreateSourcePayloadSchema, |
| 43 | − CreateWorkspaceOauthClientPayloadSchema, | |
| 44 | − DiscoverSourcePayloadSchema, | |
| 45 | 33 | UpdateSourcePayloadSchema, |
| 46 | 34 | }; |
| 47 | 35 | |
| 48 | 36 | const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema); |
| 49 | 37 | const sourceIdParam = HttpApiSchema.param("sourceId", SourceIdSchema); |
| 50 | 38 | const toolPathParam = HttpApiSchema.param("toolPath", Schema.String); |
| 51 | −const oauthClientIdParam = HttpApiSchema.param( | |
| 52 | − "oauthClientId", | |
| 53 | − WorkspaceOauthClientIdSchema, | |
| 54 | −); | |
| 55 | −const grantIdParam = HttpApiSchema.param("grantId", ProviderAuthGrantIdSchema); | |
| 56 | 39 | |
| 57 | 40 | const HtmlSchema = HttpApiSchema.Text({ |
| 58 | 41 | contentType: "text/html", |
| 59 | 42 | }); |
| 60 | 43 | |
| 61 | 44 | export 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 | 45 | .add( |
| 105 | 46 | HttpApiEndpoint.get("list")`/workspaces/${workspaceIdParam}/sources` |
| 106 | 47 | .addSuccess(Schema.Array(SourceSchema)) |
| @@ -162,22 +103,6 @@ export class SourcesApi extends HttpApiGroup.make("sources") · 55 unmodified lines | ||
| 162 | 103 | .addError(ControlPlaneNotFoundError) |
| 163 | 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 | 106 | .add( |
| 182 | 107 | HttpApiEndpoint.get("inspection")`/workspaces/${workspaceIdParam}/sources/${sourceIdParam}/inspection` |
| 183 | 108 | .addSuccess(SourceInspectionSchema) |
| @@ -37,14 +37,6 @@ const escapeHtml = (value: string): string => · 36 unmodified lines | ||
| 37 | 37 | .replaceAll('"', """) |
| 38 | 38 | .replaceAll("'", "'"); |
| 39 | 39 | |
| 40 | −const serializeJsonForScript = (value: unknown): string => | |
| 41 | − JSON.stringify(value) | |
| 42 | − .replaceAll("<", "\\u003c") | |
| 43 | − .replaceAll(">", "\\u003e") | |
| 44 | − .replaceAll("&", "\\u0026"); | |
| 45 | − | |
| 46 | −const SOURCE_OAUTH_RESULT_STORAGE_KEY_PREFIX = "executor:oauth-result:"; | |
| 47 | − | |
| 48 | 40 | const credentialPageDocument = (input: { |
| 49 | 41 | title: string; |
| 50 | 42 | eyebrow: string; |
| @@ -404,133 +396,6 @@ const credentialPageDocument = (input: { · 353 unmodified lines | ||
| 404 | 396 | </body> |
| 405 | 397 | </html>`; |
| 406 | 398 | |
| 407 | −const 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 | − | |
| 534 | 399 | const htmlResponse = ( |
| 535 | 400 | html: string, |
| 536 | 401 | status = 200, |
| @@ -616,93 +481,6 @@ export const ExecutorSourcesLive = HttpApiBuilder.group( · 79 unmodified lines | ||
| 616 | 481 | "sources", |
| 617 | 482 | (handlers) => |
| 618 | 483 | 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 | 484 | .handle("list", ({ path }) => |
| 707 | 485 | resolveRequestedLocalWorkspace("sources.list", path.workspaceId).pipe( |
| 708 | 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 | ); |
| @@ -4,7 +4,6 @@ import { · 3 unmodified lines | ||
| 4 | 4 | type ScopeId as AccountId, |
| 5 | 5 | LocalInstallationSchema, |
| 6 | 6 | LocalScopePolicySchema, |
| 7 | − SourceDiscoveryResultSchema, | |
| 8 | 7 | SourceIdSchema, |
| 9 | 8 | SourceInspectionDiscoverPayloadSchema, |
| 10 | 9 | SourceInspectionDiscoverResultSchema, |
| @@ -12,7 +11,6 @@ import { · 1 unmodified line | ||
| 12 | 11 | SourceInspectionToolDetailSchema, |
| 13 | 12 | SourceSchema, |
| 14 | 13 | type ScopeId as WorkspaceId, |
| 15 | − ScopeOauthClientSchema, | |
| 16 | 14 | } from "@executor/platform-sdk/schema"; |
| 17 | 15 | import * as Effect from "effect/Effect"; |
| 18 | 16 | import * as Layer from "effect/Layer"; |
| @@ -59,7 +57,6 @@ import { · 40 unmodified lines | ||
| 59 | 57 | createLocalSecret, |
| 60 | 58 | createPolicy, |
| 61 | 59 | deleteLocalSecret, |
| 62 | − discoverSource, | |
| 63 | 60 | discoverSourceInspectionTools, |
| 64 | 61 | getPolicy, |
| 65 | 62 | getSource, |
| @@ -81,9 +78,6 @@ import { · 15 unmodified lines | ||
| 81 | 78 | UpdatePolicyPayloadSchema, |
| 82 | 79 | } from "@executor/platform-sdk/contracts"; |
| 83 | 80 | import { |
| 84 | − CreateScopeOauthClientPayloadSchema, | |
| 85 | − DiscoverSourcePayloadSchema, | |
| 86 | − type CreateScopeOauthClientPayload as CreateWorkspaceOauthClientPayload, | |
| 87 | 81 | UpdateSourcePayloadSchema, |
| 88 | 82 | } from "@executor/platform-sdk/contracts"; |
| 89 | 83 | |
| @@ -129,9 +123,6 @@ const updateSourceInputSchema = Schema.standardSchemaV1( · 39 unmodified lines | ||
| 129 | 123 | payload: UpdateSourcePayloadSchema, |
| 130 | 124 | }), |
| 131 | 125 | ); |
| 132 | −const discoverSourceInputSchema = Schema.standardSchemaV1( | |
| 133 | − DiscoverSourcePayloadSchema, | |
| 134 | −); | |
| 135 | 126 | const inspectToolInputSchema = Schema.standardSchemaV1( |
| 136 | 127 | Schema.Struct({ |
| 137 | 128 | sourceId: SourceIdSchema, |
| @@ -161,27 +152,6 @@ const updatePolicyInputSchema = Schema.standardSchemaV1( · 23 unmodified lines | ||
| 161 | 152 | payload: UpdatePolicyPayloadSchema, |
| 162 | 153 | }), |
| 163 | 154 | ); |
| 164 | −const workspaceOauthClientListInputSchema = Schema.standardSchemaV1( | |
| 165 | − Schema.Struct({ | |
| 166 | − providerKey: Schema.String, | |
| 167 | − }), | |
| 168 | −); | |
| 169 | −const workspaceOauthClientListOutputSchema = Schema.standardSchemaV1( | |
| 170 | − Schema.Array(ScopeOauthClientSchema), | |
| 171 | −); | |
| 172 | −const createWorkspaceOauthClientInputSchema = Schema.standardSchemaV1( | |
| 173 | − CreateScopeOauthClientPayloadSchema, | |
| 174 | −); | |
| 175 | −const removeWorkspaceOauthClientInputSchema = Schema.standardSchemaV1( | |
| 176 | − Schema.Struct({ | |
| 177 | − oauthClientId: Schema.String, | |
| 178 | − }), | |
| 179 | −); | |
| 180 | −const removeProviderGrantInputSchema = Schema.standardSchemaV1( | |
| 181 | − Schema.Struct({ | |
| 182 | − grantId: Schema.String, | |
| 183 | − }), | |
| 184 | −); | |
| 185 | 155 | const sourceOutputSchema = Schema.standardSchemaV1(SourceSchema); |
| 186 | 156 | const sourceInspectionOutputSchema = Schema.standardSchemaV1( |
| 187 | 157 | SourceInspectionSchema, |
| @@ -192,9 +162,6 @@ const sourceInspectionToolOutputSchema = Schema.standardSchemaV1( · 4 unmodified lines | ||
| 192 | 162 | const sourceInspectionDiscoverOutputSchema = Schema.standardSchemaV1( |
| 193 | 163 | SourceInspectionDiscoverResultSchema, |
| 194 | 164 | ); |
| 195 | −const sourceDiscoveryOutputSchema = Schema.standardSchemaV1( | |
| 196 | − SourceDiscoveryResultSchema, | |
| 197 | −); | |
| 198 | 165 | const localScopePolicyOutputSchema = Schema.standardSchemaV1( |
| 199 | 166 | LocalScopePolicySchema, |
| 200 | 167 | ); |
| @@ -384,22 +351,6 @@ export const createWorkspaceExecutorAdminToolMap = ( · 183 unmodified lines | ||
| 384 | 351 | }, |
| 385 | 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 | 354 | "executor.sources.list": toTool({ |
| 404 | 355 | tool: { |
| 405 | 356 | description: "List sources connected in the current workspace.", |
| @@ -539,82 +490,6 @@ export const createWorkspaceExecutorAdminToolMap = ( · 133 unmodified lines | ||
| 539 | 490 | }, |
| 540 | 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 | 493 | "executor.policies.list": toTool({ |
| 619 | 494 | tool: { |
| 620 | 495 | description: "List local workspace policies.", |
| @@ -780,20 +655,6 @@ export const createExecutorAdminToolMap = (input: { · 159 unmodified lines | ||
| 780 | 655 | }, |
| 781 | 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 | 658 | "executor.sources.list": toTool({ |
| 798 | 659 | tool: { |
| 799 | 660 | description: "List sources connected in the current workspace.", |
| @@ -888,57 +749,6 @@ export const createExecutorAdminToolMap = (input: { · 88 unmodified lines | ||
| 888 | 749 | }, |
| 889 | 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 | 752 | "executor.policies.list": toTool({ |
| 943 | 753 | tool: { |
| 944 | 754 | description: "List local workspace policies.", |
| @@ -7,8 +7,6 @@ import type { · 6 unmodified lines | ||
| 7 | 7 | ExecutionInteraction, |
| 8 | 8 | LocalInstallation, |
| 9 | 9 | LocalScopePolicy, |
| 10 | − ProviderAuthGrant, | |
| 11 | − ScopeOauthClient, | |
| 12 | 10 | Source, |
| 13 | 11 | } from "./schema"; |
| 14 | 12 | import { ExecutionIdSchema } from "./schema"; |
| @@ -50,11 +48,9 @@ import { · 35 unmodified lines | ||
| 50 | 48 | updatePolicy, |
| 51 | 49 | } from "./policies/operations"; |
| 52 | 50 | import type { |
| 53 | − CreateScopeOauthClientPayload, | |
| 54 | 51 | CreateSourcePayload, |
| 55 | 52 | UpdateSourcePayload, |
| 56 | 53 | } from "./sources/contracts"; |
| 57 | −import { discoverSource } from "./sources/discovery"; | |
| 58 | 54 | import { |
| 59 | 55 | discoverSourceInspectionTools, |
| 60 | 56 | getSourceInspection, |
| @@ -83,13 +79,9 @@ import { · 22 unmodified lines | ||
| 83 | 79 | resumeExecution, |
| 84 | 80 | } from "./runtime/execution/service"; |
| 85 | 81 | import type { |
| 86 | − CompleteProviderOauthCallbackResult, | |
| 87 | 82 | CompleteSourceCredentialSetupResult, |
| 88 | − CompleteSourceOAuthSessionResult, | |
| 89 | 83 | ExecutorAddSourceInput, |
| 90 | 84 | ExecutorSourceAddResult, |
| 91 | − StartSourceOAuthSessionInput, | |
| 92 | − StartSourceOAuthSessionResult, | |
| 93 | 85 | } from "./runtime/sources/source-auth-service"; |
| 94 | 86 | |
| 95 | 87 | type DistributiveOmit<T, Keys extends PropertyKey> = T extends unknown |
| @@ -110,11 +102,6 @@ export type ExecutorSourceInput = DistributiveOmit< · 14 unmodified lines | ||
| 110 | 102 | "scopeId" | "actorScopeId" | "executionId" | "interactionId" |
| 111 | 103 | >; |
| 112 | 104 | |
| 113 | −export type ExecutorSourceOAuthInput = DistributiveOmit< | |
| 114 | − StartSourceOAuthSessionInput, | |
| 115 | − "scopeId" | "actorScopeId" | |
| 116 | −>; | |
| 117 | − | |
| 118 | 105 | export type ExecutorEffect = { |
| 119 | 106 | runtime: ExecutorRuntime; |
| 120 | 107 | installation: LocalInstallation; |
| @@ -187,10 +174,6 @@ export type ExecutorEffect = { · 66 unmodified lines | ||
| 187 | 174 | baseUrl?: string | null; |
| 188 | 175 | }, |
| 189 | 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 | 177 | list: () => ProvidedEffect<ReturnType<typeof listSources>>; |
| 195 | 178 | create: ( |
| 196 | 179 | payload: CreateSourcePayload, |
| @@ -218,41 +201,6 @@ export type ExecutorEffect = { · 21 unmodified lines | ||
| 218 | 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 | 205 | executions: { |
| 258 | 206 | create: ( |
| @@ -366,7 +314,6 @@ const fromRuntime = (runtime: ExecutorRuntime): ExecutorEffect => { · 107 unmodified lines | ||
| 366 | 314 | options, |
| 367 | 315 | ); |
| 368 | 316 | }), |
| 369 | − discover: (input) => provide(discoverSource(input)), | |
| 370 | 317 | list: () => provide(listSources({ scopeId, actorScopeId })), |
| 371 | 318 | create: (payload) => provide(createSource({ scopeId, actorScopeId, payload })), |
| 372 | 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 | 346 | executions: { |
| 461 | 347 | create: (payload) => |
| @@ -5,7 +5,6 @@ import { · 4 unmodified lines | ||
| 5 | 5 | type CreateExecutorEffectOptions, |
| 6 | 6 | type ExecutorEffect, |
| 7 | 7 | type ExecutorSourceInput, |
| 8 | − type ExecutorSourceOAuthInput, | |
| 9 | 8 | } from "./executor-effect"; |
| 10 | 9 | |
| 11 | 10 | type Promiseify<T> = T extends Effect.Effect<infer A, any, any> |
| @@ -22,7 +21,6 @@ export type Executor = Omit<Promiseify<ExecutorEffect>, "runtime">; · 10 unmodified lines | ||
| 22 | 21 | export type CreateExecutorOptions = CreateExecutorEffectOptions; |
| 23 | 22 | export type { |
| 24 | 23 | ExecutorSourceInput, |
| 25 | − ExecutorSourceOAuthInput, | |
| 26 | 24 | }; |
| 27 | 25 | |
| 28 | 26 | const toPromiseExecutor = (executor: ExecutorEffect): Executor => { |
| @@ -60,7 +58,6 @@ const toPromiseExecutor = (executor: ExecutorEffect): Executor => { · 31 unmodified lines | ||
| 60 | 58 | }, |
| 61 | 59 | sources: { |
| 62 | 60 | add: (input, options) => run(executor.sources.add(input, options)), |
| 63 | − discover: (input) => run(executor.sources.discover(input)), | |
| 64 | 61 | list: () => run(executor.sources.list()), |
| 65 | 62 | create: (payload) => run(executor.sources.create(payload)), |
| 66 | 63 | get: (sourceId) => run(executor.sources.get(sourceId)), |
| @@ -72,24 +69,6 @@ const toPromiseExecutor = (executor: ExecutorEffect): Executor => { · 5 unmodified lines | ||
| 72 | 69 | tool: (input) => run(executor.sources.inspection.tool(input)), |
| 73 | 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 | 73 | executions: { |
| 95 | 74 | create: (payload) => run(executor.executions.create(payload)), |
| @@ -1,6 +1,5 @@ | ||
| 1 | 1 | export * from "./local/operations"; |
| 2 | 2 | export * from "./local/secrets"; |
| 3 | 3 | export * from "./policies/operations"; |
| 4 | −export * from "./sources/discovery"; | |
| 5 | 4 | export * from "./sources/inspection"; |
| 6 | 5 | export * from "./sources/operations"; |
| @@ -111,7 +111,6 @@ export * from "./sources/source-store"; · 110 unmodified lines | ||
| 111 | 111 | export * from "./executor-state-store"; |
| 112 | 112 | export * from "./execution/scope/environment"; |
| 113 | 113 | export * from "../sources/inspection"; |
| 114 | −export * from "../sources/discovery"; | |
| 115 | 114 | export * from "./execution/service"; |
| 116 | 115 | export type { |
| 117 | 116 | CreateScopeInternalToolMap, |
| @@ -3,14 +3,11 @@ import type { · 2 unmodified lines | ||
| 3 | 3 | SecretRef, |
| 4 | 4 | } from "@executor/source-core"; |
| 5 | 5 | import { |
| 6 | − type ProviderAuthGrant, | |
| 7 | 6 | type ScopeId, |
| 8 | − type ScopeOauthClient, | |
| 9 | 7 | type Source, |
| 10 | 8 | type SourceAuthSession, |
| 11 | 9 | SourceAuthSessionIdSchema, |
| 12 | 10 | SourceSchema, |
| 13 | − type SourceOauthClientInput, | |
| 14 | 11 | } from "#schema"; |
| 15 | 12 | import * as Context from "effect/Context"; |
| 16 | 13 | import * as Effect from "effect/Effect"; |
| @@ -92,46 +89,11 @@ export type ExecutorAddSourceInput = RegisteredExecutorAddSourceInput & { · 75 unmodified lines | ||
| 92 | 89 | interactionId: SourceAuthSession["interactionId"]; |
| 93 | 90 | }; |
| 94 | 91 | |
| 95 | −export type CreateScopeOauthClientInput = { | |
| 96 | − scopeId: ScopeId; | |
| 97 | − providerKey: string; | |
| 98 | − label?: string | null; | |
| 99 | − oauthClient: SourceOauthClientInput; | |
| 100 | −}; | |
| 101 | − | |
| 102 | −export type SourceOAuthProviderInput = { | |
| 103 | − providerKey: string; | |
| 104 | − config?: Record<string, unknown> | null; | |
| 105 | −}; | |
| 106 | − | |
| 107 | −export type StartSourceOAuthSessionInput = { | |
| 108 | − scopeId: ScopeId; | |
| 109 | − actorScopeId?: ScopeId | null; | |
| 110 | − provider: SourceOAuthProviderInput; | |
| 111 | − baseUrl?: string | null; | |
| 112 | − displayName?: string | null; | |
| 113 | −}; | |
| 114 | − | |
| 115 | −export type StartSourceOAuthSessionResult = { | |
| 116 | − sessionId: SourceAuthSession["id"]; | |
| 117 | − authorizationUrl: string; | |
| 118 | −}; | |
| 119 | − | |
| 120 | −export type CompleteSourceOAuthSessionResult = { | |
| 121 | − sessionId: SourceAuthSession["id"]; | |
| 122 | − auth: Extract<Source["auth"], { kind: "oauth2" }>; | |
| 123 | −}; | |
| 124 | − | |
| 125 | 92 | export type CompleteSourceCredentialSetupResult = { |
| 126 | 93 | sessionId: SourceAuthSession["id"]; |
| 127 | 94 | source: Source; |
| 128 | 95 | }; |
| 129 | 96 | |
| 130 | −export type CompleteProviderOauthCallbackResult = { | |
| 131 | − sessionId: SourceAuthSession["id"]; | |
| 132 | − sources: ReadonlyArray<Source>; | |
| 133 | −}; | |
| 134 | − | |
| 135 | 97 | type RuntimeSourceAuthServiceShape = { |
| 136 | 98 | getSourceById: (input: { |
| 137 | 99 | scopeId: ScopeId; |
| @@ -150,38 +112,6 @@ type RuntimeSourceAuthServiceShape = { · 12 unmodified lines | ||
| 150 | 112 | baseUrl?: string | null; |
| 151 | 113 | }, |
| 152 | 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 | 115 | completeSourceCredentialSetup: (input: { |
| 186 | 116 | scopeId: ScopeId; |
| 187 | 117 | sourceId: Source["id"]; |
| @@ -228,16 +158,6 @@ export const createRuntimeSourceAuthService = ( · 40 unmodified lines | ||
| 228 | 158 | }), |
| 229 | 159 | addExecutorSource: () => |
| 230 | 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 | 161 | completeSourceCredentialSetup: () => |
| 242 | 162 | provideLocalWorkspace(disabledSourcePlugins("completeSourceCredentialSetup")), |
| 243 | 163 | }; |
| @@ -1,278 +0,0 @@ | ||
| 1 | −import { | |
| 2 | − createServer, | |
| 3 | − type IncomingMessage, | |
| 4 | − type ServerResponse, | |
| 5 | −} from "node:http"; | |
| 6 | − | |
| 7 | −import { | |
| 8 | − describe, | |
| 9 | − expect, | |
| 10 | − it, | |
| 11 | −} from "@effect/vitest"; | |
| 12 | −import * as Effect from "effect/Effect"; | |
| 13 | − | |
| 14 | −import { | |
| 15 | − discoverSource, | |
| 16 | −} from "../../sources/discovery"; | |
| 17 | − | |
| 18 | −type TestServer = { | |
| 19 | − url: string; | |
| 20 | −}; | |
| 21 | − | |
| 22 | −const toError = (cause: unknown): Error => | |
| 23 | − cause instanceof Error ? cause : new Error(String(cause)); | |
| 24 | − | |
| 25 | −const 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 | − | |
| 93 | −describe("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 | −}); | |
| @@ -23,22 +23,6 @@ export { · 22 unmodified lines | ||
| 23 | 23 | type SourceCatalogKind, |
| 24 | 24 | type SourceCatalogVisibility, |
| 25 | 25 | } from "./models/source-catalog"; |
| 26 | −export { | |
| 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"; | |
| 42 | 26 | export { |
| 43 | 27 | AuthArtifactKindSchema, |
| 44 | 28 | AuthArtifactSlotSchema, |
| @@ -16,5 +16,4 @@ export * from "./models/local-installation"; · 15 unmodified lines | ||
| 16 | 16 | export * from "./models/local-config"; |
| 17 | 17 | export * from "./models/execution"; |
| 18 | 18 | export * from "./models/source-inspection"; |
| 19 | −export * from "./models/source-discovery"; | |
| 20 | 19 | export * from "./models/code-migration"; |
| @@ -1,16 +0,0 @@ | ||
| 1 | −export { | |
| 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"; | |
| @@ -3,10 +3,8 @@ import { · 2 unmodified lines | ||
| 3 | 3 | JsonObjectSchema, |
| 4 | 4 | ProviderAuthGrantIdSchema, |
| 5 | 5 | SourceAuthSchema, |
| 6 | − SourceDiscoveryResultSchema, | |
| 7 | 6 | SourceImportAuthPolicySchema, |
| 8 | 7 | SourceKindSchema, |
| 9 | − SourceProbeAuthSchema, | |
| 10 | 8 | SourceSchema, |
| 11 | 9 | SourceStatusSchema, |
| 12 | 10 | SourceOauthClientInputSchema, |
| @@ -93,15 +91,7 @@ export type CreateScopeOauthClientPayload = · 80 unmodified lines | ||
| 93 | 91 | export const oauthClientIdParam = ScopeOauthClientIdSchema; |
| 94 | 92 | export const grantIdParam = ProviderAuthGrantIdSchema; |
| 95 | 93 | |
| 96 | −export const DiscoverSourcePayloadSchema = Schema.Struct({ | |
| 97 | − url: TrimmedNonEmptyStringSchema, | |
| 98 | − probeAuth: Schema.optional(SourceProbeAuthSchema), | |
| 99 | −}); | |
| 100 | − | |
| 101 | −export type DiscoverSourcePayload = typeof DiscoverSourcePayloadSchema.Type; | |
| 102 | − | |
| 103 | 94 | export { |
| 104 | − SourceDiscoveryResultSchema, | |
| 105 | 95 | ScopeIdSchema, |
| 106 | 96 | ScopeOauthClientSchema, |
| 107 | 97 | }; |
| @@ -1,56 +0,0 @@ | ||
| 1 | −import * as Effect from "effect/Effect"; | |
| 2 | − | |
| 3 | −import { | |
| 4 | − fallbackSourceDiscoveryResult, | |
| 5 | − normalizeSourceDiscoveryUrl, | |
| 6 | − probeHeadersFromAuth, | |
| 7 | − type SourceAdapter, | |
| 8 | − type SourceProbeAuth, | |
| 9 | − type SourceDiscoveryResult, | |
| 10 | −} from "@executor/source-core"; | |
| 11 | − | |
| 12 | −import { | |
| 13 | − builtInSourceAdapters, | |
| 14 | −} from "../runtime/sources/source-adapters"; | |
| 15 | − | |
| 16 | −type DiscoverableSourceAdapter = SourceAdapter & { | |
| 17 | − detectSource: NonNullable<SourceAdapter["detectSource"]>; | |
| 18 | −}; | |
| 19 | − | |
| 20 | −const sourceAdapters: ReadonlyArray<SourceAdapter> = builtInSourceAdapters; | |
| 21 | − | |
| 22 | −const discoverableSourceAdapters = sourceAdapters.filter( | |
| 23 | − (adapter): adapter is DiscoverableSourceAdapter => | |
| 24 | − adapter.detectSource !== undefined, | |
| 25 | −); | |
| 26 | − | |
| 27 | −export 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 | − }); | |
| @@ -6,10 +6,7 @@ import { · 5 unmodified lines | ||
| 6 | 6 | } from "./source-models"; |
| 7 | 7 | |
| 8 | 8 | export const SourceDiscoveryKindSchema = Schema.Literal( |
| 9 | − "mcp", | |
| 10 | − "openapi", | |
| 11 | − "google_discovery", | |
| 12 | − "graphql", | |
| 9 | + "plugin", | |
| 13 | 10 | "unknown", |
| 14 | 11 | ); |
| 15 | 12 | |
| @@ -215,9 +215,6 @@ export const executeHttpProbe = (input: { · 214 unmodified lines | ||
| 215 | 215 | catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)), |
| 216 | 216 | }); |
| 217 | 217 | |
| 218 | −export const looksLikeGraphqlEndpoint = (normalizedUrl: string): boolean => | |
| 219 | − /graphql/i.test(new URL(normalizedUrl).pathname); | |
| 220 | − | |
| 221 | 218 | export const tryParseJson = (value: string): unknown => { |
| 222 | 219 | try { |
| 223 | 220 | return JSON.parse(value) as unknown; |
| @@ -244,7 +241,7 @@ export const fallbackSourceDiscoveryResult = ( · 20 unmodified lines | ||
| 244 | 241 | ), |
| 245 | 242 | toolCount: null, |
| 246 | 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 | }; |