@cofy-x/axern-sdk 0.6.2 → 0.7.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.
Files changed (60) hide show
  1. package/README.md +26 -65
  2. package/dist/client/index.d.ts +10 -11
  3. package/dist/client/index.js +64 -68
  4. package/dist/config/index.d.ts +0 -1
  5. package/dist/config/index.js +2 -4
  6. package/dist/generated/proto.js +0 -1
  7. package/dist/index.d.ts +4 -5
  8. package/dist/index.js +1 -2
  9. package/dist/node/attached_process.d.ts +1 -2
  10. package/dist/node/attached_process.js +12 -26
  11. package/dist/node/client.d.ts +4 -6
  12. package/dist/node/client.js +2 -9
  13. package/dist/node/exec.d.ts +1 -3
  14. package/dist/node/exec.js +0 -43
  15. package/dist/proto/axern/control/admin/v1/access.proto +12 -5
  16. package/dist/proto/axern/control/admin/v1/audit.proto +14 -16
  17. package/dist/proto/axern/control/admin/v1/node.proto +23 -67
  18. package/dist/proto/axern/control/admin/v1/reliability.proto +11 -78
  19. package/dist/proto/axern/control/capability/v1/capability.proto +34 -94
  20. package/dist/proto/axern/control/common/v1/common.proto +29 -102
  21. package/dist/proto/axern/control/environment/v1/environment.proto +42 -26
  22. package/dist/proto/axern/control/gateway/v1/gateway.proto +18 -59
  23. package/dist/proto/axern/control/identity/v1/identity.proto +1 -1
  24. package/dist/proto/axern/control/namespace/v1/namespace.proto +2 -3
  25. package/dist/proto/axern/control/quota/v1/quota.proto +22 -32
  26. package/dist/proto/axern/control/run/v1/run.proto +26 -22
  27. package/dist/proto/axern/control/secret/v1/secret.proto +1 -3
  28. package/dist/proto/axern/control/tunnel/v1/tunnel.proto +18 -25
  29. package/dist/proto/axern/node/sandbox/v1/node.proto +59 -375
  30. package/dist/sandbox/index.d.ts +3 -9
  31. package/dist/sandbox/index.js +13 -22
  32. package/dist/sandbox/lifecycle.d.ts +1 -1
  33. package/dist/sandbox/lifecycle.js +55 -17
  34. package/dist/tunnel/connector.js +1 -1
  35. package/dist/tunnel/control.d.ts +0 -1
  36. package/dist/tunnel/control.js +0 -1
  37. package/dist/tunnel/runtime.d.ts +0 -1
  38. package/dist/tunnel/runtime.js +0 -1
  39. package/dist/tunnel/types.d.ts +0 -1
  40. package/dist/types.d.ts +4 -21
  41. package/dist/types.js +1 -3
  42. package/dist/version.d.ts +1 -1
  43. package/dist/version.js +1 -1
  44. package/package.json +1 -1
  45. package/dist/proto/axern/control/admin/v1/allocation_lifecycle.proto +0 -91
  46. package/dist/proto/axern/control/admin/v1/service.proto +0 -18
  47. package/dist/proto/axern/control/admin/v1/storage.proto +0 -81
  48. package/dist/proto/axern/control/agentprofile/v1/agent_profile.proto +0 -169
  49. package/dist/proto/axern/control/catalog/v1/catalog.proto +0 -127
  50. package/dist/proto/axern/control/function/v1/function.proto +0 -130
  51. package/dist/proto/axern/control/function/v1/function_types.proto +0 -233
  52. package/dist/proto/axern/control/node/v1/node_control.proto +0 -443
  53. package/dist/proto/axern/control/rollout/v1/rollout.proto +0 -344
  54. package/dist/proto/axern/control/service/v1/service.proto +0 -96
  55. package/dist/proto/axern/control/service/v1/service_event.proto +0 -47
  56. package/dist/proto/axern/control/service/v1/service_replica.proto +0 -78
  57. package/dist/proto/axern/control/service/v1/service_types.proto +0 -155
  58. package/dist/proto/axern/control/storage/v1/storage.proto +0 -107
  59. package/dist/proto/axern/control/storage/v1/storage_types.proto +0 -125
  60. package/dist/proto/axern/data/artifact/v1/artifact.proto +0 -19
package/README.md CHANGED
@@ -28,18 +28,14 @@ pnpm install
28
28
  make sdk-typescript-verify
29
29
  ```
30
30
 
31
- The SDK dynamically loads Axern proto definitions from `sdk/proto`. Set
32
- `AXERN_PROTO_ROOT` when running from a different layout.
31
+ The SDK dynamically loads Axern proto definitions from `sdk/proto`. Set `AXERN_PROTO_ROOT` when running from a different layout.
33
32
 
34
33
  ## Basic Usage
35
34
 
36
35
  ```ts
37
36
  import { AxernClient, Sandbox } from "@cofy-x/axern-sdk";
38
37
 
39
- const client = AxernClient.fromContext(
40
- process.env.AXERN_CONFIG ?? `${process.env.HOME}/.config/axern/config.json`,
41
- process.env.AXERN_CONTEXT,
42
- );
38
+ const client = AxernClient.fromContext(process.env.AXERN_CONFIG ?? `${process.env.HOME}/.config/axern/config.json`, process.env.AXERN_CONTEXT);
43
39
 
