fix(opencode): add namespaced session identity headers (#52370)

This commit is contained in:
Aiden Cline
2026-09-30 15:39:43 -05:00
committed by GitHub
parent 9b4882db54
commit e9f8a210b9
4 changed files with 95 additions and 65 deletions
+2
View File
@@ -206,6 +206,8 @@ const layer = Layer.effect(
model,
http: {
headers: {
"x-opencode-session-id": session.id,
...(session.parentID ? { "x-opencode-parent-session-id": session.parentID } : {}),
"x-session-affinity": session.id,
"X-Session-Id": session.id,
...(session.parentID ? { "x-parent-session-id": session.parentID } : {}),
+10 -1
View File
@@ -1103,10 +1103,12 @@ describe("SessionRunnerLLM", () => {
expect(requests).toHaveLength(2)
expect(requests.map((request) => request.http?.headers)).toEqual([
{
"x-opencode-session-id": sessionID,
"x-session-affinity": sessionID,
"X-Session-Id": sessionID,
},
{
"x-opencode-session-id": sessionID,
"x-session-affinity": sessionID,
"X-Session-Id": sessionID,
},
@@ -2528,6 +2530,7 @@ describe("SessionRunnerLLM", () => {
yield* session.resume(sessionID)
expect(requests[0]?.http?.headers).toEqual({
"x-opencode-session-id": sessionID,
"x-session-affinity": sessionID,
"X-Session-Id": sessionID,
})
@@ -2551,7 +2554,13 @@ describe("SessionRunnerLLM", () => {
requests.length = 0
yield* session.resume(sessionID)
expect(requests[0]?.http?.headers?.["x-parent-session-id"]).toBe(parentID)
expect(requests[0]?.http?.headers).toEqual({
"x-opencode-session-id": sessionID,
"x-opencode-parent-session-id": parentID,
"x-session-affinity": sessionID,
"X-Session-Id": sessionID,
"x-parent-session-id": parentID,
})
}),
)
@@ -185,6 +185,8 @@ export const prepare = Effect.fn("LLMRequestPrep.prepare")(function* (input: Pre
params,
messageTransformOptions: options,
headers: {
"x-opencode-session-id": input.sessionID,
...(input.parentSessionID ? { "x-opencode-parent-session-id": input.parentSessionID } : {}),
...(input.model.providerID.startsWith("opencode")
? {
...(opencodeProjectID ? { "x-opencode-project": opencodeProjectID } : {}),
+81 -64
View File
@@ -756,72 +756,89 @@ describe("session.llm.stream", () => {
const vivgridFixture = { providerID: "vivgrid", modelID: "gemini-3.1-pro-preview" }
const opencodeFixture = { providerID: "opencode-test", modelID: vivgridFixture.modelID }
it.instance(
"sends the parent session header for opencode providers",
() =>
Effect.gen(function* () {
const fixture = loadFixture(vivgridFixture.providerID, vivgridFixture.modelID)
const request = waitRequest(
"/chat/completions",
new Response(createChatStream("Hello"), {
status: 200,
headers: { "Content-Type": "text/event-stream" },
}),
)
const resolved = yield* Provider.use.getModel(
ProviderV2.ID.make(opencodeFixture.providerID),
ModelV2.ID.make(opencodeFixture.modelID),
)
const sessionID = SessionID.make("session-child")
const parentSessionID = SessionID.make("session-parent")
const agent = {
name: "test",
mode: "primary",
options: {},
permission: [{ permission: "*", pattern: "*", action: "allow" }],
} satisfies Agent.Info
const user = {
id: MessageID.make("msg_user-parent-header"),
sessionID,
role: "user",
time: { created: Date.now() },
agent: agent.name,
model: {
providerID: ProviderV2.ID.make(opencodeFixture.providerID),
modelID: resolved.id,
},
} satisfies SessionV1.User
yield* drain({
user,
sessionID,
parentSessionID,
model: resolved,
agent,
system: ["You are a helpful assistant."],
messages: [{ role: "user", content: "Hello" }],
tools: {},
})
expect((yield* Effect.promise(() => request)).headers.get("x-parent-session-id")).toBe(parentSessionID)
}),
{
config: () => {
const fixture = loadFixture(vivgridFixture.providerID, vivgridFixture.modelID)
return {
enabled_providers: [opencodeFixture.providerID],
provider: {
[opencodeFixture.providerID]: {
name: "OpenCode Test",
npm: "@ai-sdk/openai-compatible",
models: { [fixture.model.id]: configModel(fixture.model) as ConfigModel },
options: { apiKey: "test-key", baseURL: `${state.server!.url.origin}/v1` },
const headerCases = [
{ providerID: opencodeFixture.providerID, child: false },
{ providerID: opencodeFixture.providerID, child: true },
{ providerID: "custom-test", child: false },
{ providerID: "custom-test", child: true },
]
headerCases.forEach((input) => {
it.instance(
`sends session identity headers for ${input.child ? "child" : "root"} sessions on ${input.providerID}`,
() =>
Effect.gen(function* () {
const fixture = loadFixture(vivgridFixture.providerID, vivgridFixture.modelID)
const request = waitRequest(
"/chat/completions",
new Response(createChatStream("Hello"), {
status: 200,
headers: { "Content-Type": "text/event-stream" },
}),
)
const resolved = yield* Provider.use.getModel(
ProviderV2.ID.make(input.providerID),
ModelV2.ID.make(opencodeFixture.modelID),
)
const sessionID = SessionID.make("session-child")
const parentSessionID = input.child ? SessionID.make("session-parent") : undefined
const agent = {
name: "test",
mode: "primary",
options: {},
permission: [{ permission: "*", pattern: "*", action: "allow" }],
} satisfies Agent.Info
const user = {
id: MessageID.make("msg_user-parent-header"),
sessionID,
role: "user",
time: { created: Date.now() },
agent: agent.name,
model: {
providerID: ProviderV2.ID.make(input.providerID),
modelID: resolved.id,
},
},
}
} satisfies SessionV1.User
yield* drain({
user,
sessionID,
parentSessionID,
model: resolved,
agent,
system: ["You are a helpful assistant."],
messages: [{ role: "user", content: "Hello" }],
tools: {},
})
const headers = (yield* Effect.promise(() => request)).headers
expect(headers.get("x-opencode-session-id")).toBe(sessionID)
expect(headers.get("x-opencode-parent-session-id")).toBe(parentSessionID ?? null)
expect(headers.get("x-parent-session-id")).toBe(parentSessionID ?? null)
if (input.providerID.startsWith("opencode")) {
expect(headers.get("x-opencode-session")).toBe(sessionID)
return
}
expect(headers.get("x-session-affinity")).toBe(sessionID)
expect(headers.get("X-Session-Id")).toBe(sessionID)
}),
{
config: () => {
const fixture = loadFixture(vivgridFixture.providerID, vivgridFixture.modelID)
return {
enabled_providers: [input.providerID],
provider: {
[input.providerID]: {
name: "OpenCode Test",
npm: "@ai-sdk/openai-compatible",
models: { [fixture.model.id]: configModel(fixture.model) as ConfigModel },
options: { apiKey: "test-key", baseURL: `${state.server!.url.origin}/v1` },
},
},
}
},
},
},
)
)
})
it.instance(
"sends temperature, tokens, and reasoning options for openai-compatible models",