mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-24 18:13:38 +00:00
fix(core): restore default model headers (#36975)
This commit is contained in:
parent
ba1f3d3d32
commit
72cf7f12f3
7 changed files with 79 additions and 18 deletions
|
|
@ -9,6 +9,7 @@ import { makeLocationNode } from "../effect/app-node"
|
|||
import { llmClient } from "../effect/app-node-platform"
|
||||
import { SessionEvent } from "./event"
|
||||
import type { SessionMessage } from "./message"
|
||||
import { SessionModelHeaders } from "./model-headers"
|
||||
import { SessionRunnerModel } from "./runner/model"
|
||||
import { SessionSchema } from "./schema"
|
||||
import { toSessionError } from "./to-session-error"
|
||||
|
|
@ -66,7 +67,7 @@ type Dependencies = {
|
|||
}
|
||||
|
||||
export type AutoInput = {
|
||||
readonly sessionID: SessionSchema.ID
|
||||
readonly session: SessionSchema.Info
|
||||
readonly messages: readonly SessionMessage.Info[]
|
||||
readonly model: Model
|
||||
}
|
||||
|
|
@ -78,7 +79,7 @@ export type ManualInput = {
|
|||
}
|
||||
|
||||
type Plan = {
|
||||
readonly sessionID: SessionSchema.ID
|
||||
readonly session: SessionSchema.Info
|
||||
readonly model: Model
|
||||
readonly reason: SessionMessage.Compaction["reason"]
|
||||
readonly prompt: string
|
||||
|
|
@ -230,7 +231,7 @@ const make = (dependencies: Dependencies) => {
|
|||
})
|
||||
const execute = Effect.fn("SessionCompaction.execute")(function* (plan: Plan) {
|
||||
yield* dependencies.events.publish(SessionEvent.Compaction.Started, {
|
||||
sessionID: plan.sessionID,
|
||||
sessionID: plan.session.id,
|
||||
reason: plan.reason,
|
||||
recent: plan.recent,
|
||||
inputID: plan.inputID,
|
||||
|
|
@ -242,6 +243,7 @@ const make = (dependencies: Dependencies) => {
|
|||
.stream(
|
||||
LLM.request({
|
||||
model: plan.model,
|
||||
http: { headers: SessionModelHeaders.make(plan.session) },
|
||||
messages: [Message.user(plan.prompt)],
|
||||
tools: [],
|
||||
}),
|
||||
|
|
@ -256,7 +258,7 @@ const make = (dependencies: Dependencies) => {
|
|||
if (LLMEvent.is.textDelta(event)) {
|
||||
chunks.push(event.text)
|
||||
return dependencies.events.publish(SessionEvent.Compaction.Delta, {
|
||||
sessionID: plan.sessionID,
|
||||
sessionID: plan.session.id,
|
||||
text: event.text,
|
||||
})
|
||||
}
|
||||
|
|
@ -270,7 +272,7 @@ const make = (dependencies: Dependencies) => {
|
|||
Effect.onInterrupt(() =>
|
||||
plan.reason === "auto"
|
||||
? failed({
|
||||
sessionID: plan.sessionID,
|
||||
sessionID: plan.session.id,
|
||||
reason: plan.reason,
|
||||
error: { type: "compaction.interrupted", message: "Compaction was interrupted" },
|
||||
inputID: plan.inputID,
|
||||
|
|
@ -282,14 +284,14 @@ const make = (dependencies: Dependencies) => {
|
|||
if (failure || !summary.trim()) {
|
||||
const error = failure ?? { type: "compaction.failed" as const, message: "Compaction produced no summary" }
|
||||
return yield* failed({
|
||||
sessionID: plan.sessionID,
|
||||
sessionID: plan.session.id,
|
||||
reason: plan.reason,
|
||||
error,
|
||||
inputID: plan.inputID,
|
||||
})
|
||||
}
|
||||
yield* dependencies.events.publish(SessionEvent.Compaction.Ended, {
|
||||
sessionID: plan.sessionID,
|
||||
sessionID: plan.session.id,
|
||||
reason: plan.reason,
|
||||
text: summary,
|
||||
recent: plan.recent,
|
||||
|
|
@ -300,14 +302,14 @@ const make = (dependencies: Dependencies) => {
|
|||
const content = planContent(input.messages, config.tokens)
|
||||
if (content)
|
||||
return yield* execute({
|
||||
sessionID: input.sessionID,
|
||||
session: input.session,
|
||||
model: input.model,
|
||||
reason: "auto",
|
||||
...content,
|
||||
})
|
||||
const error = { type: "compaction.unavailable" as const, message: "Nothing to compact yet" }
|
||||
return yield* failed({
|
||||
sessionID: input.sessionID,
|
||||
sessionID: input.session.id,
|
||||
reason: "auto",
|
||||
error,
|
||||
})
|
||||
|
|
@ -348,7 +350,7 @@ const make = (dependencies: Dependencies) => {
|
|||
)
|
||||
if ("status" in resolved) return resolved
|
||||
return yield* execute({
|
||||
sessionID: input.session.id,
|
||||
session: input.session,
|
||||
model: resolved.model,
|
||||
reason: "manual",
|
||||
inputID: input.inputID,
|
||||
|
|
|
|||
15
packages/core/src/session/model-headers.ts
Normal file
15
packages/core/src/session/model-headers.ts
Normal file
|
|
@ -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,
|
||||
})
|
||||
|
|
@ -51,7 +51,7 @@ import { AgentNotFoundError, StepFailedError } from "../error"
|
|||
import { toSessionError } from "../to-session-error"
|
||||
import { SessionRunnerRetry } from "./retry"
|
||||
import { PluginSupervisor } from "../../plugin/supervisor"
|
||||
import { Flag } from "../../flag/flag"
|
||||
import { SessionModelHeaders } from "../model-headers"
|
||||
|
||||
type StepTokens = {
|
||||
readonly input: number
|
||||
|
|
@ -187,7 +187,7 @@ const layer = Layer.effect(
|
|||
const providerMetadataKey = model.route.providerMetadataKey ?? model.provider
|
||||
const history = yield* SessionHistory.entriesForRunner(db, session.id, instructions)
|
||||
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))) {
|
||||
const compacted = yield* compaction.compact(compactionInput)
|
||||
if (compacted.status === "completed") return { _tag: "RestartAfterCompaction", step: currentStep } as const
|
||||
|
|
@ -199,11 +199,7 @@ const layer = Layer.effect(
|
|||
const request = LLM.request({
|
||||
model,
|
||||
http: {
|
||||
headers: {
|
||||
"x-opencode-project": session.projectID,
|
||||
"x-opencode-session": session.id,
|
||||
"x-opencode-client": Flag.OPENCODE_CLIENT,
|
||||
},
|
||||
headers: SessionModelHeaders.make(session),
|
||||
},
|
||||
providerOptions: { openai: { promptCacheKey } },
|
||||
system: [agentInfo.system ? agentInfo.system : SessionRunnerSystemPrompt.provider(model), history.initial]
|
||||
|
|
@ -341,7 +337,7 @@ const layer = Layer.effect(
|
|||
recoverOverflow &&
|
||||
!publisher.hasRetryEvidence() &&
|
||||
isContextOverflowFailure(overflowFailure ?? streamFailure) &&
|
||||
(yield* restore(recoverOverflow({ sessionID: session.id, messages: context, model }))).status ===
|
||||
(yield* restore(recoverOverflow({ session, messages: context, model }))).status ===
|
||||
"completed"
|
||||
)
|
||||
return { _tag: "RestartAfterOverflowCompaction", step: currentStep } as const
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { makeLocationNode } from "../effect/app-node"
|
|||
import { llmClient } from "../effect/app-node-platform"
|
||||
import { SessionEvent } from "./event"
|
||||
import { SessionHistory } from "./history"
|
||||
import { SessionModelHeaders } from "./model-headers"
|
||||
import { SessionRunnerModel } from "./runner/model"
|
||||
import { SessionSchema } from "./schema"
|
||||
|
||||
|
|
@ -54,6 +55,7 @@ const make = (dependencies: Dependencies) => {
|
|||
.stream(
|
||||
LLM.request({
|
||||
model: resolved.model,
|
||||
http: { headers: SessionModelHeaders.make(session) },
|
||||
system: agent.system,
|
||||
messages: [Message.user(firstUser.text)],
|
||||
tools: [],
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import { SessionStore } from "@opencode-ai/core/session/store"
|
|||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
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 { DateTime, Effect, Fiber, Layer, Stream } from "effect"
|
||||
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 store = yield* SessionStore.Service
|
||||
const sessionID = SessionV2.ID.make("ses_manual_compaction")
|
||||
const parentID = SessionV2.ID.make("ses_manual_compaction_parent")
|
||||
const userMessage = {
|
||||
id: SessionMessage.ID.create(),
|
||||
type: "user" as const,
|
||||
|
|
@ -121,6 +124,7 @@ it.effect("manual compaction summarizes short context instead of no-op", () =>
|
|||
.values({
|
||||
id: sessionID,
|
||||
project_id: Project.ID.global,
|
||||
parent_id: parentID,
|
||||
slug: "manual-compaction",
|
||||
directory: "/project",
|
||||
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(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(JSON.stringify(requests[0]?.messages)).toContain("Manual compaction should include this short conversation.")
|
||||
expect(yield* store.context(sessionID)).toMatchObject([
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { EventTable } from "@opencode-ai/core/event/sql"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
|
|
@ -3086,6 +3087,9 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* session.resume(sessionID)
|
||||
|
||||
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,
|
||||
|
|
@ -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", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setup
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import { SessionTitle } from "@opencode-ai/core/session/title"
|
|||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
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 { DateTime, Effect, Layer, Stream } from "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)
|
||||
|
||||
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")
|
||||
const renamed = yield* store.get(sessionID)
|
||||
expect(renamed?.title).toBe("Generated Title")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue