fix(core): preserve structured error messages (#33530)
This commit is contained in:
parent
10f6bb7878
commit
c17b9557f1
@ -14,7 +14,12 @@ export namespace FSUtil {
|
||||
export class FileSystemError extends Schema.TaggedErrorClass<FileSystemError>()("FileSystemError", {
|
||||
method: Schema.String,
|
||||
cause: Schema.optional(Schema.Defect()),
|
||||
}) {}
|
||||
}) {
|
||||
override get message() {
|
||||
const detail = this.cause instanceof Error ? this.cause.message : this.cause && String(this.cause)
|
||||
return `Filesystem operation failed: ${this.method}${detail ? `: ${detail}` : ""}`
|
||||
}
|
||||
}
|
||||
|
||||
export type Error = PlatformError | FileSystemError
|
||||
|
||||
|
||||
@ -10,7 +10,14 @@ export class AppProcessError extends Schema.TaggedErrorClass<AppProcessError>()(
|
||||
exitCode: Schema.optional(Schema.Number),
|
||||
stderr: Schema.optional(Schema.String),
|
||||
cause: Schema.optional(Schema.Defect()),
|
||||
}) {}
|
||||
}) {
|
||||
override get message() {
|
||||
const detail =
|
||||
this.stderr?.trim() || (this.cause instanceof Error ? this.cause.message : this.cause && String(this.cause))
|
||||
const status = this.exitCode === undefined ? "" : ` (exit ${this.exitCode})`
|
||||
return `Command failed${status}: ${this.command}${detail ? `: ${detail}` : ""}`
|
||||
}
|
||||
}
|
||||
|
||||
export interface RunOptions {
|
||||
readonly maxOutputBytes?: number
|
||||
|
||||
@ -5,7 +5,11 @@ import { SessionSchema } from "./schema"
|
||||
export class MessageDecodeError extends Schema.TaggedErrorClass<MessageDecodeError>()("Session.MessageDecodeError", {
|
||||
sessionID: SessionSchema.ID,
|
||||
messageID: SessionMessage.ID,
|
||||
}) {}
|
||||
}) {
|
||||
override get message() {
|
||||
return `Failed to decode message ${this.messageID} in session ${this.sessionID}`
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextSnapshotDecodeError extends Schema.TaggedErrorClass<ContextSnapshotDecodeError>()(
|
||||
"Session.ContextSnapshotDecodeError",
|
||||
|
||||
@ -21,7 +21,11 @@ export class ModelNotSelectedError extends Schema.TaggedErrorClass<ModelNotSelec
|
||||
{
|
||||
sessionID: SessionSchema.ID,
|
||||
},
|
||||
) {}
|
||||
) {
|
||||
override get message() {
|
||||
return `No model is available for session ${this.sessionID}`
|
||||
}
|
||||
}
|
||||
|
||||
export class ModelUnavailableError extends Schema.TaggedErrorClass<ModelUnavailableError>()(
|
||||
"SessionRunnerModel.ModelUnavailableError",
|
||||
@ -29,7 +33,11 @@ export class ModelUnavailableError extends Schema.TaggedErrorClass<ModelUnavaila
|
||||
providerID: ProviderV2.ID,
|
||||
modelID: ModelV2.ID,
|
||||
},
|
||||
) {}
|
||||
) {
|
||||
override get message() {
|
||||
return `Model unavailable: ${this.providerID}/${this.modelID}`
|
||||
}
|
||||
}
|
||||
|
||||
export class VariantUnavailableError extends Schema.TaggedErrorClass<VariantUnavailableError>()(
|
||||
"SessionRunnerModel.VariantUnavailableError",
|
||||
@ -38,7 +46,11 @@ export class VariantUnavailableError extends Schema.TaggedErrorClass<VariantUnav
|
||||
modelID: ModelV2.ID,
|
||||
variant: ModelV2.VariantID,
|
||||
},
|
||||
) {}
|
||||
) {
|
||||
override get message() {
|
||||
return `Variant unavailable for ${this.providerID}/${this.modelID}: ${this.variant}`
|
||||
}
|
||||
}
|
||||
|
||||
export class UnsupportedApiError extends Schema.TaggedErrorClass<UnsupportedApiError>()(
|
||||
"SessionRunnerModel.UnsupportedApiError",
|
||||
@ -47,7 +59,11 @@ export class UnsupportedApiError extends Schema.TaggedErrorClass<UnsupportedApiE
|
||||
modelID: ModelV2.ID,
|
||||
api: Schema.String,
|
||||
},
|
||||
) {}
|
||||
) {
|
||||
override get message() {
|
||||
return `Unsupported API for ${this.providerID}/${this.modelID}: ${this.api}`
|
||||
}
|
||||
}
|
||||
|
||||
export type Error = ModelNotSelectedError | ModelUnavailableError | VariantUnavailableError | UnsupportedApiError
|
||||
|
||||
|
||||
@ -82,7 +82,11 @@ export type ReconcileResult = { readonly _tag: "Unchanged" } | Updated | Replace
|
||||
export class InitializationBlocked extends Schema.TaggedErrorClass<InitializationBlocked>()(
|
||||
"SystemContext.InitializationBlocked",
|
||||
{ keys: Schema.Array(Key) },
|
||||
) {}
|
||||
) {
|
||||
override get message() {
|
||||
return `System context initialization blocked by unavailable sources: ${this.keys.join(", ")}`
|
||||
}
|
||||
}
|
||||
|
||||
export class DuplicateKeyError extends Schema.TaggedErrorClass<DuplicateKeyError>()("SystemContext.DuplicateKeyError", {
|
||||
key: Key,
|
||||
|
||||
@ -29,7 +29,12 @@ export interface BoundResult {
|
||||
export class StorageError extends Schema.TaggedErrorClass<StorageError>()("ToolOutputStore.StorageError", {
|
||||
operation: Schema.Literals(["encode", "write"]),
|
||||
cause: Schema.Defect(),
|
||||
}) {}
|
||||
}) {
|
||||
override get message() {
|
||||
const detail = this.cause instanceof Error ? this.cause.message : String(this.cause)
|
||||
return `Failed to ${this.operation} tool output${detail ? `: ${detail}` : ""}`
|
||||
}
|
||||
}
|
||||
|
||||
export type Error = StorageError
|
||||
|
||||
|
||||
@ -61,6 +61,7 @@ describe("AppProcess", () => {
|
||||
if (reason && reason._tag === "Fail") {
|
||||
expect(reason.error).toBeInstanceOf(AppProcess.AppProcessError)
|
||||
expect((reason.error as AppProcess.AppProcessError).exitCode).toBe(1)
|
||||
expect((reason.error as AppProcess.AppProcessError).message).toContain("Command failed (exit 1)")
|
||||
} else {
|
||||
throw new Error("expected fail reason")
|
||||
}
|
||||
|
||||
@ -212,6 +212,7 @@ describe("SessionRunnerModel", () => {
|
||||
modelID: "test-model",
|
||||
variant: "unknown",
|
||||
})
|
||||
expect(failure.message).toBe("Variant unavailable for test-provider/test-model: unknown")
|
||||
}),
|
||||
)
|
||||
|
||||
@ -328,6 +329,7 @@ describe("SessionRunnerModel", () => {
|
||||
modelID: "test-model",
|
||||
api: "aisdk:@ai-sdk/google",
|
||||
})
|
||||
expect(failure.message).toBe("Unsupported API for test-provider/test-model: aisdk:@ai-sdk/google")
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@ -206,6 +206,7 @@ describe("ToolRegistry", () => {
|
||||
|
||||
expect(Exit.isFailure(exit)).toBe(true)
|
||||
if (Exit.isFailure(exit)) expect(Option.getOrUndefined(Cause.findErrorOption(exit.cause))).toBe(retentionFailure)
|
||||
expect(retentionFailure.message).toBe("Failed to write tool output: disk full")
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@ -1078,6 +1078,11 @@ export class ModelNotFoundError extends Schema.TaggedErrorClass<ModelNotFoundErr
|
||||
suggestions: Schema.optional(Schema.Array(Schema.String)),
|
||||
cause: Schema.optional(Schema.Defect()),
|
||||
}) {
|
||||
override get message() {
|
||||
const suggestions = this.suggestions?.length ? ` Did you mean: ${this.suggestions.join(", ")}?` : ""
|
||||
return `Model not found: ${this.providerID}/${this.modelID}.${suggestions}`
|
||||
}
|
||||
|
||||
static isInstance(input: unknown): input is ModelNotFoundError {
|
||||
return input instanceof ModelNotFoundError
|
||||
}
|
||||
@ -1087,12 +1092,20 @@ export class InitError extends Schema.TaggedErrorClass<InitError>()("ProviderIni
|
||||
providerID: ProviderV2.ID,
|
||||
cause: Schema.optional(Schema.Defect()),
|
||||
}) {
|
||||
override get message() {
|
||||
return `Failed to initialize provider: ${this.providerID}`
|
||||
}
|
||||
|
||||
static isInstance(input: unknown): input is InitError {
|
||||
return input instanceof InitError
|
||||
}
|
||||
}
|
||||
|
||||
export class NoProvidersError extends Schema.TaggedErrorClass<NoProvidersError>()("ProviderNoProvidersError", {}) {
|
||||
override get message() {
|
||||
return "No providers are available"
|
||||
}
|
||||
|
||||
static isInstance(input: unknown): input is NoProvidersError {
|
||||
return input instanceof NoProvidersError
|
||||
}
|
||||
@ -1101,6 +1114,10 @@ export class NoProvidersError extends Schema.TaggedErrorClass<NoProvidersError>(
|
||||
export class NoModelsError extends Schema.TaggedErrorClass<NoModelsError>()("ProviderNoModelsError", {
|
||||
providerID: ProviderV2.ID,
|
||||
}) {
|
||||
override get message() {
|
||||
return `No models are available for provider: ${this.providerID}`
|
||||
}
|
||||
|
||||
static isInstance(input: unknown): input is NoModelsError {
|
||||
return input instanceof NoModelsError
|
||||
}
|
||||
|
||||
@ -1016,6 +1016,8 @@ it.instance("ModelNotFoundError includes suggestions for typos", () =>
|
||||
.pipe(Effect.flip)
|
||||
expect(error.suggestions).toBeDefined()
|
||||
expect((error.suggestions ?? []).length).toBeGreaterThan(0)
|
||||
expect(error.message).toContain("Model not found: anthropic/claude-sonet-4")
|
||||
expect(error.message).toContain("Did you mean:")
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user