@aexhq/sdk 0.56.0 → 0.57.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 CHANGED
@@ -1,40 +1,105 @@
1
- # @aexhq/sdk
1
+ # `@aexhq/sdk`
2
+
3
+ TypeScript client for durable Aex sessions.
2
4
 
3
5
  ```ts
4
- import { Aex } from "@aexhq/sdk";
6
+ import { Aex, tool } from "@aexhq/sdk";
5
7
  import { z } from "zod";
6
8
 
7
- const aex = new Aex({ apiKey: "aex_sk_..." });
8
- const session = await aex.sessions.create({
9
- model: {
10
- provider: "anthropic",
11
- name: "claude-sonnet-5",
12
- apiKey: "sk-ant-...",
9
+ const lookup = tool(
10
+ z.object({ id: z.string() }),
11
+ async function lookup({ id }) {
12
+ return database.customers.find(id);
13
13
  },
14
+ ).client();
15
+
16
+ const aex = new Aex({
17
+ apiKey: process.env.AEX_API_KEY!,
18
+ client: { id: "customer-api" },
14
19
  });
20
+ const model = {
21
+ provider: "openai" as const,
22
+ name: "gpt-5.4",
23
+ apiKey: process.env.OPENAI_API_KEY!,
24
+ };
25
+ const session = await aex.sessions.create({ model, tools: [lookup] });
15
26
 
16
27
  const result = await session.send("Answer the question.", {
17
28
  output: z.object({ answer: z.string() }),
18
29
  });
19
30
  ```
20
31
 
21
- `send()` returns text by default. Passing `output` returns a normal typed Promise and uses
22
- `https://api.aex.dev` by default. Zod schemas must be representable as JSON Schema; process-local
23
- custom refinements and transforms fail before any model work is admitted.
32
+ `send()` returns text by default. Passing `output` returns a typed Promise. The client uses
33
+ `https://api.aex.dev` by default; the model `baseUrl` is independent of the Aex API origin.
34
+ `send()` resolves only the durable winning assistant message after provider recovery. The lower-
35
+ level `session.events()` stream is raw and attempt-aware: provisional frames have no durable cursor
36
+ and can later be superseded, so live renderers must key them by `attempt_id` and process
37
+ `model.attempt_superseded` instead of concatenating attempts.
38
+ The SDK always generates and reuses an `Idempotency-Key` for root/child creation and messages; pass
39
+ `idempotencyKey` explicitly when the application must recover the same operation across its own
40
+ process restart. Raw hosted REST calls must supply that header for those operations.
41
+
42
+ Hosted alpha has one managed-compute shape, `1gb` (0.5 vCPU and 1 GiB), so the SDK exposes no shape
43
+ selector. Child sessions and managed sandboxes inherit the root's physical seal.
24
44
 
25
- Sessions start with no model tools. Configure the exact capabilities at creation:
45
+ Tools are immutable once the session is created. `.client()` keeps closures in this application;
46
+ `.server(import.meta.url)` bundles a module whose default export is the completed Tool value for the
47
+ session's shared managed computer. One customer-app socket is shared across sessions; call
48
+ `aex.close()` during graceful process shutdown to stop it and interrupt process-local work. A
49
+ closed `Aex` instance cannot create another session. The
50
+ default `.client()` registration
51
+ is derived from its contract. If one application intentionally has different closures with the
52
+ same name and schemas, give each a stable `.client({ registration: "..." })`; a live runner rejects
53
+ a registration collision instead of invoking the wrong closure. Sessions grant no execution
54
+ capabilities by default. Add only the official capabilities needed. Aex's reserved output protocol is
55
+ present but inert unless a particular `send({ output })` request arms it:
56
+
57
+ Hosted `.server()` bindings use distinct generation-lifetime unprivileged users, separate from the
58
+ ordinary shell, while sharing the workspace through a group. Declared environment secrets are not
59
+ written by Aex to the workspace, arguments, results, or logs. This blocks ordinary sibling reads,
60
+ not guest-root compromise or a Tool deliberately writing the value. Use `.client()` or an external
61
+ service for that stronger boundary; local mode is intentionally unsandboxed.
26
62
 
