@sealant/sdk 0.3.1 → 0.5.0
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 +107 -12
- package/dist/client.d.ts +12 -7
- package/dist/client.js +33 -21
- package/dist/effect/api-client.d.ts +408 -250
- package/dist/effect/exec-workspace.d.ts +5 -0
- package/dist/effect/exec-workspace.js +60 -0
- package/dist/effect/index.d.ts +27 -0
- package/dist/effect/index.js +31 -0
- package/dist/effect/operations.d.ts +90 -22
- package/dist/effect/operations.js +7 -4
- package/dist/effect/run-harness.d.ts +2 -2
- package/dist/effect/run-harness.js +4 -4
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/facade/record.d.ts +17 -1
- package/dist/facade/record.js +17 -3
- package/dist/facade/run.d.ts +1 -1
- package/dist/facade/{sandbox.d.ts → workspace.d.ts} +6 -6
- package/dist/facade/{sandbox.js → workspace.js} +19 -17
- package/dist/harness.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/internal/blueprint.d.ts +1 -1
- package/dist/internal/blueprint.js +8 -8
- package/dist/internal/config.d.ts +2 -2
- package/dist/internal/credentials.d.ts +7 -7
- package/dist/internal/credentials.js +2 -2
- package/dist/internal/inference.d.ts +47 -0
- package/dist/internal/inference.js +78 -0
- package/dist/internal/map-error.d.ts +1 -1
- package/dist/internal/map-error.js +1 -1
- package/dist/types.d.ts +310 -39
- package/dist/types.js +2 -2
- package/package.json +7 -3
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SdkContext } from "../facade/context.js";
|
|
2
|
+
import type { WorkspaceInit } from "../facade/workspace.js";
|
|
3
|
+
import type { WorkspaceExecOptions, WorkspaceExecResult } from "../types.js";
|
|
4
|
+
/** The `workspace.exec()` implementation (Promise boundary over the Effect above). */
|
|
5
|
+
export declare const execWorkspace: (ctx: SdkContext, init: WorkspaceInit, argv: readonly string[], options?: WorkspaceExecOptions | undefined) => Promise<WorkspaceExecResult>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { SealantError } from "../errors.js";
|
|
3
|
+
import { makeRun, toRunChangesData } from "../facade/run.js";
|
|
4
|
+
import { execWorkspaceOp, getRunChangesOp, getRunOp, getRunScrollbackOp, getRunTimelineOp, } from "./operations.js";
|
|
5
|
+
const TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled"]);
|
|
6
|
+
const POLL_INTERVAL = "500 millis";
|
|
7
|
+
const EXEC_TIMEOUT_MS = 30 * 60 * 1_000;
|
|
8
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
9
|
+
/**
|
|
10
|
+
* The exec'd command's process: the `processStarted` entry whose recorded executable matches, or the
|
|
11
|
+
* first one when attribution is imprecise (the record contains only processes this run exec'd).
|
|
12
|
+
*/
|
|
13
|
+
const findCommandProcessId = (entries, executable) => {
|
|
14
|
+
const match = entries.find((entry) => isRecord(entry.ref) && entry.ref["executable"] === executable);
|
|
15
|
+
return (match ?? entries[0])?.processId;
|
|
16
|
+
};
|
|
17
|
+
const readScrollback = (runId, processId, stream) => Effect.map(getRunScrollbackOp(runId, { processId, stream }), (response) => Buffer.from(response.contentBase64, "base64").toString("utf8"));
|
|
18
|
+
const execWorkspaceEffect = (ctx, init, argv, options) => Effect.gen(function* () {
|
|
19
|
+
const [executable, ...args] = argv;
|
|
20
|
+
if (executable === undefined || executable.length === 0) {
|
|
21
|
+
return yield* Effect.fail(new SealantError("exec requires argv with at least the executable.", {
|
|
22
|
+
code: "invalid_argv",
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
const created = yield* execWorkspaceOp(init.id, {
|
|
26
|
+
ownerUserId: ctx.config.hostLocal.ownerUserId,
|
|
27
|
+
commands: [{ executable, args, ...(options?.cwd === undefined ? {} : { cwd: options.cwd }) }],
|
|
28
|
+
});
|
|
29
|
+
const runId = created.runId;
|
|
30
|
+
// Block until the check run is terminal, polling the control plane (same shape as harness.run()).
|
|
31
|
+
const deadline = Date.now() + EXEC_TIMEOUT_MS;
|
|
32
|
+
let wire = created;
|
|
33
|
+
while (!TERMINAL_STATUSES.has(wire.status)) {
|
|
34
|
+
if (Date.now() > deadline) {
|
|
35
|
+
return yield* Effect.fail(new SealantError(`Timed out waiting for exec run ${runId} to complete.`, {
|
|
36
|
+
code: "exec_timeout",
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
yield* Effect.sleep(POLL_INTERVAL);
|
|
40
|
+
wire = yield* getRunOp(runId);
|
|
41
|
+
}
|
|
42
|
+
// Exec framing: "completed" means every command executed and was recorded — anything else means
|
|
43
|
+
// the machinery broke and the exit code cannot be trusted, which IS the error case.
|
|
44
|
+
if (wire.status !== "completed") {
|
|
45
|
+
return yield* Effect.fail(new SealantError(`Workspace exec did not complete: ${wire.errorMessage ?? `run ${runId} is ${wire.status}`}`, { code: "exec_failed" }));
|
|
46
|
+
}
|
|
47
|
+
const started = yield* getRunTimelineOp(runId, { kinds: "processStarted" });
|
|
48
|
+
const processId = findCommandProcessId(started, executable);
|
|
49
|
+
const stdout = processId === undefined ? "" : yield* readScrollback(runId, processId, "stdout");
|
|
50
|
+
const stderr = processId === undefined ? "" : yield* readScrollback(runId, processId, "stderr");
|
|
51
|
+
const changes = toRunChangesData(yield* getRunChangesOp(runId));
|
|
52
|
+
return {
|
|
53
|
+
exitCode: wire.exitCode ?? -1,
|
|
54
|
+
stdout,
|
|
55
|
+
stderr,
|
|
56
|
+
run: makeRun(ctx, { wire, changes }),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
/** The `workspace.exec()` implementation (Promise boundary over the Effect above). */
|
|
60
|
+
export const execWorkspace = (ctx, init, argv, options) => ctx.runtime.run(execWorkspaceEffect(ctx, init, argv, options));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sealant/sdk/effect — the Effect-native core of the SDK, for consumers that are Effect end-to-end.
|
|
3
|
+
*
|
|
4
|
+
* The Promise facade (`@sealant/sdk`) is a thin wrapper over what this subpath exports directly:
|
|
5
|
+
*
|
|
6
|
+
* - `SealantApiClient` + `sealantApiClientLayer` — the contract-derived control-plane client as an
|
|
7
|
+
* Effect service. The client is generated from the `@sealant/api-contracts` `HttpApi`, so its
|
|
8
|
+
* request/response types and tagged error channel are the contract's, by construction.
|
|
9
|
+
* - The operation effects (`createWorkspaceOp`, `createRunOp`, `getRunTimelineOp`, …) — one per
|
|
10
|
+
* contract endpoint, returning WIRE types on a typed error channel.
|
|
11
|
+
* - `makeSdkRuntime` — the managed runtime the facade itself runs on, for consumers who want the
|
|
12
|
+
* memoized layer build + `SealantError` mapping without hand-rolling a scope.
|
|
13
|
+
* - The tagged contract errors (`WorkspaceNotFoundError`, `RunNotFoundError`, …) so failures can be
|
|
14
|
+
* matched with `Effect.catchTag` instead of string-matching a squashed `SealantError`.
|
|
15
|
+
*
|
|
16
|
+
* Errors: effects composed from this subpath fail with the TYPED contract errors (plus Effect HTTP
|
|
17
|
+
* client/schema errors) — nothing is squashed. The plain `SealantError` classes only enter play via
|
|
18
|
+
* `makeSdkRuntime`, which maps the typed channel at the Promise boundary exactly like the facade.
|
|
19
|
+
*/
|
|
20
|
+
export { SealantApiClient, sealantApiClientLayer } from "./api-client.js";
|
|
21
|
+
export type { ControlPlaneClient } from "./api-client.js";
|
|
22
|
+
export * from "./operations.js";
|
|
23
|
+
export { makeSdkRuntime } from "./runtime.js";
|
|
24
|
+
export type { SdkRuntime, SdkServices } from "./runtime.js";
|
|
25
|
+
export { resolveInternalConfig } from "../internal/config.js";
|
|
26
|
+
export type { SealantHostLocalConfig, SealantInternalConfig } from "../internal/config.js";
|
|
27
|
+
export { InferenceBadRequestError, InferenceConflictError, InferenceInternalServerError, InferenceNotFoundError, InferenceUnavailableError, RunBadRequestError, RunInternalServerError, RunNotFoundError, WorkspaceBadGatewayError, WorkspaceBadRequestError, WorkspaceConflictError, WorkspaceForbiddenError, WorkspaceInternalServerError, WorkspaceNotFoundError, WorkspaceServiceUnavailableError, WorkspaceUnauthorizedError, } from "@sealant/api-contracts";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sealant/sdk/effect — the Effect-native core of the SDK, for consumers that are Effect end-to-end.
|
|
3
|
+
*
|
|
4
|
+
* The Promise facade (`@sealant/sdk`) is a thin wrapper over what this subpath exports directly:
|
|
5
|
+
*
|
|
6
|
+
* - `SealantApiClient` + `sealantApiClientLayer` — the contract-derived control-plane client as an
|
|
7
|
+
* Effect service. The client is generated from the `@sealant/api-contracts` `HttpApi`, so its
|
|
8
|
+
* request/response types and tagged error channel are the contract's, by construction.
|
|
9
|
+
* - The operation effects (`createWorkspaceOp`, `createRunOp`, `getRunTimelineOp`, …) — one per
|
|
10
|
+
* contract endpoint, returning WIRE types on a typed error channel.
|
|
11
|
+
* - `makeSdkRuntime` — the managed runtime the facade itself runs on, for consumers who want the
|
|
12
|
+
* memoized layer build + `SealantError` mapping without hand-rolling a scope.
|
|
13
|
+
* - The tagged contract errors (`WorkspaceNotFoundError`, `RunNotFoundError`, …) so failures can be
|
|
14
|
+
* matched with `Effect.catchTag` instead of string-matching a squashed `SealantError`.
|
|
15
|
+
*
|
|
16
|
+
* Errors: effects composed from this subpath fail with the TYPED contract errors (plus Effect HTTP
|
|
17
|
+
* client/schema errors) — nothing is squashed. The plain `SealantError` classes only enter play via
|
|
18
|
+
* `makeSdkRuntime`, which maps the typed channel at the Promise boundary exactly like the facade.
|
|
19
|
+
*/
|
|
20
|
+
// The contract-derived client service + layer.
|
|
21
|
+
export { SealantApiClient, sealantApiClientLayer } from "./api-client.js";
|
|
22
|
+
// One operation effect per contract endpoint (wire types in, wire types out).
|
|
23
|
+
export * from "./operations.js";
|
|
24
|
+
// The managed runtime the Promise facade runs on.
|
|
25
|
+
export { makeSdkRuntime } from "./runtime.js";
|
|
26
|
+
// The config the layer/runtime constructors take, and the resolver from the public `SealantConfig`.
|
|
27
|
+
export { resolveInternalConfig } from "../internal/config.js";
|
|
28
|
+
// The typed contract errors carried on the client's failure channel (workspaces + runs +
|
|
29
|
+
// inference — the groups the operations above call). Re-exported so Effect consumers don't need to
|
|
30
|
+
// depend on the contracts package directly to `Effect.catchTag` a failure.
|
|
31
|
+
export { InferenceBadRequestError, InferenceConflictError, InferenceInternalServerError, InferenceNotFoundError, InferenceUnavailableError, RunBadRequestError, RunInternalServerError, RunNotFoundError, WorkspaceBadGatewayError, WorkspaceBadRequestError, WorkspaceConflictError, WorkspaceForbiddenError, WorkspaceInternalServerError, WorkspaceNotFoundError, WorkspaceServiceUnavailableError, WorkspaceUnauthorizedError, } from "@sealant/api-contracts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { SealantApiClient } from "./api-client.js";
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const createWorkspaceOp: (payload: {
|
|
4
4
|
readonly ownerUserId: string;
|
|
5
5
|
readonly registryId: string;
|
|
6
6
|
readonly repository: string;
|
|
@@ -26,15 +26,15 @@ export declare const createSandboxOp: (payload: {
|
|
|
26
26
|
} | undefined;
|
|
27
27
|
readonly spec: unknown;
|
|
28
28
|
}, idempotencyKey?: string | undefined) => Effect.Effect<{
|
|
29
|
-
readonly
|
|
29
|
+
readonly workspaceId: string;
|
|
30
30
|
readonly name: string;
|
|
31
31
|
readonly status: "cancelled" | "failed" | "queued" | "ready" | "running";
|
|
32
32
|
readonly registryId: string;
|
|
33
33
|
readonly repository: string;
|
|
34
34
|
readonly tag: string;
|
|
35
|
-
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("
|
|
36
|
-
export declare const
|
|
37
|
-
readonly
|
|
35
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadGatewayError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceConflictError | import("@sealant/api-contracts").WorkspaceForbiddenError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError | import("@sealant/api-contracts").WorkspaceServiceUnavailableError, SealantApiClient>;
|
|
36
|
+
export declare const getWorkspaceOp: (workspaceId: string) => Effect.Effect<{
|
|
37
|
+
readonly workspaceId: string;
|
|
38
38
|
readonly name: string;
|
|
39
39
|
readonly ownerUserId: string;
|
|
40
40
|
readonly status: "cancelled" | "failed" | "queued" | "ready" | "running";
|
|
@@ -62,14 +62,14 @@ export declare const getSandboxOp: (sandboxId: string) => Effect.Effect<{
|
|
|
62
62
|
readonly startedAt?: string | undefined;
|
|
63
63
|
readonly finishedAt?: string | undefined;
|
|
64
64
|
readonly spec?: unknown;
|
|
65
|
-
},
|
|
66
|
-
export declare const
|
|
65
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError, SealantApiClient>;
|
|
66
|
+
export declare const listWorkspacesOp: (query: {
|
|
67
67
|
readonly ownerUserId: string;
|
|
68
68
|
readonly status?: "cancelled" | "failed" | "queued" | "ready" | "running" | undefined;
|
|
69
69
|
readonly limit?: string | undefined;
|
|
70
70
|
}) => Effect.Effect<{
|
|
71
71
|
readonly items: readonly {
|
|
72
|
-
readonly
|
|
72
|
+
readonly workspaceId: string;
|
|
73
73
|
readonly name: string;
|
|
74
74
|
readonly ownerUserId: string;
|
|
75
75
|
readonly status: "cancelled" | "failed" | "queued" | "ready" | "running";
|
|
@@ -97,9 +97,32 @@ export declare const listSandboxesOp: (query: {
|
|
|
97
97
|
readonly startedAt?: string | undefined;
|
|
98
98
|
readonly finishedAt?: string | undefined;
|
|
99
99
|
}[];
|
|
100
|
-
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("
|
|
100
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceInternalServerError, SealantApiClient>;
|
|
101
|
+
export declare const execWorkspaceOp: (workspaceId: string, payload: {
|
|
102
|
+
readonly ownerUserId: string;
|
|
103
|
+
readonly commands: readonly {
|
|
104
|
+
readonly executable: string;
|
|
105
|
+
readonly args: readonly string[];
|
|
106
|
+
readonly cwd?: string | undefined;
|
|
107
|
+
}[];
|
|
108
|
+
}) => Effect.Effect<{
|
|
109
|
+
readonly runId: string;
|
|
110
|
+
readonly workspaceId: string;
|
|
111
|
+
readonly attemptId?: string | undefined;
|
|
112
|
+
readonly ownerUserId: string;
|
|
113
|
+
readonly harnessId: string;
|
|
114
|
+
readonly mode: "interactive" | "one-shot";
|
|
115
|
+
readonly status: "cancelled" | "completed" | "failed" | "queued" | "running";
|
|
116
|
+
readonly prompt?: string | undefined;
|
|
117
|
+
readonly exitCode?: number | undefined;
|
|
118
|
+
readonly errorMessage?: string | undefined;
|
|
119
|
+
readonly startedAt?: string | undefined;
|
|
120
|
+
readonly finishedAt?: string | undefined;
|
|
121
|
+
readonly createdAt: string;
|
|
122
|
+
readonly updatedAt: string;
|
|
123
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceConflictError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError, SealantApiClient>;
|
|
101
124
|
export declare const createRunOp: (payload: {
|
|
102
|
-
readonly
|
|
125
|
+
readonly workspaceId: string;
|
|
103
126
|
readonly harnessId: string;
|
|
104
127
|
readonly ownerUserId: string;
|
|
105
128
|
readonly mode?: "interactive" | "one-shot" | undefined;
|
|
@@ -112,7 +135,7 @@ export declare const createRunOp: (payload: {
|
|
|
112
135
|
} | undefined;
|
|
113
136
|
}) => Effect.Effect<{
|
|
114
137
|
readonly runId: string;
|
|
115
|
-
readonly
|
|
138
|
+
readonly workspaceId: string;
|
|
116
139
|
readonly attemptId?: string | undefined;
|
|
117
140
|
readonly ownerUserId: string;
|
|
118
141
|
readonly harnessId: string;
|
|
@@ -125,10 +148,10 @@ export declare const createRunOp: (payload: {
|
|
|
125
148
|
readonly finishedAt?: string | undefined;
|
|
126
149
|
readonly createdAt: string;
|
|
127
150
|
readonly updatedAt: string;
|
|
128
|
-
},
|
|
151
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunBadRequestError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
|
|
129
152
|
export declare const getRunOp: (runId: string) => Effect.Effect<{
|
|
130
153
|
readonly runId: string;
|
|
131
|
-
readonly
|
|
154
|
+
readonly workspaceId: string;
|
|
132
155
|
readonly attemptId?: string | undefined;
|
|
133
156
|
readonly ownerUserId: string;
|
|
134
157
|
readonly harnessId: string;
|
|
@@ -141,16 +164,16 @@ export declare const getRunOp: (runId: string) => Effect.Effect<{
|
|
|
141
164
|
readonly finishedAt?: string | undefined;
|
|
142
165
|
readonly createdAt: string;
|
|
143
166
|
readonly updatedAt: string;
|
|
144
|
-
},
|
|
167
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
|
|
145
168
|
export declare const listRunsOp: (query: {
|
|
146
|
-
readonly
|
|
169
|
+
readonly workspaceId?: string | undefined;
|
|
147
170
|
readonly ownerUserId?: string | undefined;
|
|
148
171
|
readonly status?: "cancelled" | "completed" | "failed" | "queued" | "running" | undefined;
|
|
149
172
|
readonly limit?: string | undefined;
|
|
150
173
|
}) => Effect.Effect<{
|
|
151
174
|
readonly items: readonly {
|
|
152
175
|
readonly runId: string;
|
|
153
|
-
readonly
|
|
176
|
+
readonly workspaceId: string;
|
|
154
177
|
readonly attemptId?: string | undefined;
|
|
155
178
|
readonly ownerUserId: string;
|
|
156
179
|
readonly harnessId: string;
|
|
@@ -177,7 +200,7 @@ export declare const updateRunOp: (runId: string, payload: {
|
|
|
177
200
|
}[] | undefined;
|
|
178
201
|
}) => Effect.Effect<{
|
|
179
202
|
readonly runId: string;
|
|
180
|
-
readonly
|
|
203
|
+
readonly workspaceId: string;
|
|
181
204
|
readonly attemptId?: string | undefined;
|
|
182
205
|
readonly ownerUserId: string;
|
|
183
206
|
readonly harnessId: string;
|
|
@@ -190,7 +213,7 @@ export declare const updateRunOp: (runId: string, payload: {
|
|
|
190
213
|
readonly finishedAt?: string | undefined;
|
|
191
214
|
readonly createdAt: string;
|
|
192
215
|
readonly updatedAt: string;
|
|
193
|
-
},
|
|
216
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunBadRequestError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
|
|
194
217
|
export declare const getRunTimelineOp: (runId: string, query: {
|
|
195
218
|
readonly fromSequence?: string | undefined;
|
|
196
219
|
readonly toSequence?: string | undefined;
|
|
@@ -206,7 +229,7 @@ export declare const getRunTimelineOp: (runId: string, query: {
|
|
|
206
229
|
readonly processId?: string | undefined;
|
|
207
230
|
readonly captureMethod: number;
|
|
208
231
|
readonly confidence: number;
|
|
209
|
-
}[],
|
|
232
|
+
}[], import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunBadRequestError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
|
|
210
233
|
export declare const getRunScrollbackOp: (runId: string, query: {
|
|
211
234
|
readonly processId: string;
|
|
212
235
|
readonly stream: "stderr" | "stdout";
|
|
@@ -216,7 +239,7 @@ export declare const getRunScrollbackOp: (runId: string, query: {
|
|
|
216
239
|
readonly stream: "stderr" | "stdout";
|
|
217
240
|
readonly byteCount: number;
|
|
218
241
|
readonly contentBase64: string;
|
|
219
|
-
},
|
|
242
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunBadRequestError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
|
|
220
243
|
export declare const getRunLossOp: (runId: string) => Effect.Effect<{
|
|
221
244
|
readonly runId: string;
|
|
222
245
|
readonly droppedEventCount: string;
|
|
@@ -231,7 +254,7 @@ export declare const getRunLossOp: (runId: string) => Effect.Effect<{
|
|
|
231
254
|
readonly detectedVia: "gap" | "marker";
|
|
232
255
|
readonly reason?: string | undefined;
|
|
233
256
|
}[];
|
|
234
|
-
},
|
|
257
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
|
|
235
258
|
export declare const getRunChangesOp: (runId: string) => Effect.Effect<{
|
|
236
259
|
readonly files: readonly {
|
|
237
260
|
readonly path: string;
|
|
@@ -239,4 +262,49 @@ export declare const getRunChangesOp: (runId: string) => Effect.Effect<{
|
|
|
239
262
|
readonly oldPath?: string | undefined;
|
|
240
263
|
}[];
|
|
241
264
|
readonly diff: string;
|
|
242
|
-
},
|
|
265
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
|
|
266
|
+
export declare const inferenceRespondOp: (payload: {
|
|
267
|
+
readonly ownerUserId: string;
|
|
268
|
+
readonly credentials?: {
|
|
269
|
+
readonly profileId?: string | undefined;
|
|
270
|
+
readonly claude?: string | undefined;
|
|
271
|
+
readonly codex?: string | undefined;
|
|
272
|
+
} | undefined;
|
|
273
|
+
readonly prompt?: string | undefined;
|
|
274
|
+
readonly system?: string | undefined;
|
|
275
|
+
readonly model?: string | undefined;
|
|
276
|
+
readonly maxTurns?: number | undefined;
|
|
277
|
+
readonly tools?: readonly {
|
|
278
|
+
readonly name: string;
|
|
279
|
+
readonly description?: string | undefined;
|
|
280
|
+
readonly inputSchema: unknown;
|
|
281
|
+
}[] | undefined;
|
|
282
|
+
readonly responseFormat?: {
|
|
283
|
+
readonly type: "json";
|
|
284
|
+
readonly schema?: unknown;
|
|
285
|
+
} | undefined;
|
|
286
|
+
readonly sessionId?: string | undefined;
|
|
287
|
+
readonly toolResults?: readonly {
|
|
288
|
+
readonly toolCallId: string;
|
|
289
|
+
readonly content: string;
|
|
290
|
+
readonly isError?: boolean | undefined;
|
|
291
|
+
}[] | undefined;
|
|
292
|
+
}) => Effect.Effect<{
|
|
293
|
+
readonly sessionId: string;
|
|
294
|
+
readonly turn: {
|
|
295
|
+
readonly type: "text";
|
|
296
|
+
readonly text: string;
|
|
297
|
+
readonly json?: unknown;
|
|
298
|
+
} | {
|
|
299
|
+
readonly type: "toolCalls";
|
|
300
|
+
readonly calls: readonly {
|
|
301
|
+
readonly toolCallId: string;
|
|
302
|
+
readonly name: string;
|
|
303
|
+
readonly input: unknown;
|
|
304
|
+
}[];
|
|
305
|
+
};
|
|
306
|
+
readonly usage?: {
|
|
307
|
+
readonly inputTokens: number;
|
|
308
|
+
readonly outputTokens: number;
|
|
309
|
+
} | undefined;
|
|
310
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").InferenceBadRequestError | import("@sealant/api-contracts").InferenceConflictError | import("@sealant/api-contracts").InferenceInternalServerError | import("@sealant/api-contracts").InferenceNotFoundError | import("@sealant/api-contracts").InferenceUnavailableError | import("effect/Schema").SchemaError, SealantApiClient>;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { SealantApiClient } from "./api-client.js";
|
|
3
|
-
// ----
|
|
4
|
-
export const
|
|
3
|
+
// ---- workspaces ----
|
|
4
|
+
export const createWorkspaceOp = (payload, idempotencyKey) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.createWorkspace({
|
|
5
5
|
payload,
|
|
6
6
|
headers: idempotencyKey === undefined ? {} : { "idempotency-key": idempotencyKey },
|
|
7
7
|
}));
|
|
8
|
-
export const
|
|
9
|
-
export const
|
|
8
|
+
export const getWorkspaceOp = (workspaceId) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.getWorkspace({ params: { workspaceId } }));
|
|
9
|
+
export const listWorkspacesOp = (query) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.listWorkspaces({ query }));
|
|
10
|
+
export const execWorkspaceOp = (workspaceId, payload) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.execWorkspace({ params: { workspaceId }, payload }));
|
|
10
11
|
// ---- runs ----
|
|
11
12
|
export const createRunOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.runs.createRun({ payload }));
|
|
12
13
|
export const getRunOp = (runId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRun({ params: { runId } }));
|
|
@@ -16,3 +17,5 @@ export const getRunTimelineOp = (runId, query) => Effect.flatMap(SealantApiClien
|
|
|
16
17
|
export const getRunScrollbackOp = (runId, query) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRunScrollback({ params: { runId }, query }));
|
|
17
18
|
export const getRunLossOp = (runId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRunLoss({ params: { runId } }));
|
|
18
19
|
export const getRunChangesOp = (runId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRunChanges({ params: { runId } }));
|
|
20
|
+
// ---- inference ----
|
|
21
|
+
export const inferenceRespondOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.inference.respond({ payload }));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { RunHarnessFn } from "../facade/
|
|
2
|
-
/** The BLOCKING `harness.run()` implementation, registered into the
|
|
1
|
+
import type { RunHarnessFn } from "../facade/workspace.js";
|
|
2
|
+
/** The BLOCKING `harness.run()` implementation, registered into the Workspace facade by the client. */
|
|
3
3
|
export declare const runHarness: RunHarnessFn;
|
|
4
4
|
/**
|
|
5
5
|
* The NON-BLOCKING `harness.start()` implementation: register the run and return the live handle
|
|
@@ -17,16 +17,16 @@ const POLL_INTERVAL = "500 millis";
|
|
|
17
17
|
const RUN_TIMEOUT_MS = 30 * 60 * 1_000;
|
|
18
18
|
/**
|
|
19
19
|
* Registers the run WITH the harness command — the control plane executes it server-side (the worker
|
|
20
|
-
* docker-execs it and ingests telemetry). The cwd is the
|
|
20
|
+
* docker-execs it and ingests telemetry). The cwd is the workspace repo, which the worker defaults to.
|
|
21
21
|
*/
|
|
22
22
|
const createHarnessRunEffect = (ctx, init, prompt) => Effect.gen(function* () {
|
|
23
23
|
const harness = init.harness;
|
|
24
24
|
if (harness === undefined) {
|
|
25
|
-
return yield* Effect.fail(new SealantError("This
|
|
25
|
+
return yield* Effect.fail(new SealantError("This workspace handle has no harness; use the handle returned by workspaces.create().", { code: "harness_required" }));
|
|
26
26
|
}
|
|
27
27
|
const command = harness.buildRunCommand(prompt);
|
|
28
28
|
return yield* createRunOp({
|
|
29
|
-
|
|
29
|
+
workspaceId: init.id,
|
|
30
30
|
ownerUserId: ctx.config.hostLocal.ownerUserId,
|
|
31
31
|
harnessId: harness.id,
|
|
32
32
|
mode: "one-shot",
|
|
@@ -53,7 +53,7 @@ const runHarnessEffect = (ctx, init, prompt) => Effect.gen(function* () {
|
|
|
53
53
|
const changes = toRunChangesData(yield* getRunChangesOp(runId));
|
|
54
54
|
return makeRun(ctx, { wire, changes });
|
|
55
55
|
});
|
|
56
|
-
/** The BLOCKING `harness.run()` implementation, registered into the
|
|
56
|
+
/** The BLOCKING `harness.run()` implementation, registered into the Workspace facade by the client. */
|
|
57
57
|
export const runHarness = (ctx, init, prompt) => ctx.runtime.run(runHarnessEffect(ctx, init, prompt));
|
|
58
58
|
/**
|
|
59
59
|
* The NON-BLOCKING `harness.start()` implementation: register the run and return the live handle
|
package/dist/errors.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export declare class SealantError extends Error {
|
|
|
13
13
|
readonly cause?: unknown;
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
|
-
/** A typed control/transport failure from the
|
|
16
|
+
/** A typed control/transport failure from the workspace runtime daemon. */
|
|
17
17
|
export declare class SealantRuntimeError extends SealantError {
|
|
18
18
|
readonly name = "SealantRuntimeError";
|
|
19
19
|
constructor(message: string, options?: {
|
package/dist/errors.js
CHANGED
|
@@ -13,7 +13,7 @@ export class SealantError extends Error {
|
|
|
13
13
|
this.code = options?.code ?? "sealant_error";
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
/** A typed control/transport failure from the
|
|
16
|
+
/** A typed control/transport failure from the workspace runtime daemon. */
|
|
17
17
|
export class SealantRuntimeError extends SealantError {
|
|
18
18
|
name = "SealantRuntimeError";
|
|
19
19
|
constructor(message, options) {
|
package/dist/facade/record.d.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import type { RunCommand, RunRecord } from "../types.js";
|
|
1
|
+
import type { RunCommand, RunRecord, TimelineEntry } from "../types.js";
|
|
2
2
|
import type { SdkContext } from "./context.js";
|
|
3
|
+
/**
|
|
4
|
+
* Maps a wire entry to the public DISCRIMINATED entry: the contract's payload schemas fold
|
|
5
|
+
* `(kind, ref)` into typed `data`, degrading to the `unknown` case (raw kind + payload preserved)
|
|
6
|
+
* for kinds newer than this SDK or payloads that fail their schema. Exported for tests.
|
|
7
|
+
*/
|
|
8
|
+
export declare const toTimelineEntry: (wire: {
|
|
9
|
+
readonly eventId: string;
|
|
10
|
+
readonly sequence: string;
|
|
11
|
+
readonly kind: string;
|
|
12
|
+
readonly occurredAt: string;
|
|
13
|
+
readonly summary: string;
|
|
14
|
+
readonly ref?: unknown;
|
|
15
|
+
readonly processId?: string | undefined;
|
|
16
|
+
readonly captureMethod: number;
|
|
17
|
+
readonly confidence: number;
|
|
18
|
+
}) => TimelineEntry;
|
|
3
19
|
/**
|
|
4
20
|
* Folds the timeline into the ordered list of terminal commands. Process boundaries are
|
|
5
21
|
* `processStarted`/`processExited`; `ioChunk` byte counts accrue to the current command. Daemon noise
|
package/dist/facade/record.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `RunRecord` facade — the execution record as the SDK exposes it. Backed by the run/record
|
|
3
|
+
* contract endpoints (`client.runs.*`), it maps the wire shapes to the public types: decimal-string
|
|
4
|
+
* sequences become `bigint`, base64 scrollback becomes `Uint8Array`. `replay()`/`timeline()`/
|
|
5
|
+
* `scrollback()`/`loss()`/`summary()`/`stream()` are live; the time-travel folds are typed but
|
|
6
|
+
* reject until their read models land (Phase 1).
|
|
7
|
+
*/
|
|
8
|
+
import { decodeRecordEventPayload, } from "@sealant/api-contracts";
|
|
1
9
|
import { getRunLossOp, getRunOp, getRunScrollbackOp, getRunTimelineOp, } from "../effect/operations.js";
|
|
2
10
|
import { SealantNotImplementedError } from "../errors.js";
|
|
3
11
|
// Live `stream()` is poll-backed for now: it tails the timeline endpoint until the run is terminal.
|
|
@@ -6,11 +14,17 @@ const TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "cancelled"]);
|
|
|
6
14
|
const STREAM_POLL_INTERVAL_MS = 500;
|
|
7
15
|
const STREAM_TIMEOUT_MS = 30 * 60 * 1_000;
|
|
8
16
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Maps a wire entry to the public DISCRIMINATED entry: the contract's payload schemas fold
|
|
19
|
+
* `(kind, ref)` into typed `data`, degrading to the `unknown` case (raw kind + payload preserved)
|
|
20
|
+
* for kinds newer than this SDK or payloads that fail their schema. Exported for tests.
|
|
21
|
+
*/
|
|
22
|
+
export const toTimelineEntry = (wire) => ({
|
|
10
23
|
sequence: BigInt(wire.sequence),
|
|
11
|
-
kind: wire.kind,
|
|
12
24
|
occurredAt: wire.occurredAt,
|
|
13
|
-
|
|
25
|
+
summary: wire.summary,
|
|
26
|
+
...(wire.processId === undefined ? {} : { processId: wire.processId }),
|
|
27
|
+
...decodeRecordEventPayload(wire.kind, wire.ref),
|
|
14
28
|
});
|
|
15
29
|
const toLossReport = (wire) => ({
|
|
16
30
|
complete: !wire.earlyClose && wire.droppedEventCount === "0" && wire.sequenceGapCount === 0,
|
package/dist/facade/run.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `Run` facade — one harness execution as the SDK exposes it. Built from a `runs.get` lookup
|
|
3
|
-
* (read a past run, record outlives the
|
|
3
|
+
* (read a past run, record outlives the workspace), from `harness.run()` (which also captures the file
|
|
4
4
|
* changes inline), or from `harness.start()` (a live handle; `wait()` settles it). `result` is
|
|
5
5
|
* derived from the wire status; `changes` is captured inline by `run()` and fetched by `wait()` once
|
|
6
6
|
* the run is terminal (the event-sourced fileChange projection that backs reads arrives in Phase 1);
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import type { Harness,
|
|
1
|
+
import type { Harness, Workspace, WorkspaceStatus } from "../types.js";
|
|
2
2
|
import type { SdkContext } from "./context.js";
|
|
3
|
-
export interface
|
|
3
|
+
export interface WorkspaceInit {
|
|
4
4
|
readonly id: string;
|
|
5
5
|
readonly name: string;
|
|
6
|
-
readonly status:
|
|
6
|
+
readonly status: WorkspaceStatus;
|
|
7
7
|
/** Present when the handle came from `create()` (needed by `harness.run()`). */
|
|
8
8
|
readonly harness?: Harness;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* The `harness.run()`/`harness.start()` implementations are injected by the run-execution module to
|
|
12
|
-
* avoid a static dependency cycle (
|
|
12
|
+
* avoid a static dependency cycle (workspace <-> run execution). Until they are registered, both
|
|
13
13
|
* report that the feature is not wired in this build.
|
|
14
14
|
*/
|
|
15
|
-
export type RunHarnessFn = (ctx: SdkContext, init:
|
|
15
|
+
export type RunHarnessFn = (ctx: SdkContext, init: WorkspaceInit, prompt: string, options?: import("../types.js").RunOptions) => Promise<import("../types.js").Run>;
|
|
16
16
|
export interface HarnessExecutors {
|
|
17
17
|
/** BLOCKING `harness.run()`: resolves once the run is terminal. */
|
|
18
18
|
readonly run: RunHarnessFn;
|
|
@@ -20,4 +20,4 @@ export interface HarnessExecutors {
|
|
|
20
20
|
readonly start: RunHarnessFn;
|
|
21
21
|
}
|
|
22
22
|
export declare const registerHarnessExecutors: (executors: HarnessExecutors) => void;
|
|
23
|
-
export declare const
|
|
23
|
+
export declare const makeWorkspace: (ctx: SdkContext, init: WorkspaceInit) => Workspace;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execWorkspace } from "../effect/exec-workspace.js";
|
|
2
|
+
import { getWorkspaceOp } from "../effect/operations.js";
|
|
2
3
|
import { SealantError, SealantNotImplementedError } from "../errors.js";
|
|
3
4
|
const FAILED_STATUSES = new Set(["failed", "cancelled"]);
|
|
4
5
|
const READY_POLL_INTERVAL_MS = 2_000;
|
|
@@ -8,7 +9,7 @@ let harnessExecutors;
|
|
|
8
9
|
export const registerHarnessExecutors = (executors) => {
|
|
9
10
|
harnessExecutors = executors;
|
|
10
11
|
};
|
|
11
|
-
export const
|
|
12
|
+
export const makeWorkspace = (ctx, init) => {
|
|
12
13
|
const harness = {
|
|
13
14
|
run: (prompt, options) => {
|
|
14
15
|
if (harnessExecutors === undefined) {
|
|
@@ -24,36 +25,37 @@ export const makeSandbox = (ctx, init) => {
|
|
|
24
25
|
},
|
|
25
26
|
session: () => Promise.reject(new SealantNotImplementedError("harness.session (interactive, Phase 3)")),
|
|
26
27
|
};
|
|
27
|
-
const
|
|
28
|
+
const workspace = {
|
|
28
29
|
id: init.id,
|
|
29
30
|
name: init.name,
|
|
30
31
|
status: async () => {
|
|
31
|
-
const details = await ctx.runtime.run(
|
|
32
|
+
const details = await ctx.runtime.run(getWorkspaceOp(init.id));
|
|
32
33
|
return details.status;
|
|
33
34
|
},
|
|
34
35
|
ready: async () => {
|
|
35
36
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
36
37
|
for (;;) {
|
|
37
|
-
const details = await ctx.runtime.run(
|
|
38
|
+
const details = await ctx.runtime.run(getWorkspaceOp(init.id));
|
|
38
39
|
// Gate on the coarse "ready" status, which the control plane now emits ONLY after the
|
|
39
|
-
// in-
|
|
40
|
+
// in-workspace daemon's control socket is accepting (readiness probe in the launch path).
|
|
40
41
|
// This is honest: when ready() resolves, harness.run() can connect without racing the socket.
|
|
41
42
|
if (details.status === "ready") {
|
|
42
|
-
return
|
|
43
|
+
return workspace;
|
|
43
44
|
}
|
|
44
45
|
if (FAILED_STATUSES.has(details.status)) {
|
|
45
|
-
throw new SealantError(`
|
|
46
|
+
throw new SealantError(`Workspace ${init.id} reached terminal status "${details.status}" before becoming ready.`, { code: "workspace_not_ready" });
|
|
46
47
|
}
|
|
47
48
|
if (Date.now() > deadline) {
|
|
48
|
-
throw new SealantError(`Timed out waiting for
|
|
49
|
-
code: "
|
|
49
|
+
throw new SealantError(`Timed out waiting for workspace ${init.id} to become ready.`, {
|
|
50
|
+
code: "workspace_ready_timeout",
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
await delay(READY_POLL_INTERVAL_MS);
|
|
53
54
|
}
|
|
54
55
|
},
|
|
55
56
|
harness,
|
|
56
|
-
|
|
57
|
+
exec: (argv, options) => execWorkspace(ctx, init, argv, options),
|
|
58
|
+
// Poll-backed lifecycle stream: emit a coarse event on each status transition until the workspace
|
|
57
59
|
// reaches a terminal/ready state. Swaps to SSE over Postgres LISTEN/NOTIFY in Stage 5 (same shape).
|
|
58
60
|
events: () => {
|
|
59
61
|
const ctxRun = ctx.runtime;
|
|
@@ -61,13 +63,13 @@ export const makeSandbox = (ctx, init) => {
|
|
|
61
63
|
let lastStatus;
|
|
62
64
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
63
65
|
for (;;) {
|
|
64
|
-
const details = await ctxRun.run(
|
|
66
|
+
const details = await ctxRun.run(getWorkspaceOp(init.id));
|
|
65
67
|
if (details.status !== lastStatus) {
|
|
66
68
|
lastStatus = details.status;
|
|
67
69
|
yield {
|
|
68
70
|
type: `status.${details.status}`,
|
|
69
71
|
occurredAt: new Date().toISOString(),
|
|
70
|
-
message: `
|
|
72
|
+
message: `Workspace status: ${details.status}`,
|
|
71
73
|
};
|
|
72
74
|
}
|
|
73
75
|
if (details.status === "ready" || FAILED_STATUSES.has(details.status)) {
|
|
@@ -81,9 +83,9 @@ export const makeSandbox = (ctx, init) => {
|
|
|
81
83
|
}
|
|
82
84
|
return iterate();
|
|
83
85
|
},
|
|
84
|
-
stop: () => Promise.reject(new SealantNotImplementedError("
|
|
85
|
-
restart: () => Promise.reject(new SealantNotImplementedError("
|
|
86
|
-
expire: () => Promise.reject(new SealantNotImplementedError("
|
|
86
|
+
stop: () => Promise.reject(new SealantNotImplementedError("workspace.stop (lifecycle, Phase 3)")),
|
|
87
|
+
restart: () => Promise.reject(new SealantNotImplementedError("workspace.restart (lifecycle, Phase 3)")),
|
|
88
|
+
expire: () => Promise.reject(new SealantNotImplementedError("workspace.expire (lifecycle, Phase 3)")),
|
|
87
89
|
};
|
|
88
|
-
return
|
|
90
|
+
return workspace;
|
|
89
91
|
};
|
package/dist/harness.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* NOTE — the one-shot invocation forms below (`opencode run <prompt>`, `codex exec <prompt>`,
|
|
6
6
|
* `claude -p <prompt>`) are the expected headless shapes but are PENDING live verification against
|
|
7
|
-
* the baked
|
|
7
|
+
* the baked workspace image (see the SDK plan's task #2, "verify harness one-shot CLI semantics").
|
|
8
8
|
* Until that is confirmed, only `opencode()` is exercised end-to-end; the others are provided for the
|
|
9
9
|
* typed surface and adjusted once verified.
|
|
10
10
|
*/
|