@sealant/sdk 0.4.0 → 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 +100 -5
- package/dist/client.d.ts +6 -1
- package/dist/client.js +13 -1
- package/dist/effect/api-client.d.ts +158 -0
- 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 +76 -8
- package/dist/effect/operations.js +3 -0
- package/dist/facade/record.d.ts +17 -1
- package/dist/facade/record.js +17 -3
- package/dist/facade/workspace.js +2 -0
- package/dist/internal/inference.d.ts +47 -0
- package/dist/internal/inference.js +78 -0
- package/dist/types.d.ts +275 -4
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -20,8 +20,23 @@ await run.record.replay();
|
|
|
20
20
|
## Design
|
|
21
21
|
|
|
22
22
|
- **Plain-Promise facade over an Effect core.** The default export is ordinary `async`/`await`. The
|
|
23
|
-
Effect-native core
|
|
24
|
-
|
|
23
|
+
Effect-native core is reachable via the `@sealant/sdk/effect` subpath for consumers that are
|
|
24
|
+
Effect end-to-end: the contract-derived client as a service, one operation effect per endpoint,
|
|
25
|
+
and the typed contract errors on the failure channel (no squashing) —
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { Effect } from "effect";
|
|
29
|
+
import { getRunOp, resolveInternalConfig, sealantApiClientLayer } from "@sealant/sdk/effect";
|
|
30
|
+
|
|
31
|
+
const layer = sealantApiClientLayer(resolveInternalConfig({ baseUrl: "http://localhost:8080" }));
|
|
32
|
+
|
|
33
|
+
const status = getRunOp("run_123").pipe(
|
|
34
|
+
Effect.map((run) => run.status),
|
|
35
|
+
Effect.catchTag("RunNotFoundError", () => Effect.succeed("gone" as const)),
|
|
36
|
+
Effect.provide(layer),
|
|
37
|
+
);
|
|
38
|
+
```
|
|
39
|
+
|
|
25
40
|
- **Decoupled public types.** The types in [`src/types.ts`](src/types.ts) are hand-written and kept
|
|
26
41
|
independent of the Effect-core and `@sealant/telemetry` internal shapes, so the public surface
|
|
27
42
|
stays stable across internal change. The whole surface is typed now, including operations not yet
|
|
@@ -29,6 +44,46 @@ await run.record.replay();
|
|
|
29
44
|
- **Harness-neutral.** `opencode()`, `codex()`, `claudeCode()`, and `customHarness()` are thin
|
|
30
45
|
client values describing how to invoke a harness one-shot.
|
|
31
46
|
|
|
47
|
+
## Deterministic exec
|
|
48
|
+
|
|
49
|
+
Run a command in the workspace with no agent in the loop — recorded into a run record like any other
|
|
50
|
+
process:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const check = await workspace.exec(["pnpm", "test"], { cwd: "/workspace/repo" });
|
|
54
|
+
check.exitCode; // the check datum — a NONZERO exit RESOLVES (that's the point)
|
|
55
|
+
check.stdout; // full stdout, decoded
|
|
56
|
+
check.run.record; // the durable evidence
|
|
57
|
+
|
|
58
|
+
// A causal proof is three execs with three recorded exit codes:
|
|
59
|
+
const base = await workspace.exec(["pnpm", "test"]); // fails
|
|
60
|
+
// ...apply the fix...
|
|
61
|
+
const head = await workspace.exec(["pnpm", "test"]); // passes
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`exec()` rejects only when the execution machinery itself broke (workspace gone, transport dropped)
|
|
65
|
+
— i.e. when the exit code cannot be trusted. The underlying endpoint
|
|
66
|
+
(`POST /v1/workspaces/:id/exec`) accepts an ordered **list** of commands recorded as one check run;
|
|
67
|
+
the SDK surface starts with the single-command form.
|
|
68
|
+
|
|
69
|
+
## Typed record events
|
|
70
|
+
|
|
71
|
+
Timeline reads are discriminated by `kind` — switch on it and `data` narrows to the event's typed
|
|
72
|
+
payload (all 12 recorded kinds: process, io, file, network, runtime, and loss events):
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
for await (const entry of run.record.timeline()) {
|
|
76
|
+
if (entry.kind === "networkSourceObserved") {
|
|
77
|
+
entry.data.host; // typed — the raw material of a "sources the agent opened" trail
|
|
78
|
+
entry.data.status;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Forward compatibility is a case, not an error: kinds newer than your SDK version (and payloads that
|
|
84
|
+
fail their schema) arrive as `{ kind: "unknown", rawKind, data }` with everything preserved. Wire
|
|
85
|
+
conventions carry through: uint64 fields are decimal strings, protocol enums are numbers.
|
|
86
|
+
|
|
32
87
|
## Connected-account credentials
|
|
33
88
|
|
|
34
89
|
Attach the caller's connected Claude / Codex / GitHub accounts to a workspace so the harness
|
|
@@ -48,13 +103,53 @@ field wins over the profile's binding for that provider. Only account references
|
|
|
48
103
|
surface — secret material never does; the control plane resolves references to encrypted credentials
|
|
49
104
|
and injects them at launch.
|
|
50
105
|
|
|
106
|
+
## Inference on connected accounts
|
|
107
|
+
|
|
108
|
+
Run short, tool-calling inference loops on the caller's own subscription — server-side, through the
|
|
109
|
+
official agent SDKs (never raw model-API calls on stored credentials), with the tool loop executed
|
|
110
|
+
on YOUR side:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
let response = await sealant.inference.respond({
|
|
114
|
+
prompt: "Compile a review brief from this run record.",
|
|
115
|
+
tools: [
|
|
116
|
+
{
|
|
117
|
+
name: "get_timeline",
|
|
118
|
+
inputSchema: { type: "object", properties: { runId: { type: "string" } } },
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
responseFormat: { type: "json", schema: briefSchema },
|
|
122
|
+
credentials: { claude: true },
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
while (response.turn.type === "toolCalls") {
|
|
126
|
+
const toolResults = await Promise.all(
|
|
127
|
+
response.turn.calls.map(async (call) => ({
|
|
128
|
+
toolCallId: call.toolCallId,
|
|
129
|
+
content: await runTool(call.name, call.input),
|
|
130
|
+
})),
|
|
131
|
+
);
|
|
132
|
+
response = await sealant.inference.respond({ sessionId: response.sessionId, toolResults });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
response.turn.json; // schema-constrained result
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Only account references cross the surface — the control plane resolves and decrypts server-side and
|
|
139
|
+
invokes the official Claude Agent SDK with the account's own subscription token. Claude accounts
|
|
140
|
+
only for now (Codex inference is a stated follow-up); sessions are held in memory by the control
|
|
141
|
+
plane and expire after a few idle minutes, so handle a 404 on continuation by restarting the
|
|
142
|
+
exchange.
|
|
143
|
+
|
|
51
144
|
## Status
|
|
52
145
|
|
|
53
146
|
The core loop is real: `workspaces.create()`/`get()`/`list()`, `ready()`, blocking `harness.run()`
|
|
54
147
|
and non-blocking `harness.start()` (run execution happens server-side; the SDK is a thin HTTP
|
|
55
|
-
client), `
|
|
56
|
-
|
|
57
|
-
|
|
148
|
+
client), deterministic `workspace.exec()`, `inference.respond()` (connected-account inference with a
|
|
149
|
+
caller-executed tool loop), `runs.get()`, and the record read surface — `replay()`, `timeline()`
|
|
150
|
+
(typed, kind-discriminated entries), `scrollback()`, `commands()`, `transcript()`, `stream()`
|
|
151
|
+
(poll-backed), `loss()`, `summary()`, plus captured `changes` (files + diff) settled by
|
|
152
|
+
`run()`/`wait()`. The Effect-native core ships at `@sealant/sdk/effect`.
|
|
58
153
|
|
|
59
154
|
Still typed stubs pending their read models / endpoints: `artifacts.get()` and the time-travel folds
|
|
60
155
|
`fileTreeAt()`/`processTreeAt()` (Phase 1), and `harness.session()` + workspace lifecycle
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CreateOptions, ListOptions, Run, Workspace, SealantConfig } from "./types.js";
|
|
1
|
+
import type { CreateOptions, InferenceNamespace, ListOptions, Run, Workspace, SealantConfig } from "./types.js";
|
|
2
2
|
export declare class Sealant {
|
|
3
3
|
#private;
|
|
4
4
|
constructor(config: SealantConfig);
|
|
@@ -10,6 +10,11 @@ export declare class Sealant {
|
|
|
10
10
|
get: (id: string) => Promise<Workspace>;
|
|
11
11
|
list: (options?: ListOptions | undefined) => Promise<readonly Workspace[]>;
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Inference on connected accounts — server-side via the official agent SDKs, never raw model-API
|
|
15
|
+
* calls. Tool calls park server-side; execute them here and `respond()` with the results.
|
|
16
|
+
*/
|
|
17
|
+
readonly inference: InferenceNamespace;
|
|
13
18
|
/** Runs by id — so a record can be replayed long after its workspace is gone. */
|
|
14
19
|
readonly runs: {
|
|
15
20
|
get: (runId: string) => Promise<Run>;
|
package/dist/client.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* stable surface and reject with `SealantNotImplementedError` so callers can compile and wire
|
|
11
11
|
* against the final shape today.
|
|
12
12
|
*/
|
|
13
|
-
import { createWorkspaceOp, getRunOp, getWorkspaceOp, listWorkspacesOp, } from "./effect/operations.js";
|
|
13
|
+
import { createWorkspaceOp, getRunOp, getWorkspaceOp, inferenceRespondOp, listWorkspacesOp, } from "./effect/operations.js";
|
|
14
14
|
import { runHarness, startHarness } from "./effect/run-harness.js";
|
|
15
15
|
import { makeSdkRuntime } from "./effect/runtime.js";
|
|
16
16
|
import { SealantError } from "./errors.js";
|
|
@@ -18,6 +18,7 @@ import { makeRun } from "./facade/run.js";
|
|
|
18
18
|
import { makeWorkspace, registerHarnessExecutors } from "./facade/workspace.js";
|
|
19
19
|
import { buildCreateWorkspaceRequest } from "./internal/blueprint.js";
|
|
20
20
|
import { resolveInternalConfig } from "./internal/config.js";
|
|
21
|
+
import { buildInferenceRespondRequest, mapInferenceResponse } from "./internal/inference.js";
|
|
21
22
|
// Wire the run-execution implementations into the Workspace facade (the injection point exists to
|
|
22
23
|
// break the workspace <-> run-harness import cycle; the client is the composition root).
|
|
23
24
|
registerHarnessExecutors({ run: runHarness, start: startHarness });
|
|
@@ -90,6 +91,17 @@ export class Sealant {
|
|
|
90
91
|
}));
|
|
91
92
|
},
|
|
92
93
|
};
|
|
94
|
+
/**
|
|
95
|
+
* Inference on connected accounts — server-side via the official agent SDKs, never raw model-API
|
|
96
|
+
* calls. Tool calls park server-side; execute them here and `respond()` with the results.
|
|
97
|
+
*/
|
|
98
|
+
inference = {
|
|
99
|
+
respond: async (options) => {
|
|
100
|
+
const payload = buildInferenceRespondRequest(options, this.#ctx.config.hostLocal.ownerUserId);
|
|
101
|
+
const wire = await this.#runtime.run(inferenceRespondOp(payload));
|
|
102
|
+
return mapInferenceResponse(wire);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
93
105
|
/** Runs by id — so a record can be replayed long after its workspace is gone. */
|
|
94
106
|
runs = {
|
|
95
107
|
get: async (runId) => {
|
|
@@ -183,6 +183,56 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
|
|
|
183
183
|
readonly syncedAt: string;
|
|
184
184
|
}, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("@sealant/api-contracts").GitHubForbiddenError | import("@sealant/api-contracts").GitHubInternalServerError | import("@sealant/api-contracts").GitHubNotFoundError | import("@sealant/api-contracts").GitHubServiceUnavailableError | import("effect/Schema").SchemaError), [Mode] extends ["response-only"] ? never : never>;
|
|
185
185
|
};
|
|
186
|
+
readonly inference: {
|
|
187
|
+
readonly respond: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
|
|
188
|
+
readonly payload: {
|
|
189
|
+
readonly ownerUserId: string;
|
|
190
|
+
readonly credentials?: {
|
|
191
|
+
readonly profileId?: string | undefined;
|
|
192
|
+
readonly claude?: string | undefined;
|
|
193
|
+
readonly codex?: string | undefined;
|
|
194
|
+
} | undefined;
|
|
195
|
+
readonly prompt?: string | undefined;
|
|
196
|
+
readonly system?: string | undefined;
|
|
197
|
+
readonly model?: string | undefined;
|
|
198
|
+
readonly maxTurns?: number | undefined;
|
|
199
|
+
readonly tools?: readonly {
|
|
200
|
+
readonly name: string;
|
|
201
|
+
readonly description?: string | undefined;
|
|
202
|
+
readonly inputSchema: unknown;
|
|
203
|
+
}[] | undefined;
|
|
204
|
+
readonly responseFormat?: {
|
|
205
|
+
readonly type: "json";
|
|
206
|
+
readonly schema?: unknown;
|
|
207
|
+
} | undefined;
|
|
208
|
+
readonly sessionId?: string | undefined;
|
|
209
|
+
readonly toolResults?: readonly {
|
|
210
|
+
readonly toolCallId: string;
|
|
211
|
+
readonly content: string;
|
|
212
|
+
readonly isError?: boolean | undefined;
|
|
213
|
+
}[] | undefined;
|
|
214
|
+
};
|
|
215
|
+
readonly responseMode?: Mode;
|
|
216
|
+
}) => Effect.Effect<HttpApiClient.Client.Response<{
|
|
217
|
+
readonly sessionId: string;
|
|
218
|
+
readonly turn: {
|
|
219
|
+
readonly type: "text";
|
|
220
|
+
readonly text: string;
|
|
221
|
+
readonly json?: unknown;
|
|
222
|
+
} | {
|
|
223
|
+
readonly type: "toolCalls";
|
|
224
|
+
readonly calls: readonly {
|
|
225
|
+
readonly toolCallId: string;
|
|
226
|
+
readonly name: string;
|
|
227
|
+
readonly input: unknown;
|
|
228
|
+
}[];
|
|
229
|
+
};
|
|
230
|
+
readonly usage?: {
|
|
231
|
+
readonly inputTokens: number;
|
|
232
|
+
readonly outputTokens: number;
|
|
233
|
+
} | undefined;
|
|
234
|
+
}, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : 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), [Mode] extends ["response-only"] ? never : never>;
|
|
235
|
+
};
|
|
186
236
|
readonly packages: {
|
|
187
237
|
readonly resolvePackage: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
|
|
188
238
|
readonly query: {
|
|
@@ -716,6 +766,35 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
|
|
|
716
766
|
readonly repository: string;
|
|
717
767
|
readonly tag: string;
|
|
718
768
|
}, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : 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), [Mode] extends ["response-only"] ? never : never>;
|
|
769
|
+
readonly execWorkspace: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
|
|
770
|
+
readonly params: {
|
|
771
|
+
readonly workspaceId: string;
|
|
772
|
+
};
|
|
773
|
+
readonly payload: {
|
|
774
|
+
readonly ownerUserId: string;
|
|
775
|
+
readonly commands: readonly {
|
|
776
|
+
readonly executable: string;
|
|
777
|
+
readonly args: readonly string[];
|
|
778
|
+
readonly cwd?: string | undefined;
|
|
779
|
+
}[];
|
|
780
|
+
};
|
|
781
|
+
readonly responseMode?: Mode;
|
|
782
|
+
}) => Effect.Effect<HttpApiClient.Client.Response<{
|
|
783
|
+
readonly runId: string;
|
|
784
|
+
readonly workspaceId: string;
|
|
785
|
+
readonly attemptId?: string | undefined;
|
|
786
|
+
readonly ownerUserId: string;
|
|
787
|
+
readonly harnessId: string;
|
|
788
|
+
readonly mode: "interactive" | "one-shot";
|
|
789
|
+
readonly status: "cancelled" | "completed" | "failed" | "queued" | "running";
|
|
790
|
+
readonly prompt?: string | undefined;
|
|
791
|
+
readonly exitCode?: number | undefined;
|
|
792
|
+
readonly errorMessage?: string | undefined;
|
|
793
|
+
readonly startedAt?: string | undefined;
|
|
794
|
+
readonly finishedAt?: string | undefined;
|
|
795
|
+
readonly createdAt: string;
|
|
796
|
+
readonly updatedAt: string;
|
|
797
|
+
}, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceConflictError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError), [Mode] extends ["response-only"] ? never : never>;
|
|
719
798
|
readonly getWorkspace: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
|
|
720
799
|
readonly params: {
|
|
721
800
|
readonly workspaceId: string;
|
|
@@ -1066,6 +1145,56 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
|
|
|
1066
1145
|
readonly syncedAt: string;
|
|
1067
1146
|
}, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("@sealant/api-contracts").GitHubForbiddenError | import("@sealant/api-contracts").GitHubInternalServerError | import("@sealant/api-contracts").GitHubNotFoundError | import("@sealant/api-contracts").GitHubServiceUnavailableError | import("effect/Schema").SchemaError), [Mode] extends ["response-only"] ? never : never>;
|
|
1068
1147
|
};
|
|
1148
|
+
readonly inference: {
|
|
1149
|
+
readonly respond: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
|
|
1150
|
+
readonly payload: {
|
|
1151
|
+
readonly ownerUserId: string;
|
|
1152
|
+
readonly credentials?: {
|
|
1153
|
+
readonly profileId?: string | undefined;
|
|
1154
|
+
readonly claude?: string | undefined;
|
|
1155
|
+
readonly codex?: string | undefined;
|
|
1156
|
+
} | undefined;
|
|
1157
|
+
readonly prompt?: string | undefined;
|
|
1158
|
+
readonly system?: string | undefined;
|
|
1159
|
+
readonly model?: string | undefined;
|
|
1160
|
+
readonly maxTurns?: number | undefined;
|
|
1161
|
+
readonly tools?: readonly {
|
|
1162
|
+
readonly name: string;
|
|
1163
|
+
readonly description?: string | undefined;
|
|
1164
|
+
readonly inputSchema: unknown;
|
|
1165
|
+
}[] | undefined;
|
|
1166
|
+
readonly responseFormat?: {
|
|
1167
|
+
readonly type: "json";
|
|
1168
|
+
readonly schema?: unknown;
|
|
1169
|
+
} | undefined;
|
|
1170
|
+
readonly sessionId?: string | undefined;
|
|
1171
|
+
readonly toolResults?: readonly {
|
|
1172
|
+
readonly toolCallId: string;
|
|
1173
|
+
readonly content: string;
|
|
1174
|
+
readonly isError?: boolean | undefined;
|
|
1175
|
+
}[] | undefined;
|
|
1176
|
+
};
|
|
1177
|
+
readonly responseMode?: Mode;
|
|
1178
|
+
}) => Effect.Effect<HttpApiClient.Client.Response<{
|
|
1179
|
+
readonly sessionId: string;
|
|
1180
|
+
readonly turn: {
|
|
1181
|
+
readonly type: "text";
|
|
1182
|
+
readonly text: string;
|
|
1183
|
+
readonly json?: unknown;
|
|
1184
|
+
} | {
|
|
1185
|
+
readonly type: "toolCalls";
|
|
1186
|
+
readonly calls: readonly {
|
|
1187
|
+
readonly toolCallId: string;
|
|
1188
|
+
readonly name: string;
|
|
1189
|
+
readonly input: unknown;
|
|
1190
|
+
}[];
|
|
1191
|
+
};
|
|
1192
|
+
readonly usage?: {
|
|
1193
|
+
readonly inputTokens: number;
|
|
1194
|
+
readonly outputTokens: number;
|
|
1195
|
+
} | undefined;
|
|
1196
|
+
}, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : 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), [Mode] extends ["response-only"] ? never : never>;
|
|
1197
|
+
};
|
|
1069
1198
|
readonly packages: {
|
|
1070
1199
|
readonly resolvePackage: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
|
|
1071
1200
|
readonly query: {
|
|
@@ -1599,6 +1728,35 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
|
|
|
1599
1728
|
readonly repository: string;
|
|
1600
1729
|
readonly tag: string;
|
|
1601
1730
|
}, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : 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), [Mode] extends ["response-only"] ? never : never>;
|
|
1731
|
+
readonly execWorkspace: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
|
|
1732
|
+
readonly params: {
|
|
1733
|
+
readonly workspaceId: string;
|
|
1734
|
+
};
|
|
1735
|
+
readonly payload: {
|
|
1736
|
+
readonly ownerUserId: string;
|
|
1737
|
+
readonly commands: readonly {
|
|
1738
|
+
readonly executable: string;
|
|
1739
|
+
readonly args: readonly string[];
|
|
1740
|
+
readonly cwd?: string | undefined;
|
|
1741
|
+
}[];
|
|
1742
|
+
};
|
|
1743
|
+
readonly responseMode?: Mode;
|
|
1744
|
+
}) => Effect.Effect<HttpApiClient.Client.Response<{
|
|
1745
|
+
readonly runId: string;
|
|
1746
|
+
readonly workspaceId: string;
|
|
1747
|
+
readonly attemptId?: string | undefined;
|
|
1748
|
+
readonly ownerUserId: string;
|
|
1749
|
+
readonly harnessId: string;
|
|
1750
|
+
readonly mode: "interactive" | "one-shot";
|
|
1751
|
+
readonly status: "cancelled" | "completed" | "failed" | "queued" | "running";
|
|
1752
|
+
readonly prompt?: string | undefined;
|
|
1753
|
+
readonly exitCode?: number | undefined;
|
|
1754
|
+
readonly errorMessage?: string | undefined;
|
|
1755
|
+
readonly startedAt?: string | undefined;
|
|
1756
|
+
readonly finishedAt?: string | undefined;
|
|
1757
|
+
readonly createdAt: string;
|
|
1758
|
+
readonly updatedAt: string;
|
|
1759
|
+
}, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceConflictError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError), [Mode] extends ["response-only"] ? never : never>;
|
|
1602
1760
|
readonly getWorkspace: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
|
|
1603
1761
|
readonly params: {
|
|
1604
1762
|
readonly workspaceId: string;
|
|
@@ -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";
|
|
@@ -62,7 +62,7 @@ export declare const getWorkspaceOp: (workspaceId: string) => Effect.Effect<{
|
|
|
62
62
|
readonly startedAt?: string | undefined;
|
|
63
63
|
readonly finishedAt?: string | undefined;
|
|
64
64
|
readonly spec?: unknown;
|
|
65
|
-
},
|
|
65
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError, SealantApiClient>;
|
|
66
66
|
export declare const listWorkspacesOp: (query: {
|
|
67
67
|
readonly ownerUserId: string;
|
|
68
68
|
readonly status?: "cancelled" | "failed" | "queued" | "ready" | "running" | undefined;
|
|
@@ -98,6 +98,29 @@ export declare const listWorkspacesOp: (query: {
|
|
|
98
98
|
readonly finishedAt?: string | undefined;
|
|
99
99
|
}[];
|
|
100
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
125
|
readonly workspaceId: string;
|
|
103
126
|
readonly harnessId: string;
|
|
@@ -125,7 +148,7 @@ 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
154
|
readonly workspaceId: string;
|
|
@@ -141,7 +164,7 @@ 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
169
|
readonly workspaceId?: string | undefined;
|
|
147
170
|
readonly ownerUserId?: string | undefined;
|
|
@@ -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>;
|
|
@@ -7,6 +7,7 @@ export const createWorkspaceOp = (payload, idempotencyKey) => Effect.flatMap(Sea
|
|
|
7
7
|
}));
|
|
8
8
|
export const getWorkspaceOp = (workspaceId) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.getWorkspace({ params: { workspaceId } }));
|
|
9
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 }));
|
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/workspace.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execWorkspace } from "../effect/exec-workspace.js";
|
|
1
2
|
import { getWorkspaceOp } from "../effect/operations.js";
|
|
2
3
|
import { SealantError, SealantNotImplementedError } from "../errors.js";
|
|
3
4
|
const FAILED_STATUSES = new Set(["failed", "cancelled"]);
|
|
@@ -53,6 +54,7 @@ export const makeWorkspace = (ctx, init) => {
|
|
|
53
54
|
}
|
|
54
55
|
},
|
|
55
56
|
harness,
|
|
57
|
+
exec: (argv, options) => execWorkspace(ctx, init, argv, options),
|
|
56
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: () => {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { InferenceContinueOptions, InferenceRespondOptions, InferenceResponse } from "../types.js";
|
|
2
|
+
export declare const buildInferenceRespondRequest: (options: InferenceContinueOptions | InferenceRespondOptions, ownerUserId: string) => {
|
|
3
|
+
readonly ownerUserId: string;
|
|
4
|
+
readonly credentials?: {
|
|
5
|
+
readonly profileId?: string | undefined;
|
|
6
|
+
readonly claude?: string | undefined;
|
|
7
|
+
readonly codex?: string | undefined;
|
|
8
|
+
} | undefined;
|
|
9
|
+
readonly prompt?: string | undefined;
|
|
10
|
+
readonly system?: string | undefined;
|
|
11
|
+
readonly model?: string | undefined;
|
|
12
|
+
readonly maxTurns?: number | undefined;
|
|
13
|
+
readonly tools?: readonly {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly description?: string | undefined;
|
|
16
|
+
readonly inputSchema: unknown;
|
|
17
|
+
}[] | undefined;
|
|
18
|
+
readonly responseFormat?: {
|
|
19
|
+
readonly type: "json";
|
|
20
|
+
readonly schema?: unknown;
|
|
21
|
+
} | undefined;
|
|
22
|
+
readonly sessionId?: string | undefined;
|
|
23
|
+
readonly toolResults?: readonly {
|
|
24
|
+
readonly toolCallId: string;
|
|
25
|
+
readonly content: string;
|
|
26
|
+
readonly isError?: boolean | undefined;
|
|
27
|
+
}[] | undefined;
|
|
28
|
+
};
|
|
29
|
+
export declare const mapInferenceResponse: (wire: {
|
|
30
|
+
readonly sessionId: string;
|
|
31
|
+
readonly turn: {
|
|
32
|
+
readonly type: "text";
|
|
33
|
+
readonly text: string;
|
|
34
|
+
readonly json?: unknown;
|
|
35
|
+
} | {
|
|
36
|
+
readonly type: "toolCalls";
|
|
37
|
+
readonly calls: readonly {
|
|
38
|
+
readonly toolCallId: string;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly input: unknown;
|
|
41
|
+
}[];
|
|
42
|
+
};
|
|
43
|
+
readonly usage?: {
|
|
44
|
+
readonly inputTokens: number;
|
|
45
|
+
readonly outputTokens: number;
|
|
46
|
+
} | undefined;
|
|
47
|
+
}) => InferenceResponse;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const DEFAULT_ACCOUNT_NAME = "default";
|
|
2
|
+
const mapAccountRef = (value) => {
|
|
3
|
+
if (value === undefined || value === false) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
return value === true ? DEFAULT_ACCOUNT_NAME : value;
|
|
7
|
+
};
|
|
8
|
+
export const buildInferenceRespondRequest = (options, ownerUserId) => {
|
|
9
|
+
if ("sessionId" in options) {
|
|
10
|
+
return {
|
|
11
|
+
ownerUserId,
|
|
12
|
+
sessionId: options.sessionId,
|
|
13
|
+
toolResults: options.toolResults.map((result) => ({
|
|
14
|
+
toolCallId: result.toolCallId,
|
|
15
|
+
content: result.content,
|
|
16
|
+
...(result.isError === undefined ? {} : { isError: result.isError }),
|
|
17
|
+
})),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const claude = mapAccountRef(options.credentials.claude);
|
|
21
|
+
const codex = mapAccountRef(options.credentials.codex);
|
|
22
|
+
return {
|
|
23
|
+
ownerUserId,
|
|
24
|
+
credentials: {
|
|
25
|
+
...(options.credentials.profile === undefined
|
|
26
|
+
? {}
|
|
27
|
+
: { profileId: options.credentials.profile }),
|
|
28
|
+
...(claude === undefined ? {} : { claude }),
|
|
29
|
+
...(codex === undefined ? {} : { codex }),
|
|
30
|
+
},
|
|
31
|
+
prompt: options.prompt,
|
|
32
|
+
...(options.system === undefined ? {} : { system: options.system }),
|
|
33
|
+
...(options.model === undefined ? {} : { model: options.model }),
|
|
34
|
+
...(options.maxTurns === undefined ? {} : { maxTurns: options.maxTurns }),
|
|
35
|
+
...(options.tools === undefined
|
|
36
|
+
? {}
|
|
37
|
+
: {
|
|
38
|
+
tools: options.tools.map((tool) => ({
|
|
39
|
+
name: tool.name,
|
|
40
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
41
|
+
inputSchema: tool.inputSchema,
|
|
42
|
+
})),
|
|
43
|
+
}),
|
|
44
|
+
...(options.responseFormat === undefined
|
|
45
|
+
? {}
|
|
46
|
+
: {
|
|
47
|
+
responseFormat: {
|
|
48
|
+
type: options.responseFormat.type,
|
|
49
|
+
...(options.responseFormat.schema === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { schema: options.responseFormat.schema }),
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
export const mapInferenceResponse = (wire) => {
|
|
57
|
+
const turn = wire.turn.type === "text"
|
|
58
|
+
? {
|
|
59
|
+
type: "text",
|
|
60
|
+
text: wire.turn.text,
|
|
61
|
+
...(wire.turn.json === undefined ? {} : { json: wire.turn.json }),
|
|
62
|
+
}
|
|
63
|
+
: {
|
|
64
|
+
type: "toolCalls",
|
|
65
|
+
calls: wire.turn.calls.map((call) => ({
|
|
66
|
+
toolCallId: call.toolCallId,
|
|
67
|
+
name: call.name,
|
|
68
|
+
input: call.input,
|
|
69
|
+
})),
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
sessionId: wire.sessionId,
|
|
73
|
+
turn,
|
|
74
|
+
...(wire.usage === undefined
|
|
75
|
+
? {}
|
|
76
|
+
: { usage: { inputTokens: wire.usage.inputTokens, outputTokens: wire.usage.outputTokens } }),
|
|
77
|
+
};
|
|
78
|
+
};
|
package/dist/types.d.ts
CHANGED
|
@@ -114,6 +114,27 @@ export interface ListOptions {
|
|
|
114
114
|
readonly status?: WorkspaceStatus;
|
|
115
115
|
readonly limit?: number;
|
|
116
116
|
}
|
|
117
|
+
/** Options for a deterministic `workspace.exec()`. */
|
|
118
|
+
export interface WorkspaceExecOptions {
|
|
119
|
+
/** Working directory inside the workspace (defaults to the repository root). */
|
|
120
|
+
readonly cwd?: string;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The settled result of a deterministic `workspace.exec()`. The exit code is a check DATUM — a
|
|
124
|
+
* nonzero exit resolves normally (that's the point: `base fails` is a recorded fact, not an error).
|
|
125
|
+
* `exec()` rejects only when the execution machinery itself broke, i.e. when the exit code cannot
|
|
126
|
+
* be trusted.
|
|
127
|
+
*/
|
|
128
|
+
export interface WorkspaceExecResult {
|
|
129
|
+
/** Exit code of the executed command. */
|
|
130
|
+
readonly exitCode: number;
|
|
131
|
+
/** Everything the command wrote to stdout, decoded as UTF-8. */
|
|
132
|
+
readonly stdout: string;
|
|
133
|
+
/** Everything the command wrote to stderr, decoded as UTF-8. */
|
|
134
|
+
readonly stderr: string;
|
|
135
|
+
/** The run this exec was recorded as — its `record` is the durable, replayable evidence. */
|
|
136
|
+
readonly run: Run;
|
|
137
|
+
}
|
|
117
138
|
/** A live, disposable development environment around a real repository. */
|
|
118
139
|
export interface Workspace {
|
|
119
140
|
readonly id: string;
|
|
@@ -124,6 +145,11 @@ export interface Workspace {
|
|
|
124
145
|
ready(): Promise<this>;
|
|
125
146
|
/** Run a harness in this workspace. */
|
|
126
147
|
readonly harness: HarnessRunner;
|
|
148
|
+
/**
|
|
149
|
+
* Execute one command deterministically in the workspace — no agent in the loop — recorded into a
|
|
150
|
+
* run record like any other process. `argv[0]` is the executable, the rest its arguments.
|
|
151
|
+
*/
|
|
152
|
+
exec(argv: readonly string[], options?: WorkspaceExecOptions): Promise<WorkspaceExecResult>;
|
|
127
153
|
/** Lifecycle events as an async stream. */
|
|
128
154
|
events(): AsyncIterable<WorkspaceEvent>;
|
|
129
155
|
/** Stop the workspace now (Phase 3). */
|
|
@@ -185,13 +211,171 @@ export interface RunArtifacts {
|
|
|
185
211
|
list(): Promise<readonly ArtifactRef[]>;
|
|
186
212
|
get(name: string): Promise<Uint8Array>;
|
|
187
213
|
}
|
|
188
|
-
/**
|
|
189
|
-
export interface
|
|
214
|
+
/** The runtime daemon's lifecycle state changed. `state` is a numeric `RuntimeState`. */
|
|
215
|
+
export interface RuntimeStateChangedEvent {
|
|
216
|
+
readonly state: number;
|
|
217
|
+
readonly reason?: string | undefined;
|
|
218
|
+
}
|
|
219
|
+
/** Periodic runtime liveness signal. `state` is a numeric `RuntimeState`. */
|
|
220
|
+
export interface RuntimeHeartbeatEvent {
|
|
221
|
+
readonly state: number;
|
|
222
|
+
}
|
|
223
|
+
/** A supervised process began executing. */
|
|
224
|
+
export interface ProcessStartedEvent {
|
|
225
|
+
readonly pid: number;
|
|
226
|
+
readonly pgid: number;
|
|
227
|
+
readonly pidfd: boolean;
|
|
228
|
+
readonly executable: string;
|
|
229
|
+
readonly args: readonly string[];
|
|
230
|
+
readonly cwd: string;
|
|
231
|
+
/** Wall clock at start, microseconds (decimal string). */
|
|
232
|
+
readonly startedAt: string;
|
|
233
|
+
}
|
|
234
|
+
/** A supervised process ended. `reason` is a numeric `ExitReason`. */
|
|
235
|
+
export interface ProcessExitedEvent {
|
|
236
|
+
readonly exitCode?: number | undefined;
|
|
237
|
+
readonly signal?: number | undefined;
|
|
238
|
+
readonly reason: number;
|
|
239
|
+
/** Wall-clock duration, microseconds (decimal string). */
|
|
240
|
+
readonly durationMicros: string;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* A run of process output. Raw bytes live in the artifact store (fetch byte-exact text via
|
|
244
|
+
* `record.scrollback()`); the event carries counts and a content hash. `stream` is a numeric
|
|
245
|
+
* `StreamKind` (stdout = 2, stderr = 3).
|
|
246
|
+
*/
|
|
247
|
+
export interface IoChunkEvent {
|
|
248
|
+
readonly stream: number;
|
|
249
|
+
readonly byteCount: string;
|
|
250
|
+
readonly streamOffset: string;
|
|
251
|
+
readonly contentAlgo?: string | undefined;
|
|
252
|
+
readonly contentHash?: string | undefined;
|
|
253
|
+
readonly transform?: {
|
|
254
|
+
readonly redacted: boolean;
|
|
255
|
+
readonly truncated: boolean;
|
|
256
|
+
readonly coalesced: boolean;
|
|
257
|
+
readonly originalByteCount?: string | undefined;
|
|
258
|
+
} | undefined;
|
|
259
|
+
}
|
|
260
|
+
/** The runtime dropped events under pressure. `priority` is a numeric `EventPriority`. */
|
|
261
|
+
export interface TelemetryDroppedEvent {
|
|
262
|
+
readonly reason: string;
|
|
263
|
+
readonly count: string;
|
|
264
|
+
readonly priority: number;
|
|
265
|
+
}
|
|
266
|
+
/** Filesystem entry metadata attached to a change. `fileType` is a numeric `FileType`. */
|
|
267
|
+
export interface FileEntryData {
|
|
268
|
+
readonly path: string;
|
|
269
|
+
readonly fileType: number;
|
|
270
|
+
readonly size: string;
|
|
271
|
+
readonly mtimeMicros: string;
|
|
272
|
+
readonly mode: number;
|
|
273
|
+
readonly hash?: string | undefined;
|
|
274
|
+
readonly symlinkTarget?: string | undefined;
|
|
275
|
+
}
|
|
276
|
+
/** A watched file changed. `kind` is a numeric `FileChangeKind`. */
|
|
277
|
+
export interface FileChangeEvent {
|
|
278
|
+
readonly kind: number;
|
|
279
|
+
readonly path: string;
|
|
280
|
+
readonly renameFrom?: string | undefined;
|
|
281
|
+
readonly entry?: FileEntryData | undefined;
|
|
282
|
+
readonly certain: boolean;
|
|
283
|
+
}
|
|
284
|
+
/** The file watcher overflowed — changes under `root` may have been missed. */
|
|
285
|
+
export interface FileWatchOverflowEvent {
|
|
286
|
+
readonly root: string;
|
|
287
|
+
}
|
|
288
|
+
/** A filesystem snapshot pass finished. */
|
|
289
|
+
export interface FileSnapshotCompletedEvent {
|
|
290
|
+
readonly root: string;
|
|
291
|
+
readonly fileCount: string;
|
|
292
|
+
}
|
|
293
|
+
/** Aggregate before/after diff counts became available. */
|
|
294
|
+
export interface FileDiffAvailableEvent {
|
|
295
|
+
readonly added: string;
|
|
296
|
+
readonly modified: string;
|
|
297
|
+
readonly deleted: string;
|
|
298
|
+
readonly renamed: string;
|
|
299
|
+
}
|
|
300
|
+
/** An outbound network request the run made. `scheme` is a numeric `NetworkScheme`. */
|
|
301
|
+
export interface NetworkRequestEvent {
|
|
302
|
+
readonly scheme: number;
|
|
303
|
+
readonly method?: string | undefined;
|
|
304
|
+
readonly host: string;
|
|
305
|
+
readonly port: number;
|
|
306
|
+
readonly path?: string | undefined;
|
|
307
|
+
readonly status?: number | undefined;
|
|
308
|
+
readonly bytesSent: string;
|
|
309
|
+
readonly bytesReceived: string;
|
|
310
|
+
readonly durationMicros: string;
|
|
311
|
+
}
|
|
312
|
+
/** A network source the run touched — the raw material of a "sources the agent opened" trail. */
|
|
313
|
+
export interface NetworkSourceObservedEvent {
|
|
314
|
+
readonly host: string;
|
|
315
|
+
readonly resolvedIps: readonly string[];
|
|
316
|
+
readonly port: number;
|
|
317
|
+
readonly scheme?: number | undefined;
|
|
318
|
+
readonly method?: string | undefined;
|
|
319
|
+
readonly path?: string | undefined;
|
|
320
|
+
readonly status?: number | undefined;
|
|
321
|
+
}
|
|
322
|
+
/** Fields shared by every timeline entry, independent of its kind. */
|
|
323
|
+
export interface TimelineEntryBase {
|
|
190
324
|
readonly sequence: bigint;
|
|
191
|
-
readonly kind: string;
|
|
192
325
|
readonly occurredAt: string;
|
|
193
|
-
|
|
326
|
+
/** One-line human summary of the event. */
|
|
327
|
+
readonly summary: string;
|
|
328
|
+
/** Correlation id of the producing process, when attributable. */
|
|
329
|
+
readonly processId?: string | undefined;
|
|
194
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* A single ordered entry in the execution record's timeline, DISCRIMINATED by `kind`: switch on it
|
|
333
|
+
* and `data` narrows to the event's typed payload. The `"unknown"` case is the forward-compatibility
|
|
334
|
+
* path — it carries kinds newer than this SDK (or payloads that failed their schema) with the wire
|
|
335
|
+
* kind preserved in `rawKind` and the payload verbatim in `data`.
|
|
336
|
+
*/
|
|
337
|
+
export type TimelineEntry = (TimelineEntryBase & {
|
|
338
|
+
readonly kind: "runtimeStateChanged";
|
|
339
|
+
readonly data: RuntimeStateChangedEvent;
|
|
340
|
+
}) | (TimelineEntryBase & {
|
|
341
|
+
readonly kind: "runtimeHeartbeat";
|
|
342
|
+
readonly data: RuntimeHeartbeatEvent;
|
|
343
|
+
}) | (TimelineEntryBase & {
|
|
344
|
+
readonly kind: "processStarted";
|
|
345
|
+
readonly data: ProcessStartedEvent;
|
|
346
|
+
}) | (TimelineEntryBase & {
|
|
347
|
+
readonly kind: "processExited";
|
|
348
|
+
readonly data: ProcessExitedEvent;
|
|
349
|
+
}) | (TimelineEntryBase & {
|
|
350
|
+
readonly kind: "ioChunk";
|
|
351
|
+
readonly data: IoChunkEvent;
|
|
352
|
+
}) | (TimelineEntryBase & {
|
|
353
|
+
readonly kind: "telemetryDropped";
|
|
354
|
+
readonly data: TelemetryDroppedEvent;
|
|
355
|
+
}) | (TimelineEntryBase & {
|
|
356
|
+
readonly kind: "fileChange";
|
|
357
|
+
readonly data: FileChangeEvent;
|
|
358
|
+
}) | (TimelineEntryBase & {
|
|
359
|
+
readonly kind: "fileWatchOverflow";
|
|
360
|
+
readonly data: FileWatchOverflowEvent;
|
|
361
|
+
}) | (TimelineEntryBase & {
|
|
362
|
+
readonly kind: "fileSnapshotCompleted";
|
|
363
|
+
readonly data: FileSnapshotCompletedEvent;
|
|
364
|
+
}) | (TimelineEntryBase & {
|
|
365
|
+
readonly kind: "fileDiffAvailable";
|
|
366
|
+
readonly data: FileDiffAvailableEvent;
|
|
367
|
+
}) | (TimelineEntryBase & {
|
|
368
|
+
readonly kind: "networkRequest";
|
|
369
|
+
readonly data: NetworkRequestEvent;
|
|
370
|
+
}) | (TimelineEntryBase & {
|
|
371
|
+
readonly kind: "networkSourceObserved";
|
|
372
|
+
readonly data: NetworkSourceObservedEvent;
|
|
373
|
+
}) | (TimelineEntryBase & {
|
|
374
|
+
readonly kind: "unknown";
|
|
375
|
+
/** The kind as received on the wire — set when this SDK version doesn't model it. */
|
|
376
|
+
readonly rawKind: string;
|
|
377
|
+
readonly data: unknown;
|
|
378
|
+
});
|
|
195
379
|
/** A re-fold of the record up to some point — scrubable by sequence. */
|
|
196
380
|
export interface RunReplay {
|
|
197
381
|
readonly entries: readonly TimelineEntry[];
|
|
@@ -289,3 +473,90 @@ export interface InteractiveSession {
|
|
|
289
473
|
output(): AsyncIterable<Uint8Array>;
|
|
290
474
|
close(): Promise<void>;
|
|
291
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* Connected-account selection for inference — the same reference shape as workspace creation,
|
|
478
|
+
* minus GitHub (not a model provider). `true` means "my default account"; a string names one.
|
|
479
|
+
* Only claude accounts are supported today; a codex selection is rejected until Codex inference
|
|
480
|
+
* ships. SECURITY: only account references cross this surface — never token material.
|
|
481
|
+
*/
|
|
482
|
+
export interface InferenceCredentialsOptions {
|
|
483
|
+
/** Profile id whose claude binding applies when `claude` is not set explicitly. */
|
|
484
|
+
readonly profile?: string;
|
|
485
|
+
/** `true` for the caller's default claude account, or a string naming a specific one. */
|
|
486
|
+
readonly claude?: boolean | string;
|
|
487
|
+
/** Reserved — rejected until Codex inference ships. */
|
|
488
|
+
readonly codex?: boolean | string;
|
|
489
|
+
}
|
|
490
|
+
/** A caller-defined tool the model may call. `inputSchema` is a JSON Schema object, verbatim. */
|
|
491
|
+
export interface InferenceToolDefinition {
|
|
492
|
+
readonly name: string;
|
|
493
|
+
readonly description?: string;
|
|
494
|
+
readonly inputSchema: unknown;
|
|
495
|
+
}
|
|
496
|
+
/** A tool call the model made. Execute it YOUR side, then respond with an `InferenceToolResult`. */
|
|
497
|
+
export interface InferenceToolCall {
|
|
498
|
+
readonly toolCallId: string;
|
|
499
|
+
readonly name: string;
|
|
500
|
+
readonly input: unknown;
|
|
501
|
+
}
|
|
502
|
+
/** Your result for one tool call, keyed by its `toolCallId`. */
|
|
503
|
+
export interface InferenceToolResult {
|
|
504
|
+
readonly toolCallId: string;
|
|
505
|
+
readonly content: string;
|
|
506
|
+
readonly isError?: boolean;
|
|
507
|
+
}
|
|
508
|
+
/** The assistant turn: the final text (with parsed `json` when requested) or pending tool calls. */
|
|
509
|
+
export type InferenceTurn = {
|
|
510
|
+
readonly type: "text";
|
|
511
|
+
readonly text: string;
|
|
512
|
+
readonly json?: unknown;
|
|
513
|
+
} | {
|
|
514
|
+
readonly type: "toolCalls";
|
|
515
|
+
readonly calls: readonly InferenceToolCall[];
|
|
516
|
+
};
|
|
517
|
+
export interface InferenceUsage {
|
|
518
|
+
readonly inputTokens: number;
|
|
519
|
+
readonly outputTokens: number;
|
|
520
|
+
}
|
|
521
|
+
export interface InferenceResponse {
|
|
522
|
+
/** Continuation handle for the tool loop (held in memory server-side; expires after idle). */
|
|
523
|
+
readonly sessionId: string;
|
|
524
|
+
readonly turn: InferenceTurn;
|
|
525
|
+
/** Usage for the exchange, present on the final text turn. */
|
|
526
|
+
readonly usage?: InferenceUsage;
|
|
527
|
+
}
|
|
528
|
+
/** Starts a new inference exchange on a connected account. */
|
|
529
|
+
export interface InferenceRespondOptions {
|
|
530
|
+
readonly prompt: string;
|
|
531
|
+
readonly system?: string;
|
|
532
|
+
readonly model?: string;
|
|
533
|
+
/** Upper bound on agentic turns within the exchange (server default 16). */
|
|
534
|
+
readonly maxTurns?: number;
|
|
535
|
+
readonly tools?: readonly InferenceToolDefinition[];
|
|
536
|
+
/** Structured output: reply as JSON (schema-constrained when `schema` is given). */
|
|
537
|
+
readonly responseFormat?: {
|
|
538
|
+
readonly type: "json";
|
|
539
|
+
readonly schema?: unknown;
|
|
540
|
+
};
|
|
541
|
+
readonly credentials: InferenceCredentialsOptions;
|
|
542
|
+
}
|
|
543
|
+
/** Continues an exchange by posting the results of the previous turn's tool calls. */
|
|
544
|
+
export interface InferenceContinueOptions {
|
|
545
|
+
readonly sessionId: string;
|
|
546
|
+
readonly toolResults: readonly InferenceToolResult[];
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Inference on connected accounts. The model call runs SERVER-SIDE through the official agent SDKs
|
|
550
|
+
* on the resolved account's credential (never raw model-API calls); the tool loop is CALLER-
|
|
551
|
+
* EXECUTED — a `toolCalls` turn parks server-side until you `respond()` with the results:
|
|
552
|
+
*
|
|
553
|
+
* let response = await sealant.inference.respond({ prompt, tools, credentials: { claude: true } })
|
|
554
|
+
* while (response.turn.type === "toolCalls") {
|
|
555
|
+
* const toolResults = await runTools(response.turn.calls)
|
|
556
|
+
* response = await sealant.inference.respond({ sessionId: response.sessionId, toolResults })
|
|
557
|
+
* }
|
|
558
|
+
* response.turn.text
|
|
559
|
+
*/
|
|
560
|
+
export interface InferenceNamespace {
|
|
561
|
+
respond(options: InferenceRespondOptions | InferenceContinueOptions): Promise<InferenceResponse>;
|
|
562
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealant/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The fluent public SDK for Sealant — create a workspace, run a harness, replay the record.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
".": {
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
18
|
"import": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./effect": {
|
|
21
|
+
"types": "./dist/effect/index.d.ts",
|
|
22
|
+
"import": "./dist/effect/index.js"
|
|
19
23
|
}
|
|
20
24
|
},
|
|
21
25
|
"publishConfig": {
|
|
@@ -23,7 +27,7 @@
|
|
|
23
27
|
},
|
|
24
28
|
"dependencies": {
|
|
25
29
|
"effect": "^4.0.0-beta.85",
|
|
26
|
-
"@sealant/api-contracts": "^0.
|
|
30
|
+
"@sealant/api-contracts": "^0.5.0"
|
|
27
31
|
},
|
|
28
32
|
"devDependencies": {
|
|
29
33
|
"@effect/vitest": "^4.0.0-beta.85",
|