History

feat: API contract, server handler, and frontend with effect-atom

- 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

Rhys Sullivan committed 7 months ago

Commit 8babea81 parent 00219a926 files · +1022 −32Browse files

Showing the changes to packages/core/api/src (5 of 26 files). Show all

Unified · Split

5 files
  1. packages/core/api/src/api.ts +14 −0
  2. packages/core/api/src/errors.ts +26 −0
  3. packages/core/api/src/index.ts +3 −0
  4. packages/core/api/src/secrets/api.ts +78 −0
  5. packages/core/api/src/tools/api.ts +87 −0
packages/core/api/src/api.ts+14−0
@@ -0,0 +1,14 @@
1import { HttpApi, OpenApi } from "@effect/platform";
2
3import { ToolsApi } from "./tools/api";
4import { SecretsApi } from "./secrets/api";
5
6export 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 );
packages/core/api/src/errors.ts+26−0
@@ -0,0 +1,26 @@
1import { HttpApiSchema } from "@effect/platform";
2import { Schema } from "effect";
3
4export class ApiNotFoundError extends Schema.TaggedError<ApiNotFoundError>()(
5 "ApiNotFoundError",
6 {
7 message: Schema.String,
8 },
9 HttpApiSchema.annotations({ status: 404 }),
10) {}
11
12export class ApiBadRequestError extends Schema.TaggedError<ApiBadRequestError>()(
13 "ApiBadRequestError",
14 {
15 message: Schema.String,
16 },
17 HttpApiSchema.annotations({ status: 400 }),
18) {}
19
20export class ApiInternalError extends Schema.TaggedError<ApiInternalError>()(
21 "ApiInternalError",
22 {
23 message: Schema.String,
24 },
25 HttpApiSchema.annotations({ status: 500 }),
26) {}
packages/core/api/src/index.ts+3−0
@@ -0,0 +1,3 @@
1export { ExecutorApi } from "./api";
2export { ToolsApi } from "./tools/api";
3export { SecretsApi } from "./secrets/api";
packages/core/api/src/secrets/api.ts+78−0
@@ -0,0 +1,78 @@
1import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform";
2import { Schema } from "effect";
3import {
4 ScopeId,
5 SecretId,
6 SecretNotFoundError,
7 SecretResolutionError,
8} from "@executor/sdk";
9
10// ---------------------------------------------------------------------------
11// Params
12// ---------------------------------------------------------------------------
13
14const scopeIdParam = HttpApiSchema.param("scopeId", ScopeId);
15const secretIdParam = HttpApiSchema.param("secretId", SecretId);
16
17// ---------------------------------------------------------------------------
18// Response / payload schemas
19// ---------------------------------------------------------------------------
20
21const 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
30const SecretStatusResponse = Schema.Struct({
31 secretId: SecretId,
32 status: Schema.Literal("resolved", "missing"),
33});
34
35const 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
47const SecretNotFound = SecretNotFoundError.annotations(
48 HttpApiSchema.annotations({ status: 404 }),
49);
50const SecretResolution = SecretResolutionError.annotations(
51 HttpApiSchema.annotations({ status: 500 }),
52);
53
54// ---------------------------------------------------------------------------
55// Group
56// ---------------------------------------------------------------------------
57
58export 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") {}
packages/core/api/src/tools/api.ts+87−0
@@ -0,0 +1,87 @@
1import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform";
2import { Schema } from "effect";
3import {
4 ScopeId,
5 ToolId,
6 ToolNotFoundError,
7 ToolInvocationError,
8 PolicyDeniedError,
9 ElicitationDeclinedError,
10} from "@executor/sdk";
11
12// ---------------------------------------------------------------------------
13// Params
14// ---------------------------------------------------------------------------
15
16const scopeIdParam = HttpApiSchema.param("scopeId", ScopeId);
17const toolIdParam = HttpApiSchema.param("toolId", ToolId);
18
19// ---------------------------------------------------------------------------
20// Response schemas
21// ---------------------------------------------------------------------------
22
23const 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
31const ToolSchemaResponse = Schema.Struct({
32 id: ToolId,
33 inputSchema: Schema.optional(Schema.Unknown),
34 outputSchema: Schema.optional(Schema.Unknown),
35});
36
37const ToolInvokePayload = Schema.Struct({
38 args: Schema.Unknown,
39});
40
41const 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
51const ToolNotFound = ToolNotFoundError.annotations(
52 HttpApiSchema.annotations({ status: 404 }),
53);
54const ToolInvocation = ToolInvocationError.annotations(
55 HttpApiSchema.annotations({ status: 500 }),
56);
57const PolicyDenied = PolicyDeniedError.annotations(
58 HttpApiSchema.annotations({ status: 403 }),
59);
60const ElicitationDeclined = ElicitationDeclinedError.annotations(
61 HttpApiSchema.annotations({ status: 422 }),
62);
63
64// ---------------------------------------------------------------------------
65// Group
66// ---------------------------------------------------------------------------
67
68export 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") {}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t