@bitkyc08/opencodex 2.52.0-preview.20260912 → 2.53.0-preview.20260913

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 (236) hide show
  1. package/gui/dist/assets/index-BBOZWGB6.css +1 -0
  2. package/gui/dist/assets/index-D7ynYo2K.js +128 -0
  3. package/gui/dist/index.html +2 -2
  4. package/native/remote-workspace-helper/Cargo.lock +130 -0
  5. package/native/remote-workspace-helper/Cargo.toml +24 -0
  6. package/native/remote-workspace-helper/src/main.rs +49 -0
  7. package/native/remote-workspace-helper/src/protocol.rs +246 -0
  8. package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
  9. package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
  10. package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
  11. package/package.json +6 -1
  12. package/src/adapters/anthropic-image-normalize.ts +30 -2
  13. package/src/adapters/anthropic.ts +1 -1
  14. package/src/adapters/base.ts +8 -2
  15. package/src/adapters/cursor/cursor-errors.ts +12 -0
  16. package/src/adapters/cursor/thread-continuity.ts +93 -0
  17. package/src/adapters/cursor.ts +104 -73
  18. package/src/adapters/devin/cloud-direct/chat.ts +312 -23
  19. package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
  20. package/src/adapters/devin/live-models.ts +70 -3
  21. package/src/adapters/devin.ts +281 -21
  22. package/src/adapters/google-wire-compiler.ts +14 -6
  23. package/src/adapters/google.ts +22 -8
  24. package/src/adapters/kiro/adapter.ts +316 -0
  25. package/src/adapters/kiro/conversation.ts +136 -0
  26. package/src/adapters/kiro/payload.ts +432 -0
  27. package/src/adapters/kiro/reasoning.ts +56 -0
  28. package/src/adapters/kiro/stream.ts +1153 -0
  29. package/src/adapters/kiro/usage.ts +223 -0
  30. package/src/adapters/kiro/wire.ts +76 -0
  31. package/src/adapters/kiro.ts +8 -2319
  32. package/src/adapters/mimo-free.ts +1 -1
  33. package/src/adapters/openai-chat-images.ts +101 -0
  34. package/src/adapters/openai-chat.ts +201 -181
  35. package/src/adapters/openai-responses.ts +92 -224
  36. package/src/adapters/registry.ts +0 -7
  37. package/src/adapters/run-turn-queue.ts +13 -6
  38. package/src/bridge.ts +14 -15
  39. package/src/chat/inbound.ts +29 -4
  40. package/src/chat/outbound.ts +145 -107
  41. package/src/claude/desktop-profile.ts +4 -6
  42. package/src/cli/account-api.ts +14 -0
  43. package/src/cli/account-extended.ts +1 -1
  44. package/src/cli/account-history.ts +60 -0
  45. package/src/cli/account-main.ts +80 -0
  46. package/src/cli/account.ts +11 -3
  47. package/src/cli/capabilities.ts +113 -0
  48. package/src/cli/catalog.ts +109 -0
  49. package/src/cli/dispatch.ts +9 -0
  50. package/src/cli/help.ts +2 -0
  51. package/src/cli/index.ts +2 -2
  52. package/src/cli/observe.ts +28 -1
  53. package/src/cli/opencode.ts +42 -8
  54. package/src/cli/provider-runtime.ts +11 -1
  55. package/src/cli/provider.ts +22 -2
  56. package/src/cli/registry.ts +21 -0
  57. package/src/cli/remote-workspace.ts +154 -0
  58. package/src/cli/status.ts +39 -7
  59. package/src/cli/usage-report.ts +14 -2
  60. package/src/client/hub-client.ts +34 -0
  61. package/src/client/hub-state.ts +9 -1
  62. package/src/codex/account-store.ts +78 -0
  63. package/src/codex/auth-api.ts +81 -54
  64. package/src/codex/auth-context.ts +45 -16
  65. package/src/codex/catalog/effort.ts +1 -1
  66. package/src/codex/catalog/metadata.ts +3 -6
  67. package/src/codex/catalog/native-models.ts +4 -4
  68. package/src/codex/catalog/parsing.ts +2 -20
  69. package/src/codex/catalog/provider-fetch.ts +10 -1
  70. package/src/codex/catalog/remote.ts +233 -0
  71. package/src/codex/catalog/sync.ts +403 -35
  72. package/src/codex/convergence.ts +1 -1
  73. package/src/codex/history-manifest.ts +36 -0
  74. package/src/codex/history-provider.ts +32 -5
  75. package/src/codex/inject.ts +9 -0
  76. package/src/codex/main-account.ts +113 -0
  77. package/src/codex/main-device-reauth-api.ts +89 -0
  78. package/src/codex/main-device-reauth.ts +217 -0
  79. package/src/codex/native-residue.ts +9 -2
  80. package/src/codex/quota-auto-refresh.ts +3 -2
  81. package/src/codex/quota-capacity.ts +98 -0
  82. package/src/codex/quota-history.ts +160 -0
  83. package/src/codex/quota-types.ts +8 -0
  84. package/src/codex/quota.ts +118 -91
  85. package/src/codex/refresh.ts +2 -1
  86. package/src/codex/routing.ts +90 -17
  87. package/src/codex/sync.ts +33 -4
  88. package/src/combos/request.ts +19 -1
  89. package/src/config/multi-agent-surface.ts +61 -0
  90. package/src/config/provider-validation.ts +176 -0
  91. package/src/config.ts +213 -11
  92. package/src/generated/compatibility-version.json +436 -168
  93. package/src/images/loop.ts +119 -36
  94. package/src/lib/admission.ts +12 -6
  95. package/src/lib/redact.ts +7 -0
  96. package/src/lib/translator-budget.ts +4 -3
  97. package/src/lib/windows-atomic-replace.ts +1 -0
  98. package/src/lib/windows-elevation.ts +1 -1
  99. package/src/oauth/chatgpt-device.ts +62 -5
  100. package/src/oauth/devin/cli-import.ts +130 -0
  101. package/src/oauth/devin.ts +63 -8
  102. package/src/oauth/index.ts +29 -14
  103. package/src/oauth/kiro.ts +18 -6
  104. package/src/oauth/login-cli.ts +9 -1
  105. package/src/oauth/meta-muse-device.ts +464 -0
  106. package/src/oauth/meta-muse.ts +123 -32
  107. package/src/oauth/pool-kernel.ts +9 -0
  108. package/src/oauth/pool-settings-capability.ts +2 -2
  109. package/src/oauth/store.ts +57 -0
  110. package/src/oauth/types.ts +31 -0
  111. package/src/providers/derive.ts +13 -3
  112. package/src/providers/devin-cli-authmode-migration.ts +57 -35
  113. package/src/providers/devin-provider-merge-migration.ts +240 -0
  114. package/src/providers/muse-key-quota.ts +117 -0
  115. package/src/providers/muse-subscription-usage.ts +14 -2
  116. package/src/providers/openai-sidecar.ts +25 -3
  117. package/src/providers/opencode-zen-rate-limit.ts +58 -0
  118. package/src/providers/provider-id-rewrite.ts +20 -5
  119. package/src/providers/quota-types.ts +12 -0
  120. package/src/providers/quota.ts +143 -102
  121. package/src/providers/reasoning-metadata.ts +543 -0
  122. package/src/providers/registry.ts +80 -49
  123. package/src/reasoning-effort.ts +26 -2
  124. package/src/remote/hub-usage.ts +32 -0
  125. package/src/remote-control/index.ts +192 -41
  126. package/src/remote-control/workspace-activation.ts +9 -0
  127. package/src/remote-control/workspace-agent-connection.ts +366 -0
  128. package/src/remote-control/workspace-claude-runtime.ts +243 -0
  129. package/src/remote-control/workspace-codex-runtime.ts +531 -0
  130. package/src/remote-control/workspace-codex-sandbox.ts +115 -0
  131. package/src/remote-control/workspace-command-runner.ts +748 -0
  132. package/src/remote-control/workspace-coordinator.ts +231 -0
  133. package/src/remote-control/workspace-device.ts +585 -0
  134. package/src/remote-control/workspace-executable.ts +43 -0
  135. package/src/remote-control/workspace-executor.ts +397 -0
  136. package/src/remote-control/workspace-hub.ts +519 -0
  137. package/src/remote-control/workspace-pi-runtime.ts +382 -0
  138. package/src/remote-control/workspace-process.ts +129 -0
  139. package/src/remote-control/workspace-rpc.ts +304 -0
  140. package/src/remote-control/workspace-runtime.ts +60 -0
  141. package/src/remote-control/workspace-secret-store.ts +39 -0
  142. package/src/remote-control/workspace-sessions.ts +799 -0
  143. package/src/remote-control/workspace-tool-bridge.ts +192 -0
  144. package/src/responses/code-mode-helper-compat.ts +22 -3
  145. package/src/responses/hosted-tool-policy.ts +0 -1
  146. package/src/responses/muse-tool-name-alias.ts +379 -0
  147. package/src/responses/plaintext-v2-agent-messages.ts +902 -0
  148. package/src/router.ts +7 -0
  149. package/src/routing/compatibility/behavior.ts +0 -1
  150. package/src/server/audio-client.ts +64 -0
  151. package/src/server/audio-dictation.ts +91 -0
  152. package/src/server/audio-live.ts +185 -0
  153. package/src/server/audio-transcriptions.ts +183 -0
  154. package/src/server/audio-upstream.ts +153 -0
  155. package/src/server/auth-cors.ts +61 -2
  156. package/src/server/chat-completions.ts +1 -1
  157. package/src/server/chat-native-sse.ts +92 -48
  158. package/src/server/chat-native.ts +37 -15
  159. package/src/server/hub-usage.ts +57 -0
  160. package/src/server/images.ts +4 -0
  161. package/src/server/index.ts +722 -57
  162. package/src/server/lifecycle.ts +5 -6
  163. package/src/server/live-call-bindings.ts +60 -0
  164. package/src/server/live.ts +12 -1
  165. package/src/server/management/agent-settings-routes.ts +25 -4
  166. package/src/server/management/api-access.ts +37 -0
  167. package/src/server/management/api-key-usage.ts +7 -2
  168. package/src/server/management/config-routes.ts +1 -18
  169. package/src/server/management/context.ts +15 -0
  170. package/src/server/management/logs-usage-routes.ts +2 -0
  171. package/src/server/management/oauth-account-routes.ts +39 -12
  172. package/src/server/management/provider-routes.ts +125 -2
  173. package/src/server/management/remote-workspace-routes.ts +140 -0
  174. package/src/server/management/route-registry.ts +15 -0
  175. package/src/server/management/usage-aggregate-cache.ts +14 -15
  176. package/src/server/management/usage-summary-cache.ts +2 -0
  177. package/src/server/management-api.ts +23 -0
  178. package/src/server/ports.ts +17 -0
  179. package/src/server/relay-eager.ts +4 -1
  180. package/src/server/relay.ts +70 -10
  181. package/src/server/request-decompress.ts +6 -3
  182. package/src/server/responses/agent-task-recovery.ts +25 -32
  183. package/src/server/responses/codex-auth-error.ts +11 -0
  184. package/src/server/responses/codex-ws-exchange.ts +52 -3
  185. package/src/server/responses/codex-ws-wire.ts +55 -0
  186. package/src/server/responses/compact.ts +9 -1
  187. package/src/server/responses/core.ts +337 -73
  188. package/src/server/responses/encrypted-payload.ts +45 -2
  189. package/src/server/responses/ws-upstream.ts +4 -1
  190. package/src/server/responses-self-named-namespace-scrub.ts +1 -3
  191. package/src/server/responses-undeclared-tool-guard.ts +1 -1
  192. package/src/server/search.ts +3 -0
  193. package/src/server/sse-payload-rewrite.ts +136 -51
  194. package/src/server/ws-bridge.ts +35 -1
  195. package/src/service/cli.ts +372 -0
  196. package/src/service/diagnostics.ts +340 -0
  197. package/src/service/guards.ts +303 -0
  198. package/src/service/health.ts +222 -0
  199. package/src/service/launchd.ts +853 -0
  200. package/src/service/orchestration.ts +617 -0
  201. package/src/service/repair.ts +334 -0
  202. package/src/service/state.ts +363 -0
  203. package/src/service/systemd.ts +229 -0
  204. package/src/service/windows-ops.ts +690 -0
  205. package/src/service/windows-scheduler.ts +769 -0
  206. package/src/service/windows-taskxml.ts +613 -0
  207. package/src/service.ts +22 -5550
  208. package/src/storage/cleanup/db.ts +258 -0
  209. package/src/storage/cleanup/execute.ts +358 -0
  210. package/src/storage/cleanup/paths.ts +189 -0
  211. package/src/storage/cleanup/pending.ts +140 -0
  212. package/src/storage/cleanup/preview.ts +292 -0
  213. package/src/storage/cleanup/reconcile.ts +347 -0
  214. package/src/storage/cleanup/restore.ts +932 -0
  215. package/src/storage/cleanup/satellite.ts +474 -0
  216. package/src/storage/cleanup/staging.ts +129 -0
  217. package/src/storage/cleanup/types.ts +98 -0
  218. package/src/storage/cleanup.ts +49 -3127
  219. package/src/types/accounts.ts +2 -0
  220. package/src/types/config.ts +13 -12
  221. package/src/types/provider.ts +37 -0
  222. package/src/types/request.ts +2 -0
  223. package/src/types/tools.ts +17 -5
  224. package/src/types.ts +1 -0
  225. package/src/usage/expected-prices.ts +127 -0
  226. package/src/usage/log.ts +58 -1
  227. package/src/vision/eligibility.ts +13 -2
  228. package/src/web-search/loop.ts +56 -3
  229. package/gui/dist/assets/index-D_t6sCWs.js +0 -115
  230. package/gui/dist/assets/index-EdoPnm9_.css +0 -1
  231. package/src/adapters/devin-cli/acp.ts +0 -204
  232. package/src/adapters/devin-cli/adapter.ts +0 -345
  233. package/src/adapters/devin-cli/binary.ts +0 -69
  234. package/src/adapters/devin-cli/models.ts +0 -57
  235. package/src/oauth/devin-cli.ts +0 -149
  236. package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
