@aexhq/sdk 0.40.9 → 0.40.11

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.
@@ -18,6 +18,17 @@ export interface HttpClientOptions {
18
18
  readonly fetch?: FetchLike;
19
19
  /** When set, every request emits a redacted one-line trace here. */
20
20
  readonly debug?: DebugSink;
21
+ /**
22
+ * Retry transient transport failures for idempotent GET/HEAD requests.
23
+ * Disabled by default; host CLIs enable this so a single dropped API
24
+ * connection does not fail read-only commands.
25
+ */
26
+ readonly retryTransientGets?: boolean | TransientGetRetryOptions;
27
+ }
28
+ export interface TransientGetRetryOptions {
29
+ readonly maxAttempts?: number;
30
+ readonly baseDelayMs?: number;
31
+ readonly sleep?: (ms: number) => Promise<void>;
21
32
  }
22
33
  /**
23
34
  * Thin transport used by every BFF-bound operation. The SDK class and
@@ -1,4 +1,4 @@
1
- import { AexError, AexNetworkError, redactUrl } from "./sdk-errors.js";
1
+ import { AexError, AexNetworkError, extractErrorCode, redactUrl } from "./sdk-errors.js";
2
2
  import { apiErrorFromResponse } from "./error-factory.js";
3
3
  import { AEX_DEFAULT_BASE_URL } from "./stable.js";
4
4
  /**
@@ -12,6 +12,7 @@ export class HttpClient {
12
12
  #apiKey;
13
13
  #fetch;
14
14
  #debug;
15
+ #retryTransientGets;
15
16
  constructor(options) {
16
17
  if (!options.apiKey) {
17
18
  throw new Error("HttpClient: apiKey is required");
@@ -28,6 +29,7 @@ export class HttpClient {
28
29
  this.#apiKey = options.apiKey;
29
30
  this.#fetch = options.fetch ?? fetch;
30
31
  this.#debug = options.debug;
32
+ this.#retryTransientGets = resolveTransientGetRetry(options.retryTransientGets);
31
33
  }
32
34
  /** Emit a redacted round-trip trace (no auth header, body, or query). */
33
35
  #trace(method, url, status, startedMs) {
@@ -52,25 +54,34 @@ export class HttpClient {
52
54
  headers["content-type"] = "application/json";
53
55
  }
54
56
  }
