@cofy-x/axern-sdk 0.2.0-bootstrap.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 (92) hide show
  1. package/README.md +169 -0
  2. package/dist/client/index.d.ts +63 -0
  3. package/dist/client/index.js +213 -0
  4. package/dist/config/index.d.ts +15 -0
  5. package/dist/config/index.js +65 -0
  6. package/dist/errors/index.d.ts +46 -0
  7. package/dist/errors/index.js +123 -0
  8. package/dist/generated/proto.d.ts +10 -0
  9. package/dist/generated/proto.js +80 -0
  10. package/dist/index.d.ts +19 -0
  11. package/dist/index.js +13 -0
  12. package/dist/node/archive.d.ts +9 -0
  13. package/dist/node/archive.js +41 -0
  14. package/dist/node/attached_process.d.ts +10 -0
  15. package/dist/node/attached_process.js +60 -0
  16. package/dist/node/capabilities.d.ts +8 -0
  17. package/dist/node/capabilities.js +51 -0
  18. package/dist/node/client.d.ts +44 -0
  19. package/dist/node/client.js +96 -0
  20. package/dist/node/computer_use.d.ts +12 -0
  21. package/dist/node/computer_use.js +81 -0
  22. package/dist/node/context.d.ts +22 -0
  23. package/dist/node/context.js +37 -0
  24. package/dist/node/exec.d.ts +12 -0
  25. package/dist/node/exec.js +93 -0
  26. package/dist/node/files.d.ts +18 -0
  27. package/dist/node/files.js +101 -0
  28. package/dist/node/process.d.ts +36 -0
  29. package/dist/node/process.js +144 -0
  30. package/dist/node/streams.d.ts +8 -0
  31. package/dist/node/streams.js +70 -0
  32. package/dist/proto/axern/common/file/v1/file.proto +31 -0
  33. package/dist/proto/axern/control/admin/v1/allocation_lifecycle.proto +91 -0
  34. package/dist/proto/axern/control/admin/v1/audit.proto +53 -0
  35. package/dist/proto/axern/control/admin/v1/node.proto +49 -0
  36. package/dist/proto/axern/control/admin/v1/reliability.proto +184 -0
  37. package/dist/proto/axern/control/admin/v1/service.proto +18 -0
  38. package/dist/proto/axern/control/admin/v1/storage.proto +81 -0
  39. package/dist/proto/axern/control/agentprofile/v1/agent_profile.proto +169 -0
  40. package/dist/proto/axern/control/catalog/v1/catalog.proto +127 -0
  41. package/dist/proto/axern/control/common/v1/common.proto +171 -0
  42. package/dist/proto/axern/control/environment/v1/environment.proto +94 -0
  43. package/dist/proto/axern/control/function/v1/function.proto +130 -0
  44. package/dist/proto/axern/control/function/v1/function_types.proto +233 -0
  45. package/dist/proto/axern/control/gateway/v1/gateway.proto +60 -0
  46. package/dist/proto/axern/control/namespace/v1/namespace.proto +52 -0
  47. package/dist/proto/axern/control/node/v1/node_control.proto +302 -0
  48. package/dist/proto/axern/control/quota/v1/quota.proto +113 -0
  49. package/dist/proto/axern/control/rollout/v1/rollout.proto +344 -0
  50. package/dist/proto/axern/control/run/v1/run.proto +88 -0
  51. package/dist/proto/axern/control/secret/v1/secret.proto +75 -0
  52. package/dist/proto/axern/control/service/v1/service.proto +96 -0
  53. package/dist/proto/axern/control/service/v1/service_event.proto +47 -0
  54. package/dist/proto/axern/control/service/v1/service_replica.proto +74 -0
  55. package/dist/proto/axern/control/service/v1/service_types.proto +155 -0
  56. package/dist/proto/axern/control/storage/v1/storage.proto +107 -0
  57. package/dist/proto/axern/control/storage/v1/storage_types.proto +125 -0
  58. package/dist/proto/axern/control/tunnel/v1/tunnel.proto +195 -0
  59. package/dist/proto/axern/data/artifact/v1/artifact.proto +19 -0
  60. package/dist/proto/axern/node/sandbox/v1/node.proto +652 -0
  61. package/dist/proto/axern/tunnel/v1/tunnel.proto +50 -0
  62. package/dist/resources.d.ts +13 -0
  63. package/dist/resources.js +107 -0
  64. package/dist/sandbox/archive.d.ts +8 -0
  65. package/dist/sandbox/archive.js +228 -0
  66. package/dist/sandbox/index.d.ts +86 -0
  67. package/dist/sandbox/index.js +204 -0
  68. package/dist/sandbox/lifecycle.d.ts +11 -0
  69. package/dist/sandbox/lifecycle.js +57 -0
  70. package/dist/tunnel/connector.d.ts +34 -0
  71. package/dist/tunnel/connector.js +141 -0
  72. package/dist/tunnel/control.d.ts +22 -0
  73. package/dist/tunnel/control.js +91 -0
  74. package/dist/tunnel/debug.d.ts +6 -0
  75. package/dist/tunnel/debug.js +19 -0
  76. package/dist/tunnel/relay.d.ts +17 -0
  77. package/dist/tunnel/relay.js +41 -0
  78. package/dist/tunnel/runtime.d.ts +34 -0
  79. package/dist/tunnel/runtime.js +118 -0
  80. package/dist/tunnel/session.d.ts +25 -0
  81. package/dist/tunnel/session.js +95 -0
  82. package/dist/tunnel/target.d.ts +7 -0
  83. package/dist/tunnel/target.js +28 -0
  84. package/dist/tunnel/types.d.ts +25 -0
  85. package/dist/tunnel/types.js +6 -0
  86. package/dist/types.d.ts +219 -0
  87. package/dist/types.js +8 -0
  88. package/dist/validation.d.ts +10 -0
  89. package/dist/validation.js +37 -0
  90. package/dist/version.d.ts +7 -0
  91. package/dist/version.js +9 -0
  92. package/package.json +57 -0
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # Axern TypeScript SDK
2
+
3
+ Node.js SDK for programmable Axern sandboxes.
4
+
5
+ This first SDK surface is intentionally focused on the programmable sandbox path:
6
+
7
+ - create or attach a `Sandbox`
8
+ - run `exec`
9
+ - start attached `process` streams
10
+ - discover optional providers with `capabilityStatus`
11
+ - use Computer Use status, display, screenshot, mouse, and keyboard APIs
12
+ - use platform file RPCs such as `readFile`, `writeFile`, `stat`, and `listDir`
13
+ - transfer directories with archive-backed `uploadDir` and `downloadDir`
14
+ - expose local services to the sandbox with `tunnel`
15
+
16
+ ## Install
17
+
18
+ Install the published package in a Node.js project:
19
+
20
+ ```bash
21
+ pnpm add @cofy-x/axern-sdk
22
+ ```
23
+
24
+ For repository development:
25
+
26
+ ```bash
27
+ pnpm install
28
+ make sdk-typescript-verify
29
+ ```
30
+
31
+ The SDK dynamically loads Axern proto definitions from `sdk/proto`. Set
32
+ `AXERN_PROTO_ROOT` when running from a different layout.
33
+
34
+ ## Basic Usage
35
+
36
+ ```ts
37
+ import { AxernClient, Sandbox } from "@cofy-x/axern-sdk";
38
+
39
+ const client = AxernClient.fromContext(
40
+ process.env.AXERN_CONFIG ?? `${process.env.HOME}/.config/axern/config.json`,
41
+ process.env.AXERN_CONTEXT,
42
+ );
43
+
44
+ const sandbox = await new Sandbox({
45
+ client,
46
+ image: "python:3.12-slim",
47
+ namespace: "typescript-sdk-example",
48
+ tunnel: {
49
+ upstream: "127.0.0.1:8080",
50
+ proxyPort: 8786,
51
+ },
52
+ }).start();
53
+
54
+ try {
55
+ const result = await sandbox.exec("python - <<'PY'\nprint('hello from axern')\nPY", {
56
+ check: true,
57
+ });
58
+ console.log(result.stdoutText());
59
+
60
+ await sandbox.writeText("/tmp/message.txt", "hello\n", { createParents: true });
61
+ console.log(await sandbox.readText("/tmp/message.txt"));
62
+
63
+ await sandbox.uploadDir("./fixtures", "/tmp/fixtures");
64
+ await sandbox.downloadDir("/tmp/fixtures", "./downloaded-fixtures");
65
+ } finally {
66
+ await sandbox.close();
67
+ client.close();
68
+ }
69
+ ```
70
+
71
+ Run a tool from a separate image with `execImage` or `processImage`. OCI and
72
+ Nydus refs use the same image field. When `mounts` is omitted, the SDK requests
73
+ `/workspace -> /workspace`; pass `mounts: []` for no shared paths. Use
74
+ `new Sandbox({ image })` when the image should be the sandbox rootfs with normal
75
+ files, exec, process, tunnel, and lifecycle APIs; image-backed processes are
76
+ temporary side processes attached to an existing sandbox.
77
+
78
+ ```ts
79
+ import { workspaceMount } from "@cofy-x/axern-sdk";
80
+
81
+ const result = await sandbox.execImage("ghcr.io/cofy-x/agent:latest", "tool run", {
82
+ check: true,
83
+ mounts: [workspaceMount("/workspace")],
84
+ });
85
+ console.log(result.stdoutText());
86
+ ```
87
+
88
+ `AxernClient` requires an explicit endpoint. Use `AxernClient.fromContext()` in
89
+ interactive examples or `AxernClient.fromEnv()` in environment-driven
90
+ automation. Neither the client constructor nor `Sandbox` silently reads the
91
+ user directory.
92
+
93
+ ## Configuration
94
+
95
+ `Sandbox` requires exactly one source: `templateId`, `image`, or
96
+ `environmentId`.
97
+
98
+ Common options:
99
+
100
+ - `namespace`: control-plane namespace, default `default`
101
+ - `client`: explicit `AxernClient` shared by the sandbox
102
+ - `argv`, `env`, `cwd`: initial sandbox process configuration
103
+ - `runtimeClass`: runtime selector, for example `runsc` or `runc`
104
+ - `requestCpu`, `requestMemory`: scheduler resource requests such as `500m`
105
+ and `512MiB`; numeric CPU values are cores and numeric memory values are bytes
106
+ - `limitCpu`, `limitMemory`: runtime cgroup resource limits such as `1` and
107
+ `1GiB`; numeric CPU values are cores and numeric memory values are bytes
108
+ - `readyTimeoutMs`: service replica readiness timeout
109
+
110
+ Tunnel options:
111
+
112
+ - `tunnel.upstream`: local TCP address reached by the SDK connector, for
113
+ example `127.0.0.1:8080`
114
+ - `tunnel.proxyPort`: sandbox-local port bound by Axern, default `8786`
115
+ - `tunnel.ttlSeconds`: control-plane tunnel session TTL, renewed by the SDK
116
+
117
+ Tunnel traffic reuses the client gateway endpoint, mTLS identity, server name,
118
+ and proxy policy.
119
+
120
+ After `start()`, use `sandbox.metadata.tunnel?.boundAddr` from inside the
121
+ sandbox, for example `http://127.0.0.1:8786`.
122
+
123
+ ## Sandbox API
124
+
125
+ - Lifecycle: `start()`, `close()`, `state`, `metadata`
126
+ - Execution: `exec(command, options)`, `process(command, options)`
127
+ - Agent sandbox: `capabilityStatus`, `computerUseStatus`,
128
+ `computerUseScreenshot`, `computerUseDisplay`, `computerUseMouse`,
129
+ `computerUseKeyboard`
130
+ - Files: `readFile`, `readText`, `writeFile`, `writeText`, `stat`, `listDir`,
131
+ `exists`, `mkdir`, `remove`, `copy`, `move`, `chmod`, `touch`
132
+ - Directories: `uploadDir(localPath, remotePath)`,
133
+ `downloadDir(remotePath, localPath)`
134
+ - Tunnel: `new Sandbox({ tunnel: { upstream, proxyPort } })`,
135
+ `metadata.tunnel`
136
+ - Errors: `AxernRpcError`, `SandboxExecError`, `SandboxStateError`,
137
+ `SandboxValidationError`, `isNotFound`, `isPermissionDenied`, `isTimeout`,
138
+ `rpcCode`
139
+
140
+ ## Local Smoke
141
+
142
+ With the local compose stack running, verify the real SDK path with:
143
+
144
+ ```bash
145
+ pnpm --filter @cofy-x/axern-sdk run smoke:local
146
+ pnpm --filter @cofy-x/axern-sdk run smoke:tunnel
147
+ ```
148
+
149
+ The smoke loads `deploy/local/state/compose/axern.env` when present. It uses the
150
+ `python311` template by default; set `AXERN_TS_SMOKE_IMAGE` to verify an image
151
+ source instead.
152
+
153
+ Check package contents with:
154
+
155
+ ```bash
156
+ pnpm --filter @cofy-x/axern-sdk run pack:dry-run
157
+ ```
158
+
159
+ ## Proto Boundary
160
+
161
+ The SDK loads protobuf definitions through `@grpc/proto-loader`. Dynamic proto
162
+ access remains isolated under `src/generated`; public callers depend only on
163
+ the stable TypeScript DTOs and error types.
164
+
165
+ ## Scope
166
+
167
+ This SDK is Node.js-first. The higher-level Browser API, generated TypeScript
168
+ proto stubs, and full control-plane administration APIs remain outside the
169
+ v0.2 public contract.
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 cofy-x
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import * as grpc from "@grpc/grpc-js";
7
+ import { NodeSandboxClient } from "../node/client.js";
8
+ import type { ResourceQuantity } from "../resources.js";
9
+ import { TunnelControlClient } from "../tunnel/control.js";
10
+ import type { VolumeMount } from "../types.js";
11
+ import type { GatewayTransportOptions } from "../tunnel/relay.js";
12
+ export interface AxernClientOptions {
13
+ endpoint: string;
14
+ credentials?: grpc.ChannelCredentials;
15
+ tlsCaCert?: string;
16
+ tlsCert?: string;
17
+ tlsKey?: string;
18
+ tlsServerName?: string;
19
+ proxyMode?: "env" | "direct";
20
+ }
21
+ export interface CreateEnvironmentOptions {
22
+ namespace?: string;
23
+ templateId?: string;
24
+ image?: string;
25
+ registryCredentialId?: string;
26
+ rootfsReadonly?: boolean;
27
+ labels?: Record<string, string>;
28
+ }
29
+ export interface CreateServiceOptions {
30
+ namespace?: string;
31
+ environmentId: string;
32
+ argv?: string[];
33
+ env?: Record<string, string>;
34
+ cwd?: string;
35
+ runtimeClass?: string;
36
+ volumes?: readonly VolumeMount[];
37
+ requestCpu?: ResourceQuantity;
38
+ requestMemory?: ResourceQuantity;
39
+ limitCpu?: ResourceQuantity;
40
+ limitMemory?: ResourceQuantity;
41
+ labels?: Record<string, string>;
42
+ }
43
+ export declare class AxernClient {
44
+ readonly endpoint: string;
45
+ private readonly credentials;
46
+ private readonly controlOptions;
47
+ private readonly environmentControl;
48
+ private readonly serviceControl;
49
+ private readonly tunnelControl;
50
+ private readonly gatewayTransport;
51
+ constructor(options: AxernClientOptions);
52
+ static fromEnv(overrides?: Partial<AxernClientOptions>): AxernClient;
53
+ static fromContext(path: string, name?: string): AxernClient;
54
+ close(): void;
55
+ createEnvironment(options: CreateEnvironmentOptions): Promise<Record<string, unknown>>;
56
+ deleteEnvironment(environmentId: string): Promise<void>;
57
+ createService(options: CreateServiceOptions): Promise<Record<string, unknown>>;
58
+ deleteService(serviceId: string): Promise<void>;
59
+ listServiceReplicas(serviceId: string): Promise<Record<string, unknown>[]>;
60
+ nodeSandbox(allocationId: string): NodeSandboxClient;
61
+ tunnelClient(): TunnelControlClient;
62
+ tunnelTransport(): GatewayTransportOptions;
63
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 cofy-x
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import * as grpc from "@grpc/grpc-js";
7
+ import { readFileSync } from "node:fs";
8
+ import { loadAxernContext, loadAxernEnv, normalizeProxyMode } from "../config/index.js";
9
+ import { mapRpcError } from "../errors/index.js";
10
+ import { serviceConstructor, unary } from "../generated/proto.js";
11
+ import { NodeSandboxClient } from "../node/client.js";
12
+ import { buildResourceSpec } from "../resources.js";
13
+ import { TunnelControlClient } from "../tunnel/control.js";
14
+ import { required } from "../validation.js";
15
+ export class AxernClient {
16
+ endpoint;
17
+ credentials;
18
+ controlOptions;
19
+ environmentControl;
20
+ serviceControl;
21
+ tunnelControl;
22
+ gatewayTransport;
23
+ constructor(options) {
24
+ this.endpoint = required("endpoint", options.endpoint);
25
+ const proxyMode = normalizeProxyMode(options.proxyMode);
26
+ const tlsConfigured = tlsOptionsConfigured({
27
+ tlsCaCert: options.tlsCaCert,
28
+ tlsCert: options.tlsCert,
29
+ tlsKey: options.tlsKey,
30
+ });
31
+ const tlsServerName = options.tlsServerName ?? defaultLocalTlsServerName(this.endpoint, tlsConfigured);
32
+ this.credentials = options.credentials ?? controlCredentials({
33
+ tlsCaCert: options.tlsCaCert,
34
+ tlsCert: options.tlsCert,
35
+ tlsKey: options.tlsKey,
36
+ });
37
+ this.controlOptions = tlsServerName === undefined || tlsServerName === ""
38
+ ? {}
39
+ : { "grpc.ssl_target_name_override": tlsServerName };
40
+ if (proxyMode === "direct") {
41
+ this.controlOptions["grpc.enable_http_proxy"] = 0;
42
+ }
43
+ this.gatewayTransport = {
44
+ insecure: !tlsConfigured,
45
+ tlsCaCert: options.tlsCaCert,
46
+ tlsCert: options.tlsCert,
47
+ tlsKey: options.tlsKey,
48
+ serverName: tlsServerName,
49
+ proxyMode,
50
+ };
51
+ const EnvironmentControl = serviceConstructor([
52
+ "axern",
53
+ "control",
54
+ "environment",
55
+ "v1",
56
+ "EnvironmentControl",
57
+ ]);
58
+ const ServiceControl = serviceConstructor(["axern", "control", "service", "v1", "ServiceControl"]);
59
+ const TunnelControl = serviceConstructor(["axern", "control", "tunnel", "v1", "TunnelControl"]);
60
+ this.environmentControl = new EnvironmentControl(this.endpoint, this.credentials, this.controlOptions);
61
+ this.serviceControl = new ServiceControl(this.endpoint, this.credentials, this.controlOptions);
62
+ this.tunnelControl = new TunnelControl(this.endpoint, this.credentials, this.controlOptions);
63
+ }
64
+ static fromEnv(overrides = {}) {
65
+ const environment = loadAxernEnv();
66
+ return new AxernClient({ ...environment, ...overrides, endpoint: overrides.endpoint ?? environment.endpoint });
67
+ }
68
+ static fromContext(path, name = "") {
69
+ const config = loadAxernContext(path, name);
70
+ return new AxernClient({
71
+ endpoint: config.endpoint,
72
+ tlsCaCert: config.tlsCaCert,
73
+ tlsCert: config.tlsCert,
74
+ tlsKey: config.tlsKey,
75
+ tlsServerName: config.tlsServerName,
76
+ proxyMode: config.proxyMode,
77
+ });
78
+ }
79
+ close() {
80
+ this.environmentControl.close();
81
+ this.serviceControl.close();
82
+ this.tunnelControl.close();
83
+ }
84
+ async createEnvironment(options) {
85
+ const namespace = options.namespace ?? "default";
86
+ const sources = [options.templateId, options.image].filter((value) => value !== undefined && value !== "");
87
+ if (sources.length !== 1) {
88
+ throw new Error("exactly one of templateId or image is required");
89
+ }
90
+ const spec = { namespace };
91
+ if (options.templateId !== undefined && options.templateId !== "") {
92
+ spec.template_id = options.templateId;
93
+ }
94
+ else {
95
+ spec.image = {
96
+ ref: required("image", options.image),
97
+ registry_credential_id: options.registryCredentialId ?? "",
98
+ rootfs_readonly: options.rootfsReadonly ?? false,
99
+ };
100
+ }
101
+ try {
102
+ const response = await unary(this.environmentControl, "CreateEnvironment", { spec, labels: options.labels ?? {} });
103
+ return response.environment;
104
+ }
105
+ catch (error) {
106
+ throw mapRpcError(error, "create environment");
107
+ }
108
+ }
109
+ async deleteEnvironment(environmentId) {
110
+ try {
111
+ await unary(this.environmentControl, "DeleteEnvironment", { environment_id: required("environmentId", environmentId) });
112
+ }
113
+ catch (error) {
114
+ throw mapRpcError(error, "delete environment");
115
+ }
116
+ }
117
+ async createService(options) {
118
+ const resources = buildResourceSpec(options);
119
+ try {
120
+ const response = await unary(this.serviceControl, "CreateService", {
121
+ namespace: options.namespace ?? "default",
122
+ environment_id: required("environmentId", options.environmentId),
123
+ replicas: 1,
124
+ config: {
125
+ argv: options.argv ?? [],
126
+ env: options.env ?? {},
127
+ cwd: options.cwd ?? "",
128
+ runtime_class: options.runtimeClass ?? "",
129
+ volume_mounts: serviceVolumeMounts(options.volumes),
130
+ resources,
131
+ },
132
+ labels: options.labels ?? {},
133
+ });
134
+ return response.service;
135
+ }
136
+ catch (error) {
137
+ throw mapRpcError(error, "create service");
138
+ }
139
+ }
140
+ async deleteService(serviceId) {
141
+ try {
142
+ await unary(this.serviceControl, "DeleteService", { service_id: required("serviceId", serviceId) });
143
+ }
144
+ catch (error) {
145
+ throw mapRpcError(error, "delete service");
146
+ }
147
+ }
148
+ async listServiceReplicas(serviceId) {
149
+ try {
150
+ const response = await unary(this.serviceControl, "ListServiceReplicas", {
151
+ service_id: required("serviceId", serviceId),
152
+ filter: { view: 2 },
153
+ });
154
+ return response.replicas ?? [];
155
+ }
156
+ catch (error) {
157
+ throw mapRpcError(error, "list service replicas");
158
+ }
159
+ }
160
+ nodeSandbox(allocationId) {
161
+ return new NodeSandboxClient({
162
+ allocationId: required("allocationId", allocationId),
163
+ target: this.endpoint,
164
+ credentials: this.credentials,
165
+ channelOptions: this.controlOptions,
166
+ });
167
+ }
168
+ tunnelClient() {
169
+ return new TunnelControlClient(this.tunnelControl);
170
+ }
171
+ tunnelTransport() {
172
+ return { ...this.gatewayTransport };
173
+ }
174
+ }
175
+ function serviceVolumeMounts(mounts) {
176
+ return (mounts ?? []).map((mount) => ({
177
+ name: mount.name,
178
+ target: mount.target,
179
+ readonly: mount.readonly ?? false,
180
+ options: [...(mount.options ?? [])],
181
+ }));
182
+ }
183
+ function controlCredentials(options) {
184
+ const configured = [options.tlsCaCert, options.tlsCert, options.tlsKey].filter((value) => value !== undefined && value !== "");
185
+ if (configured.length === 0) {
186
+ return grpc.credentials.createInsecure();
187
+ }
188
+ if (configured.length !== 3) {
189
+ throw new Error("mTLS requires tlsCaCert, tlsCert, and tlsKey");
190
+ }
191
+ const rootCerts = readFileSync(options.tlsCaCert);
192
+ const certChain = readFileSync(options.tlsCert);
193
+ const privateKey = readFileSync(options.tlsKey);
194
+ return grpc.credentials.createSsl(rootCerts, privateKey, certChain);
195
+ }
196
+ function tlsOptionsConfigured(options) {
197
+ return [options.tlsCaCert, options.tlsCert, options.tlsKey].some((value) => value !== undefined && value !== "");
198
+ }
199
+ function defaultLocalTlsServerName(target, tlsConfigured) {
200
+ if (!tlsConfigured) {
201
+ return undefined;
202
+ }
203
+ const host = targetHost(target);
204
+ return host === "127.0.0.1" || host === "::1" ? "localhost" : undefined;
205
+ }
206
+ function targetHost(target) {
207
+ const withoutScheme = target.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
208
+ if (withoutScheme.startsWith("[")) {
209
+ const end = withoutScheme.indexOf("]");
210
+ return end === -1 ? withoutScheme : withoutScheme.slice(1, end);
211
+ }
212
+ return withoutScheme.split(":", 1)[0] ?? withoutScheme;
213
+ }
@@ -0,0 +1,15 @@
1
+ /** Explicit environment and context loaders for the Axern SDK. */
2
+ export interface AxernConfig {
3
+ endpoint: string;
4
+ serviceUrl?: string;
5
+ sshEndpoint?: string;
6
+ sshIdentityFile?: string;
7
+ tlsCaCert?: string;
8
+ tlsCert?: string;
9
+ tlsKey?: string;
10
+ tlsServerName?: string;
11
+ proxyMode: "env" | "direct";
12
+ }
13
+ export declare function loadAxernEnv(overrides?: Partial<AxernConfig>): AxernConfig;
14
+ export declare function loadAxernContext(path: string, name?: string): AxernConfig;
15
+ export declare function normalizeProxyMode(value: string | undefined): "env" | "direct";
@@ -0,0 +1,65 @@
1
+ /** Explicit environment and context loaders for the Axern SDK. */
2
+ import { readFileSync } from "node:fs";
3
+ export function loadAxernEnv(overrides = {}) {
4
+ return {
5
+ endpoint: overrides.endpoint ?? process.env.AXERN_ENDPOINT ?? "127.0.0.1:25000",
6
+ serviceUrl: overrides.serviceUrl ?? process.env.AXERN_SERVICE_URL,
7
+ sshEndpoint: overrides.sshEndpoint ?? process.env.AXERN_SSH_ENDPOINT,
8
+ sshIdentityFile: overrides.sshIdentityFile ?? process.env.AXERN_SSH_IDENTITY_FILE,
9
+ tlsCaCert: overrides.tlsCaCert ?? process.env.AXERN_TLS_CA_CERT,
10
+ tlsCert: overrides.tlsCert ?? process.env.AXERN_TLS_CERT,
11
+ tlsKey: overrides.tlsKey ?? process.env.AXERN_TLS_KEY,
12
+ tlsServerName: overrides.tlsServerName ?? process.env.AXERN_TLS_SERVER_NAME,
13
+ proxyMode: normalizeProxyMode(overrides.proxyMode ?? process.env.AXERN_PROXY_MODE),
14
+ };
15
+ }
16
+ export function loadAxernContext(path, name = "") {
17
+ const file = JSON.parse(readFileSync(path, "utf8"));
18
+ rejectUnknown(file, ["current_context", "contexts", "agent_profiles"], "config");
19
+ const contextName = name || stringValue(file.current_context);
20
+ if (!contextName)
21
+ throw new Error("Axern context name is required");
22
+ const contexts = objectValue(file.contexts, "contexts");
23
+ const context = objectValue(contexts[contextName], `context ${JSON.stringify(contextName)}`);
24
+ rejectUnknown(context, ["endpoint", "service_url", "ssh_endpoint", "ssh_identity_file", "tls", "proxy_mode"], "context");
25
+ const tls = objectValue(context.tls, "context.tls");
26
+ rejectUnknown(tls, ["ca_cert", "cert", "key", "server_name"], "context.tls");
27
+ const config = {
28
+ endpoint: stringValue(context.endpoint),
29
+ serviceUrl: stringValue(context.service_url) || undefined,
30
+ sshEndpoint: stringValue(context.ssh_endpoint) || undefined,
31
+ sshIdentityFile: stringValue(context.ssh_identity_file) || undefined,
32
+ tlsCaCert: stringValue(tls.ca_cert),
33
+ tlsCert: stringValue(tls.cert),
34
+ tlsKey: stringValue(tls.key),
35
+ tlsServerName: stringValue(tls.server_name) || undefined,
36
+ proxyMode: normalizeProxyMode(stringValue(context.proxy_mode)),
37
+ };
38
+ if (!config.endpoint || !config.tlsCaCert || !config.tlsCert || !config.tlsKey) {
39
+ throw new Error("context requires endpoint and tls.ca_cert, tls.cert, and tls.key");
40
+ }
41
+ return config;
42
+ }
43
+ export function normalizeProxyMode(value) {
44
+ const mode = value?.trim() || "env";
45
+ if (mode !== "env" && mode !== "direct")
46
+ throw new Error("proxy_mode must be 'env' or 'direct'");
47
+ return mode;
48
+ }
49
+ function objectValue(value, path) {
50
+ if (typeof value !== "object" || value === null || Array.isArray(value))
51
+ throw new Error(`${path} must be an object`);
52
+ return value;
53
+ }
54
+ function stringValue(value) {
55
+ if (value === undefined)
56
+ return "";
57
+ if (typeof value !== "string")
58
+ throw new Error("context string field has invalid type");
59
+ return value.trim();
60
+ }
61
+ function rejectUnknown(value, allowed, path) {
62
+ const unknown = Object.keys(value).filter((key) => !allowed.includes(key)).sort();
63
+ if (unknown.length > 0)
64
+ throw new Error(`${path} contains unknown field ${JSON.stringify(unknown[0])}`);
65
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 cofy-x
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export declare class AxernError extends Error {
7
+ constructor(message: string, options?: ErrorOptions);
8
+ }
9
+ export declare class AxernRpcError extends AxernError {
10
+ readonly code?: number | string;
11
+ readonly operation: string;
12
+ readonly allocationId?: string;
13
+ readonly details: string;
14
+ readonly retryable: boolean;
15
+ constructor(operation: string, cause: unknown, options?: {
16
+ allocationId?: string;
17
+ });
18
+ }
19
+ export declare class SandboxTimeoutError extends AxernError {
20
+ constructor(message: string);
21
+ }
22
+ export declare class SandboxStateError extends AxernError {
23
+ constructor(message: string);
24
+ }
25
+ export declare class SandboxValidationError extends AxernError {
26
+ constructor(message: string);
27
+ }
28
+ export declare class SandboxExecError extends AxernError {
29
+ readonly argv: string[];
30
+ readonly exitCode: number;
31
+ readonly stdout: Buffer;
32
+ readonly stderr: Buffer;
33
+ constructor(argv: string[], result: {
34
+ exitCode: number;
35
+ stdout: Buffer;
36
+ stderr: Buffer;
37
+ });
38
+ }
39
+ export declare function mapRpcError(error: unknown, operation: string, allocationId?: string): AxernRpcError;
40
+ export declare function isNotFound(error: unknown): boolean;
41
+ export declare function isPermissionDenied(error: unknown): boolean;
42
+ export declare function isTimeout(error: unknown): boolean;
43
+ export declare function isCancelled(error: unknown): boolean;
44
+ export declare function isUnavailable(error: unknown): boolean;
45
+ export declare function errorRetryable(error: unknown): boolean;
46
+ export declare function rpcCode(error: unknown): number | undefined;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 cofy-x
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export class AxernError extends Error {
7
+ constructor(message, options) {
8
+ super(message, options);
9
+ this.name = "AxernError";
10
+ }
11
+ }
12
+ export class AxernRpcError extends AxernError {
13
+ code;
14
+ operation;
15
+ allocationId;
16
+ details;
17
+ retryable;
18
+ constructor(operation, cause, options = {}) {
19
+ const detail = errorDetail(cause);
20
+ super(`${operation} failed${detail ? `: ${detail}` : ""}`, { cause });
21
+ this.name = "AxernRpcError";
22
+ this.operation = operation;
23
+ this.allocationId = options.allocationId;
24
+ this.details = detail;
25
+ this.code = typeof cause === "object" && cause !== null && "code" in cause
26
+ ? String(cause.code)
27
+ : undefined;
28
+ this.retryable = isRetryableRpcCode(rpcCode(this));
29
+ }
30
+ }
31
+ export class SandboxTimeoutError extends AxernError {
32
+ constructor(message) {
33
+ super(message);
34
+ this.name = "SandboxTimeoutError";
35
+ }
36
+ }
37
+ export class SandboxStateError extends AxernError {
38
+ constructor(message) {
39
+ super(message);
40
+ this.name = "SandboxStateError";
41
+ }
42
+ }
43
+ export class SandboxValidationError extends AxernError {
44
+ constructor(message) {
45
+ super(message);
46
+ this.name = "SandboxValidationError";
47
+ }
48
+ }
49
+ export class SandboxExecError extends AxernError {
50
+ argv;
51
+ exitCode;
52
+ stdout;
53
+ stderr;
54
+ constructor(argv, result) {
55
+ super(`sandbox command exited with code ${result.exitCode}: ${argv.join(" ")}`);
56
+ this.name = "SandboxExecError";
57
+ this.argv = [...argv];
58
+ this.exitCode = result.exitCode;
59
+ this.stdout = result.stdout;
60
+ this.stderr = result.stderr;
61
+ }
62
+ }
63
+ export function mapRpcError(error, operation, allocationId) {
64
+ return new AxernRpcError(operation, error, { allocationId });
65
+ }
66
+ export function isNotFound(error) {
67
+ return rpcCode(error) === 5;
68
+ }
69
+ export function isPermissionDenied(error) {
70
+ const code = rpcCode(error);
71
+ return code === 7 || code === 16;
72
+ }
73
+ export function isTimeout(error) {
74
+ return error instanceof SandboxTimeoutError || rpcCode(error) === 4;
75
+ }
76
+ export function isCancelled(error) {
77
+ return rpcCode(error) === 1;
78
+ }
79
+ export function isUnavailable(error) {
80
+ return rpcCode(error) === 14;
81
+ }
82
+ export function errorRetryable(error) {
83
+ if (error instanceof AxernRpcError) {
84
+ return error.retryable;
85
+ }
86
+ return isRetryableRpcCode(rpcCode(error));
87
+ }
88
+ export function rpcCode(error) {
89
+ const code = rawRpcCode(error);
90
+ if (code === undefined) {
91
+ return undefined;
92
+ }
93
+ const numeric = Number(code);
94
+ return Number.isNaN(numeric) ? undefined : numeric;
95
+ }
96
+ function rawRpcCode(error) {
97
+ if (typeof error !== "object" || error === null) {
98
+ return undefined;
99
+ }
100
+ const candidate = error;
101
+ if (candidate.code !== undefined) {
102
+ return candidate.code;
103
+ }
104
+ return rawRpcCode(candidate.cause);
105
+ }
106
+ function isRetryableRpcCode(code) {
107
+ return code === 4 || code === 14;
108
+ }
109
+ function errorDetail(error) {
110
+ if (typeof error === "object" && error !== null) {
111
+ const maybeDetails = error;
112
+ if (typeof maybeDetails.details === "string" && maybeDetails.details.length > 0) {
113
+ return maybeDetails.details;
114
+ }
115
+ if (typeof maybeDetails.message === "string" && maybeDetails.message.length > 0) {
116
+ return maybeDetails.message;
117
+ }
118
+ }
119
+ if (error instanceof Error) {
120
+ return error.message;
121
+ }
122
+ return "";
123
+ }