@aexhq/sdk 0.57.4 → 0.58.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,105 +1,60 @@
1
- # `@aexhq/sdk`
1
+ # @aexhq/sdk
2
2
 
3
- TypeScript client for durable Aex sessions.
3
+ TypeScript SDK for Aex sessions and tool extensions.
4
4
 
5
5
  ```ts
6
- import { Aex, tool } from "@aexhq/sdk";
7
- import { z } from "zod";
6
+ import { Aex } from "@aexhq/sdk";
7
+ import { awsMicrovm } from "@aexhq/env-aws-microvm";
8
+ import { pi } from "@aexhq/loop-pi";
9
+ import { bash, read, write } from "@aexhq/tools";
8
10
 
9
- const lookup = tool(
10
- z.object({ id: z.string() }),
11
- async function lookup({ id }) {
12
- return database.customers.find(id);
13
- },
14
- ).client();
11
+ const aex = new Aex({ apiKey: process.env.AEX_API_KEY! });
12
+ const workspace = awsMicrovm();
15
13
 
16
- const aex = new Aex({
17
- apiKey: process.env.AEX_API_KEY!,
18
- client: { id: "customer-api" },
14
+ const session = await aex.sessions.create({
15
+ model: {
16
+ provider: "openai",
17
+ name: "gpt-5.4",
18
+ apiKey: process.env.OPENAI_API_KEY!,
19
+ },
20
+ loop: pi({ instructions: "Work carefully and verify changes." }),
21
+ environments: { workspace },
22
+ tools: [bash(), read(), write()],
19
23
  });
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] });
26
24
 
27
- const result = await session.send("Answer the question.", {
28
- output: z.object({ answer: z.string() }),
29
- });
25
+ console.log(await session.send("Inspect the workspace."));
30
26
  ```
31
27
 
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.
28
+ There is no default loop or environment. Every tool is bound to one declared environment before
29
+ the request is sent. An unbound tool is bound automatically only when exactly one declared
30
+ environment satisfies it; otherwise creation fails with the eligible or missing capabilities.
41
31
 
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.
32
+ ## Tools
44
33
 
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.
34
+ `tool()` creates an immutable tool extension from a Zod input schema and a handler:
62
35
 
63
36
  ```ts
64
- import { bash, edit, read, sandbox, storage, subagents, write } from "@aexhq/tools";
65
-
66
- const session = await aex.sessions.create({
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";
37
+ import { tool } from "@aexhq/sdk";
38
+ import { z } from "zod";
86
39
 
87
- await session.storage.upload("inputs/archive.tar", {
88
- bytes: stat.size,
89
- sha256: digest,
90
- stream: () => Readable.toWeb(createReadStream(filename)),
91
- });
92
- const body = await session.storage.downloadStream("inputs/archive.tar");
40
+ export default tool(
41
+ z.object({ orderId: z.string() }),
42
+ async function lookupOrder({ orderId }) {
43
+ return database.lookup(orderId);
44
+ },
45
+ )
46
+ .describe("Look up an order")
47
+ .returns(z.object({ status: z.string() }))
48
+ .needs({ env: ["ORDERS_TOKEN"], network: [{ host: "orders.example.com", port: 443 }] })
49
+ .setup(async function prepareIndex() {
50
+ await database.prepareIndex();
51
+ });
93
52
  ```
94
53
 
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.
54
+ Use `tool.bind(environmentRef)` when more than one environment is compatible. Source tools run
55
+ through an `app()` callback environment. `aex tools build` prepares computer tools with their
56
+ runtime and dependencies; tool authors do not pass `import.meta.url` or choose a provider.
101
57
 
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.
58
+ Environment references are opaque values returned by environment-extension factories. A created
59
+ session preserves their types, so `session.environment(ref)` returns that extension's typed handle.
60
+ Durable objects remain available through `session.storage`, independently of environment lifetime.
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ApiError, ApiErrorCode } from "@aexhq/brain/session";
1
+ import type { ApiError, ApiErrorCode } from "@aexhq/session-protocol/session";
2
2
  /** One Aex trusted-output validation failure, expressed as a JSON Pointer. */
