@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 +85 -20
- package/dist/index.d.ts +13 -3
- package/dist/index.js +15 -2
- package/dist/resources.d.ts +157 -0
- package/dist/resources.js +438 -0
- package/dist/session.d.ts +43 -14
- package/dist/session.js +160 -28
- package/dist/tools.d.ts +1 -1
- package/dist/transport.d.ts +16 -7
- package/dist/transport.js +599 -51
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,40 +1,105 @@
|
|
|
1
|
-
#
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
22
|
-
`https://api.aex.dev` by default
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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,
|
|
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 {
|
|
9
|
-
export type {
|
|
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 {
|
|
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
|
-
|
|
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
|
+
}
|