Skip to content

Commit e634099

Browse files
authored
Merge pull request #150 from browser-use/rollover-regression
test(opencode): cover message ID rollover loop
2 parents dd8d70c + dbb9ba9 commit e634099

1 file changed

Lines changed: 81 additions & 1 deletion

File tree

‎packages/opencode/test/session/prompt.test.ts‎

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,11 @@ const blockingProcessor = Layer.succeed(
164164
}),
165165
)
166166

167+
const providerGetModelStarted: Array<() => void> = []
168+
const blockingProvider = Layer.mock(ProviderSvc.Service, {
169+
getModel: () => Effect.sync(() => providerGetModelStarted.shift()?.()).pipe(Effect.andThen(Effect.never)),
170+
})
171+
167172
const runtimeFlags = RuntimeFlags.layer({ experimentalEventSystem: true })
168173

169174
const testLLMServerNode = LayerNode.make({ service: TestLLMServer, layer: TestLLMServer.layer, deps: [] })
@@ -208,7 +213,11 @@ const promptRoot = LayerNode.group([
208213
RuntimeFlags.node,
209214
])
210215

211-
function makePrompt(input?: { mcpInstructions?: MCP.ServerInstructions[]; processor?: "blocking" }) {
216+
function makePrompt(input?: {
217+
mcpInstructions?: MCP.ServerInstructions[]
218+
processor?: "blocking"
219+
provider?: "blocking"
220+
}) {
212221
const replacements = [
213222
[SessionSummary.node, summary],
214223
[LSP.node, lsp],
@@ -218,6 +227,9 @@ function makePrompt(input?: { mcpInstructions?: MCP.ServerInstructions[]; proces
218227
if (input?.processor === "blocking") {
219228
return LayerNode.compile(promptRoot, [...replacements, [SessionProcessor.node, blockingProcessor]])
220229
}
230+
if (input?.provider === "blocking") {
231+
return LayerNode.compile(promptRoot, [...replacements, [ProviderSvc.node, blockingProvider]])
232+
}
221233
return LayerNode.compile(promptRoot, replacements)
222234
}
223235

@@ -242,6 +254,7 @@ function makeHttpNoLLMServer(input?: { mcpInstructions?: MCP.ServerInstructions[
242254
const it = testEffect(makeHttp())
243255
const noLLMServer = testEffect(makeHttpNoLLMServer())
244256
const raceNoLLMServer = testEffect(makeHttpNoLLMServer({ processor: "blocking" }))
257+
const rolloverNoLLMServer = testEffect(makePrompt({ provider: "blocking" }))
245258
const withMcpInstructions = testEffect(
246259
makeHttp({
247260
mcpInstructions: [
@@ -500,6 +513,73 @@ noLLMServer.instance(
500513
{ config: cfg },
501514
)
502515

516+
rolloverNoLLMServer.instance(
517+
"loop starts a new user turn after message ID rollover",
518+
() =>
519+
Effect.gen(function* () {
520+
providerGetModelStarted.length = 0
521+
yield* Effect.addFinalizer(() =>
522+
Effect.sync(() => {
523+
providerGetModelStarted.length = 0
524+
}),
525+
)
526+
527+
const prompt = yield* SessionPrompt.Service
528+
const sessions = yield* Session.Service
529+
const chat = yield* sessions.create({ title: "Pinned" })
530+
const oldUserID = MessageID.make("msg_ffffffffe001olduser")
531+
const oldAssistantID = MessageID.make("msg_fffffffff001oldassistant")
532+
const newUserID = MessageID.make("msg_000000001001newuser")
533+
yield* sessions.updateMessage({
534+
id: oldUserID,
535+
role: "user",
536+
sessionID: chat.id,
537+
agent: "build",
538+
model: ref,
539+
time: { created: 100 },
540+
})
541+
yield* sessions.updateMessage({
542+
id: oldAssistantID,
543+
role: "assistant",
544+
parentID: oldUserID,
545+
sessionID: chat.id,
546+
mode: "build",
547+
agent: "build",
548+
cost: 0,
549+
path: { cwd: "/tmp", root: "/tmp" },
550+
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
551+
modelID: ref.modelID,
552+
providerID: ref.providerID,
553+
time: { created: 200, completed: 201 },
554+
finish: "stop",
555+
})
556+
yield* sessions.updateMessage({
557+
id: newUserID,
558+
role: "user",
559+
sessionID: chat.id,
560+
agent: "build",
561+
model: ref,
562+
time: { created: 300 },
563+
})
564+
yield* sessions.updatePart({
565+
id: PartID.ascending(),
566+
messageID: newUserID,
567+
sessionID: chat.id,
568+
type: "text",
569+
text: "continue after rollover",
570+
})
571+
const latest = MessageV2.latest(yield* sessions.messages({ sessionID: chat.id }))
572+
expect(latest.user?.id).toBe(newUserID)
573+
expect(latest.assistant?.id).toBe(oldAssistantID)
574+
575+
const started = defer<void>()
576+
providerGetModelStarted.push(started.resolve)
577+
const loop = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild)
578+
yield* awaitWithTimeout(Effect.promise(() => started.promise), "rollover turn never started")
579+
yield* Fiber.interrupt(loop)
580+
}),
581+
)
582+
503583
it.instance("loop exits without an LLM request for interrupted orphan tool calls", () =>
504584
Effect.gen(function* () {
505585
const { llm } = yield* useServerConfig(providerCfg)

0 commit comments

Comments
 (0)