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", {
|
export class FileSystemError extends Schema.TaggedErrorClass<FileSystemError>()("FileSystemError", {
|
||||||
method: Schema.String,
|
method: Schema.String,
|
||||||
cause: Schema.optional(Schema.Defect()),
|
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
|
export type Error = PlatformError | FileSystemError
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,14 @@ export class AppProcessError extends Schema.TaggedErrorClass<AppProcessError>()(
|
|||||||
exitCode: Schema.optional(Schema.Number),
|
exitCode: Schema.optional(Schema.Number),
|
||||||
stderr: Schema.optional(Schema.String),
|
stderr: Schema.optional(Schema.String),
|
||||||
cause: Schema.optional(Schema.Defect()),
|
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 {
|
export interface RunOptions {
|
||||||
readonly maxOutputBytes?: number
|
readonly maxOutputBytes?: number
|
||||||
|
|||||||
@ -5,7 +5,11 @@ import { SessionSchema } from "./schema"
|
|||||||
export class MessageDecodeError extends Schema.TaggedErrorClass<MessageDecodeError>()("Session.MessageDecodeError", {
|
export class MessageDecodeError extends Schema.TaggedErrorClass<MessageDecodeError>()("Session.MessageDecodeError", {
|
||||||
sessionID: SessionSchema.ID,
|
sessionID: SessionSchema.ID,
|
||||||
messageID: SessionMessage.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>()(
|
export class ContextSnapshotDecodeError extends Schema.TaggedErrorClass<ContextSnapshotDecodeError>()(
|
||||||
"Session.ContextSnapshotDecodeError",
|
"Session.ContextSnapshotDecodeError",
|
||||||
|
|||||||
@ -21,7 +21,11 @@ export class ModelNotSelectedError extends Schema.TaggedErrorClass<ModelNotSelec
|
|||||||
{
|
{
|
||||||
sessionID: SessionSchema.ID,
|
sessionID: SessionSchema.ID,
|
||||||
},
|
},
|
||||||
) {}
|
) {
|
||||||
|
override get message() {
|
||||||
|
return `No model is available for session ${this.sessionID}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ModelUnavailableError extends Schema.TaggedErrorClass<ModelUnavailableError>()(
|
export class ModelUnavailableError extends Schema.TaggedErrorClass<ModelUnavailableError>()(
|
||||||
"SessionRunnerModel.ModelUnavailableError",
|
"SessionRunnerModel.ModelUnavailableError",
|
||||||
@ -29,7 +33,11 @@ export class ModelUnavailableError extends Schema.TaggedErrorClass<ModelUnavaila
|
|||||||
providerID: ProviderV2.ID,
|
providerID: ProviderV2.ID,
|
||||||
modelID: ModelV2.ID,
|
modelID: ModelV2.ID,
|
||||||
},
|
},
|
||||||
) {}
|
) {
|
||||||
|
override get message() {
|
||||||
|
return `Model unavailable: ${this.providerID}/${this.modelID}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class VariantUnavailableError extends Schema.TaggedErrorClass<VariantUnavailableError>()(
|
export class VariantUnavailableError extends Schema.TaggedErrorClass<VariantUnavailableError>()(
|
||||||
"SessionRunnerModel.VariantUnavailableError",
|
"SessionRunnerModel.VariantUnavailableError",
|
||||||
@ -38,7 +46,11 @@ export class VariantUnavailableError extends Schema.TaggedErrorClass<VariantUnav
|
|||||||
modelID: ModelV2.ID,
|
modelID: ModelV2.ID,
|
||||||
variant: ModelV2.VariantID,
|
variant: ModelV2.VariantID,
|
||||||
},
|
},
|
||||||
) {}
|
) {
|
||||||
|
override get message() {
|
||||||
|
return `Variant unavailable for ${this.providerID}/${this.modelID}: ${this.variant}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class UnsupportedApiError extends Schema.TaggedErrorClass<UnsupportedApiError>()(
|
export class UnsupportedApiError extends Schema.TaggedErrorClass<UnsupportedApiError>()(
|
||||||
"SessionRunnerModel.UnsupportedApiError",
|
"SessionRunnerModel.UnsupportedApiError",
|
||||||
@ -47,7 +59,11 @@ export class UnsupportedApiError extends Schema.TaggedErrorClass<UnsupportedApiE
|
|||||||
modelID: ModelV2.ID,
|
modelID: ModelV2.ID,
|
||||||
api: Schema.String,
|
api: Schema.String,
|
||||||
},
|
},
|
||||||
) {}
|
) {
|
||||||
|
override get message() {
|
||||||
|
return `Unsupported API for ${this.providerID}/${this.modelID}: ${this.api}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type Error = ModelNotSelectedError | ModelUnavailableError | VariantUnavailableError | UnsupportedApiError
|
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>()(
|
export class InitializationBlocked extends Schema.TaggedErrorClass<InitializationBlocked>()(
|
||||||
"SystemContext.InitializationBlocked",
|
"SystemContext.InitializationBlocked",
|
||||||
{ keys: Schema.Array(Key) },
|
{ 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", {
|
export class DuplicateKeyError extends Schema.TaggedErrorClass<DuplicateKeyError>()("SystemContext.DuplicateKeyError", {
|
||||||
key: Key,
|
key: Key,
|
||||||
|
|||||||
@ -29,7 +29,12 @@ export interface BoundResult {
|
|||||||
export class StorageError extends Schema.TaggedErrorClass<StorageError>()("ToolOutputStore.StorageError", {
|
export class StorageError extends Schema.TaggedErrorClass<StorageError>()("ToolOutputStore.StorageError", {
|
||||||
operation: Schema.Literals(["encode", "write"]),
|
operation: Schema.Literals(["encode", "write"]),
|
||||||
cause: Schema.Defect(),
|
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
|
export type Error = StorageError
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,7 @@ describe("AppProcess", () => {
|
|||||||
if (reason && reason._tag === "Fail") {
|
if (reason && reason._tag === "Fail") {
|
||||||
expect(reason.error).toBeInstanceOf(AppProcess.AppProcessError)
|
expect(reason.error).toBeInstanceOf(AppProcess.AppProcessError)
|
||||||
expect((reason.error as AppProcess.AppProcessError).exitCode).toBe(1)
|
expect((reason.error as AppProcess.AppProcessError).exitCode).toBe(1)
|
||||||
|
expect((reason.error as AppProcess.AppProcessError).message).toContain("Command failed (exit 1)")
|
||||||
} else {
|
} else {
|
||||||
throw new Error("expected fail reason")
|
throw new Error("expected fail reason")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -212,6 +212,7 @@ describe("SessionRunnerModel", () => {
|
|||||||
modelID: "test-model",
|
modelID: "test-model",
|
||||||
variant: "unknown",
|
variant: "unknown",
|
||||||
})
|
})
|
||||||
|
expect(failure.message).toBe("Variant unavailable for test-provider/test-model: unknown")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -328,6 +329,7 @@ describe("SessionRunnerModel", () => {
|
|||||||
modelID: "test-model",
|
modelID: "test-model",
|
||||||
api: "aisdk:@ai-sdk/google",
|
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)
|
expect(Exit.isFailure(exit)).toBe(true)
|
||||||
if (Exit.isFailure(exit)) expect(Option.getOrUndefined(Cause.findErrorOption(exit.cause))).toBe(retentionFailure)
|
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)),
|
suggestions: Schema.optional(Schema.Array(Schema.String)),
|
||||||
cause: Schema.optional(Schema.Defect()),
|
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 {
|
static isInstance(input: unknown): input is ModelNotFoundError {
|
||||||
return input instanceof ModelNotFoundError
|
return input instanceof ModelNotFoundError
|
||||||
}
|
}
|
||||||
@ -1087,12 +1092,20 @@ export class InitError extends Schema.TaggedErrorClass<InitError>()("ProviderIni
|
|||||||
providerID: ProviderV2.ID,
|
providerID: ProviderV2.ID,
|
||||||
cause: Schema.optional(Schema.Defect()),
|
cause: Schema.optional(Schema.Defect()),
|
||||||
}) {
|
}) {
|
||||||
|
override get message() {
|
||||||
|
return `Failed to initialize provider: ${this.providerID}`
|
||||||
|
}
|
||||||
|
|
||||||
static isInstance(input: unknown): input is InitError {
|
static isInstance(input: unknown): input is InitError {
|
||||||
return input instanceof InitError
|
return input instanceof InitError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NoProvidersError extends Schema.TaggedErrorClass<NoProvidersError>()("ProviderNoProvidersError", {}) {
|
export class NoProvidersError extends Schema.TaggedErrorClass<NoProvidersError>()("ProviderNoProvidersError", {}) {
|
||||||
|
override get message() {
|
||||||
|
return "No providers are available"
|
||||||
|
}
|
||||||
|
|
||||||
static isInstance(input: unknown): input is NoProvidersError {
|
static isInstance(input: unknown): input is NoProvidersError {
|
||||||
return input instanceof NoProvidersError
|
return input instanceof NoProvidersError
|
||||||
}
|
}
|
||||||
@ -1101,6 +1114,10 @@ export class NoProvidersError extends Schema.TaggedErrorClass<NoProvidersError>(
|
|||||||
export class NoModelsError extends Schema.TaggedErrorClass<NoModelsError>()("ProviderNoModelsError", {
|
export class NoModelsError extends Schema.TaggedErrorClass<NoModelsError>()("ProviderNoModelsError", {
|
||||||
providerID: ProviderV2.ID,
|
providerID: ProviderV2.ID,
|
||||||
}) {
|
}) {
|
||||||
|
override get message() {
|
||||||
|
return `No models are available for provider: ${this.providerID}`
|
||||||
|
}
|
||||||
|
|
||||||
static isInstance(input: unknown): input is NoModelsError {
|
static isInstance(input: unknown): input is NoModelsError {
|
||||||
return input instanceof NoModelsError
|
return input instanceof NoModelsError
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1016,6 +1016,8 @@ it.instance("ModelNotFoundError includes suggestions for typos", () =>
|
|||||||
.pipe(Effect.flip)
|
.pipe(Effect.flip)
|
||||||
expect(error.suggestions).toBeDefined()
|
expect(error.suggestions).toBeDefined()
|
||||||
expect((error.suggestions ?? []).length).toBeGreaterThan(0)
|
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