@cofy-x/axern-sdk 0.3.1 → 0.4.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.
@@ -40,11 +40,16 @@ export interface CreateServiceOptions {
40
40
  limitMemory?: ResourceQuantity;
41
41
  labels?: Record<string, string>;
42
42
  }
43
+ export interface ReadRunOutputOptions {
44
+ cursor?: string;
45
+ follow?: boolean;
46
+ }
43
47
  export declare class AxernClient {
44
48
  readonly endpoint: string;
45
49
  private readonly credentials;
46
50
  private readonly controlOptions;
47
51
  private readonly environmentControl;
52
+ private readonly runControl;
48
53
  private readonly serviceControl;
49
54
  private readonly tunnelControl;
50
55
  private readonly gatewayTransport;
@@ -54,6 +59,8 @@ export declare class AxernClient {
54
59
  close(): void;
55
60
  createEnvironment(options: CreateEnvironmentOptions): Promise<Record<string, unknown>>;
56
61
  deleteEnvironment(environmentId: string): Promise<void>;
62
+ watchRun(runId: string, afterVersion?: number): AsyncGenerator<Record<string, unknown>>;
63
+ readRunOutput(runId: string, options?: ReadRunOutputOptions): AsyncGenerator<Record<string, unknown>>;
57
64
  createService(options: CreateServiceOptions): Promise<Record<string, unknown>>;
58
65
  deleteService(serviceId: string): Promise<void>;
59
66
  listServiceReplicas(serviceId: string): Promise<Record<string, unknown>[]>;
@@ -17,6 +17,7 @@ export class AxernClient {
17
17
  credentials;
18
18
  controlOptions;
19
19
  environmentControl;
20
+ runControl;
20
21
  serviceControl;
21
22
  tunnelControl;
22
23
  gatewayTransport;
@@ -55,9 +56,11 @@ export class AxernClient {
55
56
  "v1",
56
57
  "EnvironmentControl",
57
58
  ]);
59
+ const RunControl = serviceConstructor(["axern", "control", "run", "v1", "RunControl"]);
58
60
  const ServiceControl = serviceConstructor(["axern", "control", "service", "v1", "ServiceControl"]);
59
61
  const TunnelControl = serviceConstructor(["axern", "control", "tunnel", "v1", "TunnelControl"]);
60
62
  this.environmentControl = new EnvironmentControl(this.endpoint, this.credentials, this.controlOptions);
63
+ this.runControl = new RunControl(this.endpoint, this.credentials, this.controlOptions);
61
64
  this.serviceControl = new ServiceControl(this.endpoint, this.credentials, this.controlOptions);
62
65
  this.tunnelControl = new TunnelControl(this.endpoint, this.credentials, this.controlOptions);
63
66
  }
@@ -78,6 +81,7 @@ export class AxernClient {
78
81
  }
79
82
  close() {
80
83
  this.environmentControl.close();
84
+ this.runControl.close();
81
85
  this.serviceControl.close();
82
86
  this.tunnelControl.close();
83
87
  }
@@ -114,6 +118,83 @@ export class AxernClient {
114
118
  throw mapRpcError(error, "delete environment");
115
119
  }
116
120
  }
