Spaces:
Paused
Paused
Download src/proxy/upstream.ts from bolikoto/zcode-daili: direct link, hf CLI and curl.
- Browser
- Download file 8.94 kB
-
https://huggingface.co/spaces/bolikoto/zcode-daili/resolve/main/src/proxy/upstream.ts
- Command line
-
hf download hf://spaces/bolikoto/zcode-daili/src/proxy/upstream.ts
-
curl -L -o upstream.ts https://huggingface.co/spaces/bolikoto/zcode-daili/resolve/main/src/proxy/upstream.ts
8.94 kB
| /** | |
| * Upstream request builder — constructs the forwarded HTTP request. | |
| * | |
| * **`format` semantics**: This is the *upstream* format — the format used to | |
| * talk to the upstream LLM provider, not the client's inbound format. Both | |
| * ZCode coding-plan and start-plan routes post an Anthropic-format upstream | |
| * (since PR #34, 2026-08-29 — start-plan's old OpenAI gateway is retired); | |
| * OpenAI clients are translated before this builder is called. | |
| * | |
| * @see _reverse/NOTEPAD.md "How Credential is Used for LLM Calls" | |
| */ | |
| import type { Format } from "../translator/types.js"; | |
| import type { ProviderDef } from "../provider/types.js"; | |
| import type { Credential } from "../auth/types.js"; | |
| import type { ProxyIdentity } from "../config/types.js"; | |
| import { credentialString } from "../auth/types.js"; | |
| import { buildLlmIdentityHeaders } from "./identity.js"; | |
| import { buildZcodeTraceHeaders } from "./trace-headers.js"; | |
| import { sessionIdForHeader, shouldUseExactTraceHeaders } from "./session-context.js"; | |
| export interface UpstreamClientSession { | |
| source?: "none" | "explicit" | "lineage"; | |
| action: "off" | "observe" | "enforce"; | |
| sessionId?: string; | |
| upstreamSessionId?: string; | |
| /** Explicit x-zcode-session-type override (wins over sessionId-prefix derivation). */ | |
| sessionType?: "main" | "subagent" | "other"; | |
| requestId?: string; | |
| traceId?: string; | |
| queryId?: string; | |
| } | |
| export type UpstreamHeaderPair = [string, string]; | |
| const ANTHROPIC_VERSION = "2023-06-01"; | |
| /** | |
| * SDK UA suffix the real client appends to its User-Agent on LLM calls | |
| * (bundle `Cm` merges `ai-sdk/anthropic/${k0o}` into the UA; k0o = "3.0.81"). | |
| * Only the LLM request path carries it — control-plane fetches (endpoint | |
| * routing, signing gate, claim/billing) keep the bare `ZCode/…` UA. | |
| */ | |
| const ANTHROPIC_SDK_UA = "ai-sdk/anthropic/3.0.81"; | |
| const STARTPLAN_ANTHROPIC_BASE = "https://zcode.z.ai/api/v1/zcode-plan"; | |
| /** | |
| * Build the upstream URL based on format + plan + provider. | |
| * | |
| * The `format` parameter is the *upstream* format — callers in handler.ts | |
| * pass the format the upstream will receive, which may differ from the | |
| * client's inbound format when the proxy is in compatibility mode. | |
| */ | |
| export function buildUpstreamURL(format: Format, provider: ProviderDef, plan: "coding-plan" | "start-plan" = "coding-plan"): string { | |
| if (plan === "start-plan") { | |
| // Anthropic-messages gateway used by the live ZCode desktop client for | |
| // start-plan (incl. claimed trial plans like the weekend package). | |
| // The legacy OpenAI route `${STARTPLAN_ANTHROPIC_BASE}/chat/completions` | |
| // returns "404 page not found" since the server-side rollout of this | |
| // endpoint (observed 2026-08-28). | |
| return `${STARTPLAN_ANTHROPIC_BASE}/anthropic/v1/messages`; | |
| } | |
| if (format === "anthropic") { | |
| return `${provider.anthropicBaseURL}/v1/messages`; | |
| } | |
| return `${provider.openaiBaseURL}/chat/completions`; | |
| } | |
| /** | |
| * Build auth + identity + trace headers for the upstream LLM request. | |
| * | |
| * The `format` parameter is the *upstream* format — selects auth scheme: | |
| * - Anthropic upstream, coding-plan → `x-api-key: {cred}` AND | |
| * `Authorization: Bearer {cred}` (dual headers, bundle `ebo`) + | |
| * `anthropic-version` | |
| * - Anthropic upstream, start-plan → `Authorization: Bearer {jwt}` + `anthropic-version` | |
| * - OpenAI upstream (any plan) → `Authorization: Bearer {cred|jwt}` | |
| * | |
| * Identity headers come from `buildLlmIdentityHeaders` (bundle 3.12.3 `g6n` | |
| * shape); the User-Agent carries the `ai-sdk/anthropic` SDK suffix (bundle | |
| * `Cm`/`k0o`). | |
| * | |
| * Trace/attribution headers mirror the bundle's `Bdt` | |
| * ("createModelRequestAttributionHeaders") when an explicit/enforced trace | |
| * context exists. Default enforce mode forwards the inferred conversation | |
| * session id; sessions without a resolvable id fall back to a synthesized | |
| * one (coding-plan must always carry x-session-id — signing requires it). | |
| */ | |
| export function buildAuthHeaders( | |
| format: Format, | |
| cred: Credential, | |
| identity: ProxyIdentity, | |
| plan: "coding-plan" | "start-plan" = "coding-plan", | |
| clientSession?: UpstreamClientSession, | |
| ): Record<string, string> { | |
| const credStr = plan === "start-plan" && cred.jwt ? cred.jwt : credentialString(cred); | |
| const base: Record<string, string> = { | |
| // LLM requests mirror the bundle's 3.12.3 source-headers builder (`g6n`, | |
| // no wrapper) — language/timezone always sent with "unknown" fallback, | |
| // X-ZCode-Agent inline 8th before the platform headers, no X-Device-Mid. | |
| // Context/control-plane fetches use buildIdentityHeaders (`TV`). | |
| ...buildLlmIdentityHeaders(identity), | |
| ...buildTraceHeaders(plan, clientSession), | |
| }; | |
| // Bundle `Cm`/`k0o`: the anthropic SDK merges its identity into the UA — | |
| // real LLM requests arrive as `ZCode/{ver} ai-sdk/anthropic/3.0.81`. | |
| base["User-Agent"] = `${base["User-Agent"]} ${ANTHROPIC_SDK_UA}`; | |
| if (format === "anthropic") { | |
| if (plan === "start-plan" && cred.jwt) { | |
| base["authorization"] = `Bearer ${cred.jwt}`; | |
| } else { | |
| // Bundle `ebo` (coding-plan, anthropic): x-api-key from the SDK config | |
| // AND `Authorization: Bearer {credential}` from the custom-header merge | |
| // — both headers, same value. | |
| base["x-api-key"] = credStr; | |
| base["authorization"] = `Bearer ${credStr}`; | |
| } | |
| base["anthropic-version"] = ANTHROPIC_VERSION; | |
| } else { | |
| base["authorization"] = `Bearer ${credStr}`; | |
| } | |
| return base; | |
| } | |
| function buildTraceHeaders(plan: "coding-plan" | "start-plan", clientSession?: UpstreamClientSession): Record<string, string> { | |
| if (shouldUseExactTraceHeaders(plan, clientSession)) { | |
| // Degenerate bodies (lineage failed) and requestId-only requests resolve | |
| // to an exact context with NO session id — coding-plan would then ship | |
| // without x-session-id and signing would skip (client-signing requires | |
| // it), violating the "coding-plan 必签" invariant. Synthesize like the | |
| // fallback branch; start-plan is never signed and keeps the omission. | |
| const sessionId = sessionIdForHeader(clientSession) | |
| ?? (plan === "coding-plan" ? crypto.randomUUID() : undefined); | |
| return buildZcodeTraceHeaders({ | |
| requestId: clientSession?.requestId, | |
| traceId: clientSession?.traceId, | |
| queryId: clientSession?.queryId, | |
| sessionId, | |
| ...(clientSession?.sessionType ? { sessionType: clientSession.sessionType } : {}), | |
| }); | |
| } | |
| const headers: Record<string, string> = { | |
| "x-request-id": crypto.randomUUID(), | |
| // ZCode 3.9.1 attributes every model request; a forwarded conversation turn is the main-agent loop. | |
| "x-zcode-session-type": "main", | |
| "x-zcode-trace-id": crypto.randomUUID(), | |
| }; | |
| if (plan !== "start-plan") { | |
| headers["x-query-id"] = crypto.randomUUID(); | |
| headers["x-session-id"] = crypto.randomUUID(); | |
| } | |
| return headers; | |
| } | |
| function collectPassthroughHeaders(req: Request): Record<string, string> { | |
| const result: Record<string, string> = {}; | |
| for (const [key, value] of req.headers.entries()) { | |
| const lower = key.toLowerCase(); | |
| // Explicit allowlist: client auth, routing auth, trace and V4 signing | |
| // headers are never forwarded. Their trusted values are rebuilt below. | |
| if (lower === "anthropic-beta") { | |
| result[lower] = value; | |
| } | |
| } | |
| return result; | |
| } | |
| export function buildUpstreamHeaderPairs( | |
| clientReq: Request, | |
| format: Format, | |
| cred: Credential, | |
| identity: ProxyIdentity, | |
| plan: "coding-plan" | "start-plan" = "coding-plan", | |
| extraHeaders?: Record<string, string>, | |
| clientSession?: UpstreamClientSession, | |
| ): UpstreamHeaderPair[] { | |
| // Forward the client's Accept-Encoding so the upstream compresses only when | |
| // the client can decode it. Hardcoding "gzip" broke clients (e.g. some Tauri | |
| // builds) that send `accept-encoding: identity` and cannot auto-decompress. | |
| const clientAcceptEncoding = clientReq.headers.get("accept-encoding") ?? "gzip"; | |
| return [ | |
| ["content-type", "application/json"], | |
| ["accept-encoding", clientAcceptEncoding], | |
| ...Object.entries(collectPassthroughHeaders(clientReq)), | |
| ...Object.entries(buildAuthHeaders(format, cred, identity, plan, clientSession)), | |
| ...Object.entries(extraHeaders ?? {}), | |
| ]; | |
| } | |
| export function buildUpstreamRequest( | |
| clientReq: Request, | |
| format: Format, | |
| provider: ProviderDef, | |
| cred: Credential, | |
| body: string | undefined, | |
| identity: ProxyIdentity, | |
| plan: "coding-plan" | "start-plan" = "coding-plan", | |
| extraHeaders?: Record<string, string>, | |
| clientSession?: UpstreamClientSession, | |
| ): Request { | |
| const url = buildUpstreamURL(format, provider, plan); | |
| const headerPairs = buildUpstreamHeaderPairs(clientReq, format, cred, identity, plan, extraHeaders, clientSession); | |
| const init: RequestInit = { | |
| method: "POST", | |
| headers: Object.fromEntries(headerPairs), | |
| }; | |
| if (body !== undefined) { | |
| init.body = body; | |
| } | |
| return new Request(url, init); | |
| } | |