@@ -16,8 +16,8 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-D_t6sCWs.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-EdoPnm9_.css">
19
+ <script type="module" crossorigin src="/assets/index-D7ynYo2K.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-BBOZWGB6.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
@@ -0,0 +1,130 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "base64"
7
+ version = "0.22.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
10
+
11
+ [[package]]
12
+ name = "itoa"
13
+ version = "1.0.18"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
16
+
17
+ [[package]]
18
+ name = "memchr"
19
+ version = "2.8.3"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
22
+
23
+ [[package]]
24
+ name = "opencodex-remote-workspace-helper"
25
+ version = "0.1.0"
26
+ dependencies = [
27
+ "base64",
28
+ "serde",
29
+ "serde_json",
30
+ "windows-sys",
31
+ ]
32
+
33
+ [[package]]
34
+ name = "proc-macro2"
35
+ version = "1.0.107"
36
+ source = "registry+https://github.com/rust-lang/crates.io-index"
37
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
38
+ dependencies = [
39
+ "unicode-ident",
40
+ ]
41
+
42
+ [[package]]
43
+ name = "quote"
44
+ version = "1.0.47"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
47
+ dependencies = [
48
+ "proc-macro2",
49
+ ]
50
+
51
+ [[package]]
52
+ name = "serde"
53
+ version = "1.0.229"
54
+ source = "registry+https://github.com/rust-lang/crates.io-index"
55
+ checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
56
+ dependencies = [
57
+ "serde_core",
58
+ "serde_derive",
59
+ ]
60
+
61
+ [[package]]
62
+ name = "serde_core"
63
+ version = "1.0.229"
64
+ source = "registry+https://github.com/rust-lang/crates.io-index"
65
+ checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
66
+ dependencies = [
67
+ "serde_derive",
68
+ ]
69
+
70
+ [[package]]
71
+ name = "serde_derive"
72
+ version = "1.0.229"
73
+ source = "registry+https://github.com/rust-lang/crates.io-index"
74
+ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
75
+ dependencies = [
76
+ "proc-macro2",
77
+ "quote",
78
+ "syn",
79
+ ]
80
+
81
+ [[package]]
82
+ name = "serde_json"
83
+ version = "1.0.151"
84
+ source = "registry+https://github.com/rust-lang/crates.io-index"
85
+ checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
86
+ dependencies = [
87
+ "itoa",
88
+ "memchr",
89
+ "serde",
90
+ "serde_core",
91
+ "zmij",
92
+ ]
93
+
94
+ [[package]]
95
+ name = "syn"
96
+ version = "3.0.4"
97
+ source = "registry+https://github.com/rust-lang/crates.io-index"
98
+ checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
99
+ dependencies = [
100
+ "proc-macro2",
101
+ "quote",
102
+ "unicode-ident",
103
+ ]
104
+
105
+ [[package]]
106
+ name = "unicode-ident"
107
+ version = "1.0.24"
108
+ source = "registry+https://github.com/rust-lang/crates.io-index"
109
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
110
+
111
+ [[package]]
112
+ name = "windows-link"
113
+ version = "0.2.1"
114
+ source = "registry+https://github.com/rust-lang/crates.io-index"
115
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
116
+
117
+ [[package]]
118
+ name = "windows-sys"
119
+ version = "0.61.2"
120
+ source = "registry+https://github.com/rust-lang/crates.io-index"
121
+ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
122
+ dependencies = [
123
+ "windows-link",
124
+ ]
125
+
126
+ [[package]]
127
+ name = "zmij"
128
+ version = "1.0.23"
129
+ source = "registry+https://github.com/rust-lang/crates.io-index"
130
+ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
@@ -0,0 +1,24 @@
1
+ [package]
2
+ name = "opencodex-remote-workspace-helper"
3
+ version = "0.1.0"
4
+ edition = "2024"
5
+ license = "MIT"
6
+ publish = false
7
+
8
+ [dependencies]
9
+ base64 = "0.22"
10
+ serde = { version = "1", features = ["derive"] }
11
+ serde_json = "1"
12
+
13
+ [target.'cfg(windows)'.dependencies]
14
+ windows-sys = { version = "0.61", features = [
15
+ "Win32_Foundation",
16
+ "Win32_Security",
17
+ "Win32_Security_Authorization",
18
+ "Win32_Security_Isolation",
19
+ "Win32_Storage_FileSystem",
20
+ "Win32_System_JobObjects",
21
+ "Win32_System_Memory",
22
+ "Win32_System_Pipes",
23
+ "Win32_System_Threading",
24
+ ] }
@@ -0,0 +1,49 @@
1
+ #![deny(unsafe_op_in_unsafe_fn)]
2
+
3
+ mod protocol;
4
+ mod sandbox;
5
+
6
+ use std::io::{self, Read, Write};
7
+
8
+ use protocol::{HelperRequest, HelperResponse, MAX_REQUEST_BYTES, PROTOCOL_VERSION};
9
+
10
+ fn main() {
11
+ if std::env::args().nth(1).as_deref() == Some("__probe-child") {
12
+ std::process::exit(sandbox::run_probe_child());
13
+ }
14
+
15
+ let response = match read_request().and_then(handle_request) {
16
+ Ok(response) => response,
17
+ Err(error) => HelperResponse::error(error),
18
+ };
19
+ let mut stdout = io::stdout().lock();
20
+ if serde_json::to_writer(&mut stdout, &response).is_err() || stdout.write_all(b"\n").is_err() {
21
+ std::process::exit(2);
22
+ }
23
+ }
24
+
25
+ fn read_request() -> Result<HelperRequest, String> {
26
+ let mut body = Vec::new();
27
+ io::stdin()
28
+ .take((MAX_REQUEST_BYTES + 1) as u64)
29
+ .read_to_end(&mut body)
30
+ .map_err(|_| "could not read helper request".to_owned())?;
31
+ if body.len() > MAX_REQUEST_BYTES {
32
+ return Err("helper request exceeds its size limit".to_owned());
33
+ }
34
+ let request: HelperRequest =
35
+ serde_json::from_slice(&body).map_err(|_| "helper request is invalid".to_owned())?;
36
+ request.validate()?;
37
+ Ok(request)
38
+ }
39
+
40
+ fn handle_request(request: HelperRequest) -> Result<HelperResponse, String> {
41
+ if request.version != PROTOCOL_VERSION {
42
+ return Err("unsupported helper protocol version".to_owned());
43
+ }
44
+ match request.operation.as_str() {
45
+ "probe" => sandbox::probe().map(|()| HelperResponse::probe_success()),
46
+ "run" => sandbox::run(&request).map(HelperResponse::command_success),
47
+ _ => Err("unsupported helper operation".to_owned()),
48
+ }
49
+ }
@@ -0,0 +1,246 @@
1
+ use base64::{Engine as _, engine::general_purpose::STANDARD};
2
+ use serde::{Deserialize, Serialize};
3
+ use std::path::Path;
4
+ #[cfg(target_os = "windows")]
5
+ use std::path::PathBuf;
6
+
7
+ pub const PROTOCOL_VERSION: u8 = 1;
8
+ pub const MAX_REQUEST_BYTES: usize = 64 * 1024;
9
+ pub const MAX_OUTPUT_BYTES: usize = 256 * 1024;
10
+ const MAX_PATH_BYTES: usize = 4096;
11
+ const MAX_COMMAND_ARGUMENTS: usize = 64;
12
+ const MAX_COMMAND_ARGUMENT_BYTES: usize = 4096;
13
+ const MAX_COMMAND_BYTES: usize = 16 * 1024;
14
+ const MAX_TOOLCHAIN_ROOTS: usize = 16;
15
+
16
+ #[derive(Debug, Deserialize)]
17
+ #[serde(deny_unknown_fields, rename_all = "camelCase")]
18
+ pub struct HelperRequest {
19
+ pub version: u8,
20
+ pub operation: String,
21
+ #[serde(default)]
22
+ pub root: String,
23
+ #[serde(default)]
24
+ pub cwd: String,
25
+ #[serde(default)]
26
+ pub command: Vec<String>,
27
+ #[serde(default)]
28
+ pub toolchain_roots: Vec<String>,
29
+ #[serde(default)]
30
+ pub timeout_ms: u64,
31
+ #[serde(default)]
32
+ pub max_output_bytes: usize,
33
+ #[serde(default)]
34
+ pub network_access: bool,
35
+ }
36
+
37
+ impl HelperRequest {
38
+ pub fn validate(&self) -> Result<(), String> {
39
+ if self.operation == "probe" {
40
+ if !self.root.is_empty()
41
+ || !self.cwd.is_empty()
42
+ || !self.command.is_empty()
43
+ || !self.toolchain_roots.is_empty()
44
+ || self.timeout_ms != 0
45
+ || self.max_output_bytes != 0
46
+ || self.network_access
47
+ {
48
+ return Err("probe request must not carry command authority".to_owned());
49
+ }
50
+ return Ok(());
51
+ }
52
+ if self.operation != "run" {
53
+ return Ok(());
54
+ }
55
+ validate_path(&self.root, "workspace root")?;
56
+ validate_path(&self.cwd, "command cwd")?;
57
+ if !Path::new(&self.root).is_absolute() || !Path::new(&self.cwd).is_absolute() {
58
+ return Err("workspace root and cwd must be absolute".to_owned());
59
+ }
60
+ if self.command.is_empty() || self.command.len() > MAX_COMMAND_ARGUMENTS {
61
+ return Err("invalid command vector".to_owned());
62
+ }
63
+ let mut command_bytes = 0usize;
64
+ for value in &self.command {
65
+ if value.is_empty() || value.len() > MAX_COMMAND_ARGUMENT_BYTES || value.contains('\0')
66
+ {
67
+ return Err("invalid command vector".to_owned());
68
+ }
69
+ command_bytes = command_bytes
70
+ .checked_add(value.len())
71
+ .ok_or_else(|| "command vector is too large".to_owned())?;
72
+ }
73
+ if command_bytes > MAX_COMMAND_BYTES {
74
+ return Err("command vector is too large".to_owned());
75
+ }
76
+ if self.toolchain_roots.len() > MAX_TOOLCHAIN_ROOTS {
77
+ return Err("too many toolchain roots".to_owned());
78
+ }
79
+ for path in &self.toolchain_roots {
80
+ validate_path(path, "toolchain root")?;
81
+ if !Path::new(path).is_absolute() {
82
+ return Err("toolchain roots must be absolute".to_owned());
83
+ }
84
+ }
85
+ if !(1..=60_000).contains(&self.timeout_ms) {
86
+ return Err("command timeout is outside its limit".to_owned());
87
+ }
88
+ if !(1024..=MAX_OUTPUT_BYTES).contains(&self.max_output_bytes) {
89
+ return Err("command output limit is outside its limit".to_owned());
90
+ }
91
+ Ok(())
92
+ }
93
+
94
+ #[cfg(target_os = "windows")]
95
+ pub fn canonical_paths(&self) -> Result<CanonicalPaths, String> {
96
+ let root = canonical_directory(&self.root, "workspace root")?;
97
+ let cwd = canonical_directory(&self.cwd, "command cwd")?;
98
+ if !cwd.starts_with(&root) {
99
+ return Err("command cwd escaped its workspace root".to_owned());
100
+ }
101
+ let mut toolchain_roots = Vec::with_capacity(self.toolchain_roots.len());
102
+ for value in &self.toolchain_roots {
103
+ let canonical = canonical_directory(value, "toolchain root")?;
104
+ if !toolchain_roots.contains(&canonical) {
105
+ toolchain_roots.push(canonical);
106
+ }
107
+ }
108
+ Ok(CanonicalPaths {
109
+ root,
110
+ cwd,
111
+ toolchain_roots,
112
+ })
113
+ }
114
+ }
115
+
116
+ fn validate_path(value: &str, label: &str) -> Result<(), String> {
117
+ if value.is_empty() || value.len() > MAX_PATH_BYTES || value.contains('\0') {
118
+ return Err(format!("invalid {label}"));
119
+ }
120
+ Ok(())
121
+ }
122
+
123
+ #[cfg(target_os = "windows")]
124
+ fn canonical_directory(value: &str, label: &str) -> Result<PathBuf, String> {
125
+ let original = Path::new(value);
126
+ let metadata =
127
+ std::fs::symlink_metadata(original).map_err(|_| format!("{label} is unavailable"))?;
128
+ if metadata.file_type().is_symlink() || !metadata.is_dir() {
129
+ return Err(format!("{label} must remain a real directory"));
130
+ }
131
+ original
132
+ .canonicalize()
133
+ .map_err(|_| format!("{label} is unavailable"))
134
+ }
135
+
136
+ #[cfg(target_os = "windows")]
137
+ #[derive(Debug)]
138
+ pub struct CanonicalPaths {
139
+ pub root: PathBuf,
140
+ pub cwd: PathBuf,
141
+ pub toolchain_roots: Vec<PathBuf>,
142
+ }
143
+
144
+ #[derive(Debug)]
145
+ pub struct CommandOutcome {
146
+ pub exit_code: i32,
147
+ pub stdout: Vec<u8>,
148
+ pub stderr: Vec<u8>,
149
+ }
150
+
151
+ #[derive(Debug, Serialize)]
152
+ #[serde(rename_all = "camelCase")]
153
+ pub struct HelperResponse {
154
+ version: u8,
155
+ ok: bool,
156
+ #[serde(skip_serializing_if = "Option::is_none")]
157
+ probe: Option<bool>,
158
+ #[serde(skip_serializing_if = "Option::is_none")]
159
+ exit_code: Option<i32>,
160
+ #[serde(skip_serializing_if = "Option::is_none")]
161
+ stdout_base64: Option<String>,
162
+ #[serde(skip_serializing_if = "Option::is_none")]
163
+ stderr_base64: Option<String>,
164
+ #[serde(skip_serializing_if = "Option::is_none")]
165
+ error: Option<String>,
166
+ }
167
+
168
+ impl HelperResponse {
169
+ pub fn error(error: String) -> Self {
170
+ Self {
171
+ version: PROTOCOL_VERSION,
172
+ ok: false,
173
+ probe: None,
174
+ exit_code: None,
175
+ stdout_base64: None,
176
+ stderr_base64: None,
177
+ error: Some(limit_error(error)),
178
+ }
179
+ }
180
+
181
+ pub fn probe_success() -> Self {
182
+ Self {
183
+ version: PROTOCOL_VERSION,
184
+ ok: true,
185
+ probe: Some(true),
186
+ exit_code: None,
187
+ stdout_base64: None,
188
+ stderr_base64: None,
189
+ error: None,
190
+ }
191
+ }
192
+
193
+ pub fn command_success(outcome: CommandOutcome) -> Self {
194
+ Self {
195
+ version: PROTOCOL_VERSION,
196
+ ok: true,
197
+ probe: None,
198
+ exit_code: Some(outcome.exit_code),
199
+ stdout_base64: Some(STANDARD.encode(outcome.stdout)),
200
+ stderr_base64: Some(STANDARD.encode(outcome.stderr)),
201
+ error: None,
202
+ }
203
+ }
204
+ }
205
+
206
+ fn limit_error(mut value: String) -> String {
207
+ const MAX_ERROR_CHARS: usize = 512;
208
+ if value.chars().count() <= MAX_ERROR_CHARS {
209
+ return value;
210
+ }
211
+ value = value.chars().take(MAX_ERROR_CHARS).collect();
212
+ value.push('…');
213
+ value
214
+ }
215
+
216
+ #[cfg(test)]
217
+ mod tests {
218
+ use super::*;
219
+
220
+ #[test]
221
+ fn rejects_authority_smuggled_into_probe() {
222
+ let request: HelperRequest =
223
+ serde_json::from_str(r#"{"version":1,"operation":"probe","command":["whoami"]}"#)
224
+ .expect("valid JSON fixture");
225
+ assert!(request.validate().is_err());
226
+ }
227
+
228
+ #[test]
229
+ fn rejects_unknown_wire_fields() {
230
+ assert!(
231
+ serde_json::from_str::<HelperRequest>(
232
+ r#"{"version":1,"operation":"probe","surprise":true}"#,
233
+ )
234
+ .is_err()
235
+ );
236
+ }
237
+
238
+ #[test]
239
+ fn bounds_command_shape_before_platform_code() {
240
+ let request: HelperRequest = serde_json::from_str(
241
+ r#"{"version":1,"operation":"run","root":"/tmp/a","cwd":"/tmp/a","command":["x"],"timeoutMs":0,"maxOutputBytes":262144}"#,
242
+ )
243
+ .expect("valid JSON fixture");
244
+ assert!(request.validate().is_err());
245
+ }
246
+ }
@@ -0,0 +1,19 @@
1
+ use crate::protocol::{CommandOutcome, HelperRequest};
2
+
3
+ const MACOS_CONFINEMENT_UNAVAILABLE: &str =
4
+ "macOS Remote Workspace command confinement is unavailable; file tools remain enabled";
5
+
6
+ /// macOS has no unprivileged Job Object or cgroup equivalent that can revoke every descendant's
7
+ /// workspace access. A Seatbelt profile can constrain a process, but allowing subprocesses lets a
8
+ /// descendant call `setsid()` and outlive cancellation. Importing broad system profiles merely to
9
+ /// make a single-process probe start would also widen unrelated host-service authority. Until a
10
+ /// native containment owner closes both boundaries, command execution must stay unavailable.
11
+ pub fn probe() -> Result<(), String> {
12
+ Err(MACOS_CONFINEMENT_UNAVAILABLE.to_owned())
13
+ }
14
+
15
+ /// Keep the helper itself fail-closed even if a caller bypasses OCX capability negotiation and
16
+ /// submits a `run` request directly.
17
+ pub fn run(_request: &HelperRequest) -> Result<CommandOutcome, String> {
18
+ Err(MACOS_CONFINEMENT_UNAVAILABLE.to_owned())
19
+ }
@@ -0,0 +1,77 @@
1
+ #[cfg(target_os = "macos")]
2
+ mod macos;
3
+ #[cfg(target_os = "windows")]
4
+ mod windows;
5
+
6
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
7
+ use crate::protocol::{CommandOutcome, HelperRequest};
8
+ use std::fs::{self, OpenOptions};
9
+ use std::io::Read;
10
+ use std::net::{SocketAddr, TcpStream};
11
+ use std::time::Duration;
12
+
13
+ #[cfg(target_os = "macos")]
14
+ pub use macos::{probe, run};
15
+ #[cfg(target_os = "windows")]
16
+ pub use windows::{probe, run};
17
+
18
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
19
+ pub fn probe() -> Result<(), String> {
20
+ Err("native helper is supported only on macOS and Windows".to_owned())
21
+ }
22
+
23
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
24
+ pub fn run(_request: &HelperRequest) -> Result<CommandOutcome, String> {
25
+ Err("native helper is supported only on macOS and Windows".to_owned())
26
+ }
27
+
28
+ pub fn run_probe_child() -> i32 {
29
+ let mut args = std::env::args().skip(2);
30
+ let Some(workspace) = args.next() else {
31
+ return 20;
32
+ };
33
+ let Some(outside_file) = args.next() else {
34
+ return 21;
35
+ };
36
+ let Some(outside_write) = args.next() else {
37
+ return 22;
38
+ };
39
+ let Some(listener_address) = args.next() else {
40
+ return 23;
41
+ };
42
+ let Some(existing_workspace_file) = args.next() else {
43
+ return 24;
44
+ };
45
+ if args.next().is_some() {
46
+ return 24;
47
+ }
48
+
49
+ let marker = std::path::Path::new(&workspace).join("probe-marker");
50
+ if fs::write(&marker, b"sandboxed").is_err() {
51
+ return 25;
52
+ }
53
+ if !matches!(fs::read(&existing_workspace_file), Ok(value) if value == b"existing")
54
+ || fs::write(&existing_workspace_file, b"updated").is_err()
55
+ {
56
+ return 29;
57
+ }
58
+ let mut outside = Vec::new();
59
+ if OpenOptions::new()
60
+ .read(true)
61
+ .open(&outside_file)
62
+ .and_then(|mut file| file.read_to_end(&mut outside))
63
+ .is_ok()
64
+ {
65
+ return 26;
66
+ }
67
+ if fs::write(&outside_write, b"escaped").is_ok() {
68
+ return 27;
69
+ }
70
+ let Ok(listener_address) = listener_address.parse::<SocketAddr>() else {
71
+ return 23;
72
+ };
73
+ if TcpStream::connect_timeout(&listener_address, Duration::from_millis(500)).is_ok() {
74
+ return 28;
75
+ }
76
+ 0
77
+ }
@@ -0,0 +1,15 @@
1
+ use crate::protocol::{CommandOutcome, HelperRequest};
2
+
3
+ const WINDOWS_CONFINEMENT_UNAVAILABLE: &str =
4
+ "Windows Remote Workspace command confinement is unavailable; command execution is disabled";
5
+
6
+ // A command-capable implementation must retain cleanup ownership through helper cancellation
7
+ // and establish Job membership atomically. Until that owner is implemented and verified,
8
+ // direct helper requests and capability probes refuse before allocating OS resources.
9
+ pub fn probe() -> Result<(), String> {
10
+ Err(WINDOWS_CONFINEMENT_UNAVAILABLE.to_owned())
11
+ }
12
+
13
+ pub fn run(_request: &HelperRequest) -> Result<CommandOutcome, String> {
14
+ Err(WINDOWS_CONFINEMENT_UNAVAILABLE.to_owned())
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.52.0-preview.20260912",
3
+ "version": "2.53.0-preview.20260913",
4
4
  "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
@@ -28,6 +28,9 @@
28
28
  "README.md",
29
29
  "SPONSORS.md",
30
30
  "AGENTS_INSTALL.md",
31
+ "native/remote-workspace-helper/Cargo.toml",
32
+ "native/remote-workspace-helper/Cargo.lock",
33
+ "native/remote-workspace-helper/src",
31
34
  "LICENSE"
32
35
  ],
33
36
  "engines": {
@@ -52,6 +55,8 @@
52
55
  "structure:check": "bun scripts/structure-ssot.ts",
53
56
  "generate:model-metadata": "bun scripts/generate-model-metadata.ts",
54
57
  "build:gui": "cd gui && bun install --frozen-lockfile && bun run build && cd .. && bun run prepare:package",
58
+ "build:remote-workspace-helper": "cargo build --release --locked --manifest-path native/remote-workspace-helper/Cargo.toml",
59
+ "test:remote-workspace-helper": "cargo test --locked --manifest-path native/remote-workspace-helper/Cargo.toml",
55
60
  "prepare:package": "bun scripts/prepare-package.ts",
56
61
  "prepack": "bun run prepare:package",
57
62
  "prepublishOnly": "bun run audit:high && bun run typecheck && bun run build:gui",
@@ -68,6 +68,14 @@ export interface NormalizeTarget {
68
68
  mediaType: string;
69
69
  replace(data: string, mediaType: string): void;
70
70
  drop(note: string): void;
71
+ /**
72
+ * True when `drop` leaves the original bytes on the wire instead of removing or
73
+ * textifying them (openai-chat, which has no downstream guard that could re-attach a
74
+ * dropped image). The core normally stops counting a dropped target, which is correct
75
+ * only when the bytes actually leave. Here they do not, so those bytes keep counting
76
+ * toward the budget and the demotion loop keeps shrinking the images it still can.
77
+ */
78
+ retainsBytesOnDrop?: boolean;
71
79
  }
72
80
 
73
81
  export interface NormalizeTargetsOptions extends NormalizeOptions {
@@ -127,11 +135,17 @@ export async function normalizeImageTargets(targets: NormalizeTarget[], options:
127
135
  if (newestFirstIndex >= processLimit) continue;
128
136
  if (b64.length > MAX_INPUT_BASE64_LENGTH) {
129
137
  target.drop(BOMB_TEXT);
138
+ if (target.retainsBytesOnDrop) {
139
+ entries[i] = { target, sourceB64: b64, sourceMedia: target.mediaType.toLowerCase(), pos: TERMINAL_POS, size: b64.length, done: true };
140
+ }
130
141
  continue;
131
142
  }
132
143
  const dims = sniffImageDimensions(b64);
133
144
  if (dims && dims.width * dims.height > MAX_INPUT_PIXELS) {
134
145
  target.drop(BOMB_TEXT);
146
+ if (target.retainsBytesOnDrop) {
147
+ entries[i] = { target, sourceB64: b64, sourceMedia: target.mediaType.toLowerCase(), pos: TERMINAL_POS, size: b64.length, done: true };
148
+ }
135
149
  continue;
136
150
  }
137
151
  const sourceMedia = target.mediaType.toLowerCase();
@@ -139,6 +153,9 @@ export async function normalizeImageTargets(targets: NormalizeTarget[], options:
139
153
  const result = await processAt(b64, pos, sourceMedia, encode, validate);
140
154
  if (result.kind === "failed") {
141
155
  target.drop(UNDECODABLE_TEXT);
156
+ if (target.retainsBytesOnDrop) {
157
+ entries[i] = { target, sourceB64: b64, sourceMedia, pos: TERMINAL_POS, size: b64.length, done: true };
158
+ }
142
159
  continue;
143
160
  }
144
161
  let size = b64.length;
@@ -178,8 +195,14 @@ export async function normalizeImageTargets(targets: NormalizeTarget[], options:
178
195
  const result = await processAt(entry.sourceB64, entry.pos + 1, entry.sourceMedia, encode, validate);
179
196
  if (result.kind === "failed") {
180
197
  entry.target.drop(UNDECODABLE_TEXT);
181
- sum -= entry.size;
182
- entries[entries.indexOf(entry)] = null;
198
+ if (entry.target.retainsBytesOnDrop) {
199
+ // Bytes stay on the wire, so they stay in the total; mark it terminal so the
200
+ // loop moves on to a target it can still shrink instead of retrying this one.
201
+ entry.done = true;
202
+ } else {
203
+ sum -= entry.size;
204
+ entries[entries.indexOf(entry)] = null;
205
+ }
183
206
  continue;
184
207
  }
185
208
  let newSize = entry.size;
@@ -202,6 +225,11 @@ export async function normalizeImageTargets(targets: NormalizeTarget[], options:
202
225
  const e = entries[i];
203
226
  if (!e) continue;
204
227
  e.target.drop(OVERFLOW_DROP_TEXT);
228
+ if (e.target.retainsBytesOnDrop) {
229
+ // The drop left the bytes in place, so they still count and dropping another
230
+ // copy of this target would not help. Move on to one that can actually leave.
231
+ continue;
232
+ }
205
233
  sum -= e.size;
206
234
  entries[i] = null;
207
235
  }
@@ -1320,7 +1320,7 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
1320
1320
  }];
1321
1321
  }
1322
1322
  const json = parsed;
1323
- const responseBytes = new TextEncoder().encode(JSON.stringify(json)).byteLength;
1323
+ const responseBytes = Buffer.byteLength(JSON.stringify(json), "utf8");
1324
1324
  budget.chargeRetained(responseBytes, { kind: "retained_collectors" });
1325
1325
  try {
1326
1326
  const events: AdapterEvent[] = [];