- packages/core/api: HttpApi contract with ToolsApi + SecretsApi groups SDK tagged errors pass through directly with status annotations (ToolNotFoundError→404, PolicyDeniedError→403, etc.) - apps/server: API handler implementation via HttpApiBuilder Exports createApiHandler() for reuse (no baked-in server runtime) Handlers are pure pass-through — no manual error catching - apps/web: Vite + React frontend served on same port as API Vite plugin forwards /v1/*, /docs, /openapi.json to Effect handler effect-atom integration: AtomHttpApi.Tag typed client, query/mutation atoms RegistryProvider + useAtomValue with Result pattern matching - Catalog pins @effect/platform, platform-bun, platform-node, cluster - All 9 packages typecheck clean
Showing the changes to packages/core/api/src (5 of 26 files). Show all
Unified · Split
| @@ -0,0 +1,14 @@ | |||
| 1 | import { HttpApi, OpenApi } from "@effect/platform"; | ||
| 2 | |||
| 3 | import { ToolsApi } from "./tools/api"; | ||
| 4 | import { SecretsApi } from "./secrets/api"; | ||
| 5 | |||
| 6 | export const ExecutorApi = HttpApi.make("executor") | ||
| 7 | .add(ToolsApi) | ||
| 8 | .add(SecretsApi) | ||
| 9 | .annotateContext( | ||
| 10 | OpenApi.annotations({ | ||
| 11 | title: "Executor API", | ||
| 12 | description: "Tool execution platform API", | ||
| 13 | }), | ||
| 14 | ); | ||
| @@ -0,0 +1,26 @@ | |||
| 1 | import { HttpApiSchema } from "@effect/platform"; | ||
| 2 | import { Schema } from "effect"; | ||
| 3 | |||
| 4 | export class ApiNotFoundError extends Schema.TaggedError<ApiNotFoundError>()( | ||
| 5 | "ApiNotFoundError", | ||
| 6 | { | ||
| 7 | message: Schema.String, | ||
| 8 | }, | ||
| 9 | HttpApiSchema.annotations({ status: 404 }), | ||
| 10 | ) {} | ||
| 11 | |||
| 12 | export class ApiBadRequestError extends Schema.TaggedError<ApiBadRequestError>()( | ||
| 13 | "ApiBadRequestError", | ||
| 14 | { | ||
| 15 | message: Schema.String, | ||
| 16 | }, | ||
| 17 | HttpApiSchema.annotations({ status: 400 }), | ||
| 18 | ) {} | ||
| 19 | |||
| 20 | export class ApiInternalError extends Schema.TaggedError<ApiInternalError>()( | ||
| 21 | "ApiInternalError", | ||
| 22 | { | ||
| 23 | message: Schema.String, | ||
| 24 | }, | ||
| 25 | HttpApiSchema.annotations({ status: 500 }), | ||
| 26 | ) {} | ||
| @@ -0,0 +1,3 @@ | |||
| 1 | export { ExecutorApi } from "./api"; | ||
| 2 | export { ToolsApi } from "./tools/api"; | ||
| 3 | export { SecretsApi } from "./secrets/api"; | ||
| @@ -0,0 +1,78 @@ | |||
| 1 | import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform"; | ||
| 2 | import { Schema } from "effect"; | ||
| 3 | import { | ||
| 4 | ScopeId, | ||
| 5 | SecretId, | ||
| 6 | SecretNotFoundError, | ||
| 7 | SecretResolutionError, | ||
| 8 | } from "@executor/sdk"; | ||
| 9 | |||
| 10 | // --------------------------------------------------------------------------- | ||
| 11 | // Params | ||
| 12 | // --------------------------------------------------------------------------- | ||
| 13 | |||
| 14 | const scopeIdParam = HttpApiSchema.param("scopeId", ScopeId); | ||
| 15 | const secretIdParam = HttpApiSchema.param("secretId", SecretId); | ||
| 16 | |||
| 17 | // --------------------------------------------------------------------------- | ||
| 18 | // Response / payload schemas | ||
| 19 | // --------------------------------------------------------------------------- | ||
| 20 | |||
| 21 | const SecretRefResponse = Schema.Struct({ | ||
| 22 | id: SecretId, | ||
| 23 | scopeId: ScopeId, | ||
| 24 | name: Schema.String, | ||
| 25 | provider: Schema.optional(Schema.String), | ||
| 26 | purpose: Schema.optional(Schema.String), | ||
| 27 | createdAt: Schema.Number, | ||
| 28 | }); | ||
| 29 | |||
| 30 | const SecretStatusResponse = Schema.Struct({ | ||
| 31 | secretId: SecretId, | ||
| 32 | status: Schema.Literal("resolved", "missing"), | ||
| 33 | }); | ||
| 34 | |||
| 35 | const SetSecretPayload = Schema.Struct({ | ||
| 36 | id: SecretId, | ||
| 37 | name: Schema.String, | ||
| 38 | value: Schema.String, | ||
| 39 | purpose: Schema.optional(Schema.String), | ||
| 40 | provider: Schema.optional(Schema.String), | ||
| 41 | }); | ||
| 42 | |||
| 43 | // --------------------------------------------------------------------------- | ||
| 44 | // Error schemas with HTTP status annotations | ||
| 45 | // --------------------------------------------------------------------------- | ||
| 46 | |||
| 47 | const SecretNotFound = SecretNotFoundError.annotations( | ||
| 48 | HttpApiSchema.annotations({ status: 404 }), | ||
| 49 | ); | ||
| 50 | const SecretResolution = SecretResolutionError.annotations( | ||
| 51 | HttpApiSchema.annotations({ status: 500 }), | ||
| 52 | ); | ||
| 53 | |||
| 54 | // --------------------------------------------------------------------------- | ||
| 55 | // Group | ||
| 56 | // --------------------------------------------------------------------------- | ||
| 57 | |||
| 58 | export class SecretsApi extends HttpApiGroup.make("secrets") | ||
| 59 | .add( | ||
| 60 | HttpApiEndpoint.get("list")`/scopes/${scopeIdParam}/secrets` | ||
| 61 | .addSuccess(Schema.Array(SecretRefResponse)), | ||
| 62 | ) | ||
| 63 | .add( | ||
| 64 | HttpApiEndpoint.get("status")`/scopes/${scopeIdParam}/secrets/${secretIdParam}/status` | ||
| 65 | .addSuccess(SecretStatusResponse), | ||
| 66 | ) | ||
| 67 | .add( | ||
| 68 | HttpApiEndpoint.post("set")`/scopes/${scopeIdParam}/secrets` | ||
| 69 | .setPayload(SetSecretPayload) | ||
| 70 | .addSuccess(SecretRefResponse) | ||
| 71 | .addError(SecretResolution), | ||
| 72 | ) | ||
| 73 | .add( | ||
| 74 | HttpApiEndpoint.del("remove")`/scopes/${scopeIdParam}/secrets/${secretIdParam}` | ||
| 75 | .addSuccess(Schema.Struct({ removed: Schema.Boolean })) | ||
| 76 | .addError(SecretNotFound), | ||
| 77 | ) | ||
| 78 | .prefix("/v1") {} | ||
| @@ -0,0 +1,87 @@ | |||
| 1 | import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform"; | ||
| 2 | import { Schema } from "effect"; | ||
| 3 | import { | ||
| 4 | ScopeId, | ||
| 5 | ToolId, | ||
| 6 | ToolNotFoundError, | ||
| 7 | ToolInvocationError, | ||
| 8 | PolicyDeniedError, | ||
| 9 | ElicitationDeclinedError, | ||
| 10 | } from "@executor/sdk"; | ||
| 11 | |||
| 12 | // --------------------------------------------------------------------------- | ||
| 13 | // Params | ||
| 14 | // --------------------------------------------------------------------------- | ||
| 15 | |||
| 16 | const scopeIdParam = HttpApiSchema.param("scopeId", ScopeId); | ||
| 17 | const toolIdParam = HttpApiSchema.param("toolId", ToolId); | ||
| 18 | |||
| 19 | // --------------------------------------------------------------------------- | ||
| 20 | // Response schemas | ||
| 21 | // --------------------------------------------------------------------------- | ||
| 22 | |||
| 23 | const ToolMetadataResponse = Schema.Struct({ | ||
| 24 | id: ToolId, | ||
| 25 | name: Schema.String, | ||
| 26 | description: Schema.optional(Schema.String), | ||
| 27 | tags: Schema.Array(Schema.String), | ||
| 28 | mayElicit: Schema.optional(Schema.Boolean), | ||
| 29 | }); | ||
| 30 | |||
| 31 | const ToolSchemaResponse = Schema.Struct({ | ||
| 32 | id: ToolId, | ||
| 33 | inputSchema: Schema.optional(Schema.Unknown), | ||
| 34 | outputSchema: Schema.optional(Schema.Unknown), | ||
| 35 | }); | ||
| 36 | |||
| 37 | const ToolInvokePayload = Schema.Struct({ | ||
| 38 | args: Schema.Unknown, | ||
| 39 | }); | ||
| 40 | |||
| 41 | const ToolInvokeResponse = Schema.Struct({ | ||
| 42 | data: Schema.Unknown, | ||
| 43 | error: Schema.NullOr(Schema.Unknown), | ||
| 44 | status: Schema.optional(Schema.Number), | ||
| 45 | }); | ||
| 46 | |||
| 47 | // --------------------------------------------------------------------------- | ||
| 48 | // Error schemas with HTTP status annotations | ||
| 49 | // --------------------------------------------------------------------------- | ||
| 50 | |||
| 51 | const ToolNotFound = ToolNotFoundError.annotations( | ||
| 52 | HttpApiSchema.annotations({ status: 404 }), | ||
| 53 | ); | ||
| 54 | const ToolInvocation = ToolInvocationError.annotations( | ||
| 55 | HttpApiSchema.annotations({ status: 500 }), | ||
| 56 | ); | ||
| 57 | const PolicyDenied = PolicyDeniedError.annotations( | ||
| 58 | HttpApiSchema.annotations({ status: 403 }), | ||
| 59 | ); | ||
| 60 | const ElicitationDeclined = ElicitationDeclinedError.annotations( | ||
| 61 | HttpApiSchema.annotations({ status: 422 }), | ||
| 62 | ); | ||
| 63 | |||
| 64 | // --------------------------------------------------------------------------- | ||
| 65 | // Group | ||
| 66 | // --------------------------------------------------------------------------- | ||
| 67 | |||
| 68 | export class ToolsApi extends HttpApiGroup.make("tools") | ||
| 69 | .add( | ||
| 70 | HttpApiEndpoint.get("list")`/scopes/${scopeIdParam}/tools` | ||
| 71 | .addSuccess(Schema.Array(ToolMetadataResponse)), | ||
| 72 | ) | ||
| 73 | .add( | ||
| 74 | HttpApiEndpoint.get("schema")`/scopes/${scopeIdParam}/tools/${toolIdParam}/schema` | ||
| 75 | .addSuccess(ToolSchemaResponse) | ||
| 76 | .addError(ToolNotFound), | ||
| 77 | ) | ||
| 78 | .add( | ||
| 79 | HttpApiEndpoint.post("invoke")`/scopes/${scopeIdParam}/tools/${toolIdParam}/invoke` | ||
| 80 | .setPayload(ToolInvokePayload) | ||
| 81 | .addSuccess(ToolInvokeResponse) | ||
| 82 | .addError(ToolNotFound) | ||
| 83 | .addError(ToolInvocation) | ||
| 84 | .addError(PolicyDenied) | ||
| 85 | .addError(ElicitationDeclined), | ||
| 86 | ) | ||
| 87 | .prefix("/v1") {} | ||