Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/ai/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ The dependency arrow points down: `providers/*.ts` files import protocol routes
- `joinText(parts)` — joins an array of `TextPart` (or anything with a `.text`) with newlines. Use this anywhere a protocol flattens text content into a single string for a provider field.
- `parseToolInput(route, name, raw)` — Schema-decodes a tool-call argument string with the canonical "Invalid JSON input for `<route>` tool call `<name>`" error message. Treats empty input as `{}`.
- `parseJson(route, raw, message)` — generic JSON-via-Schema decode for non-tool bodies.
- `eventError(route, message, ...)` — typed `InvalidProviderOutput` constructor for stream-time decode failures.
- `validateWith(decoder)` — maps Schema decode errors to `InvalidRequest`. `Route.make(...)` uses this for body validation; lower-level routes can reuse it.
- `eventError(route, message, ...)` — typed `MalformedResponse` constructor for stream-time decode failures.
- `validateWith(decoder)` — maps Schema decode errors to `BadRequest`. `Route.make(...)` uses this for body validation; lower-level routes can reuse it.
- `matchToolChoice(provider, choice, branches)` — branches over `LLMRequest["toolChoice"]` for provider-specific lowering.

If you find yourself copying a 3-to-5-line snippet between two protocols, lift it into `ProviderShared` next to these helpers rather than duplicating.
Expand Down
8 changes: 7 additions & 1 deletion packages/ai/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ export { LLMClient } from "./route/client"
export { Auth } from "./route/auth"
export { Provider } from "./provider"
export { ProviderPackage } from "./provider-package"
export { isContextOverflow, isContextOverflowFailure } from "./provider-error"
export {
classifyApiFailure,
extractApiFailureCode,
isContextOverflow,
isContextOverflowFailure,
type ApiFailure,
} from "./provider-error"
export type {
RouteModelInput,
RouteRoutedModelInput,
Expand Down
20 changes: 6 additions & 14 deletions packages/ai/src/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { LLMClient } from "./route/client"
import {
GenerationOptions,
HttpOptions,
InvalidProviderOutputReason,
LLMError,
MalformedResponse,
type LLMError,
LLMEvent,
LLMRequest,
LLMResponse,
Expand Down Expand Up @@ -121,22 +121,14 @@ const runGenerateObject = Effect.fn("LLM.generateObject")(function* (
(event) => LLMEvent.is.toolCall(event) && event.name === GENERATE_OBJECT_TOOL_NAME,
)
if (!call || !LLMEvent.is.toolCall(call))
return yield* new LLMError({
module: "LLM",
method: "generateObject",
reason: new InvalidProviderOutputReason({
message: `generateObject: model did not call the forced \`${GENERATE_OBJECT_TOOL_NAME}\` tool`,
}),
return yield* new MalformedResponse({
message: `generateObject: model did not call the forced \`${GENERATE_OBJECT_TOOL_NAME}\` tool`,
})
const object = yield* tool._decode(call.input).pipe(
Effect.mapError(
(error) =>
new LLMError({
module: "LLM",
method: "generateObject",
reason: new InvalidProviderOutputReason({
message: `generateObject: tool input failed schema decode: ${error.message}`,
}),
new MalformedResponse({
message: `generateObject: tool input failed schema decode: ${error.message}`,
}),
),
)
Expand Down
9 changes: 2 additions & 7 deletions packages/ai/src/protocols/anthropic-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Endpoint } from "../route/endpoint"
import { Framing } from "../route/framing"
import { Protocol } from "../route/protocol"
import {
LLMError,
LLMEvent,
Usage,
type CacheHint,
Expand All @@ -20,7 +19,7 @@ import {
type ToolResultPart,
} from "../schema"
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
import { classifyProviderFailure } from "../provider-error"
import { classifyApiFailure } from "../provider-error"
import * as Cache from "./utils/cache"
import { Lifecycle } from "./utils/lifecycle"
import { ToolSchemaProjection } from "./utils/tool-schema"
Expand Down Expand Up @@ -834,11 +833,7 @@ const providerErrorMessage = (event: AnthropicEvent): string => {
}

const onError = (event: AnthropicEvent) =>
new LLMError({
module: ADAPTER,
method: "stream",
reason: classifyProviderFailure({ message: providerErrorMessage(event), code: event.error?.type }),
})
classifyApiFailure({ message: providerErrorMessage(event), code: event.error?.type })

const step = (state: ParserState, event: AnthropicEvent) => {
if (event.type === "message_start") return Effect.succeed(onMessageStart(state, event))
Expand Down
13 changes: 4 additions & 9 deletions packages/ai/src/protocols/bedrock-converse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Route } from "../route/client"
import { Endpoint } from "../route/endpoint"
import { Protocol } from "../route/protocol"
import {
LLMError,
LLMEvent,
Usage,
type CacheHint,
Expand All @@ -18,7 +17,7 @@ import {
type ToolResultPart,
} from "../schema"
import { BedrockEventStream } from "./bedrock-event-stream"
import { classifyProviderFailure } from "../provider-error"
import { classifyApiFailure } from "../provider-error"
import { JsonObject, optionalArray, ProviderShared } from "./shared"
import { BedrockAuth } from "./utils/bedrock-auth"
import { BedrockCache } from "./utils/bedrock-cache"
Expand Down Expand Up @@ -597,13 +596,9 @@ const step = (state: ParserState, event: BedrockEvent) =>
] as const
).find((entry) => entry[1] !== undefined)
if (exception) {
return yield* new LLMError({
module: ADAPTER,
method: "stream",
reason: classifyProviderFailure({
message: exception[1]?.message ?? "Bedrock Converse stream error",
code: exception[0],
}),
return yield* classifyApiFailure({
message: exception[1]?.message ?? "Bedrock Converse stream error",
code: exception[0],
})
}

Expand Down
9 changes: 2 additions & 7 deletions packages/ai/src/protocols/openai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Endpoint } from "../route/endpoint"
import { HttpTransport, WebSocketTransport } from "../route/transport"
import { Protocol } from "../route/protocol"
import {
LLMError,
LLMEvent,
Usage,
type FinishReason,
Expand All @@ -20,7 +19,7 @@ import {
type ToolResultPart,
} from "../schema"
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
import { classifyProviderFailure } from "../provider-error"
import { classifyApiFailure } from "../provider-error"
import { OpenAIOptions } from "./utils/openai-options"
import { Lifecycle } from "./utils/lifecycle"
import { ToolSchemaProjection } from "./utils/tool-schema"
Expand Down Expand Up @@ -910,11 +909,7 @@ const providerErrorMessage = (event: OpenAIResponsesEvent, fallback: string): st
const providerError = (event: OpenAIResponsesEvent, fallback: string) => {
const code = event.code || event.error?.code || event.response?.error?.code || undefined
const message = providerErrorMessage(event, fallback)
return new LLMError({
module: ADAPTER,
method: "stream",
reason: classifyProviderFailure({ message, code }),
})
return classifyApiFailure({ message, code })
}

const step = (state: ParserState, event: OpenAIResponsesEvent) => {
Expand Down
22 changes: 6 additions & 16 deletions packages/ai/src/protocols/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { Effect, Schema, Stream } from "effect"
import * as Sse from "effect/unstable/encoding/Sse"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import {
InvalidProviderOutputReason,
InvalidRequestReason,
LLMError,
BadRequest,
MalformedResponse,
type LLMError,
type ContentPart,
type LLMRequest,
type MediaPart,
Expand Down Expand Up @@ -88,11 +88,7 @@ export const sumTokens = (...values: ReadonlyArray<number | undefined>): number
}

export const eventError = (route: string, message: string, raw?: string) =>
new LLMError({
module: "ProviderShared",
method: "stream",
reason: new InvalidProviderOutputReason({ route, message, raw }),
})
new MalformedResponse({ route, message, raw })

export const parseJson = (route: string, input: string, message: string) =>
Effect.try({
Expand Down Expand Up @@ -252,15 +248,9 @@ export const sseFraming = (bytes: Stream.Stream<Uint8Array, LLMError>): Stream.S
* Canonical invalid-request constructor. Lift one-line `const invalid =
* (message) => invalidRequest(message)` aliases out of every
* route so the error constructor lives in one place. If we ever extend
* `InvalidRequestReason` with route context or trace metadata, the change
* lands here.
* `BadRequest` with route context or trace metadata, the change lands here.
*/
export const invalidRequest = (message: string) =>
new LLMError({
module: "ProviderShared",
method: "request",
reason: new InvalidRequestReason({ message }),
})
export const invalidRequest = (message: string) => new BadRequest({ message })

export const matchToolChoice = <Auto, None, Required, Tool>(
route: string,
Expand Down
4 changes: 2 additions & 2 deletions packages/ai/src/protocols/utils/tool-stream.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Effect } from "effect"
import { LLMError, LLMEvent, type ProviderMetadata, type ToolCall } from "../../schema"
import { isLLMError, LLMEvent, type LLMError, type ProviderMetadata, type ToolCall } from "../../schema"
import { eventError, parseToolInput, type ToolAccumulator } from "../shared"

type StreamKey = string | number
Expand Down Expand Up @@ -95,7 +95,7 @@ const appendTool = <K extends StreamKey>(
}

export const isError = <K extends StreamKey>(result: AppendOutcome<K> | LLMError): result is LLMError =>
result instanceof LLMError
isLLMError(result)

/**
* Register a tool call whose start event arrived before any argument deltas.
Expand Down
Loading
Loading