@amir83nasr/10router 0.1.4 → 0.1.6

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 (198) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +7 -7
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/required-server-files.json +5 -0
  6. package/.next/server/app/(dashboard)/dashboard/basic-chat/page_client-reference-manifest.js +1 -1
  7. package/.next/server/app/(dashboard)/dashboard/cli-tools/[toolId]/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
  10. package/.next/server/app/(dashboard)/dashboard/console-log/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
  12. package/.next/server/app/(dashboard)/dashboard/media-providers/systemone/[id]/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/(dashboard)/dashboard/media-providers/systemone/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
  15. package/.next/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
  19. package/.next/server/app/(dashboard)/dashboard/proxy-pools/page_client-reference-manifest.js +1 -1
  20. package/.next/server/app/(dashboard)/dashboard/pxpipe/page_client-reference-manifest.js +1 -1
  21. package/.next/server/app/(dashboard)/dashboard/quota/page_client-reference-manifest.js +1 -1
  22. package/.next/server/app/(dashboard)/dashboard/token-saver/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
  24. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/_global-error.html +1 -1
  26. package/.next/server/app/_global-error.rsc +1 -1
  27. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  28. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  29. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  30. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  31. package/.next/server/app/_not-found.html +1 -1
  32. package/.next/server/app/_not-found.rsc +5 -5
  33. package/.next/server/app/_not-found.segments/_full.segment.rsc +5 -5
  34. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -5
  35. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  36. package/.next/server/app/api/changelog/route.js +1 -1
  37. package/.next/server/app/api/cli-tools/all-statuses/route.js +1 -1
  38. package/.next/server/app/api/cli-tools/claude-settings/route.js +1 -1
  39. package/.next/server/app/api/cli-tools/codex-settings/route.js +1 -1
  40. package/.next/server/app/api/cli-tools/cowork-mcp-registry/route.js +1 -1
  41. package/.next/server/app/api/cli-tools/cowork-mcp-tools/route.js +1 -1
  42. package/.next/server/app/api/cli-tools/cowork-settings/route.js +1 -1
  43. package/.next/server/app/api/cli-tools/dsh-settings/route.js +1 -1
  44. package/.next/server/app/api/cli-tools/opencode-settings/route.js +2 -2
  45. package/.next/server/app/api/oauth/[provider]/[action]/route.js +1 -1
  46. package/.next/server/app/api/oauth/codex/bulk-import/route.js +1 -1
  47. package/.next/server/app/api/oauth/codex/import-token/route.js +1 -1
  48. package/.next/server/app/api/providers/client/route.js +1 -1
  49. package/.next/server/app/api/translator/send/route.js +1 -1
  50. package/.next/server/app/api/translator/translate/route.js +1 -1
  51. package/.next/server/app/api/tunnel/tailscale-install/route.js +2 -2
  52. package/.next/server/app/api/usage/[connectionId]/codex-reset-credits/route.js +1 -1
  53. package/.next/server/app/api/usage/[connectionId]/route.js +1 -1
  54. package/.next/server/app/api/version/route.js +1 -1
  55. package/.next/server/app/callback/page_client-reference-manifest.js +1 -1
  56. package/.next/server/app/callback.html +1 -1
  57. package/.next/server/app/callback.rsc +6 -6
  58. package/.next/server/app/callback.segments/_full.segment.rsc +6 -6
  59. package/.next/server/app/callback.segments/_tree.segment.rsc +1 -1
  60. package/.next/server/app/callback.segments/callback/__PAGE__.segment.rsc +6 -6
  61. package/.next/server/app/dashboard/basic-chat.html +1 -1
  62. package/.next/server/app/dashboard/basic-chat.rsc +7 -7
  63. package/.next/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard/basic-chat/__PAGE__.segment.rsc +7 -7
  64. package/.next/server/app/dashboard/basic-chat.segments/_full.segment.rsc +7 -7
  65. package/.next/server/app/dashboard/basic-chat.segments/_tree.segment.rsc +1 -1
  66. package/.next/server/app/dashboard/cli-tools.html +1 -1
  67. package/.next/server/app/dashboard/cli-tools.rsc +7 -7
  68. package/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard/cli-tools/__PAGE__.segment.rsc +7 -7
  69. package/.next/server/app/dashboard/cli-tools.segments/_full.segment.rsc +7 -7
  70. package/.next/server/app/dashboard/cli-tools.segments/_tree.segment.rsc +1 -1
  71. package/.next/server/app/dashboard/combos.html +1 -1
  72. package/.next/server/app/dashboard/combos.rsc +7 -7
  73. package/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard/combos/__PAGE__.segment.rsc +7 -7
  74. package/.next/server/app/dashboard/combos.segments/_full.segment.rsc +7 -7
  75. package/.next/server/app/dashboard/combos.segments/_tree.segment.rsc +1 -1
  76. package/.next/server/app/dashboard/endpoint.html +1 -1
  77. package/.next/server/app/dashboard/endpoint.rsc +7 -7
  78. package/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard/endpoint/__PAGE__.segment.rsc +7 -7
  79. package/.next/server/app/dashboard/endpoint.segments/_full.segment.rsc +7 -7
  80. package/.next/server/app/dashboard/endpoint.segments/_tree.segment.rsc +1 -1
  81. package/.next/server/app/dashboard/media-providers/systemone.html +1 -1
  82. package/.next/server/app/dashboard/media-providers/systemone.rsc +7 -7
  83. package/.next/server/app/dashboard/media-providers/systemone.segments/!KGRhc2hib2FyZCk/dashboard/media-providers/systemone/__PAGE__.segment.rsc +7 -7
  84. package/.next/server/app/dashboard/media-providers/systemone.segments/_full.segment.rsc +7 -7
  85. package/.next/server/app/dashboard/media-providers/systemone.segments/_tree.segment.rsc +1 -1
  86. package/.next/server/app/dashboard/profile.html +1 -1
  87. package/.next/server/app/dashboard/profile.rsc +7 -7
  88. package/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard/profile/__PAGE__.segment.rsc +7 -7
  89. package/.next/server/app/dashboard/profile.segments/_full.segment.rsc +7 -7
  90. package/.next/server/app/dashboard/profile.segments/_tree.segment.rsc +1 -1
  91. package/.next/server/app/dashboard/providers/new.html +1 -1
  92. package/.next/server/app/dashboard/providers/new.rsc +7 -7
  93. package/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers/new/__PAGE__.segment.rsc +7 -7
  94. package/.next/server/app/dashboard/providers/new.segments/_full.segment.rsc +7 -7
  95. package/.next/server/app/dashboard/providers/new.segments/_tree.segment.rsc +1 -1
  96. package/.next/server/app/dashboard/providers.html +1 -1
  97. package/.next/server/app/dashboard/providers.rsc +7 -7
  98. package/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard/providers/__PAGE__.segment.rsc +7 -7
  99. package/.next/server/app/dashboard/providers.segments/_full.segment.rsc +7 -7
  100. package/.next/server/app/dashboard/providers.segments/_tree.segment.rsc +1 -1
  101. package/.next/server/app/dashboard/proxy-pools.html +1 -1
  102. package/.next/server/app/dashboard/proxy-pools.rsc +7 -7
  103. package/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard/proxy-pools/__PAGE__.segment.rsc +7 -7
  104. package/.next/server/app/dashboard/proxy-pools.segments/_full.segment.rsc +7 -7
  105. package/.next/server/app/dashboard/proxy-pools.segments/_tree.segment.rsc +1 -1
  106. package/.next/server/app/dashboard/pxpipe.html +1 -1
  107. package/.next/server/app/dashboard/pxpipe.rsc +6 -6
  108. package/.next/server/app/dashboard/pxpipe.segments/!KGRhc2hib2FyZCk/dashboard/pxpipe/__PAGE__.segment.rsc +6 -6
  109. package/.next/server/app/dashboard/pxpipe.segments/_full.segment.rsc +6 -6
  110. package/.next/server/app/dashboard/pxpipe.segments/_tree.segment.rsc +1 -1
  111. package/.next/server/app/dashboard/quota.html +2 -2
  112. package/.next/server/app/dashboard/quota.rsc +8 -8
  113. package/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard/quota/__PAGE__.segment.rsc +8 -8
  114. package/.next/server/app/dashboard/quota.segments/_full.segment.rsc +8 -8
  115. package/.next/server/app/dashboard/quota.segments/_tree.segment.rsc +1 -1
  116. package/.next/server/app/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
  117. package/.next/server/app/dashboard/settings/pricing.html +1 -1
  118. package/.next/server/app/dashboard/settings/pricing.rsc +5 -5
  119. package/.next/server/app/dashboard/settings/pricing.segments/_full.segment.rsc +5 -5
  120. package/.next/server/app/dashboard/settings/pricing.segments/_tree.segment.rsc +1 -1
  121. package/.next/server/app/dashboard/settings/pricing.segments/dashboard/settings/pricing/__PAGE__.segment.rsc +5 -5
  122. package/.next/server/app/dashboard/token-saver.html +1 -1
  123. package/.next/server/app/dashboard/token-saver.rsc +6 -6
  124. package/.next/server/app/dashboard/token-saver.segments/!KGRhc2hib2FyZCk/dashboard/token-saver/__PAGE__.segment.rsc +6 -6
  125. package/.next/server/app/dashboard/token-saver.segments/_full.segment.rsc +6 -6
  126. package/.next/server/app/dashboard/token-saver.segments/_tree.segment.rsc +1 -1
  127. package/.next/server/app/dashboard/usage.html +2 -2
  128. package/.next/server/app/dashboard/usage.rsc +7 -7
  129. package/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard/usage/__PAGE__.segment.rsc +7 -7
  130. package/.next/server/app/dashboard/usage.segments/_full.segment.rsc +7 -7
  131. package/.next/server/app/dashboard/usage.segments/_tree.segment.rsc +1 -1
  132. package/.next/server/app/dashboard.html +1 -1
  133. package/.next/server/app/dashboard.rsc +7 -7
  134. package/.next/server/app/dashboard.segments/!KGRhc2hib2FyZCk/dashboard/__PAGE__.segment.rsc +7 -7
  135. package/.next/server/app/dashboard.segments/_full.segment.rsc +7 -7
  136. package/.next/server/app/dashboard.segments/_tree.segment.rsc +1 -1
  137. package/.next/server/app/index.html +1 -1
  138. package/.next/server/app/index.rsc +5 -5
  139. package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
  140. package/.next/server/app/index.segments/_full.segment.rsc +5 -5
  141. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  142. package/.next/server/app/landing/page_client-reference-manifest.js +1 -1
  143. package/.next/server/app/landing.html +1 -1
  144. package/.next/server/app/landing.rsc +6 -6
  145. package/.next/server/app/landing.segments/_full.segment.rsc +6 -6
  146. package/.next/server/app/landing.segments/_tree.segment.rsc +1 -1
  147. package/.next/server/app/landing.segments/landing/__PAGE__.segment.rsc +6 -6
  148. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  149. package/.next/server/app/login.html +1 -1
  150. package/.next/server/app/login.rsc +5 -5
  151. package/.next/server/app/login.segments/_full.segment.rsc +5 -5
  152. package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  153. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +5 -5
  154. package/.next/server/app/page_client-reference-manifest.js +1 -1
  155. package/.next/server/app-paths-manifest.json +7 -7
  156. package/.next/server/chunks/1011.js +1 -1
  157. package/.next/server/chunks/1194.js +1 -1
  158. package/.next/server/chunks/1289.js +1 -1
  159. package/.next/server/chunks/1633.js +1 -1
  160. package/.next/server/chunks/2282.js +7 -7
  161. package/.next/server/chunks/2339.js +1 -1
  162. package/.next/server/chunks/2801.js +1 -1
  163. package/.next/server/chunks/3425.js +1 -1
  164. package/.next/server/chunks/537.js +5 -1
  165. package/.next/server/chunks/6056.js +1 -1
  166. package/.next/server/chunks/6343.js +6 -6
  167. package/.next/server/chunks/9429.js +1 -1
  168. package/.next/server/middleware-build-manifest.js +1 -1
  169. package/.next/server/pages/404.html +1 -1
  170. package/.next/server/pages/500.html +1 -1
  171. package/.next/server/server-reference-manifest.js +1 -1
  172. package/.next/server/server-reference-manifest.json +1 -1
  173. package/.next/static/chunks/1278-16cc8ebc3a4c8af5.js +1 -0
  174. package/.next/static/chunks/{1471-69a7c30abf98c656.js → 1471-10f225f62f7469cc.js} +1 -1
  175. package/.next/static/chunks/{1699-49be310981e2759e.js → 1699-13ee6864a8a31a96.js} +2 -2
  176. package/.next/static/chunks/{3577-1f02993330d7fba7.js → 3577-cd98d40e3099e1b0.js} +1 -1
  177. package/.next/static/chunks/{5862-6f3a3a6d4c00100d.js → 5862-f98c2f3d480b80d2.js} +1 -1
  178. package/.next/static/chunks/{9ce8c186-6cd204e9c27ecee1.js → 9ce8c186-3c7d7dbc5d428052.js} +1 -1
  179. package/.next/static/chunks/app/(dashboard)/dashboard/console-log/{page-c803a3376280f0e8.js → page-30d1f039dfdca294.js} +1 -1
  180. package/.next/static/chunks/app/(dashboard)/dashboard/providers/new/{page-cef3c079d15c9349.js → page-fc4d1c1291545177.js} +1 -1
  181. package/.next/static/chunks/app/{layout-101b832aa6789b60.js → layout-43bf918527d2f137.js} +1 -1
  182. package/CHANGELOG.md +66 -0
  183. package/next.config.mjs +5 -0
  184. package/open-sse/executors/opencode-zen.js +38 -98
  185. package/open-sse/executors/opencode.js +41 -198
  186. package/open-sse/handlers/chatCore/nonStreamingHandler.js +2 -1
  187. package/open-sse/handlers/chatCore/sseToJsonHandler.js +5 -3
  188. package/open-sse/handlers/chatCore.js +6 -0
  189. package/open-sse/translator/index.js +6 -2
  190. package/open-sse/utils/opencodeFingerprint.js +385 -0
  191. package/package.json +3 -2
  192. package/src/app/api/changelog/{route.js → route.ts} +2 -1
  193. package/src/app/api/version/route.js +1 -1
  194. package/src/lib/versionCheck.js +18 -16
  195. package/src/shared/components/UpdateDialog.tsx +1 -1
  196. package/.next/static/chunks/1278-ad2cb39d11b4a5f2.js +0 -1
  197. /package/.next/static/{nOEr8lKXMARlP2mV3XFuF → kcbgqbQp7PqJfusgfYenC}/_buildManifest.js +0 -0
  198. /package/.next/static/{nOEr8lKXMARlP2mV3XFuF → kcbgqbQp7PqJfusgfYenC}/_ssgManifest.js +0 -0