55
- const startedMs = Date.now();
56
- let response;
57
- try {
58
- response = await this.#fetch(url, { ...init, headers });
59
- }
60
- catch (err) {
61
- throw toNetworkError(init.method, url, err);
62
- }
63
- this.#trace(init.method, url, response.status, startedMs);
64
- const body = await readJson(response);
65
- if (!response.ok) {
66
- const errorBody = withResponseRequestId(body, response.headers);
67
- throw apiErrorFromResponse({
68
- status: response.status,
69
- body: errorBody,
70
- message: extractErrorMessage(errorBody)
71
- });
57
+ const method = methodOf(init.method);
58
+ const retry = retryForMethod(method, this.#retryTransientGets);
59
+ const requestStartedMs = Date.now();
60
+ for (let attempt = 1; attempt <= retry.maxAttempts; attempt += 1) {
61
+ const startedMs = Date.now();
62
+ try {
63
+ const response = await this.#fetch(url, { ...init, headers });
64
+ this.#trace(method, url, response.status, startedMs);
65
+ const body = await readJson(response);
66
+ if (!response.ok) {
67
+ const errorBody = withResponseRequestId(body, response.headers);
68
+ throw apiErrorFromResponse({
69
+ status: response.status,
70
+ body: errorBody,
71
+ message: extractErrorMessage(errorBody)
72
+ });
73
+ }
74
+ return body;
75
+ }
76
+ catch (err) {
77
+ if (shouldRetryTransientRead(err, retry, attempt)) {
78
+ await sleepBeforeRetry(this.#debug, method, url, err, retry, attempt);
79
+ continue;
80
+ }
81
+ throw toNetworkError(method, url, err, retry.maxAttempts > 1 ? attempt : undefined, Date.now() - requestStartedMs);
82
+ }
72
83
  }
73
- return body;
84
+ throw new Error("HttpClient.request retry loop exhausted");
74
85
  }
75
86
  async download(path, init = {}, query = {}) {
76
87
  const url = new URL(path.replace(/^\//, ""), this.#baseUrl);
@@ -81,26 +92,93 @@ export class HttpClient {
81
92
  authorization: `Bearer ${this.#apiKey}`,
82
93
  ...normalizeHeaders(init.headers)
83
94
  };
84
- const startedMs = Date.now();
85
- let response;
86
- try {
87
- response = await this.#fetch(url, { ...init, headers });
88
- }
89
- catch (err) {
90
- throw toNetworkError(init.method, url, err);
91
- }
92
- this.#trace(init.method, url, response.status, startedMs);
93
- if (!response.ok) {
94
- const body = await readJson(response);
95
- const errorBody = withResponseRequestId(body, response.headers);
96
- throw apiErrorFromResponse({
97
- status: response.status,
98
- body: errorBody,
99
- message: extractErrorMessage(errorBody)
100
- });
95
+ const method = methodOf(init.method);
96
+ const retry = retryForMethod(method, this.#retryTransientGets);
97
+ const requestStartedMs = Date.now();
98
+ for (let attempt = 1; attempt <= retry.maxAttempts; attempt += 1) {
99
+ const startedMs = Date.now();
100
+ try {
101
+ const response = await this.#fetch(url, { ...init, headers });
102
+ this.#trace(method, url, response.status, startedMs);
103
+ if (!response.ok) {
104
+ const body = await readJson(response);
105
+ const errorBody = withResponseRequestId(body, response.headers);
106
+ throw apiErrorFromResponse({
107
+ status: response.status,
108
+ body: errorBody,
109
+ message: extractErrorMessage(errorBody)
110
+ });
111
+ }
112
+ return { response };
113
+ }
114
+ catch (err) {
115
+ if (shouldRetryTransientRead(err, retry, attempt)) {
116
+ await sleepBeforeRetry(this.#debug, method, url, err, retry, attempt);
117
+ continue;
118
+ }
119
+ throw toNetworkError(method, url, err, retry.maxAttempts > 1 ? attempt : undefined, Date.now() - requestStartedMs);
120
+ }
101
121
  }
102
- return { response };
122
+ throw new Error("HttpClient.download retry loop exhausted");
123
+ }
124
+ }
125
+ const DEFAULT_TRANSIENT_GET_RETRY = {
126
+ maxAttempts: 3,
127
+ baseDelayMs: 250,
128
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
129
+ };
130
+ const TRANSIENT_READ_CODES = new Set([
131
+ "ConnectionRefused",
132
+ "ECONNRESET",
133
+ "ECONNREFUSED",
134
+ "ETIMEDOUT",
135
+ "EAI_AGAIN",
136
+ "UND_ERR_CONNECT_TIMEOUT",
137
+ "UND_ERR_HEADERS_TIMEOUT",
138
+ "UND_ERR_SOCKET",
139
+ "UND_ERR_BODY_TIMEOUT"
140
+ ]);
141
+ function resolveTransientGetRetry(retry) {
142
+ if (!retry)
143
+ return null;
144
+ if (retry === true)
145
+ return DEFAULT_TRANSIENT_GET_RETRY;
146
+ return {
147
+ maxAttempts: Math.max(1, retry.maxAttempts ?? DEFAULT_TRANSIENT_GET_RETRY.maxAttempts),
148
+ baseDelayMs: Math.max(0, retry.baseDelayMs ?? DEFAULT_TRANSIENT_GET_RETRY.baseDelayMs),
149
+ sleep: retry.sleep ?? DEFAULT_TRANSIENT_GET_RETRY.sleep
150
+ };
151
+ }
152
+ function methodOf(method) {
153
+ return (method ?? "GET").toUpperCase();
154
+ }
155
+ function retryForMethod(method, retry) {
156
+ if (!retry || (method !== "GET" && method !== "HEAD")) {
157
+ return { ...DEFAULT_TRANSIENT_GET_RETRY, maxAttempts: 1 };
103
158
  }
159
+ return retry;
160
+ }
161
+ function shouldRetryTransientRead(err, retry, attempt) {
162
+ return attempt < retry.maxAttempts && transientReadErrorCode(err) !== undefined;
163
+ }
164
+ async function sleepBeforeRetry(debug, method, url, err, retry, attempt) {
165
+ const delayMs = retry.baseDelayMs * attempt;
166
+ debug?.(`[aex] ${method} ${url.pathname} transient ${transientReadErrorCode(err) ?? "network"} ` +
167
+ `attempt ${attempt}/${retry.maxAttempts}; retrying in ${delayMs}ms`);
168
+ await retry.sleep(delayMs);
169
+ }
170
+ function transientReadErrorCode(err) {
171
+ if (err instanceof AexError)
172
+ return undefined;
173
+ if (err?.name === "AbortError")
174
+ return undefined;
175
+ const code = extractErrorCode(err);
176
+ if (code && TRANSIENT_READ_CODES.has(code))
177
+ return code;
178
+ const text = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
179
+ if (/fetch failed|socket hang up|other side closed|terminated|network.*reset/i.test(text))
180
+ return "fetch";
181
+ return undefined;
104
182
  }
105
183
  /**
106
184
  * Wrap a fetch rejection into an {@link AexNetworkError} carrying the
@@ -108,7 +186,7 @@ export class HttpClient {
108
186
  * already-structured aex errors (e.g. a retry layer's AexRateLimitError or
109
187
  * AexNetworkError) pass through untouched.
110
188
  */
111
- function toNetworkError(method, url, err) {
189
+ function toNetworkError(method, url, err, attempts, elapsedMs) {
112
190
  if (err instanceof AexError)
113
191
  return err;
114
192
  // `DOMException` is not an `Error` subclass on every runtime, so match aborts by name.
@@ -118,7 +196,9 @@ function toNetworkError(method, url, err) {
118
196
  method: (method ?? "GET").toUpperCase(),
119
197
  host: url.host,
120
198
  path: url.pathname,
121
- cause: err
199
+ cause: err,
200
+ ...(attempts !== undefined ? { attempts } : {}),
201
+ ...(elapsedMs !== undefined ? { elapsedMs } : {})
122
202
  });
123
203
  }
124
204
  function normalizeHeaders(headers) {
@@ -224,12 +224,12 @@ export declare function download(http: HttpClient, runId: string): Promise<Uint8
224
224
  export declare function downloadOutputs(http: HttpClient, runId: string, options?: OutputTransferOptions): Promise<Uint8Array>;
225
225
  /**
226
226
  * Download only the event archive (the `events` namespace). Always includes
227
- * typed `events.jsonl`.
227
+ * typed `events.jsonl` plus `manifest.json`.
228
228
  */
229
229
  export declare function downloadEvents(http: HttpClient, runId: string): Promise<Uint8Array>;
230
230
  /**
231
231
  * Download only the run record (the `metadata` namespace) as a zip
232
- * containing `run.json`.
232
+ * containing `run.json` plus `manifest.json`.
233
233
  */
234
234
  export declare function downloadMetadata(http: HttpClient, runId: string): Promise<Uint8Array>;
235
235
  export declare function submitRun(http: HttpClient, request: PlatformRunSubmissionInput): Promise<Run>;
@@ -980,19 +980,35 @@ export async function downloadOutputs(http, runId, options) {
980
980
  }
981
981
  /**
982
982
  * Download only the event archive (the `events` namespace). Always includes
983
- * typed `events.jsonl`.
983
+ * typed `events.jsonl` plus `manifest.json`.
984
984
  */
985
985
  export async function downloadEvents(http, runId) {
986
986
  const events = await listRunEvents(http, runId);
987
- return zipEntries([jsonlEntry("events.jsonl", events)]);
987
+ return zipEntries([
988
+ jsonlEntry("events.jsonl", events),
989
+ jsonEntry("manifest.json", {
990
+ runId,
991
+ namespace: "events",
992
+ files: [{ path: "events.jsonl", role: "typed_events", status: "present", recordCount: events.length }],
993
+ errors: []
994
+ })
995
+ ]);
988
996
  }
989
997
  /**
990
998
  * Download only the run record (the `metadata` namespace) as a zip
991
- * containing `run.json`.
999
+ * containing `run.json` plus `manifest.json`.
992
1000
  */
993
1001
  export async function downloadMetadata(http, runId) {
994
1002
  const run = await getRun(http, runId);
995
- return zipEntries([jsonEntry("run.json", run)]);
1003
+ return zipEntries([
1004
+ jsonEntry("run.json", run),
1005
+ jsonEntry("manifest.json", {
1006
+ runId,
1007
+ namespace: "metadata",
1008
+ files: [{ path: "run.json", role: "run_metadata", status: "present" }],
1009
+ errors: []
1010
+ })
1011
+ ]);
996
1012
  }
997
1013
  function zipEntries(entries) {
998
1014
  assertRunRecordArchivePublicSafeV1(entries);
package/dist/cli.mjs CHANGED
@@ -2104,6 +2104,7 @@ var HttpClient = class {
2104
2104
  #apiKey;
2105
2105
  #fetch;
2106
2106
  #debug;
2107
+ #retryTransientGets;
2107
2108
  constructor(options) {
2108
2109
  if (!options.apiKey) {
2109
2110
  throw new Error("HttpClient: apiKey is required");
@@ -2118,6 +2119,7 @@ var HttpClient = class {
2118
2119
  this.#apiKey = options.apiKey;
2119
2120
  this.#fetch = options.fetch ?? fetch;
2120
2121
  this.#debug = options.debug;
2122
+ this.#retryTransientGets = resolveTransientGetRetry(options.retryTransientGets);
2121
2123
  }
2122
2124
  /** Emit a redacted round-trip trace (no auth header, body, or query). */
2123
2125
  #trace(method, url, status, startedMs) {
@@ -2138,24 +2140,33 @@ var HttpClient = class {
2138
2140
  headers["content-type"] = "application/json";
2139
2141
  }
2140
2142
  }
2141
- const startedMs = Date.now();
2142
- let response;
2143
- try {
2144
- response = await this.#fetch(url, { ...init, headers });
2145
- } catch (err2) {
2146
- throw toNetworkError(init.method, url, err2);
2147
- }
2148
- this.#trace(init.method, url, response.status, startedMs);
2149
- const body = await readJson(response);
2150
- if (!response.ok) {
2151
- const errorBody = withResponseRequestId(body, response.headers);
2152
- throw apiErrorFromResponse({
2153
- status: response.status,
2154
- body: errorBody,
2155
- message: extractErrorMessage(errorBody)
2156
- });
2143
+ const method = methodOf(init.method);
2144
+ const retry = retryForMethod(method, this.#retryTransientGets);
2145
+ const requestStartedMs = Date.now();
2146
+ for (let attempt = 1; attempt <= retry.maxAttempts; attempt += 1) {
2147
+ const startedMs = Date.now();
2148
+ try {
2149
+ const response = await this.#fetch(url, { ...init, headers });
2150
+ this.#trace(method, url, response.status, startedMs);
2151
+ const body = await readJson(response);
2152
+ if (!response.ok) {
2153
+ const errorBody = withResponseRequestId(body, response.headers);
2154
+ throw apiErrorFromResponse({
2155
+ status: response.status,
2156
+ body: errorBody,
2157
+ message: extractErrorMessage(errorBody)
2158
+ });
2159
+ }
2160
+ return body;
2161
+ } catch (err2) {
2162
+ if (shouldRetryTransientRead(err2, retry, attempt)) {
2163
+ await sleepBeforeRetry(this.#debug, method, url, err2, retry, attempt);
2164
+ continue;
2165
+ }
2166
+ throw toNetworkError(method, url, err2, retry.maxAttempts > 1 ? attempt : void 0, Date.now() - requestStartedMs);
2167
+ }
2157
2168
  }
2158
- return body;
2169
+ throw new Error("HttpClient.request retry loop exhausted");
2159
2170
  }
2160
2171
  async download(path, init = {}, query = {}) {
2161
2172
  const url = new URL(path.replace(/^\//, ""), this.#baseUrl);
@@ -2166,27 +2177,93 @@ var HttpClient = class {
2166
2177
  authorization: `Bearer ${this.#apiKey}`,
2167
2178
  ...normalizeHeaders(init.headers)
2168
2179
  };
2169
- const startedMs = Date.now();
2170
- let response;
2171
- try {
2172
- response = await this.#fetch(url, { ...init, headers });
2173
- } catch (err2) {
2174
- throw toNetworkError(init.method, url, err2);
2175
- }
2176
- this.#trace(init.method, url, response.status, startedMs);
2177
- if (!response.ok) {
2178
- const body = await readJson(response);
2179
- const errorBody = withResponseRequestId(body, response.headers);
2180
- throw apiErrorFromResponse({
2181
- status: response.status,
2182
- body: errorBody,
2183
- message: extractErrorMessage(errorBody)
2184
- });
2180
+ const method = methodOf(init.method);
2181
+ const retry = retryForMethod(method, this.#retryTransientGets);
2182
+ const requestStartedMs = Date.now();
2183
+ for (let attempt = 1; attempt <= retry.maxAttempts; attempt += 1) {
2184
+ const startedMs = Date.now();
2185
+ try {
2186
+ const response = await this.#fetch(url, { ...init, headers });
2187
+ this.#trace(method, url, response.status, startedMs);
2188
+ if (!response.ok) {
2189
+ const body = await readJson(response);
2190
+ const errorBody = withResponseRequestId(body, response.headers);
2191
+ throw apiErrorFromResponse({
2192
+ status: response.status,
2193
+ body: errorBody,
2194
+ message: extractErrorMessage(errorBody)
2195
+ });
2196
+ }
2197
+ return { response };
2198
+ } catch (err2) {
2199
+ if (shouldRetryTransientRead(err2, retry, attempt)) {
2200
+ await sleepBeforeRetry(this.#debug, method, url, err2, retry, attempt);
2201
+ continue;
2202
+ }
2203
+ throw toNetworkError(method, url, err2, retry.maxAttempts > 1 ? attempt : void 0, Date.now() - requestStartedMs);
2204
+ }
2185
2205
  }
2186
- return { response };
2206
+ throw new Error("HttpClient.download retry loop exhausted");
2187
2207
  }
2188
2208
  };
2189
- function toNetworkError(method, url, err2) {
2209
+ var DEFAULT_TRANSIENT_GET_RETRY = {
2210
+ maxAttempts: 3,
2211
+ baseDelayMs: 250,
2212
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
2213
+ };
2214
+ var TRANSIENT_READ_CODES = /* @__PURE__ */ new Set([
2215
+ "ConnectionRefused",
2216
+ "ECONNRESET",
2217
+ "ECONNREFUSED",
2218
+ "ETIMEDOUT",
2219
+ "EAI_AGAIN",
2220
+ "UND_ERR_CONNECT_TIMEOUT",
2221
+ "UND_ERR_HEADERS_TIMEOUT",
2222
+ "UND_ERR_SOCKET",
2223
+ "UND_ERR_BODY_TIMEOUT"
2224
+ ]);
2225
+ function resolveTransientGetRetry(retry) {
2226
+ if (!retry)
2227
+ return null;
2228
+ if (retry === true)
2229
+ return DEFAULT_TRANSIENT_GET_RETRY;
2230
+ return {
2231
+ maxAttempts: Math.max(1, retry.maxAttempts ?? DEFAULT_TRANSIENT_GET_RETRY.maxAttempts),
2232
+ baseDelayMs: Math.max(0, retry.baseDelayMs ?? DEFAULT_TRANSIENT_GET_RETRY.baseDelayMs),
2233
+ sleep: retry.sleep ?? DEFAULT_TRANSIENT_GET_RETRY.sleep
2234
+ };
2235
+ }
2236
+ function methodOf(method) {
2237
+ return (method ?? "GET").toUpperCase();
2238
+ }
2239
+ function retryForMethod(method, retry) {
2240
+ if (!retry || method !== "GET" && method !== "HEAD") {
2241
+ return { ...DEFAULT_TRANSIENT_GET_RETRY, maxAttempts: 1 };
2242
+ }
2243
+ return retry;
2244
+ }
2245
+ function shouldRetryTransientRead(err2, retry, attempt) {
2246
+ return attempt < retry.maxAttempts && transientReadErrorCode(err2) !== void 0;
2247
+ }
2248
+ async function sleepBeforeRetry(debug2, method, url, err2, retry, attempt) {
2249
+ const delayMs = retry.baseDelayMs * attempt;
2250
+ debug2?.(`[aex] ${method} ${url.pathname} transient ${transientReadErrorCode(err2) ?? "network"} attempt ${attempt}/${retry.maxAttempts}; retrying in ${delayMs}ms`);
2251
+ await retry.sleep(delayMs);
2252
+ }
2253
+ function transientReadErrorCode(err2) {
2254
+ if (err2 instanceof AexError)
2255
+ return void 0;
2256
+ if (err2?.name === "AbortError")
2257
+ return void 0;
2258
+ const code = extractErrorCode(err2);
2259
+ if (code && TRANSIENT_READ_CODES.has(code))
2260
+ return code;
2261
+ const text = err2 instanceof Error ? `${err2.name}: ${err2.message}` : String(err2);
2262
+ if (/fetch failed|socket hang up|other side closed|terminated|network.*reset/i.test(text))
2263
+ return "fetch";
2264
+ return void 0;
2265
+ }
2266
+ function toNetworkError(method, url, err2, attempts, elapsedMs) {
2190
2267
  if (err2 instanceof AexError)
2191
2268
  return err2;
2192
2269
  if (err2?.name === "AbortError")
@@ -2195,7 +2272,9 @@ function toNetworkError(method, url, err2) {
2195
2272
  method: (method ?? "GET").toUpperCase(),
2196
2273
  host: url.host,
2197
2274
  path: url.pathname,
2198
- cause: err2
2275
+ cause: err2,
2276
+ ...attempts !== void 0 ? { attempts } : {},
2277
+ ...elapsedMs !== void 0 ? { elapsedMs } : {}
2199
2278
  });
2200
2279
  }
2201
2280
  function normalizeHeaders(headers) {
@@ -3816,11 +3895,27 @@ async function downloadOutputs(http, runId, options) {
3816
3895
  }
3817
3896
  async function downloadEvents(http, runId) {
3818
3897
  const events = await listRunEvents(http, runId);
3819
- return zipEntries([jsonlEntry("events.jsonl", events)]);
3898
+ return zipEntries([
3899
+ jsonlEntry("events.jsonl", events),
3900
+ jsonEntry("manifest.json", {
3901
+ runId,
3902
+ namespace: "events",
3903
+ files: [{ path: "events.jsonl", role: "typed_events", status: "present", recordCount: events.length }],
3904
+ errors: []
3905
+ })
3906
+ ]);
3820
3907
  }
3821
3908
  async function downloadMetadata(http, runId) {
3822
3909
  const run = await getRun(http, runId);
3823
- return zipEntries([jsonEntry("run.json", run)]);
3910
+ return zipEntries([
3911
+ jsonEntry("run.json", run),
3912
+ jsonEntry("manifest.json", {
3913
+ runId,
3914
+ namespace: "metadata",
3915
+ files: [{ path: "run.json", role: "run_metadata", status: "present" }],
3916
+ errors: []
3917
+ })
3918
+ ]);
3824
3919
  }
3825
3920
  function zipEntries(entries) {
3826
3921
  assertRunRecordArchivePublicSafeV1(entries);
@@ -4186,6 +4281,7 @@ function makeHttpClient(io2, flags) {
4186
4281
  return new HttpClient({
4187
4282
  baseUrl: flags.aexUrl,
4188
4283
  apiKey: flags.apiKey,
4284
+ retryTransientGets: true,
4189
4285
  fetch: io2.fetchImpl,
4190
4286
  // `--debug`: route the transport's redacted per-request traces to stderr.
4191
4287
  ...flags.debug ? { debug: (line) => io2.stderr(`${line}
@@ -1 +1 @@
1
- eb3b73297a9e0ef272eefa66f60dc4a13da741cb24a809c7558de2ee5d4d3555 cli.mjs
1
+ 383e87cd6909a7a5eb73b90a77c12b6adf78491678cdc14612d5cc878971458e cli.mjs
package/dist/version.d.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  *
7
7
  * Used by the (future) User-Agent header on outbound SDK requests.
8
8
  */
9
- export declare const SDK_VERSION = "0.40.9";
9
+ export declare const SDK_VERSION = "0.40.11";
package/dist/version.js CHANGED
@@ -6,5 +6,5 @@
6
6
  *
7
7
  * Used by the (future) User-Agent header on outbound SDK requests.
8
8
  */
9
- export const SDK_VERSION = "0.40.9";
9
+ export const SDK_VERSION = "0.40.11";
10
10
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC"}
package/docs/outputs.md CHANGED
@@ -38,8 +38,8 @@ A session's downloadable content is organised into three logical namespaces, eac
38
38
  | Namespace | What it holds | Verb | CLI |
39
39
  | --- | --- | --- | --- |
40
40
  | `outputs` | The session's real deliverables. | `session.outputs().download()` | `download <id> --only outputs` |
41
- | `events` | Typed event-channel records (`events.jsonl`). | `session.events().download()` | `download <id> --only events` |
42
- | `metadata` | The session record (`run.json`). | `session.downloadMetadata()` | `download <id> --only metadata` |
41
+ | `events` | Typed event-channel records (`events.jsonl`) plus a namespace manifest. | `session.events().download()` | `download <id> --only events` |
42
+ | `metadata` | The session record (`run.json`) plus a namespace manifest. | `session.downloadMetadata()` | `download <id> --only metadata` |
43
43
 
44
44
  Platform diagnostics are stored outside the public archive under `runs/<runId>/internal/logs/` for internal/admin access only. They are not exposed by the SDK download helpers or the public CLI.
45
45
 
@@ -66,7 +66,7 @@ manifest.json # RunRecordManifestV1
66
66
  | `outputs[]` | `{ id, filename, sizeBytes?, contentType? }` — one row per file successfully written under `outputs/`. |
67
67
  | `errors[]` | `{ namespace, id, filename, message }` — per-artifact byte fetches that failed during assembly. Best-effort: a failure records an entry here and is skipped from the tree rather than aborting the whole zip. |
68
68
 
69
- The single-namespace verbs return the same per-file bytes at the zip root (e.g. `session.outputs().download()` -> `report.txt` + a `manifest.json`; `session.events().download()` -> `events.jsonl`).
69
+ The single-namespace verbs return the same per-file bytes at the zip root (e.g. `session.outputs().download()` -> `report.txt` + `manifest.json`; `session.events().download()` -> `events.jsonl` + `manifest.json`; `session.downloadMetadata()` -> `run.json` + `manifest.json`).
70
70
 
71
71
  ## Downloading one output
72
72
 
@@ -253,7 +253,7 @@ Capture notes:
253
253
 
254
254
  ## Runs without explicit `outputs.allowedDirs`
255
255
 
256
- Metadata still gets the full treatment. aex captures every regular file the run created or modified outside mandatory platform excludes. A run that produces no files still returns a zip with `run.json`, `events.jsonl`, and an empty `outputs/` directory (manifest `outputs: []`).
256
+ Metadata still gets the full treatment. aex captures every regular file the run created or modified outside mandatory platform excludes. A run that produces no files still returns a whole-session zip with `metadata/run.json`, `events/events.jsonl`, and `manifest.json` (manifest `outputs: []`).
257
257
 
258
258
  ## Mid-session download semantics
259
259
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexhq/sdk",
3
- "version": "0.40.9",
3
+ "version": "0.40.11",
4
4
  "description": "TypeScript SDK for running autonomous agent sessions across providers (Anthropic, OpenAI, DeepSeek, Gemini, Mistral) behind one interface.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {