@the-open-engine/zeroshot 6.29.0 → 6.30.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 (347) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/cli/index.js +2 -4
  3. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
  4. package/lib/agent-cli-provider/adapters/codex.js +0 -1
  5. package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
  6. package/lib/agent-cli-provider/contract-actions.d.ts +1 -1
  7. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -1
  8. package/lib/agent-cli-provider/contract-actions.js +5 -5
  9. package/lib/agent-cli-provider/contract-actions.js.map +1 -1
  10. package/lib/agent-cli-provider/contract-invoke.d.ts +1 -1
  11. package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -1
  12. package/lib/agent-cli-provider/contract-invoke.js +2 -2
  13. package/lib/agent-cli-provider/contract-invoke.js.map +1 -1
  14. package/lib/agent-cli-provider/contract-support.d.ts +1 -1
  15. package/lib/agent-cli-provider/contract-support.d.ts.map +1 -1
  16. package/lib/agent-cli-provider/contract-support.js +2 -2
  17. package/lib/agent-cli-provider/contract-support.js.map +1 -1
  18. package/lib/agent-cli-provider/contract.d.ts +1 -0
  19. package/lib/agent-cli-provider/contract.d.ts.map +1 -1
  20. package/lib/agent-cli-provider/contract.js +1 -1
  21. package/lib/agent-cli-provider/contract.js.map +1 -1
  22. package/lib/agent-cli-provider/index.d.ts +1 -1
  23. package/lib/agent-cli-provider/index.d.ts.map +1 -1
  24. package/lib/agent-cli-provider/index.js +2 -1
  25. package/lib/agent-cli-provider/index.js.map +1 -1
  26. package/lib/agent-cli-provider/provider-registry.d.ts +1 -0
  27. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  28. package/lib/agent-cli-provider/provider-registry.js +4 -0
  29. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  30. package/lib/agent-cli-provider/single-agent-runtime.d.ts +3 -3
  31. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  32. package/lib/agent-cli-provider/single-agent-runtime.js +22 -48
  33. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  34. package/lib/cluster/client.cjs +30 -7
  35. package/lib/cluster/client.d.cts +52 -0
  36. package/lib/cluster/client.d.mts +52 -0
  37. package/lib/cluster/client.d.ts +4 -6
  38. package/lib/cluster/client.mjs +32 -9
  39. package/lib/cluster/connection-state.cjs +25 -0
  40. package/lib/cluster/connection-state.d.cts +13 -0
  41. package/lib/cluster/connection-state.d.mts +13 -0
  42. package/lib/cluster/connection-state.d.ts +13 -0
  43. package/lib/cluster/connection-state.mjs +21 -0
  44. package/lib/{target/hosted-run/contracts.js → cluster/connection-types.cjs} +0 -1
  45. package/lib/cluster/connection-types.d.cts +33 -0
  46. package/lib/cluster/connection-types.d.mts +33 -0
  47. package/lib/cluster/connection-types.d.ts +33 -0
  48. package/lib/cluster/connection-types.mjs +1 -0
  49. package/lib/cluster/connection.cjs +18 -46
  50. package/lib/cluster/connection.d.cts +26 -0
  51. package/lib/cluster/connection.d.mts +26 -0
  52. package/lib/cluster/connection.d.ts +7 -25
  53. package/lib/cluster/connection.mjs +12 -42
  54. package/lib/cluster/errors.cjs +9 -1
  55. package/lib/cluster/errors.d.cts +29 -0
  56. package/lib/cluster/errors.d.mts +29 -0
  57. package/lib/cluster/errors.d.ts +4 -0
  58. package/lib/cluster/errors.mjs +7 -0
  59. package/lib/cluster/frames.d.cts +12 -0
  60. package/lib/cluster/frames.d.mts +12 -0
  61. package/lib/cluster/generated/protocol-schema.d.cts +1 -0
  62. package/lib/cluster/generated/protocol-schema.d.mts +1 -0
  63. package/lib/cluster/generated/protocol.d.cts +781 -0
  64. package/lib/cluster/generated/protocol.d.mts +781 -0
  65. package/lib/cluster/index.cjs +2 -2
  66. package/lib/cluster/index.d.cts +13 -0
  67. package/lib/cluster/index.d.mts +13 -0
  68. package/lib/cluster/index.d.ts +3 -3
  69. package/lib/cluster/index.mjs +2 -2
  70. package/lib/cluster/json-source.cjs +1 -0
  71. package/lib/cluster/json-source.d.cts +5 -0
  72. package/lib/cluster/json-source.d.mts +5 -0
  73. package/lib/cluster/json-source.d.ts +1 -0
  74. package/lib/cluster/json-source.mjs +1 -0
  75. package/lib/cluster/payload-value.cjs +1 -0
  76. package/lib/cluster/payload-value.d.cts +8 -0
  77. package/lib/cluster/payload-value.d.mts +8 -0
  78. package/lib/cluster/payload-value.d.ts +1 -0
  79. package/lib/cluster/payload-value.mjs +1 -0
  80. package/lib/cluster/queue.d.cts +15 -0
  81. package/lib/cluster/queue.d.mts +15 -0
  82. package/lib/cluster/socket.cjs +8 -0
  83. package/lib/cluster/socket.d.cts +13 -0
  84. package/lib/cluster/socket.d.mts +13 -0
  85. package/lib/cluster/socket.d.ts +2 -0
  86. package/lib/cluster/socket.mjs +7 -0
  87. package/lib/cluster/subscriptions.d.cts +73 -0
  88. package/lib/cluster/subscriptions.d.mts +73 -0
  89. package/lib/cluster/subscriptions.d.ts +5 -1
  90. package/lib/cluster/validators.cjs +1 -0
  91. package/lib/cluster/validators.d.cts +7 -0
  92. package/lib/cluster/validators.d.mts +7 -0
  93. package/lib/cluster/validators.d.ts +1 -0
  94. package/lib/cluster/validators.mjs +1 -0
  95. package/lib/cluster-worker/engine-adapter-common.js +37 -0
  96. package/lib/cluster-worker/engine-adapter.js +24 -30
  97. package/lib/cluster-worker/engine-start.js +59 -0
  98. package/lib/cluster-worker/index.js +49 -67
  99. package/lib/cluster-worker/profiles.js +1 -42
  100. package/lib/cluster-worker/runtime-dependencies.js +49 -0
  101. package/lib/cluster-worker/runtime-support.js +2 -34
  102. package/lib/cluster-worker/terminal-normalizer.js +9 -10
  103. package/lib/hosted-session/authority.cjs +61 -0
  104. package/lib/hosted-session/authority.d.cts +3 -0
  105. package/lib/hosted-session/authority.d.mts +3 -0
  106. package/lib/hosted-session/authority.d.ts +3 -0
  107. package/lib/hosted-session/authority.mjs +57 -0
  108. package/lib/hosted-session/coordinator.cjs +78 -51
  109. package/lib/hosted-session/coordinator.d.cts +11 -0
  110. package/lib/hosted-session/coordinator.d.mts +11 -0
  111. package/lib/hosted-session/coordinator.d.ts +4 -2
  112. package/lib/hosted-session/coordinator.mjs +75 -51
  113. package/lib/hosted-session/errors.cjs +25 -0
  114. package/lib/hosted-session/errors.d.cts +10 -0
  115. package/lib/hosted-session/errors.d.mts +10 -0
  116. package/lib/hosted-session/errors.d.ts +10 -0
  117. package/lib/hosted-session/errors.mjs +19 -0
  118. package/lib/hosted-session/index.cjs +4 -1
  119. package/lib/hosted-session/index.d.cts +2 -0
  120. package/lib/hosted-session/index.d.mts +2 -0
  121. package/lib/hosted-session/index.d.ts +2 -2
  122. package/lib/hosted-session/index.mjs +1 -1
  123. package/lib/hosted-session/reconnecting-watch.cjs +131 -0
  124. package/lib/hosted-session/reconnecting-watch.d.cts +13 -0
  125. package/lib/hosted-session/reconnecting-watch.d.mts +13 -0
  126. package/lib/hosted-session/reconnecting-watch.d.ts +13 -0
  127. package/lib/hosted-session/reconnecting-watch.mjs +127 -0
  128. package/lib/hosted-session/types.d.cts +32 -0
  129. package/lib/hosted-session/types.d.mts +32 -0
  130. package/lib/hosted-session/types.d.ts +19 -7
  131. package/lib/hosted-target/access-url.cjs +44 -0
  132. package/lib/hosted-target/access-url.d.cts +2 -0
  133. package/lib/hosted-target/access-url.d.mts +2 -0
  134. package/lib/hosted-target/access-url.d.ts +2 -0
  135. package/lib/hosted-target/access-url.mjs +41 -0
  136. package/lib/hosted-target/adapter-request.cjs +12 -0
  137. package/lib/hosted-target/adapter-request.d.cts +17 -0
  138. package/lib/hosted-target/adapter-request.d.mts +17 -0
  139. package/lib/hosted-target/adapter-request.d.ts +17 -0
  140. package/lib/hosted-target/adapter-request.mjs +9 -0
  141. package/lib/hosted-target/adapter-types.cjs +2 -0
  142. package/lib/hosted-target/adapter-types.d.cts +27 -0
  143. package/lib/hosted-target/adapter-types.d.mts +27 -0
  144. package/lib/hosted-target/adapter-types.d.ts +27 -0
  145. package/lib/hosted-target/adapter-types.mjs +1 -0
  146. package/lib/hosted-target/bounds.cjs +10 -0
  147. package/lib/hosted-target/bounds.d.cts +7 -0
  148. package/lib/hosted-target/bounds.d.mts +7 -0
  149. package/lib/hosted-target/bounds.d.ts +7 -0
  150. package/lib/hosted-target/bounds.mjs +7 -0
  151. package/lib/hosted-target/capsule-error-response.cjs +76 -0
  152. package/lib/hosted-target/capsule-error-response.d.cts +2 -0
  153. package/lib/hosted-target/capsule-error-response.d.mts +2 -0
  154. package/lib/hosted-target/capsule-error-response.d.ts +2 -0
  155. package/lib/hosted-target/capsule-error-response.mjs +73 -0
  156. package/lib/hosted-target/errors.cjs +88 -0
  157. package/lib/hosted-target/errors.d.cts +36 -0
  158. package/lib/hosted-target/errors.d.mts +36 -0
  159. package/lib/hosted-target/errors.d.ts +36 -0
  160. package/lib/hosted-target/errors.mjs +75 -0
  161. package/lib/hosted-target/index.cjs +16 -0
  162. package/lib/hosted-target/index.d.cts +3 -0
  163. package/lib/hosted-target/index.d.mts +3 -0
  164. package/lib/hosted-target/index.d.ts +3 -0
  165. package/lib/hosted-target/index.mjs +2 -0
  166. package/lib/hosted-target/response-validation.cjs +169 -0
  167. package/lib/hosted-target/response-validation.d.cts +5 -0
  168. package/lib/hosted-target/response-validation.d.mts +5 -0
  169. package/lib/hosted-target/response-validation.d.ts +5 -0
  170. package/lib/hosted-target/response-validation.mjs +163 -0
  171. package/lib/hosted-target/retry-executor.cjs +51 -0
  172. package/lib/hosted-target/retry-executor.d.cts +8 -0
  173. package/lib/hosted-target/retry-executor.d.mts +8 -0
  174. package/lib/hosted-target/retry-executor.d.ts +8 -0
  175. package/lib/hosted-target/retry-executor.mjs +48 -0
  176. package/lib/hosted-target/retry.cjs +78 -0
  177. package/lib/hosted-target/retry.d.cts +9 -0
  178. package/lib/hosted-target/retry.d.mts +9 -0
  179. package/lib/hosted-target/retry.d.ts +9 -0
  180. package/lib/hosted-target/retry.mjs +73 -0
  181. package/lib/hosted-target/target-adapter.cjs +10 -0
  182. package/lib/hosted-target/target-adapter.d.cts +3 -0
  183. package/lib/hosted-target/target-adapter.d.mts +3 -0
  184. package/lib/hosted-target/target-adapter.d.ts +3 -0
  185. package/lib/hosted-target/target-adapter.mjs +7 -0
  186. package/lib/hosted-target/types.cjs +4 -0
  187. package/lib/hosted-target/types.d.cts +50 -0
  188. package/lib/hosted-target/types.d.mts +50 -0
  189. package/lib/hosted-target/types.d.ts +50 -0
  190. package/lib/hosted-target/types.mjs +1 -0
  191. package/lib/hosted-target/zero-cloud-v1-adapter.cjs +185 -0
  192. package/lib/hosted-target/zero-cloud-v1-adapter.d.cts +13 -0
  193. package/lib/hosted-target/zero-cloud-v1-adapter.d.mts +13 -0
  194. package/lib/hosted-target/zero-cloud-v1-adapter.d.ts +13 -0
  195. package/lib/hosted-target/zero-cloud-v1-adapter.mjs +181 -0
  196. package/lib/start-cluster.js +3 -3
  197. package/lib/target/bounded-response.cjs +51 -0
  198. package/lib/target/bounded-response.d.cts +1 -0
  199. package/lib/target/bounded-response.d.mts +1 -0
  200. package/lib/target/bounded-response.d.ts +1 -0
  201. package/lib/target/bounded-response.js +51 -0
  202. package/lib/target/bounded-response.mjs +48 -0
  203. package/lib/target/credential-lock.js +1 -3
  204. package/lib/target/credential-store.js +9 -4
  205. package/lib/target/device-flow.cjs +213 -0
  206. package/lib/target/device-flow.d.cts +54 -0
  207. package/lib/target/device-flow.d.mts +54 -0
  208. package/lib/target/device-flow.d.ts +26 -14
  209. package/lib/target/device-flow.js +162 -58
  210. package/lib/target/device-flow.mjs +203 -0
  211. package/lib/target/discovery-errors.cjs +10 -0
  212. package/lib/target/discovery-errors.d.cts +3 -0
  213. package/lib/target/discovery-errors.d.mts +3 -0
  214. package/lib/target/discovery-errors.d.ts +3 -0
  215. package/lib/target/discovery-errors.js +10 -0
  216. package/lib/target/discovery-errors.mjs +6 -0
  217. package/lib/target/discovery-sections.cjs +191 -0
  218. package/lib/target/discovery-sections.d.cts +52 -0
  219. package/lib/target/discovery-sections.d.mts +52 -0
  220. package/lib/target/discovery-sections.d.ts +52 -0
  221. package/lib/target/discovery-sections.js +191 -0
  222. package/lib/target/discovery-sections.mjs +179 -0
  223. package/lib/target/discovery-validation.cjs +109 -0
  224. package/lib/target/discovery-validation.d.cts +28 -0
  225. package/lib/target/discovery-validation.d.mts +28 -0
  226. package/lib/target/discovery-validation.d.ts +28 -0
  227. package/lib/target/discovery-validation.js +109 -0
  228. package/lib/target/discovery-validation.mjs +99 -0
  229. package/lib/target/discovery.cjs +112 -0
  230. package/lib/target/discovery.d.cts +74 -0
  231. package/lib/target/discovery.d.mts +74 -0
  232. package/lib/target/discovery.d.ts +67 -5
  233. package/lib/target/discovery.js +94 -79
  234. package/lib/target/discovery.mjs +105 -0
  235. package/lib/target/index.d.ts +6 -6
  236. package/lib/target/index.js +36 -36
  237. package/lib/target/oauth-http.d.ts +2 -0
  238. package/lib/target/oauth-http.js +25 -0
  239. package/lib/target/refresh-exchange.d.ts +11 -0
  240. package/lib/target/refresh-exchange.js +24 -0
  241. package/lib/target/refresh-revocation.d.ts +8 -0
  242. package/lib/target/refresh-revocation.js +19 -0
  243. package/lib/target/route-template.cjs +85 -0
  244. package/lib/target/route-template.d.cts +7 -0
  245. package/lib/target/route-template.d.mts +7 -0
  246. package/lib/target/route-template.d.ts +7 -0
  247. package/lib/target/route-template.js +85 -0
  248. package/lib/target/route-template.mjs +81 -0
  249. package/lib/target/session-errors.d.ts +4 -0
  250. package/lib/target/session-errors.js +12 -0
  251. package/lib/target/session-verification.d.ts +10 -0
  252. package/lib/target/session-verification.js +42 -0
  253. package/lib/target/target-registry.d.ts +7 -3
  254. package/lib/target/target-registry.js +41 -10
  255. package/lib/target/target-session-manager.d.ts +48 -0
  256. package/lib/target/target-session-manager.js +226 -0
  257. package/lib/target/target-session.d.ts +39 -32
  258. package/lib/target/target-session.js +63 -151
  259. package/lib/target/token-response.cjs +51 -0
  260. package/lib/target/token-response.d.cts +7 -0
  261. package/lib/target/token-response.d.mts +7 -0
  262. package/lib/target/token-response.d.ts +7 -0
  263. package/lib/target/token-response.js +51 -0
  264. package/lib/target/token-response.mjs +48 -0
  265. package/package.json +14 -7
  266. package/protocol/openengine-cluster/v1/worker.schema.json +171 -9
  267. package/scripts/build-cluster.js +23 -3
  268. package/scripts/opcore-introduced-check.js +0 -2
  269. package/src/agent-cli-provider/adapters/codex.ts +0 -1
  270. package/src/agent-cli-provider/contract-actions.ts +9 -5
  271. package/src/agent-cli-provider/contract-invoke.ts +3 -2
  272. package/src/agent-cli-provider/contract-support.ts +12 -6
  273. package/src/agent-cli-provider/contract.ts +6 -1
  274. package/src/agent-cli-provider/index.ts +1 -0
  275. package/src/agent-cli-provider/provider-registry.ts +4 -0
  276. package/src/agent-cli-provider/single-agent-runtime.ts +32 -45
  277. package/src/cluster/client.ts +42 -15
  278. package/src/cluster/connection-state.ts +33 -0
  279. package/src/cluster/connection-types.ts +28 -0
  280. package/src/cluster/connection.ts +37 -68
  281. package/src/cluster/errors.ts +5 -0
  282. package/src/cluster/index.ts +3 -1
  283. package/src/cluster/json-source.ts +1 -0
  284. package/src/cluster/payload-value.ts +1 -0
  285. package/src/cluster/socket.ts +13 -0
  286. package/src/cluster/subscriptions.ts +7 -2
  287. package/src/cluster/validators.ts +1 -0
  288. package/src/cluster/ws.d.ts +1 -1
  289. package/src/hosted-session/authority.ts +77 -0
  290. package/src/hosted-session/coordinator.ts +113 -56
  291. package/src/hosted-session/errors.ts +21 -0
  292. package/src/hosted-session/index.ts +14 -2
  293. package/src/hosted-session/reconnecting-watch.ts +148 -0
  294. package/src/hosted-session/types.ts +22 -8
  295. package/src/hosted-target/access-url.ts +46 -0
  296. package/src/hosted-target/adapter-request.ts +28 -0
  297. package/src/hosted-target/adapter-types.ts +39 -0
  298. package/src/hosted-target/bounds.ts +1 -0
  299. package/src/hosted-target/capsule-error-response.ts +107 -0
  300. package/src/hosted-target/errors.ts +36 -35
  301. package/src/hosted-target/index.ts +10 -23
  302. package/src/hosted-target/response-validation.ts +170 -62
  303. package/src/hosted-target/retry-executor.ts +62 -0
  304. package/src/hosted-target/retry.ts +47 -11
  305. package/src/hosted-target/target-adapter.ts +12 -8
  306. package/src/hosted-target/types.ts +19 -18
  307. package/src/hosted-target/zero-cloud-v1-adapter.ts +229 -316
  308. package/src/isolation-manager.js +1 -16
  309. package/src/target/bounded-response.ts +68 -0
  310. package/src/target/credential-lock.ts +1 -3
  311. package/src/target/credential-store.ts +14 -10
  312. package/src/target/device-flow.ts +209 -85
  313. package/src/target/discovery-errors.ts +6 -0
  314. package/src/target/discovery-sections.ts +251 -0
  315. package/src/target/discovery-validation.ts +144 -0
  316. package/src/target/discovery.ts +163 -97
  317. package/src/target/index.ts +15 -11
  318. package/src/target/oauth-http.ts +27 -0
  319. package/src/target/refresh-exchange.ts +36 -0
  320. package/src/target/refresh-revocation.ts +29 -0
  321. package/src/target/route-template.ts +105 -0
  322. package/src/target/session-errors.ts +9 -0
  323. package/src/target/session-verification.ts +51 -0
  324. package/src/target/target-registry.ts +60 -27
  325. package/src/target/target-session-manager.ts +285 -0
  326. package/src/target/target-session.ts +105 -226
  327. package/src/target/token-response.ts +62 -0
  328. package/docker/zeroshot-oecp/Cargo.toml +0 -12
  329. package/docker/zeroshot-oecp/Dockerfile +0 -65
  330. package/docker/zeroshot-oecp/src/main.rs +0 -32
  331. package/lib/target/bounded-json.d.ts +0 -6
  332. package/lib/target/bounded-json.js +0 -43
  333. package/lib/target/hosted-run/client.d.ts +0 -26
  334. package/lib/target/hosted-run/client.js +0 -158
  335. package/lib/target/hosted-run/commands.d.ts +0 -4
  336. package/lib/target/hosted-run/commands.js +0 -113
  337. package/lib/target/hosted-run/contracts.d.ts +0 -49
  338. package/lib/target/hosted-run/input.d.ts +0 -5
  339. package/lib/target/hosted-run/input.js +0 -200
  340. package/lib/target/hosted-run.d.ts +0 -4
  341. package/lib/target/hosted-run.js +0 -12
  342. package/src/target/bounded-json.ts +0 -48
  343. package/src/target/hosted-run/client.ts +0 -198
  344. package/src/target/hosted-run/commands.ts +0 -140
  345. package/src/target/hosted-run/contracts.ts +0 -53
  346. package/src/target/hosted-run/input.ts +0 -199
  347. package/src/target/hosted-run.ts +0 -8
@@ -0,0 +1,68 @@
1
+ async function declaredResponseBytes(
2
+ response: Response,
3
+ maxBytes: number,
4
+ error: (kind: 'size' | 'json') => Error,
5
+ ): Promise<number> {
6
+ const declared = response.headers.get('content-length');
7
+ if (declared === null) return 0;
8
+ if (!/^\d+$/.test(declared) || Number(declared) > maxBytes) {
9
+ await response.body?.cancel().catch(() => undefined);
10
+ throw error('size');
11
+ }
12
+ return Number(declared);
13
+ }
14
+
15
+ function growBuffer(
16
+ bytes: Uint8Array,
17
+ required: number,
18
+ used: number,
19
+ maxBytes: number,
20
+ ): Uint8Array {
21
+ const capacity = Math.min(
22
+ maxBytes,
23
+ Math.max(required, Math.max(bytes.byteLength * 2, 8 * 1024)),
24
+ );
25
+ const grown = new Uint8Array(capacity);
26
+ grown.set(bytes.subarray(0, used));
27
+ return grown;
28
+ }
29
+
30
+ function decodeJson(
31
+ bytes: Uint8Array,
32
+ error: (kind: 'size' | 'json') => Error,
33
+ ): unknown {
34
+ try {
35
+ return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
36
+ } catch {
37
+ throw error('json');
38
+ }
39
+ }
40
+
41
+ export async function readBoundedResponseJson(
42
+ response: Response,
43
+ maxBytes: number,
44
+ error: (kind: 'size' | 'json') => Error,
45
+ ): Promise<unknown> {
46
+ const declaredBytes = await declaredResponseBytes(response, maxBytes, error);
47
+ const reader = response.body?.getReader();
48
+ if (reader === undefined) return decodeJson(new Uint8Array(), error);
49
+ let bytes: Uint8Array = new Uint8Array(
50
+ Math.min(maxBytes, Math.max(declaredBytes, 8 * 1024)),
51
+ );
52
+ let total = 0;
53
+ while (true) {
54
+ const { done, value } = await reader.read();
55
+ if (done) break;
56
+ const nextTotal = total + value.byteLength;
57
+ if (nextTotal > maxBytes) {
58
+ await reader.cancel().catch(() => undefined);
59
+ throw error('size');
60
+ }
61
+ if (nextTotal > bytes.byteLength) {
62
+ bytes = growBuffer(bytes, nextTotal, total, maxBytes);
63
+ }
64
+ bytes.set(value, total);
65
+ total = nextTotal;
66
+ }
67
+ return decodeJson(bytes.subarray(0, total), error);
68
+ }
@@ -29,7 +29,5 @@ export async function acquireTargetLock(targetId: string): Promise<() => Promise
29
29
  },
30
30
  });
31
31
 
32
- return async () => {
33
- await release();
34
- };
32
+ return release;
35
33
  }
@@ -22,16 +22,16 @@ export const TARGET_ACCOUNT = 'refresh-token';
22
22
 
23
23
  export class KeyringCredentialStore implements TargetCredentialStore {
24
24
  private readonly Entry: new (service: string, account: string) => {
25
- getPassword(): string;
25
+ getPassword(): string | null;
26
26
  setPassword(password: string): void;
27
- deletePassword(): void;
27
+ deletePassword(): boolean;
28
28
  };
29
29
 
30
30
  private constructor(
31
31
  Entry: new (service: string, account: string) => {
32
- getPassword(): string;
32
+ getPassword(): string | null;
33
33
  setPassword(password: string): void;
34
- deletePassword(): void;
34
+ deletePassword(): boolean;
35
35
  },
36
36
  ) {
37
37
  this.Entry = Entry;
@@ -39,9 +39,9 @@ export class KeyringCredentialStore implements TargetCredentialStore {
39
39
 
40
40
  static async create(): Promise<KeyringCredentialStore> {
41
41
  let keyringModule: { Entry: new (service: string, account: string) => {
42
- getPassword(): string;
42
+ getPassword(): string | null;
43
43
  setPassword(password: string): void;
44
- deletePassword(): void;
44
+ deletePassword(): boolean;
45
45
  } };
46
46
  try {
47
47
  keyringModule = await import('@napi-rs/keyring') as typeof keyringModule;
@@ -59,13 +59,17 @@ export class KeyringCredentialStore implements TargetCredentialStore {
59
59
  const entry = new this.Entry(service, account);
60
60
  return entry.getPassword();
61
61
  } catch {
62
- return null;
62
+ throw new CredentialStoreUnavailableError('The secure credential store could not be read.');
63
63
  }
64
64
  }
65
65
 
66
66
  async set(service: string, account: string, token: string): Promise<void> {
67
- const entry = new this.Entry(service, account);
68
- entry.setPassword(token);
67
+ try {
68
+ const entry = new this.Entry(service, account);
69
+ entry.setPassword(token);
70
+ } catch {
71
+ throw new CredentialStoreUnavailableError('The secure credential store could not be updated.');
72
+ }
69
73
  }
70
74
 
71
75
  async delete(service: string, account: string): Promise<void> {
@@ -73,7 +77,7 @@ export class KeyringCredentialStore implements TargetCredentialStore {
73
77
  const entry = new this.Entry(service, account);
74
78
  entry.deletePassword();
75
79
  } catch {
76
- // Already deleted or not present
80
+ throw new CredentialStoreUnavailableError('The secure credential store could not be cleared.');
77
81
  }
78
82
  }
79
83
  }
@@ -1,3 +1,6 @@
1
+ import { readBoundedResponseJson } from './bounded-response.js';
2
+ import { parseTokenResponse, type TokenResponse } from './token-response.js';
3
+ export { parseTokenResponse, type TokenResponse } from './token-response.js';
1
4
  export interface DeviceCodeResponse {
2
5
  readonly device_code: string;
3
6
  readonly user_code: string;
@@ -7,12 +10,36 @@ export interface DeviceCodeResponse {
7
10
  readonly interval: number;
8
11
  }
9
12
 
10
- export interface TokenResponse {
11
- readonly access_token: string;
12
- readonly refresh_token: string;
13
- readonly token_type: string;
14
- readonly expires_in: number;
15
- readonly organization?: { readonly id: string; readonly name: string };
13
+
14
+ export interface DeviceExchangeContext {
15
+ readonly grantType: string;
16
+ readonly deviceToken: string;
17
+ readonly deviceLabel: string;
18
+ readonly audience: string;
19
+ }
20
+
21
+ export interface DeviceIdentity {
22
+ readonly token: string;
23
+ readonly label: string;
24
+ }
25
+
26
+ function exchangeFields(
27
+ exchange: DeviceExchangeContext | DeviceIdentity | undefined,
28
+ ): Readonly<Record<string, string>> {
29
+ if (exchange === undefined) return {};
30
+ if ('grantType' in exchange) {
31
+ return {
32
+ grant_type: exchange.grantType,
33
+ device_token: exchange.deviceToken,
34
+ device_label: exchange.deviceLabel,
35
+ audience: exchange.audience,
36
+ };
37
+ }
38
+ return {
39
+ device_token: exchange.token,
40
+ device_label: exchange.label,
41
+ audience: 'admin',
42
+ };
16
43
  }
17
44
 
18
45
  export interface HttpTransport {
@@ -23,11 +50,6 @@ export interface Clock {
23
50
  now(): number;
24
51
  }
25
52
 
26
- export interface DeviceIdentity {
27
- readonly token: string;
28
- readonly label: string;
29
- }
30
-
31
53
  export class DeviceFlowDeniedError extends Error {
32
54
  constructor() {
33
55
  super('Device authorization denied by user');
@@ -46,7 +68,7 @@ export class UnboundSessionError extends Error {
46
68
  readonly verificationUri: string;
47
69
  constructor(verificationUri: string) {
48
70
  super(
49
- `Session not bound to an organization. Re-approve at ${verificationUri} and select an organization.`
71
+ `Session not bound to an organization. Re-approve at ${verificationUri} and select an organization.`,
50
72
  );
51
73
  this.name = 'UnboundSessionError';
52
74
  this.verificationUri = verificationUri;
@@ -54,6 +76,124 @@ export class UnboundSessionError extends Error {
54
76
  }
55
77
 
56
78
  const DEFAULT_CLOCK: Clock = { now: () => Date.now() };
79
+ const MAX_OAUTH_RESPONSE_BYTES = 64 * 1024;
80
+ const MAX_DEVICE_CODE_BYTES = 16 * 1024;
81
+ const LOOPBACK_HOSTS: Readonly<Record<string, true>> = Object.freeze({
82
+ '127.0.0.1': true,
83
+ '::1': true,
84
+ '[::1]': true,
85
+ });
86
+ const MAX_URI_BYTES = 4 * 1024;
87
+ export function oauthFormRequest(
88
+ body: URLSearchParams,
89
+ signal?: AbortSignal,
90
+ ): RequestInit & { redirect: 'error' } {
91
+ const init: RequestInit & { redirect: 'error' } = {
92
+ method: 'POST',
93
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
94
+ body: body.toString(),
95
+ redirect: 'error',
96
+ };
97
+ if (signal !== undefined) init.signal = signal;
98
+ return init;
99
+ }
100
+
101
+ async function requireResponseRoute(response: Response, expectedUrl: string): Promise<void> {
102
+ if (!response.url || new URL(response.url).href === expectedUrl) return;
103
+ await response.body?.cancel().catch(() => undefined);
104
+ throw new Error('OAuth response changed target route or authority');
105
+ }
106
+
107
+
108
+ async function readBoundedJson(response: Response): Promise<unknown> {
109
+ return readBoundedResponseJson(response, MAX_OAUTH_RESPONSE_BYTES, (kind) =>
110
+ new Error(kind === 'size' ? 'OAuth response exceeds the size limit' : 'OAuth response is malformed'),
111
+ );
112
+ }
113
+
114
+ function boundedString(value: unknown, maxBytes: number): value is string {
115
+ return (
116
+ typeof value === 'string' &&
117
+ value.length > 0 &&
118
+ new TextEncoder().encode(value).byteLength <= maxBytes
119
+ );
120
+ }
121
+
122
+ function safeVerificationAuthority(url: URL, allowQuery: boolean): boolean {
123
+ const literalLoopback = LOOPBACK_HOSTS[url.hostname] === true;
124
+ return !url.username && !url.password && !url.hash &&
125
+ (allowQuery || !url.search) &&
126
+ (url.protocol === 'https:' || (url.protocol === 'http:' && literalLoopback));
127
+ }
128
+
129
+ function verificationUrl(value: unknown, allowQuery: boolean): value is string {
130
+ if (
131
+ !boundedString(value, MAX_URI_BYTES) ||
132
+ /[\u0000-\u0020\u007f]|\s/u.test(value)
133
+ ) {
134
+ return false;
135
+ }
136
+ try {
137
+ const url = new URL(value);
138
+ return url.href === value && safeVerificationAuthority(url, allowQuery);
139
+ } catch {
140
+ return false;
141
+ }
142
+ }
143
+ function deviceFieldsValid(device: Record<string, unknown>): boolean {
144
+ const fields = Object.keys(device);
145
+ const required = ['device_code', 'user_code', 'verification_uri', 'expires_in', 'interval'];
146
+ return fields.every((field) =>
147
+ required.includes(field) || field === 'verification_uri_complete',
148
+ ) && required.every((field) => field in device);
149
+ }
150
+
151
+ function deviceStringsValid(device: Record<string, unknown>): boolean {
152
+ return boundedString(device.device_code, MAX_DEVICE_CODE_BYTES) &&
153
+ boundedString(device.user_code, 256) &&
154
+ verificationUrl(device.verification_uri, false) &&
155
+ (device.verification_uri_complete === undefined ||
156
+ verificationUrl(device.verification_uri_complete, true));
157
+ }
158
+
159
+ function deviceTimingValid(device: Record<string, unknown>): boolean {
160
+ return Number.isSafeInteger(device.expires_in) &&
161
+ (device.expires_in as number) >= 1 &&
162
+ (device.expires_in as number) <= 86_400 &&
163
+ Number.isSafeInteger(device.interval) &&
164
+ (device.interval as number) >= 0 &&
165
+ (device.interval as number) <= 300;
166
+ }
167
+
168
+ function browserAuthorityMatches(device: Record<string, unknown>): boolean {
169
+ return device.verification_uri_complete === undefined ||
170
+ new URL(device.verification_uri_complete as string).origin ===
171
+ new URL(device.verification_uri as string).origin;
172
+ }
173
+
174
+
175
+ function parseDeviceCodeResponse(value: unknown): DeviceCodeResponse {
176
+ const device = object(value, 'Device code');
177
+ if (!deviceFieldsValid(device) || !deviceStringsValid(device) ||
178
+ !deviceTimingValid(device) || !browserAuthorityMatches(device)) {
179
+ throw new Error('Device code response is malformed');
180
+ }
181
+ return Object.freeze(device) as unknown as DeviceCodeResponse;
182
+ }
183
+
184
+ function parseOAuthError(value: unknown): string {
185
+ const error = object(value, 'OAuth error');
186
+ if (Object.keys(error).length !== 1 || typeof error.error !== 'string') {
187
+ throw new Error('OAuth error response is malformed');
188
+ }
189
+ return error.error;
190
+ }
191
+ function object(value: unknown, field: string): Record<string, unknown> {
192
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
193
+ throw new Error(`${field} response is malformed`);
194
+ }
195
+ return value as Record<string, unknown>;
196
+ }
57
197
 
58
198
  function sleep(ms: number, signal?: AbortSignal): Promise<void> {
59
199
  return new Promise((resolve, reject) => {
@@ -68,7 +208,7 @@ function sleep(ms: number, signal?: AbortSignal): Promise<void> {
68
208
  clearTimeout(timer);
69
209
  reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));
70
210
  },
71
- { once: true }
211
+ { once: true },
72
212
  );
73
213
  });
74
214
  }
@@ -77,92 +217,76 @@ export async function requestDeviceCode(
77
217
  deviceAuthorizationEndpoint: string,
78
218
  clientId: string,
79
219
  http: HttpTransport,
80
- signal?: AbortSignal
220
+ signal?: AbortSignal,
81
221
  ): Promise<DeviceCodeResponse> {
82
- const body = new URLSearchParams({
83
- client_id: clientId,
84
- scope: 'openid',
85
- });
86
-
87
- const init: RequestInit & { redirect: 'error' } = {
88
- method: 'POST',
89
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
90
- body: body.toString(),
91
- redirect: 'error',
92
- };
93
- if (signal) init.signal = signal;
222
+ const body = new URLSearchParams({ client_id: clientId });
94
223
 
95
- const response = await http.fetch(deviceAuthorizationEndpoint, init);
224
+ const response = await http.fetch(
225
+ deviceAuthorizationEndpoint,
226
+ oauthFormRequest(body, signal),
227
+ );
228
+ await requireResponseRoute(response, deviceAuthorizationEndpoint);
96
229
 
97
230
  if (!response.ok) {
98
- const text = await response.text();
99
- throw new Error(`Device code request failed (${response.status}): ${text}`);
231
+ parseOAuthError(await readBoundedJson(response));
232
+ throw new Error(`Device code request failed (${response.status})`);
100
233
  }
101
234
 
102
- return (await response.json()) as DeviceCodeResponse;
235
+ return parseDeviceCodeResponse(await readBoundedJson(response));
103
236
  }
104
237
 
105
- export async function pollForToken(
106
- tokenEndpoint: string,
107
- clientId: string,
108
- deviceCode: string,
109
- interval: number,
110
- expiresIn: number,
111
- http: HttpTransport,
112
- clock: Clock = DEFAULT_CLOCK,
113
- signal?: AbortSignal,
114
- deviceIdentity?: DeviceIdentity
115
- ): Promise<TokenResponse> {
116
- const deadline = clock.now() + expiresIn * 1000;
117
- let currentInterval = interval;
118
-
119
- while (clock.now() < deadline) {
120
- if (signal?.aborted) {
121
- throw signal.reason ?? new DOMException('Aborted', 'AbortError');
122
- }
238
+ export interface PollForTokenRequest {
239
+ readonly tokenEndpoint: string;
240
+ readonly clientId: string;
241
+ readonly deviceCode: string;
242
+ readonly interval: number;
243
+ readonly expiresIn: number;
244
+ readonly http: HttpTransport;
245
+ readonly clock?: Clock;
246
+ readonly signal?: AbortSignal;
247
+ readonly exchange?: DeviceExchangeContext | DeviceIdentity;
248
+ }
249
+ type PollResult = TokenResponse | 'authorization_pending' | 'slow_down';
123
250
 
124
- await sleep(currentInterval * 1000, signal);
125
-
126
- const body = new URLSearchParams({
127
- grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
128
- device_code: deviceCode,
129
- client_id: clientId,
130
- });
131
- if (deviceIdentity) {
132
- body.set('audience', 'admin');
133
- body.set('device_token', deviceIdentity.token);
134
- body.set('device_label', deviceIdentity.label);
135
- }
251
+ async function parsePollResponse(response: Response): Promise<PollResult> {
252
+ if (response.ok) return parseTokenResponse(await readBoundedJson(response));
253
+ const error = parseOAuthError(await readBoundedJson(response));
254
+ if (error === 'authorization_pending' || error === 'slow_down') return error;
255
+ if (error === 'access_denied') throw new DeviceFlowDeniedError();
256
+ if (error === 'expired_token') throw new DeviceFlowExpiredError();
257
+ throw new Error('Token endpoint returned an unsupported OAuth error');
258
+ }
136
259
 
137
- const init: RequestInit & { redirect: 'error' } = {
138
- method: 'POST',
139
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
140
- body: body.toString(),
141
- redirect: 'error',
142
- };
143
- if (signal) init.signal = signal;
260
+ function pollBody(request: PollForTokenRequest): URLSearchParams {
261
+ return new URLSearchParams({
262
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
263
+ device_code: request.deviceCode,
264
+ client_id: request.clientId,
265
+ ...exchangeFields(request.exchange),
266
+ });
267
+ }
144
268
 
145
- const response = await http.fetch(tokenEndpoint, init);
269
+ export async function pollForToken(request: PollForTokenRequest): Promise<TokenResponse> {
270
+ const clock = request.clock ?? DEFAULT_CLOCK;
271
+ const deadline = clock.now() + request.expiresIn * 1000;
272
+ let currentInterval = request.interval;
146
273
 
147
- if (response.ok) {
148
- return (await response.json()) as TokenResponse;
274
+ while (clock.now() < deadline) {
275
+ if (request.signal?.aborted) {
276
+ throw request.signal.reason ?? new DOMException('Aborted', 'AbortError');
149
277
  }
150
-
151
- const errorBody = (await response.json()) as { error: string };
152
- switch (errorBody.error) {
153
- case 'authorization_pending':
154
- continue;
155
- case 'slow_down':
156
- currentInterval += 5;
157
- continue;
158
- case 'access_denied':
159
- throw new DeviceFlowDeniedError();
160
- case 'expired_token':
161
- throw new DeviceFlowExpiredError();
162
- default:
163
- throw new Error(`Token endpoint error: ${errorBody.error}`);
278
+ await sleep(currentInterval * 1000, request.signal);
279
+ const response = await request.http.fetch(
280
+ request.tokenEndpoint,
281
+ oauthFormRequest(pollBody(request), request.signal),
282
+ );
283
+ const result = await parsePollResponse(response);
284
+ if (result === 'authorization_pending') continue;
285
+ if (result === 'slow_down') {
286
+ currentInterval += 5;
287
+ continue;
164
288
  }
289
+ return result;
165
290
  }
166
-
167
291
  throw new DeviceFlowExpiredError();
168
292
  }
@@ -0,0 +1,6 @@
1
+ export class TargetDiscoveryError extends Error {
2
+ constructor(message: string) {
3
+ super(message);
4
+ this.name = 'TargetDiscoveryError';
5
+ }
6
+ }