@aexhq/sdk 0.46.4-canary → 0.50.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 (337) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +38 -0
  3. package/README.md +23 -31
  4. package/dist/client/aex.d.ts +33 -0
  5. package/dist/client/aex.js +98 -0
  6. package/dist/client/aex.js.map +1 -0
  7. package/dist/client/credentials.d.ts +25 -0
  8. package/dist/client/credentials.js +97 -0
  9. package/dist/client/credentials.js.map +1 -0
  10. package/dist/client/routing.d.ts +7 -0
  11. package/dist/client/routing.js +29 -0
  12. package/dist/client/routing.js.map +1 -0
  13. package/dist/downloads/download.d.ts +25 -0
  14. package/dist/downloads/download.js +53 -0
  15. package/dist/downloads/download.js.map +1 -0
  16. package/dist/generated/errors.d.ts +12 -0
  17. package/dist/generated/errors.js +81 -0
  18. package/dist/generated/errors.js.map +1 -0
  19. package/dist/generated/resources.d.ts +730 -0
  20. package/dist/generated/resources.js +606 -0
  21. package/dist/generated/resources.js.map +1 -0
  22. package/dist/generated/routes.d.ts +42 -0
  23. package/dist/generated/routes.js +2101 -0
  24. package/dist/generated/routes.js.map +1 -0
  25. package/dist/index.d.ts +20 -50
  26. package/dist/index.js +11 -62
  27. package/dist/index.js.map +1 -1
  28. package/dist/observations/stream.d.ts +1 -0
  29. package/dist/observations/stream.js +18 -0
  30. package/dist/observations/stream.js.map +1 -0
  31. package/dist/transport/errors.d.ts +60 -0
  32. package/dist/transport/errors.js +107 -0
  33. package/dist/transport/errors.js.map +1 -0
  34. package/dist/transport/pagination.d.ts +8 -0
  35. package/dist/transport/pagination.js +34 -0
  36. package/dist/transport/pagination.js.map +1 -0
  37. package/dist/transport/retry.d.ts +21 -0
  38. package/dist/transport/retry.js +37 -0
  39. package/dist/transport/retry.js.map +1 -0
  40. package/dist/transport/transport.d.ts +25 -0
  41. package/dist/transport/transport.js +28 -0
  42. package/dist/transport/transport.js.map +1 -0
  43. package/package.json +63 -30
  44. package/dist/_contracts/account-operations.d.ts +0 -101
  45. package/dist/_contracts/account-operations.js +0 -242
  46. package/dist/_contracts/account-types.d.ts +0 -461
  47. package/dist/_contracts/account-types.js +0 -1
  48. package/dist/_contracts/api-key.d.ts +0 -61
  49. package/dist/_contracts/api-key.js +0 -101
  50. package/dist/_contracts/api-routes.d.ts +0 -20
  51. package/dist/_contracts/api-routes.js +0 -109
  52. package/dist/_contracts/archive-limits.d.ts +0 -3
  53. package/dist/_contracts/archive-limits.js +0 -23
  54. package/dist/_contracts/asset-authoring.d.ts +0 -22
  55. package/dist/_contracts/asset-authoring.js +0 -106
  56. package/dist/_contracts/asset-bundle.d.ts +0 -64
  57. package/dist/_contracts/asset-bundle.js +0 -263
  58. package/dist/_contracts/asset-upload-helper.d.ts +0 -31
  59. package/dist/_contracts/asset-upload-helper.js +0 -84
  60. package/dist/_contracts/billing-admission.d.ts +0 -29
  61. package/dist/_contracts/billing-admission.js +0 -28
  62. package/dist/_contracts/bundle-manifest.d.ts +0 -89
  63. package/dist/_contracts/bundle-manifest.js +0 -158
  64. package/dist/_contracts/canonical-sha256.d.ts +0 -8
  65. package/dist/_contracts/canonical-sha256.js +0 -8
  66. package/dist/_contracts/connection-ticket.d.ts +0 -22
  67. package/dist/_contracts/connection-ticket.js +0 -54
  68. package/dist/_contracts/continuation-event.d.ts +0 -31
  69. package/dist/_contracts/continuation-event.js +0 -6
  70. package/dist/_contracts/contract-parse-error.d.ts +0 -12
  71. package/dist/_contracts/contract-parse-error.js +0 -51
  72. package/dist/_contracts/error-codes.d.ts +0 -26
  73. package/dist/_contracts/error-codes.js +0 -116
  74. package/dist/_contracts/error-factory.d.ts +0 -32
  75. package/dist/_contracts/error-factory.js +0 -174
  76. package/dist/_contracts/event-envelope.d.ts +0 -471
  77. package/dist/_contracts/event-envelope.js +0 -501
  78. package/dist/_contracts/event-stream-client.d.ts +0 -122
  79. package/dist/_contracts/event-stream-client.js +0 -445
  80. package/dist/_contracts/event-view.d.ts +0 -44
  81. package/dist/_contracts/event-view.js +0 -69
  82. package/dist/_contracts/failure-class.d.ts +0 -29
  83. package/dist/_contracts/failure-class.js +0 -73
  84. package/dist/_contracts/http.d.ts +0 -135
  85. package/dist/_contracts/http.js +0 -434
  86. package/dist/_contracts/ids.d.ts +0 -66
  87. package/dist/_contracts/ids.js +0 -119
  88. package/dist/_contracts/index.d.ts +0 -42
  89. package/dist/_contracts/index.js +0 -52
  90. package/dist/_contracts/internal.d.ts +0 -55
  91. package/dist/_contracts/internal.js +0 -113
  92. package/dist/_contracts/models.d.ts +0 -30
  93. package/dist/_contracts/models.js +0 -28
  94. package/dist/_contracts/operation-core.d.ts +0 -36
  95. package/dist/_contracts/operation-core.js +0 -70
  96. package/dist/_contracts/operations.d.ts +0 -218
  97. package/dist/_contracts/operations.js +0 -1496
  98. package/dist/_contracts/otlp-projection.d.ts +0 -78
  99. package/dist/_contracts/otlp-projection.js +0 -171
  100. package/dist/_contracts/post-hook.d.ts +0 -31
  101. package/dist/_contracts/post-hook.js +0 -61
  102. package/dist/_contracts/provider-fault.d.ts +0 -34
  103. package/dist/_contracts/provider-fault.js +0 -68
  104. package/dist/_contracts/retry-core.d.ts +0 -29
  105. package/dist/_contracts/retry-core.js +0 -79
  106. package/dist/_contracts/runner-event.d.ts +0 -117
  107. package/dist/_contracts/runner-event.js +0 -172
  108. package/dist/_contracts/runtime-kind.d.ts +0 -60
  109. package/dist/_contracts/runtime-kind.js +0 -70
  110. package/dist/_contracts/runtime-manifest.d.ts +0 -121
  111. package/dist/_contracts/runtime-manifest.js +0 -83
  112. package/dist/_contracts/runtime-security-profile.d.ts +0 -26
  113. package/dist/_contracts/runtime-security-profile.js +0 -73
  114. package/dist/_contracts/runtime-sizes.d.ts +0 -104
  115. package/dist/_contracts/runtime-sizes.js +0 -111
  116. package/dist/_contracts/runtime-types.d.ts +0 -618
  117. package/dist/_contracts/runtime-types.js +0 -58
  118. package/dist/_contracts/schemas/asset-bundle.d.ts +0 -70
  119. package/dist/_contracts/schemas/asset-bundle.js +0 -107
  120. package/dist/_contracts/schemas/asset-ref.d.ts +0 -61
  121. package/dist/_contracts/schemas/asset-ref.js +0 -118
  122. package/dist/_contracts/schemas/bundle-manifest.d.ts +0 -66
  123. package/dist/_contracts/schemas/bundle-manifest.js +0 -77
  124. package/dist/_contracts/schemas/index.d.ts +0 -32
  125. package/dist/_contracts/schemas/index.js +0 -30
  126. package/dist/_contracts/schemas/mcp-server.d.ts +0 -99
  127. package/dist/_contracts/schemas/mcp-server.js +0 -209
  128. package/dist/_contracts/schemas/models.d.ts +0 -29
  129. package/dist/_contracts/schemas/models.js +0 -51
  130. package/dist/_contracts/schemas/numeric.d.ts +0 -18
  131. package/dist/_contracts/schemas/numeric.js +0 -28
  132. package/dist/_contracts/schemas/post-hook.d.ts +0 -45
  133. package/dist/_contracts/schemas/post-hook.js +0 -68
  134. package/dist/_contracts/schemas/response-assets.d.ts +0 -75
  135. package/dist/_contracts/schemas/response-assets.js +0 -81
  136. package/dist/_contracts/schemas/response-billing.d.ts +0 -208
  137. package/dist/_contracts/schemas/response-billing.js +0 -139
  138. package/dist/_contracts/schemas/response-common.d.ts +0 -132
  139. package/dist/_contracts/schemas/response-common.js +0 -162
  140. package/dist/_contracts/schemas/response-identity.d.ts +0 -648
  141. package/dist/_contracts/schemas/response-identity.js +0 -131
  142. package/dist/_contracts/schemas/response-mcp-servers.d.ts +0 -51
  143. package/dist/_contracts/schemas/response-mcp-servers.js +0 -32
  144. package/dist/_contracts/schemas/response-secrets.d.ts +0 -50
  145. package/dist/_contracts/schemas/response-secrets.js +0 -32
  146. package/dist/_contracts/schemas/response-sessions-internal.d.ts +0 -200
  147. package/dist/_contracts/schemas/response-sessions-internal.js +0 -142
  148. package/dist/_contracts/schemas/response-sessions.d.ts +0 -1598
  149. package/dist/_contracts/schemas/response-sessions.js +0 -377
  150. package/dist/_contracts/schemas/response-webhooks.d.ts +0 -76
  151. package/dist/_contracts/schemas/response-webhooks.js +0 -42
  152. package/dist/_contracts/schemas/response-workspace.d.ts +0 -225
  153. package/dist/_contracts/schemas/response-workspace.js +0 -99
  154. package/dist/_contracts/schemas/runtime-kind.d.ts +0 -31
  155. package/dist/_contracts/schemas/runtime-kind.js +0 -29
  156. package/dist/_contracts/schemas/runtime-security-profile.d.ts +0 -28
  157. package/dist/_contracts/schemas/runtime-security-profile.js +0 -26
  158. package/dist/_contracts/schemas/runtime-sizes.d.ts +0 -70
  159. package/dist/_contracts/schemas/runtime-sizes.js +0 -127
  160. package/dist/_contracts/schemas/session-limits.d.ts +0 -34
  161. package/dist/_contracts/schemas/session-limits.js +0 -39
  162. package/dist/_contracts/schemas/session-machine.d.ts +0 -23
  163. package/dist/_contracts/schemas/session-machine.js +0 -24
  164. package/dist/_contracts/schemas/session-request-config.d.ts +0 -58
  165. package/dist/_contracts/schemas/session-request-config.js +0 -134
  166. package/dist/_contracts/schemas/session-webhook.d.ts +0 -11
  167. package/dist/_contracts/schemas/session-webhook.js +0 -38
  168. package/dist/_contracts/schemas/side-effect-audit.d.ts +0 -98
  169. package/dist/_contracts/schemas/side-effect-audit.js +0 -102
  170. package/dist/_contracts/schemas/submission-assets.d.ts +0 -117
  171. package/dist/_contracts/schemas/submission-assets.js +0 -147
  172. package/dist/_contracts/schemas/submission-body.d.ts +0 -251
  173. package/dist/_contracts/schemas/submission-body.js +0 -378
  174. package/dist/_contracts/schemas/submission-environment.d.ts +0 -79
  175. package/dist/_contracts/schemas/submission-environment.js +0 -179
  176. package/dist/_contracts/schemas/submission-request.d.ts +0 -158
  177. package/dist/_contracts/schemas/submission-request.js +0 -49
  178. package/dist/_contracts/schemas/submission-secrets.d.ts +0 -47
  179. package/dist/_contracts/schemas/submission-secrets.js +0 -108
  180. package/dist/_contracts/schemas/wire.d.ts +0 -118
  181. package/dist/_contracts/schemas/wire.js +0 -171
  182. package/dist/_contracts/schemas/workspace-resources.d.ts +0 -50
  183. package/dist/_contracts/schemas/workspace-resources.js +0 -87
  184. package/dist/_contracts/sdk-errors.d.ts +0 -212
  185. package/dist/_contracts/sdk-errors.js +0 -313
  186. package/dist/_contracts/sdk-secrets.d.ts +0 -67
  187. package/dist/_contracts/sdk-secrets.js +0 -427
  188. package/dist/_contracts/session-archive.d.ts +0 -16
  189. package/dist/_contracts/session-archive.js +0 -92
  190. package/dist/_contracts/session-artifacts.d.ts +0 -189
  191. package/dist/_contracts/session-artifacts.js +0 -264
  192. package/dist/_contracts/session-config.d.ts +0 -373
  193. package/dist/_contracts/session-config.js +0 -562
  194. package/dist/_contracts/session-cost-types.d.ts +0 -211
  195. package/dist/_contracts/session-cost-types.js +0 -69
  196. package/dist/_contracts/session-cost.d.ts +0 -8
  197. package/dist/_contracts/session-cost.js +0 -582
  198. package/dist/_contracts/session-custody.d.ts +0 -165
  199. package/dist/_contracts/session-custody.js +0 -345
  200. package/dist/_contracts/session-file-query.d.ts +0 -14
  201. package/dist/_contracts/session-file-query.js +0 -178
  202. package/dist/_contracts/session-record.d.ts +0 -112
  203. package/dist/_contracts/session-record.js +0 -165
  204. package/dist/_contracts/session-retention.d.ts +0 -201
  205. package/dist/_contracts/session-retention.js +0 -450
  206. package/dist/_contracts/side-effect-audit.d.ts +0 -126
  207. package/dist/_contracts/side-effect-audit.js +0 -520
  208. package/dist/_contracts/sse.d.ts +0 -74
  209. package/dist/_contracts/sse.js +0 -227
  210. package/dist/_contracts/stable.d.ts +0 -45
  211. package/dist/_contracts/stable.js +0 -62
  212. package/dist/_contracts/status.d.ts +0 -25
  213. package/dist/_contracts/status.js +0 -57
  214. package/dist/_contracts/submission-limits.d.ts +0 -61
  215. package/dist/_contracts/submission-limits.js +0 -60
  216. package/dist/_contracts/submission.d.ts +0 -547
  217. package/dist/_contracts/submission.js +0 -812
  218. package/dist/_contracts/suggest.d.ts +0 -15
  219. package/dist/_contracts/suggest.js +0 -53
  220. package/dist/_contracts/testing/response-bindings.d.ts +0 -45
  221. package/dist/_contracts/testing/response-bindings.js +0 -256
  222. package/dist/_contracts/testing/wire-conformance-entry.d.ts +0 -10
  223. package/dist/_contracts/testing/wire-conformance-entry.js +0 -8
  224. package/dist/_contracts/testing/wire-conformance.d.ts +0 -169
  225. package/dist/_contracts/testing/wire-conformance.js +0 -276
  226. package/dist/_contracts/turn-trace.d.ts +0 -28
  227. package/dist/_contracts/turn-trace.js +0 -1
  228. package/dist/_contracts/unknown-field-error.d.ts +0 -13
  229. package/dist/_contracts/unknown-field-error.js +0 -21
  230. package/dist/_contracts/value-guards.d.ts +0 -20
  231. package/dist/_contracts/value-guards.js +0 -34
  232. package/dist/_contracts/webhook-verify.d.ts +0 -34
  233. package/dist/_contracts/webhook-verify.js +0 -93
  234. package/dist/_contracts/wire-observer.d.ts +0 -49
  235. package/dist/_contracts/wire-observer.js +0 -34
  236. package/dist/_contracts/workflow-status.d.ts +0 -7
  237. package/dist/_contracts/workflow-status.js +0 -43
  238. package/dist/_contracts/workspace-resources.d.ts +0 -98
  239. package/dist/_contracts/workspace-resources.js +0 -39
  240. package/dist/archive-limits.d.ts +0 -1
  241. package/dist/archive-limits.js +0 -2
  242. package/dist/archive-limits.js.map +0 -1
  243. package/dist/asset-upload.d.ts +0 -47
  244. package/dist/asset-upload.js +0 -269
  245. package/dist/asset-upload.js.map +0 -1
  246. package/dist/bundle.d.ts +0 -9
  247. package/dist/bundle.js +0 -20
  248. package/dist/bundle.js.map +0 -1
  249. package/dist/canonical-zip.d.ts +0 -68
  250. package/dist/canonical-zip.js +0 -355
  251. package/dist/canonical-zip.js.map +0 -1
  252. package/dist/cli.mjs +0 -12048
  253. package/dist/cli.mjs.sha256 +0 -1
  254. package/dist/client-types.d.ts +0 -192
  255. package/dist/client-types.js +0 -2
  256. package/dist/client-types.js.map +0 -1
  257. package/dist/client.d.ts +0 -464
  258. package/dist/client.js +0 -1207
  259. package/dist/client.js.map +0 -1
  260. package/dist/event-projection.d.ts +0 -22
  261. package/dist/event-projection.js +0 -380
  262. package/dist/event-projection.js.map +0 -1
  263. package/dist/fetch-archive.d.ts +0 -16
  264. package/dist/fetch-archive.js +0 -252
  265. package/dist/fetch-archive.js.map +0 -1
  266. package/dist/file.d.ts +0 -96
  267. package/dist/file.js +0 -272
  268. package/dist/file.js.map +0 -1
  269. package/dist/instructions.d.ts +0 -20
  270. package/dist/instructions.js +0 -40
  271. package/dist/instructions.js.map +0 -1
  272. package/dist/legacy-session-provider-fault.d.ts +0 -7
  273. package/dist/legacy-session-provider-fault.js +0 -38
  274. package/dist/legacy-session-provider-fault.js.map +0 -1
  275. package/dist/mcp-server.d.ts +0 -84
  276. package/dist/mcp-server.js +0 -117
  277. package/dist/mcp-server.js.map +0 -1
  278. package/dist/node-fs.d.ts +0 -29
  279. package/dist/node-fs.js +0 -19
  280. package/dist/node-fs.js.map +0 -1
  281. package/dist/node-walk.d.ts +0 -69
  282. package/dist/node-walk.js +0 -151
  283. package/dist/node-walk.js.map +0 -1
  284. package/dist/path-basename.d.ts +0 -5
  285. package/dist/path-basename.js +0 -9
  286. package/dist/path-basename.js.map +0 -1
  287. package/dist/retry.d.ts +0 -70
  288. package/dist/retry.js +0 -155
  289. package/dist/retry.js.map +0 -1
  290. package/dist/secret.d.ts +0 -65
  291. package/dist/secret.js +0 -110
  292. package/dist/secret.js.map +0 -1
  293. package/dist/session-validate.d.ts +0 -100
  294. package/dist/session-validate.js +0 -303
  295. package/dist/session-validate.js.map +0 -1
  296. package/dist/skill.d.ts +0 -99
  297. package/dist/skill.js +0 -169
  298. package/dist/skill.js.map +0 -1
  299. package/dist/submission-wire.d.ts +0 -13
  300. package/dist/submission-wire.js +0 -69
  301. package/dist/submission-wire.js.map +0 -1
  302. package/dist/tool.d.ts +0 -41
  303. package/dist/tool.js +0 -76
  304. package/dist/tool.js.map +0 -1
  305. package/dist/version.d.ts +0 -9
  306. package/dist/version.js +0 -10
  307. package/dist/version.js.map +0 -1
  308. package/docs/authentication.md +0 -125
  309. package/docs/billing.md +0 -164
  310. package/docs/cleanup.md +0 -27
  311. package/docs/concepts/agent-tools.md +0 -47
  312. package/docs/concepts/composition.md +0 -60
  313. package/docs/concepts/providers-and-runtimes.md +0 -121
  314. package/docs/concepts/sessions.md +0 -51
  315. package/docs/concepts/subagents.md +0 -35
  316. package/docs/credentials.md +0 -116
  317. package/docs/defaults.md +0 -51
  318. package/docs/errors.md +0 -258
  319. package/docs/events.md +0 -143
  320. package/docs/files.md +0 -130
  321. package/docs/limits-and-quotas.md +0 -114
  322. package/docs/limits.md +0 -51
  323. package/docs/mcp.md +0 -47
  324. package/docs/networking.md +0 -114
  325. package/docs/provider-runtime-capabilities.md +0 -32
  326. package/docs/public-surface.json +0 -73
  327. package/docs/quickstart.md +0 -135
  328. package/docs/release.md +0 -44
  329. package/docs/retries.md +0 -108
  330. package/docs/secrets.md +0 -141
  331. package/docs/session-config.md +0 -51
  332. package/docs/session-record.md +0 -58
  333. package/docs/skills.md +0 -65
  334. package/docs/telemetry.md +0 -66
  335. package/docs/testing.md +0 -35
  336. package/docs/vision-skills.md +0 -94
  337. package/docs/webhooks.md +0 -143