27
63
  ```ts
28
- import { bash, edit, read, subagents, write } from "@aexhq/tools";
64
+ import { bash, edit, read, sandbox, storage, subagents, write } from "@aexhq/tools";
29
65
 
30
66
  const session = await aex.sessions.create({
31
- model: {
32
- provider: "anthropic",
33
- name: "claude-sonnet-5",
34
- apiKey: "sk-ant-...",
35
- },
36
- tools: [bash(), read(), write(), edit(), subagents()],
67
+ model,
68
+ tools: [bash(), read(), write(), edit(), storage(), sandbox(), subagents()],
69
+ network: { outbound: "none" },
70
+ });
71
+ ```
72
+
73
+ Omitting `tools` and passing `tools: []` are equivalent. Zod schemas must be representable as JSON
74
+ Schema; process-local refinements and transforms fail before model work starts. Temporary files are
75
+ available through `session.sandbox.files`; durable objects use `session.storage`. Large transfers
76
+ automatically bypass the Brain actor. Durable direct children use `session.children`.
77
+ Sandbox file operations honor guest permissions; they do not bypass a `.server()` binding's
78
+ deliberate mode-0600 files. The binding must explicitly export or relax those permissions.
79
+
80
+ Buffered `upload()` and `download()` are convenient for small objects. Large transfers can stay
81
+ O(1)-heap with `downloadStream()` and a replayable declared source:
82
+
83
+ ```ts
84
+ import { createReadStream } from "node:fs";
85
+ import { Readable } from "node:stream";
86
+
87
+ await session.storage.upload("inputs/archive.tar", {
88
+ bytes: stat.size,
89
+ sha256: digest,
90
+ stream: () => Readable.toWeb(createReadStream(filename)),
37
91
  });
92
+ const body = await session.storage.downloadStream("inputs/archive.tar");
38
93
  ```
39
94
 
40
- Omitting `tools` and passing `tools: []` are equivalent.
95
+ The same methods are available on `session.sandbox.files`; the SDK enforces upload length, ticket
96
+ ceilings, abort signals, and the downloaded object's exact metadata length while bytes bypass
97
+ Brain. Direct large sandbox transfers are a happy-path convenience: the SDK does not automatically
98
+ retry an ambiguous completion or recover a ticket after Brain restart or expiry. Inspect the
99
+ generation and path, then prepare a fresh transfer. Put bytes in `session.storage` and copy them to
100
+ or from the sandbox when the transfer itself must be recovery-safe.
101
+
102
+ `await session.end()` closes work but retains journal and storage. Both deletion modes first make
103
+ the same short, durable deletion-job request. `await session.delete()` then polls the status
104
+ resource for confirmed removal; `{ queue: true }` returns after acceptance. No HTTP request stays
105
+ open while sandbox and storage cleanup runs.
package/dist/index.d.ts CHANGED
@@ -1,18 +1,28 @@
1
1
  import { Sessions } from "./session.js";
2
2
  import type { Fetch } from "./transport.js";
3
+ import type { WebSocketFactory } from "@aexhq/brain";
3
4
  export { AbortError, AexError, OutputRefusalError, OutputSchemaError, OutputValidationError, SessionError, } from "./errors.js";
4
5
  export type { AexErrorOptions, OutputValidationIssue } from "./errors.js";
5
6
  export { Session, Sessions, } from "./session.js";
6
- export type { CreateSessionOptions, ListSessionsOptions, McpServerOptions, ModelSummary, ModelOptions, OutputOptions, RequestOptions, SessionInput, SessionList, SessionSummary, } from "./session.js";
7
+ export type { CreateSessionOptions, ListSessionsOptions, ModelSummary, ModelOptions, OutputOptions, RequestOptions, SessionInput, SessionList, SessionSummary, } from "./session.js";
8
+ export { SandboxFiles, SessionChild, SessionChildren, SessionSandbox, SessionStorage, } from "./resources.js";
9
+ export type { BinarySource, ChildSummary, IdempotentOperationOptions, OperationOptions, PageOptions, SandboxFile, SandboxFileOptions, SandboxFilePage, SandboxFilePageOptions, SandboxStatus, StorageObject, StoragePage, StreamingUploadSource, UploadSource, } from "./resources.js";
7
10
  export type { EventOptions } from "./transport.js";
8
- export { defineIntrinsicTool, definePreinstalledTool, defineServerTool, defineTool, } from "@aexhq/brain";
9
- export type { DefineIntrinsicToolOptions, DefinePreinstalledToolOptions, DefineServerToolOptions, DefineToolOptions, Tool, ToolContext, ToolHandler, } from "@aexhq/brain";
11
+ export { tool } from "@aexhq/brain";
12
+ export type { ClientToolOptions, Tool, ToolBuilder, ToolContract, ToolContext, ToolHandler, ServerToolOptions, NetworkDestination, NetworkPolicy, WebSocketFactory, } from "@aexhq/brain";
10
13
  export interface AexOptions {
11
14
  apiKey: string;
12
15
  baseUrl?: string;
13
16
  fetch?: Fetch;
17
+ webSocketFactory?: WebSocketFactory;
18
+ /** Stable tenant-scoped identity for this exact customer-application runner. */
19
+ client?: {
20
+ id: string;
21
+ };
14
22
  }
