mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-13 18:52:18 +03:00
fix(opencode): force CLI User-Agent when CLI identity synthesis is enabled (#10222)
This commit is contained in:
1
changelog.d/fixes/opencode-force-cli-ua.md
Normal file
1
changelog.d/fixes/opencode-force-cli-ua.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(providers):** when `OPENCODE_SYNTHESIZE_CLI_HEADERS=true`, a non-CLI client User-Agent (e.g. `curl/8.5.0`, SDKs) on opencode-go/opencode-zen/opencode-free requests is now REPLACED with the synthesized `opencode-cli/1.0.0` instead of being honored — opencode.ai's free tier (`/zen/v1`) returns `FreeUsageLimitError` 429 for generic client UAs egressing from datacenter IPs, which made the #5997 CLI-identity synthesis ineffective for non-CLI clients. Client UAs already matching `opencode-cli/…` are preserved (the real CLI's versioned identity stays intact); all other client-supplied `x-opencode-*` headers keep client-wins. Regression guard: `tests/unit/opencode-cli-headers-synthesis-5997.test.ts` (7, incl. non-CLI UA replaced + CLI UA preserved). (#5997 follow-up)
|
||||
@@ -367,7 +367,9 @@ export class OpencodeExecutor extends BaseExecutor {
|
||||
// value risks upstream rejection (#5720 regressed with "opencode/local"), and this
|
||||
// is deployment-specific. So it stays OFF by default and the VPS operator enables it
|
||||
// with OPENCODE_SYNTHESIZE_CLI_HEADERS=true (values env-overridable). Client-supplied
|
||||
// headers always take precedence.
|
||||
// headers take precedence, EXCEPT User-Agent: a non-CLI client UA (curl/SDK) is
|
||||
// replaced with the synthesized CLI UA because opencode.ai's free tier rejects
|
||||
// generic client UAs from datacenter IPs (FreeUsageLimitError 429).
|
||||
const synthesizeCli = /^(1|true|yes|on)$/i.test(
|
||||
process.env.OPENCODE_SYNTHESIZE_CLI_HEADERS?.trim() ?? ""
|
||||
);
|
||||
|
||||
@@ -47,7 +47,10 @@ function findHeader(headers: Record<string, string>, name: string): string | und
|
||||
* the OpenCode CLI identity headers that Cloudflare requires on VPS egress
|
||||
* (User-Agent, x-opencode-client, x-opencode-project) plus fresh request/session
|
||||
* UUIDs, but ONLY for keys the client did not already supply. Client values always
|
||||
* win; these defaults only fill gaps. (#5997)
|
||||
* win; these defaults only fill gaps. User-Agent is the one exception: a client UA
|
||||
* that is not already the OpenCode CLI (e.g. curl/8.5.0) is REPLACED with the
|
||||
* synthesized CLI UA, because opencode.ai's free tier rejects generic client UAs
|
||||
* from datacenter IPs with FreeUsageLimitError 429. (#5997, follow-up #10229)
|
||||
*/
|
||||
export function forwardOpencodeClientHeaders(
|
||||
headers: Record<string, string>,
|
||||
@@ -100,14 +103,22 @@ export function forwardOpencodeClientHeaders(
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the OpenCode CLI identity headers Cloudflare requires on VPS egress, but only for
|
||||
* keys the client did not already supply (client values always win). (#5997)
|
||||
* Fill the OpenCode CLI identity headers Cloudflare requires on VPS egress. For
|
||||
* x-opencode-* headers, client values always win (defaults only fill gaps). The
|
||||
* User-Agent is the exception: a non-CLI client UA (curl, python, SDKs) is replaced
|
||||
* with the synthesized CLI UA, because opencode.ai's free tier flags generic client
|
||||
* UAs from datacenter IPs (FreeUsageLimitError 429). A client UA that already looks
|
||||
* like the OpenCode CLI (opencode-cli/...) is preserved so the real CLI's versioned
|
||||
* identity stays intact. (#5997, follow-up)
|
||||
*/
|
||||
function applyCliDefaults(
|
||||
headers: Record<string, string>,
|
||||
cliDefaults: { userAgent: string; client: string; project: string }
|
||||
): void {
|
||||
if (!headers["User-Agent"] && !headers["user-agent"]) {
|
||||
const existingUa = headers["User-Agent"] || headers["user-agent"];
|
||||
const clientUaIsCliLike =
|
||||
typeof existingUa === "string" && /^opencode-cli\//i.test(existingUa.trim());
|
||||
if (!clientUaIsCliLike) {
|
||||
setUserAgentHeader(headers, cliDefaults.userAgent);
|
||||
}
|
||||
headers["x-opencode-client"] ||= cliDefaults.client;
|
||||
|
||||
@@ -53,10 +53,10 @@ test("forwardOpencodeClientHeaders: cliDefaults synthesize all CLI identity head
|
||||
assert.notEqual(headers["x-opencode-request"], headers["x-opencode-session"]);
|
||||
});
|
||||
|
||||
test("forwardOpencodeClientHeaders: client-supplied CLI headers take precedence over defaults [#5997]", () => {
|
||||
test("forwardOpencodeClientHeaders: non-CLI client UA is REPLACED with the CLI UA; other headers keep client-wins [#5997 follow-up]", () => {
|
||||
const headers: Record<string, string> = {};
|
||||
const clientHeaders = {
|
||||
"User-Agent": "my-tool/9.9",
|
||||
"User-Agent": "curl/8.5.0",
|
||||
"x-opencode-client": "vscode",
|
||||
"x-opencode-project": "acme",
|
||||
"x-opencode-request": "req-from-client",
|
||||
@@ -64,13 +64,20 @@ test("forwardOpencodeClientHeaders: client-supplied CLI headers take precedence
|
||||
};
|
||||
forwardOpencodeClientHeaders(headers, clientHeaders, { cliDefaults: CLI_DEFAULTS });
|
||||
|
||||
assert.equal(headers["User-Agent"], "my-tool/9.9");
|
||||
assert.equal(headers["User-Agent"], "opencode-cli/1.0.0");
|
||||
assert.equal(headers["x-opencode-client"], "vscode");
|
||||
assert.equal(headers["x-opencode-project"], "acme");
|
||||
assert.equal(headers["x-opencode-request"], "req-from-client");
|
||||
assert.equal(headers["x-opencode-session"], "sess-from-client");
|
||||
});
|
||||
|
||||
test("forwardOpencodeClientHeaders: an existing opencode-cli UA is preserved (real CLI version intact)", () => {
|
||||
const headers: Record<string, string> = {};
|
||||
const clientHeaders = { "User-Agent": "opencode-cli/2.5.0" };
|
||||
forwardOpencodeClientHeaders(headers, clientHeaders, { cliDefaults: CLI_DEFAULTS });
|
||||
assert.equal(headers["User-Agent"], "opencode-cli/2.5.0");
|
||||
});
|
||||
|
||||
test("forwardOpencodeClientHeaders: without cliDefaults, no synthesis (DefaultExecutor path unchanged)", () => {
|
||||
const headers: Record<string, string> = {};
|
||||
forwardOpencodeClientHeaders(headers, {});
|
||||
|
||||
Reference in New Issue
Block a user