@@ -6,6 +6,7 @@ import { getThinkingLevels } from "../providers/thinkingLevels.js";
6
6
  import { injectReasoningContent } from "../utils/reasoningContentInjector.js";
7
7
  import { resolveSessionId } from "../utils/sessionManager.js";
8
8
  import { isMuseSparkModel } from "../providers/models/helpers.js";
9
+ import { applyFingerprintTools } from "../utils/opencodeFingerprint.js";
9
10
  import { ANTHROPIC_API_VERSION } from "../providers/shared.js";
10
11
  import {
11
12
  normalizeResponsesInput,
@@ -26,86 +27,22 @@ import {
26
27
  // `noauth` virtual connection (Authorization: Bearer public for everyone).
27
28
  const FREE_USAGE_BREAKER_KEY = "opencode-free";
28
29
 
29
- const OPENCODE_UA = "opencode/1.18.35";
30
+ // OpenCode v2 UA shape: opencode/<channel>/<version>/<client>. Upstream rejects
31
+ // the v1 "opencode/1.18.x" form with 403 FreeTierError since 2026-10-14.
32
+ const OPENCODE_UA = "opencode/latest/2.0.26/cli";
33
+ const OPENCODE_CLIENT = "cli";
30
34
  const MAX_SESSION_LENGTH = 256;
31
35
  const MAX_TOOL_NAME_LEN = 128;
32
36
  const SESSION_HEADER = "x-opencode-session";
33
37
  const SESSION_FIELD = "_opencodeSession";
34
- const REQ_FIELD = "_opencodeRequest";
35
38
  export const OPENCODE_SESSION_RE = /^ses_[0-9a-f]{12}[0-9A-Za-z]{14}$/;
36
- export const OPENCODE_REQUEST_RE = /^msg_[0-9a-f]{12}[0-9A-Za-z]{14}$/;
37
39
  const BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
38
40
 
39
- // OpenCode free tier requires both 'bash' and 'read' in tools payload.
40
- // Injected as cloaked decoy tools so external CLI tools (e.g. Claude Code's Bash/Read)
41
- // take precedence while satisfying upstream verification.
42
- const OPENCODE_DECOY_CHAT_TOOLS = [
43
- {
44
- type: "function",
45
- function: {
46
- name: "bash",
47
- description: "This tool is currently unavailable and must not be used.",
48
- parameters: { type: "object", properties: {} },
49
- },
50
- },
51
- {
52
- type: "function",
53
- function: {
54
- name: "read",
55
- description: "This tool is currently unavailable and must not be used.",
56
- parameters: { type: "object", properties: {} },
57
- },
58
- },
59
- ];
60
-
61
- const OPENCODE_DECOY_RESPONSES_TOOLS = [
62
- {
63
- type: "function",
64
- name: "bash",
65
- description: "This tool is currently unavailable and must not be used.",
66
- parameters: { type: "object", properties: {} },
67
- },
68
- {
69
- type: "function",
70
- name: "read",
71
- description: "This tool is currently unavailable and must not be used.",
72
- parameters: { type: "object", properties: {} },
73
- },
74
- ];
75
-
76
- function cloakOpencodeTools(body, isResponses) {
77
- if (!body || typeof body !== "object") return;
78
- if (isResponses) {
79
- if (!Array.isArray(body.tools)) body.tools = [];
80
- const names = new Set(body.tools.map((t) => t.name || t.function?.name));
81
- for (const tool of OPENCODE_DECOY_RESPONSES_TOOLS) {
82
- if (!names.has(tool.name)) body.tools.push({ ...tool });
83
- }
84
- if (!body.tool_choice) body.tool_choice = "auto";
85
- } else {
86
- const hasTools = Array.isArray(body.tools) && body.tools.length > 0;
87
- if (!hasTools) {
88
- body.tools = OPENCODE_DECOY_CHAT_TOOLS.map((t) => ({ ...t, function: { ...t.function } }));
89
- if (!body.tool_choice) body.tool_choice = "none";
90
- } else {
91
- const names = new Set(body.tools.map((t) => t.function?.name || t.name));
92
- for (const tool of OPENCODE_DECOY_CHAT_TOOLS) {
93
- if (!names.has(tool.function.name)) {
94
- body.tools.push({ ...tool, function: { ...tool.function } });
95
- }
96
- }
97
- }
98
- }
99
- }
100
-
101
41
  function hasValidOpencodeVersion(ua) {
102
- const m = String(ua || "").match(/opencode\/(\d+)\.(\d+)(?:\.(\d+))?/i);
103
- if (!m) return false;
104
- const major = parseInt(m[1], 10);
105
- const minor = parseInt(m[2], 10);
106
- return major > 1 || (major === 1 && minor >= 17);
42
+ const m = String(ua || "").match(/^opencode\/[a-z0-9-]+\/(\d+)\.\d+\.\d+\/[a-z0-9-]+$/i);
43
+ return !!m && parseInt(m[1], 10) >= 2;
107
44
  }
108
- // Models served by /zen/v1/responses; every other model stays on /chat/completions.
45
+ // Models served by /inference/v1/responses; every other model stays on /chat/completions.
109
46
  const RESPONSES_MODELS = new Set([
110
47
  "muse-spark-1.2-contributor-free",
111
48
  "muse-spark-1.3-contributor-free",
@@ -124,65 +61,43 @@ function unstableRandom() {
124
61
  return randomPart;
125
62
  }
126
63
 
64
+ // Session prefix = 12 hex of ~(timeMs * 0x1000 + counter), low 48 bits, like the CLI.
65
+ // Upstream decodes it and rejects a non-small counter with 403 FreeTierError
66
+ // (live probes: counter 0–16 pass, >=100 fail), so the counter must stay real.
67
+ const SESSION_PREFIX_MASK = (1n << 48n) - 1n;
68
+ const SESSION_TIME_MASK = (1n << 36n) - 1n;
69
+
70
+ function descendingTimeHex(time, counterValue) {
71
+ return (~(BigInt(time) * 0x1000n + BigInt(counterValue)) & SESSION_PREFIX_MASK)
72
+ .toString(16)
73
+ .padStart(12, "0");
74
+ }
75
+
127
76
  export function generateSessionId(timestamp = Date.now()) {
128
77
  if (timestamp !== lastTimestamp) {
129
78
  lastTimestamp = timestamp;
130
79
  counter = 0;
131
80
  }
132
81
  counter++;
133
-
134
- const current = BigInt(timestamp) * 0x1000n + BigInt(counter);
135
- const value = ~current;
136
- const time = Array.from({ length: 6 }, (_, index) =>
137
- Number((value >> BigInt(40 - 8 * index)) & 0xffn)
138
- .toString(16)
139
- .padStart(2, "0"),
140
- ).join("");
141
- return `ses_${time}${unstableRandom()}`;
142
- }
143
-
144
- export function generateRequestId(timestamp = Date.now()) {
145
- const current = BigInt(timestamp) * 0x1000n + 1n;
146
- const value = current;
147
- const time = Array.from({ length: 6 }, (_, index) =>
148
- Number((value >> BigInt(40 - 8 * index)) & 0xffn)
149
- .toString(16)
150
- .padStart(2, "0"),
151
- ).join("");
152
- return `msg_${time}${unstableRandom()}`;
82
+ return `ses_${descendingTimeHex(timestamp, counter)}${unstableRandom()}`;
153
83
  }
154
84
 
155
85
  export function translateSessionId(sessionId, clientTool = "") {
156
86
  if (typeof sessionId === "string" && OPENCODE_SESSION_RE.test(sessionId.trim())) {
157
87
  return sessionId.trim();
158
88
  }
159
- const key = `translated:${clientTool || "generic"}:${sessionId || ""}`;
160
- const existing = stableOpencodeSessions.get(key);
161
- if (existing) {
162
- existing.lastUsed = Date.now();
163
- return existing.sessionId;
164
- }
165
89
  const digest = crypto
166
90
  .createHash("sha256")
167
91
  .update(`opencode\0${clientTool || "generic"}\0${sessionId || ""}`)
168
92
  .digest();
169
- const current = BigInt(Date.now()) * 0x1000n + 1n;
170
- const value = ~current;
171
- const timeHex = Array.from({ length: 6 }, (_, index) =>
172
- Number((value >> BigInt(40 - 8 * index)) & 0xffn)
173
- .toString(16)
174
- .padStart(2, "0"),
175
- ).join("");
93
+ // Deterministic time field from the digest, counter pinned to 1 (what the CLI sends).
94
+ const time = BigInt(`0x${digest.subarray(0, 5).toString("hex")}`) & SESSION_TIME_MASK;
95
+ const timeHex = descendingTimeHex(time, 1);
176
96
  let randomPart = "";
177
- for (let i = 0; i < 14; i++) {
97
+ for (let i = 6; i < 20; i++) {
178
98
  randomPart += BASE62_CHARS[digest[i] % 62];
179
99
  }
180
- const sid = `ses_${timeHex}${randomPart}`;
181
- if (stableOpencodeSessions.size >= MAX_STABLE_SESSIONS) {
182
- stableOpencodeSessions.delete(stableOpencodeSessions.keys().next().value);
183
- }
184
- stableOpencodeSessions.set(key, { sessionId: sid, lastUsed: Date.now() });
185
- return sid;
100
+ return `ses_${timeHex}${randomPart}`;
186
101
  }
187
102
 
188
103
  function normalizeSession(value) {
@@ -250,66 +165,6 @@ export function stableSessionId(credentials) {
250
165
  return sessionId;
251
166
  }
252
167
 
253
- function lastUserText(body) {
254
- try {
255
- if (!body || typeof body !== "object") return "";
256
- const arr = Array.isArray(body.messages)
257
- ? body.messages
258
- : Array.isArray(body.input)
259
- ? body.input
260
- : null;
261
- if (!arr) return typeof body.input === "string" ? body.input.slice(-600) : "";
262
- for (let i = arr.length - 1; i >= 0; i--) {
263
- const msg = arr[i];
264
- if (!msg) continue;
265
- if (msg.role && msg.role !== "user") continue;
266
- const content = msg.content;
267
- if (typeof content === "string" && content.trim()) return content.trim().slice(-600);
268
- if (Array.isArray(content)) {
269
- const text = content
270
- .map((part) => (typeof part === "string" ? part : part?.text || part?.input_text || ""))
271
- .join(" ")
272
- .trim();
273
- if (text) return text.slice(-600);
274
- }
275
- }
276
- } catch {
277
- return "";
278
- }
279
- return "";
280
- }
281
-
282
- // The real CLI sends the current user message id (stable per turn, same on
283
- // retries) as x-opencode-request. Derive it deterministically from the
284
- // session plus the last user message so retries share the id.
285
- export function deriveRequestId(sessionId, body) {
286
- const text = lastUserText(body);
287
- const current = BigInt(Date.now()) * 0x1000n + 1n;
288
- const timeHex = Array.from({ length: 6 }, (_, index) =>
289
- Number((current >> BigInt(40 - 8 * index)) & 0xffn)
290
- .toString(16)
291
- .padStart(2, "0"),
292
- ).join("");
293
- if (!text) return `msg_${timeHex}${unstableRandom()}`;
294
- const digest = crypto
295
- .createHash("sha256")
296
- .update(`opencode-req\0${sessionId || ""}\0${text}`)
297
- .digest();
298
- let randomPart = "";
299
- for (let i = 0; i < 14; i++) {
300
- randomPart += BASE62_CHARS[digest[i] % 62];
301
- }
302
- const id = `msg_${timeHex}${randomPart}`;
303
- return OPENCODE_REQUEST_RE.test(id) ? id : generateRequestId();
304
- }
305
-
306
- function normalizeRequestId(value) {
307
- if (typeof value !== "string") return null;
308
- const normalized = value.trim();
309
- if (!normalized || normalized.length > MAX_SESSION_LENGTH) return null;
310
- return OPENCODE_REQUEST_RE.test(normalized) ? normalized : null;
311
- }
312
-
313
168
  function bodyHasSessionHints(body) {
314
169
  try {
315
170
  if (!body || typeof body !== "object") return false;
@@ -394,18 +249,6 @@ function resolveOpencodeSession(body, credentials, providerSessionId, clientTool
394
249
  return stableSessionId(credentials);
395
250
  }
396
251
 
397
- function resolveOpencodeRequestId(body, credentials, sessionId) {
398
- const raw = credentials?.rawHeaders || {};
399
- for (const [key, value] of Object.entries(raw)) {
400
- if (key.toLowerCase() === "x-opencode-request") {
401
- const normalized = normalizeRequestId(value);
402
- if (normalized) return normalized;
403
- break;
404
- }
405
- }
406
- return deriveRequestId(sessionId, body);
407
- }
408
-
409
252
  function normalizeResponsesTools(body) {
410
253
  if (!Array.isArray(body.tools)) return;
411
254
  const validNames = new Set();
@@ -523,7 +366,6 @@ export class OpenCodeExecutor extends BaseExecutor {
523
366
  return {
524
367
  ...sourceCredentials,
525
368
  [SESSION_FIELD]: session,
526
- [REQ_FIELD]: resolveOpencodeRequestId(body, sourceCredentials, session),
527
369
  };
528
370
  }
529
371
 
@@ -562,14 +404,12 @@ export class OpenCodeExecutor extends BaseExecutor {
562
404
  body.store = false;
563
405
  normalizeResponsesTools(body);
564
406
  sanitizeResponsesItems(body);
565
- // Free tier gates on both 'bash' and 'read' being present in the tools
566
- // payload (verified live: any Responses request without both returns 403
567
- // FreeTierError "can only be used from within OpenCode", with both +
568
- // tool_choice auto it returns 200). Cloak on every request, not just
569
- // empty ones, so external clients sending 1..N tools still pass.
570
- cloakOpencodeTools(body, true);
407
+ // Free-tier fingerprint tools are required even when an agent client
408
+ // already supplied tools. ZCode/Claude Code requests normally have
409
+ // non-empty tool arrays; skipping cloaking here triggers 403 FreeTierError.
410
+ applyFingerprintTools(body, true);
571
411
  } else if (body && typeof body === "object") {
572
- cloakOpencodeTools(body, false);
412
+ applyFingerprintTools(body, false);
573
413
  }
574
414
  return injectReasoningContent({ provider: this.provider, model, body });
575
415
  }
@@ -603,11 +443,13 @@ export class OpenCodeExecutor extends BaseExecutor {
603
443
  return parseFreeUsageError(response, bodyText) || super.parseError(response, bodyText);
604
444
  }
605
445
 
446
+ // Free tier moved from /zen/v1 to /inference/v1 (OpenCode v2); /zen/v1 now
447
+ // answers every free request with 403 FreeTierError.
606
448
  buildUrl(model) {
607
449
  const base = this.config.baseUrl;
608
- if (isResponsesModel(model)) return `${base}/zen/v1/responses`;
609
- if (isMessagesModel(model)) return `${base}/zen/v1/messages`;
610
- return `${base}/zen/v1/chat/completions`;
450
+ if (isResponsesModel(model)) return `${base}/inference/v1/responses`;
451
+ if (isMessagesModel(model)) return `${base}/inference/v1/messages`;
452
+ return `${base}/inference/v1/chat/completions`;
611
453
  }
612
454
 
613
455
  buildHeaders(credentials, stream = true, url = "") {
@@ -621,17 +463,18 @@ export class OpenCodeExecutor extends BaseExecutor {
621
463
  const session =
622
464
  credentials?.[SESSION_FIELD] ||
623
465
  this.prepareRequestCredentials({ credentials })[SESSION_FIELD];
624
- const downstreamReq = normalizeRequestId(lower["x-opencode-request"]);
625
- const requestId = credentials?.[REQ_FIELD] || downstreamReq || generateRequestId();
626
466
 
467
+ // Mirrors the v2 CLI. Upstream needs x-opencode-session-id next to
468
+ // x-opencode-session, and rejects any x-opencode-request (v1 only) with 403.
627
469
  const headers = {
628
470
  "Content-Type": "application/json",
629
471
  Authorization: "Bearer public",
630
472
  "User-Agent": isOpencodeDownstream ? downstreamUa : OPENCODE_UA,
631
- "x-opencode-client": lower["x-opencode-client"] || "cli",
473
+ "x-opencode-client": lower["x-opencode-client"] || OPENCODE_CLIENT,
632
474
  "x-opencode-session": session,
633
475
  "x-opencode-session-id": session,
634
- "x-opencode-request": requestId,
476
+ "x-session-affinity": session,
477
+ "x-session-id": session,
635
478
  "x-opencode-project": lower["x-opencode-project"] || "global",
636
479
  Accept: stream ? "text/event-stream" : "*/*",
637
480
  };
@@ -14,6 +14,7 @@ import {
14
14
  } from "./requestDetail.js";
15
15
  import { appendRequestLog, saveRequestDetail } from "@/lib/usageDb.js";
16
16
  import { decloakToolNames } from "../../utils/claudeCloaking.js";
17
+ import { restoreToolNames } from "../../utils/opencodeFingerprint.js";
17
18
  import { ROLE, RESPONSES_ITEM } from "../../translator/schema/index.js";
18
19
 
19
20
  function parseToolArguments(value) {
@@ -483,7 +484,7 @@ export async function handleNonStreamingResponse({
483
484
 
484
485
  return {
485
486
  success: true,
486
- response: new Response(JSON.stringify(translatedResponse), {
487
+ response: new Response(JSON.stringify(restoreToolNames(translatedResponse, toolNameMap)), {
487
488
  headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
488
489
  }),
489
490
  };
@@ -1,4 +1,5 @@
1
1
  import { convertResponsesStreamToJson } from "../../transformer/streamToJsonConverter.js";
2
+ import { restoreToolNames } from "../../utils/opencodeFingerprint.js";
2
3
  import { createErrorResult } from "../../utils/error.js";
3
4
  import { HTTP_STATUS } from "../../config/runtimeConfig.js";
4
5
  import { FORMATS } from "../../translator/formats.js";
@@ -219,6 +220,7 @@ export async function handleForcedSSEToJson({
219
220
  clientRawRequest,
220
221
  onRequestSuccess,
221
222
  customToolNames,
223
+ toolNameMap,
222
224
  trackDone,
223
225
  appendLog,
224
226
  reqTag,
@@ -299,7 +301,7 @@ export async function handleForcedSSEToJson({
299
301
  if (sourceFormat === FORMATS.OPENAI_RESPONSES) {
300
302
  return {
301
303
  success: true,
302
- response: new Response(JSON.stringify(jsonResponse), {
304
+ response: new Response(JSON.stringify(restoreToolNames(jsonResponse, toolNameMap)), {
303
305
  headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
304
306
  }),
305
307
  };
@@ -388,7 +390,7 @@ export async function handleForcedSSEToJson({
388
390
 
389
391
  return {
390
392
  success: true,
391
- response: new Response(JSON.stringify(finalResp), {
393
+ response: new Response(JSON.stringify(restoreToolNames(finalResp, toolNameMap)), {
392
394
  headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
393
395
  }),
394
396
  };
@@ -489,7 +491,7 @@ export async function handleForcedSSEToJson({
489
491
 
490
492
  return {
491
493
  success: true,
492
- response: new Response(JSON.stringify(finalBody), {
494
+ response: new Response(JSON.stringify(restoreToolNames(finalBody, toolNameMap)), {
493
495
  headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
494
496
  }),
495
497
  };
@@ -31,6 +31,7 @@ import { handleNonStreamingResponse } from "./chatCore/nonStreamingHandler.js";
31
31
  import { handleStreamingResponse, buildOnStreamComplete } from "./chatCore/streamingHandler.js";
32
32
  import { detectClientTool, isNativePassthrough } from "../utils/clientDetector.js";
33
33
  import { dedupeTools } from "../utils/toolDeduper.js";
34
+ import { takeRenamedToolNames } from "../utils/opencodeFingerprint.js";
34
35
  import { injectCaveman } from "../rtk/caveman.js";
35
36
  import { injectPonytail } from "../rtk/ponytail.js";
36
37
  import { compressMessages, formatRtkLog } from "../rtk/index.js";
@@ -520,6 +521,10 @@ export async function handleChatCore({
520
521
  providerHeaders = result.headers;
521
522
  finalBody = result.transformedBody;
522
523
  providerResponseFormat = result.responseFormat || targetFormat;
524
+ const renamedToolNames = takeRenamedToolNames(translatedBody);
525
+ if (renamedToolNames?.size) {
526
+ toolNameMap = new Map([...(toolNameMap || []), ...renamedToolNames]);
527
+ }
523
528
  reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody);
524
529
  } catch (error) {
525
530
  trackPendingRequest(model, provider, connectionId, false, true);
@@ -690,6 +695,7 @@ export async function handleChatCore({
690
695
  sourceFormat,
691
696
  targetFormat: providerResponseFormat,
692
697
  customToolNames,
698
+ toolNameMap,
693
699
  trackDone,
694
700
  appendLog,
695
701
  });
@@ -2,6 +2,7 @@ import { FORMATS } from "./formats.js";
2
2
  import { ensureToolCallIds, fixMissingToolResponses } from "./concerns/toolCall.js";
3
3
  import { prepareClaudeRequest } from "./formats/claude.js";
4
4
  import { cloakClaudeTools, decloakStreamChunk } from "../utils/claudeCloaking.js";
5
+ import { restoreToolNames } from "../utils/opencodeFingerprint.js";
5
6
  import { filterToOpenAIFormat } from "./formats/openai.js";
6
7
  import { normalizeThinkingConfig } from "../services/provider.js";
7
8
  import { applyThinking, captureThinking } from "./concerns/thinkingUnified.js";
@@ -170,7 +171,7 @@ export function translateResponse(targetFormat, sourceFormat, chunk, state) {
170
171
  // even when no format conversion is needed, so streamed tool_use blocks must
171
172
  // be decloaked here or the client sees an unknown ("_ide"-suffixed) tool.
172
173
  if (sourceFormat === targetFormat) {
173
- return [decloakStreamChunk(chunk, state?.toolNameMap)];
174
+ return [restoreToolNames(decloakStreamChunk(chunk, state?.toolNameMap), state?.toolNameMap)];
174
175
  }
175
176
 
176
177
  let results = [chunk];
@@ -183,7 +184,8 @@ export function translateResponse(targetFormat, sourceFormat, chunk, state) {
183
184
  const directFn = responseRegistry.get(`${targetFormat}:${sourceFormat}`);
184
185
  if (directFn) {
185
186
  const converted = directFn(chunk, state);
186
- return converted ? (Array.isArray(converted) ? converted : [converted]) : [];
187
+ const directResults = converted ? (Array.isArray(converted) ? converted : [converted]) : [];
188
+ return restoreToolNames(directResults, state?.toolNameMap);
187
189
  }
188
190
 
189
191
  // Step 1: target -> openai (if target is not openai)
@@ -214,6 +216,8 @@ export function translateResponse(targetFormat, sourceFormat, chunk, state) {
214
216
  }
215
217
  }
216
218
 
219
+ results = restoreToolNames(results, state?.toolNameMap);
220
+
217
221
  // Attach OpenAI intermediate results for logging
218
222
  if (openaiResults && sourceFormat !== FORMATS.OPENAI && targetFormat !== FORMATS.OPENAI) {
219
223
  results._openaiIntermediate = openaiResults;