diff --git a/packages/agent/src/pi/upstream.ts b/packages/agent/src/pi/upstream.ts index a162b6d3..b60771b9 100644 --- a/packages/agent/src/pi/upstream.ts +++ b/packages/agent/src/pi/upstream.ts @@ -356,7 +356,14 @@ export class PiUpstreamClient { res = await this.models.completeSimple( { ...model, maxTokens: Math.min(model.maxTokens || 16, 16) }, { messages: [{ role: "user", content: "Reply with: ok", timestamp: Date.now() }] }, - { maxTokens: 16, ...(opts.signal ? { signal: opts.signal } : {}), timeoutMs: 30_000, maxRetries: 0 }, + { + maxTokens: 16, + ...(opts.signal ? { signal: opts.signal } : {}), + timeoutMs: 30_000, + maxRetries: 0, + // OpenCode(Zen / Go)按会话路由,缺 x-opencode-session 会被拒;其余 provider 用它做缓存键,无副作用 + sessionId: `umt-upstream-test-${crypto.randomUUID()}`, + }, ); } catch (err) { throw classifyUpstreamError(err); diff --git a/packages/agent/test/upstream.test.ts b/packages/agent/test/upstream.test.ts index d662bcf8..4c856651 100644 --- a/packages/agent/test/upstream.test.ts +++ b/packages/agent/test/upstream.test.ts @@ -133,6 +133,31 @@ describe("按上游装配", () => { expect(t).toMatchObject({ model: "claude-future-9", inCatalog: false, source: "stored credential" }); }); + it("测试补全带 sessionId:OpenCode Go 请求头有 x-opencode-session", async () => { + const seen: { url: string; headers: Headers }[] = []; + const orig = globalThis.fetch; + globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { + const req = new Request(input, init); + seen.push({ url: req.url, headers: req.headers }); + return new Response(JSON.stringify({ error: { message: "stubbed" } }), { + status: 500, + headers: { "content-type": "application/json" }, + }); + }) as typeof fetch; + try { + const c = new PiUpstreamClient( + spec({ provider: "opencode-go", authKind: "api_key" }), + memStore({ type: "api_key", key: "sk-opencode-test" }), + ); + await expect(c.test({ mode: "completion" })).rejects.toBeInstanceOf(UpstreamError); + } finally { + globalThis.fetch = orig; + } + const call = seen.find((x) => x.headers.get("x-opencode-session")); + expect(call, `请求:${seen.map((x) => x.url).join(", ")}`).toBeDefined(); + expect(call?.headers.get("x-opencode-session")).toMatch(/^umt-upstream-test-/); + }); + it("自定义兼容端点:baseUrl/模型必填;authKind none 不需要 key", async () => { expect(() => new PiUpstreamClient(spec({ provider: "openai-compatible", authKind: "none" }), memStore())).toThrow( UpstreamError,