15
23
  export declare class Aex {
16
24
  readonly sessions: Sessions;
17
25
  constructor(options: AexOptions);
26
+ /** Stop customer-app execution permanently; this Aex instance cannot create another session. */
27
+ close(): void;
18
28
  }
package/dist/index.js CHANGED
@@ -2,7 +2,8 @@ import { Sessions } from "./session.js";
2
2
  import { Transport } from "./transport.js";
3
3
  export { AbortError, AexError, OutputRefusalError, OutputSchemaError, OutputValidationError, SessionError, } from "./errors.js";
4
4
  export { Session, Sessions, } from "./session.js";
5
- export { defineIntrinsicTool, definePreinstalledTool, defineServerTool, defineTool, } from "@aexhq/brain";
5
+ export { SandboxFiles, SessionChild, SessionChildren, SessionSandbox, SessionStorage, } from "./resources.js";
6
+ export { tool } from "@aexhq/brain";
6
7
  const DEFAULT_API_URL = "https://api.aex.dev";
7
8
  export class Aex {
8
9
  sessions;
@@ -13,7 +14,19 @@ export class Aex {
13
14
  if (fetchImplementation === undefined) {
14
15
  throw new TypeError("This runtime does not provide fetch; pass a fetch implementation to Aex");
15
16
  }
17
+ if (options.client !== undefined &&
18
+ !/^[A-Za-z0-9_.:-]{1,128}$/u.test(options.client.id)) {
19
+ throw new TypeError("Aex client.id must contain 1 through 128 letters, digits, dots, colons, underscores, or hyphens");
20
+ }
16
21
  const transport = new Transport(options.apiKey, options.baseUrl ?? DEFAULT_API_URL, fetchImplementation);
17
- this.sessions = new Sessions(transport);
22
+ const webSocketFactory = options.webSocketFactory ??
23
+ (globalThis.WebSocket === undefined
24
+ ? undefined
25
+ : (request) => new globalThis.WebSocket(request.url, request.protocol));
26
+ this.sessions = new Sessions(transport, webSocketFactory, options.client?.id);
27
+ }
28
+ /** Stop customer-app execution permanently; this Aex instance cannot create another session. */
29
+ close() {
30
+ this.sessions.close();
18
31
  }
19
32
  }