121
+ async *watchRun(runId, afterVersion = 0) {
122
+ if (afterVersion < 0) {
123
+ throw new Error("afterVersion must be non-negative");
124
+ }
125
+ let version = afterVersion;
126
+ let retryDelayMs = 100;
127
+ for (;;) {
128
+ const stream = serverStream(this.runControl, "WatchRun", {
129
+ run_id: required("runId", runId),
130
+ after_version: version,
131
+ });
132
+ try {
133
+ for await (const response of stream) {
134
+ const run = response.run;
135
+ if (run === undefined)
136
+ continue;
137
+ const nextVersion = Number(run.version ?? 0);
138
+ if (nextVersion <= version)
139
+ continue;
140
+ version = nextVersion;
141
+ retryDelayMs = 100;
142
+ yield run;
143
+ }
144
+ return;
145
+ }
146
+ catch (error) {
147
+ if (!transientReadError(error))
148
+ throw mapRpcError(error, "watch run");
149
+ }
150
+ await sleep(retryDelayMs);
151
+ retryDelayMs = Math.min(retryDelayMs * 2, 2_000);
152
+ }
153
+ }
154
+ async *readRunOutput(runId, options = {}) {
155
+ const response = await unary(this.runControl, "GetRun", { run_id: required("runId", runId) });
156
+ const allocationId = String(response.run?.allocation_id ?? "");
157
+ if (allocationId === "")
158
+ throw new Error(`run ${runId} output is not available yet`);
159
+ let cursor = options.cursor ?? "";
160
+ let retryDelayMs = 100;
161
+ let notFoundSince = 0;
162
+ for (;;) {
163
+ const NodeSandbox = serviceConstructor(["axern", "node", "sandbox", "v1", "NodeSandbox"]);
164
+ const node = new NodeSandbox(this.endpoint, this.credentials, this.controlOptions);
165
+ const stream = serverStream(node, "ReadOutput", {
166
+ allocation_id: allocationId,
167
+ cursor,
168
+ follow: options.follow ?? false,
169
+ });
170
+ try {
171
+ for await (const event of stream) {
172
+ cursor = String(event.next_cursor ?? cursor);
173
+ retryDelayMs = 100;
174
+ notFoundSince = 0;
175
+ yield event;
176
+ }
177
+ return;
178
+ }
179
+ catch (error) {
180
+ const code = error.code;
181
+ const startupNotFound = code === grpc.status.NOT_FOUND;
182
+ if (!(options.follow ?? false) || (!transientReadError(error) && !startupNotFound)) {
183
+ throw mapRpcError(error, "read run output");
184
+ }
185
+ if (startupNotFound) {
186
+ notFoundSince ||= Date.now();
187
+ if (Date.now() - notFoundSince >= 30_000)
188
+ throw mapRpcError(error, "read run output");
189
+ }
190
+ }
191
+ finally {
192
+ node.close();
193
+ }
194
+ await sleep(retryDelayMs);
195
+ retryDelayMs = Math.min(retryDelayMs * 2, 2_000);
196
+ }
197
+ }
117
198
  async createService(options) {
118
199
  const resources = buildResourceSpec(options);
119
200
  try {
@@ -180,6 +261,17 @@ function serviceVolumeMounts(mounts) {
180
261
  options: [...(mount.options ?? [])],
181
262
  }));
182
263
  }
