@rigkit/runtime-client 0.0.0-canary-20260518T014918-c5bc0c2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @rigkit/runtime-client
2
+
3
+ Shared client and daemon manager for Rigkit runtime hosts.
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@rigkit/runtime-client",
3
+ "version": "0.0.0-canary-20260518T014918-c5bc0c2",
4
+ "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/freestyle-sh/rigkit.git",
8
+ "directory": "packages/runtime-client"
9
+ },
10
+ "exports": {
11
+ ".": "./src/index.ts",
12
+ "./package.json": "./package.json"
13
+ },
14
+ "files": [
15
+ "src",
16
+ "README.md"
17
+ ],
18
+ "dependencies": {
19
+ "@effect/platform": "0.96.1",
20
+ "effect": "^3.21.2",
21
+ "ws": "^8.20.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/bun": "latest",
25
+ "typescript": "latest"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc --noEmit",
32
+ "typecheck": "tsc --noEmit",
33
+ "test": "bun test"
34
+ }
35
+ }
package/src/api.ts ADDED
@@ -0,0 +1,242 @@
1
+ import {
2
+ HttpApi,
3
+ HttpApiEndpoint,
4
+ HttpApiGroup,
5
+ HttpApiSchema,
6
+ OpenApi,
7
+ } from "@effect/platform";
8
+ import { Schema } from "effect";
9
+ import { SUPPORTED_RUNTIME_API_VERSION } from "./http.ts";
10
+
11
+ const UnknownRecord = Schema.Record({ key: Schema.String, value: Schema.Unknown });
12
+ const OptionalString = Schema.optional(Schema.String);
13
+
14
+ export const RuntimeControlHealthEffectSchema = Schema.Struct({
15
+ ok: Schema.Boolean,
16
+ projectId: Schema.String,
17
+ runtimeFingerprint: OptionalString,
18
+ projectDir: Schema.String,
19
+ configPath: Schema.String,
20
+ statePath: OptionalString,
21
+ globalFragmentRoot: OptionalString,
22
+ engineVersion: Schema.String,
23
+ runtimeVersion: Schema.String,
24
+ expiresAt: Schema.String,
25
+ }).annotations({ identifier: "Health" });
26
+
27
+ export const RuntimeControlMetadataEffectSchema = Schema.Struct({
28
+ apiVersion: Schema.Number,
29
+ engineVersion: Schema.String,
30
+ runtimeVersion: Schema.String,
31
+ protocolHash: Schema.String,
32
+ }).annotations({ identifier: "RuntimeMetadata" });
33
+
34
+ export const RuntimeControlWorkflowSummaryEffectSchema = Schema.Struct({
35
+ name: Schema.String,
36
+ providers: Schema.Array(Schema.String),
37
+ nodes: Schema.Array(Schema.String),
38
+ operations: Schema.Array(Schema.String),
39
+ createsWorkspace: Schema.Boolean,
40
+ }).annotations({ identifier: "WorkflowSummary" });
41
+
42
+ export const RuntimeControlProjectInfoEffectSchema = Schema.Struct({
43
+ projectDir: Schema.String,
44
+ configPath: Schema.String,
45
+ statePath: OptionalString,
46
+ workflow: Schema.optional(RuntimeControlWorkflowSummaryEffectSchema),
47
+ workflows: Schema.Array(RuntimeControlWorkflowSummaryEffectSchema),
48
+ }).annotations({ identifier: "ProjectInfo" });
49
+
50
+ export const RuntimeControlOperationCliEffectSchema = Schema.Struct({
51
+ positionals: Schema.optional(Schema.Array(Schema.Struct({
52
+ name: Schema.String,
53
+ index: Schema.Number,
54
+ }))),
55
+ options: Schema.optional(Schema.Array(Schema.Struct({
56
+ name: Schema.String,
57
+ flag: Schema.String,
58
+ aliases: Schema.optional(Schema.Array(Schema.String)),
59
+ required: Schema.optional(Schema.Boolean),
60
+ runtime: Schema.optional(Schema.Boolean),
61
+ type: Schema.optional(Schema.Literal("string", "boolean", "number")),
62
+ }))),
63
+ }).annotations({ identifier: "RuntimeOperationCli" });
64
+
65
+ export const RuntimeControlOperationEffectSchema = Schema.Struct({
66
+ id: Schema.String,
67
+ aliases: Schema.optional(Schema.Array(Schema.String)),
68
+ kind: Schema.Literal("command", "workspace-action"),
69
+ source: Schema.Literal("core", "config"),
70
+ title: Schema.String,
71
+ description: Schema.String,
72
+ createsWorkspace: Schema.optional(Schema.Boolean),
73
+ cli: Schema.optional(RuntimeControlOperationCliEffectSchema),
74
+ inputSchema: UnknownRecord,
75
+ }).annotations({ identifier: "RuntimeOperation" });
76
+
77
+ export const RuntimeControlOperationsManifestEffectSchema = Schema.Struct({
78
+ operations: Schema.Array(RuntimeControlOperationEffectSchema),
79
+ workspaceOperations: Schema.Array(RuntimeControlOperationEffectSchema),
80
+ }).annotations({ identifier: "OperationsManifest" });
81
+
82
+ export const RuntimeControlWorkflowsResponseEffectSchema = Schema.Struct({
83
+ workflows: Schema.Array(RuntimeControlWorkflowSummaryEffectSchema),
84
+ }).annotations({ identifier: "WorkflowsResponse" });
85
+
86
+ export const RuntimeControlWorkspaceEffectSchema = Schema.Struct({
87
+ id: Schema.String,
88
+ name: Schema.String,
89
+ workflow: Schema.String,
90
+ ctx: UnknownRecord,
91
+ createdAt: Schema.String,
92
+ updatedAt: Schema.String,
93
+ }).annotations({ identifier: "Workspace" });
94
+
95
+ export const RuntimeControlWorkspacesResponseEffectSchema = Schema.Struct({
96
+ workspaces: Schema.Array(RuntimeControlWorkspaceEffectSchema),
97
+ }).annotations({ identifier: "WorkspacesResponse" });
98
+
99
+ export const RuntimeControlSnapshotsResponseEffectSchema = Schema.Struct({
100
+ snapshots: Schema.Array(Schema.Unknown),
101
+ }).annotations({ identifier: "SnapshotsResponse" });
102
+
103
+ export const RuntimeControlCacheEntryEffectSchema = Schema.Struct({
104
+ scope: Schema.Literal("local", "global"),
105
+ workflow: Schema.String,
106
+ nodePath: Schema.String,
107
+ nodeName: Schema.String,
108
+ nodeKind: Schema.String,
109
+ runId: Schema.String,
110
+ invalidated: Schema.Boolean,
111
+ createdAt: Schema.String,
112
+ fragmentHash: Schema.optional(Schema.String),
113
+ }).annotations({ identifier: "CacheEntry" });
114
+
115
+ export const RuntimeControlCacheResponseEffectSchema = Schema.Struct({
116
+ entries: Schema.Array(RuntimeControlCacheEntryEffectSchema),
117
+ }).annotations({ identifier: "CacheResponse" });
118
+
119
+ export const RuntimeControlCacheClearRequestEffectSchema = Schema.Struct({
120
+ scope: Schema.optional(Schema.Literal("local", "global", "all")),
121
+ }).annotations({ identifier: "CacheClearRequest" });
122
+
123
+ export const RuntimeControlCacheClearResponseEffectSchema = Schema.Struct({
124
+ ok: Schema.Boolean,
125
+ deleted: Schema.Number,
126
+ }).annotations({ identifier: "CacheClearResponse" });
127
+
128
+ export const RuntimeControlCacheInvalidateRequestEffectSchema = Schema.Struct({
129
+ workflow: Schema.optional(Schema.String),
130
+ nodePaths: Schema.optional(Schema.Array(Schema.String)),
131
+ }).annotations({ identifier: "CacheInvalidateRequest" });
132
+
133
+ export const RuntimeControlCacheInvalidateResponseEffectSchema = Schema.Struct({
134
+ ok: Schema.Boolean,
135
+ invalidated: Schema.Number,
136
+ }).annotations({ identifier: "CacheInvalidateResponse" });
137
+
138
+ export const RuntimeControlRunEffectSchema = Schema.Struct({
139
+ runId: Schema.String,
140
+ operation: Schema.String,
141
+ input: Schema.Unknown,
142
+ status: Schema.Literal("running", "completed", "failed"),
143
+ result: Schema.optional(Schema.Unknown),
144
+ error: Schema.optional(Schema.Struct({
145
+ code: Schema.String,
146
+ message: Schema.String,
147
+ })),
148
+ createdAt: Schema.String,
149
+ updatedAt: Schema.String,
150
+ }).annotations({ identifier: "Run" });
151
+
152
+ export const RuntimeControlRunsResponseEffectSchema = Schema.Struct({
153
+ runs: Schema.Array(RuntimeControlRunEffectSchema),
154
+ }).annotations({ identifier: "RunsResponse" });
155
+
156
+ export const RuntimeControlRunOperationRequestEffectSchema = Schema.Struct({
157
+ operation: Schema.NonEmptyString,
158
+ input: Schema.optional(Schema.Unknown),
159
+ }).annotations({ identifier: "RunOperationRequest" });
160
+
161
+ export const RuntimeControlRunStartedEffectSchema = Schema.Struct({
162
+ runId: Schema.String,
163
+ operation: Schema.String,
164
+ status: Schema.Literal("running", "completed", "failed"),
165
+ eventsUrl: Schema.String,
166
+ sessionUrl: Schema.String,
167
+ }).annotations({ identifier: "RunStarted" });
168
+
169
+ export const RuntimeControlHostResponseEffectSchema = Schema.Union(
170
+ Schema.Struct({
171
+ error: Schema.Struct({
172
+ code: Schema.optional(Schema.String),
173
+ message: Schema.optional(Schema.String),
174
+ }),
175
+ }),
176
+ Schema.Struct({
177
+ result: Schema.optional(Schema.Unknown),
178
+ }),
179
+ ).annotations({ identifier: "HostResponse" });
180
+
181
+ export const RuntimeControlOkResponseEffectSchema = Schema.Struct({
182
+ ok: Schema.Boolean,
183
+ }).annotations({ identifier: "OkResponse" });
184
+
185
+ export type RuntimeControlHealth = Schema.Schema.Type<typeof RuntimeControlHealthEffectSchema>;
186
+ export type RuntimeControlMetadata = Schema.Schema.Type<typeof RuntimeControlMetadataEffectSchema>;
187
+ export type RuntimeControlWorkflowSummary = Schema.Schema.Type<typeof RuntimeControlWorkflowSummaryEffectSchema>;
188
+ export type RuntimeControlProjectInfo = Schema.Schema.Type<typeof RuntimeControlProjectInfoEffectSchema>;
189
+ export type RuntimeControlOperationCli = Schema.Schema.Type<typeof RuntimeControlOperationCliEffectSchema>;
190
+ export type RuntimeControlOperation = Schema.Schema.Type<typeof RuntimeControlOperationEffectSchema>;
191
+ export type RuntimeControlOperationsManifest = Schema.Schema.Type<typeof RuntimeControlOperationsManifestEffectSchema>;
192
+ export type RuntimeControlWorkflowsResponse = Schema.Schema.Type<typeof RuntimeControlWorkflowsResponseEffectSchema>;
193
+ export type RuntimeControlWorkspace = Schema.Schema.Type<typeof RuntimeControlWorkspaceEffectSchema>;
194
+ export type RuntimeControlWorkspacesResponse = Schema.Schema.Type<typeof RuntimeControlWorkspacesResponseEffectSchema>;
195
+ export type RuntimeControlSnapshotsResponse = Schema.Schema.Type<typeof RuntimeControlSnapshotsResponseEffectSchema>;
196
+ export type RuntimeControlCacheEntry = Schema.Schema.Type<typeof RuntimeControlCacheEntryEffectSchema>;
197
+ export type RuntimeControlCacheResponse = Schema.Schema.Type<typeof RuntimeControlCacheResponseEffectSchema>;
198
+ export type RuntimeControlCacheClearRequest = Schema.Schema.Type<typeof RuntimeControlCacheClearRequestEffectSchema>;
199
+ export type RuntimeControlCacheClearResponse = Schema.Schema.Type<typeof RuntimeControlCacheClearResponseEffectSchema>;
200
+ export type RuntimeControlCacheInvalidateRequest = Schema.Schema.Type<typeof RuntimeControlCacheInvalidateRequestEffectSchema>;
201
+ export type RuntimeControlCacheInvalidateResponse = Schema.Schema.Type<typeof RuntimeControlCacheInvalidateResponseEffectSchema>;
202
+ export type RuntimeControlRunOperationRequest = Schema.Schema.Type<typeof RuntimeControlRunOperationRequestEffectSchema>;
203
+ export type RuntimeControlRun = Schema.Schema.Type<typeof RuntimeControlRunEffectSchema>;
204
+ export type RuntimeControlRunsResponse = Schema.Schema.Type<typeof RuntimeControlRunsResponseEffectSchema>;
205
+ export type RuntimeControlRunStarted = Schema.Schema.Type<typeof RuntimeControlRunStartedEffectSchema>;
206
+ export type RuntimeControlHostResponse = Schema.Schema.Type<typeof RuntimeControlHostResponseEffectSchema>;
207
+ export type RuntimeControlOkResponse = Schema.Schema.Type<typeof RuntimeControlOkResponseEffectSchema>;
208
+
209
+ const runId = HttpApiSchema.param("runId", Schema.String);
210
+ const requestId = HttpApiSchema.param("requestId", Schema.String);
211
+
212
+ export const runtimeControlApi = HttpApi.make("rigkit-runtime")
213
+ .annotate(OpenApi.Title, "Rigkit runtime")
214
+ .annotate(OpenApi.Version, String(SUPPORTED_RUNTIME_API_VERSION))
215
+ .add(
216
+ HttpApiGroup.make("control", { topLevel: true })
217
+ .add(HttpApiEndpoint.get("health", "/health").addSuccess(RuntimeControlHealthEffectSchema))
218
+ .add(HttpApiEndpoint.get("openApi", "/openapi.json").addSuccess(Schema.Unknown))
219
+ .add(HttpApiEndpoint.get("runtime", "/runtime").addSuccess(RuntimeControlMetadataEffectSchema))
220
+ .add(HttpApiEndpoint.get("project", "/project").addSuccess(RuntimeControlProjectInfoEffectSchema))
221
+ .add(HttpApiEndpoint.get("operations", "/operations").addSuccess(RuntimeControlOperationsManifestEffectSchema))
222
+ .add(HttpApiEndpoint.get("workflows", "/workflows").addSuccess(RuntimeControlWorkflowsResponseEffectSchema))
223
+ .add(HttpApiEndpoint.get("workspaces", "/workspaces").addSuccess(RuntimeControlWorkspacesResponseEffectSchema))
224
+ .add(HttpApiEndpoint.get("snapshots", "/snapshots").addSuccess(RuntimeControlSnapshotsResponseEffectSchema))
225
+ .add(HttpApiEndpoint.get("cache", "/cache").addSuccess(RuntimeControlCacheResponseEffectSchema))
226
+ .add(HttpApiEndpoint.post("clearCache", "/cache/clear")
227
+ .setPayload(RuntimeControlCacheClearRequestEffectSchema)
228
+ .addSuccess(RuntimeControlCacheClearResponseEffectSchema))
229
+ .add(HttpApiEndpoint.post("invalidateCache", "/cache/invalidate")
230
+ .setPayload(RuntimeControlCacheInvalidateRequestEffectSchema)
231
+ .addSuccess(RuntimeControlCacheInvalidateResponseEffectSchema))
232
+ .add(HttpApiEndpoint.get("runs", "/runs").addSuccess(RuntimeControlRunsResponseEffectSchema))
233
+ .add(HttpApiEndpoint.post("startRun", "/runs")
234
+ .setPayload(RuntimeControlRunOperationRequestEffectSchema)
235
+ .addSuccess(RuntimeControlRunStartedEffectSchema, { status: 202 }))
236
+ .add(HttpApiEndpoint.get("run")`/runs/${runId}`.addSuccess(RuntimeControlRunEffectSchema))
237
+ .add(HttpApiEndpoint.get("runEvents")`/runs/${runId}/events`.addSuccess(Schema.String))
238
+ .add(HttpApiEndpoint.post("hostResponse")`/host-responses/${requestId}`
239
+ .setPayload(RuntimeControlHostResponseEffectSchema)
240
+ .addSuccess(RuntimeControlOkResponseEffectSchema))
241
+ .add(HttpApiEndpoint.post("shutdown", "/shutdown").addSuccess(RuntimeControlOkResponseEffectSchema)),
242
+ );
package/src/client.ts ADDED
@@ -0,0 +1,261 @@
1
+ import {
2
+ FetchHttpClient,
3
+ Headers,
4
+ HttpApiClient,
5
+ HttpClient,
6
+ HttpClientError,
7
+ HttpClientRequest,
8
+ HttpClientResponse,
9
+ } from "@effect/platform";
10
+ import { Cause, Effect, Exit, Option } from "effect";
11
+ import {
12
+ RuntimeAuthError,
13
+ RuntimeConnectionError,
14
+ RuntimeHttpError,
15
+ RuntimeProtocolError,
16
+ isRuntimeClientError,
17
+ type RuntimeClientError,
18
+ } from "./errors.ts";
19
+ import { assertSupportedApiVersionHeader, toRuntimeTransportError } from "./http.ts";
20
+ import { RuntimeErrorResponseSchema } from "./schemas.ts";
21
+ import {
22
+ runtimeControlApi,
23
+ type RuntimeControlHealth,
24
+ type RuntimeControlHostResponse,
25
+ type RuntimeControlCacheClearRequest,
26
+ type RuntimeControlCacheClearResponse,
27
+ type RuntimeControlCacheInvalidateRequest,
28
+ type RuntimeControlCacheInvalidateResponse,
29
+ type RuntimeControlCacheResponse,
30
+ type RuntimeControlMetadata,
31
+ type RuntimeControlOkResponse,
32
+ type RuntimeControlOperationsManifest,
33
+ type RuntimeControlProjectInfo,
34
+ type RuntimeControlRun,
35
+ type RuntimeControlRunOperationRequest,
36
+ type RuntimeControlRunsResponse,
37
+ type RuntimeControlRunStarted,
38
+ type RuntimeControlSnapshotsResponse,
39
+ type RuntimeControlWorkflowsResponse,
40
+ type RuntimeControlWorkspacesResponse,
41
+ } from "./api.ts";
42
+
43
+ export type RuntimeHttpClientOptions = {
44
+ readonly baseUrl: string;
45
+ readonly token: string;
46
+ };
47
+
48
+ export type RuntimeHttpClient = {
49
+ health(): Promise<RuntimeControlHealth>;
50
+ openApi(): Promise<unknown>;
51
+ runtime(): Promise<RuntimeControlMetadata>;
52
+ project(): Promise<RuntimeControlProjectInfo>;
53
+ operations(): Promise<RuntimeControlOperationsManifest>;
54
+ workflows(): Promise<RuntimeControlWorkflowsResponse>;
55
+ workspaces(): Promise<RuntimeControlWorkspacesResponse>;
56
+ snapshots(): Promise<RuntimeControlSnapshotsResponse>;
57
+ cache(): Promise<RuntimeControlCacheResponse>;
58
+ clearCache(body: RuntimeControlCacheClearRequest): Promise<RuntimeControlCacheClearResponse>;
59
+ invalidateCache(body: RuntimeControlCacheInvalidateRequest): Promise<RuntimeControlCacheInvalidateResponse>;
60
+ runs(): Promise<RuntimeControlRunsResponse>;
61
+ startRun(body: RuntimeControlRunOperationRequest): Promise<RuntimeControlRunStarted>;
62
+ run(runId: string): Promise<RuntimeControlRun>;
63
+ hostResponse(requestId: string, body: RuntimeControlHostResponse): Promise<RuntimeControlOkResponse>;
64
+ shutdown(): Promise<RuntimeControlOkResponse>;
65
+ };
66
+
67
+ type RuntimeControlApiClient = Effect.Effect.Success<ReturnType<typeof runtimeControlApiClientEffect>>;
68
+ type RuntimeHttpResponse<A> = [A, HttpClientResponse.HttpClientResponse];
69
+ type RuntimeHttpContext = {
70
+ readonly method: string;
71
+ readonly path: string;
72
+ };
73
+
74
+ export function runtimeHttpClientEffect(
75
+ options: RuntimeHttpClientOptions,
76
+ ): Effect.Effect<RuntimeHttpClient, RuntimeClientError> {
77
+ return Effect.succeed(createRuntimeHttpClient(options));
78
+ }
79
+
80
+ export function createRuntimeHttpClient(options: RuntimeHttpClientOptions): RuntimeHttpClient {
81
+ return makeRuntimeHttpClient({
82
+ baseUrl: options.baseUrl.replace(/\/+$/, ""),
83
+ token: options.token,
84
+ });
85
+ }
86
+
87
+ function runtimeControlApiClientEffect(options: RuntimeHttpClientOptions) {
88
+ return HttpApiClient.make(runtimeControlApi, {
89
+ baseUrl: options.baseUrl,
90
+ transformClient: HttpClient.mapRequest(HttpClientRequest.bearerToken(options.token)),
91
+ });
92
+ }
93
+
94
+ function makeRuntimeHttpClient(options: RuntimeHttpClientOptions): RuntimeHttpClient {
95
+ return {
96
+ health: () =>
97
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.health({ withResponse: true })), {
98
+ method: "GET",
99
+ path: "/health",
100
+ }),
101
+ openApi: () =>
102
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.openApi({ withResponse: true })), {
103
+ method: "GET",
104
+ path: "/openapi.json",
105
+ }),
106
+ runtime: () =>
107
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.runtime({ withResponse: true })), {
108
+ method: "GET",
109
+ path: "/runtime",
110
+ }),
111
+ project: () =>
112
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.project({ withResponse: true })), {
113
+ method: "GET",
114
+ path: "/project",
115
+ }),
116
+ operations: () =>
117
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.operations({ withResponse: true })), {
118
+ method: "GET",
119
+ path: "/operations",
120
+ }),
121
+ workflows: () =>
122
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.workflows({ withResponse: true })), {
123
+ method: "GET",
124
+ path: "/workflows",
125
+ }),
126
+ workspaces: () =>
127
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.workspaces({ withResponse: true })), {
128
+ method: "GET",
129
+ path: "/workspaces",
130
+ }),
131
+ snapshots: () =>
132
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.snapshots({ withResponse: true })), {
133
+ method: "GET",
134
+ path: "/snapshots",
135
+ }),
136
+ cache: () =>
137
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.cache({ withResponse: true })), {
138
+ method: "GET",
139
+ path: "/cache",
140
+ }),
141
+ clearCache: (body) =>
142
+ runRuntimeHttpRequest(
143
+ withRuntimeControlClient(options, (client) => client.clearCache({ payload: body, withResponse: true })),
144
+ { method: "POST", path: "/cache/clear" },
145
+ ),
146
+ invalidateCache: (body) =>
147
+ runRuntimeHttpRequest(
148
+ withRuntimeControlClient(options, (client) => client.invalidateCache({ payload: body, withResponse: true })),
149
+ { method: "POST", path: "/cache/invalidate" },
150
+ ),
151
+ runs: () =>
152
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.runs({ withResponse: true })), {
153
+ method: "GET",
154
+ path: "/runs",
155
+ }),
156
+ startRun: (body) =>
157
+ runRuntimeHttpRequest(
158
+ withRuntimeControlClient(options, (client) => client.startRun({ payload: body, withResponse: true })),
159
+ { method: "POST", path: "/runs" },
160
+ ),
161
+ run: (runId) =>
162
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.run({ path: { runId }, withResponse: true })), {
163
+ method: "GET",
164
+ path: `/runs/${encodeURIComponent(runId)}`,
165
+ }),
166
+ hostResponse: (requestId, body) => {
167
+ const path = `/host-responses/${encodeURIComponent(requestId)}`;
168
+ if ("error" in body) {
169
+ return runRuntimeHttpRequest(
170
+ withRuntimeControlClient(options, (client) =>
171
+ client.hostResponse({ path: { requestId }, payload: { error: body.error }, withResponse: true })
172
+ ),
173
+ { method: "POST", path },
174
+ );
175
+ }
176
+ return runRuntimeHttpRequest(
177
+ withRuntimeControlClient(options, (client) =>
178
+ client.hostResponse({ path: { requestId }, payload: { result: body.result }, withResponse: true })
179
+ ),
180
+ { method: "POST", path },
181
+ );
182
+ },
183
+ shutdown: () =>
184
+ runRuntimeHttpRequest(withRuntimeControlClient(options, (client) => client.shutdown({ withResponse: true })), {
185
+ method: "POST",
186
+ path: "/shutdown",
187
+ }),
188
+ };
189
+ }
190
+
191
+ function withRuntimeControlClient<A>(
192
+ options: RuntimeHttpClientOptions,
193
+ run: (client: RuntimeControlApiClient) => Effect.Effect<RuntimeHttpResponse<A>, unknown, never>,
194
+ ) {
195
+ return Effect.flatMap(
196
+ Effect.provide(runtimeControlApiClientEffect(options), FetchHttpClient.layer),
197
+ run,
198
+ );
199
+ }
200
+
201
+ async function runRuntimeHttpRequest<A>(
202
+ program: Effect.Effect<RuntimeHttpResponse<A>, unknown, never>,
203
+ context: RuntimeHttpContext,
204
+ ): Promise<A> {
205
+ const response = await runRuntimeHttpEffect(program, context);
206
+ const [value, httpResponse] = response;
207
+ try {
208
+ assertSupportedApiVersionHeader(Option.getOrNull(Headers.get(httpResponse.headers, "x-rigkit-api-version")));
209
+ } catch (error) {
210
+ throw toRuntimeTransportError(error, context);
211
+ }
212
+ return value;
213
+ }
214
+
215
+ async function runRuntimeHttpEffect<A>(
216
+ program: Effect.Effect<A, unknown, never>,
217
+ context: RuntimeHttpContext,
218
+ ): Promise<A> {
219
+ const exit = await Effect.runPromiseExit(program);
220
+ if (Exit.isSuccess(exit)) return exit.value;
221
+ throw await runtimeHttpClientError(Cause.squash(exit.cause), context);
222
+ }
223
+
224
+ async function runtimeHttpClientError(cause: unknown, context: RuntimeHttpContext): Promise<RuntimeClientError> {
225
+ if (isRuntimeClientError(cause)) return cause;
226
+ if (cause instanceof HttpClientError.ResponseError) {
227
+ const status = cause.response.status;
228
+ const message = await responseErrorMessage(cause);
229
+ if (status === 401 || status === 403) {
230
+ return new RuntimeAuthError({ ...context, status, message });
231
+ }
232
+ if (status >= 200 && status < 300) {
233
+ return new RuntimeProtocolError({
234
+ ...context,
235
+ message: `${context.method} ${context.path} returned invalid response`,
236
+ cause,
237
+ });
238
+ }
239
+ return new RuntimeHttpError({ ...context, status, message, cause });
240
+ }
241
+ if (cause instanceof HttpClientError.RequestError) {
242
+ return new RuntimeConnectionError({ ...context, cause });
243
+ }
244
+ return new RuntimeProtocolError({
245
+ ...context,
246
+ message: `${context.method} ${context.path} returned invalid response`,
247
+ cause,
248
+ });
249
+ }
250
+
251
+ async function responseErrorMessage(error: HttpClientError.ResponseError): Promise<string | undefined> {
252
+ try {
253
+ const text = await Effect.runPromise(error.response.text);
254
+ if (!text) return undefined;
255
+ const parsed = JSON.parse(text);
256
+ const body = RuntimeErrorResponseSchema.safeParse(parsed);
257
+ return body.success ? body.data.error.message : undefined;
258
+ } catch {
259
+ return undefined;
260
+ }
261
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,124 @@
1
+ export type RuntimeStartupErrorReason =
2
+ | "missing-runtime"
3
+ | "lock-timeout"
4
+ | "startup-timeout"
5
+ | "exited-before-ready"
6
+ | "invalid-ready-output"
7
+ | "unhealthy-after-start";
8
+
9
+ export class RuntimeStartupError extends Error {
10
+ readonly reason: RuntimeStartupErrorReason;
11
+ readonly projectDir?: string;
12
+ readonly path?: string;
13
+
14
+ constructor(input: {
15
+ reason: RuntimeStartupErrorReason;
16
+ message: string;
17
+ projectDir?: string;
18
+ path?: string;
19
+ cause?: unknown;
20
+ }) {
21
+ super(input.message, { cause: input.cause });
22
+ this.name = "RuntimeStartupError";
23
+ this.reason = input.reason;
24
+ this.projectDir = input.projectDir;
25
+ this.path = input.path;
26
+ }
27
+ }
28
+
29
+ export class RuntimeApiVersionError extends Error {
30
+ readonly version: string | null;
31
+ readonly supportedVersion: number;
32
+
33
+ constructor(input: { version: string | null; supportedVersion: number }) {
34
+ super(
35
+ input.version
36
+ ? `Unsupported Rigkit runtime API version ${input.version}; this host supports ${input.supportedVersion}`
37
+ : `Runtime did not report x-rigkit-api-version; this host requires ${input.supportedVersion}`,
38
+ );
39
+ this.name = "RuntimeApiVersionError";
40
+ this.version = input.version;
41
+ this.supportedVersion = input.supportedVersion;
42
+ }
43
+ }
44
+
45
+ export class RuntimeAuthError extends Error {
46
+ readonly status: number;
47
+ readonly method: string;
48
+ readonly path: string;
49
+
50
+ constructor(input: { status: number; method: string; path: string; message?: string }) {
51
+ super(input.message ?? `${input.method} ${input.path} failed with authentication status ${input.status}`);
52
+ this.name = "RuntimeAuthError";
53
+ this.status = input.status;
54
+ this.method = input.method;
55
+ this.path = input.path;
56
+ }
57
+ }
58
+
59
+ export class RuntimeHttpError extends Error {
60
+ readonly status: number;
61
+ readonly method: string;
62
+ readonly path: string;
63
+
64
+ constructor(input: { status: number; method: string; path: string; message?: string; cause?: unknown }) {
65
+ super(input.message ?? `${input.method} ${input.path} failed with ${input.status}`, { cause: input.cause });
66
+ this.name = "RuntimeHttpError";
67
+ this.status = input.status;
68
+ this.method = input.method;
69
+ this.path = input.path;
70
+ }
71
+ }
72
+
73
+ export class RuntimeConnectionError extends Error {
74
+ readonly method: string;
75
+ readonly path: string;
76
+
77
+ constructor(input: { method: string; path: string; message?: string; cause?: unknown }) {
78
+ super(input.message ?? `${input.method} ${input.path} failed before the runtime responded`, { cause: input.cause });
79
+ this.name = "RuntimeConnectionError";
80
+ this.method = input.method;
81
+ this.path = input.path;
82
+ }
83
+ }
84
+
85
+ export class RuntimeProtocolError extends Error {
86
+ readonly method: string;
87
+ readonly path: string;
88
+
89
+ constructor(input: { method: string; path: string; message: string; cause?: unknown }) {
90
+ super(input.message, { cause: input.cause });
91
+ this.name = "RuntimeProtocolError";
92
+ this.method = input.method;
93
+ this.path = input.path;
94
+ }
95
+ }
96
+
97
+ export class RuntimeSessionError extends Error {
98
+ readonly url: string;
99
+
100
+ constructor(input: { url: string; message?: string; cause?: unknown }) {
101
+ super(input.message ?? `WebSocket session failed for ${input.url}`, { cause: input.cause });
102
+ this.name = "RuntimeSessionError";
103
+ this.url = input.url;
104
+ }
105
+ }
106
+
107
+ export type RuntimeClientError =
108
+ | RuntimeStartupError
109
+ | RuntimeApiVersionError
110
+ | RuntimeAuthError
111
+ | RuntimeHttpError
112
+ | RuntimeConnectionError
113
+ | RuntimeProtocolError
114
+ | RuntimeSessionError;
115
+
116
+ export function isRuntimeClientError(error: unknown): error is RuntimeClientError {
117
+ return error instanceof RuntimeStartupError
118
+ || error instanceof RuntimeApiVersionError
119
+ || error instanceof RuntimeAuthError
120
+ || error instanceof RuntimeHttpError
121
+ || error instanceof RuntimeConnectionError
122
+ || error instanceof RuntimeProtocolError
123
+ || error instanceof RuntimeSessionError;
124
+ }