@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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +7 -7
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.json +5 -0
- package/.next/server/app/(dashboard)/dashboard/basic-chat/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/cli-tools/[toolId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/console-log/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/media-providers/systemone/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/media-providers/systemone/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/proxy-pools/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/pxpipe/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/quota/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/token-saver/page_client-reference-manifest.js +1 -1
- package/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +5 -5
- package/.next/server/app/_not-found.segments/_full.segment.rsc +5 -5
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/changelog/route.js +1 -1
- package/.next/server/app/api/cli-tools/all-statuses/route.js +1 -1
- package/.next/server/app/api/cli-tools/claude-settings/route.js +1 -1
- package/.next/server/app/api/cli-tools/codex-settings/route.js +1 -1
- package/.next/server/app/api/cli-tools/cowork-mcp-registry/route.js +1 -1
- package/.next/server/app/api/cli-tools/cowork-mcp-tools/route.js +1 -1
- package/.next/server/app/api/cli-tools/cowork-settings/route.js +1 -1
- package/.next/server/app/api/cli-tools/dsh-settings/route.js +1 -1
- package/.next/server/app/api/cli-tools/opencode-settings/route.js +2 -2
- package/.next/server/app/api/oauth/[provider]/[action]/route.js +1 -1
- package/.next/server/app/api/oauth/codex/bulk-import/route.js +1 -1
- package/.next/server/app/api/oauth/codex/import-token/route.js +1 -1
- package/.next/server/app/api/providers/client/route.js +1 -1
- package/.next/server/app/api/translator/send/route.js +1 -1
- package/.next/server/app/api/translator/translate/route.js +1 -1
- package/.next/server/app/api/tunnel/tailscale-install/route.js +2 -2
- package/.next/server/app/api/usage/[connectionId]/codex-reset-credits/route.js +1 -1
- package/.next/server/app/api/usage/[connectionId]/route.js +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/callback/page_client-reference-manifest.js +1 -1
- package/.next/server/app/callback.html +1 -1
- package/.next/server/app/callback.rsc +6 -6
- package/.next/server/app/callback.segments/_full.segment.rsc +6 -6
- package/.next/server/app/callback.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/callback.segments/callback/__PAGE__.segment.rsc +6 -6
- package/.next/server/app/dashboard/basic-chat.html +1 -1
- package/.next/server/app/dashboard/basic-chat.rsc +7 -7
- package/.next/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard/basic-chat/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/basic-chat.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/basic-chat.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/cli-tools.html +1 -1
- package/.next/server/app/dashboard/cli-tools.rsc +7 -7
- package/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard/cli-tools/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/cli-tools.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/cli-tools.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/combos.html +1 -1
- package/.next/server/app/dashboard/combos.rsc +7 -7
- package/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard/combos/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/combos.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/combos.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/endpoint.html +1 -1
- package/.next/server/app/dashboard/endpoint.rsc +7 -7
- package/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard/endpoint/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/endpoint.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/endpoint.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/media-providers/systemone.html +1 -1
- package/.next/server/app/dashboard/media-providers/systemone.rsc +7 -7
- package/.next/server/app/dashboard/media-providers/systemone.segments/!KGRhc2hib2FyZCk/dashboard/media-providers/systemone/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/media-providers/systemone.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/media-providers/systemone.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/profile.html +1 -1
- package/.next/server/app/dashboard/profile.rsc +7 -7
- package/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard/profile/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/profile.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/profile.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/providers/new.html +1 -1
- package/.next/server/app/dashboard/providers/new.rsc +7 -7
- package/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers/new/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/providers/new.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/providers/new.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/providers.html +1 -1
- package/.next/server/app/dashboard/providers.rsc +7 -7
- package/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard/providers/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/providers.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/providers.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/proxy-pools.html +1 -1
- package/.next/server/app/dashboard/proxy-pools.rsc +7 -7
- package/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard/proxy-pools/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/proxy-pools.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/proxy-pools.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/pxpipe.html +1 -1
- package/.next/server/app/dashboard/pxpipe.rsc +6 -6
- package/.next/server/app/dashboard/pxpipe.segments/!KGRhc2hib2FyZCk/dashboard/pxpipe/__PAGE__.segment.rsc +6 -6
- package/.next/server/app/dashboard/pxpipe.segments/_full.segment.rsc +6 -6
- package/.next/server/app/dashboard/pxpipe.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/quota.html +2 -2
- package/.next/server/app/dashboard/quota.rsc +8 -8
- package/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard/quota/__PAGE__.segment.rsc +8 -8
- package/.next/server/app/dashboard/quota.segments/_full.segment.rsc +8 -8
- package/.next/server/app/dashboard/quota.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
- package/.next/server/app/dashboard/settings/pricing.html +1 -1
- package/.next/server/app/dashboard/settings/pricing.rsc +5 -5
- package/.next/server/app/dashboard/settings/pricing.segments/_full.segment.rsc +5 -5
- package/.next/server/app/dashboard/settings/pricing.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/settings/pricing.segments/dashboard/settings/pricing/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/dashboard/token-saver.html +1 -1
- package/.next/server/app/dashboard/token-saver.rsc +6 -6
- package/.next/server/app/dashboard/token-saver.segments/!KGRhc2hib2FyZCk/dashboard/token-saver/__PAGE__.segment.rsc +6 -6
- package/.next/server/app/dashboard/token-saver.segments/_full.segment.rsc +6 -6
- package/.next/server/app/dashboard/token-saver.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard/usage.html +2 -2
- package/.next/server/app/dashboard/usage.rsc +7 -7
- package/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard/usage/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard/usage.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard/usage.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/dashboard.html +1 -1
- package/.next/server/app/dashboard.rsc +7 -7
- package/.next/server/app/dashboard.segments/!KGRhc2hib2FyZCk/dashboard/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/dashboard.segments/_full.segment.rsc +7 -7
- package/.next/server/app/dashboard.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +5 -5
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/landing/page_client-reference-manifest.js +1 -1
- package/.next/server/app/landing.html +1 -1
- package/.next/server/app/landing.rsc +6 -6
- package/.next/server/app/landing.segments/_full.segment.rsc +6 -6
- package/.next/server/app/landing.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/landing.segments/landing/__PAGE__.segment.rsc +6 -6
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +5 -5
- package/.next/server/app/login.segments/_full.segment.rsc +5 -5
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +7 -7
- package/.next/server/chunks/1011.js +1 -1
- package/.next/server/chunks/1194.js +1 -1
- package/.next/server/chunks/1289.js +1 -1
- package/.next/server/chunks/1633.js +1 -1
- package/.next/server/chunks/2282.js +7 -7
- package/.next/server/chunks/2339.js +1 -1
- package/.next/server/chunks/2801.js +1 -1
- package/.next/server/chunks/3425.js +1 -1
- package/.next/server/chunks/537.js +5 -1
- package/.next/server/chunks/6056.js +1 -1
- package/.next/server/chunks/6343.js +6 -6
- package/.next/server/chunks/9429.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/1278-16cc8ebc3a4c8af5.js +1 -0
- package/.next/static/chunks/{1471-69a7c30abf98c656.js → 1471-10f225f62f7469cc.js} +1 -1
- package/.next/static/chunks/{1699-49be310981e2759e.js → 1699-13ee6864a8a31a96.js} +2 -2
- package/.next/static/chunks/{3577-1f02993330d7fba7.js → 3577-cd98d40e3099e1b0.js} +1 -1
- package/.next/static/chunks/{5862-6f3a3a6d4c00100d.js → 5862-f98c2f3d480b80d2.js} +1 -1
- package/.next/static/chunks/{9ce8c186-6cd204e9c27ecee1.js → 9ce8c186-3c7d7dbc5d428052.js} +1 -1
- package/.next/static/chunks/app/(dashboard)/dashboard/console-log/{page-c803a3376280f0e8.js → page-30d1f039dfdca294.js} +1 -1
- package/.next/static/chunks/app/(dashboard)/dashboard/providers/new/{page-cef3c079d15c9349.js → page-fc4d1c1291545177.js} +1 -1
- package/.next/static/chunks/app/{layout-101b832aa6789b60.js → layout-43bf918527d2f137.js} +1 -1
- package/CHANGELOG.md +66 -0
- package/next.config.mjs +5 -0
- package/open-sse/executors/opencode-zen.js +38 -98
- package/open-sse/executors/opencode.js +41 -198
- package/open-sse/handlers/chatCore/nonStreamingHandler.js +2 -1
- package/open-sse/handlers/chatCore/sseToJsonHandler.js +5 -3
- package/open-sse/handlers/chatCore.js +6 -0
- package/open-sse/translator/index.js +6 -2
- package/open-sse/utils/opencodeFingerprint.js +385 -0
- package/package.json +3 -2
- package/src/app/api/changelog/{route.js → route.ts} +2 -1
- package/src/app/api/version/route.js +1 -1
- package/src/lib/versionCheck.js +18 -16
- package/src/shared/components/UpdateDialog.tsx +1 -1
- package/.next/static/chunks/1278-ad2cb39d11b4a5f2.js +0 -1
- /package/.next/static/{nOEr8lKXMARlP2mV3XFuF → kcbgqbQp7PqJfusgfYenC}/_buildManifest.js +0 -0
- /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
|
-
|
|
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(
|
|
103
|
-
|
|
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 /
|
|
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
|
-
|
|
170
|
-
const
|
|
171
|
-
const timeHex =
|
|
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 =
|
|
97
|
+
for (let i = 6; i < 20; i++) {
|
|
178
98
|
randomPart += BASE62_CHARS[digest[i] % 62];
|
|
179
99
|
}
|
|
180
|
-
|
|
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
|
|
566
|
-
//
|
|
567
|
-
//
|
|
568
|
-
|
|
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
|
-
|
|
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}/
|
|
609
|
-
if (isMessagesModel(model)) return `${base}/
|
|
610
|
-
return `${base}/
|
|
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"] ||
|
|
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-
|
|
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
|
-
|
|
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;
|