Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1112,7 +1112,7 @@ const layer = Layer.effect(
lastAssistant?.finish &&
!["tool-calls"].includes(lastAssistant.finish) &&
!hasToolCalls &&
lastUser.id < lastAssistant.id
lastAssistant.parentID === lastUser.id
) {
const orphan = lastAssistantMsg?.parts.find(
(part): part is SessionV1.ToolPart => part.type === "tool" && isOrphanedInterruptedTool(part),
Expand Down
33 changes: 28 additions & 5 deletions packages/opencode/test/session/prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,10 @@ const succeedVoid = (deferred: Deferred.Deferred<void>) => {
Effect.runSync(Deferred.succeed(deferred, void 0).pipe(Effect.ignore))
}

const user = Effect.fn("test.user")(function* (sessionID: SessionID, text: string) {
const user = Effect.fn("test.user")(function* (sessionID: SessionID, text: string, id = MessageID.ascending()) {
const session = yield* Session.Service
const msg = yield* session.updateMessage({
id: MessageID.ascending(),
id,
role: "user",
sessionID,
agent: "build",
Expand All @@ -388,11 +388,14 @@ const user = Effect.fn("test.user")(function* (sessionID: SessionID, text: strin
return msg
})

const seed = Effect.fn("test.seed")(function* (sessionID: SessionID, opts?: { finish?: string }) {
const seed = Effect.fn("test.seed")(function* (
sessionID: SessionID,
opts?: { finish?: string; userID?: MessageID; assistantID?: MessageID },
) {
const session = yield* Session.Service
const msg = yield* user(sessionID, "hello")
const msg = yield* user(sessionID, "hello", opts?.userID)
const assistant: SessionV1.Assistant = {
id: MessageID.ascending(),
id: opts?.assistantID ?? MessageID.ascending(),
role: "assistant",
parentID: msg.id,
sessionID,
Expand Down Expand Up @@ -460,6 +463,26 @@ noLLMServer.instance(
{ config: cfg },
)

noLLMServer.instance(
"loop exits when a client-generated user id sorts after its server assistant id",
() =>
Effect.gen(function* () {
const prompt = yield* SessionPrompt.Service
const sessions = yield* Session.Service
const chat = yield* sessions.create({ title: "Pinned" })
const seeded = yield* seed(chat.id, {
finish: "stop",
userID: MessageID.make("msg_ffff_client_clock"),
assistantID: MessageID.make("msg_0000_server_clock"),
})

expect(seeded.assistant.id < seeded.user.id).toBe(true)
const result = yield* prompt.loop({ sessionID: chat.id })
expect(result.info.id).toBe(seeded.assistant.id)
}),
{ config: cfg },
)

it.instance("loop exits without an LLM request for interrupted orphan tool calls", () =>
Effect.gen(function* () {
const { llm } = yield* useServerConfig(providerCfg)
Expand Down
Loading