fix(ai): classify failed Google Interactions by reported errors (#58771)

This commit is contained in:
Aiden Cline 2026-10-24 18:15:06 -05:00 • committed by GitHub
parent 252ed4a2d8
commit 6ac6549a0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 40 additions and 3 deletions

View file

@ -155,6 +155,9 @@ const Interaction = Schema.StructWithRest(
id: Schema.optional(Schema.String),
status: Schema.String,
usage: Schema.optional(RawUsage),
errors: Schema.optional(
Schema.Array(Schema.Struct({ code: Schema.optional(Schema.String), message: Schema.optional(Schema.String) })),
),
}),
[Schema.Record(Schema.String, Schema.Unknown)],
)
@ -521,14 +524,17 @@ const step = Effect.fnUntraced(function* (state: ParserState, event: Event) {
})
case "interaction.completed": {
const interaction = event.interaction
if (interaction.status === "failed" || interaction.status === "cancelled")
if (interaction.status === "failed" || interaction.status === "cancelled") {
const messages = (interaction.errors ?? []).flatMap((error) => (error.message?.trim() ? [error.message] : []))
return yield* new AIError({
reason: classifyProviderFailure({
message: `Google Interactions ${interaction.status}`,
data: interaction,
message: messages.length > 0 ? messages.join("\n") : `Google Interactions ${interaction.status}`,
// The first coded error decides the classification; every message stays visible.
data: interaction.errors?.find((error) => error.code) ?? interaction,
rawBody: encodeJson(event),
}),
})
}
if (!["completed", "requires_action", "incomplete"].includes(interaction.status))
return yield* ProviderShared.eventError(
ADAPTER,

View file

@ -187,6 +187,37 @@ describe("Google Vertex Interactions", () => {
}),
)
it.effect("classifies failed interactions by their reported errors", () =>
Effect.gen(function* () {
const model = GoogleVertexInteractions.configure({ apiKey: "vertex-key" }).model("gemini-3.8-flash")
const cases = [
{
errors: [{ code: "permission_denied", message: "Access denied" }],
tag: "Authentication",
message: "Access denied",
},
{
errors: [{ message: "Out of credits" }, { code: "insufficient_quota", message: "Add billing" }],
tag: "QuotaExceeded",
message: "Out of credits\nAdd billing",
},
{ errors: [], tag: "UnknownProvider", message: "Google Interactions failed" },
]
for (const item of cases) {
const event = { event_type: "interaction.completed", interaction: { status: "failed", errors: item.errors } }
const error = yield* LLMClient.generate(LLM.request({ model, prompt: "Hi" })).pipe(
Effect.provide(fixedResponse(sseEvents(event))),
Effect.flip,
)
expect([error.reason._tag, error.reason.message, error.reason.body]).toEqual([
item.tag,
item.message,
JSON.stringify(event),
])
}
}),
)
it.effect("round-trips tool signatures under Vertex metadata without enabling storage", () =>
Effect.gen(function* () {
const model = GoogleVertexInteractions.configure({