3
3
  export interface OutputValidationIssue {
4
4
  path: string;
package/dist/index.d.ts CHANGED
@@ -1,24 +1,22 @@
1
1
  import { Sessions } from "./session.js";
2
2
  import type { Fetch } from "./transport.js";
3
- import type { WebSocketFactory } from "@aexhq/brain";
3
+ import type { WebSocketFactory } from "@aexhq/session-protocol";
4
4
  export { AbortError, AexError, OutputRefusalError, OutputSchemaError, OutputValidationError, SessionError, } from "./errors.js";
5
5
  export type { AexErrorOptions, OutputValidationIssue } from "./errors.js";
6
6
  export { Session, Sessions, } 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
+ export type { CreateSessionOptions, ListSessionsOptions, Loop, ModelSummary, ModelOptions, OutputOptions, RequestOptions, SessionInput, SessionList, SessionSummary, } from "./session.js";
8
+ export { SessionChild, SessionChildren, SessionStorage, } from "./resources.js";
9
+ export type { BinarySource, ChildSummary, EnvironmentFile, IdempotentOperationOptions, OperationOptions, PageOptions, StorageObject, StoragePage, StreamingUploadSource, UploadSource, } from "./resources.js";
10
10
  export type { EventOptions } from "./transport.js";
11
- export { tool } from "@aexhq/brain";
12
- export type { ClientToolOptions, Tool, ToolBuilder, ToolContract, ToolContext, ToolHandler, ServerToolOptions, NetworkDestination, NetworkPolicy, WebSocketFactory, } from "@aexhq/brain";
11
+ export { tool } from "./tools.js";
12
+ export type { BoundTool, EnvironmentMap, EnvironmentValue, PreparedArtifact, Tool, ToolContract, ToolContext, ToolHandler, ToolRequirements, ToolSelection, ToolSetupHandler, } from "./tools.js";
13
+ export type { NetworkPolicy, WebSocketFactory } from "@aexhq/session-protocol";
14
+ export type { EnvironmentRef, HandleOf } from "@aexhq/environment";
13
15
  export interface AexOptions {
14
16
  apiKey: string;
15
17
  baseUrl?: string;
16
18
  fetch?: Fetch;
17
19
  webSocketFactory?: WebSocketFactory;
18
- /** Stable tenant-scoped identity for this exact customer-application runner. */
19
- client?: {
20
- id: string;
21
- };
22
20
  }
23
21
  export declare class Aex {
24
22
  readonly sessions: Sessions;
package/dist/index.js CHANGED
@@ -2,8 +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 { SandboxFiles, SessionChild, SessionChildren, SessionSandbox, SessionStorage, } from "./resources.js";
6
- export { tool } from "@aexhq/brain";
5
+ export { SessionChild, SessionChildren, SessionStorage, } from "./resources.js";
6
+ export { tool } from "./tools.js";
7
7
  const DEFAULT_API_URL = "https://api.aex.dev";
8
8
  export class Aex {
9
9
  sessions;
@@ -14,16 +14,12 @@ export class Aex {
14
14
  if (fetchImplementation === undefined) {
15
15
  throw new TypeError("This runtime does not provide fetch; pass a fetch implementation to Aex");
16
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
- }
21
17
  const transport = new Transport(options.apiKey, options.baseUrl ?? DEFAULT_API_URL, fetchImplementation);
22
18
  const webSocketFactory = options.webSocketFactory ??
23
19
  (globalThis.WebSocket === undefined
24
20
  ? undefined
25
21
  : (request) => new globalThis.WebSocket(request.url, request.protocol));
26
- this.sessions = new Sessions(transport, webSocketFactory, options.client?.id);
22
+ this.sessions = new Sessions(transport, webSocketFactory);
27
23
  }
28
24
  /** Stop customer-app execution permanently; this Aex instance cannot create another session. */
29
25
  close() {
@@ -0,0 +1,2 @@
1
+ export { withPreparedArtifact } from "./tools.js";
2
+ export type { PreparedArtifact, Tool } from "./tools.js";
@@ -0,0 +1 @@
1
+ export { withPreparedArtifact } from "./tools.js";
@@ -1,6 +1,7 @@
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";
1
+ import { SessionChildren as BrainChildren } from "@aexhq/session-protocol";
2
+ import type { EnvironmentFileEntry as BrainEnvironmentFileEntry } from "@aexhq/session-protocol";
3
+ import type { Event, Session as BrainSession } from "@aexhq/session-protocol/session";
4
+ import type { EnvironmentRef } from "@aexhq/environment";
4
5
  import type { EventOptions } from "./transport.js";
5
6
  import { Transport } from "./transport.js";
6
7
  export type BinarySource = string | ArrayBuffer | ArrayBufferView | Blob;
@@ -22,57 +23,13 @@ export interface PageOptions extends OperationOptions {
22
23
  cursor?: string;
23
24
  limit?: number;
24
25
  }
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 {
26
+ export interface EnvironmentFile {
33
27
  path: string;
34
- kind: BrainSandboxFileEntry["kind"];
28
+ kind: BrainEnvironmentFileEntry["kind"];
35
29
  bytes: number;
36
30
  sha256?: string;
37
31
  modifiedAt: string;
38
32
  }
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
33
  export interface StorageObject {
77
34
  key: string;
78
35
  bytes: number;
@@ -88,7 +45,7 @@ export interface StoragePage {
88
45
  }
89
46
  export declare class SessionStorage {
90
47
  #private;
91
- constructor(transport: Transport, sessionId: string);
48
+ constructor(transport: Transport, sessionId: string, environmentNames?: ReadonlyMap<EnvironmentRef, string>);
92
49
  list(options?: PageOptions & {
93
50
  prefix?: string;
94
51
  }): Promise<StoragePage>;
@@ -100,18 +57,18 @@ export declare class SessionStorage {
100
57
  overwrite?: boolean;
101
58
  }): Promise<StorageObject>;
102
59
  delete(key: string, options?: OperationOptions): Promise<void>;
103
- copyFromSandbox(input: {
60
+ copyFromEnvironment(environment: EnvironmentRef, input: {
104
61
  key: string;
105
62
  path: string;
106
- sandboxGeneration: string;
63
+ generation: string;
107
64
  overwrite?: boolean;
108
65
  }, options?: OperationOptions): Promise<StorageObject>;
109
- copyToSandbox(input: {
66
+ copyToEnvironment(environment: EnvironmentRef, input: {
110
67
  key: string;
111
68
  path: string;
112
- sandboxGeneration: string;
69
+ generation: string;
113
70
  overwrite?: boolean;
114
- }, options?: OperationOptions): Promise<SandboxFile>;
71
+ }, options?: OperationOptions): Promise<EnvironmentFile>;
115
72
  }
116
73
  export interface ChildSummary {
117
74
  id: string;
@@ -155,3 +112,4 @@ export declare class SessionChildren {
155
112
  }>;
156
113
  get(childId: string): SessionChild;
157
114
  }
115
+ export declare function encodeBase64(bytes: Uint8Array): string;
package/dist/resources.js CHANGED
@@ -1,110 +1,14 @@
1
- import { MAX_INLINE_FILE_BYTES, SessionChildren as BrainChildren, SessionSandbox as BrainSandbox, SessionStorage as BrainStorage, } from "@aexhq/brain";
1
+ import { MAX_INLINE_FILE_BYTES, SessionChildren as BrainChildren, SessionStorage as BrainStorage, } from "@aexhq/session-protocol";
2
2
  import { SessionError, abortError } from "./errors.js";
3
3
  import { randomIdempotencyKey } from "./json.js";
4
- export class SandboxFiles {
5
- #inner;
6
- #transport;
7
- constructor(inner, transport) {
8
- this.#inner = inner;
9
- this.#transport = transport;
10
- }
11
- async list(path, options) {
12
- const page = await this.#inner.list({
13
- path,
14
- generation: options.generation,
15
- ...(options.cursor === undefined ? {} : { cursor: options.cursor }),
16
- ...(options.limit === undefined ? {} : { limit: options.limit }),
17
- }, request(options));
18
- return filePage(page);
19
- }
20
- async stat(path, options) {
21
- return file(await this.#inner.stat({ path, generation: options.generation }, request(options)));
22
- }
23
- async download(path, options) {
24
- return collect(await this.downloadStream(path, options));
25
- }
26
- async downloadStream(path, options) {
27
- const entry = await this.#inner.stat({ path, generation: options.generation }, request(options));
28
- if (entry.bytes <= MAX_INLINE_FILE_BYTES) {
29
- const result = await this.#inner.readInline({ path, generation: options.generation }, request(options));
30
- return byteStream(decodeBase64(result.content_base64));
31
- }
32
- const ticket = await this.#inner.prepareDownload({ path, generation: options.generation }, request(options));
33
- return this.#transport.downloadTransferStream(ticket, options.signal, entry.bytes);
34
- }
35
- async upload(path, source, options) {
36
- const base = {
37
- path,
38
- generation: options.generation,
39
- ...(options.overwrite === undefined ? {} : { overwrite: options.overwrite }),
40
- };
41
- let entry;
42
- if (isStreamingSource(source)) {
43
- assertStreamingSource(source);
44
- entry = source.bytes <= MAX_INLINE_FILE_BYTES
45
- ? await this.#inner.writeInline({ ...base, content_base64: encodeBase64(await collectDeclared(source, options.signal)) }, request(options))
46
- : await this.#uploadStream(base, source, options);
47
- }
48
- else {
49
- const content = await bytesOf(source);
50
- entry = content.byteLength <= MAX_INLINE_FILE_BYTES
51
- ? await this.#inner.writeInline({ ...base, content_base64: encodeBase64(content) }, request(options))
52
- : await this.#upload(base, content, options);
53
- }
54
- return file(entry);
55
- }
56
- async #upload(input, content, options) {
57
- const ticket = await this.#inner.prepareUpload({ ...input, bytes: content.byteLength, sha256: await sha256(content) }, request(options));
58
- await this.#transport.uploadTransfer(ticket, content, content.byteLength, options.signal);
59
- // Direct sandbox transfers are intentionally happy-path only. Do not automatically replay an
60
- // ambiguous import: surface the error so the caller can inspect the generation/path and
61
- // prepare a fresh transfer. Durable storage completion has separate retry-safe semantics.
62
- return this.#inner.completeUpload(ticket.transfer_id, request(options));
63
- }
64
- async #uploadStream(input, source, options) {
65
- const ticket = await this.#inner.prepareUpload({ ...input, bytes: source.bytes, sha256: source.sha256 }, request(options));
66
- await this.#transport.uploadTransfer(ticket, () => source.stream(), source.bytes, options.signal);
67
- return this.#inner.completeUpload(ticket.transfer_id, request(options));
68
- }
69
- async find(input, options) {
70
- const page = await this.#inner.find({
71
- ...input,
72
- generation: options.generation,
73
- ...(options.cursor === undefined ? {} : { cursor: options.cursor }),
74
- ...(options.limit === undefined ? {} : { limit: options.limit }),
75
- }, request(options));
76
- return filePage(page);
77
- }
78
- async grep(input, options) {
79
- const page = await this.#inner.grep({
80
- ...input,
81
- generation: options.generation,
82
- ...(options.cursor === undefined ? {} : { cursor: options.cursor }),
83
- ...(options.limit === undefined ? {} : { limit: options.limit }),
84
- }, request(options));
85
- return filePage(page);
86
- }
87
- }
88
- export class SessionSandbox {
89
- #inner;
90
- files;
91
- constructor(transport, sessionId) {
92
- this.#inner = new BrainSandbox(transport, sessionId);
93
- this.files = new SandboxFiles(this.#inner.files, transport);
94
- }
95
- async status(options = {}) {
96
- return sandboxStatus(await this.#inner.status(request(options)));
97
- }
98
- async create(options = {}) {
99
- return sandboxStatus(await this.#inner.create(intrinsicRequest(options)));
100
- }
101
- }
102
4
  export class SessionStorage {
103
5
  #inner;
104
6
  #transport;
105
- constructor(transport, sessionId) {
7
+ #environmentNames;
8
+ constructor(transport, sessionId, environmentNames = new Map()) {
106
9
  this.#inner = new BrainStorage(transport, sessionId);
107
10
  this.#transport = transport;
11
+ this.#environmentNames = environmentNames;
108
12
  }
109
13
  async list(options = {}) {
110
14
  const page = await this.#inner.list({
@@ -158,22 +62,28 @@ export class SessionStorage {
158
62
  delete(key, options = {}) {
159
63
  return this.#inner.delete(key, request(options));
160
64
  }
161
- async copyFromSandbox(input, options = {}) {
162
- return storageObject(await this.#inner.copyFromSandbox({
65
+ async copyFromEnvironment(environment, input, options = {}) {
66
+ return storageObject(await this.#inner.copyFromEnvironment(this.#environmentName(environment), {
163
67
  key: input.key,
164
68
  path: input.path,
165
- sandbox_generation: input.sandboxGeneration,
69
+ environment_generation: input.generation,
166
70
  ...(input.overwrite === undefined ? {} : { overwrite: input.overwrite }),
167
71
  }, request(options)));
168
72
  }
169
- async copyToSandbox(input, options = {}) {
170
- return file(await this.#inner.copyToSandbox({
73
+ async copyToEnvironment(environment, input, options = {}) {
74
+ return environmentFile(await this.#inner.copyToEnvironment(this.#environmentName(environment), {
171
75
  key: input.key,
172
76
  path: input.path,
173
- sandbox_generation: input.sandboxGeneration,
77
+ environment_generation: input.generation,
174
78
  ...(input.overwrite === undefined ? {} : { overwrite: input.overwrite }),
175
79
  }, request(options)));
176
80
  }
81
+ #environmentName(environment) {
82
+ const name = this.#environmentNames.get(environment);
83
+ if (name === undefined)
84
+ throw new TypeError("EnvironmentRef does not belong to this Session");
85
+ return name;
86
+ }
177
87
  }
178
88
  export class SessionChild {
179
89
  #inner;
@@ -249,16 +159,7 @@ function idempotentRequest(options) {
249
159
  retry: true,
250
160
  };
251
161
  }
252
- function sandboxStatus(value) {
253
- return {
254
- state: value.state,
255
- ...(value.generation == null ? {} : { generation: value.generation }),
256
- ...(value.reason == null ? {} : { reason: value.reason }),
257
- ...(value.changed_at_ms == null ? {} : { changedAt: timestamp(value.changed_at_ms) }),
258
- ...(value.expires_at_ms == null ? {} : { expiresAt: timestamp(value.expires_at_ms) }),
259
- };
260
- }
261
- function file(value) {
162
+ function environmentFile(value) {
262
163
  return {
263
164
  path: value.path,
264
165
  kind: value.kind,
@@ -267,14 +168,6 @@ function file(value) {
267
168
  modifiedAt: timestamp(value.modified_at_ms),
268
169
  };
269
170
  }
270
- function filePage(value) {
271
- return {
272
- data: value.data.map(file),
273
- hasMore: value.has_more,
274
- ...(value.next_cursor === undefined ? {} : { nextCursor: value.next_cursor }),
275
- generation: value.generation,
276
- };
277
- }
278
171
  function storageObject(value) {
279
172
  return {
280
173
  key: value.key,
@@ -413,7 +306,7 @@ async function collectDeclared(source, signal) {
413
306
  }
414
307
  return result;
415
308
  }
416
- function encodeBase64(bytes) {
309
+ export function encodeBase64(bytes) {
417
310
  let binary = "";
418
311
  for (let offset = 0; offset < bytes.byteLength; offset += 32_768) {
419
312
  binary += String.fromCharCode(...bytes.subarray(offset, offset + 32_768));
package/dist/session.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- import type { Event, Provider, Session as SessionData, SessionState } from "@aexhq/brain/session";
2
- import type { NetworkPolicy, WebSocketFactory } from "@aexhq/brain";
1
+ import type { Event, Provider, Session as SessionData, SessionState } from "@aexhq/session-protocol/session";
2
+ import { type EnvironmentRef, type HandleOf } from "@aexhq/environment";
3
+ import type { NetworkPolicy, WebSocketFactory } from "@aexhq/session-protocol";
3
4
  import * as z from "zod";
4
5
  import type { EventOptions } from "./transport.js";
5
6
  import { Transport } from "./transport.js";
6
- import type { Tool } from "./tools.js";
7
- import { SessionChildren, SessionSandbox, SessionStorage } from "./resources.js";
7
+ import type { EnvironmentMap, EnvironmentValue, ToolSelection } from "./tools.js";
8
+ import { SessionChildren, SessionStorage } from "./resources.js";
8
9
  export type SessionInput = string;
9
10
  export interface ModelOptions {
10
11
  provider: Provider;
@@ -14,24 +15,33 @@ export interface ModelOptions {
14
15
  maxOutputTokens?: number;
15
16
  /** Immutable context capacity used for admission and compaction; Brain never guesses by name. */
16
17
  contextWindowTokens?: number;
17
- temperature?: number;
18
- reasoningEffort?: "low" | "medium" | "high";
19
18
  }
20
- export interface CreateSessionOptions {
19
+ /**
20
+ * A built agentloop implementation, as exported by a loop package or produced by
21
+ * `buildLoopBundle` from `@aexhq/agentloop`. Assignment is by import, never by name:
22
+ * the sealed identity is the content digest plus the pinned toolchain.
23
+ */
24
+ export interface Loop {
25
+ /** The complete deterministic ESM source bundle — the exact bytes sealed and uploaded. */
26
+ source: string;
27
+ /** SHA-256 hex of the UTF-8 source bytes. */
28
+ sha256: string;
29
+ /** The pinned loop-toolchain identity the bundle was built for. */
30
+ toolchain: string;
31
+ }
32
+ export interface CreateSessionOptions<Environments extends EnvironmentMap = EnvironmentMap> {
21
33
  model: ModelOptions;
34
+ /** The imported agent loop that drives this session and every child unless spawn overrides it. */
35
+ loop: Loop;
22
36
  /** Omitted or empty grants no tools. A non-empty list is the exact grant. */
23
- tools?: readonly Tool[];
24
- systemPrompt?: string;
37
+ environments?: Environments;
38
+ tools?: readonly ToolSelection<EnvironmentValue<Environments>>[];
25
39
  /** Write-only values for environment names declared by managed Tools. */
26
40
  secrets?: Record<string, string>;
27
41
  /** Maximum direct outbound network authority sealed for managed sandboxes. Omission is deny-all. */
28
42
  network?: NetworkPolicy;
29
43
  /** Replacement attempts after an unrecoverable provider outcome. Defaults to one. */
30
44
  providerRecoveryRetries?: 0 | 1;
31
- client?: {
32
- /** Replacement sends to the same customer process and operation. Defaults to one. */
33
- submitRetries?: 0 | 1;
34
- };
35
45
  /** Optional ceilings for durable child sessions. Omitted fields use the hosted defaults. */
36
46
  children?: {
37
47
  maxDepth?: number;
@@ -81,19 +91,19 @@ export interface SessionSummary {
81
91
  }
82
92
  export declare class Sessions {
83
93
  #private;
84
- constructor(transport: Transport, webSocketFactory?: WebSocketFactory, clientId?: string);
94
+ constructor(transport: Transport, webSocketFactory?: WebSocketFactory);
85
95
  /** @internal Called by `Aex.close()`. */
86
96
  close(): void;
87
- create(options: CreateSessionOptions, request?: RequestOptions): Promise<Session>;
97
+ create<Environments extends EnvironmentMap>(options: CreateSessionOptions<Environments>, request?: RequestOptions): Promise<Session<Environments>>;
88
98
  get(id: string, options?: Pick<RequestOptions, "signal">): Promise<Session>;
89
99
  list(options?: ListSessionsOptions): Promise<SessionList>;
90
100
  }
91
- export declare class Session implements SessionSummary {
101
+ export declare class Session<Environments extends EnvironmentMap = EnvironmentMap> implements SessionSummary {
92
102
  #private;
93
- readonly sandbox: SessionSandbox;
94
103
  readonly storage: SessionStorage;
95
104
  readonly children: SessionChildren;
96
- constructor(transport: Transport, data: SessionData);
105
+ constructor(transport: Transport, data: SessionData, environmentNames?: ReadonlyMap<EnvironmentRef, string>);
106
+ environment<Environment extends EnvironmentValue<Environments>>(environment: Environment): HandleOf<Environment>;
97
107
  get id(): string;
98
108
  get state(): SessionState;
99
109
  get turnState(): SessionData["turn_state"];