@@ -1,1496 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { CANONICAL_SHA256_DIGEST_PATTERN } from "./canonical-sha256.js";
3
- import { isRecord, isStringLiteral } from "./value-guards.js";
4
- import { AexNetworkError, SessionStateError } from "./sdk-errors.js";
5
- import { buildSessionArchive, buildSessionEventsArchive, buildSessionFilesArchive, buildSessionMetadataArchive } from "./session-archive.js";
6
- import { filterSessionFiles, isPathSelector, resolveSessionFileSelector } from "./session-file-query.js";
7
- import { configError, idempotencyHeaders, resolveIdempotencyKey, deriveMessageIdempotencyKey } from "./operation-core.js";
8
- import { BILLING_ADMISSION_STATES } from "./billing-admission.js";
9
- import { RUNTIME_CAPABILITY_NAMES, SESSION_RUN_PHASES } from "./runtime-types.js";
10
- import { RUNTIME_SIZES, parseRuntimeSize } from "./runtime-sizes.js";
11
- import { RUNTIME_KINDS } from "./runtime-kind.js";
12
- import { SESSION_STATUSES, SESSION_TERMINAL_OUTCOMES } from "./status.js";
13
- import { parseProviderFault } from "./provider-fault.js";
14
- export { classifySessionFile, filterSessionFiles, resolveSessionFileSelector, toFilenameMatcher } from "./session-file-query.js";
15
- // The idempotency-key policy moved to the `operation-core.js` leaf so
16
- // `account-operations.js` can share it without either module importing the
17
- // other. Named rather than star re-exported: `configError` and
18
- // `idempotencyHeaders` stay module-private, exactly as they were when they
19
- // lived here.
20
- export { IDEMPOTENCY_KEY_MAX_LENGTH, deriveMessageIdempotencyKey, resolveIdempotencyKey } from "./operation-core.js";
21
- // Billing, workspace secrets and the control plane. Star re-exported because
22
- // every name over there is a public operation this module used to declare, and
23
- // `internal.ts` publishes this module as the whole `operations` namespace.
24
- export * from "./account-operations.js";
25
- /**
26
- * The single source of truth for SDK<->BFF transport. The SDK class
27
- * AND the CLI subcommands both call these functions; neither
28
- * surface re-implements HTTP requests against the dashboard.
29
- *
30
- * Every function takes an HttpClient (so callers control auth + fetch
31
- * injection) and returns parsed responses.
32
- *
33
- * Workspace identity is derived server-side from the API key on
34
- * every request; callers do not pass `workspaceId`.
35
- */
36
- const SESSION_STATUS_SET = new Set(SESSION_STATUSES);
37
- const SESSION_FILE_SHA256_PATTERN = /^[0-9a-f]{64}$/;
38
- const SESSION_FILE_CHECKPOINT_ID_PATTERN = /^[A-Za-z0-9._-]{1,200}$/;
39
- export async function createSession(http, request, options) {
40
- const headers = idempotencyHeaders(options);
41
- const result = await http.request("/api/sessions", {
42
- method: "POST",
43
- ...(headers ? { headers } : {}),
44
- body: JSON.stringify(request)
45
- });
46
- return unwrapSession(result);
47
- }
48
- /** Create a session and enqueue its first message without waiting for the RUN terminal. */
49
- export async function createSessionWithMessage(http, request, input, options) {
50
- const createKey = resolveIdempotencyKey(options?.idempotencyKey);
51
- const messageKey = options?.messageIdempotencyKey !== undefined
52
- ? resolveIdempotencyKey(options.messageIdempotencyKey)
53
- : deriveMessageIdempotencyKey(createKey);
54
- if (!((typeof input === "string" && input.length > 0) ||
55
- (Array.isArray(input) && input.length > 0 && input.every((part) => typeof part === "string" && part.length > 0)))) {
56
- throw configError("input", "session message must be a non-empty string or string array");
57
- }
58
- const created = await createSession(http, request, { idempotencyKey: createKey });
59
- return sendSessionMessage(http, created.id, { input }, { idempotencyKey: messageKey });
60
- }
61
- export async function getSession(http, sessionId) {
62
- const result = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}`);
63
- return unwrapSession(result);
64
- }
65
- export async function listSessions(http, query) {
66
- validateSessionListQuery(query);
67
- const params = {};
68
- if (query?.status !== undefined)
69
- params.status = query.status;
70
- if (query?.since !== undefined)
71
- params.since = query.since;
72
- if (query?.limit !== undefined)
73
- params.limit = String(query.limit);
74
- if (query?.cursor !== undefined)
75
- params.cursor = query.cursor;
76
- const page = await http.request("/api/sessions", {}, params);
77
- if (!isRecord(page) || !Array.isArray(page.sessions)) {
78
- throw new SessionStateError("sessions.list returned an invalid page: sessions must be an array");
79
- }
80
- const sessions = page.sessions.map((value, index) => {
81
- if (!isRecord(value)) {
82
- throw new SessionStateError(`sessions.list returned an invalid row at index ${index}`);
83
- }
84
- for (const field of ["id", "status", "createdAt", "updatedAt"]) {
85
- if (typeof value[field] !== "string" || value[field].length === 0) {
86
- throw new SessionStateError(`sessions.list row ${index} has an invalid ${field}`);
87
- }
88
- }
89
- if (!SESSION_STATUS_SET.has(value.status)) {
90
- throw new SessionStateError(`sessions.list row ${index} has an unknown lifecycle status`);
91
- }
92
- assertCanonicalSessionWireFields(value, `sessions.list row ${index}`);
93
- if (typeof value.acceptsMessages !== "boolean") {
94
- throw new SessionStateError(`sessions.list row ${index} has an invalid acceptsMessages`);
95
- }
96
- if (value.costUsd !== undefined &&
97
- (typeof value.costUsd !== "number" || !Number.isFinite(value.costUsd) || value.costUsd < 0)) {
98
- throw new SessionStateError(`sessions.list row ${index} has an invalid costUsd`);
99
- }
100
- const currentRun = normalizeOptionalSessionRun(value.currentRun, value.id, `sessions.list row ${index}.currentRun`);
101
- const lastRun = normalizeOptionalSessionRun(value.lastRun, value.id, `sessions.list row ${index}.lastRun`);
102
- // `providerFault` is detail-only and must never leak onto SessionSummary.
103
- const { providerFault: _providerFault, ...normalized } = normalizeSessionRuntime(value, `sessions.list row ${index}`);
104
- return {
105
- ...normalized,
106
- ...(currentRun !== undefined ? { currentRun } : {}),
107
- ...(lastRun !== undefined ? { lastRun } : {})
108
- };
109
- });
110
- if (page.nextCursor !== undefined && typeof page.nextCursor !== "string") {
111
- throw new SessionStateError("sessions.list returned an invalid page: nextCursor must be a string");
112
- }
113
- return {
114
- sessions,
115
- ...(typeof page.nextCursor === "string" ? { nextCursor: page.nextCursor } : {})
116
- };
117
- }
118
- function validateSessionListQuery(query) {
119
- if (query?.limit !== undefined && (!Number.isInteger(query.limit) || query.limit < 1 || query.limit > 100)) {
120
- throw configError("limit", "sessions.list limit must be an integer between 1 and 100");
121
- }
122
- if (query?.status !== undefined && !SESSION_STATUS_SET.has(query.status)) {
123
- throw configError("status", "sessions.list status must be a session lifecycle status");
124
- }
125
- if (query?.since !== undefined && (query.since.length === 0 || !Number.isFinite(Date.parse(query.since)))) {
126
- throw configError("since", "sessions.list since must be an ISO-8601 timestamp");
127
- }
128
- if (query?.cursor !== undefined && query.cursor.length === 0) {
129
- throw configError("cursor", "sessions.list cursor must be a non-empty opaque string");
130
- }
131
- }
132
- export async function sendSessionMessage(http, sessionId, request, options) {
133
- const headers = idempotencyHeaders(options);
134
- const accepted = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/messages`, {
135
- method: "POST",
136
- ...(headers ? { headers } : {}),
137
- body: JSON.stringify(request)
138
- });
139
- return normalizeSessionMessageAccepted(accepted, sessionId);
140
- }
141
- export async function listSessionMessages(http, sessionId, query) {
142
- const params = {};
143
- if (query?.limit !== undefined)
144
- params.limit = String(query.limit);
145
- if (query?.cursor !== undefined)
146
- params.cursor = query.cursor;
147
- if (query?.since !== undefined)
148
- params.since = query.since;
149
- return http.request(`/api/sessions/${encodeURIComponent(sessionId)}/messages`, {}, params);
150
- }
151
- export async function suspendSession(http, sessionId) {
152
- const accepted = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/suspend`, { method: "POST" });
153
- return normalizeSessionAccepted(accepted, "session suspend response");
154
- }
155
- export async function cancelSession(http, sessionId) {
156
- const accepted = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/cancel`, { method: "POST" });
157
- return normalizeSessionAccepted(accepted, "session cancel response");
158
- }
159
- export async function resumeSession(http, sessionId) {
160
- const accepted = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/resume`, { method: "POST" });
161
- return normalizeSessionAccepted(accepted, "session resume response");
162
- }
163
- /**
164
- * Request the HITL write-gate: park the session `awaiting_approval` before its
165
- * next gated action (mirrors {@link suspendSession}). Imperative counterpart to
166
- * the declarative submission-time `approvalGate`.
167
- */
168
- export async function requestApproval(http, sessionId) {
169
- const accepted = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/request-approval`, { method: "POST" });
170
- return normalizeSessionAccepted(accepted, "session approval-request response");
171
- }
172
- /** Approve an `awaiting_approval` session so the held turn resumes (→ running). */
173
- export async function approveSession(http, sessionId) {
174
- const accepted = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/approve`, { method: "POST" });
175
- return normalizeSessionAccepted(accepted, "session approve response");
176
- }
177
- /** Deny an `awaiting_approval` session so the held turn is cancelled (→ cancelled). */
178
- export async function denySession(http, sessionId) {
179
- const accepted = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/deny`, { method: "POST" });
180
- return normalizeSessionAccepted(accepted, "session deny response");
181
- }
182
- /**
183
- * Delete a session and purge its stored footprint.
184
- *
185
- * Answers 200 with a BODY, not 204 — the deleted session plus the counters from
186
- * a best-effort cleanup. This used to be typed `SessionStateChangeAccepted |
187
- * void`, which said the body might not arrive and hid the two cleanup fields
188
- * entirely; it always arrives, and `cleanupComplete: false` is the only signal
189
- * that some stored objects survived the delete.
190
- */
191
- export async function deleteSession(http, sessionId) {
192
- const accepted = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" });
193
- const normalized = normalizeSessionAccepted(accepted, "session delete response");
194
- if (typeof normalized.purgedSessionFileObjects !== "number") {
195
- throw new SessionStateError("session delete response is missing purgedSessionFileObjects");
196
- }
197
- if (typeof normalized.cleanupComplete !== "boolean") {
198
- throw new SessionStateError("session delete response is missing cleanupComplete");
199
- }
200
- return normalized;
201
- }
202
- export async function listSessionEvents(http, sessionId) {
203
- const events = [];
204
- for await (const event of iterateSessionEvents(http, sessionId))
205
- events.push(event);
206
- return events;
207
- }
208
- /**
209
- * Lazily traverse a session's durable event history. At most one bounded API
210
- * page is retained by this iterator, and breaking the loop prevents later
211
- * pages from being requested.
212
- */
213
- export async function* iterateSessionEvents(http, sessionId, options = {}) {
214
- const pageSize = options.pageSize;
215
- if (pageSize !== undefined && (!Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 1000)) {
216
- throw configError("pageSize", "session event pageSize must be an integer between 1 and 1000");
217
- }
218
- const path = `/api/sessions/${encodeURIComponent(sessionId)}/events`;
219
- const seenCursors = new Set();
220
- let cursor;
221
- for (let pageIndex = 0; pageIndex < LIST_EVENTS_PAGE_BUDGET; pageIndex += 1) {
222
- if (options.signal?.aborted)
223
- return;
224
- const query = {
225
- ...(pageSize === undefined ? {} : { limit: String(pageSize) }),
226
- ...(cursor === undefined ? {} : { cursor })
227
- };
228
- const result = await http.request(path, options.signal === undefined ? {} : { signal: options.signal }, query);
229
- for (const event of result.events)
230
- yield event;
231
- if (result.nextCursor === undefined || result.nextCursor === null)
232
- return;
233
- if (typeof result.nextCursor !== "string" || result.nextCursor.length === 0) {
234
- throw new SessionStateError("session events response contains an invalid nextCursor", { sessionId });
235
- }
236
- if (seenCursors.has(result.nextCursor)) {
237
- throw new SessionStateError("session events pagination repeated a cursor", {
238
- sessionId,
239
- cursor: result.nextCursor
240
- });
241
- }
242
- seenCursors.add(result.nextCursor);
243
- cursor = result.nextCursor;
244
- }
245
- throw new SessionStateError("session events pagination exceeded its page budget", {
246
- sessionId,
247
- pageBudget: LIST_EVENTS_PAGE_BUDGET
248
- });
249
- }
250
- export async function getSessionOtlpPage(http, sessionId, signal, cursor, abortSignal) {
251
- const path = `/api/sessions/${encodeURIComponent(sessionId)}/otel`;
252
- const { response } = await http.download(path, abortSignal === undefined
253
- ? { headers: { accept: "application/json" } }
254
- : { headers: { accept: "application/json" }, signal: abortSignal }, { signal, ...(cursor === undefined ? {} : { cursor }) });
255
- let body;
256
- try {
257
- body = await response.json();
258
- }
259
- catch (cause) {
260
- throw new SessionStateError("session OTLP response is not valid JSON", { sessionId, signal }, { cause });
261
- }
262
- assertStandardsPureOtlpBody(body, sessionId, signal);
263
- const responseCursor = response.headers.get("x-aex-next-cursor");
264
- if (responseCursor !== null && responseCursor.trim().length === 0) {
265
- throw new SessionStateError("session OTLP response contains an invalid x-aex-next-cursor", {
266
- sessionId,
267
- signal
268
- });
269
- }
270
- return {
271
- body,
272
- ...(responseCursor === null ? {} : { nextCursor: responseCursor })
273
- };
274
- }
275
- /**
276
- * Lazily traverse standards-pure OTLP pages. Pagination metadata remains in
277
- * `x-aex-next-cursor`; it is never mixed into an OTLP request body.
278
- */
279
- export async function* iterateSessionOtlpPages(http, sessionId, signal, options = {}) {
280
- const seenCursors = new Set();
281
- let cursor;
282
- for (let pageIndex = 0; pageIndex < LIST_EVENTS_PAGE_BUDGET; pageIndex += 1) {
283
- if (options.signal?.aborted)
284
- return;
285
- const page = signal === "traces"
286
- ? await getSessionOtlpPage(http, sessionId, "traces", cursor, options.signal)
287
- : await getSessionOtlpPage(http, sessionId, "logs", cursor, options.signal);
288
- if (page.nextCursor !== undefined && seenCursors.has(page.nextCursor)) {
289
- throw new SessionStateError("session OTLP pagination repeated a cursor", {
290
- sessionId,
291
- signal,
292
- cursor: page.nextCursor
293
- });
294
- }
295
- yield page.body;
296
- if (page.nextCursor === undefined)
297
- return;
298
- seenCursors.add(page.nextCursor);
299
- cursor = page.nextCursor;
300
- }
301
- throw new SessionStateError("session OTLP pagination exceeded its page budget", {
302
- sessionId,
303
- signal,
304
- pageBudget: LIST_EVENTS_PAGE_BUDGET
305
- });
306
- }
307
- function assertStandardsPureOtlpBody(body, sessionId, signal) {
308
- const root = signal === "traces" ? "resourceSpans" : "resourceLogs";
309
- if (!isRecord(body) || Object.keys(body).length !== 1 || !Array.isArray(body[root])) {
310
- throw new SessionStateError(`session OTLP ${signal} response must contain only ${root}`, {
311
- sessionId,
312
- signal
313
- });
314
- }
315
- }
316
- export async function listSessionFiles(http, sessionId, query) {
317
- const requestedCheckpointId = query?.checkpointId === undefined
318
- ? undefined
319
- : requireSessionFileCheckpointId(query.checkpointId, "files.list");
320
- const result = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/files`, {}, requestedCheckpointId === undefined ? {} : { checkpointId: requestedCheckpointId });
321
- if (!Array.isArray(result.files) ||
322
- !result.revision ||
323
- typeof result.revision.checkpointId !== "string" ||
324
- !SESSION_FILE_CHECKPOINT_ID_PATTERN.test(result.revision.checkpointId) ||
325
- typeof result.revision.runId !== "string" ||
326
- result.revision.runId.trim().length === 0 ||
327
- !Number.isSafeInteger(result.revision.turnSeq) ||
328
- result.revision.turnSeq < 1 ||
329
- typeof result.revision.committedAt !== "string" ||
330
- !Number.isFinite(Date.parse(result.revision.committedAt)) ||
331
- !Number.isSafeInteger(result.revision.throughSeq) ||
332
- result.revision.throughSeq < 0) {
333
- throw new SessionStateError("session files response is missing checkpoint revision metadata", { sessionId });
334
- }
335
- if (requestedCheckpointId !== undefined && result.revision.checkpointId !== requestedCheckpointId) {
336
- throw new SessionStateError("session files response did not resolve the requested checkpoint", {
337
- sessionId,
338
- requestedCheckpointId,
339
- resolvedCheckpointId: result.revision.checkpointId
340
- });
341
- }
342
- for (const file of result.files) {
343
- if (typeof file.id !== "string" || file.id.length === 0) {
344
- throw new SessionStateError("session files response contains an invalid file id", { sessionId });
345
- }
346
- if (file.checkpointId !== result.revision.checkpointId) {
347
- throw new SessionStateError("session file is not pinned to the response checkpoint", {
348
- sessionId,
349
- fileId: file.id,
350
- fileCheckpointId: file.checkpointId,
351
- checkpointId: result.revision.checkpointId
352
- });
353
- }
354
- if (!Number.isSafeInteger(file.sizeBytes) || file.sizeBytes < 0) {
355
- throw new SessionStateError("session file is missing a valid committed byte length", {
356
- sessionId,
357
- fileId: file.id,
358
- sizeBytes: file.sizeBytes
359
- });
360
- }
361
- if (!SESSION_FILE_SHA256_PATTERN.test(file.sha256)) {
362
- throw new SessionStateError("session file is missing a valid committed SHA-256 digest", {
363
- sessionId,
364
- fileId: file.id
365
- });
366
- }
367
- }
368
- return {
369
- revision: result.revision,
370
- files: query === undefined ? result.files : filterSessionFiles(result.files, query)
371
- };
372
- }
373
- export async function getSessionCoordinatorTicket(http, sessionId) {
374
- return http.request(`/api/sessions/${encodeURIComponent(sessionId)}/events/ticket`, { method: "POST" });
375
- }
376
- // Bound the transparent pager: the read route caps each page at 1000, so this
377
- // admits up to ~1e6 events before bailing — past any real session, but bounded so a
378
- // server that never clears `nextCursor` can't loop forever.
379
- const LIST_EVENTS_PAGE_BUDGET = 1000;
380
- export async function findSessionFiles(http, sessionId, query) {
381
- return (await listSessionFiles(http, sessionId, query)).files;
382
- }
383
- export async function findSessionFile(http, sessionId, query) {
384
- const matches = await findSessionFiles(http, sessionId, query);
385
- if (matches.length === 0)
386
- return null;
387
- if (matches.length === 1)
388
- return matches[0];
389
- throw new SessionStateError("files.findOne: file query matched multiple files", {
390
- sessionId,
391
- matches: matches.map((file) => file.filename ?? file.id)
392
- });
393
- }
394
- export async function sessionFileLink(http, sessionId, selectorOrQuery, options) {
395
- const requestedCheckpointId = options?.checkpointId === undefined
396
- ? undefined
397
- : requireSessionFileCheckpointId(options.checkpointId, "files.link");
398
- const file = await resolveSessionFileLinkTarget(http, sessionId, selectorOrQuery, requestedCheckpointId);
399
- const expiresInSeconds = normalizeSessionFileLinkExpiresIn(options?.expiresIn);
400
- const checkpointId = requestedCheckpointId ?? file.checkpointId;
401
- // The WIRE type, not `SessionFileLink`: the server sends no absolute
402
- // `expiresAt`, and asking for the client type here is what made the synthesis
403
- // below look like a fallback for a field the server sometimes omits.
404
- const result = await http.request(sessionFileRoute(sessionId, file.id, "link", checkpointId), {
405
- method: "POST",
406
- body: JSON.stringify({ expiresInSeconds })
407
- });
408
- const effectiveExpiresIn = result.expiresInSeconds ?? expiresInSeconds;
409
- return {
410
- url: result.url,
411
- expiresInSeconds: effectiveExpiresIn,
412
- expiresAt: syntheticExpiresAt(effectiveExpiresIn),
413
- file
414
- };
415
- }
416
- /**
417
- * The hosted API returns `{ url, expiresInSeconds }` without an absolute
418
- * timestamp; the documented `link.expiresAt` is synthesized client-side from
419
- * the mint time so it is always present on a returned link.
420
- */
421
- function syntheticExpiresAt(expiresInSeconds) {
422
- return new Date(Date.now() + expiresInSeconds * 1000).toISOString();
423
- }
424
- export async function eventArchiveLink(http, sessionId, options) {
425
- const expiresInSeconds = normalizeSessionFileLinkExpiresIn(options?.expiresIn);
426
- // `EventArchiveLinkResponseSchema` — `{ url, expiresInSeconds }` and NO
427
- // `file`, unlike the per-file link. Hence `SessionFileLink.file` staying
428
- // optional while `expiresAt` and `expiresInSeconds` are not.
429
- const result = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/events/link`, {
430
- method: "POST",
431
- body: JSON.stringify({ expiresInSeconds })
432
- });
433
- const effectiveExpiresIn = result.expiresInSeconds ?? expiresInSeconds;
434
- return {
435
- url: result.url,
436
- expiresInSeconds: effectiveExpiresIn,
437
- expiresAt: syntheticExpiresAt(effectiveExpiresIn)
438
- };
439
- }
440
- export async function downloadSessionFile(http, sessionId, selector, options) {
441
- const timeoutMs = normalizeSessionFileTransferTimeoutMs(options?.timeoutMs);
442
- const requestedCheckpointId = options?.checkpointId === undefined
443
- ? undefined
444
- : requireSessionFileCheckpointId(options.checkpointId, "files.download");
445
- const file = await resolveAuthoritativeSessionFile(http, sessionId, selector, requestedCheckpointId);
446
- const checkpointId = requestedCheckpointId ?? file.checkpointId;
447
- const path = sessionFileRoute(sessionId, file.id, "download", checkpointId);
448
- return { file, bytes: await downloadSessionFileBytesWithRetry(http, path, timeoutMs, file) };
449
- }
450
- /** Byte ceiling for {@link readSessionFileText} — a hard cap even if a caller asks for more. */
451
- export const READ_SESSION_FILE_TEXT_MAX_BYTES = 10_000_000;
452
- /** Default `maxBytes` for {@link readSessionFileText} — a chat-sized preview. */
453
- export const READ_SESSION_FILE_TEXT_DEFAULT_BYTES = 50_000;
454
- /** Default per-attempt timeout while fetching or reading one session file body. */
455
- export const SESSION_FILE_TRANSFER_DEFAULT_TIMEOUT_MS = 30_000;
456
- /** Idempotent file GETs retry once on a transfer timeout. */
457
- export const SESSION_FILE_TRANSFER_ATTEMPTS = 2;
458
- /**
459
- * Read ONE session file as byte-capped, decoded UTF-8 text. Built for handing a session
460
- * deliverable to an LLM tool: it streams the file body and STOPS at `maxBytes`, so
461
- * a 200 MB artifact never fully buffers in memory or context. `truncated` is true
462
- * when the file is larger than the cap. Optionally `grep` keeps only matching lines.
463
- *
464
- * Selector is the same `{ path }` / `{ id }` shape as `downloadSessionFile`.
465
- * Both forms resolve against the authoritative checkpoint snapshot first.
466
- */
467
- export async function readSessionFileText(http, sessionId, selector, options) {
468
- const timeoutMs = normalizeSessionFileTransferTimeoutMs(options?.timeoutMs);
469
- const maxBytes = Math.max(1, Math.min(options?.maxBytes ?? READ_SESSION_FILE_TEXT_DEFAULT_BYTES, READ_SESSION_FILE_TEXT_MAX_BYTES));
470
- const requestedCheckpointId = options?.checkpointId === undefined
471
- ? undefined
472
- : requireSessionFileCheckpointId(options.checkpointId, "files.read");
473
- const file = await resolveAuthoritativeSessionFile(http, sessionId, selector, requestedCheckpointId);
474
- const checkpointId = requestedCheckpointId ?? file.checkpointId;
475
- const path = sessionFileRoute(sessionId, file.id, "download", checkpointId);
476
- const capped = await readSessionFileTextWithRetry(http, path, maxBytes, timeoutMs, file);
477
- const text = options?.grep === undefined ? capped.text : grepLines(capped.text, options.grep);
478
- return { file, text, truncated: capped.truncated, totalBytes: capped.totalBytes };
479
- }
480
- async function downloadSessionFileBytesWithRetry(http, path, timeoutMs, file) {
481
- return sessionFileTransferWithRetry(path, timeoutMs, async () => {
482
- const response = await downloadSessionFileResponse(http, path, timeoutMs);
483
- assertSessionFileResponseLength(file, response);
484
- const bytes = await readResponseBytes(response, timeoutMs);
485
- assertSessionFileIntegrity(file, bytes);
486
- return bytes;
487
- });
488
- }
489
- async function resolveAuthoritativeSessionFile(http, sessionId, selector, checkpointOverride) {
490
- const selectorCheckpointId = isPathSelector(selector)
491
- ? undefined
492
- : requireSessionFileCheckpointId(selector && typeof selector === "object" ? selector.checkpointId : undefined, "session file id selector");
493
- const checkpointId = checkpointOverride === undefined
494
- ? selectorCheckpointId
495
- : requireSessionFileCheckpointId(checkpointOverride, "session file operation");
496
- const snapshot = await listSessionFiles(http, sessionId, checkpointId === undefined ? undefined : { checkpointId });
497
- if (isPathSelector(selector))
498
- return resolveSessionFileSelector(snapshot.files, selector, sessionId);
499
- if (typeof selector.id !== "string" || selector.id.length === 0) {
500
- throw new SessionStateError("files.download: selector must include a file id or path", { sessionId });
501
- }
502
- const file = snapshot.files.find((candidate) => candidate.id === selector.id);
503
- if (file !== undefined)
504
- return file;
505
- throw new SessionStateError(`files.download: file id "${selector.id}" was not found in checkpoint`, {
506
- sessionId,
507
- fileId: selector.id,
508
- checkpointId: snapshot.revision.checkpointId
509
- });
510
- }
511
- class SessionFileIntegrityError extends SessionStateError {
512
- }
513
- function assertSessionFileResponseLength(file, response) {
514
- const raw = response.headers.get("content-length");
515
- if (raw === null)
516
- return;
517
- const declaredSizeBytes = /^\d+$/.test(raw) ? Number(raw) : Number.NaN;
518
- if (Number.isSafeInteger(declaredSizeBytes) && declaredSizeBytes === file.sizeBytes)
519
- return;
520
- throw new SessionFileIntegrityError("files.download: checkpoint file integrity verification failed", {
521
- fileId: file.id,
522
- checkpointId: file.checkpointId,
523
- expectedSizeBytes: file.sizeBytes,
524
- declaredSizeBytes: raw
525
- });
526
- }
527
- function assertSessionFileIntegrity(file, bytes) {
528
- const actualSha256 = createHash("sha256").update(bytes).digest("hex");
529
- if (bytes.byteLength === file.sizeBytes && actualSha256 === file.sha256)
530
- return;
531
- throw new SessionFileIntegrityError("files.download: checkpoint file integrity verification failed", {
532
- fileId: file.id,
533
- checkpointId: file.checkpointId,
534
- expectedSizeBytes: file.sizeBytes,
535
- actualSizeBytes: bytes.byteLength,
536
- expectedSha256: file.sha256,
537
- actualSha256
538
- });
539
- }
540
- async function readSessionFileTextWithRetry(http, path, maxBytes, timeoutMs, file) {
541
- return sessionFileTransferWithRetry(path, timeoutMs, async () => {
542
- const response = await downloadSessionFileResponse(http, path, timeoutMs);
543
- assertSessionFileResponseLength(file, response);
544
- return readCappedText(response, maxBytes, timeoutMs, file);
545
- });
546
- }
547
- async function downloadSessionFileResponse(http, path, timeoutMs) {
548
- const controller = new AbortController();
549
- const { response } = await withSessionFileTransferTimeout(http.download(path, { signal: controller.signal }), timeoutMs, () => controller.abort(), "download-open");
550
- return response;
551
- }
552
- async function sessionFileTransferWithRetry(path, timeoutMs, action) {
553
- const startedMs = Date.now();
554
- let lastTimeout;
555
- for (let attempt = 1; attempt <= SESSION_FILE_TRANSFER_ATTEMPTS; attempt += 1) {
556
- try {
557
- return await action();
558
- }
559
- catch (err) {
560
- if (!(err instanceof SessionFileTransferTimeoutError))
561
- throw err;
562
- lastTimeout = err;
563
- }
564
- }
565
- throw new AexNetworkError({
566
- method: "GET",
567
- host: "",
568
- path,
569
- cause: lastTimeout ?? new SessionFileTransferTimeoutError("unknown", timeoutMs),
570
- attempts: SESSION_FILE_TRANSFER_ATTEMPTS,
571
- elapsedMs: Date.now() - startedMs
572
- });
573
- }
574
- function sessionFileRoute(sessionId, fileId, action, checkpointId) {
575
- if (!checkpointId) {
576
- throw new SessionStateError("session file operations require checkpointId", { sessionId, fileId });
577
- }
578
- return `/api/sessions/${encodeURIComponent(sessionId)}/files/${encodeURIComponent(fileId)}/${action}` +
579
- `?checkpointId=${encodeURIComponent(checkpointId)}`;
580
- }
581
- function requireSessionFileCheckpointId(value, context) {
582
- if (typeof value !== "string" || !SESSION_FILE_CHECKPOINT_ID_PATTERN.test(value)) {
583
- throw new SessionStateError(`${context}: checkpointId must match ${SESSION_FILE_CHECKPOINT_ID_PATTERN.source}`, {
584
- checkpointId: value
585
- });
586
- }
587
- return value;
588
- }
589
- function normalizeSessionFileTransferTimeoutMs(value) {
590
- if (value === undefined)
591
- return SESSION_FILE_TRANSFER_DEFAULT_TIMEOUT_MS;
592
- if (!Number.isFinite(value) || value <= 0) {
593
- throw configError("timeoutMs", "files.download: timeoutMs must be a positive finite number");
594
- }
595
- return Math.max(1, Math.floor(value));
596
- }
597
- class SessionFileTransferTimeoutError extends Error {
598
- code = "ETIMEDOUT";
599
- phase;
600
- constructor(phase, timeoutMs) {
601
- super(`file transfer phase=${phase} timed out after ${timeoutMs}ms`);
602
- this.name = "SessionFileTransferTimeoutError";
603
- this.phase = phase;
604
- }
605
- }
606
- async function withSessionFileTransferTimeout(promise, timeoutMs, abort, phase) {
607
- let timedOut = false;
608
- let timeout;
609
- const timeoutPromise = new Promise((_, reject) => {
610
- timeout = setTimeout(() => {
611
- timedOut = true;
612
- reject(new SessionFileTransferTimeoutError(phase, timeoutMs));
613
- queueMicrotask(() => {
614
- try {
615
- abort();
616
- }
617
- catch {
618
- // Best effort: the timeout itself is the user-facing failure.
619
- }
620
- });
621
- }, timeoutMs);
622
- });
623
- try {
624
- return await Promise.race([promise, timeoutPromise]);
625
- }
626
- catch (err) {
627
- if (timedOut && isAbortLikeError(err))
628
- throw new SessionFileTransferTimeoutError(phase, timeoutMs);
629
- throw err;
630
- }
631
- finally {
632
- if (timeout !== undefined)
633
- clearTimeout(timeout);
634
- }
635
- }
636
- function isAbortLikeError(err) {
637
- const name = err?.name;
638
- return name === "AbortError";
639
- }
640
- async function readResponseBytes(response, timeoutMs) {
641
- const body = response.body;
642
- if (!body) {
643
- const buffer = await withSessionFileTransferTimeout(response.arrayBuffer(), timeoutMs, () => { }, "body-read");
644
- return new Uint8Array(buffer);
645
- }
646
- const reader = body.getReader();
647
- const chunks = [];
648
- try {
649
- while (true) {
650
- const { done, value } = await withSessionFileTransferTimeout(reader.read(), timeoutMs, () => {
651
- void reader.cancel().catch(() => { });
652
- }, "body-read");
653
- if (done)
654
- break;
655
- if (value && value.byteLength > 0)
656
- chunks.push(value);
657
- }
658
- }
659
- finally {
660
- void reader.cancel().catch(() => { });
661
- }
662
- return concatBytes(chunks);
663
- }
664
- /**
665
- * Read a streamed response body up to `maxBytes` and decode as UTF-8. The
666
- * checkpoint supplies the authoritative total size. A partial read cancels as
667
- * soon as the retained prefix reaches the cap; a complete read reaches EOF and
668
- * verifies both committed size and digest.
669
- */
670
- async function readCappedText(response, maxBytes, timeoutMs, file) {
671
- const decoder = new TextDecoder("utf-8");
672
- const wholeFile = file.sizeBytes <= maxBytes;
673
- const targetBytes = wholeFile ? file.sizeBytes : maxBytes;
674
- const body = response.body;
675
- if (!body) {
676
- // No streaming body (some fetch polyfills) — buffer, then slice to the cap.
677
- const buf = new Uint8Array(await withSessionFileTransferTimeout(response.arrayBuffer(), timeoutMs, () => { }, "body-read"));
678
- if (wholeFile) {
679
- assertSessionFileIntegrity(file, buf);
680
- }
681
- else if (buf.byteLength < targetBytes) {
682
- throw new SessionFileIntegrityError("files.read: checkpoint file ended before the requested prefix", {
683
- fileId: file.id,
684
- checkpointId: file.checkpointId,
685
- expectedPrefixBytes: targetBytes,
686
- actualSizeBytes: buf.byteLength
687
- });
688
- }
689
- return {
690
- text: decoder.decode(buf.subarray(0, targetBytes)),
691
- truncated: !wholeFile,
692
- totalBytes: file.sizeBytes
693
- };
694
- }
695
- const reader = body.getReader();
696
- const chunks = [];
697
- let retainedBytes = 0;
698
- try {
699
- while (wholeFile || retainedBytes < targetBytes) {
700
- const { done, value } = await withSessionFileTransferTimeout(reader.read(), timeoutMs, () => {
701
- void reader.cancel().catch(() => { });
702
- }, "body-read");
703
- if (done)
704
- break;
705
- if (value && value.byteLength > 0) {
706
- const remainingBytes = targetBytes - retainedBytes;
707
- const retainedFromChunk = Math.min(remainingBytes, value.byteLength);
708
- if (retainedFromChunk > 0) {
709
- chunks.push(retainedFromChunk === value.byteLength ? value : value.slice(0, retainedFromChunk));
710
- retainedBytes += retainedFromChunk;
711
- }
712
- if (wholeFile && value.byteLength > remainingBytes) {
713
- throw new SessionFileIntegrityError("files.read: checkpoint file exceeded its committed byte length", {
714
- fileId: file.id,
715
- checkpointId: file.checkpointId,
716
- expectedSizeBytes: file.sizeBytes,
717
- actualSizeBytesAtLeast: retainedBytes + value.byteLength - retainedFromChunk
718
- });
719
- }
720
- if (!wholeFile && retainedBytes >= targetBytes)
721
- break;
722
- }
723
- }
724
- }
725
- finally {
726
- void reader.cancel().catch(() => { });
727
- }
728
- const merged = concatBytes(chunks);
729
- if (wholeFile) {
730
- assertSessionFileIntegrity(file, merged);
731
- }
732
- else if (retainedBytes < targetBytes) {
733
- throw new SessionFileIntegrityError("files.read: checkpoint file ended before the requested prefix", {
734
- fileId: file.id,
735
- checkpointId: file.checkpointId,
736
- expectedPrefixBytes: targetBytes,
737
- actualSizeBytes: retainedBytes
738
- });
739
- }
740
- return { text: decoder.decode(merged), truncated: !wholeFile, totalBytes: file.sizeBytes };
741
- }
742
- function concatBytes(chunks) {
743
- if (chunks.length === 1)
744
- return chunks[0];
745
- const total = chunks.reduce((n, c) => n + c.byteLength, 0);
746
- const out = new Uint8Array(total);
747
- let offset = 0;
748
- for (const c of chunks) {
749
- out.set(c, offset);
750
- offset += c.byteLength;
751
- }
752
- return out;
753
- }
754
- function grepLines(text, pattern) {
755
- const test = typeof pattern === "string"
756
- ? (line) => line.toLowerCase().includes(pattern.toLowerCase())
757
- : (line) => pattern.test(line);
758
- return text
759
- .split("\n")
760
- .filter((line) => test(line))
761
- .join("\n");
762
- }
763
- /**
764
- * List a session's subagent child sessions (`GET /api/sessions/:id/children`). Each
765
- * {@link ChildSessionRef} is a read-only lineage snapshot whose id addresses
766
- * events, checkpointed files, and descendants. An empty array means the session
767
- * spawned no children.
768
- */
769
- export async function listSessionChildren(http, sessionId) {
770
- const result = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/children`);
771
- if (!isRecord(result) || !Array.isArray(result.children)) {
772
- throw new SessionStateError("session children response must contain a children array");
773
- }
774
- return result.children.map(parseChildSessionRef);
775
- }
776
- function parseChildSessionRef(value, index) {
777
- if (!isRecord(value)) {
778
- throw new SessionStateError(`session children response has an invalid row at index ${index}`);
779
- }
780
- for (const field of ["id", "parentSessionId", "status", "createdAt", "updatedAt"]) {
781
- if (typeof value[field] !== "string" || value[field].length === 0) {
782
- throw new SessionStateError(`session children row ${index} has an invalid ${field}`);
783
- }
784
- }
785
- if (!SESSION_STATUS_SET.has(value.status)) {
786
- throw new SessionStateError(`session children row ${index} has an unknown lifecycle status`);
787
- }
788
- assertCanonicalSessionWireFields(value, `session children row ${index}`);
789
- if (value.depth !== undefined && (!Number.isSafeInteger(value.depth) || value.depth < 1)) {
790
- throw new SessionStateError(`session children row ${index} has an invalid depth`);
791
- }
792
- if (value.costUsd !== undefined &&
793
- (typeof value.costUsd !== "number" || !Number.isFinite(value.costUsd) || value.costUsd < 0)) {
794
- throw new SessionStateError(`session children row ${index} has an invalid costUsd`);
795
- }
796
- if (value.terminalAt !== undefined && value.terminalAt !== null && typeof value.terminalAt !== "string") {
797
- throw new SessionStateError(`session children row ${index} has an invalid terminalAt`);
798
- }
799
- if (value.lastRun !== undefined && !isRecord(value.lastRun)) {
800
- throw new SessionStateError(`session children row ${index} has an invalid lastRun`);
801
- }
802
- return value;
803
- }
804
- /**
805
- * List a session's run-terminal webhook deliveries. Each finalized run has its
806
- * own row; an empty array means the session has no webhook or no run has
807
- * finalized yet.
808
- */
809
- export async function getSessionWebhookDeliveries(http, sessionId) {
810
- const result = await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/webhook-deliveries`);
811
- if (!isRecord(result) || !Array.isArray(result.deliveries)) {
812
- throw new SessionStateError("session webhook deliveries response must contain a deliveries array");
813
- }
814
- return result.deliveries;
815
- }
816
- /**
817
- * Manually re-trigger one run's webhook delivery: resets the row to `pending`
818
- * and re-sends the frozen payload with the same run-scoped `webhook-id`.
819
- */
820
- export async function redeliverSessionWebhook(http, sessionId, deliveryId) {
821
- await http.request(`/api/sessions/${encodeURIComponent(sessionId)}/webhook-deliveries/${encodeURIComponent(deliveryId)}/redeliver`, { method: "POST" });
822
- }
823
- /**
824
- * Delete a workspace asset cache entry. Accepts an `asset_<id>` value,
825
- * `sha256:<hex>`, or a bare 64-hex digest. Workspace is derived server-side
826
- * from the token; idempotent.
827
- * Does NOT affect sessions that already snapshotted the asset.
828
- */
829
- export async function deleteWorkspaceAsset(http, hash) {
830
- const assetId = hash.startsWith("asset_")
831
- ? hash
832
- : `asset_${hash.startsWith("sha256:") ? hash.slice("sha256:".length) : hash}`;
833
- await http.request(`/api/assets/${encodeURIComponent(assetId)}`, { method: "DELETE" });
834
- }
835
- export async function whoami(http) {
836
- return parseWhoAmI(await http.request("/api/whoami"));
837
- }
838
- /**
839
- * Validate a CONTROL-plane account token (PAT / device session) against the
840
- * dashboard BFF `GET /api/whoami`. Same endpoint as {@link whoami}, but the
841
- * bearer is an account PAT (`aexu_...`) so the server answers with an
842
- * `account_token` principal (no workspace, no data-plane limits) — a shape the
843
- * workspace-key {@link whoami} parser rejects. Used by
844
- * `aex login --api-key <account PAT>` to prove the PAT before persisting it.
845
- */
846
- export async function accountWhoami(http) {
847
- return parseAccountWhoAmI(await http.request("/api/whoami"));
848
- }
849
- function parseAccountWhoAmI(value) {
850
- if (!isRecord(value)) {
851
- throw new SessionStateError("account whoami response must be an object");
852
- }
853
- if (value.ok !== true || value.principalType !== "account_token") {
854
- throw new SessionStateError("account whoami response must identify an account_token principal");
855
- }
856
- if (typeof value.appUserId !== "string" || value.appUserId.length === 0) {
857
- throw new SessionStateError("account whoami response appUserId must be a non-empty string");
858
- }
859
- if (!Array.isArray(value.scopes) || !value.scopes.every((scope) => typeof scope === "string")) {
860
- throw new SessionStateError("account whoami response scopes must be an array of strings");
861
- }
862
- return {
863
- ok: true,
864
- principalType: "account_token",
865
- appUserId: value.appUserId,
866
- scopes: value.scopes,
867
- ...(typeof value.orgId === "string" ? { orgId: value.orgId } : {}),
868
- ...(typeof value.tokenId === "string" ? { tokenId: value.tokenId } : {}),
869
- ...(typeof value.tokenName === "string" ? { tokenName: value.tokenName } : {}),
870
- ...(typeof value.tokenKind === "string" ? { tokenKind: value.tokenKind } : {})
871
- };
872
- }
873
- function parseWhoAmI(value) {
874
- if (!isRecord(value)) {
875
- throw new SessionStateError("whoami response must be an object");
876
- }
877
- const removed = ["caps", "tokenId", "tokenName"].find((field) => Object.hasOwn(value, field));
878
- if (removed !== undefined) {
879
- throw new SessionStateError(`whoami response contains the removed ${removed} field`);
880
- }
881
- if (value.ok !== true || value.principalType !== "api_key") {
882
- throw new SessionStateError("whoami response must identify an api_key principal");
883
- }
884
- if (typeof value.workspaceId !== "string" || value.workspaceId.length === 0) {
885
- throw new SessionStateError("whoami response workspaceId must be a non-empty string");
886
- }
887
- if (!Array.isArray(value.scopes) || !value.scopes.every((scope) => typeof scope === "string" && scope.length > 0)) {
888
- throw new SessionStateError("whoami response scopes must be an array of non-empty strings");
889
- }
890
- const limits = parseWhoAmILimits(value.limits);
891
- const runtimeCapabilities = value.runtimeCapabilities === undefined
892
- ? undefined
893
- : parseRuntimeCapabilities(value.runtimeCapabilities);
894
- return {
895
- ok: true,
896
- principalType: "api_key",
897
- workspaceId: value.workspaceId,
898
- scopes: value.scopes,
899
- limits,
900
- ...(runtimeCapabilities ? { runtimeCapabilities } : {})
901
- };
902
- }
903
- const RUNTIME_KIND_SET = new Set(RUNTIME_KINDS);
904
- const RUNTIME_SIZE_SET = new Set(RUNTIME_SIZES);
905
- function parseRuntimeCapabilities(value) {
906
- const field = "whoami response runtimeCapabilities";
907
- if (!isRecord(value))
908
- throw new SessionStateError(`${field} must be an object`);
909
- if (value.schemaVersion !== 1)
910
- throw new SessionStateError(`${field}.schemaVersion must be 1`);
911
- if (typeof value.capabilityVersion !== "string" || value.capabilityVersion.length === 0) {
912
- throw new SessionStateError(`${field}.capabilityVersion must be a non-empty string`);
913
- }
914
- if (typeof value.capabilityHash !== "string" || !CANONICAL_SHA256_DIGEST_PATTERN.test(value.capabilityHash)) {
915
- throw new SessionStateError(`${field}.capabilityHash must be a canonical SHA-256 digest`);
916
- }
917
- if (!Array.isArray(value.availableRuntimeKinds)) {
918
- throw new SessionStateError(`${field}.availableRuntimeKinds must be an array`);
919
- }
920
- const availableRuntimeKinds = value.availableRuntimeKinds;
921
- if (!availableRuntimeKinds.every((kind) => typeof kind === "string" && RUNTIME_KIND_SET.has(kind)) ||
922
- new Set(availableRuntimeKinds).size !== availableRuntimeKinds.length) {
923
- throw new SessionStateError(`${field}.availableRuntimeKinds must contain unique supported runtime kinds`);
924
- }
925
- if (!isRecord(value.sizesByRuntimeKind)) {
926
- throw new SessionStateError(`${field}.sizesByRuntimeKind must be an object`);
927
- }
928
- if (!isRecord(value.unavailable)) {
929
- throw new SessionStateError(`${field}.unavailable must be an object`);
930
- }
931
- const unknownSizeKind = Object.keys(value.sizesByRuntimeKind).find((kind) => !RUNTIME_KIND_SET.has(kind));
932
- const unknownUnavailableKind = Object.keys(value.unavailable).find((kind) => !RUNTIME_KIND_SET.has(kind));
933
- if (unknownSizeKind || unknownUnavailableKind) {
934
- throw new SessionStateError(`${field} contains an unknown runtime kind`);
935
- }
936
- const available = new Set(availableRuntimeKinds);
937
- const sizesByRuntimeKind = {};
938
- const unavailable = {};
939
- for (const runtimeKind of RUNTIME_KINDS) {
940
- const sizes = value.sizesByRuntimeKind[runtimeKind];
941
- const reason = value.unavailable[runtimeKind];
942
- if (available.has(runtimeKind)) {
943
- const parsedSizes = Array.isArray(sizes)
944
- ? sizes.map((size) => {
945
- try {
946
- return parseRuntimeSizeForRead(size);
947
- }
948
- catch {
949
- return undefined;
950
- }
951
- })
952
- : undefined;
953
- if (!Array.isArray(parsedSizes) || parsedSizes.length === 0 ||
954
- !parsedSizes.every((size) => typeof size === "string" && RUNTIME_SIZE_SET.has(size)) ||
955
- new Set(parsedSizes).size !== parsedSizes.length || reason !== undefined) {
956
- throw new SessionStateError(`${field}.${runtimeKind} must have unique supported sizes and no unavailable reason`);
957
- }
958
- sizesByRuntimeKind[runtimeKind] = parsedSizes;
959
- }
960
- else {
961
- if (sizes !== undefined || !isRecord(reason) || typeof reason.code !== "string" || reason.code.length === 0) {
962
- throw new SessionStateError(`${field}.${runtimeKind} must have one unavailable reason and no sizes`);
963
- }
964
- unavailable[runtimeKind] = { code: reason.code };
965
- }
966
- }
967
- return {
968
- schemaVersion: 1,
969
- capabilityVersion: value.capabilityVersion,
970
- capabilityHash: value.capabilityHash,
971
- availableRuntimeKinds,
972
- sizesByRuntimeKind,
973
- unavailable,
974
- profilesByRuntimeKind: parseRuntimeProfiles(value.profilesByRuntimeKind)
975
- };
976
- }
977
- const RUNTIME_CAPABILITY_STATES = new Set(["supported", "unsupported"]);
978
- const TOOL_EXECUTION_DELIVERY = new Set(["at-least-once", "exactly-once"]);
979
- const COLD_START_CLASSES = new Set(["warm", "cold-seconds", "cold-tens-of-seconds"]);
980
- const IDLE_BILLING_CLASSES = new Set(["wall-clock", "zero"]);
981
- const COMPUTE_BASES = new Set(["wall_clock", "microvm_running"]);
982
- /**
983
- * The profile set is TOTAL over runtime kinds and is not optional: a response that
984
- * declares which runtimes exist but not what they do is exactly the gap the public
985
- * parity claim used to paper over. A missing or partial set is a contract violation.
986
- */
987
- function parseRuntimeProfiles(value) {
988
- const field = "whoami response runtimeCapabilities.profilesByRuntimeKind";
989
- if (!isRecord(value))
990
- throw new SessionStateError(`${field} must be an object`);
991
- const unknownKind = Object.keys(value).find((kind) => !RUNTIME_KIND_SET.has(kind));
992
- if (unknownKind !== undefined)
993
- throw new SessionStateError(`${field} contains an unknown runtime kind`);
994
- const profiles = {};
995
- for (const runtimeKind of RUNTIME_KINDS) {
996
- profiles[runtimeKind] = parseRuntimeProfile(value[runtimeKind], `${field}.${runtimeKind}`, runtimeKind);
997
- }
998
- return profiles;
999
- }
1000
- function parseRuntimeProfile(value, field, runtimeKind) {
1001
- if (!isRecord(value))
1002
- throw new SessionStateError(`${field} must be an object`);
1003
- if (value.schemaVersion !== 1)
1004
- throw new SessionStateError(`${field}.schemaVersion must be 1`);
1005
- if (value.runtimeKind !== runtimeKind)
1006
- throw new SessionStateError(`${field}.runtimeKind must be ${runtimeKind}`);
1007
- if (!isRecord(value.capabilities))
1008
- throw new SessionStateError(`${field}.capabilities must be an object`);
1009
- const capabilities = {};
1010
- for (const capability of RUNTIME_CAPABILITY_NAMES) {
1011
- const state = value.capabilities[capability];
1012
- if (typeof state !== "string" || !RUNTIME_CAPABILITY_STATES.has(state)) {
1013
- throw new SessionStateError(`${field}.capabilities.${capability} must be supported or unsupported`);
1014
- }
1015
- capabilities[capability] = state;
1016
- }
1017
- if (!isRecord(value.limits))
1018
- throw new SessionStateError(`${field}.limits must be an object`);
1019
- const limits = value.limits;
1020
- for (const limit of ["maxSessionMs", "maxSingleEffectMs", "maxWorkspaceBytes", "maxConcurrentToolCalls"]) {
1021
- const observed = limits[limit];
1022
- if (typeof observed !== "number" || !Number.isFinite(observed) || observed <= 0) {
1023
- throw new SessionStateError(`${field}.limits.${limit} must be a positive number`);
1024
- }
1025
- }
1026
- if (!isRecord(value.delivery))
1027
- throw new SessionStateError(`${field}.delivery must be an object`);
1028
- const delivery = value.delivery;
1029
- for (const [key, allowed] of [
1030
- ["toolExecution", TOOL_EXECUTION_DELIVERY],
1031
- ["coldStartClass", COLD_START_CLASSES],
1032
- ["idleBilling", IDLE_BILLING_CLASSES]
1033
- ]) {
1034
- const observed = delivery[key];
1035
- if (typeof observed !== "string" || !allowed.has(observed)) {
1036
- throw new SessionStateError(`${field}.delivery.${key} is not a recognized value`);
1037
- }
1038
- }
1039
- if (typeof value.computeBasis !== "string" || !COMPUTE_BASES.has(value.computeBasis)) {
1040
- throw new SessionStateError(`${field}.computeBasis is not a recognized value`);
1041
- }
1042
- return {
1043
- schemaVersion: 1,
1044
- runtimeKind,
1045
- capabilities,
1046
- limits: {
1047
- maxSessionMs: limits.maxSessionMs,
1048
- maxSingleEffectMs: limits.maxSingleEffectMs,
1049
- maxWorkspaceBytes: limits.maxWorkspaceBytes,
1050
- maxConcurrentToolCalls: limits.maxConcurrentToolCalls
1051
- },
1052
- delivery: {
1053
- toolExecution: delivery.toolExecution,
1054
- coldStartClass: delivery.coldStartClass,
1055
- idleBilling: delivery.idleBilling
1056
- },
1057
- computeBasis: value.computeBasis
1058
- };
1059
- }
1060
- function parseWhoAmILimits(value) {
1061
- if (!isRecord(value)) {
1062
- throw new SessionStateError("whoami response limits must be an object");
1063
- }
1064
- const requiredNumbers = [
1065
- "maxConcurrentSessions",
1066
- "submitRatePerMinute",
1067
- "spendCapUsd",
1068
- "monthSpendUsd",
1069
- "balanceUsd",
1070
- "balanceGraceFloorUsd",
1071
- "llmTokenAllowanceRemainingUsd"
1072
- ];
1073
- for (const field of requiredNumbers) {
1074
- if (typeof value[field] !== "number" || !Number.isFinite(value[field])) {
1075
- throw new SessionStateError(`whoami response limits.${field} must be a finite number`);
1076
- }
1077
- }
1078
- for (const field of ["creditGateActive", "autoTopupEnabled"]) {
1079
- if (typeof value[field] !== "boolean") {
1080
- throw new SessionStateError(`whoami response limits.${field} must be a boolean`);
1081
- }
1082
- }
1083
- const paymentMethodStatus = value.paymentMethodStatus;
1084
- const admissionState = value.admissionState;
1085
- const accountType = value.accountType;
1086
- assertOneOf(paymentMethodStatus, ["none", "active"], "limits.paymentMethodStatus");
1087
- assertOneOf(admissionState, BILLING_ADMISSION_STATES, "limits.admissionState");
1088
- assertOneOf(accountType, ["standard", "internal"], "limits.accountType");
1089
- return {
1090
- maxConcurrentSessions: value.maxConcurrentSessions,
1091
- submitRatePerMinute: value.submitRatePerMinute,
1092
- spendCapUsd: value.spendCapUsd,
1093
- monthSpendUsd: value.monthSpendUsd,
1094
- balanceUsd: value.balanceUsd,
1095
- balanceGraceFloorUsd: value.balanceGraceFloorUsd,
1096
- llmTokenAllowanceRemainingUsd: value.llmTokenAllowanceRemainingUsd,
1097
- creditGateActive: value.creditGateActive,
1098
- paymentMethodStatus,
1099
- admissionState,
1100
- autoTopupEnabled: value.autoTopupEnabled,
1101
- accountType
1102
- };
1103
- }
1104
- function assertOneOf(value, allowed, field) {
1105
- if (!isStringLiteral(value, allowed)) {
1106
- throw new SessionStateError(`whoami response ${field} is invalid`);
1107
- }
1108
- }
1109
- /**
1110
- * Download each artifact's bytes into a zip-file map keyed by
1111
- * `<zipPrefix><relative-path>`, fetched from the `files`
1112
- * download route. Best-effort: a per-artifact fetch failure records an
1113
- * `errors[]` entry rather than aborting the rest, so the failure is
1114
- * surfaced (never silent) while a partially-available run still yields a
1115
- * usable zip. A committed size or digest contradiction is terminal because
1116
- * returning a partial archive would misrepresent corrupted bytes as absent.
1117
- */
1118
- async function collectArtifactBytes(http, sessionId, items, zipPrefix, namespace, timeoutMs = SESSION_FILE_TRANSFER_DEFAULT_TIMEOUT_MS) {
1119
- const entries = [];
1120
- const captured = [];
1121
- const errors = [];
1122
- for (const item of items) {
1123
- const rel = item.filename ?? item.id;
1124
- try {
1125
- const path = sessionFileRoute(sessionId, item.id, "download", item.checkpointId);
1126
- entries.push({
1127
- path: `${zipPrefix}${rel}`,
1128
- bytes: await downloadSessionFileBytesWithRetry(http, path, timeoutMs, item),
1129
- ...(item.contentType !== undefined ? { contentType: item.contentType } : {}),
1130
- customerContent: true
1131
- });
1132
- captured.push({
1133
- id: item.id,
1134
- filename: item.filename ?? null,
1135
- ...(item.sizeBytes !== undefined ? { sizeBytes: item.sizeBytes } : {}),
1136
- ...(item.contentType !== undefined ? { contentType: item.contentType } : {})
1137
- });
1138
- }
1139
- catch (err) {
1140
- if (err instanceof SessionFileIntegrityError)
1141
- throw err;
1142
- errors.push({ namespace, id: item.id, filename: item.filename ?? null, message: err.message });
1143
- }
1144
- }
1145
- return { entries: Object.freeze(entries), captured, errors };
1146
- }
1147
- export function normalizeSessionFileLinkExpiresIn(input = "1h") {
1148
- if (typeof input === "number") {
1149
- if (!Number.isFinite(input) || input <= 0) {
1150
- throw new SessionStateError("sessionFileLink: expiresIn must be a positive number of seconds", {
1151
- expiresIn: input
1152
- });
1153
- }
1154
- return Math.floor(input);
1155
- }
1156
- if (input === "15m")
1157
- return 15 * 60;
1158
- if (input === "1h")
1159
- return 60 * 60;
1160
- if (input === "1d")
1161
- return 24 * 60 * 60;
1162
- throw new SessionStateError("sessionFileLink: expiresIn must be seconds, \"15m\", \"1h\", or \"1d\"", {
1163
- expiresIn: input
1164
- });
1165
- }
1166
- async function resolveSessionFileLinkTarget(http, sessionId, selectorOrQuery, checkpointId) {
1167
- if (hasSessionFileIdProperty(selectorOrQuery)) {
1168
- if (typeof selectorOrQuery.id !== "string" || selectorOrQuery.id.length === 0) {
1169
- throw new SessionStateError("sessionFileLink: selector must include a file id or query", { sessionId });
1170
- }
1171
- return resolveAuthoritativeSessionFile(http, sessionId, selectorOrQuery, checkpointId);
1172
- }
1173
- if (isPathSelector(selectorOrQuery) && selectorOrQuery.match === "suffix") {
1174
- const snapshot = await listSessionFiles(http, sessionId, checkpointId ? { checkpointId } : undefined);
1175
- return resolveSessionFileSelector(snapshot.files, selectorOrQuery, sessionId);
1176
- }
1177
- const match = await findSessionFile(http, sessionId, {
1178
- ...selectorOrQuery,
1179
- ...(checkpointId ? { checkpointId } : {})
1180
- });
1181
- if (match)
1182
- return match;
1183
- throw new SessionStateError("sessionFileLink: file query matched no files", { sessionId });
1184
- }
1185
- function hasSessionFileIdProperty(value) {
1186
- return Boolean(value &&
1187
- typeof value === "object" &&
1188
- "id" in value);
1189
- }
1190
- /**
1191
- * Download EVERYTHING public about a session as one zip, organised into the three
1192
- * namespace folders:
1193
- *
1194
- * metadata/session.json — the session record.
1195
- * events/events.jsonl — typed event-channel records.
1196
- * files/<rel> — the session's captured files.
1197
- * manifest.json — `SessionRecordManifestV1`.
1198
- */
1199
- export async function download(http, sessionId) {
1200
- const [session, events, snapshot] = await Promise.all([
1201
- getSession(http, sessionId),
1202
- listSessionEvents(http, sessionId),
1203
- listSessionFiles(http, sessionId)
1204
- ]);
1205
- const collectedFiles = await collectArtifactBytes(http, sessionId, snapshot.files, "files/", "files");
1206
- return buildSessionArchive(sessionId, session, events, collectedFiles);
1207
- }
1208
- /**
1209
- * Download only the session's captured files (the `files` namespace). Zip
1210
- * layout: `<rel>` per file plus a `manifest.json`
1211
- * (`{ sessionId, namespace: "files", files[], errors[] }`).
1212
- */
1213
- export async function downloadSessionFiles(http, sessionId, options) {
1214
- const timeoutMs = normalizeSessionFileTransferTimeoutMs(options?.timeoutMs);
1215
- const requestedCheckpointId = options?.checkpointId === undefined
1216
- ? undefined
1217
- : requireSessionFileCheckpointId(options.checkpointId, "files.download");
1218
- const snapshot = await listSessionFiles(http, sessionId, requestedCheckpointId === undefined ? undefined : { checkpointId: requestedCheckpointId });
1219
- const collectedFiles = await collectArtifactBytes(http, sessionId, snapshot.files, "", "files", timeoutMs);
1220
- return buildSessionFilesArchive(sessionId, collectedFiles);
1221
- }
1222
- /**
1223
- * Download only the event archive (the `events` namespace). Always includes
1224
- * typed `events.jsonl` plus `manifest.json`.
1225
- */
1226
- export async function downloadEvents(http, sessionId) {
1227
- const events = await listSessionEvents(http, sessionId);
1228
- return buildSessionEventsArchive(sessionId, events);
1229
- }
1230
- /**
1231
- * Download only the session record (the `metadata` namespace) as a zip
1232
- * containing `session.json` plus `manifest.json`.
1233
- */
1234
- export async function downloadMetadata(http, sessionId) {
1235
- const session = await getSession(http, sessionId);
1236
- return buildSessionMetadataArchive(sessionId, session);
1237
- }
1238
- export async function publishWorkspaceFile(http, input) {
1239
- return publishWorkspaceResource(http, "files", input);
1240
- }
1241
- export async function publishWorkspaceSkill(http, input) {
1242
- return publishWorkspaceResource(http, "skills", input);
1243
- }
1244
- export async function publishWorkspaceTool(http, input) {
1245
- return publishWorkspaceResource(http, "tools", input);
1246
- }
1247
- export async function publishWorkspaceInstruction(http, input) {
1248
- return publishWorkspaceResource(http, "instructions", input);
1249
- }
1250
- export function listWorkspaceFiles(http, query = {}) {
1251
- return listWorkspaceResources(http, "files", query);
1252
- }
1253
- export function listWorkspaceSkills(http, query = {}) {
1254
- return listWorkspaceResources(http, "skills", query);
1255
- }
1256
- export function listWorkspaceTools(http, query = {}) {
1257
- return listWorkspaceResources(http, "tools", query);
1258
- }
1259
- export function listWorkspaceInstructions(http, query = {}) {
1260
- return listWorkspaceResources(http, "instructions", query);
1261
- }
1262
- export function getWorkspaceFile(http, resourceId, version) {
1263
- return getWorkspaceResource(http, "files", resourceId, version);
1264
- }
1265
- export function getWorkspaceSkill(http, resourceId, version) {
1266
- return getWorkspaceResource(http, "skills", resourceId, version);
1267
- }
1268
- export function getWorkspaceTool(http, resourceId, version) {
1269
- return getWorkspaceResource(http, "tools", resourceId, version);
1270
- }
1271
- export function getWorkspaceInstruction(http, resourceId, version) {
1272
- return getWorkspaceResource(http, "instructions", resourceId, version);
1273
- }
1274
- export function deleteWorkspaceFile(http, resourceId) {
1275
- return deleteWorkspaceResource(http, "files", resourceId);
1276
- }
1277
- export function deleteWorkspaceSkill(http, resourceId) {
1278
- return deleteWorkspaceResource(http, "skills", resourceId);
1279
- }
1280
- export function deleteWorkspaceTool(http, resourceId) {
1281
- return deleteWorkspaceResource(http, "tools", resourceId);
1282
- }
1283
- export function deleteWorkspaceInstruction(http, resourceId) {
1284
- return deleteWorkspaceResource(http, "instructions", resourceId);
1285
- }
1286
- async function publishWorkspaceResource(http, kind, input) {
1287
- const result = await http.request(`/api/workspace/${kind}`, {
1288
- method: "POST",
1289
- body: JSON.stringify(input)
1290
- });
1291
- return result.resource;
1292
- }
1293
- async function listWorkspaceResources(http, kind, query) {
1294
- if (query.limit !== undefined &&
1295
- (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > 100)) {
1296
- throw new Error("workspace resource list limit must be an integer from 1 through 100");
1297
- }
1298
- return http.request(`/api/workspace/${kind}`, {}, {
1299
- ...(query.cursor !== undefined ? { cursor: query.cursor } : {}),
1300
- ...(query.limit !== undefined ? { limit: String(query.limit) } : {})
1301
- });
1302
- }
1303
- async function getWorkspaceResource(http, kind, resourceId, version) {
1304
- const result = await http.request(`/api/workspace/${kind}/${encodeURIComponent(resourceId)}`, {}, version === undefined ? {} : { version: String(version) });
1305
- return result.resource;
1306
- }
1307
- async function deleteWorkspaceResource(http, kind, resourceId) {
1308
- await http.request(`/api/workspace/${kind}/${encodeURIComponent(resourceId)}`, { method: "DELETE" });
1309
- }
1310
- function unwrapSession(result) {
1311
- if (!isRecord(result) || !isRecord(result.session)) {
1312
- throw new SessionStateError("session response must contain a session object");
1313
- }
1314
- const value = result.session;
1315
- if (typeof value.id !== "string" || value.id.length === 0) {
1316
- throw new SessionStateError("session response is missing its canonical id");
1317
- }
1318
- assertCanonicalSessionWireFields(value, "session response");
1319
- if (typeof value.status !== "string" || !SESSION_STATUS_SET.has(value.status)) {
1320
- throw new SessionStateError("session response has an unknown lifecycle status");
1321
- }
1322
- if (typeof value.acceptsMessages !== "boolean") {
1323
- throw new SessionStateError("session response is missing acceptsMessages");
1324
- }
1325
- const currentRun = normalizeOptionalSessionRun(value.currentRun, value.id, "session response currentRun");
1326
- const lastRun = normalizeOptionalSessionRun(value.lastRun, value.id, "session response lastRun");
1327
- let providerFault;
1328
- if (Object.hasOwn(value, "providerFault")) {
1329
- try {
1330
- providerFault = parseProviderFault(value.providerFault);
1331
- }
1332
- catch (error) {
1333
- throw new SessionStateError(`session response has an invalid providerFault: ${error instanceof Error ? error.message : "invalid value"}`);
1334
- }
1335
- if (lastRun?.outcome !== "failed") {
1336
- throw new SessionStateError("session response providerFault must belong to a failed lastRun");
1337
- }
1338
- }
1339
- return {
1340
- ...normalizeSessionRuntime(value, "session response"),
1341
- ...(currentRun !== undefined ? { currentRun } : {}),
1342
- ...(lastRun !== undefined ? { lastRun } : {}),
1343
- ...(providerFault !== undefined ? { providerFault } : {})
1344
- };
1345
- }
1346
- const REMOVED_SESSION_WIRE_FIELDS = ["sessionId", "runtime", "turnSeq", "cleanupStatus"];
1347
- function assertCanonicalSessionWireFields(value, context) {
1348
- const removed = REMOVED_SESSION_WIRE_FIELDS.find((field) => Object.hasOwn(value, field));
1349
- if (removed !== undefined) {
1350
- throw new SessionStateError(`${context} contains the removed ${removed} field`);
1351
- }
1352
- }
1353
- function normalizeSessionAccepted(value, context) {
1354
- if (!isRecord(value) || !isRecord(value.session)) {
1355
- throw new SessionStateError(`${context} must contain a session object`);
1356
- }
1357
- return {
1358
- ...value,
1359
- session: unwrapSession({ session: value.session })
1360
- };
1361
- }
1362
- const SESSION_RUN_PHASE_SET = new Set(SESSION_RUN_PHASES);
1363
- const SESSION_RUN_OUTCOME_SET = new Set(SESSION_TERMINAL_OUTCOMES);
1364
- function normalizeSessionMessageAccepted(value, requestedSessionId) {
1365
- if (!isRecord(value)) {
1366
- throw new SessionStateError("session message response must be an object");
1367
- }
1368
- if (Object.hasOwn(value, "turn")) {
1369
- throw new SessionStateError("session message response contains the removed turn field; use run");
1370
- }
1371
- if (!isRecord(value.session)) {
1372
- throw new SessionStateError("session message response must contain a session object");
1373
- }
1374
- const session = unwrapSession({ session: value.session });
1375
- if (session.id !== requestedSessionId) {
1376
- throw new SessionStateError("session message response session.id does not match the requested session", {
1377
- requestedSessionId,
1378
- sessionId: session.id
1379
- });
1380
- }
1381
- if (!isRecord(value.run)) {
1382
- throw new SessionStateError("session message response must contain a run object");
1383
- }
1384
- const run = normalizeSessionRun(value.run, session.id, "session message response run");
1385
- const eventCursor = value.eventCursor;
1386
- if (eventCursor !== undefined && (!Number.isSafeInteger(eventCursor) || eventCursor < 0)) {
1387
- throw new SessionStateError("session message response eventCursor must be a non-negative safe integer");
1388
- }
1389
- if (eventCursor !== undefined &&
1390
- run.eventCursor !== undefined &&
1391
- eventCursor !== run.eventCursor) {
1392
- throw new SessionStateError("session message response eventCursor does not match run.eventCursor");
1393
- }
1394
- return {
1395
- ...value,
1396
- session,
1397
- run,
1398
- ...(typeof eventCursor === "number" ? { eventCursor } : {})
1399
- };
1400
- }
1401
- function normalizeOptionalSessionRun(value, sessionId, context) {
1402
- if (value === undefined)
1403
- return undefined;
1404
- if (!isRecord(value)) {
1405
- throw new SessionStateError(`${context} must be an object`);
1406
- }
1407
- return normalizeSessionRun(value, sessionId, context);
1408
- }
1409
- function normalizeSessionRun(value, sessionId, context) {
1410
- if (Object.hasOwn(value, "executionEndedAt")) {
1411
- throw new SessionStateError(`${context} contains the removed executionEndedAt field`);
1412
- }
1413
- if (typeof value.sessionId !== "string" || value.sessionId.length === 0) {
1414
- throw new SessionStateError(`${context}.sessionId must be a non-empty string`);
1415
- }
1416
- if (value.sessionId !== sessionId) {
1417
- throw new SessionStateError(`${context}.sessionId does not match session.id`, {
1418
- sessionId,
1419
- runSessionId: value.sessionId
1420
- });
1421
- }
1422
- if (typeof value.runId !== "string" || value.runId.length === 0) {
1423
- throw new SessionStateError(`${context}.runId must be a non-empty string`);
1424
- }
1425
- if (!Number.isSafeInteger(value.turnSeq) || value.turnSeq < 1) {
1426
- throw new SessionStateError(`${context}.turnSeq must be a positive safe integer`);
1427
- }
1428
- if (typeof value.phase !== "string" || !SESSION_RUN_PHASE_SET.has(value.phase)) {
1429
- throw new SessionStateError(`${context}.phase is invalid`);
1430
- }
1431
- if (value.outcome !== undefined && (typeof value.outcome !== "string" || !SESSION_RUN_OUTCOME_SET.has(value.outcome))) {
1432
- throw new SessionStateError(`${context}.outcome is invalid`);
1433
- }
1434
- for (const field of ["startedAt", "finishedAt"]) {
1435
- if (value[field] !== undefined && typeof value[field] !== "string") {
1436
- throw new SessionStateError(`${context}.${field} must be a string`);
1437
- }
1438
- }
1439
- if (value.eventCursor !== undefined && (!Number.isSafeInteger(value.eventCursor) || value.eventCursor < 0)) {
1440
- throw new SessionStateError(`${context}.eventCursor must be a non-negative safe integer`);
1441
- }
1442
- if (value.checkpoint !== undefined && !isRecord(value.checkpoint)) {
1443
- throw new SessionStateError(`${context}.checkpoint must be an object`);
1444
- }
1445
- return {
1446
- sessionId: value.sessionId,
1447
- turnSeq: value.turnSeq,
1448
- runId: value.runId,
1449
- phase: value.phase,
1450
- ...(typeof value.outcome === "string"
1451
- ? { outcome: value.outcome }
1452
- : {}),
1453
- ...(typeof value.startedAt === "string" ? { startedAt: value.startedAt } : {}),
1454
- ...(typeof value.finishedAt === "string" ? { finishedAt: value.finishedAt } : {}),
1455
- ...(isRecord(value.checkpoint) ? { checkpoint: value.checkpoint } : {}),
1456
- ...(typeof value.eventCursor === "number" ? { eventCursor: value.eventCursor } : {})
1457
- };
1458
- }
1459
- function normalizeSessionRuntime(value, context) {
1460
- let size;
1461
- try {
1462
- size = parseRuntimeSizeForRead(value.runtimeSize);
1463
- }
1464
- catch (error) {
1465
- throw new SessionStateError(`${context} has an invalid runtimeSize: ${error instanceof Error ? error.message : String(error)}`);
1466
- }
1467
- // Fold the flat wire fields (`runtimeKind`, `runtimeSize`) into the grouped
1468
- // client shape `runtime: { kind, size }`. Tolerant on read — an unknown future
1469
- // runtime kind passes through rather than throwing (forward-compat), unlike the
1470
- // strict submit path.
1471
- const kind = typeof value.runtimeKind === "string" ? value.runtimeKind : undefined;
1472
- const runtime = kind !== undefined || size !== undefined
1473
- ? { ...(kind !== undefined ? { kind } : {}), ...(size !== undefined ? { size } : {}) }
1474
- : undefined;
1475
- const { runtimeSize: _wireSize, runtimeKind: _wireKind, ...normalized } = value;
1476
- return {
1477
- ...normalized,
1478
- ...(runtime !== undefined ? { runtime } : {})
1479
- };
1480
- }
1481
- // Session records can outlive a public runtime-size vocabulary migration. Keep
1482
- // read-side normalization compatible with records emitted by the previous
1483
- // pre-launch platform while retaining strict validation for new submissions.
1484
- const LEGACY_RUNTIME_SIZE_ALIASES = {
1485
- "shared-0.25x-1gb": "0.25cpu-1gb",
1486
- "shared-0.5x-4gb": "0.5cpu-4gb",
1487
- "shared-1x-6gb": "1cpu-6gb",
1488
- "shared-2x-8gb": "2cpu-8gb",
1489
- "shared-4x-12gb": "4cpu-12gb"
1490
- };
1491
- function parseRuntimeSizeForRead(input) {
1492
- if (typeof input === "string" && input in LEGACY_RUNTIME_SIZE_ALIASES) {
1493
- return LEGACY_RUNTIME_SIZE_ALIASES[input];
1494
- }
1495
- return parseRuntimeSize(input);
1496
- }