mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 19:46:16 +03:00
fix(core): don't warm a session while its model request is running (#59325)
This commit is contained in:
committed by
GitHub
Unverified
parent
9ed37f8b4d
commit
2bf756e3ab
@@ -3,7 +3,7 @@ export * as WarmingPlugin from "./warming.js"
|
||||
import { define } from "@opencode/plugin/effect/plugin"
|
||||
import type { SessionHooks } from "@opencode/plugin/effect/session"
|
||||
import type { Session } from "@opencode/schema/session"
|
||||
import { Clock, Duration, Effect, Scope } from "effect"
|
||||
import { Clock, Duration, Effect, Scope, Stream } from "effect"
|
||||
import { Config } from "../config.js"
|
||||
|
||||
const defaults = {
|
||||
@@ -28,6 +28,31 @@ export const Plugin = define({
|
||||
|
||||
const scope = yield* Scope.Scope
|
||||
const sessions = new Map<Session.ID, { last: number; expires: number; settings: typeof defaults }>()
|
||||
// A ping during a model request would compete with the request it keeps warm, which stalls single-slot local models.
|
||||
// A request counts from its context hook, because `session.step.started` waits for the first response chunk.
|
||||
const requesting = new Set<Session.ID>()
|
||||
// A turn always ends with an execution event, so a step that ends without its own event can't stay in flight.
|
||||
yield* ctx.event
|
||||
.subscribe({
|
||||
types: [
|
||||
"session.step.streamed",
|
||||
"session.step.failed",
|
||||
"session.execution.succeeded",
|
||||
"session.execution.failed",
|
||||
"session.execution.interrupted",
|
||||
],
|
||||
})
|
||||
.pipe(
|
||||
Stream.runForEach((event) =>
|
||||
Effect.gen(function* () {
|
||||
requesting.delete(event.data.sessionID)
|
||||
// Count the next interval from the end of the response, not from when a long request started.
|
||||
const current = sessions.get(event.data.sessionID)
|
||||
if (current) current.last = yield* Clock.currentTimeMillis
|
||||
}),
|
||||
),
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
)
|
||||
const loop: (sessionID: Session.ID) => Effect.Effect<void> = Effect.fn("WarmingPlugin.loop")(
|
||||
function* (sessionID) {
|
||||
const current = sessions.get(sessionID)
|
||||
@@ -43,6 +68,10 @@ export const Plugin = define({
|
||||
sessions.delete(sessionID)
|
||||
return
|
||||
}
|
||||
if (requesting.has(sessionID)) {
|
||||
yield* Effect.sleep(current.settings.interval)
|
||||
return yield* loop(sessionID)
|
||||
}
|
||||
|
||||
const last = current.last
|
||||
yield* Effect.logInfo("warming session", { sessionID, last })
|
||||
@@ -100,7 +129,9 @@ export const Plugin = define({
|
||||
Effect.forkIn(scope),
|
||||
)
|
||||
})
|
||||
yield* ctx.session.hook("context", hook)
|
||||
yield* ctx.session.hook("context", (event) =>
|
||||
Effect.sync(() => requesting.add(event.sessionID)).pipe(Effect.andThen(hook(event))),
|
||||
)
|
||||
yield* ctx.session.hook("compaction", hook)
|
||||
yield* ctx.session.hook("generate", hook)
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user