fix: use mapError instead of orDie for context snapshot decoding (#30905)
Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>
This commit is contained in:
parent
a136caa1b3
commit
a261b55e43
5 changed files with 46 additions and 4 deletions
|
|
@ -92,7 +92,7 @@ export class OperationUnavailableError extends Schema.TaggedErrorClass<Operation
|
||||||
},
|
},
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
export { MessageDecodeError } from "./session/error"
|
export { ContextSnapshotDecodeError, MessageDecodeError } from "./session/error"
|
||||||
|
|
||||||
export class PromptConflictError extends Schema.TaggedErrorClass<PromptConflictError>()("Session.PromptConflictError", {
|
export class PromptConflictError extends Schema.TaggedErrorClass<PromptConflictError>()("Session.PromptConflictError", {
|
||||||
sessionID: SessionSchema.ID,
|
sessionID: SessionSchema.ID,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { EventV2 } from "../event"
|
||||||
import { Location } from "../location"
|
import { Location } from "../location"
|
||||||
import { SystemContext } from "../system-context"
|
import { SystemContext } from "../system-context"
|
||||||
import { SystemContextRegistry } from "../system-context-registry"
|
import { SystemContextRegistry } from "../system-context-registry"
|
||||||
|
import { ContextSnapshotDecodeError } from "./error"
|
||||||
import { SessionEvent } from "./event"
|
import { SessionEvent } from "./event"
|
||||||
import { SessionInput } from "./input"
|
import { SessionInput } from "./input"
|
||||||
import { SessionMessageID } from "./message-id"
|
import { SessionMessageID } from "./message-id"
|
||||||
|
|
@ -49,7 +50,7 @@ export function prepare(
|
||||||
context: SystemContextRegistry.Interface,
|
context: SystemContextRegistry.Interface,
|
||||||
sessionID: SessionSchema.ID,
|
sessionID: SessionSchema.ID,
|
||||||
location: Location.Ref,
|
location: Location.Ref,
|
||||||
): Effect.Effect<Prepared, SystemContext.InitializationBlocked> {
|
): Effect.Effect<Prepared, SystemContext.InitializationBlocked | ContextSnapshotDecodeError> {
|
||||||
return retryRevisionMismatch(() => prepareOnce(db, events, context, sessionID, location)).pipe(
|
return retryRevisionMismatch(() => prepareOnce(db, events, context, sessionID, location)).pipe(
|
||||||
Effect.withSpan("SessionContextEpoch.prepare"),
|
Effect.withSpan("SessionContextEpoch.prepare"),
|
||||||
)
|
)
|
||||||
|
|
@ -69,7 +70,9 @@ const prepareOnce = Effect.fnUntraced(function* (
|
||||||
return { baseline: generation.baseline, baselineSeq }
|
return { baseline: generation.baseline, baselineSeq }
|
||||||
}
|
}
|
||||||
|
|
||||||
const snapshot = yield* Schema.decodeUnknownEffect(SystemContext.Snapshot)(stored.snapshot).pipe(Effect.orDie)
|
const snapshot = yield* Schema.decodeUnknownEffect(SystemContext.Snapshot)(stored.snapshot).pipe(
|
||||||
|
Effect.mapError((error) => new ContextSnapshotDecodeError({ sessionID, details: String(error) })),
|
||||||
|
)
|
||||||
const result =
|
const result =
|
||||||
stored.replacement_seq === null
|
stored.replacement_seq === null
|
||||||
? yield* SystemContext.reconcile(value, snapshot)
|
? yield* SystemContext.reconcile(value, snapshot)
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,15 @@ export class MessageDecodeError extends Schema.TaggedErrorClass<MessageDecodeErr
|
||||||
sessionID: SessionSchema.ID,
|
sessionID: SessionSchema.ID,
|
||||||
messageID: SessionMessage.ID,
|
messageID: SessionMessage.ID,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
|
export class ContextSnapshotDecodeError extends Schema.TaggedErrorClass<ContextSnapshotDecodeError>()(
|
||||||
|
"Session.ContextSnapshotDecodeError",
|
||||||
|
{
|
||||||
|
sessionID: SessionSchema.ID,
|
||||||
|
details: Schema.String,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
override get message() {
|
||||||
|
return `Failed to decode context snapshot for session ${this.sessionID}: ${this.details}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ export * as SessionRunner from "./index"
|
||||||
import type { LLMError } from "@opencode-ai/llm"
|
import type { LLMError } from "@opencode-ai/llm"
|
||||||
import { Context, Effect, Schema } from "effect"
|
import { Context, Effect, Schema } from "effect"
|
||||||
import { SessionSchema } from "../schema"
|
import { SessionSchema } from "../schema"
|
||||||
import type { MessageDecodeError } from "../error"
|
import type { ContextSnapshotDecodeError, MessageDecodeError } from "../error"
|
||||||
import { SessionRunnerModel } from "./model"
|
import { SessionRunnerModel } from "./model"
|
||||||
import type { SystemContext } from "../../system-context"
|
import type { SystemContext } from "../../system-context"
|
||||||
|
|
||||||
|
|
@ -19,6 +19,7 @@ export type RunError =
|
||||||
| LLMError
|
| LLMError
|
||||||
| SessionRunnerModel.Error
|
| SessionRunnerModel.Error
|
||||||
| MessageDecodeError
|
| MessageDecodeError
|
||||||
|
| ContextSnapshotDecodeError
|
||||||
| StepLimitExceededError
|
| StepLimitExceededError
|
||||||
| SystemContext.InitializationBlocked
|
| SystemContext.InitializationBlocked
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||||
import { QuestionV2 } from "@opencode-ai/core/question"
|
import { QuestionV2 } from "@opencode-ai/core/question"
|
||||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||||
import { SessionV2 } from "@opencode-ai/core/session"
|
import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
|
import { ContextSnapshotDecodeError } from "@opencode-ai/core/session/error"
|
||||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||||
import { SessionInput } from "@opencode-ai/core/session/input"
|
import { SessionInput } from "@opencode-ai/core/session/input"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
|
|
@ -631,6 +632,31 @@ describe("SessionRunnerLLM", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("fails gracefully when a stored context snapshot cannot be decoded", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
yield* setup
|
||||||
|
const session = yield* SessionV2.Service
|
||||||
|
const { db } = yield* Database.Service
|
||||||
|
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "First" }), resume: false })
|
||||||
|
response = []
|
||||||
|
yield* session.resume(sessionID)
|
||||||
|
yield* db
|
||||||
|
.update(SessionContextEpochTable)
|
||||||
|
.set({ snapshot: { invalid: { value: "bad" } } })
|
||||||
|
.where(eq(SessionContextEpochTable.session_id, sessionID))
|
||||||
|
.run()
|
||||||
|
.pipe(Effect.orDie)
|
||||||
|
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Second" }), resume: false })
|
||||||
|
requests.length = 0
|
||||||
|
|
||||||
|
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
|
||||||
|
|
||||||
|
expect(Exit.isFailure(exit)).toBe(true)
|
||||||
|
if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(ContextSnapshotDecodeError)
|
||||||
|
expect(requests).toHaveLength(0)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("does not create a source Location epoch after a concurrent Session move", () =>
|
it.effect("does not create a source Location epoch after a concurrent Session move", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue