fix(core): restore default model headers (#36975)

This commit is contained in:
Aiden Cline
2026-07-14 20:50:32 -05:00
committed by GitHub
parent ba1f3d3d32
commit 72cf7f12f3
7 changed files with 79 additions and 18 deletions
+12 -10
View File
@@ -9,6 +9,7 @@ import { makeLocationNode } from "../effect/app-node"
import { llmClient } from "../effect/app-node-platform" import { llmClient } from "../effect/app-node-platform"
import { SessionEvent } from "./event" import { SessionEvent } from "./event"
import type { SessionMessage } from "./message" import type { SessionMessage } from "./message"
import { SessionModelHeaders } from "./model-headers"
import { SessionRunnerModel } from "./runner/model" import { SessionRunnerModel } from "./runner/model"
import { SessionSchema } from "./schema" import { SessionSchema } from "./schema"
import { toSessionError } from "./to-session-error" import { toSessionError } from "./to-session-error"
@@ -66,7 +67,7 @@ type Dependencies = {
} }
export type AutoInput = { export type AutoInput = {
readonly sessionID: SessionSchema.ID readonly session: SessionSchema.Info
readonly messages: readonly SessionMessage.Info[] readonly messages: readonly SessionMessage.Info[]
readonly model: Model readonly model: Model
} }
@@ -78,7 +79,7 @@ export type ManualInput = {
} }
type Plan = { type Plan = {
readonly sessionID: SessionSchema.ID readonly session: SessionSchema.Info
readonly model: Model readonly model: Model
readonly reason: SessionMessage.Compaction["reason"] readonly reason: SessionMessage.Compaction["reason"]
readonly prompt: string readonly prompt: string
@@ -230,7 +231,7 @@ const make = (dependencies: Dependencies) => {
}) })
const execute = Effect.fn("SessionCompaction.execute")(function* (plan: Plan) { const execute = Effect.fn("SessionCompaction.execute")(function* (plan: Plan) {
yield* dependencies.events.publish(SessionEvent.Compaction.Started, { yield* dependencies.events.publish(SessionEvent.Compaction.Started, {
sessionID: plan.sessionID, sessionID: plan.session.id,
reason: plan.reason, reason: plan.reason,
recent: plan.recent, recent: plan.recent,
inputID: plan.inputID, inputID: plan.inputID,
@@ -242,6 +243,7 @@ const make = (dependencies: Dependencies) => {
.stream( .stream(
LLM.request({ LLM.request({
model: plan.model, model: plan.model,
http: { headers: SessionModelHeaders.make(plan.session) },
messages: [Message.user(plan.prompt)], messages: [Message.user(plan.prompt)],
tools: [], tools: [],
}), }),
@@ -256,7 +258,7 @@ const make = (dependencies: Dependencies) => {
if (LLMEvent.is.textDelta(event)) { if (LLMEvent.is.textDelta(event)) {
chunks.push(event.text) chunks.push(event.text)
return dependencies.events.publish(SessionEvent.Compaction.Delta, { return dependencies.events.publish(SessionEvent.Compaction.Delta, {
sessionID: plan.sessionID, sessionID: plan.session.id,
text: event.text, text: event.text,
}) })
} }
@@ -270,7 +272,7 @@ const make = (dependencies: Dependencies) => {
Effect.onInterrupt(() => Effect.onInterrupt(() =>
plan.reason === "auto" plan.reason === "auto"
? failed({ ? failed({
sessionID: plan.sessionID, sessionID: plan.session.id,
reason: plan.reason, reason: plan.reason,
error: { type: "compaction.interrupted", message: "Compaction was interrupted" }, error: { type: "compaction.interrupted", message: "Compaction was interrupted" },
inputID: plan.inputID, inputID: plan.inputID,
@@ -282,14 +284,14 @@ const make = (dependencies: Dependencies) => {
if (failure || !summary.trim()) { if (failure || !summary.trim()) {
const error = failure ?? { type: "compaction.failed" as const, message: "Compaction produced no summary" } const error = failure ?? { type: "compaction.failed" as const, message: "Compaction produced no summary" }
return yield* failed({ return yield* failed({
sessionID: plan.sessionID, sessionID: plan.session.id,
reason: plan.reason, reason: plan.reason,
error, error,
inputID: plan.inputID, inputID: plan.inputID,
}) })
} }
yield* dependencies.events.publish(SessionEvent.Compaction.Ended, { yield* dependencies.events.publish(SessionEvent.Compaction.Ended, {
sessionID: plan.sessionID, sessionID: plan.session.id,
reason: plan.reason, reason: plan.reason,
text: summary, text: summary,
recent: plan.recent, recent: plan.recent,
@@ -300,14 +302,14 @@ const make = (dependencies: Dependencies) => {
const content = planContent(input.messages, config.tokens) const content = planContent(input.messages, config.tokens)
if (content) if (content)
return yield* execute({ return yield* execute({
sessionID: input.sessionID, session: input.session,
model: input.model, model: input.model,
reason: "auto", reason: "auto",
...content, ...content,
}) })
const error = { type: "compaction.unavailable" as const, message: "Nothing to compact yet" } const error = { type: "compaction.unavailable" as const, message: "Nothing to compact yet" }
return yield* failed({ return yield* failed({
sessionID: input.sessionID, sessionID: input.session.id,
reason: "auto", reason: "auto",
error, error,
}) })
@@ -348,7 +350,7 @@ const make = (dependencies: Dependencies) => {
) )
if ("status" in resolved) return resolved if ("status" in resolved) return resolved
return yield* execute({ return yield* execute({
sessionID: input.session.id, session: input.session,
model: resolved.model, model: resolved.model,
reason: "manual", reason: "manual",
inputID: input.inputID, inputID: input.inputID,
@@ -0,0 +1,15 @@
export * as SessionModelHeaders from "./model-headers"
import { Flag } from "../flag/flag"
import { InstallationVersion } from "../installation/version"
import { SessionSchema } from "./schema"
export const make = (session: Pick<SessionSchema.Info, "id" | "parentID" | "projectID">) => ({
"x-session-affinity": session.id,
"X-Session-Id": session.id,
...(session.parentID ? { "x-parent-session-id": session.parentID } : {}),
"User-Agent": `opencode/${InstallationVersion}`,
"x-opencode-project": session.projectID,
"x-opencode-session": session.id,
"x-opencode-client": Flag.OPENCODE_CLIENT,
})
+4 -8
View File
@@ -51,7 +51,7 @@ import { AgentNotFoundError, StepFailedError } from "../error"
import { toSessionError } from "../to-session-error" import { toSessionError } from "../to-session-error"
import { SessionRunnerRetry } from "./retry" import { SessionRunnerRetry } from "./retry"
import { PluginSupervisor } from "../../plugin/supervisor" import { PluginSupervisor } from "../../plugin/supervisor"
import { Flag } from "../../flag/flag" import { SessionModelHeaders } from "../model-headers"
type StepTokens = { type StepTokens = {
readonly input: number readonly input: number
@@ -187,7 +187,7 @@ const layer = Layer.effect(
const providerMetadataKey = model.route.providerMetadataKey ?? model.provider const providerMetadataKey = model.route.providerMetadataKey ?? model.provider
const history = yield* SessionHistory.entriesForRunner(db, session.id, instructions) const history = yield* SessionHistory.entriesForRunner(db, session.id, instructions)
const context = history.entries.map((entry) => entry.message) const context = history.entries.map((entry) => entry.message)
const compactionInput = { sessionID: session.id, messages: context, model } const compactionInput = { session, messages: context, model }
if (compaction.required(compactionInput) && !(yield* SessionPending.compaction(db, session.id))) { if (compaction.required(compactionInput) && !(yield* SessionPending.compaction(db, session.id))) {
const compacted = yield* compaction.compact(compactionInput) const compacted = yield* compaction.compact(compactionInput)
if (compacted.status === "completed") return { _tag: "RestartAfterCompaction", step: currentStep } as const if (compacted.status === "completed") return { _tag: "RestartAfterCompaction", step: currentStep } as const
@@ -199,11 +199,7 @@ const layer = Layer.effect(
const request = LLM.request({ const request = LLM.request({
model, model,
http: { http: {
headers: { headers: SessionModelHeaders.make(session),
"x-opencode-project": session.projectID,
"x-opencode-session": session.id,
"x-opencode-client": Flag.OPENCODE_CLIENT,
},
}, },
providerOptions: { openai: { promptCacheKey } }, providerOptions: { openai: { promptCacheKey } },
system: [agentInfo.system ? agentInfo.system : SessionRunnerSystemPrompt.provider(model), history.initial] system: [agentInfo.system ? agentInfo.system : SessionRunnerSystemPrompt.provider(model), history.initial]
@@ -341,7 +337,7 @@ const layer = Layer.effect(
recoverOverflow && recoverOverflow &&
!publisher.hasRetryEvidence() && !publisher.hasRetryEvidence() &&
isContextOverflowFailure(overflowFailure ?? streamFailure) && isContextOverflowFailure(overflowFailure ?? streamFailure) &&
(yield* restore(recoverOverflow({ sessionID: session.id, messages: context, model }))).status === (yield* restore(recoverOverflow({ session, messages: context, model }))).status ===
"completed" "completed"
) )
return { _tag: "RestartAfterOverflowCompaction", step: currentStep } as const return { _tag: "RestartAfterOverflowCompaction", step: currentStep } as const
+2
View File
@@ -9,6 +9,7 @@ import { makeLocationNode } from "../effect/app-node"
import { llmClient } from "../effect/app-node-platform" import { llmClient } from "../effect/app-node-platform"
import { SessionEvent } from "./event" import { SessionEvent } from "./event"
import { SessionHistory } from "./history" import { SessionHistory } from "./history"
import { SessionModelHeaders } from "./model-headers"
import { SessionRunnerModel } from "./runner/model" import { SessionRunnerModel } from "./runner/model"
import { SessionSchema } from "./schema" import { SessionSchema } from "./schema"
@@ -54,6 +55,7 @@ const make = (dependencies: Dependencies) => {
.stream( .stream(
LLM.request({ LLM.request({
model: resolved.model, model: resolved.model,
http: { headers: SessionModelHeaders.make(session) },
system: agent.system, system: agent.system,
messages: [Message.user(firstUser.text)], messages: [Message.user(firstUser.text)],
tools: [], tools: [],
@@ -18,6 +18,8 @@ import { SessionStore } from "@opencode-ai/core/session/store"
import { SessionV2 } from "@opencode-ai/core/session" import { SessionV2 } from "@opencode-ai/core/session"
import { Project } from "@opencode-ai/core/project" import { Project } from "@opencode-ai/core/project"
import { ProjectTable } from "@opencode-ai/core/project/sql" import { ProjectTable } from "@opencode-ai/core/project/sql"
import { Flag } from "@opencode-ai/core/flag/flag"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { AbsolutePath } from "@opencode-ai/core/schema" import { AbsolutePath } from "@opencode-ai/core/schema"
import { DateTime, Effect, Fiber, Layer, Stream } from "effect" import { DateTime, Effect, Fiber, Layer, Stream } from "effect"
import { asc, eq } from "drizzle-orm" import { asc, eq } from "drizzle-orm"
@@ -104,6 +106,7 @@ it.effect("manual compaction summarizes short context instead of no-op", () =>
const events = yield* EventV2.Service const events = yield* EventV2.Service
const store = yield* SessionStore.Service const store = yield* SessionStore.Service
const sessionID = SessionV2.ID.make("ses_manual_compaction") const sessionID = SessionV2.ID.make("ses_manual_compaction")
const parentID = SessionV2.ID.make("ses_manual_compaction_parent")
const userMessage = { const userMessage = {
id: SessionMessage.ID.create(), id: SessionMessage.ID.create(),
type: "user" as const, type: "user" as const,
@@ -121,6 +124,7 @@ it.effect("manual compaction summarizes short context instead of no-op", () =>
.values({ .values({
id: sessionID, id: sessionID,
project_id: Project.ID.global, project_id: Project.ID.global,
parent_id: parentID,
slug: "manual-compaction", slug: "manual-compaction",
directory: "/project", directory: "/project",
title: "Manual compaction", title: "Manual compaction",
@@ -151,6 +155,15 @@ it.effect("manual compaction summarizes short context instead of no-op", () =>
expect(Array.from(yield* Fiber.join(delta)).map((event) => event.data.text)).toEqual(["manual summary"]) expect(Array.from(yield* Fiber.join(delta)).map((event) => event.data.text)).toEqual(["manual summary"])
expect(requests).toHaveLength(1) expect(requests).toHaveLength(1)
expect(requests[0]?.http?.headers).toEqual({
"x-session-affinity": sessionID,
"X-Session-Id": sessionID,
"x-parent-session-id": parentID,
"User-Agent": `opencode/${InstallationVersion}`,
"x-opencode-project": Project.ID.global,
"x-opencode-session": sessionID,
"x-opencode-client": Flag.OPENCODE_CLIENT,
})
expect(requests[0]?.generation).toBeUndefined() expect(requests[0]?.generation).toBeUndefined()
expect(JSON.stringify(requests[0]?.messages)).toContain("Manual compaction should include this short conversation.") expect(JSON.stringify(requests[0]?.messages)).toContain("Manual compaction should include this short conversation.")
expect(yield* store.context(sessionID)).toMatchObject([ expect(yield* store.context(sessionID)).toMatchObject([
+23
View File
@@ -20,6 +20,7 @@ import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { EventV2 } from "@opencode-ai/core/event" import { EventV2 } from "@opencode-ai/core/event"
import { Flag } from "@opencode-ai/core/flag/flag" import { Flag } from "@opencode-ai/core/flag/flag"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { PermissionV2 } from "@opencode-ai/core/permission" import { PermissionV2 } from "@opencode-ai/core/permission"
import { EventTable } from "@opencode-ai/core/event/sql" import { EventTable } from "@opencode-ai/core/event/sql"
import { Project } from "@opencode-ai/core/project" import { Project } from "@opencode-ai/core/project"
@@ -3086,6 +3087,9 @@ describe("SessionRunnerLLM", () => {
yield* session.resume(sessionID) yield* session.resume(sessionID)
expect(requests[0]?.http?.headers).toEqual({ expect(requests[0]?.http?.headers).toEqual({
"x-session-affinity": sessionID,
"X-Session-Id": sessionID,
"User-Agent": `opencode/${InstallationVersion}`,
"x-opencode-project": Project.ID.global, "x-opencode-project": Project.ID.global,
"x-opencode-session": sessionID, "x-opencode-session": sessionID,
"x-opencode-client": Flag.OPENCODE_CLIENT, "x-opencode-client": Flag.OPENCODE_CLIENT,
@@ -3093,6 +3097,25 @@ describe("SessionRunnerLLM", () => {
}), }),
) )
it.effect("adds the parent session header to child model requests", () =>
Effect.gen(function* () {
const session = yield* setup
const parentID = SessionV2.ID.make("ses_runner_parent")
const { db } = yield* Database.Service
yield* db
.update(SessionTable)
.set({ parent_id: parentID })
.where(eq(SessionTable.id, sessionID))
.run()
.pipe(Effect.orDie)
yield* admit(session, "Run child request")
yield* session.resume(sessionID)
expect(requests[0]?.http?.headers?.["x-parent-session-id"]).toBe(parentID)
}),
)
it.effect("runs different sessions concurrently", () => it.effect("runs different sessions concurrently", () =>
Effect.gen(function* () { Effect.gen(function* () {
const session = yield* setup const session = yield* setup
+10
View File
@@ -17,6 +17,8 @@ import { SessionTitle } from "@opencode-ai/core/session/title"
import { SessionV2 } from "@opencode-ai/core/session" import { SessionV2 } from "@opencode-ai/core/session"
import { Project } from "@opencode-ai/core/project" import { Project } from "@opencode-ai/core/project"
import { ProjectTable } from "@opencode-ai/core/project/sql" import { ProjectTable } from "@opencode-ai/core/project/sql"
import { Flag } from "@opencode-ai/core/flag/flag"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { AbsolutePath } from "@opencode-ai/core/schema" import { AbsolutePath } from "@opencode-ai/core/schema"
import { DateTime, Effect, Layer, Stream } from "effect" import { DateTime, Effect, Layer, Stream } from "effect"
import { testEffect } from "./lib/effect" import { testEffect } from "./lib/effect"
@@ -117,6 +119,14 @@ it.effect("generates a title from the sole user message and renames the session"
yield* title.generateForFirstPrompt(session) yield* title.generateForFirstPrompt(session)
expect(requests).toHaveLength(1) expect(requests).toHaveLength(1)
expect(requests[0]?.http?.headers).toEqual({
"x-session-affinity": sessionID,
"X-Session-Id": sessionID,
"User-Agent": `opencode/${InstallationVersion}`,
"x-opencode-project": Project.ID.global,
"x-opencode-session": sessionID,
"x-opencode-client": Flag.OPENCODE_CLIENT,
})
expect(JSON.stringify(requests[0]?.messages)).toContain("Help me debug the failing build") expect(JSON.stringify(requests[0]?.messages)).toContain("Help me debug the failing build")
const renamed = yield* store.get(sessionID) const renamed = yield* store.get(sessionID)
expect(renamed?.title).toBe("Generated Title") expect(renamed?.title).toBe("Generated Title")