diff --git a/open-sse/executors/opencode.js b/open-sse/executors/opencode.js index f7aee211..27a81c00 100644 --- a/open-sse/executors/opencode.js +++ b/open-sse/executors/opencode.js @@ -1,16 +1,43 @@ +import crypto from "crypto"; import { BaseExecutor } from "./base.js"; import { PROVIDERS } from "../config/providers.js"; import { injectReasoningContent } from "../utils/reasoningContentInjector.js"; +import { resolveSessionId } from "../utils/sessionManager.js"; -// Models that use /zen/v1/messages (claude format) +const OPENCODE_UA = "opencode"; const MESSAGES_MODELS = new Set(); +function generateRequestId() { + return `msg_${crypto.randomUUID().replace(/-/g, "")}`; +} + +function generateSessionId() { + return `ses_${crypto.randomUUID().replace(/-/g, "")}`; +} + +// Normalize any resolved id into opencode's ses_ format (stable per-conversation) +function toOpencodeSession(id) { + const stripped = String(id || "").replace(/^ses_/, "").replace(/-/g, ""); + return stripped ? `ses_${stripped}` : null; +} + +function resolveOpencodeSession(body, credentials) { + return toOpencodeSession(resolveSessionId({ + headers: credentials?.rawHeaders, + body, + connectionId: credentials?.connectionId, + scope: "opencode", + })); +} + export class OpenCodeExecutor extends BaseExecutor { constructor() { super("opencode", PROVIDERS.opencode); + this._currentSessionId = null; } - transformRequest(model, body) { + transformRequest(model, body, stream, credentials) { + this._currentSessionId = resolveOpencodeSession(body, credentials); return injectReasoningContent({ provider: this.provider, model, body }); } @@ -21,12 +48,23 @@ export class OpenCodeExecutor extends BaseExecutor { : `${base}/zen/v1/chat/completions`; } - buildHeaders() { + buildHeaders(credentials, stream = true) { + const raw = credentials?.rawHeaders || {}; + const lower = {}; + for (const [k, v] of Object.entries(raw)) lower[k.toLowerCase()] = v; + + const downstreamUa = lower["user-agent"] || ""; + const isOpencodeDownstream = downstreamUa.toLowerCase().includes("opencode"); + return { "Content-Type": "application/json", "Authorization": "Bearer public", - "x-opencode-client": "desktop", - "Accept": "text/event-stream" + "User-Agent": isOpencodeDownstream ? downstreamUa : OPENCODE_UA, + "x-opencode-client": lower["x-opencode-client"] || "desktop", + "x-opencode-session": lower["x-opencode-session"] || this._currentSessionId || generateSessionId(), + "x-opencode-request": lower["x-opencode-request"] || generateRequestId(), + "x-opencode-project": lower["x-opencode-project"] || "global", + "Accept": stream ? "text/event-stream" : "*/*", }; } }