@bitkyc08/opencodex 2.63.0 → 2.64.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 (262) hide show
  1. package/AGENTS_INSTALL.md +4 -3
  2. package/README.md +46 -33
  3. package/assets/download-linux.svg +10 -0
  4. package/assets/download-macos.svg +10 -0
  5. package/assets/download-windows.svg +10 -0
  6. package/bin/ocx.mjs +12 -4
  7. package/gui/dist/assets/App-CpuDF3ci.js +50 -0
  8. package/gui/dist/assets/App-I5AnaSLh.css +1 -0
  9. package/gui/dist/assets/{Tray-CncKDBTp.js → Tray-B4uEIa1O.js} +1 -1
  10. package/gui/dist/assets/index-DiBRuK-d.css +1 -0
  11. package/gui/dist/assets/index-SggB6t3z.js +86 -0
  12. package/gui/dist/assets/usage-companion-chart-DLbKOJml.js +1 -0
  13. package/gui/dist/index.html +2 -2
  14. package/native/remote-workspace-helper/src/protocol.rs +5 -0
  15. package/package.json +4 -1
  16. package/src/adapters/anthropic.ts +66 -7
  17. package/src/adapters/codebuddy/adapter.ts +112 -16
  18. package/src/adapters/codebuddy/mcp-server.ts +180 -0
  19. package/src/adapters/codebuddy/scaffold-guard.ts +25 -8
  20. package/src/adapters/codebuddy/tool-bridge.ts +597 -0
  21. package/src/adapters/coding-agent/protocol.ts +123 -10
  22. package/src/adapters/coding-agent/turn.ts +324 -2
  23. package/src/adapters/command-code-restored-schema.ts +112 -0
  24. package/src/adapters/command-code-tool-text.ts +589 -0
  25. package/src/adapters/command-code.ts +80 -6
  26. package/src/adapters/cursor/catalog.ts +12 -0
  27. package/src/adapters/cursor/current-request.ts +46 -0
  28. package/src/adapters/cursor/discovery.ts +1 -1
  29. package/src/adapters/cursor/effort-map.ts +4 -0
  30. package/src/adapters/cursor/native-exec.ts +15 -0
  31. package/src/adapters/cursor/protobuf-events.ts +40 -12
  32. package/src/adapters/cursor/protobuf-request.ts +83 -18
  33. package/src/adapters/cursor/request-builder.ts +5 -4
  34. package/src/adapters/cursor/tool-guidance.ts +1 -1
  35. package/src/adapters/devin/live-models.ts +7 -0
  36. package/src/adapters/inline-think-tags.ts +251 -0
  37. package/src/adapters/kiro/adapter.ts +1 -0
  38. package/src/adapters/kiro/stream.ts +5 -3
  39. package/src/adapters/kiro/usage.ts +4 -3
  40. package/src/adapters/mimo-free.ts +37 -18
  41. package/src/adapters/openai-chat/messages.ts +5 -5
  42. package/src/adapters/openai-chat/tool-schema.ts +124 -2
  43. package/src/adapters/openai-chat.ts +13 -3
  44. package/src/adapters/openai-responses/passthrough.ts +15 -2
  45. package/src/adapters/openai-responses/reasoning.ts +14 -1
  46. package/src/adapters/openai-responses/request-strips.ts +29 -3
  47. package/src/adapters/openai-responses/tool-output-recovery.ts +9 -3
  48. package/src/adapters/qoder/adapter.ts +15 -12
  49. package/src/adapters/qoder/scaffold-guard.ts +45 -13
  50. package/src/bridge/internal.ts +20 -0
  51. package/src/bridge/sse.ts +14 -5
  52. package/src/claude/agents-inject.ts +2 -1
  53. package/src/claude/desktop-applied-marker.ts +44 -0
  54. package/src/claude/desktop-profile.ts +22 -12
  55. package/src/claude/inbound.ts +8 -3
  56. package/src/cli/access.ts +22 -5
  57. package/src/cli/account-api.ts +6 -0
  58. package/src/cli/account-auth.ts +5 -2
  59. package/src/cli/account.ts +5 -6
  60. package/src/cli/aside-profiles.ts +61 -1
  61. package/src/cli/claude-desktop.ts +3 -2
  62. package/src/cli/claude.ts +3 -1
  63. package/src/cli/codex-cli-update.ts +2 -2
  64. package/src/cli/codex-shim-autorestore.ts +3 -0
  65. package/src/cli/dispatch.ts +12 -4
  66. package/src/cli/index.ts +98 -10
  67. package/src/cli/registry.ts +1 -1
  68. package/src/cli/resolve.ts +113 -1
  69. package/src/cli/root.ts +5 -0
  70. package/src/cli/stop-approval.ts +186 -0
  71. package/src/cli/stop-report.ts +1 -1
  72. package/src/cli/system-restart-client.ts +19 -4
  73. package/src/client/hub-client.ts +25 -18
  74. package/src/clients/config-export.ts +12 -4
  75. package/src/codex/account-auto-switch.ts +59 -0
  76. package/src/codex/account-lifecycle.ts +7 -0
  77. package/src/codex/account-priority.ts +10 -0
  78. package/src/codex/account-usability.ts +9 -0
  79. package/src/codex/auth-api/account-list.ts +5 -0
  80. package/src/codex/auth-api/login-flow.ts +15 -5
  81. package/src/codex/auth-api/login-state.ts +5 -16
  82. package/src/codex/auth-api/pool-mode-gate.ts +30 -7
  83. package/src/codex/auth-api/routes.ts +39 -3
  84. package/src/codex/auth-context.ts +145 -17
  85. package/src/codex/catalog/build-entries.ts +2 -1
  86. package/src/codex/catalog/effort.ts +8 -2
  87. package/src/codex/catalog/metadata.ts +33 -7
  88. package/src/codex/catalog/native-models.ts +102 -5
  89. package/src/codex/catalog/parsing.ts +2 -0
  90. package/src/codex/catalog/provider-models.ts +19 -6
  91. package/src/codex/cli-installation-targets.ts +44 -40
  92. package/src/codex/codex-write-lock.ts +32 -2
  93. package/src/codex/inject/multi-agent-v2.ts +90 -0
  94. package/src/codex/inject/plan.ts +365 -0
  95. package/src/codex/inject.ts +237 -362
  96. package/src/codex/log-guard/maintenance.ts +30 -13
  97. package/src/codex/project-config-warnings.ts +47 -7
  98. package/src/codex/prompt-layers/encoding.ts +41 -0
  99. package/src/codex/prompt-layers/toml-read.ts +1 -1
  100. package/src/codex/prompt-layers.ts +17 -13
  101. package/src/codex/prompt-text-probe.ts +20 -10
  102. package/src/codex/quota-observation-freshness.ts +34 -0
  103. package/src/codex/quota-rejection.ts +8 -4
  104. package/src/codex/quota.ts +4 -2
  105. package/src/codex/routing/selection.ts +32 -9
  106. package/src/codex/routing.ts +53 -11
  107. package/src/codex/subagent-model-fallback.ts +4 -3
  108. package/src/codex/sync.ts +11 -0
  109. package/src/codex/windows-installation-files.ts +33 -11
  110. package/src/codex/write-coordination.ts +4 -0
  111. package/src/combos/request.ts +6 -2
  112. package/src/companion/settings.ts +6 -1
  113. package/src/config/atomic-write.ts +12 -1
  114. package/src/config/derived-registries.ts +29 -0
  115. package/src/config/diagnostics.ts +17 -0
  116. package/src/config/live-reconcile.ts +11 -2
  117. package/src/config/load-degrade.ts +9 -2
  118. package/src/config/persist-unlocked.ts +4 -3
  119. package/src/config/persisted-mutation.ts +94 -0
  120. package/src/config/provider-validation.ts +1 -1
  121. package/src/config/proxy-env.ts +62 -12
  122. package/src/config/rebase-provenance.ts +80 -1
  123. package/src/config/schema/config-schema.ts +5 -0
  124. package/src/config/schema/leaf-validators.ts +50 -1
  125. package/src/config/subagent-models.ts +41 -15
  126. package/src/config.ts +18 -95
  127. package/src/generated/compatibility-version.json +330 -218
  128. package/src/generated/model-metadata.ts +8 -5
  129. package/src/github/star-state.ts +46 -6
  130. package/src/grok/inject.ts +74 -40
  131. package/src/grok/status.ts +11 -4
  132. package/src/integrations/cursor-effort-table.ts +50 -13
  133. package/src/integrations/raycast-detect.ts +19 -4
  134. package/src/integrations/serialize.ts +11 -1
  135. package/src/lib/bounded-body.ts +30 -0
  136. package/src/lib/crash-guard.ts +52 -4
  137. package/src/lib/local-aside-sync-contract.ts +41 -0
  138. package/src/lib/package-tree-integrity.ts +181 -9
  139. package/src/lib/proxy-env.ts +18 -1
  140. package/src/lib/request-failure-attribution.ts +1 -0
  141. package/src/lib/request-failure-model.ts +3 -0
  142. package/src/lib/service-secrets.ts +99 -2
  143. package/src/lib/socks5-fetch.ts +43 -14
  144. package/src/lib/token-estimate.ts +17 -2
  145. package/src/oauth/command-code.ts +3 -2
  146. package/src/oauth/generic-account-failover.ts +29 -1
  147. package/src/oauth/index.ts +24 -16
  148. package/src/oauth/login-flow-state.ts +5 -5
  149. package/src/oauth/meta-muse-device.ts +49 -7
  150. package/src/oauth/store.ts +74 -9
  151. package/src/providers/alibaba-region-backup.ts +16 -1
  152. package/src/providers/anthropic-fast.ts +89 -0
  153. package/src/providers/anthropic-reset-grant-ledger.ts +288 -0
  154. package/src/providers/anthropic-reset-grants.ts +329 -0
  155. package/src/providers/claude-cli-identity.ts +12 -0
  156. package/src/providers/command-code-efforts.ts +184 -102
  157. package/src/providers/derive.ts +11 -3
  158. package/src/providers/fastwire.ts +16 -3
  159. package/src/providers/label.ts +8 -3
  160. package/src/providers/model-rename-fields.ts +1 -0
  161. package/src/providers/openai-virtual-models.ts +1 -0
  162. package/src/providers/quota/vendor-probes-oauth.ts +2 -1
  163. package/src/providers/reasoning-metadata.ts +38 -15
  164. package/src/providers/registry/entries-core.ts +95 -7
  165. package/src/providers/registry/entries-extended.ts +21 -10
  166. package/src/providers/registry/model-ids.ts +1 -0
  167. package/src/providers/registry/model-seeds.ts +31 -3
  168. package/src/providers/registry/types.ts +3 -1
  169. package/src/providers/resolved-model-policy-merge.ts +38 -8
  170. package/src/providers/resolved-model-policy.ts +4 -2
  171. package/src/providers/service-tier.ts +3 -1
  172. package/src/reasoning-effort.ts +6 -5
  173. package/src/responses/code-mode-helper-compat.ts +14 -4
  174. package/src/responses/code-mode-shell-input.ts +54 -0
  175. package/src/responses/custom-tool-compat.ts +173 -5
  176. package/src/responses/parser.ts +38 -2
  177. package/src/responses/reasoning-replay-cache.ts +26 -0
  178. package/src/router.ts +11 -1
  179. package/src/routing/history/indexer.ts +7 -2
  180. package/src/routing/history/schema.ts +3 -1
  181. package/src/server/adapter-resolve.ts +2 -2
  182. package/src/server/auth-cors.ts +3 -0
  183. package/src/server/chat-completions.ts +14 -1
  184. package/src/server/chat-native.ts +17 -0
  185. package/src/server/claude-messages.ts +4 -3
  186. package/src/server/direct-local-http.ts +45 -19
  187. package/src/server/gui-session.ts +6 -15
  188. package/src/server/index/package-tree-guard.ts +53 -0
  189. package/src/server/index/serve-options.ts +17 -3
  190. package/src/server/index/startup-warnings.ts +17 -0
  191. package/src/server/index.ts +15 -17
  192. package/src/server/lifecycle.ts +8 -0
  193. package/src/server/live.ts +54 -4
  194. package/src/server/management/agent-settings-routes.ts +44 -8
  195. package/src/server/management/anthropic-reset-grant-routes.ts +252 -0
  196. package/src/server/management/codex-prompt-routes.ts +5 -1
  197. package/src/server/management/config-routes.ts +14 -4
  198. package/src/server/management/oauth-account-routes.ts +17 -1
  199. package/src/server/management/provider-overwrite-carry.ts +164 -0
  200. package/src/server/management/provider-routes.ts +36 -6
  201. package/src/server/management/remote-workspace-routes.ts +2 -2
  202. package/src/server/management/route-registry.ts +2 -0
  203. package/src/server/management/shadow-call-validation.ts +39 -0
  204. package/src/server/management/system-restart.ts +72 -6
  205. package/src/server/management-api.ts +16 -2
  206. package/src/server/management-auth.ts +52 -8
  207. package/src/server/proxy-liveness.ts +110 -4
  208. package/src/server/request-log.ts +26 -9
  209. package/src/server/request-metrics.ts +5 -0
  210. package/src/server/responses/adapter-continuation.ts +6 -3
  211. package/src/server/responses/adapter-dispatch.ts +65 -1
  212. package/src/server/responses/compact.ts +31 -1
  213. package/src/server/responses/compaction-routing.ts +28 -3
  214. package/src/server/responses/core-codex-account.ts +85 -26
  215. package/src/server/responses/core-combo-failure.ts +16 -15
  216. package/src/server/responses/core-normalize.ts +5 -1
  217. package/src/server/responses/core-opaque-recovery.ts +50 -2
  218. package/src/server/responses/core-options.ts +2 -0
  219. package/src/server/responses/core-replay.ts +7 -0
  220. package/src/server/responses/core.ts +1 -1
  221. package/src/server/responses/encrypted-payload.ts +23 -9
  222. package/src/server/responses/passthrough-delivery.ts +55 -35
  223. package/src/server/responses/passthrough-dispatch.ts +12 -13
  224. package/src/server/responses/policy-fallback.ts +12 -1
  225. package/src/server/responses/request-prepare.ts +46 -7
  226. package/src/server/responses/request-spend.ts +31 -15
  227. package/src/server/responses/run-turn-execution.ts +6 -5
  228. package/src/server/responses/shadow-target-availability.ts +61 -0
  229. package/src/server/responses-custom-tool-repair.ts +2 -0
  230. package/src/service/claim.ts +185 -0
  231. package/src/service/cli.ts +10 -1
  232. package/src/service/guarded-manager-target.ts +151 -0
  233. package/src/service/guards.ts +47 -48
  234. package/src/service/managing-cli.ts +170 -0
  235. package/src/service/orchestration.ts +3 -1
  236. package/src/service/state.ts +4 -0
  237. package/src/service/systemd.ts +16 -1
  238. package/src/service.ts +1 -1
  239. package/src/types/config.ts +8 -0
  240. package/src/types/provider.ts +16 -0
  241. package/src/types/request.ts +10 -0
  242. package/src/types/tools.ts +9 -1
  243. package/src/types/wire.ts +68 -4
  244. package/src/types.ts +1 -0
  245. package/src/update/job.ts +10 -16
  246. package/src/update/npm-invocation.mjs +17 -16
  247. package/src/update/transactional-install.d.mts +22 -1
  248. package/src/update/transactional-install.mjs +176 -19
  249. package/src/update/update-failure-guidance.d.mts +8 -0
  250. package/src/update/update-failure-guidance.mjs +47 -0
  251. package/src/usage/expected-prices.ts +54 -12
  252. package/src/usage/log.ts +117 -3
  253. package/src/usage/telemetry-contract.ts +1 -0
  254. package/src/usage/timeline.ts +34 -6
  255. package/src/usage/user-cost-overlay-reconciler.ts +3 -3
  256. package/src/vision/reasoning.ts +2 -4
  257. package/src/web-search/xai-executor.ts +14 -4
  258. package/gui/dist/assets/App-BqrsSrIR.js +0 -50
  259. package/gui/dist/assets/index-C6SJrh0N.js +0 -86
  260. package/gui/dist/assets/index-DdDunwDb.css +0 -1
  261. package/gui/dist/assets/usage-companion-chart-CzAAAB1o.js +0 -1
  262. package/src/adapters/kiro-thinking.ts +0 -112
