Skip to content

Commit fb76857

Browse files
committed
fix(opencode): publish the session error when a turn is truncated
1 parent a034533 commit fb76857

4 files changed

Lines changed: 28 additions & 3 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,7 @@ const layer = Layer.effect(
13081308

13091309
handle.message.error = new SessionV1.OutputLengthError({}).toObject()
13101310
yield* sessions.updateMessage(handle.message)
1311+
yield* events.publish(Session.Event.Error, { sessionID, error: handle.message.error })
13111312
return "break" as const
13121313
}
13131314

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -692,15 +692,14 @@ it.instance("loop completes end-to-end recovery after a recoverable length finis
692692
const result = yield* prompt.loop({ sessionID: chat.id })
693693
expect(yield* llm.hits).toHaveLength(2)
694694
expect(result.info).toMatchObject({ role: "assistant", finish: "stop" })
695-
expect(result.parts).toEqual(
696-
expect.arrayContaining([expect.objectContaining({ type: "text", text: "complete" })]),
697-
)
695+
expect(result.parts).toEqual(expect.arrayContaining([expect.objectContaining({ type: "text", text: "complete" })]))
698696
}),
699697
)
700698

701699
it.instance("loop surfaces reasoning-only length finish as an error", () =>
702700
Effect.gen(function* () {
703701
const { llm } = yield* useServerConfig(providerCfg)
702+
const events = yield* EventV2Bridge.Service
704703
const prompt = yield* SessionPrompt.Service
705704
const sessions = yield* Session.Service
706705
const chat = yield* sessions.create({ title: "Pinned" })
@@ -711,9 +710,22 @@ it.instance("loop surfaces reasoning-only length finish as an error", () =>
711710
parts: [{ type: "text", text: "hello" }],
712711
})
713712

713+
const errors: NonNullable<SessionV1.Assistant["error"]>[] = []
714+
const expected = {
715+
name: "MessageOutputLengthError",
716+
data: {},
717+
} satisfies NonNullable<SessionV1.Assistant["error"]>
718+
const off = yield* events.listen((event) => {
719+
if (event.type !== Session.Event.Error.type) return Effect.void
720+
const data = event.data as typeof Session.Event.Error.data.Type
721+
if (data.sessionID === chat.id && data.error) errors.push(data.error)
722+
return Effect.void
723+
})
724+
714725
yield* llm.push(reply().reason("unfinished reasoning").length())
715726
const result = yield* prompt.loop({ sessionID: chat.id })
716727
const stored = yield* MessageV2.get({ sessionID: chat.id, messageID: result.info.id })
728+
yield* off
717729
expect(yield* llm.hits).toHaveLength(1)
718730
expect(result.info.role).toBe("assistant")
719731
if (result.info.role === "assistant") {
@@ -723,6 +735,7 @@ it.instance("loop surfaces reasoning-only length finish as an error", () =>
723735
})
724736
expect(stored.info).toMatchObject({ error: result.info.error })
725737
}
738+
expect(errors).toContainEqual(expected)
726739
expect(result.parts).toEqual(
727740
expect.arrayContaining([expect.objectContaining({ type: "reasoning", text: "unfinished reasoning" })]),
728741
)

‎packages/tui/src/util/error.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ export function errorFormat(error: unknown): string {
134134
}
135135

136136
export function errorMessage(error: unknown): string {
137+
if (named(error, "MessageOutputLengthError")) {
138+
return "Response was cut off by the output token limit before completing. Increase the model's limit.output or reduce reasoning effort."
139+
}
140+
137141
if (error instanceof Error) {
138142
if (error.message) return error.message
139143
if (error.name) return error.name

‎packages/tui/test/util/error.test.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ describe("util.error", () => {
2222
expect(data.code).toBe("E_BAD")
2323
})
2424

25+
test("maps MessageOutputLengthError to a readable message", () => {
26+
const err = { name: "MessageOutputLengthError", data: {} }
27+
expect(errorMessage(err)).toBe(
28+
"Response was cut off by the output token limit before completing. Increase the model's limit.output or reduce reasoning effort.",
29+
)
30+
})
31+
2532
test("never returns bare {} for opaque object errors", () => {
2633
expect(errorFormat({})).not.toBe("{}")
2734
expect(errorFormat({})).toContain("no message")

0 commit comments

Comments
 (0)