@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/dist/session.js CHANGED
@@ -1,38 +1,38 @@
1
- import { CustomerHand } from "@aexhq/brain";
1
+ import { CustomerEnvironment } from "@aexhq/session-protocol";
2
+ import { inspectEnvironment } from "@aexhq/environment";
2
3
  import * as z from "zod";
3
4
  import { AbortError, OutputRefusalError, OutputSchemaError, OutputValidationError, SessionError, abortError, errorFromApi, } from "./errors.js";
4
5
  import { canonicalize, jcsSha256, randomIdempotencyKey } from "./json.js";
5
6
  import { compileTools } from "./tools.js";
6
- import { SessionChildren, SessionSandbox, SessionStorage } from "./resources.js";
7
+ import { encodeBase64, SessionChildren, SessionStorage } from "./resources.js";
7
8
  export class Sessions {
8
9
  #transport;
9
10
  #webSocketFactory;
10
- #clientId;
11
- #customerHand;
12
- #customerHandInstance;
11
+ #customerEnvironments = new Map();
12
+ #customerEnvironmentInstances = new Map();
13
13
  #closed = false;
14
- constructor(transport, webSocketFactory, clientId) {
14
+ constructor(transport, webSocketFactory) {
15
15
  this.#transport = transport;
16
16
  this.#webSocketFactory = webSocketFactory;
17
- this.#clientId = clientId;
18
17
  }
19
18
  /** @internal Called by `Aex.close()`. */
20
19
  close() {
21
20
  if (this.#closed)
22
21
  return;
23
22
  this.#closed = true;
24
- this.#customerHandInstance?.close();
25
- this.#customerHandInstance = undefined;
26
- this.#customerHand = undefined;
23
+ for (const environment of this.#customerEnvironmentInstances.values())
24
+ environment.close();
25
+ this.#customerEnvironmentInstances.clear();
26
+ this.#customerEnvironments.clear();
27
27
  }
28
28
  async create(options, request = {}) {
29
29
  if (this.#closed)
30
30
  throw new SessionError("Aex client is closed");
31
- const compiledTools = await compileTools(options.tools);
32
- if (options.client?.submitRetries !== undefined && this.#clientId === undefined) {
33
- throw new TypeError("client.submitRetries requires Aex({ client: { id } })");
34
- }
35
- await this.#ensureCustomerHand(compiledTools.clientRegistrations, request.signal);
31
+ if (options.loop === undefined)
32
+ throw new TypeError("sessions.create requires an imported loop");
33
+ const compiledTools = await compileTools(options.tools, options.environments, options.secrets);
34
+ await Promise.all(compiledTools.layers.map((layer) => this.#transport.ensureArtifactLayer(layer.checksum, layer.media_type, layer.content, request.signal)));
35
+ await this.#ensureCustomerEnvironment(compiledTools.callbackClientId, compiledTools.clientRegistrations, request.signal);
36
36
  const body = {
37
37
  model: {
38
38
  provider: options.model.provider,
@@ -45,17 +45,20 @@ export class Sessions {
45
45
  ...(options.model.contextWindowTokens === undefined
46
46
  ? {}
47
47
  : { context_window_tokens: options.model.contextWindowTokens }),
48
- ...(options.model.temperature === undefined ? {} : { temperature: options.model.temperature }),
49
- ...(options.model.reasoningEffort === undefined
50
- ? {}
51
- : { reasoning_effort: options.model.reasoningEffort }),
52
48
  },
53
49
  tools: {
54
50
  items: compiledTools.items,
55
51
  },
52
+ ...(Object.keys(compiledTools.environments).length === 0
53
+ ? {}
54
+ : { environments: compiledTools.environments }),
56
55
  ...(compiledTools.bundles.length === 0 ? {} : { tool_bundles: compiledTools.bundles }),
56
+ agentloop: {
57
+ source_bundle_sha256: options.loop.sha256,
58
+ toolchain: options.loop.toolchain,
59
+ bundle_base64: encodeBase64(new TextEncoder().encode(options.loop.source)),
60
+ },
57
61
  ...(options.secrets === undefined ? {} : { secrets: options.secrets }),
58
- ...(options.systemPrompt === undefined ? {} : { system_prompt: options.systemPrompt }),
59
62
  ...(options.metadata === undefined ? {} : { metadata: options.metadata }),
60
63
  ...(options.network === undefined
61
64
  ? {}
@@ -63,14 +66,11 @@ export class Sessions {
63
66
  ...(options.providerRecoveryRetries === undefined
64
67
  ? {}
65
68
  : { provider_recovery_retries: options.providerRecoveryRetries }),
66
- ...(this.#clientId === undefined
69
+ ...(compiledTools.callbackClientId === undefined
67
70
  ? {}
68
71
  : {
69
72
  client: {
70
- id: this.#clientId,
71
- ...(options.client?.submitRetries === undefined
72
- ? {}
73
- : { submit_retries: options.client.submitRetries }),
73
+ id: compiledTools.callbackClientId,
74
74
  },
75
75
  }),
76
76
  ...(options.children === undefined
@@ -95,7 +95,7 @@ export class Sessions {
95
95
  signal: request.signal,
96
96
  retry: true,
97
97
  });
98
- return new Session(this.#transport, data);
98
+ return new Session(this.#transport, data, compiledTools.environmentNames);
99
99
  }
100
100
  async get(id, options = {}) {
101
101
  const data = await this.#transport.json("GET", `/v1/sessions/${encodeURIComponent(id)}`, { signal: options.signal });
@@ -119,29 +119,29 @@ export class Sessions {
119
119
  ...(list.next_cursor === undefined ? {} : { nextCursor: list.next_cursor }),
120
120
  };
121
121
  }
122
- async #ensureCustomerHand(registrations, signal) {
122
+ async #ensureCustomerEnvironment(clientId, registrations, signal) {
123
123
  if (this.#closed)
124
124
  throw new SessionError("Aex client is closed");
125
125
  if (registrations.length === 0)
126
126
  return;
127
- if (this.#clientId === undefined) {
128
- throw new TypeError("Customer-app Tools require Aex({ client: { id } })");
129
- }
127
+ if (clientId === undefined)
128
+ throw new TypeError("Callback Tools require an app() environment");
130
129
  if (this.#webSocketFactory === undefined) {
131
130
  throw new TypeError("This runtime does not provide WebSocket; pass webSocketFactory to Aex");
132
131
  }
133
- if (this.#customerHand === undefined) {
132
+ const existing = this.#customerEnvironments.get(clientId);
133
+ if (existing === undefined) {
134
134
  let partial;
135
135
  const starting = (async () => {
136
136
  try {
137
- partial = new CustomerHand(async () => {
138
- const grant = await this.#transport.customerHandGrant(this.#clientId);
137
+ partial = new CustomerEnvironment(async () => {
138
+ const grant = await this.#transport.customerEnvironmentGrant(clientId);
139
139
  return {
140
140
  request: { url: grant.url, protocol: grant.protocol },
141
- observe: (observation) => this.#transport.customerHandObserve(grant.observationUrl, grant.observationToken, observation),
141
+ observe: (observation) => this.#transport.customerEnvironmentObserve(grant.observationUrl, grant.observationToken, observation),
142
142
  };
143
- }, registrations, this.#webSocketFactory, { clientId: this.#clientId });
144
- this.#customerHandInstance = partial;
143
+ }, registrations, this.#webSocketFactory, { clientId });
144
+ this.#customerEnvironmentInstances.set(clientId, partial);
145
145
  await partial.ready;
146
146
  if (this.#closed) {
147
147
  partial.close();
@@ -154,12 +154,13 @@ export class Sessions {
154
154
  throw error;
155
155
  }
156
156
  })();
157
- this.#customerHand = starting;
157
+ this.#customerEnvironments.set(clientId, starting);
158
158
  void starting.catch(() => {
159
- if (this.#customerHand === starting) {
160
- this.#customerHand = undefined;
161
- if (this.#customerHandInstance === partial)
162
- this.#customerHandInstance = undefined;
159
+ if (this.#customerEnvironments.get(clientId) === starting) {
160
+ this.#customerEnvironments.delete(clientId);
161
+ if (this.#customerEnvironmentInstances.get(clientId) === partial) {
162
+ this.#customerEnvironmentInstances.delete(clientId);
163
+ }
163
164
  }
164
165
  });
165
166
  // The request may stop waiting, but the process-scoped runner remains reconnectable for
@@ -167,10 +168,10 @@ export class Sessions {
167
168
  await waitWithSignal(starting, signal);
168
169
  return;
169
170
  }
170
- const hand = await waitWithSignal(this.#customerHand, signal);
171
+ const environment = await waitWithSignal(existing, signal);
171
172
  if (this.#closed)
172
173
  throw new SessionError("Aex client is closed");
173
- await waitWithSignal(hand.register(registrations), signal);
174
+ await waitWithSignal(environment.register(registrations), signal);
174
175
  }
175
176
  }
176
177
  function waitWithSignal(promise, signal) {
@@ -191,16 +192,32 @@ function waitWithSignal(promise, signal) {
191
192
  export class Session {
192
193
  #transport;
193
194
  #data;
194
- sandbox;
195
195
  storage;
196
196
  children;
197
- constructor(transport, data) {
197
+ #environmentNames;
198
+ #environmentHandles = new Map();
199
+ constructor(transport, data, environmentNames = new Map()) {
198
200
  this.#transport = transport;
199
201
  this.#data = data;
200
- this.sandbox = new SessionSandbox(transport, data.id);
201
- this.storage = new SessionStorage(transport, data.id);
202
+ this.#environmentNames = environmentNames;
203
+ this.storage = new SessionStorage(transport, data.id, environmentNames);
202
204
  this.children = new SessionChildren(transport, data.id);
203
205
  }
206
+ environment(environment) {
207
+ const name = this.#environmentNames.get(environment);
208
+ if (name === undefined)
209
+ throw new TypeError("EnvironmentRef does not belong to this Session");
210
+ const existing = this.#environmentHandles.get(environment);
211
+ if (existing !== undefined)
212
+ return existing;
213
+ const handle = inspectEnvironment(environment).createHandle({
214
+ sessionId: this.id,
215
+ environment: name,
216
+ request: async (method, path, body) => await this.#transport.json(method, path, body === undefined ? {} : { body }),
217
+ });
218
+ this.#environmentHandles.set(environment, handle);
219
+ return handle;
220
+ }
204
221
  get id() {
205
222
  return this.#data.id;
206
223
  }
package/dist/tools.d.ts CHANGED
@@ -1,2 +1,100 @@
1
- export { compileTools } from "@aexhq/brain";
2
- export type { ClientRegistration, CompiledTools, Tool } from "@aexhq/brain";
1
+ import { type ComputerProfile, type EnvironmentRef } from "@aexhq/environment";
2
+ import * as z from "zod";
3
+ export type JsonSchema = Record<string, unknown>;
4
+ export interface ToolContext {
5
+ readonly signal: AbortSignal;
6
+ readonly operationId: string;
7
+ readonly sessionId: string;
8
+ readonly deadlineMs: number;
9
+ readonly workspace?: string;
10
+ }
11
+ export type ToolHandler<Input extends z.ZodType, Output = unknown> = (input: z.output<Input>, context: ToolContext) => Output | Promise<Output>;
12
+ export type ToolSetupHandler = () => void | Promise<void>;
13
+ export interface ToolRequirements {
14
+ readonly env?: readonly string[];
15
+ readonly workspace?: boolean;
16
+ readonly processes?: boolean;
17
+ readonly network?: readonly {
18
+ readonly host: string;
19
+ readonly port: number;
20
+ }[];
21
+ readonly streaming?: boolean;
22
+ readonly recovery?: "retained" | "connection" | "replay-safe";
23
+ }
24
+ export interface PreparedArtifact {
25
+ readonly digest: string;
26
+ readonly target: ComputerProfile["platform"];
27
+ readonly bytes: number;
28
+ readonly execute: string;
29
+ readonly setup?: string;
30
+ readonly layers: readonly PreparedArtifactLayer[];
31
+ }
32
+ export interface PreparedArtifactLayer {
33
+ readonly digest: string;
34
+ readonly bytes: number;
35
+ readonly mediaType: "application/javascript+esm" | "application/x-xz";
36
+ readonly mountPath: string;
37
+ readonly unpack: "file" | "tar.xz";
38
+ readonly source: URL;
39
+ }
40
+ export interface ToolContract {
41
+ readonly name: string;
42
+ readonly description?: string;
43
+ readonly inputSchema: JsonSchema;
44
+ readonly outputSchema?: JsonSchema;
45
+ readonly contractDigest: string;
46
+ }
47
+ export interface Tool<Input extends z.ZodType = z.ZodType, Output = unknown> {
48
+ readonly kind: "aex.tool";
49
+ readonly name?: string;
50
+ readonly description?: string;
51
+ readonly input: Input;
52
+ readonly output?: z.ZodType;
53
+ readonly requirements: ToolRequirements;
54
+ readonly handler: ToolHandler<Input, Output>;
55
+ readonly setupHandler?: ToolSetupHandler;
56
+ readonly artifact?: PreparedArtifact;
57
+ describe(description: string): Tool<Input, Output>;
58
+ named(name: string): Tool<Input, Output>;
59
+ returns<Schema extends z.ZodType>(schema: Schema): Tool<Input, z.output<Schema>>;
60
+ needs(requirements: ToolRequirements): Tool<Input, Output>;
61
+ setup(handler: ToolSetupHandler): Tool<Input, Output>;
62
+ bind<Environment extends EnvironmentRef>(environment: Environment): BoundTool<Environment, Input, Output>;
63
+ }
64
+ export interface BoundTool<Environment extends EnvironmentRef = EnvironmentRef, Input extends z.ZodType = z.ZodType, Output = unknown> {
65
+ readonly kind: "aex.bound-tool";
66
+ readonly tool: Tool<Input, Output>;
67
+ readonly environment: Environment;
68
+ }
69
+ declare const EMPTY_INPUT: z.ZodObject<{}, z.core.$strip>;
70
+ export declare function tool<Output>(handler: ToolHandler<typeof EMPTY_INPUT, Output>): Tool<typeof EMPTY_INPUT, Output>;
71
+ export declare function tool<Input extends z.ZodType, Output>(input: Input, handler: ToolHandler<Input, Output>): Tool<Input, Output>;
72
+ export declare function withPreparedArtifact<Input extends z.ZodType, Output>(value: Tool<Input, Output>, artifact: PreparedArtifact): Tool<Input, Output>;
73
+ export type EnvironmentMap = Record<string, EnvironmentRef>;
74
+ export type EnvironmentValue<Environments extends EnvironmentMap> = Environments[keyof Environments];
75
+ export type ToolSelection<Environment extends EnvironmentRef = EnvironmentRef> = Tool | BoundTool<Environment>;
76
+ export interface ClientRegistration {
77
+ readonly registration: string;
78
+ readonly name: string;
79
+ readonly contractDigest: string;
80
+ readonly input: z.ZodType;
81
+ readonly output?: z.ZodType;
82
+ readonly handler: ToolHandler<z.ZodType>;
83
+ }
84
+ export interface CompiledTools {
85
+ readonly items: readonly unknown[];
86
+ readonly bundles: readonly unknown[];
87
+ readonly layers: readonly CompiledArtifactLayer[];
88
+ readonly clientRegistrations: readonly ClientRegistration[];
89
+ readonly environments: Readonly<Record<string, unknown>>;
90
+ readonly environmentNames: ReadonlyMap<EnvironmentRef, string>;
91
+ readonly callbackClientId?: string;
92
+ }
93
+ export interface CompiledArtifactLayer {
94
+ readonly checksum: string;
95
+ readonly bytes: number;
96
+ readonly media_type: string;
97
+ readonly content: Uint8Array;
98
+ }
99
+ export declare function compileTools(selections: readonly ToolSelection[] | undefined, declared: EnvironmentMap | undefined, secrets?: Readonly<Record<string, string>>): Promise<CompiledTools>;
100
+ export {};