@@ -0,0 +1,597 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ namespacedToolName,
4
+ toolChoiceToolPredicate,
5
+ type OcxParsedRequest,
6
+ type OcxTool,
7
+ type OcxToolChoice,
8
+ } from "../../types";
9
+ import { stripResponsesOnlyEncryptedMarker } from "../responses-tool-schema";
10
+
11
+ export const CODEBUDDY_MCP_SERVER_NAME = "opencodex";
12
+ export const CODEBUDDY_MCP_TOOL_PREFIX = `mcp__${CODEBUDDY_MCP_SERVER_NAME}__`;
13
+
14
+ // These caps protect both the request path and the isolated MCP process. They sit
15
+ // below the adapter's 4 MiB total prompt cap so a maximal tool catalog cannot
16
+ // crowd the transcript and system prompt out of the request budget.
17
+ export const CODEBUDDY_TOOL_LIMITS = Object.freeze({
18
+ maxTools: 128,
19
+ // Captured tool_use blocks accepted in a single assistant turn. Kimi emits
20
+ // parallel calls as sibling content blocks of one assistant message, all
21
+ // streamed before message_stop; the capture-only MCP handler never returns,
22
+ // so every block must be observed before the parent terminates the turn.
23
+ // Each captured call is fully buffered under the per-call translator
24
+ // budget, so this bound also caps per-turn capture memory.
25
+ maxTurnToolCalls: 16,
26
+ maxNameBytes: 512,
27
+ maxDescriptionBytes: 64 * 1024,
28
+ maxSchemaBytes: 224 * 1024,
29
+ maxToolBytes: 256 * 1024,
30
+ maxCatalogBytes: 2 * 1024 * 1024,
31
+ maxSchemaDepth: 32,
32
+ maxSchemaNodes: 4_096,
33
+ maxPatternBytes: 8 * 1024,
34
+ });
35
+
36
+ // CodeBuddy renders MCP tools as `mcp__<server>__<tool>`. Keep the complete
37
+ // rendered name comfortably below the common 64-character function-name limit.
38
+ const MAX_CODEBUDDY_TOOL_ALIAS_CHARS = 40;
39
+ const CODEBUDDY_TOOL_ALIAS_HASH_CHARS = 16;
40
+ const CODEBUDDY_TOOL_ALIAS_PATTERN = /^[A-Za-z0-9_-]+$/;
41
+ const INVALID_TOOL_NAME_PATTERN = /[\s\u0000-\u001f\u007f]/u;
42
+ const INVALID_DESCRIPTION_CONTROL_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
43
+ const JSON_SCHEMA_TYPES = new Set(["array", "boolean", "integer", "null", "number", "object", "string"]);
44
+ const SCHEMA_MAP_KEYWORDS = [
45
+ "properties",
46
+ "patternProperties",
47
+ "$defs",
48
+ "definitions",
49
+ "dependentSchemas",
50
+ ] as const;
51
+ const SCHEMA_VALUE_KEYWORDS = [
52
+ "additionalItems",
53
+ "additionalProperties",
54
+ "contains",
55
+ "contentSchema",
56
+ "else",
57
+ "if",
58
+ "not",
59
+ "propertyNames",
60
+ "then",
61
+ "unevaluatedItems",
62
+ "unevaluatedProperties",
63
+ ] as const;
64
+ const SCHEMA_ARRAY_KEYWORDS = ["allOf", "anyOf", "oneOf", "prefixItems"] as const;
65
+ const NON_NEGATIVE_INTEGER_KEYWORDS = [
66
+ "maxContains",
67
+ "maxItems",
68
+ "maxLength",
69
+ "maxProperties",
70
+ "minContains",
71
+ "minItems",
72
+ "minLength",
73
+ "minProperties",
74
+ ] as const;
75
+ const FINITE_NUMBER_KEYWORDS = [
76
+ "exclusiveMaximum",
77
+ "exclusiveMinimum",
78
+ "maximum",
79
+ "minimum",
80
+ ] as const;
81
+ const STRING_KEYWORDS = [
82
+ "$anchor",
83
+ "$comment",
84
+ "$id",
85
+ "$schema",
86
+ "$dynamicAnchor",
87
+ "contentEncoding",
88
+ "contentMediaType",
89
+ "description",
90
+ "format",
91
+ "title",
92
+ ] as const;
93
+ const BOOLEAN_KEYWORDS = ["deprecated", "nullable", "readOnly", "uniqueItems", "writeOnly"] as const;
94
+ const textEncoder = new TextEncoder();
95
+
96
+ export interface CodeBuddyMcpToolDefinition {
97
+ name: string;
98
+ description: string;
99
+ inputSchema: Record<string, unknown>;
100
+ }
101
+
102
+ export interface CodeBuddyToolBridge {
103
+ tools: CodeBuddyMcpToolDefinition[];
104
+ /** Exact nested-CLI-emitted MCP name -> Responses wire name. */
105
+ emittedNameMap: Map<string, string>;
106
+ requireToolCall: boolean;
107
+ }
108
+
109
+ interface PreparedTool {
110
+ source: OcxTool;
111
+ wireName: string;
112
+ description: string;
113
+ inputSchema: Record<string, unknown>;
114
+ }
115
+
116
+ interface JsonCloneState {
117
+ active: WeakSet<object>;
118
+ nodes: number;
119
+ }
120
+
121
+ function utf8Bytes(value: string): number {
122
+ return textEncoder.encode(value).byteLength;
123
+ }
124
+
125
+ function serializedBytes(value: unknown): number {
126
+ return utf8Bytes(JSON.stringify(value));
127
+ }
128
+
129
+ function hasUnpairedSurrogate(value: string): boolean {
130
+ for (let index = 0; index < value.length; index++) {
131
+ const unit = value.charCodeAt(index);
132
+ if (unit >= 0xd800 && unit <= 0xdbff) {
133
+ const next = value.charCodeAt(index + 1);
134
+ if (next < 0xdc00 || next > 0xdfff) return true;
135
+ index += 1;
136
+ } else if (unit >= 0xdc00 && unit <= 0xdfff) {
137
+ return true;
138
+ }
139
+ }
140
+ return false;
141
+ }
142
+
143
+ function isRecord(value: unknown): value is Record<string, unknown> {
144
+ return value !== null && typeof value === "object" && !Array.isArray(value);
145
+ }
146
+
147
+ function defineDataProperty(target: Record<string, unknown>, key: string, value: unknown): void {
148
+ // `__proto__` is a valid JSON Schema property name. Defining it as data keeps
149
+ // it from invoking Object.prototype's legacy setter while retaining a normal
150
+ // object prototype for downstream SDKs.
151
+ Object.defineProperty(target, key, {
152
+ configurable: true,
153
+ enumerable: true,
154
+ value,
155
+ writable: true,
156
+ });
157
+ }
158
+
159
+ function invalidJson(reason: string): never {
160
+ throw new Error(`invalid JSON value (${reason})`);
161
+ }
162
+
163
+ /**
164
+ * Clone one schema into inert JSON data. A bounded recursive walk is safe here:
165
+ * the depth check happens before descent, and the resulting maximum call depth
166
+ * is fixed rather than attacker-controlled.
167
+ */
168
+ function cloneBoundedJson(value: unknown, depth: number, state: JsonCloneState): unknown {
169
+ if (depth > CODEBUDDY_TOOL_LIMITS.maxSchemaDepth) invalidJson("nesting is too deep");
170
+ state.nodes += 1;
171
+ if (state.nodes > CODEBUDDY_TOOL_LIMITS.maxSchemaNodes) invalidJson("node count is too large");
172
+
173
+ if (value === null || typeof value === "boolean" || typeof value === "string") {
174
+ if (typeof value === "string" && hasUnpairedSurrogate(value)) invalidJson("text contains an unpaired surrogate");
175
+ return value;
176
+ }
177
+ if (typeof value === "number") {
178
+ if (!Number.isFinite(value)) invalidJson("numbers must be finite");
179
+ return value;
180
+ }
181
+ if (typeof value !== "object") invalidJson(`unsupported ${typeof value}`);
182
+
183
+ const object = value as object;
184
+ if (state.active.has(object)) invalidJson("cycles are not allowed");
185
+ state.active.add(object);
186
+ try {
187
+ if (Array.isArray(value)) {
188
+ if (Object.getPrototypeOf(value) !== Array.prototype) invalidJson("arrays must use the built-in prototype");
189
+ if (value.length > CODEBUDDY_TOOL_LIMITS.maxSchemaNodes) invalidJson("array length is too large");
190
+
191
+ const keys = Reflect.ownKeys(value);
192
+ for (const key of keys) {
193
+ if (key === "length") continue;
194
+ if (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/.test(key)) {
195
+ invalidJson("arrays may not have custom properties");
196
+ }
197
+ const index = Number(key);
198
+ if (!Number.isSafeInteger(index) || index < 0 || index >= value.length) {
199
+ invalidJson("array index is invalid");
200
+ }
201
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
202
+ if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) {
203
+ invalidJson("array entries must be enumerable data properties");
204
+ }
205
+ }
206
+ if (keys.length - 1 !== value.length) invalidJson("sparse arrays are not allowed");
207
+
208
+ return value.map(entry => cloneBoundedJson(entry, depth + 1, state));
209
+ }
210
+
211
+ const prototype = Object.getPrototypeOf(value);
212
+ if (prototype !== Object.prototype && prototype !== null) invalidJson("objects must be plain records");
213
+ const out: Record<string, unknown> = {};
214
+ for (const key of Reflect.ownKeys(value)) {
215
+ if (typeof key !== "string") invalidJson("symbol keys are not allowed");
216
+ if (hasUnpairedSurrogate(key)) invalidJson("property name contains an unpaired surrogate");
217
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
218
+ if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) {
219
+ invalidJson("object fields must be enumerable data properties");
220
+ }
221
+ defineDataProperty(out, key, cloneBoundedJson(descriptor.value, depth + 1, state));
222
+ }
223
+ return out;
224
+ } finally {
225
+ state.active.delete(object);
226
+ }
227
+ }
228
+
229
+ function invalidSchema(reason: string): never {
230
+ throw new Error(reason);
231
+ }
232
+
233
+ function assertStringArray(value: unknown, keyword: string, allowEmpty = true): string[] {
234
+ if (!Array.isArray(value) || (!allowEmpty && value.length === 0)) {
235
+ invalidSchema(`${keyword} must be ${allowEmpty ? "an" : "a non-empty"} array of unique strings`);
236
+ }
237
+ const seen = new Set<string>();
238
+ for (const item of value) {
239
+ if (typeof item !== "string" || seen.has(item)) {
240
+ invalidSchema(`${keyword} must be ${allowEmpty ? "an" : "a non-empty"} array of unique strings`);
241
+ }
242
+ seen.add(item);
243
+ }
244
+ return value as string[];
245
+ }
246
+
247
+ function assertSchema(value: unknown, keyword: string): void {
248
+ if (typeof value === "boolean") return;
249
+ if (!isRecord(value)) invalidSchema(`${keyword} must contain a JSON Schema`);
250
+ validateSchema(value);
251
+ }
252
+
253
+ function validateSchemaMap(value: unknown, keyword: string, validatePatterns = false): void {
254
+ if (!isRecord(value)) invalidSchema(`${keyword} must be an object of JSON Schemas`);
255
+ for (const [name, schema] of Object.entries(value)) {
256
+ if (validatePatterns) validatePattern(name, `${keyword} key`);
257
+ assertSchema(schema, `${keyword}.${name}`);
258
+ }
259
+ }
260
+
261
+ function validatePattern(value: unknown, keyword = "pattern"): void {
262
+ if (typeof value !== "string" || utf8Bytes(value) > CODEBUDDY_TOOL_LIMITS.maxPatternBytes) {
263
+ invalidSchema(`${keyword} must be a bounded regular-expression string`);
264
+ }
265
+ try {
266
+ new RegExp(value, "u");
267
+ } catch {
268
+ invalidSchema(`${keyword} is not a valid regular expression`);
269
+ }
270
+ }
271
+
272
+ function validateSchema(schema: Record<string, unknown>): void {
273
+ if (Object.hasOwn(schema, "type")) {
274
+ const type = schema.type;
275
+ if (typeof type === "string") {
276
+ if (!JSON_SCHEMA_TYPES.has(type)) invalidSchema("type contains an unknown JSON Schema type");
277
+ } else {
278
+ const types = assertStringArray(type, "type", false);
279
+ if (types.some(candidate => !JSON_SCHEMA_TYPES.has(candidate))) {
280
+ invalidSchema("type contains an unknown JSON Schema type");
281
+ }
282
+ }
283
+ }
284
+
285
+ for (const keyword of SCHEMA_MAP_KEYWORDS) {
286
+ if (Object.hasOwn(schema, keyword)) {
287
+ validateSchemaMap(schema[keyword], keyword, keyword === "patternProperties");
288
+ }
289
+ }
290
+ for (const keyword of SCHEMA_VALUE_KEYWORDS) {
291
+ if (Object.hasOwn(schema, keyword)) assertSchema(schema[keyword], keyword);
292
+ }
293
+ for (const keyword of SCHEMA_ARRAY_KEYWORDS) {
294
+ if (!Object.hasOwn(schema, keyword)) continue;
295
+ const value = schema[keyword];
296
+ if (!Array.isArray(value) || (keyword !== "prefixItems" && value.length === 0)) {
297
+ invalidSchema(`${keyword} must be an array of JSON Schemas${keyword === "prefixItems" ? "" : " with at least one entry"}`);
298
+ }
299
+ for (const entry of value) assertSchema(entry, keyword);
300
+ }
301
+
302
+ if (Object.hasOwn(schema, "items")) {
303
+ const items = schema.items;
304
+ if (Array.isArray(items)) {
305
+ for (const entry of items) assertSchema(entry, "items");
306
+ } else {
307
+ assertSchema(items, "items");
308
+ }
309
+ }
310
+ if (Object.hasOwn(schema, "required")) assertStringArray(schema.required, "required");
311
+ if (Object.hasOwn(schema, "enum")) {
312
+ if (!Array.isArray(schema.enum) || schema.enum.length === 0) invalidSchema("enum must be a non-empty array");
313
+ }
314
+ if (Object.hasOwn(schema, "examples") && !Array.isArray(schema.examples)) {
315
+ invalidSchema("examples must be an array");
316
+ }
317
+
318
+ for (const keyword of NON_NEGATIVE_INTEGER_KEYWORDS) {
319
+ if (!Object.hasOwn(schema, keyword)) continue;
320
+ const value = schema[keyword];
321
+ if (!Number.isSafeInteger(value) || (value as number) < 0) {
322
+ invalidSchema(`${keyword} must be a non-negative safe integer`);
323
+ }
324
+ }
325
+ for (const keyword of FINITE_NUMBER_KEYWORDS) {
326
+ if (!Object.hasOwn(schema, keyword)) continue;
327
+ if (typeof schema[keyword] !== "number" || !Number.isFinite(schema[keyword])) {
328
+ invalidSchema(`${keyword} must be a finite number`);
329
+ }
330
+ }
331
+ if (Object.hasOwn(schema, "multipleOf")) {
332
+ if (typeof schema.multipleOf !== "number" || !Number.isFinite(schema.multipleOf) || schema.multipleOf <= 0) {
333
+ invalidSchema("multipleOf must be a finite number greater than zero");
334
+ }
335
+ }
336
+
337
+ for (const keyword of STRING_KEYWORDS) {
338
+ if (Object.hasOwn(schema, keyword) && typeof schema[keyword] !== "string") {
339
+ invalidSchema(`${keyword} must be a string`);
340
+ }
341
+ }
342
+ for (const keyword of BOOLEAN_KEYWORDS) {
343
+ if (Object.hasOwn(schema, keyword) && typeof schema[keyword] !== "boolean") {
344
+ invalidSchema(`${keyword} must be a boolean`);
345
+ }
346
+ }
347
+ if (Object.hasOwn(schema, "pattern")) validatePattern(schema.pattern);
348
+
349
+ for (const keyword of ["$ref", "$dynamicRef"] as const) {
350
+ if (!Object.hasOwn(schema, keyword)) continue;
351
+ const ref = schema[keyword];
352
+ // External references hand resolution authority to the nested runtime and
353
+ // can turn a data-only catalog into network or filesystem access. Local
354
+ // JSON Pointer/anchor references retain recursive and reusable schemas.
355
+ if (typeof ref !== "string" || !ref.startsWith("#")) {
356
+ invalidSchema(`${keyword} must be a local fragment reference`);
357
+ }
358
+ }
359
+
360
+ if (Object.hasOwn(schema, "$vocabulary")) {
361
+ if (!isRecord(schema.$vocabulary)) invalidSchema("$vocabulary must be an object");
362
+ for (const enabled of Object.values(schema.$vocabulary)) {
363
+ if (typeof enabled !== "boolean") invalidSchema("$vocabulary values must be booleans");
364
+ }
365
+ }
366
+ if (Object.hasOwn(schema, "dependentRequired")) {
367
+ if (!isRecord(schema.dependentRequired)) invalidSchema("dependentRequired must be an object");
368
+ for (const [name, required] of Object.entries(schema.dependentRequired)) {
369
+ assertStringArray(required, `dependentRequired.${name}`);
370
+ }
371
+ }
372
+ if (Object.hasOwn(schema, "dependencies")) {
373
+ if (!isRecord(schema.dependencies)) invalidSchema("dependencies must be an object");
374
+ for (const [name, dependency] of Object.entries(schema.dependencies)) {
375
+ if (Array.isArray(dependency)) assertStringArray(dependency, `dependencies.${name}`);
376
+ else assertSchema(dependency, `dependencies.${name}`);
377
+ }
378
+ }
379
+
380
+ for (const [minimum, maximum] of [
381
+ ["minContains", "maxContains"],
382
+ ["minItems", "maxItems"],
383
+ ["minLength", "maxLength"],
384
+ ["minProperties", "maxProperties"],
385
+ ] as const) {
386
+ if (
387
+ typeof schema[minimum] === "number"
388
+ && typeof schema[maximum] === "number"
389
+ && schema[minimum] > schema[maximum]
390
+ ) {
391
+ invalidSchema(`${minimum} must not exceed ${maximum}`);
392
+ }
393
+ }
394
+ if (
395
+ typeof schema.minimum === "number"
396
+ && typeof schema.maximum === "number"
397
+ && schema.minimum > schema.maximum
398
+ ) {
399
+ invalidSchema("minimum must not exceed maximum");
400
+ }
401
+ }
402
+
403
+ function normalizeInputSchema(parameters: unknown): Record<string, unknown> {
404
+ if (!isRecord(parameters)) invalidSchema("the root must be an object schema");
405
+ const cloned = cloneBoundedJson(parameters, 0, { active: new WeakSet(), nodes: 0 });
406
+ if (!isRecord(cloned)) invalidSchema("the root must be an object schema");
407
+ if (serializedBytes(cloned) > CODEBUDDY_TOOL_LIMITS.maxSchemaBytes) {
408
+ throw new Error(`schema exceeds ${CODEBUDDY_TOOL_LIMITS.maxSchemaBytes} bytes`);
409
+ }
410
+ validateSchema(cloned);
411
+ if (Object.hasOwn(cloned, "type") && cloned.type !== "object") {
412
+ invalidSchema('the root type must be "object"');
413
+ }
414
+
415
+ const stripped = stripResponsesOnlyEncryptedMarker(cloned);
416
+ if (!isRecord(stripped)) invalidSchema("the root must remain an object schema");
417
+ if (!Object.hasOwn(stripped, "type")) stripped.type = "object";
418
+ if (serializedBytes(stripped) > CODEBUDDY_TOOL_LIMITS.maxSchemaBytes) {
419
+ throw new Error(`schema exceeds ${CODEBUDDY_TOOL_LIMITS.maxSchemaBytes} bytes`);
420
+ }
421
+ return stripped;
422
+ }
423
+
424
+ function shortHash(value: string, salt = 0): string {
425
+ return createHash("sha256")
426
+ .update(salt === 0 ? value : `${value}\0${salt}`)
427
+ .digest("hex")
428
+ .slice(0, CODEBUDDY_TOOL_ALIAS_HASH_CHARS);
429
+ }
430
+
431
+ function directCodeBuddyAlias(wireName: string): string | undefined {
432
+ return CODEBUDDY_TOOL_ALIAS_PATTERN.test(wireName)
433
+ && wireName.length <= MAX_CODEBUDDY_TOOL_ALIAS_CHARS
434
+ ? wireName
435
+ : undefined;
436
+ }
437
+
438
+ /**
439
+ * Produce a deterministic MCP-safe alias while retaining a readable prefix.
440
+ * `used` closes both normalization and truncated-hash collision domains.
441
+ */
442
+ export function codeBuddyToolAlias(wireName: string, used = new Set<string>()): string {
443
+ const direct = directCodeBuddyAlias(wireName);
444
+ if (direct && !used.has(direct)) {
445
+ used.add(direct);
446
+ return direct;
447
+ }
448
+
449
+ const cleaned = wireName.replace(/[^A-Za-z0-9_-]/g, "_");
450
+ const maxBaseChars = MAX_CODEBUDDY_TOOL_ALIAS_CHARS - CODEBUDDY_TOOL_ALIAS_HASH_CHARS - 1;
451
+ const base = (cleaned || "tool").slice(0, maxBaseChars);
452
+ for (let salt = 0; salt <= CODEBUDDY_TOOL_LIMITS.maxTools; salt++) {
453
+ const candidate = `${base}_${shortHash(wireName, salt)}`;
454
+ if (!used.has(candidate)) {
455
+ used.add(candidate);
456
+ return candidate;
457
+ }
458
+ }
459
+ throw new Error("CodeBuddy could not allocate a collision-free tool alias.");
460
+ }
461
+
462
+ /** Reserve direct names before hashing and sort the rest so request ordering cannot change aliases. */
463
+ function codeBuddyToolAliases(wireNames: readonly string[]): Map<string, string> {
464
+ const aliases = new Map<string, string>();
465
+ const used = new Set<string>();
466
+ for (const wireName of wireNames) {
467
+ const direct = directCodeBuddyAlias(wireName);
468
+ if (direct) {
469
+ aliases.set(wireName, direct);
470
+ used.add(direct);
471
+ }
472
+ }
473
+ const hashedNames = wireNames.filter(wireName => !aliases.has(wireName)).sort();
474
+ for (const wireName of hashedNames) aliases.set(wireName, codeBuddyToolAlias(wireName, used));
475
+ return aliases;
476
+ }
477
+
478
+ function requiresToolCall(choice: OcxToolChoice | undefined): boolean {
479
+ return choice === "required"
480
+ || (typeof choice === "object" && choice !== null && (
481
+ "name" in choice || ("mode" in choice && choice.mode === "required")
482
+ ));
483
+ }
484
+
485
+ function validateToolNamePart(value: unknown): value is string {
486
+ return typeof value === "string"
487
+ && value.length > 0
488
+ && !hasUnpairedSurrogate(value)
489
+ && !INVALID_TOOL_NAME_PATTERN.test(value);
490
+ }
491
+
492
+ function prepareTool(tool: OcxTool, index: number, seenWireNames: Set<string>): PreparedTool {
493
+ if (!tool || typeof tool !== "object") throw new Error(`CodeBuddy tool ${index + 1} is not an object.`);
494
+ if (!validateToolNamePart(tool.name) || (
495
+ tool.namespace !== undefined && !validateToolNamePart(tool.namespace)
496
+ )) {
497
+ throw new Error(`CodeBuddy tool ${index + 1} has an invalid name or namespace.`);
498
+ }
499
+ const wireName = namespacedToolName(tool.namespace, tool.name);
500
+ if (utf8Bytes(wireName) > CODEBUDDY_TOOL_LIMITS.maxNameBytes) {
501
+ throw new Error(`CodeBuddy tool ${index + 1} name exceeds ${CODEBUDDY_TOOL_LIMITS.maxNameBytes} bytes.`);
502
+ }
503
+ if (seenWireNames.has(wireName)) {
504
+ throw new Error(`CodeBuddy tool catalog contains a duplicate wire name: ${wireName}.`);
505
+ }
506
+ seenWireNames.add(wireName);
507
+
508
+ if (typeof tool.description !== "string" || hasUnpairedSurrogate(tool.description)
509
+ || INVALID_DESCRIPTION_CONTROL_PATTERN.test(tool.description)) {
510
+ throw new Error(`CodeBuddy tool ${index + 1} has an invalid description.`);
511
+ }
512
+ const description = tool.description || `Tool: ${wireName}`;
513
+ if (utf8Bytes(description) > CODEBUDDY_TOOL_LIMITS.maxDescriptionBytes) {
514
+ throw new Error(`CodeBuddy tool ${index + 1} description exceeds ${CODEBUDDY_TOOL_LIMITS.maxDescriptionBytes} bytes.`);
515
+ }
516
+
517
+ let inputSchema: Record<string, unknown>;
518
+ try {
519
+ inputSchema = normalizeInputSchema(tool.parameters ?? {});
520
+ } catch (error) {
521
+ const detail = error instanceof Error ? error.message : "unknown schema error";
522
+ throw new Error(`CodeBuddy tool ${index + 1} has an invalid input schema: ${detail}.`);
523
+ }
524
+ return { source: tool, wireName, description, inputSchema };
525
+ }
526
+
527
+ function buildToolBridge(parsed: OcxParsedRequest): CodeBuddyToolBridge {
528
+ const allTools = parsed.context.tools ?? [];
529
+ if (!Array.isArray(allTools)) throw new Error("CodeBuddy tool catalog must be an array.");
530
+
531
+ const choice = parsed.options.toolChoice;
532
+ const requireToolCall = requiresToolCall(choice);
533
+ // `none` is an authorization decision, so do not traverse or validate a
534
+ // catalog that the nested CLI must never see. Besides avoiding needless
535
+ // work, this prevents an unselected malformed or oversized definition from
536
+ // turning an explicitly tool-free request into a local adapter failure.
537
+ if (choice === "none") {
538
+ return { tools: [], emittedNameMap: new Map(), requireToolCall: false };
539
+ }
540
+
541
+ // Named and allowed-tools choices still need the complete identity view to
542
+ // reject ambiguous shorthand, but schema/description/size validation belongs
543
+ // only to definitions that can actually be advertised. `auto`/`required`
544
+ // select the whole catalog and therefore retain the original full boundary.
545
+ // Non-object entries have no selectable identity. Ignore them for a selective
546
+ // choice; if the choice names nothing else, the required-choice check below
547
+ // still fails closed. Unfiltered modes retain them so prepareTool rejects the
548
+ // malformed catalog as before.
549
+ const identityCatalog = typeof choice === "object" && choice !== null
550
+ ? allTools.filter(tool => tool !== null && typeof tool === "object")
551
+ : allTools;
552
+ const allows = toolChoiceToolPredicate(choice, identityCatalog);
553
+ const selected = identityCatalog
554
+ .map((tool, index) => ({ index, tool }))
555
+ .filter(({ tool }) => allows(tool));
556
+ if (requireToolCall && selected.length === 0) {
557
+ throw new Error("CodeBuddy tool_choice requires a tool, but no matching tool is available.");
558
+ }
559
+ if (selected.length > CODEBUDDY_TOOL_LIMITS.maxTools) {
560
+ throw new Error(`CodeBuddy tool catalog exceeds the ${CODEBUDDY_TOOL_LIMITS.maxTools}-tool limit.`);
561
+ }
562
+
563
+ const seenWireNames = new Set<string>();
564
+ const prepared = selected.map(({ index, tool }) => prepareTool(tool, index, seenWireNames));
565
+ const aliases = codeBuddyToolAliases(prepared.map(tool => tool.wireName));
566
+ const definitions = prepared.map((tool, index): CodeBuddyMcpToolDefinition => {
567
+ const definition = {
568
+ name: aliases.get(tool.wireName)!,
569
+ description: tool.description,
570
+ inputSchema: tool.inputSchema,
571
+ };
572
+ if (serializedBytes(definition) > CODEBUDDY_TOOL_LIMITS.maxToolBytes) {
573
+ throw new Error(`CodeBuddy tool ${index + 1} definition exceeds ${CODEBUDDY_TOOL_LIMITS.maxToolBytes} bytes.`);
574
+ }
575
+ return definition;
576
+ });
577
+ if (serializedBytes(definitions) > CODEBUDDY_TOOL_LIMITS.maxCatalogBytes) {
578
+ throw new Error(`CodeBuddy tool catalog exceeds ${CODEBUDDY_TOOL_LIMITS.maxCatalogBytes} bytes.`);
579
+ }
580
+
581
+ const emittedNameMap = new Map<string, string>();
582
+ const tools = prepared.map((preparedTool, index) => {
583
+ const definition = definitions[index];
584
+ const emittedName = `${CODEBUDDY_MCP_TOOL_PREFIX}${definition.name}`;
585
+ if (emittedNameMap.has(emittedName)) {
586
+ throw new Error("CodeBuddy tool catalog contains a colliding emitted alias.");
587
+ }
588
+ emittedNameMap.set(emittedName, preparedTool.wireName);
589
+ return definition;
590
+ });
591
+
592
+ return { tools, emittedNameMap, requireToolCall };
593
+ }
594
+
595
+ export function buildCodeBuddyToolBridge(parsed: OcxParsedRequest): CodeBuddyToolBridge {
596
+ return buildToolBridge(parsed);
597
+ }