264
+ function serverStream(client, method, request) {
265
+ const fn = client[method];
266
+ return fn.call(client, request);
267
+ }
268
+ function transientReadError(error) {
269
+ const code = error.code;
270
+ return code === grpc.status.UNAVAILABLE || code === grpc.status.DEADLINE_EXCEEDED;
271
+ }
272
+ function sleep(milliseconds) {
273
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
274
+ }
183
275
  function controlCredentials(options) {
184
276
  const configured = [options.tlsCaCert, options.tlsCert, options.tlsKey].filter((value) => value !== undefined && value !== "");
185
277
  if (configured.length === 0) {
@@ -15,6 +15,7 @@ const defaultProtoRootCandidates = [
15
15
  ];
16
16
  const protoFiles = [
17
17
  "axern/control/environment/v1/environment.proto",
18
+ "axern/control/run/v1/run.proto",
18
19
  "axern/control/service/v1/service.proto",
19
20
  "axern/control/tunnel/v1/tunnel.proto",
20
21
  "axern/node/sandbox/v1/node.proto",
@@ -44,6 +44,13 @@ message ResolveAllocationTerminalRequest {
44
44
  int64 ttl_seconds = 2;
45
45
  string client_certificate_fingerprint = 3;
46
46
  string rollout_execution_lease = 4;
47
+ AllocationAccessPurpose purpose = 5;
48
+ }
49
+
50
+ enum AllocationAccessPurpose {
51
+ ALLOCATION_ACCESS_PURPOSE_UNSPECIFIED = 0;
52
+ ALLOCATION_ACCESS_PURPOSE_INTERACTIVE = 1;
53
+ ALLOCATION_ACCESS_PURPOSE_RUN_OUTPUT = 2;
47
54
  }
48
55
 
49
56
  message ResolveAllocationTerminalResponse {
@@ -34,6 +34,9 @@ message Run {
34
34
  bool exit_code_known = 13;
35
35
  string message = 14;
36
36
  axern.control.common.v1.WorkloadDiagnosticCode diagnostic_code = 15;
37
+ // Reserved for durable output metadata once output retention is owned by
38
+ // the control plane rather than inferred from a node-local allocation.
39
+ reserved 16 to 18;
37
40
  }
38
41
 
39
42
  message RunListFilter {
@@ -63,6 +66,15 @@ message GetRunResponse {
63
66
  Run run = 1;
64
67
  }
65
68
 
69
+ message WatchRunRequest {
70
+ string run_id = 1;
71
+ int64 after_version = 2;
72
+ }
73
+
74
+ message WatchRunResponse {
75
+ Run run = 1;
76
+ }
77
+
66
78
  message ListRunsRequest {
67
79
  RunListFilter filter = 1;
68
80
  }
@@ -83,6 +95,7 @@ message CancelRunResponse {
83
95
  service RunControl {
84
96
  rpc CreateRun(CreateRunRequest) returns (CreateRunResponse) {}
85
97
  rpc GetRun(GetRunRequest) returns (GetRunResponse) {}
98
+ rpc WatchRun(WatchRunRequest) returns (stream WatchRunResponse) {}
86
99
  rpc ListRuns(ListRunsRequest) returns (ListRunsResponse) {}
87
100
  rpc CancelRun(CancelRunRequest) returns (CancelRunResponse) {}
88
101
  }
@@ -34,6 +34,7 @@ message SecretListFilter {
34
34
  message CreateSecretRequest {
35
35
  string namespace = 1;
36
36
  SecretType type = 2;
37
+ // The JSON-encoded map must not exceed 64 KiB. Values are preserved verbatim.
37
38
  map<string, string> string_data = 3;
38
39
  map<string, string> labels = 4;
39
40
  }
@@ -199,6 +199,29 @@ message WaitSandboxResponse {
199
199
  string message = 4;
200
200
  }
201
201
 
202
+ enum OutputStream {
203
+ OUTPUT_STREAM_UNSPECIFIED = 0;
204
+ OUTPUT_STREAM_STDOUT = 1;
205
+ OUTPUT_STREAM_STDERR = 2;
206
+ }
207
+
208
+ message ReadOutputRequest {
209
+ string allocation_id = 1;
210
+ int64 attempt = 2;
211
+ string execution_lease_token = 3;
212
+ string cursor = 4;
213
+ bool follow = 5;
214
+ }
215
+
216
+ message ReadOutputResponse {
217
+ OutputStream stream = 1;
218
+ bytes data = 2;
219
+ string next_cursor = 3;
220
+ bool terminal = 4;
221
+ bool truncated = 5;
222
+ int64 observed_at_unix_milli = 6;
223
+ }
224
+
202
225
  // Capabilities
203
226
 
204
227
  message CapabilityStatusRequest {
@@ -620,6 +643,7 @@ service NodeSandbox {
620
643
  rpc ExecImage(ExecImageRequest) returns (ExecImageResponse) {}
621
644
  rpc ProcessImage(stream ProcessImageRequest) returns (stream ProcessImageResponse) {}
622
645
  rpc WaitSandbox(WaitSandboxRequest) returns (WaitSandboxResponse) {}
646
+ rpc ReadOutput(ReadOutputRequest) returns (stream ReadOutputResponse) {}
623
647
  rpc CapabilityStatus(CapabilityStatusRequest) returns (CapabilityStatusResponse) {}
624
648
  rpc ProxyHTTP(stream ProxyHTTPRequest) returns (stream ProxyHTTPResponse) {}
625
649
  rpc StatFile(StatFileRequest) returns (StatFileResponse) {}
package/dist/version.d.ts CHANGED
@@ -3,5 +3,5 @@
3
3
  * Copyright 2026 cofy-x
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- export declare const AXERN_VERSION = "0.3.1";
6
+ export declare const AXERN_VERSION = "0.4.0";
7
7
  export declare function platformName(): string;
package/dist/version.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Copyright 2026 cofy-x
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- export const AXERN_VERSION = "0.3.1";
6
+ export const AXERN_VERSION = "0.4.0";
7
7
  export function platformName() {
8
8
  return "axern";
9
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cofy-x/axern-sdk",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "TypeScript SDK for Axern agentic infrastructure and isolated sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {