From feff914904f443aa537c058b7155100468d258c2 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Thu, 10 Sep 2026 15:58:00 -0700 Subject: [PATCH] Forward OpenCode Go worker session IDs without a catalog No-catalog subagent Go sources were built through the OpenAI-compatible path and omitted opencodeSessionId, so Go Console rejected the worker. Generate one worker session ID up front, route no-catalog Go through buildGoSource, and require sessionId on that builder. --- src/config.test.ts | 1 + src/config/index.ts | 36 +++++++------------- src/subagent/run-source.test.ts | 58 +++++++++++++++++++++++++++++++++ src/subagent/run.ts | 22 ++++++++++++- 4 files changed, 92 insertions(+), 25 deletions(-) diff --git a/src/config.test.ts b/src/config.test.ts index be1a0b7eb..5f5e66ce7 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -1423,6 +1423,7 @@ describe("buildGoSource", () => { id: "opencode-go", apiKey: "sk-go", model: "kimi-k2.7-code", + sessionId: "sess-1", }); expect(source.provider).toBe("opencode-go"); diff --git a/src/config/index.ts b/src/config/index.ts index 02ff2ad0c..26272ab52 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -383,7 +383,7 @@ export function buildGoSource(fields: { id: string; apiKey?: string; model: string; - sessionId?: string; + sessionId: string; reasoningEffort?: ReasoningEffort; }): InferenceSource { const endpoint = resolveGoEndpoint(fields.model); @@ -400,13 +400,9 @@ export function buildGoSource(fields: { model: fields.model, defaults: { maxTokens: SOURCE_MAX_TOKENS, - ...(fields.sessionId !== undefined - ? { - providerOptions: { - [OPENCODE_SESSION_ID_OPTION]: fields.sessionId, - }, - } - : {}), + providerOptions: { + [OPENCODE_SESSION_ID_OPTION]: fields.sessionId, + }, }, }; } @@ -419,14 +415,10 @@ export function buildGoSource(fields: { model: fields.model, defaults: { maxTokens: SOURCE_MAX_TOKENS, - ...(fields.sessionId !== undefined - ? { - providerOptions: { - [OPENAI_SESSION_ID_OPTION]: fields.sessionId, - [OPENCODE_SESSION_ID_OPTION]: fields.sessionId, - }, - } - : {}), + providerOptions: { + [OPENAI_SESSION_ID_OPTION]: fields.sessionId, + [OPENCODE_SESSION_ID_OPTION]: fields.sessionId, + }, }, }; } @@ -446,14 +438,10 @@ export function buildGoSource(fields: { provider: OPENCODE_GO_PROVIDER_ID, defaults: { ...source.defaults, - ...(fields.sessionId !== undefined - ? { - providerOptions: { - ...(source.defaults?.providerOptions ?? {}), - [OPENCODE_SESSION_ID_OPTION]: fields.sessionId, - }, - } - : {}), + providerOptions: { + ...(source.defaults?.providerOptions ?? {}), + [OPENCODE_SESSION_ID_OPTION]: fields.sessionId, + }, }, }; } diff --git a/src/subagent/run-source.test.ts b/src/subagent/run-source.test.ts index 290bf977e..2d2659a4c 100644 --- a/src/subagent/run-source.test.ts +++ b/src/subagent/run-source.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test"; import { KEYLESS_API_KEY } from "../config/index.js"; +import { OPENCODE_GO_BASE_URL } from "../../packages/opencode-go/src/index.js"; import { buildSubAgentPrimarySource } from "./run.js"; describe("buildSubAgentPrimarySource", () => { @@ -22,4 +23,61 @@ describe("buildSubAgentPrimarySource", () => { model: "qwen3", }); }); + + test.each([ + ["canonical provider id", "opencode-go", "https://example.com/v1"], + ["canonical base URL", "custom-go", OPENCODE_GO_BASE_URL], + ])( + "routes no-catalog OpenCode Go by %s with a non-empty session ID", + (_, providerName, baseURL) => { + const bundle = buildSubAgentPrimarySource({ + providerName, + baseURL, + apiKey: "sk-go", + model: "gpt-5.6-luna", + }); + + const source = bundle.sources[0]; + const sessionId = source?.defaults?.providerOptions?.opencodeSessionId; + expect(bundle.defaultSource).toBe(providerName); + expect(source).toMatchObject({ + id: providerName, + provider: "openai-responses", + apiKey: "sk-go", + model: "gpt-5.6-luna", + }); + expect(typeof sessionId).toBe("string"); + expect(sessionId).not.toHaveLength(0); + expect(source?.defaults?.providerOptions?.openaiSessionId).toBe( + sessionId, + ); + }, + ); + + test("forwards a session ID through the catalog OpenCode Go path", () => { + const bundle = buildSubAgentPrimarySource( + { + providerName: "opencode-go", + baseURL: OPENCODE_GO_BASE_URL, + apiKey: "sk-go", + model: "kimi-k2.7-code", + }, + [ + { + name: "opencode-go", + baseURL: OPENCODE_GO_BASE_URL, + apiKey: "sk-go", + models: ["kimi-k2.7-code"], + opencodeGo: true, + }, + ], + ); + + const source = bundle.sources[0]; + const sessionId = source?.defaults?.providerOptions?.opencodeSessionId; + expect(bundle.defaultSource).toBe("opencode-go"); + expect(source?.provider).toBe("opencode-go"); + expect(typeof sessionId).toBe("string"); + expect(sessionId).not.toHaveLength(0); + }); }); diff --git a/src/subagent/run.ts b/src/subagent/run.ts index 88916c5b8..76eafa2ae 100644 --- a/src/subagent/run.ts +++ b/src/subagent/run.ts @@ -36,6 +36,7 @@ import type { import { buildBifrostSource, + buildGoSource, buildOpenAISource, type ProviderCatalogEntry, } from "../config/index.js"; @@ -63,6 +64,7 @@ import { import { createCodexReadRawFile } from "../agent/codex-read-raw-file.js"; import { isCodexProviderName } from "../config/codex-providers.js"; +import { isOpenCodeGoProvider } from "../../packages/opencode-go/src/index.js"; import { createCompositeBlobReader } from "../agent/lazy-blob-reader.js"; import { buildSubAgentSystemPrompt } from "../agent/prompts.js"; @@ -216,11 +218,12 @@ export function buildSubAgentPrimarySource( catalog?: readonly ProviderCatalogEntry[], settings?: Settings, ) { + const sessionId = generateSessionId(); if (catalog !== undefined) { const source = buildInferenceSourceForRef( { provider: provider.providerName, model: provider.model }, { - sessionId: generateSessionId(), + sessionId, catalog, ...(provider.reasoningEffort !== undefined ? { reasoningEffort: provider.reasoningEffort } @@ -230,6 +233,23 @@ export function buildSubAgentPrimarySource( ); if (source !== null) return { sources: [source], defaultSource: source.id }; } + if ( + isOpenCodeGoProvider({ + name: provider.providerName, + baseURL: provider.baseURL, + }) + ) { + const source = buildGoSource({ + id: provider.providerName, + ...(provider.apiKey !== undefined ? { apiKey: provider.apiKey } : {}), + model: provider.model, + sessionId, + ...(provider.reasoningEffort !== undefined + ? { reasoningEffort: provider.reasoningEffort } + : {}), + }); + return { sources: [source], defaultSource: source.id }; + } const build = provider.bifrostVirtualKey === true ? buildBifrostSource