44
40
  const sandbox = await new Sandbox({
45
41
  client,
@@ -70,9 +66,7 @@ try {
70
66
 
71
67
  ## Network Policies
72
68
 
73
- Omitting `networkPolicy` keeps unrestricted v0.5 behavior. Strict policies are
74
- fail-closed; `denyDns` only refuses matching traditional UDP/TCP DNS queries and
75
- does not block direct IP traffic, DoH, DoT, or already-resolved addresses.
69
+ Omitting `networkPolicy` requests unrestricted networking. Strict policies are fail-closed; `denyDns` only refuses matching traditional UDP/TCP DNS queries and does not block direct IP traffic, DoH, DoT, or already-resolved addresses.
76
70
 
77
71
  ```ts
78
72
  import { NetworkPolicy, Sandbox } from "@cofy-x/axern-sdk";
@@ -80,26 +74,13 @@ import { NetworkPolicy, Sandbox } from "@cofy-x/axern-sdk";
80
74
  const sandbox = await new Sandbox({
81
75
  client,
82
76
  image: "docker.io/library/python:3.12-slim",
83
- networkPolicy: NetworkPolicy.denyDns(
84
- "github.com",
85
- "*.github.com",
86
- "githubusercontent.com",
87
- "*.githubusercontent.com",
88
- ),
77
+ networkPolicy: NetworkPolicy.denyDns("github.com", "*.github.com", "githubusercontent.com", "*.githubusercontent.com"),
89
78
  }).start();
90
79
  ```
91
80
 
92
- `NetworkPolicy.allowDomains("example.com", "*.example.com")` allows only
93
- strict HTTP/HTTPS destinations validated by DNS plus HTTP Host or TLS SNI.
94
- `NetworkPolicy.strict({ cidrRules: [...] })` adds explicit TCP/UDP CIDR and port
95
- grants; `NetworkPolicy.denyAll()` allows no egress.
81
+ `NetworkPolicy.allowDomains("example.com", "*.example.com")` allows only strict HTTP/HTTPS destinations validated by DNS plus HTTP Host or TLS SNI. `NetworkPolicy.strict({ cidrRules: [...] })` adds explicit TCP/UDP CIDR and port grants; `NetworkPolicy.denyAll()` allows no egress.
96
82
 
97
- Run a tool from a separate image with `execImage` or `processImage`. OCI and
98
- Nydus refs use the same image field. When `mounts` is omitted, the SDK requests
99
- `/workspace -> /workspace`; pass `mounts: []` for no shared paths. Use
100
- `new Sandbox({ image })` when the image should be the sandbox rootfs with normal
101
- files, exec, process, tunnel, and lifecycle APIs; image-backed processes are
102
- temporary side processes attached to an existing sandbox.
83
+ Run a tool from a separate image with `execImage` or `processImage`. OCI and Nydus refs use the same image field. When `mounts` is omitted, the SDK requests `/workspace -> /workspace`; pass `mounts: []` for no shared paths. Use `new Sandbox({ image })` when the image should be the sandbox rootfs with normal files, exec, process, tunnel, and lifecycle APIs; image-backed processes are temporary side processes attached to an existing sandbox.
103
84
 
104
85
  ```ts
105
86
  import { workspaceMount } from "@cofy-x/axern-sdk";
@@ -111,58 +92,40 @@ const result = await sandbox.execImage("ghcr.io/cofy-x/agent:latest", "tool run"
111
92
  console.log(result.stdoutText());
112
93
  ```
113
94
 
114
- `AxernClient` requires an explicit endpoint. Use `AxernClient.fromContext()` in
115
- interactive examples or `AxernClient.fromEnv()` in environment-driven
116
- automation. Neither the client constructor nor `Sandbox` silently reads the
117
- user directory.
95
+ `AxernClient` requires an explicit endpoint. Use `AxernClient.fromContext()` in interactive examples or `AxernClient.fromEnv()` in environment-driven automation. Neither the client constructor nor `Sandbox` silently reads the user directory.
118
96
 
119
97
  ## Configuration
120
98
 
121
- `Sandbox` requires exactly one source: `templateId`, `image`, or
122
- `environmentId`.
99
+ `Sandbox` requires exactly one source: `templateId`, `image`, or `environmentId`.
123
100
 
124
101
  Common options:
125
102
 
126
103
  - `namespace`: control-plane namespace, default `default`
127
104
  - `client`: explicit `AxernClient` shared by the sandbox
128
105
  - `argv`, `env`, `cwd`: initial sandbox process configuration
129
- - `runtimeClass`: runtime selector, for example `runsc` or `runc`
130
- - `requestCpu`, `requestMemory`, `requestEphemeralStorage`: scheduler resource
131
- requests such as `500m`, `512MiB`, and `1GiB`; numeric CPU values are cores
132
- and numeric memory/storage values are bytes
133
- - `limitCpu`, `limitMemory`, `limitEphemeralStorage`: runtime hard limits;
134
- numeric CPU values are cores and numeric memory/storage values are bytes
135
- - `readyTimeoutMs`: service replica readiness timeout
106
+ - `requestCpu`, `requestMemory`, `requestEphemeralStorage`: scheduler resource requests such as `500m`, `512MiB`, and `1GiB`; numeric CPU values are cores and numeric memory/storage values are bytes
107
+ - `limitCpu`, `limitMemory`, `limitEphemeralStorage`: runtime hard limits; numeric CPU values are cores and numeric memory/storage values are bytes
108
+ - `readyTimeoutMs`: Run allocation startup timeout
136
109
 
137
110
  Tunnel options:
138
111
 
139
- - `tunnel.upstream`: local TCP address reached by the SDK connector, for
140
- example `127.0.0.1:8080`
112
+ - `tunnel.upstream`: local TCP address reached by the SDK connector, for example `127.0.0.1:8080`
141
113
  - `tunnel.proxyPort`: sandbox-local port bound by Axern, default `8786`
142
114
  - `tunnel.ttlSeconds`: control-plane tunnel session TTL, renewed by the SDK
143
115
 
144
- Tunnel traffic reuses the client gateway endpoint, mTLS identity, server name,
145
- and proxy policy.
116
+ Tunnel traffic reuses the client gateway endpoint, mTLS identity, server name, and proxy policy.
146
117
 
147
- After `start()`, use `sandbox.metadata.tunnel?.boundAddr` from inside the
148
- sandbox, for example `http://127.0.0.1:8786`.
118
+ After `start()`, use `sandbox.metadata.tunnel?.boundAddr` from inside the sandbox, for example `http://127.0.0.1:8786`.
149
119
 
150
120
  ## Sandbox API
151
121
 
152
122
  - Lifecycle: `start()`, `close()`, `state`, `metadata`
153
123
  - Execution: `exec(command, options)`, `process(command, options)`
154
- - Agent sandbox: `capabilityStatus`, `computerUseStatus`,
155
- `computerUseScreenshot`, `computerUseDisplay`, `computerUseMouse`,
156
- `computerUseKeyboard`
157
- - Files: `readFile`, `readText`, `writeFile`, `writeText`, `stat`, `listDir`,
158
- `exists`, `mkdir`, `remove`, `copy`, `move`, `chmod`, `touch`
159
- - Directories: `uploadDir(localPath, remotePath)`,
160
- `downloadDir(remotePath, localPath)`
161
- - Tunnel: `new Sandbox({ tunnel: { upstream, proxyPort } })`,
162
- `metadata.tunnel`
163
- - Errors: `AxernRpcError`, `SandboxExecError`, `SandboxStateError`,
164
- `SandboxValidationError`, `isNotFound`, `isPermissionDenied`, `isTimeout`,
165
- `rpcCode`
124
+ - Agent sandbox: `capabilityStatus`, `computerUseStatus`, `computerUseScreenshot`, `computerUseDisplay`, `computerUseMouse`, `computerUseKeyboard`
125
+ - Files: `readFile`, `readText`, `writeFile`, `writeText`, `stat`, `listDir`, `exists`, `mkdir`, `remove`, `copy`, `move`, `chmod`, `touch`
126
+ - Directories: `uploadDir(localPath, remotePath)`, `downloadDir(remotePath, localPath)`
127
+ - Tunnel: `new Sandbox({ tunnel: { upstream, proxyPort } })`, `metadata.tunnel`
128
+ - Errors: `AxernRpcError`, `SandboxExecError`, `SandboxStateError`, `SandboxValidationError`, `isNotFound`, `isPermissionDenied`, `isTimeout`, `rpcCode`
166
129
 
167
130
  ## Local Smoke
168
131
 
@@ -173,9 +136,7 @@ pnpm --filter @cofy-x/axern-sdk run smoke:local
173
136
  pnpm --filter @cofy-x/axern-sdk run smoke:tunnel
174
137
  ```
175
138
 
176
- The smoke loads `deploy/local/state/compose/axern.env` when present. It uses the
177
- `python311` template by default; set `AXERN_TS_SMOKE_IMAGE` to verify an image
178
- source instead.
139
+ The smoke loads `deploy/local/state/compose/axern.env` when present. It uses the `python311` template by default; set `AXERN_TS_SMOKE_IMAGE` to verify an image source instead.
179
140
 
180
141
  Check package contents with:
181
142
 
@@ -185,12 +146,12 @@ pnpm --filter @cofy-x/axern-sdk run pack:dry-run
185
146
 
186
147
  ## Proto Boundary
187
148
 
188
- The SDK loads protobuf definitions through `@grpc/proto-loader`. Dynamic proto
189
- access remains isolated under `src/generated`; public callers depend only on
190
- the stable TypeScript DTOs and error types.
149
+ The SDK loads protobuf definitions through `@grpc/proto-loader`. Dynamic proto access remains isolated under `src/generated`; public callers depend only on the stable TypeScript DTOs and error types.
191
150
 
192
151
  ## Scope
193
152
 
194
- This SDK is Node.js-first. The higher-level Browser API, generated TypeScript
195
- proto stubs, and full control-plane administration APIs remain outside the
196
- v0.2 public contract.
153
+ This SDK is Node.js-first. Browser automation runs as caller-owned workload software through process and Computer Use operations; generated TypeScript proto stubs and full control-plane administration APIs remain outside its public contract.
154
+
155
+ ## Run Output Retention
156
+
157
+ Run output reads expose Allocation-local stdout/stderr after runtime cleanup until `output_expires_at`, fixed at 15 minutes after cleanup begins. The combined readable limit is 64 MiB, with an explicit truncation signal. Node-process restart preserves sealed output; node-disk loss does not. Ordinary writable files still require explicit download before termination. No durable output object or persistent workspace is created.
@@ -4,11 +4,10 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import * as grpc from "@grpc/grpc-js";
7
- import { NodeSandboxClient } from "../node/client.js";
7
+ import { AllocationClient } from "../node/client.js";
8
8
  import type { ResourceQuantity } from "../resources.js";
9
9
  import type { NetworkPolicy } from "../network-policy.js";
10
10
  import { TunnelControlClient } from "../tunnel/control.js";
11
- import type { VolumeMount } from "../types.js";
12
11
  import type { GatewayTransportOptions } from "../tunnel/relay.js";
13
12
  export interface AxernClientOptions {
14
13
  endpoint: string;
@@ -27,16 +26,14 @@ export interface CreateEnvironmentOptions {
27
26
  rootfsReadonly?: boolean;
28
27
  labels?: Record<string, string>;
29
28
  }
30
- export interface CreateServiceOptions {
29
+ export interface CreateRunOptions {
31
30
  namespace?: string;
32
31
  environmentId: string;
33
32
  argv?: string[];
34
33
  env?: Record<string, string>;
35
34
  cwd?: string;
36
- runtimeClass?: string;
37
35
  networkPolicy?: NetworkPolicy;
38
36
  extensionCapabilities?: readonly ExtensionCapability[];
39
- volumes?: readonly VolumeMount[];
40
37
  requestCpu?: ResourceQuantity;
41
38
  requestMemory?: ResourceQuantity;
42
39
  requestEphemeralStorage?: ResourceQuantity;
@@ -53,13 +50,16 @@ export interface ReadRunOutputOptions {
53
50
  cursor?: string;
54
51
  follow?: boolean;
55
52
  }
53
+ export interface WatchRunOptions {
54
+ afterVersion?: number;
55
+ signal?: AbortSignal;
56
+ }
56
57
  export declare class AxernClient {
57
58
  readonly endpoint: string;
58
59
  private readonly credentials;
59
60
  private readonly controlOptions;
60
61
  private readonly environmentControl;
61
62
  private readonly runControl;
62
- private readonly serviceControl;
63
63
  private readonly tunnelControl;
64
64
  private readonly gatewayTransport;
65
65
  constructor(options: AxernClientOptions);
@@ -68,12 +68,11 @@ export declare class AxernClient {
68
68
  close(): void;
69
69
  createEnvironment(options: CreateEnvironmentOptions): Promise<Record<string, unknown>>;
70
70
  deleteEnvironment(environmentId: string): Promise<void>;
71
- watchRun(runId: string, afterVersion?: number): AsyncGenerator<Record<string, unknown>>;
71
+ createRun(options: CreateRunOptions): Promise<Record<string, unknown>>;
72
+ cancelRun(runId: string): Promise<void>;
73
+ watchRun(runId: string, options?: WatchRunOptions): AsyncGenerator<Record<string, unknown>>;
72
74
  readRunOutput(runId: string, options?: ReadRunOutputOptions): AsyncGenerator<Record<string, unknown>>;
73
- createService(options: CreateServiceOptions): Promise<Record<string, unknown>>;
74
- deleteService(serviceId: string): Promise<void>;
75
- listServiceReplicas(serviceId: string): Promise<Record<string, unknown>[]>;
76
- nodeSandbox(allocationId: string): NodeSandboxClient;
75
+ allocation(allocationId: string): AllocationClient;
77
76
  tunnelClient(): TunnelControlClient;
78
77
  tunnelTransport(): GatewayTransportOptions;
79
78
  }
@@ -8,7 +8,7 @@ import { readFileSync } from "node:fs";
8
8
  import { loadAxernContext, loadAxernEnv, normalizeProxyMode } from "../config/index.js";
9
9
  import { mapRpcError } from "../errors/index.js";
10
10
  import { serviceConstructor, unary } from "../generated/proto.js";
11
- import { NodeSandboxClient } from "../node/client.js";
11
+ import { AllocationClient } from "../node/client.js";
12
12
  import { buildResourceSpec } from "../resources.js";
13
13
  import { TunnelControlClient } from "../tunnel/control.js";
14
14
  import { required } from "../validation.js";
@@ -18,7 +18,6 @@ export class AxernClient {
18
18
  controlOptions;
19
19
  environmentControl;
20
20
  runControl;
21
- serviceControl;
22
21
  tunnelControl;
23
22
  gatewayTransport;
24
23
  constructor(options) {
@@ -57,11 +56,9 @@ export class AxernClient {
57
56
  "EnvironmentControl",
58
57
  ]);
59
58
  const RunControl = serviceConstructor(["axern", "control", "run", "v1", "RunControl"]);
60
- const ServiceControl = serviceConstructor(["axern", "control", "service", "v1", "ServiceControl"]);
61
59
  const TunnelControl = serviceConstructor(["axern", "control", "tunnel", "v1", "TunnelControl"]);
62
60
  this.environmentControl = new EnvironmentControl(this.endpoint, this.credentials, this.controlOptions);
63
61
  this.runControl = new RunControl(this.endpoint, this.credentials, this.controlOptions);
64
- this.serviceControl = new ServiceControl(this.endpoint, this.credentials, this.controlOptions);
65
62
  this.tunnelControl = new TunnelControl(this.endpoint, this.credentials, this.controlOptions);
66
63
  }
67
64
  static fromEnv(overrides = {}) {
@@ -82,7 +79,6 @@ export class AxernClient {
82
79
  close() {
83
80
  this.environmentControl.close();
84
81
  this.runControl.close();
85
- this.serviceControl.close();
86
82
  this.tunnelControl.close();
87
83
  }
88
84
  async createEnvironment(options) {
@@ -118,17 +114,57 @@ export class AxernClient {
118
114
  throw mapRpcError(error, "delete environment");
119
115
  }
120
116
  }
121
- async *watchRun(runId, afterVersion = 0) {
117
+ async createRun(options) {
118
+ const resources = buildResourceSpec(options);
119
+ try {
120
+ const response = await unary(this.runControl, "CreateRun", {
121
+ namespace: options.namespace ?? "default",
122
+ environment_id: required("environmentId", options.environmentId),
123
+ config: {
124
+ argv: options.argv ?? [],
125
+ env: options.env ?? {},
126
+ cwd: options.cwd ?? "",
127
+ ...(options.networkPolicy === undefined
128
+ ? {}
129
+ : { network: { egress_policy: options.networkPolicy.toWire() } }),
130
+ extension_capability_requirements: (options.extensionCapabilities ?? []).map((capability) => ({
131
+ capability: { name: capability.name, value: capability.value ?? "" },
132
+ })),
133
+ resources,
134
+ },
135
+ labels: options.labels ?? {},
136
+ });
137
+ return response.run;
138
+ }
139
+ catch (error) {
140
+ throw mapRpcError(error, "create run");
141
+ }
142
+ }
143
+ async cancelRun(runId) {
144
+ try {
145
+ await unary(this.runControl, "CancelRun", { run_id: required("runId", runId) });
146
+ }
147
+ catch (error) {
148
+ throw mapRpcError(error, "cancel run");
149
+ }
150
+ }
151
+ async *watchRun(runId, options = {}) {
152
+ const afterVersion = options.afterVersion ?? 0;
153
+ const signal = options.signal;
122
154
  if (afterVersion < 0) {
123
155
  throw new Error("afterVersion must be non-negative");
124
156
  }
125
157
  let version = afterVersion;
126
158
  let retryDelayMs = 100;
127
159
  for (;;) {
160
+ if (signal?.aborted)
161
+ return;
128
162
  const stream = serverStream(this.runControl, "WatchRun", {
129
163
  run_id: required("runId", runId),
130
164
  after_version: version,
131
165
  });
166
+ const cancel = () => stream.cancel();
167
+ signal?.addEventListener("abort", cancel, { once: true });
132
168
  try {
133
169
  for await (const response of stream) {
134
170
  const run = response.run;
@@ -144,14 +180,21 @@ export class AxernClient {
144
180
  return;
145
181
  }
146
182
  catch (error) {
183
+ if (signal?.aborted)
184
+ return;
147
185
  if (!transientReadError(error))
148
186
  throw mapRpcError(error, "watch run");
149
187
  }
150
- await sleep(retryDelayMs);
188
+ finally {
189
+ signal?.removeEventListener("abort", cancel);
190
+ }
191
+ await sleep(retryDelayMs, signal);
151
192
  retryDelayMs = Math.min(retryDelayMs * 2, 2_000);
152
193
  }
153
194
  }
154
195
  async *readRunOutput(runId, options = {}) {
196
+ // Output is Allocation-local and may be unavailable after cleanup; callers
197
+ // that need durable bytes must consume and persist them before then.
155
198
  const response = await unary(this.runControl, "GetRun", { run_id: required("runId", runId) });
156
199
  const allocationId = String(response.run?.allocation_id ?? "");
157
200
  if (allocationId === "")
@@ -195,57 +238,8 @@ export class AxernClient {
195
238
  retryDelayMs = Math.min(retryDelayMs * 2, 2_000);
196
239
  }
197
240
  }
198
- async createService(options) {
199
- const resources = buildResourceSpec(options);
200
- try {
201
- const response = await unary(this.serviceControl, "CreateService", {
202
- namespace: options.namespace ?? "default",
203
- environment_id: required("environmentId", options.environmentId),
204
- replicas: 1,
205
- config: {
206
- argv: options.argv ?? [],
207
- env: options.env ?? {},
208
- cwd: options.cwd ?? "",
209
- runtime_class: options.runtimeClass ?? "",
210
- ...(options.networkPolicy === undefined
211
- ? {}
212
- : { network: { egress_policy: options.networkPolicy.toWire() } }),
213
- extension_capability_requirements: (options.extensionCapabilities ?? []).map((capability) => ({
214
- capability: { name: capability.name, value: capability.value ?? "" },
215
- })),
216
- volume_mounts: serviceVolumeMounts(options.volumes),
217
- resources,
218
- },
219
- labels: options.labels ?? {},
220
- });
221
- return response.service;
222
- }
223
- catch (error) {
224
- throw mapRpcError(error, "create service");
225
- }
226
- }
227
- async deleteService(serviceId) {
228
- try {
229
- await unary(this.serviceControl, "DeleteService", { service_id: required("serviceId", serviceId) });
230
- }
231
- catch (error) {
232
- throw mapRpcError(error, "delete service");
233
- }
234
- }
235
- async listServiceReplicas(serviceId) {
236
- try {
237
- const response = await unary(this.serviceControl, "ListServiceReplicas", {
238
- service_id: required("serviceId", serviceId),
239
- filter: { view: 2 },
240
- });
241
- return response.replicas ?? [];
242
- }
243
- catch (error) {
244
- throw mapRpcError(error, "list service replicas");
245
- }
246
- }
247
- nodeSandbox(allocationId) {
248
- return new NodeSandboxClient({
241
+ allocation(allocationId) {
242
+ return new AllocationClient({
249
243
  allocationId: required("allocationId", allocationId),
250
244
  target: this.endpoint,
251
245
  credentials: this.credentials,
@@ -259,14 +253,6 @@ export class AxernClient {
259
253
  return { ...this.gatewayTransport };
260
254
  }
261
255
  }
262
- function serviceVolumeMounts(mounts) {
263
- return (mounts ?? []).map((mount) => ({
264
- name: mount.name,
265
- target: mount.target,
266
- readonly: mount.readonly ?? false,
267
- options: [...(mount.options ?? [])],
268
- }));
269
- }
270
256
  function serverStream(client, method, request) {
271
257
  const fn = client[method];
272
258
  return fn.call(client, request);
@@ -275,8 +261,18 @@ function transientReadError(error) {
275
261
  const code = error.code;
276
262
  return code === grpc.status.UNAVAILABLE || code === grpc.status.DEADLINE_EXCEEDED;
277
263
  }
278
- function sleep(milliseconds) {
279
- return new Promise((resolve) => setTimeout(resolve, milliseconds));
264
+ function sleep(milliseconds, signal) {
265
+ if (signal?.aborted)
266
+ return Promise.resolve();
267
+ return new Promise((resolve) => {
268
+ const complete = () => {
269
+ clearTimeout(timer);
270
+ signal?.removeEventListener("abort", complete);
271
+ resolve();
272
+ };
273
+ const timer = setTimeout(complete, milliseconds);
274
+ signal?.addEventListener("abort", complete, { once: true });
275
+ });
280
276
  }
281
277
  function controlCredentials(options) {
282
278
  const configured = [options.tlsCaCert, options.tlsCert, options.tlsKey].filter((value) => value !== undefined && value !== "");
@@ -1,7 +1,6 @@
1
1
  /** Explicit environment and context loaders for the Axern SDK. */
2
2
  export interface AxernConfig {
3
3
  endpoint: string;
4
- serviceUrl?: string;
5
4
  sshEndpoint?: string;
6
5
  sshIdentityFile?: string;
7
6
  tlsCaCert?: string;
@@ -3,7 +3,6 @@ import { readFileSync } from "node:fs";
3
3
  export function loadAxernEnv(overrides = {}) {
4
4
  return {
5
5
  endpoint: overrides.endpoint ?? process.env.AXERN_ENDPOINT ?? "127.0.0.1:25000",
6
- serviceUrl: overrides.serviceUrl ?? process.env.AXERN_SERVICE_URL,
7
6
  sshEndpoint: overrides.sshEndpoint ?? process.env.AXERN_SSH_ENDPOINT,
8
7
  sshIdentityFile: overrides.sshIdentityFile ?? process.env.AXERN_SSH_IDENTITY_FILE,
9
8
  tlsCaCert: overrides.tlsCaCert ?? process.env.AXERN_TLS_CA_CERT,
@@ -15,18 +14,17 @@ export function loadAxernEnv(overrides = {}) {
15
14
  }
16
15
  export function loadAxernContext(path, name = "") {
17
16
  const file = JSON.parse(readFileSync(path, "utf8"));
18
- rejectUnknown(file, ["current_context", "contexts", "agent_profiles"], "config");
17
+ rejectUnknown(file, ["current_context", "contexts"], "config");
19
18
  const contextName = name || stringValue(file.current_context);
20
19
  if (!contextName)
21
20
  throw new Error("Axern context name is required");
22
21
  const contexts = objectValue(file.contexts, "contexts");
23
22
  const context = objectValue(contexts[contextName], `context ${JSON.stringify(contextName)}`);
24
- rejectUnknown(context, ["endpoint", "service_url", "ssh_endpoint", "ssh_identity_file", "tls", "proxy_mode"], "context");
23
+ rejectUnknown(context, ["endpoint", "ssh_endpoint", "ssh_identity_file", "tls", "proxy_mode"], "context");
25
24
  const tls = objectValue(context.tls, "context.tls");
26
25
  rejectUnknown(tls, ["ca_cert", "cert", "key", "server_name"], "context.tls");
27
26
  const config = {
28
27
  endpoint: stringValue(context.endpoint),
29
- serviceUrl: stringValue(context.service_url) || undefined,
30
28
  sshEndpoint: stringValue(context.ssh_endpoint) || undefined,
31
29
  sshIdentityFile: stringValue(context.ssh_identity_file) || undefined,
32
30
  tlsCaCert: stringValue(tls.ca_cert),
@@ -16,7 +16,6 @@ const defaultProtoRootCandidates = [
16
16
  const protoFiles = [
17
17
  "axern/control/environment/v1/environment.proto",
18
18
  "axern/control/run/v1/run.proto",
19
- "axern/control/service/v1/service.proto",
20
19
  "axern/control/tunnel/v1/tunnel.proto",
21
20
  "axern/node/sandbox/v1/node.proto",
22
21
  "axern/tunnel/v1/tunnel.proto",
package/dist/index.d.ts CHANGED
@@ -4,18 +4,17 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  export { AxernClient } from "./client/index.js";
7
- export type { AxernClientOptions, CreateEnvironmentOptions, CreateServiceOptions, } from "./client/index.js";
7
+ export type { AxernClientOptions, CreateEnvironmentOptions, CreateRunOptions, } from "./client/index.js";
8
8
  export { loadAxernContext, loadAxernEnv } from "./config/index.js";
9
9
  export type { AxernConfig } from "./config/index.js";
10
10
  export { AxernError, AxernRpcError, isNotFound, isCancelled, isUnavailable, errorRetryable, isPermissionDenied, isTimeout, rpcCode, SandboxExecError, SandboxStateError, SandboxTimeoutError, SandboxValidationError, } from "./errors/index.js";
11
- export { NodeSandboxClient } from "./node/client.js";
12
- export type { NodeSandboxClientOptions } from "./node/client.js";
11
+ export { AllocationClient } from "./node/client.js";
12
+ export type { AllocationClientOptions } from "./node/client.js";
13
13
  export { SandboxProcess } from "./node/process.js";
14
14
  export type { ResourceQuantity } from "./resources.js";
15
15
  export { Sandbox } from "./sandbox/index.js";
16
16
  export type { SandboxMetadata, SandboxOptions, SandboxState } from "./sandbox/index.js";
17
17
  export { NetworkPolicy, cidrRule, portRange } from "./network-policy.js";
18
18
  export type { CIDRRule, PortRange, StrictNetworkPolicyOptions } from "./network-policy.js";
19
- export type { ChmodOptions, CapabilityDependencyStatus, CapabilityProviderStatus, CapabilityProviderSummary, CapabilityStatus, Command, ComputerUseDependencyStatus, ComputerUseDisplay, ComputerUseKeyboardOptions, ComputerUseMouseOptions, ComputerUseRegion, ComputerUseScreenshot, ComputerUseScreenshotOptions, ComputerUseStatus, CopyOptions, DownloadArchiveOptions, DownloadDirOptions, ExecOptions, ExecResult, ImageExecOptions, ImageProcessMount, ImageProcessOptions, MkdirOptions, MoveOptions, NodeCallOptions, ProcessEvent, ProcessOptions, ProcessResult, RemoveOptions, SandboxFileInfo, SandboxFileKind, TouchOptions, TunnelConnectorOptions, TunnelMetadata, TunnelOptions, UploadArchiveOptions, UploadDirOptions, VolumeMount, WriteFileOptions, } from "./types.js";
20
- export { workspaceMount } from "./types.js";
19
+ export type { ChmodOptions, CapabilityProviderDependencyStatus, CapabilityProviderStatus, CapabilityProviderSummary, CapabilityStatus, Command, ComputerUseDependencyStatus, ComputerUseDisplay, ComputerUseKeyboardOptions, ComputerUseMouseOptions, ComputerUseRegion, ComputerUseScreenshot, ComputerUseScreenshotOptions, ComputerUseStatus, CopyOptions, DownloadArchiveOptions, DownloadDirOptions, ExecOptions, ExecResult, MkdirOptions, MoveOptions, NodeCallOptions, ProcessEvent, ProcessOptions, ProcessResult, RemoveOptions, SandboxFileInfo, SandboxFileKind, TouchOptions, TunnelConnectorOptions, TunnelMetadata, TunnelOptions, UploadArchiveOptions, UploadDirOptions, WriteFileOptions, } from "./types.js";
21
20
  export { AXERN_VERSION, platformName } from "./version.js";
package/dist/index.js CHANGED
@@ -6,9 +6,8 @@
6
6
  export { AxernClient } from "./client/index.js";
7
7
  export { loadAxernContext, loadAxernEnv } from "./config/index.js";
8
8
  export { AxernError, AxernRpcError, isNotFound, isCancelled, isUnavailable, errorRetryable, isPermissionDenied, isTimeout, rpcCode, SandboxExecError, SandboxStateError, SandboxTimeoutError, SandboxValidationError, } from "./errors/index.js";
9
- export { NodeSandboxClient } from "./node/client.js";
9
+ export { AllocationClient } from "./node/client.js";
10
10
  export { SandboxProcess } from "./node/process.js";
11
11
  export { Sandbox } from "./sandbox/index.js";
12
12
  export { NetworkPolicy, cidrRule, portRange } from "./network-policy.js";
13
- export { workspaceMount } from "./types.js";
14
13
  export { AXERN_VERSION, platformName } from "./version.js";
@@ -3,8 +3,7 @@
3
3
  * Copyright 2026 cofy-x
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import type { Command, ImageProcessOptions, ProcessOptions } from "../types.js";
6
+ import type { Command, ProcessOptions } from "../types.js";
7
7
  import { NodeClientContext } from "./context.js";
8
8
  import { SandboxProcess } from "./process.js";
9
9
  export declare function process(ctx: NodeClientContext, command: Command, options?: ProcessOptions): Promise<SandboxProcess>;
10
- export declare function processImage(ctx: NodeClientContext, image: string, command: Command, options?: ImageProcessOptions): Promise<SandboxProcess>;
@@ -3,12 +3,13 @@
3
3
  * Copyright 2026 cofy-x
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { mapRpcError } from "../errors/index.js";
6
+ import { mapRpcError, SandboxValidationError } from "../errors/index.js";
7
7
  import { normalizeCommand } from "../validation.js";
8
- import { execSpec, imageProcessSpec } from "./exec.js";
8
+ import { execSpec } from "./exec.js";
9
9
  import { SandboxProcess } from "./process.js";
10
10
  export async function process(ctx, command, options = {}) {
11
11
  const argv = normalizeCommand(command);
12
+ const initialSize = processInitialSize(options);
12
13
  const client = ctx.rpcClient();
13
14
  const call = client
14
15
  .Process();
@@ -20,6 +21,7 @@ export async function process(ctx, command, options = {}) {
20
21
  call.write({
21
22
  open: ctx.authRequest({
22
23
  spec: execSpec(argv, options),
24
+ ...(initialSize === undefined ? {} : { initial_size: initialSize }),
23
25
  }),
24
26
  });
25
27
  try {
@@ -31,30 +33,14 @@ export async function process(ctx, command, options = {}) {
31
33
  throw mapRpcError(error, "sandbox process", ctx.allocationId);
32
34
  }
33
35
  }
34
- export async function processImage(ctx, image, command, options = {}) {
35
- if (image.trim() === "") {
36
- throw new Error("image is required");
36
+ function processInitialSize(options) {
37
+ const cols = options.initialCols ?? 0;
38
+ const rows = options.initialRows ?? 0;
39
+ if ((cols === 0) !== (rows === 0)) {
40
+ throw new SandboxValidationError("initialCols and initialRows must both be zero or both be positive");
37
41
  }
38
- const argv = normalizeCommand(command);
39
- const client = ctx.rpcClient();
40
- const call = client
41
- .ProcessImage();
42
- const sandboxProcess = new SandboxProcess({
43
- allocationId: ctx.allocationId,
44
- call,
45
- closeClient: () => client.close(),
46
- });
47
- call.write({
48
- open: ctx.authRequest({
49
- spec: imageProcessSpec(image, argv, options),
50
- }),
51
- });
52
- try {
53
- await sandboxProcess.waitReady();
54
- return sandboxProcess;
55
- }
56
- catch (error) {
57
- await sandboxProcess.close();
58
- throw mapRpcError(error, "sandbox image process", ctx.allocationId);
42
+ if (cols < 0 || rows < 0 || !Number.isInteger(cols) || !Number.isInteger(rows)) {
43
+ throw new SandboxValidationError("initialCols and initialRows must be non-negative integers");
59
44
  }
45
+ return cols === 0 ? undefined : { cols, rows };
60
46
  }
@@ -4,22 +4,20 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import type * as grpc from "@grpc/grpc-js";
7
- import type { ChmodOptions, CapabilityStatus, Command, ComputerUseDisplay, ComputerUseKeyboardOptions, ComputerUseMouseOptions, ComputerUseScreenshot, ComputerUseScreenshotOptions, ComputerUseStatus, CopyOptions, DownloadArchiveOptions, ExecOptions, ExecResult, ImageExecOptions, ImageProcessOptions, MkdirOptions, MoveOptions, NodeCallOptions, ProcessOptions, RemoveOptions, SandboxFileInfo, TouchOptions, UploadArchiveOptions, WriteFileOptions } from "../types.js";
7
+ import type { ChmodOptions, CapabilityStatus, Command, ComputerUseDisplay, ComputerUseKeyboardOptions, ComputerUseMouseOptions, ComputerUseScreenshot, ComputerUseScreenshotOptions, ComputerUseStatus, CopyOptions, DownloadArchiveOptions, ExecOptions, ExecResult, MkdirOptions, MoveOptions, NodeCallOptions, ProcessOptions, RemoveOptions, SandboxFileInfo, TouchOptions, UploadArchiveOptions, WriteFileOptions } from "../types.js";
8
8
  import type { SandboxProcess } from "./process.js";
9
- export interface NodeSandboxClientOptions {
9
+ export interface AllocationClientOptions {
10
10
  allocationId: string;
11
11
  target: string;
12
12
  credentials: grpc.ChannelCredentials;
13
13
  channelOptions?: grpc.ChannelOptions;
14
14
  }
15
- export declare class NodeSandboxClient {
15
+ export declare class AllocationClient {
16
16
  readonly allocationId: string;
17
17
  private readonly ctx;
18
- constructor(options: NodeSandboxClientOptions);
18
+ constructor(options: AllocationClientOptions);
19
19
  exec(command: Command, options?: ExecOptions): Promise<ExecResult>;
20
20
  process(command: Command, options?: ProcessOptions): Promise<SandboxProcess>;
21
- execImage(image: string, command: Command, options?: ImageExecOptions): Promise<ExecResult>;
22
- processImage(image: string, command: Command, options?: ImageProcessOptions): Promise<SandboxProcess>;
23
21
  capabilityStatus(options?: NodeCallOptions): Promise<CapabilityStatus>;
24
22
  computerUseStatus(options?: NodeCallOptions): Promise<ComputerUseStatus>;
25
23
  computerUseScreenshot(options?: ComputerUseScreenshotOptions): Promise<ComputerUseScreenshot>;