mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 02:24:10 +08:00
fix(opencode): add namespaced session identity headers (#52370)
This commit is contained in:
@@ -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 } : {}),
|
||||
|
||||
@@ -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 } : {}),
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user