@@ -0,0 +1,157 @@
1
+ import { SessionChildren as BrainChildren, SessionSandbox as BrainSandbox } from "@aexhq/brain";
2
+ import type { SandboxFileEntry as BrainSandboxFileEntry, SandboxStatus as BrainSandboxStatus } from "@aexhq/brain";
3
+ import type { Event, Session as BrainSession } from "@aexhq/brain/session";
4
+ import type { EventOptions } from "./transport.js";
5
+ import { Transport } from "./transport.js";
6
+ export type BinarySource = string | ArrayBuffer | ArrayBufferView | Blob;
7
+ /** A replayable, length- and digest-declared source for O(1)-heap large uploads. */
8
+ export interface StreamingUploadSource {
9
+ readonly bytes: number;
10
+ readonly sha256: string;
11
+ stream(): ReadableStream<Uint8Array>;
12
+ }
13
+ export type UploadSource = BinarySource | StreamingUploadSource;
14
+ export interface OperationOptions {
15
+ signal?: AbortSignal;
16
+ }
17
+ export interface IdempotentOperationOptions extends OperationOptions {
18
+ /** Stable retry identity; the SDK generates one when omitted. */
19
+ idempotencyKey?: string;
20
+ }
21
+ export interface PageOptions extends OperationOptions {
22
+ cursor?: string;
23
+ limit?: number;
24
+ }
25
+ export interface SandboxStatus {
26
+ state: BrainSandboxStatus["state"];
27
+ generation?: string;
28
+ reason?: string;
29
+ changedAt?: string;
30
+ expiresAt?: string;
31
+ }
32
+ export interface SandboxFile {
33
+ path: string;
34
+ kind: BrainSandboxFileEntry["kind"];
35
+ bytes: number;
36
+ sha256?: string;
37
+ modifiedAt: string;
38
+ }
39
+ export interface SandboxFilePage {
40
+ data: SandboxFile[];
41
+ hasMore: boolean;
42
+ nextCursor?: string;
43
+ generation: string;
44
+ }
45
+ export interface SandboxFileOptions extends OperationOptions {
46
+ generation: string;
47
+ }
48
+ export interface SandboxFilePageOptions extends SandboxFileOptions, PageOptions {
49
+ }
50
+ export declare class SandboxFiles {
51
+ #private;
52
+ constructor(inner: BrainSandbox["files"], transport: Transport);
53
+ list(path: string, options: SandboxFilePageOptions): Promise<SandboxFilePage>;
54
+ stat(path: string, options: SandboxFileOptions): Promise<SandboxFile>;
55
+ download(path: string, options: SandboxFileOptions): Promise<Uint8Array>;
56
+ downloadStream(path: string, options: SandboxFileOptions): Promise<ReadableStream<Uint8Array>>;
57
+ upload(path: string, source: UploadSource, options: SandboxFileOptions & {
58
+ overwrite?: boolean;
59
+ }): Promise<SandboxFile>;
60
+ find(input: {
61
+ path: string;
62
+ glob: string;
63
+ }, options: SandboxFilePageOptions): Promise<SandboxFilePage>;
64
+ grep(input: {
65
+ path: string;
66
+ query: string;
67
+ }, options: SandboxFilePageOptions): Promise<SandboxFilePage>;
68
+ }
69
+ export declare class SessionSandbox {
70
+ #private;
71
+ readonly files: SandboxFiles;
72
+ constructor(transport: Transport, sessionId: string);
73
+ status(options?: OperationOptions): Promise<SandboxStatus>;
74
+ create(options?: OperationOptions): Promise<SandboxStatus>;
75
+ }
76
+ export interface StorageObject {
77
+ key: string;
78
+ bytes: number;
79
+ sha256: string;
80
+ contentType?: string;
81
+ createdAt: string;
82
+ updatedAt: string;
83
+ }
84
+ export interface StoragePage {
85
+ data: StorageObject[];
86
+ hasMore: boolean;
87
+ nextCursor?: string;
88
+ }
89
+ export declare class SessionStorage {
90
+ #private;
91
+ constructor(transport: Transport, sessionId: string);
92
+ list(options?: PageOptions & {
93
+ prefix?: string;
94
+ }): Promise<StoragePage>;
95
+ stat(key: string, options?: OperationOptions): Promise<StorageObject>;
96
+ download(key: string, options?: OperationOptions): Promise<Uint8Array>;
97
+ downloadStream(key: string, options?: OperationOptions): Promise<ReadableStream<Uint8Array>>;
98
+ upload(key: string, source: UploadSource, options?: OperationOptions & {
99
+ contentType?: string;
100
+ overwrite?: boolean;
101
+ }): Promise<StorageObject>;
102
+ delete(key: string, options?: OperationOptions): Promise<void>;
103
+ copyFromSandbox(input: {
104
+ key: string;
105
+ path: string;
106
+ sandboxGeneration: string;
107
+ overwrite?: boolean;
108
+ }, options?: OperationOptions): Promise<StorageObject>;
109
+ copyToSandbox(input: {
110
+ key: string;
111
+ path: string;
112
+ sandboxGeneration: string;
113
+ overwrite?: boolean;
114
+ }, options?: OperationOptions): Promise<SandboxFile>;
115
+ }
116
+ export interface ChildSummary {
117
+ id: string;
118
+ parentId: string;
119
+ rootId: string;
120
+ name?: string;
121
+ depth: number;
122
+ state: BrainSession["state"];
123
+ turnState: BrainSession["turn_state"];
124
+ turnPhase?: string;
125
+ shape: BrainSession["shape"];
126
+ createdAt: string;
127
+ updatedAt: string;
128
+ }
129
+ export declare class SessionChild {
130
+ #private;
131
+ constructor(inner: ReturnType<BrainChildren["get"]>);
132
+ get id(): string;
133
+ info(options?: OperationOptions): Promise<ChildSummary>;
134
+ send(message: string, options?: IdempotentOperationOptions): Promise<void>;
135
+ followUp(message: string, options?: IdempotentOperationOptions): Promise<ChildSummary>;
136
+ wait(options?: OperationOptions & {
137
+ timeoutMs?: number;
138
+ }): Promise<ChildSummary>;
139
+ interrupt(options?: OperationOptions): Promise<ChildSummary>;
140
+ end(options?: OperationOptions): Promise<ChildSummary>;
141
+ events(options?: EventOptions): AsyncGenerator<Event>;
142
+ }
143
+ export declare class SessionChildren {
144
+ #private;
145
+ constructor(transport: Transport, sessionId: string);
146
+ create(input: {
147
+ prompt: string;
148
+ name?: string;
149
+ forkTurns?: "all" | "none" | `${number}`;
150
+ }, options?: IdempotentOperationOptions): Promise<SessionChild>;
151
+ list(options?: PageOptions): Promise<{
152
+ data: ChildSummary[];
153
+ hasMore: boolean;
154
+ nextCursor?: string;
155
+ }>;
156
+ get(childId: string): SessionChild;
157
+ }