New:Socket for Asana Is Now Available.Learn more
Get Started

@opencode-ai/ai

Package Overview
Dependencies
Maintainers
2
Versions
1018
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@opencode-ai/ai - npm Package Compare versions

Comparing version
0.0.0-dev-18409
to
0.0.0-dev-18411
+5
-4
dist/image.js
import { Effect, Schema } from "effect";
import { HttpOptions, InvalidRequestReason, AIError, ModelID, ProviderID, ProviderMetadata, Usage, } from "./schema/index.js";
import { HttpOptions, InvalidRequestError, AIError, ModelID, ProviderID, ProviderMetadata, Usage, } from "./schema/index.js";
import { ImageClient, Service } from "./image-client.js";

@@ -93,5 +93,6 @@ export class ImageModel {

catch: (error) => new AIError({
module: "Image",
method: "generate",
reason: new InvalidRequestReason({ message: error instanceof Error ? error.message : String(error) }),
reason: new InvalidRequestError({
message: error instanceof Error ? error.message : String(error),
cause: error,
}),
}),

@@ -98,0 +99,0 @@ }).pipe(Effect.flatMap((request) => ImageClient.generate(request)));

import { Effect, JsonSchema, Schema } from "effect";
import { LLMClient, Service } from "./route/client.js";
import { GenerationOptions, HttpOptions, InvalidProviderOutputReason, AIError, LLMEvent, LLMRequest, LLMResponse, Message, LanguageModel, SystemPart, ToolChoice, ToolDefinition, } from "./schema/index.js";
import { GenerationOptions, HttpOptions, InvalidProviderOutputError, AIError, LLMEvent, LLMRequest, LLMResponse, Message, LanguageModel, SystemPart, ToolChoice, ToolDefinition, } from "./schema/index.js";
import { make as makeTool, toDefinitions } from "./tool.js";

@@ -46,5 +46,3 @@ export const generate = LLMClient.generate;

return yield* new AIError({
module: "LLM",
method: "generateObject",
reason: new InvalidProviderOutputReason({
reason: new InvalidProviderOutputError({
message: `generateObject: model did not call the forced \`${GENERATE_OBJECT_TOOL_NAME}\` tool`,

@@ -54,6 +52,5 @@ }),

const object = yield* tool._decode(call.input).pipe(Effect.mapError((error) => new AIError({
module: "LLM",
method: "generateObject",
reason: new InvalidProviderOutputReason({
reason: new InvalidProviderOutputError({
message: `generateObject: tool input failed schema decode: ${error.message}`,
cause: error,
}),

@@ -60,0 +57,0 @@ })));

@@ -1170,7 +1170,9 @@ import { Buffer } from "node:buffer";

};
const onError = (event) => Effect.fail(new AIError({
module: ADAPTER,
method: "stream",
reason: classifyProviderFailure({ message: providerErrorMessage(event), code: event.error?.type }),
}));
const onError = (event) => {
const message = providerErrorMessage(event);
const body = ProviderShared.encodeJson(event);
return Effect.fail(new AIError({
reason: classifyProviderFailure({ message, rawBody: body }),
}));
};
const isKnownStreamBlockType = (type) => type === "text" ||

@@ -1177,0 +1179,0 @@ type === "thinking" ||

@@ -510,10 +510,8 @@ import { Effect, Schema } from "effect";

if (event.exception) {
const message = event.exception.details.message ?? event.exception.details.originalMessage ?? "Bedrock Converse stream error";
const body = ProviderShared.encodeJson(event);
return yield* new AIError({
module: ADAPTER,
method: "stream",
reason: classifyProviderFailure({
message: event.exception.details.message ??
event.exception.details.originalMessage ??
"Bedrock Converse stream error",
code: event.exception.type,
message,
rawBody: body,
}),

@@ -520,0 +518,0 @@ });

import { EventStreamCodec } from "@smithy/eventstream-codec";
import { fromUtf8, toUtf8 } from "@smithy/util-utf8";
import { Effect, Stream } from "effect";
import { Effect, Encoding, Stream } from "effect";
import { AIError, AIErrorReason } from "../schema/index.js";
import { Framing } from "../route/framing.js";

@@ -33,5 +34,7 @@ import { ProviderShared } from "./shared.js";

try: () => eventCodec.decode(view.subarray(0, totalLength)),
catch: (error) => ProviderShared.eventError(route, `Failed to decode Bedrock Converse event-stream frame: ${error instanceof Error ? error.message : String(error)}`),
catch: (error) => ProviderShared.eventError(route, `Failed to decode Bedrock Converse event-stream frame: ${error instanceof Error ? error.message : String(error)}`, Encoding.encodeBase64(view.subarray(0, totalLength)), error),
});
cursor = { buffer: cursor.buffer, offset: cursor.offset + totalLength };
const payload = utf8.decode(decoded.body);
const body = ProviderShared.encodeJson({ headers: decoded.headers, body: payload });
const messageType = decoded.headers[":message-type"]?.value;

@@ -42,3 +45,3 @@ if (messageType === "error") {

return yield* ProviderShared.eventError(route, [code, message].filter((value) => typeof value === "string").join(": ") ||
"Bedrock Converse event-stream error");
"Bedrock Converse event-stream error", body);
}

@@ -52,3 +55,2 @@ const eventType = messageType === "event"

continue;
const payload = utf8.decode(decoded.body);
if (!payload)

@@ -60,5 +62,12 @@ continue;

// against ad-hoc `JSON.parse` calls.
const parsed = (yield* ProviderShared.parseJson(route, payload, "Failed to parse Bedrock Converse event-stream payload"));
const parsed = (yield* ProviderShared.parseJson(route, payload, "Failed to parse Bedrock Converse event-stream payload").pipe(Effect.mapError((error) => new AIError({
reason: AIErrorReason.make({ ...error.reason, message: error.message, cause: error.reason.cause, body }),
}))));
delete parsed.p;
out.push(messageType === "exception" ? { exception: { type: eventType, details: parsed } } : { [eventType]: parsed });
out.push({
...(messageType === "exception"
? { exception: { type: eventType, details: parsed } }
: { [eventType]: parsed }),
rawBody: body,
});
}

@@ -75,4 +84,5 @@ return [cursor, out];

id: "aws-event-stream",
body: (frame) => ("rawBody" in frame && typeof frame.rawBody === "string" ? frame.rawBody : undefined),
frame: (bytes) => bytes.pipe(Stream.mapAccumEffect(() => initialFrameBuffer, consumeFrames(route))),
});
export * as BedrockEventStream from "./bedrock-event-stream.js";

@@ -178,2 +178,3 @@ import { Schema } from "effect";

}, string, {
readonly error?: unknown;
readonly candidates?: readonly {

@@ -180,0 +181,0 @@ readonly content?: {

@@ -8,3 +8,4 @@ import { Effect, Option, Schema } from "effect";

import { Protocol } from "../route/protocol.js";
import { LLMEvent, Usage, } from "../schema/index.js";
import { AIError, LLMEvent, Usage, } from "../schema/index.js";
import { classifyProviderFailure } from "../provider-error.js";
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";

@@ -155,2 +156,3 @@ import { GeminiToolSchema } from "./utils/gemini-tool-schema.js";

const GeminiEvent = Schema.Struct({
error: Schema.optional(Schema.Unknown),
candidates: optionalNull(Schema.Array(GeminiCandidate)),

@@ -484,2 +486,12 @@ promptFeedback: optionalNull(GeminiPromptFeedback),

const step = (state, event) => {
if (ProviderShared.isRecord(event.error) && typeof event.error.message === "string") {
const body = ProviderShared.encodeJson(event);
return Effect.fail(new AIError({
reason: classifyProviderFailure({
message: event.error.message,
status: typeof event.error.code === "number" ? event.error.code : undefined,
rawBody: body,
}),
}));
}
const nextState = {

@@ -486,0 +498,0 @@ ...state,

@@ -5,3 +5,3 @@ import { Effect, Encoding, Schema } from "effect";

import { Auth } from "../route/auth.js";
import { InvalidProviderOutputReason, AIError, Usage, mergeHttpOptions, mergeJsonRecords, } from "../schema/index.js";
import { AIError, Usage, mergeHttpOptions, mergeJsonRecords } from "../schema/index.js";
import { ProviderShared } from "./shared.js";

@@ -62,7 +62,2 @@ import { ImageInputs } from "./utils/image-input.js";

};
const invalidOutput = (message, providerMetadata) => new AIError({
module: ADAPTER,
method: "generate",
reason: new InvalidProviderOutputReason({ message, route: ADAPTER, providerMetadata }),
});
const applyQuery = (url, query) => {

@@ -95,4 +90,4 @@ if (!query)

const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
const payload = yield* response.json.pipe(Effect.mapError(() => invalidOutput("Failed to read the Google Images response")));
const decoded = yield* Schema.decodeUnknownEffect(GoogleImageResponse)(payload).pipe(Effect.mapError(() => invalidOutput("Google Images returned an invalid response")));
const output = yield* ProviderShared.imageResponse(ADAPTER, "Google Images", response);
const decoded = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(GoogleImageResponse))(output.body).pipe(Effect.mapError((cause) => output.invalid("Google Images returned an invalid response", cause)));
const candidates = decoded.candidates ?? [];

@@ -123,3 +118,3 @@ const candidateMetadata = candidates.map((candidate, candidateIndex) => ({

: [{ candidate, candidateIndex, partIndex, inlineData: part.inlineData }]));
const images = yield* Effect.forEach(encoded, (item) => Effect.fromResult(Encoding.decodeBase64(item.inlineData.data)).pipe(Effect.mapError(() => invalidOutput(`Google Images candidate ${item.candidateIndex} part ${item.partIndex} contains invalid base64 data`)), Effect.map((data) => new GeneratedImage({
const images = yield* Effect.forEach(encoded, (item) => Effect.fromResult(Encoding.decodeBase64(item.inlineData.data)).pipe(Effect.mapError((cause) => output.invalid(`Google Images candidate ${item.candidateIndex} part ${item.partIndex} contains invalid base64 data`, cause)), Effect.map((data) => new GeneratedImage({
mediaType: item.inlineData.mimeType,

@@ -141,8 +136,3 @@ data,

const finishReasons = candidates.flatMap((candidate) => candidate.finishReason === undefined ? [] : [candidate.finishReason]);
return yield* invalidOutput(`Google Images returned no final images${finishReasons.length === 0 ? "" : ` (finish reasons: ${finishReasons.join(", ")})`}; inspect reason.providerMetadata.google for prompt feedback and candidate details`, {
google: {
promptFeedback: decoded.promptFeedback,
candidates: candidateMetadata,
},
});
return yield* output.invalid(`Google Images returned no final images${finishReasons.length === 0 ? "" : ` (finish reasons: ${finishReasons.join(", ")})`}; inspect body for prompt feedback and candidate details`);
}

@@ -185,5 +175,5 @@ const usage = decoded.usageMetadata;

if (image.type === "url")
return ImageInputs.decodeDataUrl(image.url, ADAPTER).pipe(Effect.flatMap((decoded) => {
return ImageInputs.decodeDataUrl(image.url).pipe(Effect.flatMap((decoded) => {
if (decoded === undefined)
return Effect.fail(ImageInputs.invalid(ADAPTER, "Google generateContent does not fetch public image URLs; use bytes, a data URL, or a Gemini file URI"));
return Effect.fail(ImageInputs.invalid("Google generateContent does not fetch public image URLs; use bytes, a data URL, or a Gemini file URI"));
return Effect.succeed({

@@ -193,3 +183,3 @@ inlineData: { mimeType: decoded.mediaType, data: Encoding.encodeBase64(decoded.data) },

}));
return Effect.fail(ImageInputs.invalid(ADAPTER, "Google generateContent requires Gemini file URIs rather than provider file IDs"));
return Effect.fail(ImageInputs.invalid("Google generateContent requires Gemini file URIs rather than provider file IDs"));
};

@@ -196,0 +186,0 @@ export const GoogleImages = {

@@ -31,3 +31,3 @@ import { Effect, Schema, Stream } from "effect";

observe: (_create, frame) => Effect.gen(function* () {
const event = yield* decodeEvent(frame).pipe(Effect.mapError(() => ProviderShared.eventError(options.id, `Invalid ${options.name} WebSocket event`, frame)));
const event = yield* decodeEvent(frame).pipe(Effect.mapError((cause) => ProviderShared.eventError(options.id, `Invalid ${options.name} WebSocket event`, frame, cause)));
if (terminal)

@@ -37,6 +37,6 @@ return yield* ProviderShared.eventError(options.id, `${options.name} emitted ${event.type} after a terminal event`, frame);

terminal = true;
yield* OpenResponses.decodeKnownErrorEvent(event).pipe(Effect.mapError(() => ProviderShared.eventError(options.id, `${options.name} returned a malformed error event`, frame)));
yield* OpenResponses.decodeKnownErrorEvent(event).pipe(Effect.mapError((cause) => ProviderShared.eventError(options.id, `${options.name} returned a malformed error event`, frame, cause)));
return {
type: "provider-failure",
error: OpenResponses.providerFailure(options.id, event, `${options.name} stream error`),
error: OpenResponses.providerFailure(event, `${options.name} stream error`, frame),
};

@@ -50,3 +50,3 @@ }

type: "provider-failure",
error: OpenResponses.providerFailure(options.id, event, `${options.name} response failed`),
error: OpenResponses.providerFailure(event, `${options.name} response failed`, frame),
};

@@ -116,5 +116,6 @@ }

}),
execute: (prepared, request, runtime, executeOptions) => {
execute: (prepared, request, runtime, executeOptions) => Effect.gen(function* () {
if (!executeOptions?.webSocket || !prepared.channel)
return http.execute(prepared.http, request, runtime);
return yield* http.execute(prepared.http, request, runtime);
let fallbackHttp;
const exchange = {

@@ -127,9 +128,19 @@ id: request.id ?? "request",

},
fallback: () => Stream.unwrap(http.execute(prepared.http, request, runtime).pipe(Effect.map((execution) => execution.frames))),
fallback: () => Stream.unwrap(http.execute(prepared.http, request, runtime).pipe(Effect.map((execution) => {
fallbackHttp = execution.http;
return execution.frames;
}))),
driver: prepared.channel.driver,
};
return executeOptions.webSocket.execute(exchange);
},
const execution = yield* executeOptions.webSocket.execute(exchange);
return {
frames: execution.frames,
complete: execution.complete,
get http() {
return fallbackHttp ?? execution.http;
},
};
}),
};
};
export const OpenResponsesChannel = { transport };

@@ -1,2 +0,2 @@

import { AIError, TransportReason } from "../schema/index.js";
import { AIError, TransportError } from "../schema/index.js";
import { Effect, Option, Schema } from "effect";

@@ -84,10 +84,11 @@ import * as ProviderShared from "./shared.js";

const code = (event) => event.code || event.error?.code || event.response?.error?.code || undefined;
const rejected = (input, observation, recovery) => ({
const rejected = (observation, recovery) => ({
type: "rejected",
recovery,
error: new AIError({
module: input.id,
method: "stream",
reason: new TransportReason({
reason: new TransportError({
message: observation.error.message,
body: observation.error.reason.body,
http: observation.error.reason.http,
cause: observation.error.reason.cause,
transport: "websocket",

@@ -117,3 +118,3 @@ operation: "read",

observe: (create, frame) => Effect.gen(function* () {
const event = yield* decodeEvent(frame).pipe(Effect.mapError(() => ProviderShared.eventError(input.id, `Invalid ${input.name} WebSocket event`, frame)));
const event = yield* decodeEvent(frame).pipe(Effect.mapError((cause) => ProviderShared.eventError(input.id, `Invalid ${input.name} WebSocket event`, frame, cause)));
const observation = yield* input.base.observe(create, frame);

@@ -125,5 +126,5 @@ if (event.type === "response.output_item.done" && event.item)

if (rejection === "previous_response_not_found")
return rejected(input, observation, "retry-full");
return rejected(observation, "retry-full");
if (rejection === "websocket_connection_limit_reached")
return rejected(input, observation, "rotate-and-retry-full");
return rejected(observation, "rotate-and-retry-full");
}

@@ -130,0 +131,0 @@ if (observation.type !== "completed")

@@ -807,3 +807,3 @@ import { Effect, Schema } from "effect";

export declare const onReasoningDone: (state: ParserState, event: Event, itemID: string) => StepResult;
export declare const providerFailure: (id: string, event: Event, fallback: string) => AIError;
export declare const providerFailure: (event: Event, fallback: string, body?: string) => AIError;
export declare const step: (state: ParserState, input: Event) => AIError | Effect.Effect<[ParserState, readonly ({

@@ -810,0 +810,0 @@ readonly type: "step-start";

import { Effect, Schema } from "effect";
import { HttpTransport } from "../route/transport/index.js";
import { Protocol } from "../route/protocol.js";
import { AIError, LLMEvent, ProviderInternalReason, Usage, } from "../schema/index.js";
import { AIError, LLMEvent, ProviderInternalError, Usage, } from "../schema/index.js";
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";

@@ -921,7 +921,4 @@ import { classifyProviderFailure } from "../provider-error.js";

};
export const providerFailure = (id, event, fallback) => {
export const providerFailure = (event, fallback, body = ProviderShared.encodeJson(event)) => {
const nested = event.error ?? event.response?.error ?? undefined;
const code = event.code || nested?.code || undefined;
// Keep the full raw payload on the error even when the message is a summary.
const body = JSON.stringify(nested ?? event) ?? "";
const summary = providerErrorMessage(event, nested);

@@ -939,12 +936,6 @@ const message = summary ?? (body === "{}" ? fallback : body);

status === undefined
? new ProviderInternalReason({ message })
: classifyProviderFailure({ message, code, status, rawBody: body });
return new AIError({
module: id,
method: "stream",
body,
reason,
});
? new ProviderInternalError({ message, body })
: classifyProviderFailure({ message, status, rawBody: body });
return new AIError({ reason });
};
const providerError = (state, event, fallback) => providerFailure(state.id, event, fallback);
export const step = (state, input) => {

@@ -1010,5 +1001,5 @@ // The OpenAPI requires string IDs but imposes no minLength; empty is not missing.

if (event.type === "response.failed")
return providerError(state, event, `${state.name} response failed`);
return providerFailure(event, `${state.name} response failed`);
if (event.type === "error")
return decodeKnownErrorEvent(event).pipe(Effect.mapError(() => ProviderShared.eventError(state.id, `${state.name} returned a malformed error event`)), Effect.flatMap(() => providerError(state, event, `${state.name} stream error`)));
return decodeKnownErrorEvent(event).pipe(Effect.mapError((cause) => ProviderShared.eventError(state.id, `${state.name} returned a malformed error event`, ProviderShared.encodeJson(event), cause)), Effect.flatMap(() => providerFailure(event, `${state.name} stream error`)));
return Effect.succeed([state, NO_EVENTS]);

@@ -1015,0 +1006,0 @@ };

@@ -8,3 +8,3 @@ import { Effect, Schema } from "effect";

import { Protocol } from "../route/protocol.js";
import { AIError, InvalidProviderOutputReason, LLMEvent, ProviderInternalReason, UnknownProviderReason, Usage, } from "../schema/index.js";
import { AIError, AIErrorReason, InvalidProviderOutputError, LLMEvent, ProviderInternalError, UnknownProviderError, Usage, } from "../schema/index.js";
import { classifyProviderFailure } from "../provider-error.js";

@@ -617,14 +617,18 @@ import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";

// because OpenAI streams JSON arguments across multiple deltas.
const finishReasonError = (event, reason) => new AIError({
module: ADAPTER,
method: "stream",
body: ProviderShared.encodeJson(event),
reason,
});
const mapFinishReason = Effect.fn("OpenAIChat.mapFinishReason")(function* (event, reason) {
switch (reason) {
case "error":
return yield* finishReasonError(event, new UnknownProviderReason({ message: "Provider reported an error (finish_reason: error)" }));
return yield* new AIError({
reason: new UnknownProviderError({
message: "Provider reported an error (finish_reason: error)",
body: ProviderShared.encodeJson(event),
}),
});
case "network_error":
return yield* finishReasonError(event, new ProviderInternalReason({ message: "Provider reported a network error (finish_reason: network_error)" }));
return yield* new AIError({
reason: new ProviderInternalError({
message: "Provider reported a network error (finish_reason: network_error)",
body: ProviderShared.encodeJson(event),
}),
});
case "stop":

@@ -743,8 +747,4 @@ case "end":

return yield* new AIError({
module: ADAPTER,
method: "stream",
body,
reason: classifyProviderFailure({
message: event.error.message,
code: event.error.code === undefined || event.error.code === null ? undefined : String(event.error.code),
status: typeof event.error.code === "number" ? event.error.code : undefined,

@@ -837,3 +837,10 @@ rawBody: body,

if (ToolStream.isError(result))
return yield* ProviderShared.eventError(ADAPTER, result.reason.message, ProviderShared.encodeJson(event));
return yield* new AIError({
reason: AIErrorReason.make({
...result.reason,
message: result.message,
cause: result.reason.cause,
body: ProviderShared.encodeJson(event),
}),
});
tools = result.tools;

@@ -874,7 +881,5 @@ if (result.events.length)

return yield* new AIError({
module: ADAPTER,
method: "stream",
reason: new InvalidProviderOutputReason({
reason: new InvalidProviderOutputError({
message: "OpenAI Chat stream ended without finish_reason",
classification: "incomplete-stream",
message: "OpenAI Chat stream ended without finish_reason",
route: ADAPTER,

@@ -881,0 +886,0 @@ }),

@@ -5,3 +5,3 @@ import { Effect, Encoding, Schema } from "effect";

import { Auth } from "../route/auth.js";
import { InvalidProviderOutputReason, AIError, Usage, mergeHttpOptions, mergeJsonRecords, } from "../schema/index.js";
import { Usage, mergeHttpOptions, mergeJsonRecords } from "../schema/index.js";
import { ProviderShared } from "./shared.js";

@@ -39,7 +39,2 @@ import { ImageInputs } from "./utils/image-input.js";

};
const invalidOutput = (message) => new AIError({
module: ADAPTER,
method: "generate",
reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
});
const applyQuery = (url, query) => {

@@ -58,3 +53,3 @@ if (!query)

if (mask !== undefined && (request.images?.length ?? 0) === 0)
return yield* ImageInputs.invalid(ADAPTER, "An OpenAI image mask requires at least one input image");
return yield* ImageInputs.invalid("An OpenAI image mask requires at least one input image");
const http = mergeHttpOptions(request.model.http, request.http);

@@ -66,3 +61,3 @@ const sourceImages = request.images ?? [];

if (image.type === "url")
return ImageInputs.decodeDataUrl(image.url, ADAPTER);
return ImageInputs.decodeDataUrl(image.url);
return Effect.undefined;

@@ -75,3 +70,3 @@ });

: mask.type === "url"
? yield* ImageInputs.decodeDataUrl(mask.url, ADAPTER)
? yield* ImageInputs.decodeDataUrl(mask.url)
: undefined;

@@ -119,3 +114,3 @@ const useMultipart = sourceImages.length > 0 &&

if (references.some((image) => image === undefined))
return yield* ImageInputs.invalid(ADAPTER, "OpenAI Images accepts image URLs, data URLs, bytes, and file IDs");
return yield* ImageInputs.invalid("OpenAI Images accepts image URLs, data URLs, bytes, and file IDs");
const maskReference = mask === undefined

@@ -131,3 +126,3 @@ ? undefined

if (mask !== undefined && maskReference === undefined)
return yield* ImageInputs.invalid(ADAPTER, "OpenAI Images accepts masks as URLs, data URLs, bytes, or file IDs");
return yield* ImageInputs.invalid("OpenAI Images accepts masks as URLs, data URLs, bytes, or file IDs");
const requestBody = mergeJsonRecords({

@@ -154,4 +149,4 @@ model: request.model.id,

const parseResponse = Effect.fn("OpenAIImages.parseResponse")(function* (response, options, overlay) {
const payload = yield* response.json.pipe(Effect.mapError(() => invalidOutput("Failed to read the OpenAI Images response")));
const decoded = yield* Schema.decodeUnknownEffect(OpenAIImageResponse)(payload).pipe(Effect.mapError(() => invalidOutput("OpenAI Images returned an invalid response")));
const output = yield* ProviderShared.imageResponse(ADAPTER, "OpenAI Images", response);
const decoded = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(OpenAIImageResponse))(output.body).pipe(Effect.mapError((cause) => output.invalid("OpenAI Images returned an invalid response", cause)));
const requestBody = mergeJsonRecords(nativeOptions(options), overlay);

@@ -161,3 +156,3 @@ const format = decoded.output_format ?? (typeof requestBody?.output_format === "string" ? requestBody.output_format : "png");

if (item.b64_json)
return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(Effect.mapError(() => invalidOutput(`OpenAI Images result ${index} contains invalid base64 data`)), Effect.map((data) => new GeneratedImage({
return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(Effect.mapError((cause) => output.invalid(`OpenAI Images result ${index} contains invalid base64 data`, cause)), Effect.map((data) => new GeneratedImage({
mediaType: `image/${format}`,

@@ -173,6 +168,6 @@ data,

}));
return Effect.fail(invalidOutput(`OpenAI Images result ${index} has neither image data nor a URL`));
return Effect.fail(output.invalid(`OpenAI Images result ${index} has neither image data nor a URL`));
});
if (images.length === 0)
return yield* invalidOutput("OpenAI Images returned no images");
return yield* output.invalid("OpenAI Images returned no images");
return new ImageResponse({

@@ -179,0 +174,0 @@ images,

@@ -118,3 +118,3 @@ import { Effect, Encoding, Schema } from "effect";

if (item.type === "image_generation_call" && item.result) {
yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(Effect.mapError(() => ProviderShared.eventError(ADAPTER, "OpenAI Responses returned invalid image base64")));
yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(Effect.mapError((cause) => ProviderShared.eventError(ADAPTER, "OpenAI Responses returned invalid image base64", undefined, cause)));
const format = item.output_format ?? "png";

@@ -121,0 +121,0 @@ return {

import { Tool } from "@opencode-ai/schema/tool";
import { Effect, Schema, Stream } from "effect";
import { Headers, HttpClientRequest } from "effect/unstable/http";
import { Headers, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
import { AIError, type ContentPart, type LLMRequest, type MediaPart, type ToolResultPart } from "../schema/index.js";

@@ -57,3 +57,3 @@ import { isRecord } from "../utils/record.js";

export declare const sumTokens: (...values: ReadonlyArray<number | undefined>) => number | undefined;
export declare const eventError: (route: string, message: string, raw?: string) => AIError;
export declare const eventError: (route: string, message: string, body?: string, cause?: unknown) => AIError;
export declare const parseJson: (route: string, input: string, message: string) => Effect.Effect<unknown, AIError, never>;

@@ -131,3 +131,7 @@ /**

*/
export declare const invalidRequest: (message: string) => AIError;
export declare const invalidRequest: (message: string, cause?: unknown) => AIError;
export declare const imageResponse: (route: string, name: string, response: HttpClientResponse.HttpClientResponse) => Effect.Effect<{
body: string;
invalid: (message: string, cause?: unknown) => AIError;
}, AIError, never>;
export declare const matchToolChoice: <Auto, None, Required, Tool>(route: string, toolChoice: NonNullable<LLMRequest["toolChoice"]>, cases: {

@@ -134,0 +138,0 @@ readonly auto: () => Auto;

@@ -5,4 +5,4 @@ import { Buffer } from "node:buffer";

import * as Sse from "effect/unstable/encoding/Sse";
import { Headers, HttpClientRequest } from "effect/unstable/http";
import { InvalidProviderOutputReason, InvalidRequestReason, AIError, } from "../schema/index.js";
import { Headers, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
import { InvalidProviderOutputError, InvalidRequestError, AIError, HttpContext, } from "../schema/index.js";
import { isRecord } from "../utils/record.js";

@@ -76,10 +76,8 @@ export { isRecord };

};
export const eventError = (route, message, raw) => new AIError({
module: "ProviderShared",
method: "stream",
reason: new InvalidProviderOutputReason({ route, message, raw }),
export const eventError = (route, message, body, cause) => new AIError({
reason: new InvalidProviderOutputError({ route, message, body, cause }),
});
export const parseJson = (route, input, message) => Effect.try({
try: () => decodeJson(input),
catch: () => eventError(route, message, input),
catch: (cause) => eventError(route, message, input, cause),
});

@@ -183,3 +181,3 @@ /**

if (error)
return yield* eventError("sse", error.message);
return yield* eventError("sse", error.message, chunk, error);
return [state, state.output.splice(0)];

@@ -192,7 +190,22 @@ })), Stream.filter((event) => (events === undefined || events.has(event.event)) &&

*/
export const invalidRequest = (message) => new AIError({
module: "ProviderShared",
method: "request",
reason: new InvalidRequestReason({ message }),
export const invalidRequest = (message, cause) => new AIError({
reason: new InvalidRequestError({ message, cause }),
});
export const imageResponse = Effect.fn("ProviderShared.imageResponse")(function* (route, name, response) {
const http = new HttpContext({ url: response.request.url, status: response.status, headers: response.headers });
const body = yield* response.text.pipe(Effect.mapError((cause) => new AIError({
reason: new InvalidProviderOutputError({
route,
message: `Failed to read the ${name} response`,
http,
cause,
}),
})));
return {
body,
invalid: (message, cause) => new AIError({
reason: new InvalidProviderOutputError({ route, message, body, http, cause }),
}),
};
});
export const matchToolChoice = (route, toolChoice, cases) => Effect.gen(function* () {

@@ -224,3 +237,3 @@ if (toolChoice.type === "auto")

*/
export const validateWith = (decode) => (payload) => decode(payload).pipe(Effect.mapError((error) => invalidRequest(error.message)));
export const validateWith = (decode) => (payload) => decode(payload).pipe(Effect.mapError((error) => invalidRequest(error.message, error)));
/**

@@ -227,0 +240,0 @@ * Build an HTTP POST with a JSON body. Sets `content-type: application/json`

@@ -7,7 +7,7 @@ import { Effect } from "effect";

}>) => string;
export declare const decodeDataUrl: (url: string, module: string) => Effect.Effect<{
export declare const decodeDataUrl: (url: string) => Effect.Effect<{
readonly mediaType: string;
readonly data: Uint8Array;
} | undefined, AIError>;
export declare const invalidImageInput: (module: string, message: string) => AIError;
export declare const invalidImageInput: (message: string, cause?: unknown) => AIError;
export declare const ImageInputs: {

@@ -17,7 +17,7 @@ readonly dataUrl: (input: Extract<ImageInput, {

}>) => string;
readonly decodeDataUrl: (url: string, module: string) => Effect.Effect<{
readonly decodeDataUrl: (url: string) => Effect.Effect<{
readonly mediaType: string;
readonly data: Uint8Array;
} | undefined, AIError>;
readonly invalid: (module: string, message: string) => AIError;
readonly invalid: (message: string, cause?: unknown) => AIError;
};
import { Effect, Encoding } from "effect";
import { InvalidRequestReason, AIError } from "../../schema/index.js";
const invalid = (module, message) => new AIError({
module,
method: "generate",
reason: new InvalidRequestReason({ message }),
import { InvalidRequestError, AIError } from "../../schema/index.js";
const invalid = (message, cause) => new AIError({
reason: new InvalidRequestError({ message, cause }),
});
export const dataUrl = (input) => `data:${input.mediaType};base64,${Encoding.encodeBase64(input.data)}`;
export const decodeDataUrl = (url, module) => {
export const decodeDataUrl = (url) => {
if (!url.startsWith("data:"))

@@ -14,4 +12,4 @@ return Effect.undefined;

if (!match)
return Effect.fail(invalid(module, "Image data URLs must contain a MIME type and base64 data"));
return Effect.fromResult(Encoding.decodeBase64(match[2])).pipe(Effect.mapError(() => invalid(module, "Image data URL contains invalid base64 data")), Effect.map((data) => ({ mediaType: match[1], data })));
return Effect.fail(invalid("Image data URLs must contain a MIME type and base64 data"));
return Effect.fromResult(Encoding.decodeBase64(match[2])).pipe(Effect.mapError((cause) => invalid("Image data URL contains invalid base64 data", cause)), Effect.map((data) => ({ mediaType: match[1], data })));
};

@@ -18,0 +16,0 @@ export const invalidImageInput = invalid;

@@ -5,3 +5,3 @@ import { Effect, Encoding, Schema } from "effect";

import { Auth } from "../route/auth.js";
import { InvalidProviderOutputReason, AIError, Usage, mergeHttpOptions, mergeJsonRecords, } from "../schema/index.js";
import { Usage, mergeHttpOptions, mergeJsonRecords } from "../schema/index.js";
import { ProviderShared, optionalNull } from "./shared.js";

@@ -32,7 +32,2 @@ import { ImageInputs } from "./utils/image-input.js";

};
const invalidOutput = (message) => new AIError({
module: ADAPTER,
method: "generate",
reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
});
const applyQuery = (url, query) => {

@@ -60,3 +55,3 @@ if (!query)

if (imageReferences.some((image) => image === undefined))
return yield* ImageInputs.invalid(ADAPTER, "xAI Images accepts image URLs, data URLs, bytes, and file IDs");
return yield* ImageInputs.invalid("xAI Images accepts image URLs, data URLs, bytes, and file IDs");
const requestBody = mergeJsonRecords({

@@ -78,8 +73,8 @@ model: request.model.id,

const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
const payload = yield* response.json.pipe(Effect.mapError(() => invalidOutput("Failed to read the xAI Images response")));
const decoded = yield* Schema.decodeUnknownEffect(XAIImageResponse)(payload).pipe(Effect.mapError(() => invalidOutput("xAI Images returned an invalid response")));
const output = yield* ProviderShared.imageResponse(ADAPTER, "xAI Images", response);
const decoded = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(XAIImageResponse))(output.body).pipe(Effect.mapError((cause) => output.invalid("xAI Images returned an invalid response", cause)));
const images = yield* Effect.forEach(decoded.data, (item, index) => {
const mediaType = item.mime_type ?? "application/octet-stream";
if (item.b64_json)
return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(Effect.mapError(() => invalidOutput(`xAI Images result ${index} contains invalid base64 data`)), Effect.map((data) => new GeneratedImage({
return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(Effect.mapError((cause) => output.invalid(`xAI Images result ${index} contains invalid base64 data`, cause)), Effect.map((data) => new GeneratedImage({
mediaType,

@@ -99,6 +94,6 @@ data,

}));
return Effect.fail(invalidOutput(`xAI Images result ${index} has neither image data nor a URL`));
return Effect.fail(output.invalid(`xAI Images result ${index} has neither image data nor a URL`));
});
if (images.length === 0)
return yield* invalidOutput("xAI Images returned no images");
return yield* output.invalid("xAI Images returned no images");
const usage = ProviderShared.isRecord(decoded.usage) ? decoded.usage : undefined;

@@ -105,0 +100,0 @@ return new ImageResponse({

@@ -5,3 +5,3 @@ import { Effect, Schema } from "effect";

import { Auth } from "../route/auth.js";
import { InvalidProviderOutputReason, AIError, mergeHttpOptions, mergeJsonRecords, } from "../schema/index.js";
import { mergeHttpOptions, mergeJsonRecords } from "../schema/index.js";
import { ProviderShared } from "./shared.js";

@@ -31,7 +31,2 @@ import { ImageInputs } from "./utils/image-input.js";

};
const invalidOutput = (message) => new AIError({
module: ADAPTER,
method: "generate",
reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
});
const applyQuery = (url, query) => {

@@ -49,3 +44,3 @@ if (!query)

if ((request.images?.length ?? 0) > 0)
return yield* ImageInputs.invalid(ADAPTER, "Z.ai hosted image generation does not support image inputs");
return yield* ImageInputs.invalid("Z.ai hosted image generation does not support image inputs");
const http = mergeHttpOptions(request.model.http, request.http);

@@ -63,6 +58,6 @@ const requestBody = mergeJsonRecords({ model: request.model.id, prompt: request.prompt }, nativeOptions(request.options), http?.body);

const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
const payload = yield* response.json.pipe(Effect.mapError(() => invalidOutput("Failed to read the Z.ai Images response")));
const decoded = yield* Schema.decodeUnknownEffect(ZAIImageResponse)(payload).pipe(Effect.mapError(() => invalidOutput("Z.ai Images returned an invalid response")));
const output = yield* ProviderShared.imageResponse(ADAPTER, "Z.ai Images", response);
const decoded = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(ZAIImageResponse))(output.body).pipe(Effect.mapError((cause) => output.invalid("Z.ai Images returned an invalid response", cause)));
if (decoded.data.length === 0)
return yield* invalidOutput("Z.ai Images returned no images");
return yield* output.invalid("Z.ai Images returned no images");
return new ImageResponse({

@@ -69,0 +64,0 @@ images: decoded.data.map((item) => new GeneratedImage({

@@ -1,2 +0,2 @@

import { AIError, type HttpContext, type HttpRateLimitDetails, type ProviderMetadata } from "./schema/index.js";
import { AIError, type HttpContext, type HttpRateLimitDetails } from "./schema/index.js";
export declare const isContextOverflow: (message: string) => boolean;

@@ -8,9 +8,9 @@ export declare const isPayloadTooLarge: (message: string) => boolean;

readonly status?: number | undefined;
readonly code?: string | undefined;
readonly rawBody?: string | undefined;
readonly data?: unknown;
readonly http?: HttpContext | undefined;
readonly cause?: unknown;
readonly retryAfterMs?: number | undefined;
readonly rateLimit?: HttpRateLimitDetails | undefined;
readonly http?: HttpContext | undefined;
readonly providerMetadata?: ProviderMetadata | undefined;
}
export declare function classifyProviderFailure(input: ProviderFailure): AIError["reason"];
import { Option, Schema } from "effect";
import { AuthenticationReason, ContentPolicyReason, InvalidRequestReason, AIError, ProviderErrorEvent, ProviderInternalReason, QuotaExceededReason, RateLimitReason, UnknownProviderReason, } from "./schema/index.js";
import { AuthenticationError, ContentPolicyError, InvalidRequestError, AIError, ProviderErrorEvent, ProviderInternalError, QuotaExceededError, RateLimitError, UnknownProviderError, } from "./schema/index.js";
const patterns = [

@@ -60,10 +60,8 @@ /prompt is too long/i,

export function classifyProviderFailure(input) {
const body = input.http?.body ?? input.rawBody ?? "";
const codes = [input.code, ...providerCodes(body), ...providerCodes(input.message)]
.filter((code) => code !== undefined)
.map((code) => code.toLowerCase());
const details = { message: input.message, body: input.rawBody, http: input.http, cause: input.cause };
const body = input.rawBody ?? "";
const codes = [...providerCodes(input.data), ...providerCodes(body), ...providerCodes(input.message)].map((code) => code.toLowerCase());
// Scan the raw payload too so signals missing from the summary message
// (e.g. overflow phrases nested in a JSON error body) still classify.
const text = [input.message, body].filter((value) => value.length > 0).join("\n");
const common = { message: input.message, providerMetadata: input.providerMetadata, http: input.http };
const clientScoped = input.status === undefined || (input.status >= 400 && input.status < 500);

@@ -75,20 +73,20 @@ if (clientScoped &&

isContextOverflow(text)))
return new InvalidRequestReason({ ...common, classification: "context-overflow" });
return new InvalidRequestError({ ...details, classification: "context-overflow" });
if (input.status === 413 || isPayloadTooLarge(text))
return new InvalidRequestReason({ ...common, classification: "payload-too-large" });
return new InvalidRequestError({ ...details, classification: "payload-too-large" });
if (CONTENT_POLICY_TEXT.test(text))
return new ContentPolicyReason(common);
return new ContentPolicyError(details);
if (codes.some((code) => QUOTA_CODES.has(code)) || (input.status === 429 && QUOTA_TEXT.test(text)))
return new QuotaExceededReason(common);
return new QuotaExceededError(details);
if (input.status === 401)
return new AuthenticationReason({ ...common, kind: "invalid" });
return new AuthenticationError({ ...details, kind: "invalid" });
if (input.status === 403)
return new AuthenticationReason({ ...common, kind: "insufficient-permissions" });
return new AuthenticationError({ ...details, kind: "insufficient-permissions" });
if (codes.includes("authentication_error"))
return new AuthenticationReason({ ...common, kind: "invalid" });
return new AuthenticationError({ ...details, kind: "invalid" });
if (codes.includes("permission_error"))
return new AuthenticationReason({ ...common, kind: "insufficient-permissions" });
return new AuthenticationError({ ...details, kind: "insufficient-permissions" });
if (codes.some((code) => code.includes("rate_limit") || code === "too_many_requests" || code === "throttlingexception"))
return new RateLimitReason({
...common,
return new RateLimitError({
...details,
retryAfterMs: input.retryAfterMs,

@@ -98,4 +96,4 @@ rateLimit: input.rateLimit,

if (RATE_LIMIT_TEXT.test(text))
return new RateLimitReason({
...common,
return new RateLimitError({
...details,
retryAfterMs: input.retryAfterMs,

@@ -105,12 +103,11 @@ rateLimit: input.rateLimit,

if (NETWORK_ERROR_TEXT.test(text))
return new ProviderInternalReason({ ...common, status: input.status });
return new ProviderInternalError(details);
if (codes.some((code) => SERVER_CODES.has(code) || code.includes("exhausted") || code.includes("unavailable")))
return new ProviderInternalReason({
...common,
status: input.status,
return new ProviderInternalError({
...details,
retryAfterMs: input.retryAfterMs,
});
if (input.status === 429) {
return new RateLimitReason({
...common,
return new RateLimitError({
...details,
retryAfterMs: input.retryAfterMs,

@@ -121,19 +118,21 @@ rateLimit: input.rateLimit,

if (input.status === 408 || input.status === 409 || (input.status !== undefined && input.status >= 500))
return new ProviderInternalReason({
...common,
status: input.status,
return new ProviderInternalError({
...details,
retryAfterMs: input.retryAfterMs,
});
if (codes.some((code) => INVALID_REQUEST_CODES.has(code)))
return new InvalidRequestReason(common);
return new InvalidRequestError(details);
if (input.status === 400 || input.status === 404 || input.status === 413 || input.status === 422)
return new InvalidRequestReason(common);
return new UnknownProviderReason({ ...common, status: input.status });
return new InvalidRequestError(details);
return new UnknownProviderError(details);
}
function providerCodes(value) {
const decoded = Option.getOrUndefined(decodeJson(value));
const decoded = typeof value === "string" ? Option.getOrUndefined(decodeJson(value)) : value;
if (!isRecord(decoded))
return [];
const error = isRecord(decoded.error) ? decoded.error : undefined;
return [decoded.code, error?.code, error?.type].filter((value) => typeof value === "string");
const response = isRecord(decoded.response) ? decoded.response : undefined;
const responseError = response && isRecord(response.error) ? response.error : undefined;
const exception = isRecord(decoded.exception) ? decoded.exception : undefined;
return [decoded.code, error?.code, error?.type, error?.status, responseError?.code, exception?.type].filter((value) => typeof value === "string");
}

@@ -140,0 +139,0 @@ function isRecord(value) {

import { Config, Effect, Redacted } from "effect";
import { Headers } from "effect/unstable/http";
import { AuthenticationReason, InvalidRequestReason, AIError } from "../schema/index.js";
import { AuthenticationError, InvalidRequestError, AIError } from "../schema/index.js";
export class MissingCredentialError extends Error {

@@ -79,7 +79,5 @@ source;

return new AIError({
module: "Auth",
method: "apply",
reason: error instanceof MissingCredentialError
? new AuthenticationReason({ message: error.message, kind: "missing" })
: new InvalidRequestReason({ message: `Failed to resolve auth config: ${error.message}` }),
? new AuthenticationError({ message: error.message, cause: error, kind: "missing" })
: new InvalidRequestError({ message: `Failed to resolve auth config: ${error.message}`, cause: error }),
});

@@ -86,0 +84,0 @@ }

@@ -10,3 +10,3 @@ import { Cause, Context, Effect, Layer, Schema, Stream } from "effect";

import * as ProviderShared from "../protocols/shared.js";
import { AIError, GenerationOptions, HttpOptions, LLMRequest, LLMResponse, LanguageModel, LLMEvent, InvalidProviderOutputReason, ProviderID, mergeGenerationOptions, mergeHttpOptions, mergeProviderOptions, } from "../schema/index.js";
import { AIError, AIErrorReason, GenerationOptions, HttpOptions, LLMRequest, LLMResponse, LanguageModel, LLMEvent, InvalidProviderOutputError, ProviderID, mergeGenerationOptions, mergeHttpOptions, mergeProviderOptions, } from "../schema/index.js";
const makeRouteLanguageModel = (route, mapped) => {

@@ -64,10 +64,8 @@ const provider = route.provider ?? ("provider" in mapped ? mapped.provider : undefined);

return failed;
return ProviderShared.eventError(route, message, Cause.pretty(cause));
return ProviderShared.eventError(route, message, undefined, cause);
};
const incompleteStreamError = (route) => new AIError({
module: "LLMClient",
method: "stream",
reason: new InvalidProviderOutputReason({
reason: new InvalidProviderOutputError({
message: "The provider response ended unexpectedly.",
classification: "incomplete-stream",
message: "The provider response ended unexpectedly.",
route,

@@ -90,3 +88,3 @@ }),

const decodeEventEffect = Schema.decodeUnknownEffect(protocol.stream.event);
const decodeEvent = (route) => (frame) => decodeEventEffect(frame).pipe(Effect.mapError(() => ProviderShared.eventError(input.id, `Invalid ${route} stream event`, typeof frame === "string" ? frame : ProviderShared.encodeJson(frame))));
const decodeEvent = (route) => (frame) => decodeEventEffect(frame).pipe(Effect.mapError((cause) => ProviderShared.eventError(input.id, `Invalid ${route} stream event`, typeof frame === "string" ? frame : ProviderShared.encodeJson(frame), cause)));
const build = (routeInput) => {

@@ -133,9 +131,22 @@ const route = {

return Stream.unwrap(routeInput.transport.execute(prepared, request, runtime, options).pipe(Effect.map((execution) => {
const events = execution.frames.pipe(Stream.mapEffect(decodeEvent(route)), protocol.stream.terminal ? Stream.takeUntil(protocol.stream.terminal) : (stream) => stream);
const terminal = protocol.stream.terminal;
// Preserve assembled inputs; replace only serialized event fallbacks with their original wire data.
const frameError = (frame, event = frame) => (error) => new AIError({
reason: AIErrorReason.make({
...error.reason,
message: error.reason.message,
cause: error.reason.cause,
body: error.reason.body !== undefined && error.reason.body !== ProviderShared.encodeJson(event)
? error.reason.body
: (execution.body?.(frame) ??
(typeof frame === "string" ? frame : ProviderShared.encodeJson(frame))),
}),
});
const events = execution.frames.pipe(Stream.mapEffect((frame) => decodeEvent(route)(frame).pipe(Effect.catchCause((cause) => Effect.fail(streamError(route, `Failed to decode ${route} event`, cause))), Effect.map((event) => ({ event, frame })), Effect.mapError(frameError(frame)))), terminal ? Stream.takeUntil(({ event }) => terminal(event)) : (stream) => stream);
const stream = Stream.suspend(() => {
let state = protocol.stream.initial(request);
const parsed = events.pipe(Stream.mapEffect((event) => protocol.stream.step(state, event).pipe(Effect.map(([next, output]) => {
const parsed = events.pipe(Stream.mapEffect(({ event, frame }) => protocol.stream.step(state, event).pipe(Effect.catchCause((cause) => Effect.fail(streamError(route, `Failed to parse ${route} event`, cause))), Effect.map(([next, output]) => {
state = next;
return output;
}))), Stream.flatMap(Stream.fromIterable));
}), Effect.mapError(frameError(frame, event)))), Stream.flatMap(Stream.fromIterable));
const onHalt = protocol.stream.onHalt;

@@ -145,3 +156,10 @@ return onHalt

: parsed;
}).pipe(Stream.catchCause((cause) => Stream.fail(streamError(route, `Failed to read ${route} stream`, cause))), requireTerminalEvent(route));
}).pipe(Stream.catchCause((cause) => Stream.fail(streamError(route, `Failed to read ${route} stream`, cause))), requireTerminalEvent(route), Stream.mapError((error) => new AIError({
reason: AIErrorReason.make({
...error.reason,
message: error.reason.message,
cause: error.reason.cause,
http: error.reason.http ?? execution.http,
}),
})));
return execution.complete ? stream.pipe(Stream.onEnd(execution.complete)) : stream;

@@ -148,0 +166,0 @@ })));

import { Context, Effect, Layer, Stream } from "effect";
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
import { AIError, TransportReason } from "../schema/index.js";
import { HttpContext, AIError } from "../schema/index.js";
export interface Interface {

@@ -12,10 +12,14 @@ readonly execute: (request: HttpClientRequest.HttpClientRequest, middleware?: HttpMiddleware) => Effect.Effect<HttpClientResponse.HttpClientResponse, AIError>;

}
export declare const classifyHttpFailure: (input: {
export declare const responseHttp: (response: HttpClientResponse.HttpClientResponse) => HttpContext;
/** Preserve HTTP diagnostics for executor and externally captured failures alike. */
export declare const httpFailure: (input: {
readonly message: string;
readonly url: string;
readonly url?: string | undefined;
readonly status?: number | undefined;
readonly code?: string | undefined;
readonly data?: unknown;
readonly responseHeaders?: Record<string, string> | undefined;
readonly responseBody?: string | undefined;
}) => import("../schema/errors.js").InvalidRequestReason | import("../schema/errors.js").NoRouteReason | import("../schema/errors.js").AuthenticationReason | import("../schema/errors.js").RateLimitReason | import("../schema/errors.js").QuotaExceededReason | import("../schema/errors.js").ContentPolicyReason | import("../schema/errors.js").ProviderInternalReason | TransportReason | import("../schema/errors.js").InvalidProviderOutputReason | import("../schema/errors.js").UnknownProviderReason;
readonly cause?: unknown;
}) => AIError;
export declare const responseStream: (response: HttpClientResponse.HttpClientResponse) => Stream.Stream<Uint8Array, AIError>;
export declare const stream: (executor: Interface, request: HttpClientRequest.HttpClientRequest, middleware?: HttpMiddleware) => Stream.Stream<Uint8Array, AIError>;

@@ -22,0 +26,0 @@ export declare const layer: Layer.Layer<Service, never, HttpClient.HttpClient>;

import { Cause, Context, Effect, Layer, Option, Schema, Stream } from "effect";
import { FetchHttpClient, Headers, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, } from "effect/unstable/http";
import { HttpContext, HttpRateLimitDetails, HttpRequestDetails, HttpResponseDetails, AIError, TransportReason, } from "../schema/index.js";
import { HttpContext, HttpRateLimitDetails, AIError, TransportError } from "../schema/index.js";
import { classifyProviderFailure } from "../provider-error.js";

@@ -63,16 +63,7 @@ export class Service extends Context.Service()("@opencode/AI/RequestExecutor") {

};
const requestDetails = (request) => new HttpRequestDetails({
method: request.method,
url: request.url,
headers: headerDetails(request.headers),
});
const responseDetails = (response) => new HttpResponseDetails({
export const responseHttp = (response) => new HttpContext({
url: response.request.url,
status: response.status,
headers: headerDetails(response.headers),
});
const responseBody = (body) => {
if (body === undefined)
return {};
return { body };
};
const decodeProviderBody = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Struct({

@@ -87,56 +78,32 @@ message: Schema.optionalKey(Schema.String),

};
const responseHttp = (input) => new HttpContext({
request: requestDetails(input.request),
response: responseDetails(input.response),
...input.body,
rateLimit: input.rateLimit,
});
const statusError = (request) => (response) => Effect.gen(function* () {
const statusError = (response) => Effect.gen(function* () {
if (response.status < 400)
return response;
const body = yield* response.text.pipe(Effect.catch(() => Effect.void));
const headers = normalizedHeaders(response.headers);
const result = yield* response.text.pipe(Effect.result);
return yield* httpFailure({
message: providerMessage(response.status, result._tag === "Success" ? result.success : undefined),
url: response.request.url,
status: response.status,
responseHeaders: headerDetails(response.headers),
responseBody: result._tag === "Success" ? result.success : undefined,
cause: result._tag === "Failure" ? (result.failure.cause ?? result.failure) : undefined,
});
});
/** Preserve HTTP diagnostics for executor and externally captured failures alike. */
export const httpFailure = (input) => {
const headers = normalizedHeaders(Headers.fromInput(input.responseHeaders));
const retryAfter = retryAfterMs(headers);
const rateLimit = rateLimitDetails(headers, retryAfter);
const details = responseBody(body);
return yield* new AIError({
module: "RequestExecutor",
method: "execute",
return new AIError({
reason: classifyProviderFailure({
status: response.status,
message: providerMessage(response.status, body),
message: input.message,
status: input.status,
data: input.data,
rawBody: input.responseBody,
retryAfterMs: retryAfter,
rateLimit,
http: responseHttp({
request,
response,
body: details,
rateLimit,
}),
}),
});
});
// Classifies an HTTP failure captured outside the executor (for example by the
// AI SDK's own fetch) onto the same reason types and HttpContext that
// executor-driven requests produce. The originating request is not available on
// that path, so the method is assumed (language model calls are always POST),
// request headers are empty.
export const classifyHttpFailure = (input) => {
const headers = normalizedHeaders(Headers.fromInput(input.responseHeaders));
const retryAfter = retryAfterMs(headers);
const rateLimit = rateLimitDetails(headers, retryAfter);
const details = responseBody(input.responseBody);
return classifyProviderFailure({
message: input.message,
status: input.status,
code: input.code,
retryAfterMs: retryAfter,
rateLimit,
http: new HttpContext({
request: new HttpRequestDetails({ method: "POST", url: input.url, headers: {} }),
response: input.status === undefined
cause: input.cause,
http: input.status === undefined || input.url === undefined
? undefined
: new HttpResponseDetails({ status: input.status, headers: headerDetails(Headers.fromInput(headers)) }),
...details,
rateLimit,
: new HttpContext({ url: input.url, status: input.status, headers }),
}),

@@ -165,6 +132,6 @@ });

const transportError = (failure) => new AIError({
module: "RequestExecutor",
method: input.operation,
reason: new TransportReason({
reason: new TransportError({
message: failure.message,
cause: source,
http: input.http,
transport: "http",

@@ -174,7 +141,6 @@ operation: input.operation,

url: request.url,
http: new HttpContext({ request: requestDetails(request) }),
}),
});
const source = HttpClientError.isHttpClientError(input.error) && "cause" in input.error.reason
? input.error.reason.cause
? (input.error.reason.cause ?? input.error)
: input.error;

@@ -201,5 +167,6 @@ const native = nativeTransportFailure(source);

};
export const responseStream = (response) => response.stream.pipe(Stream.mapError((error) => httpError({ error, request: response.request, operation: "read", http: responseHttp(response) })));
export const stream = (executor, request, middleware) => Stream.unwrap(Effect.gen(function* () {
const response = yield* executor.execute(request, middleware);
return response.stream.pipe(Stream.mapError((error) => httpError({ error, request: response.request, operation: "read" })));
return responseStream(response);
}));

@@ -210,7 +177,7 @@ export const layer = Layer.effect(Service, Effect.gen(function* () {

if (!middleware)
return yield* http.execute(request).pipe(Effect.mapError((error) => httpError({ error, request, operation: "request" })), Effect.flatMap(statusError(request)));
return yield* http.execute(request).pipe(Effect.mapError((error) => httpError({ error, request, operation: "request" })), Effect.flatMap(statusError));
const response = yield* middleware(request, (input) => http
.execute(input)
.pipe(Effect.mapError((cause) => (cause instanceof Error ? cause : new Error(String(cause)))))).pipe(Effect.mapError((error) => httpError({ error, request, operation: "request" })));
return yield* statusError(response.request)(response);
return yield* statusError(response);
});

@@ -217,0 +184,0 @@ return Service.of({

@@ -20,2 +20,4 @@ import type { Stream } from "effect";

readonly frame: (bytes: Stream.Stream<Uint8Array, AIError>) => Stream.Stream<Frame, AIError>;
/** Original wire representation when framing transforms the provider payload. */
readonly body?: (frame: Frame) => string | undefined;
}

@@ -22,0 +24,0 @@ /** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */

@@ -56,4 +56,9 @@ import { Effect } from "effect";

}),
execute: (prepared, _request, runtime) => Effect.succeed({
frames: prepared.framing.frame(RequestExecutor.stream(runtime.http, prepared.request, prepared.middleware)),
execute: (prepared, _request, runtime) => Effect.gen(function* () {
const response = yield* runtime.http.execute(prepared.request, prepared.middleware);
return {
frames: prepared.framing.frame(RequestExecutor.responseStream(response)),
http: RequestExecutor.responseHttp(response),
body: prepared.framing.body,
};
}),

@@ -60,0 +65,0 @@ });

@@ -6,3 +6,3 @@ import type { Effect, Scope, Stream } from "effect";

import type { WebSocketChannelExecutor } from "./websocket-channel.js";
import type { AIError, LLMRequest } from "../../schema/index.js";
import type { AIError, HttpContext, LLMRequest } from "../../schema/index.js";
export interface TransportRuntime {

@@ -13,2 +13,4 @@ readonly http: RequestExecutorInterface;

readonly frames: Stream.Stream<Frame, AIError>;
readonly http?: HttpContext;
body?(frame: Frame): string | undefined;
/** Optional successful-consumption acknowledgement. HTTP leaves this absent. */

@@ -15,0 +17,0 @@ readonly complete?: Effect.Effect<void>;

import type { Effect, Scope, Stream } from "effect";
import type { Headers } from "effect/unstable/http";
import type { AIError } from "../../schema/index.js";
import type { AIError, HttpContext } from "../../schema/index.js";
export interface WebSocketChannelExecutor {

@@ -9,2 +9,3 @@ readonly execute: (exchange: WebSocketChannelExchange) => Effect.Effect<WebSocketChannelExecution, AIError, Scope.Scope>;

readonly frames: Stream.Stream<string, AIError>;
readonly http?: HttpContext;
/** Commits staged state after the decoded Route stream ends successfully. */

@@ -11,0 +12,0 @@ readonly complete: Effect.Effect<void>;

import { Effect, Stream } from "effect";
import { Headers } from "effect/unstable/http";
import { Socket } from "effect/unstable/socket";
import { AIError } from "../../schema/index.js";
import { AIError, type HttpContext } from "../../schema/index.js";
import type { Transport } from "./index.js";

@@ -12,2 +12,3 @@ import type { WebSocketChannelExecutor } from "./websocket-channel.js";

export interface WebSocketConnection {
readonly http?: HttpContext;
readonly sendText: (message: string) => Effect.Effect<void, AIError>;

@@ -14,0 +15,0 @@ readonly messages: Stream.Stream<string | Uint8Array, AIError>;

import { Cause, Effect, Queue, Stream } from "effect";
import { Headers } from "effect/unstable/http";
import { Socket } from "effect/unstable/socket";
import { AIError, TransportReason } from "../../schema/index.js";
import { AIError, AIErrorReason, TransportError, } from "../../schema/index.js";
import * as HttpTransport from "./http.js";
const MAX_FRAME_BYTES = 16 * 1024 * 1024;
const transportError = (method, message, input) => new AIError({
module: "WebSocketConnector",
method,
reason: new TransportReason({
const transportError = (message, input) => new AIError({
reason: new TransportError({
message,
body: input.body,
cause: input.cause,
transport: "websocket",

@@ -22,14 +22,8 @@ operation: input.operation,

? new AIError({
module: error.module,
method: error.method,
reason: new TransportReason({
reason: new TransportError({
...error.reason,
message: error.reason.message,
transport: error.reason.transport,
operation: error.reason.operation,
code: error.reason.code,
url: error.reason.url,
http: error.reason.http,
cause: error.reason.cause,
phase: input.phase,
delivery: input.delivery,
recovery: error.reason.recovery,
}),

@@ -56,3 +50,3 @@ })

if (ws.readyState === globalThis.WebSocket.CLOSING || ws.readyState === globalThis.WebSocket.CLOSED) {
return Effect.fail(transportError("open", `WebSocket closed before opening (state ${ws.readyState})`, {
return Effect.fail(transportError(`WebSocket closed before opening (state ${ws.readyState})`, {
url: input.url,

@@ -83,3 +77,4 @@ operation: "request",

cleanup();
resume(Effect.fail(transportError("open", `Failed to open WebSocket: ${eventMessage(event)}`, {
resume(Effect.fail(transportError(`Failed to open WebSocket: ${eventMessage(event)}`, {
cause: "error" in event ? (event.error ?? event) : event,
url: input.url,

@@ -93,3 +88,5 @@ operation: "request",

cleanup();
resume(Effect.fail(transportError("open", `WebSocket closed before opening with code ${event.code}`, {
resume(Effect.fail(transportError(`WebSocket closed before opening with code ${event.code}`, {
body: event.reason,
cause: event,
url: input.url,

@@ -121,3 +118,4 @@ operation: "request",

},
catch: (error) => transportError("prepare", error instanceof Error ? error.message : "Invalid WebSocket URL", {
catch: (error) => transportError(error instanceof Error ? error.message : "Invalid WebSocket URL", {
cause: error,
url: value,

@@ -139,3 +137,4 @@ operation: "request",

}),
catch: (error) => transportError("open", error instanceof Error ? error.message : "Failed to construct WebSocket", {
catch: (error) => transportError(error instanceof Error ? error.message : "Failed to construct WebSocket", {
cause: error,
url: input.url,

@@ -156,3 +155,4 @@ operation: "request",

return false;
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", "WebSocket message exceeds the 16 MiB limit", {
Queue.failCauseUnsafe(messages, Cause.fail(transportError("WebSocket message exceeds the 16 MiB limit", {
body: typeof message === "string" ? message : new TextDecoder().decode(message),
url: input.url,

@@ -172,3 +172,4 @@ operation: "read",

return;
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", "WebSocket inbound queue overflow", {
Queue.failCauseUnsafe(messages, Cause.fail(transportError("WebSocket inbound queue overflow", {
body: typeof message === "string" ? message : new TextDecoder().decode(message),
url: input.url,

@@ -186,3 +187,4 @@ operation: "read",

return offer(binary);
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", "Unsupported WebSocket message payload", {
Queue.failCauseUnsafe(messages, Cause.fail(transportError("Unsupported WebSocket message payload", {
cause: event,
url: input.url,

@@ -195,3 +197,4 @@ operation: "read",

const onError = (event) => {
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", `WebSocket error: ${eventMessage(event)}`, {
Queue.failCauseUnsafe(messages, Cause.fail(transportError(`WebSocket error: ${eventMessage(event)}`, {
cause: "error" in event ? (event.error ?? event) : event,
url: input.url,

@@ -204,3 +207,5 @@ operation: "read",

const onClose = (event) => {
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", `WebSocket closed with code ${event.code}`, {
Queue.failCauseUnsafe(messages, Cause.fail(transportError(`WebSocket closed with code ${event.code}`, {
body: event.reason,
cause: event,
url: input.url,

@@ -223,3 +228,3 @@ operation: "read",

if (ws.readyState !== globalThis.WebSocket.OPEN)
return Effect.fail(transportError("sendText", `WebSocket is not open (state ${ws.readyState})`, {
return Effect.fail(transportError(`WebSocket is not open (state ${ws.readyState})`, {
url: input.url,

@@ -232,3 +237,4 @@ operation: "write",

try: () => ws.send(message),
catch: (error) => transportError("sendText", error instanceof Error ? error.message : "Failed to send WebSocket message", {
catch: (error) => transportError(error instanceof Error ? error.message : "Failed to send WebSocket message", {
cause: error,
url: input.url,

@@ -262,6 +268,14 @@ operation: "write",

const create = yield* exchange.driver.create(undefined);
yield* connection.sendText(create.message);
yield* connection.sendText(create.message).pipe(Effect.mapError((error) => new AIError({
reason: AIErrorReason.make({
...error.reason,
message: error.reason.message,
cause: error.reason.cause,
http: error.reason.http ?? connection.http,
}),
})));
const decoder = new TextDecoder();
let observed = false;
return {
http: connection.http,
frames: connection.messages.pipe(Stream.map((message) => {

@@ -273,3 +287,29 @@ observed = true;

delivery: observed ? "accepted" : "ambiguous",
})), Stream.mapEffect((frame) => exchange.driver.observe(create, frame)), Stream.takeUntil(observationTerminal), Stream.mapEffect(observationFrame)),
})), Stream.mapEffect((frame) => exchange.driver.observe(create, frame).pipe(Effect.mapError((error) => new AIError({
reason: AIErrorReason.make({
...error.reason,
message: error.reason.message,
cause: error.reason.cause,
body: frame,
}),
})), Effect.map((observation) => "error" in observation
? {
...observation,
error: new AIError({
reason: AIErrorReason.make({
...observation.error.reason,
message: observation.error.reason.message,
cause: observation.error.reason.cause,
body: frame,
}),
}),
}
: observation))), Stream.takeUntil(observationTerminal), Stream.mapEffect(observationFrame), Stream.mapError((error) => new AIError({
reason: AIErrorReason.make({
...error.reason,
message: error.reason.message,
cause: error.reason.cause,
http: error.reason.http ?? connection.http,
}),
}))),
complete: Effect.void,

@@ -301,3 +341,3 @@ };

if (!webSocket) {
return Effect.fail(transportError("json", "WebSocket JSON transport requires StreamOptions.webSocket", {
return Effect.fail(transportError("WebSocket JSON transport requires StreamOptions.webSocket", {
url: prepared.url,

@@ -317,3 +357,3 @@ operation: "request",

connect: { url: prepared.url, headers: prepared.headers },
fallback: () => Stream.fail(transportError("fallback", "WebSocket JSON transport does not provide HTTP fallback", {
fallback: () => Stream.fail(transportError("WebSocket JSON transport does not provide HTTP fallback", {
url: prepared.url,

@@ -320,0 +360,0 @@ operation: "request",

@@ -5,15 +5,9 @@ import { Schema } from "effect";

export type ProviderFailureClassification = typeof ProviderFailureClassification.Type;
declare const HttpRequestDetails_base: Schema.Class<HttpRequestDetails, Schema.Struct<{
readonly method: Schema.String;
declare const HttpContext_base: Schema.Class<HttpContext, Schema.Struct<{
readonly url: Schema.String;
readonly status: Schema.Int;
readonly headers: Schema.$Record<Schema.String, Schema.String>;
}>, {}>;
export declare class HttpRequestDetails extends HttpRequestDetails_base {
export declare class HttpContext extends HttpContext_base {
}
declare const HttpResponseDetails_base: Schema.Class<HttpResponseDetails, Schema.Struct<{
readonly status: Schema.Number;
readonly headers: Schema.$Record<Schema.String, Schema.String>;
}>, {}>;
export declare class HttpResponseDetails extends HttpResponseDetails_base {
}
declare const HttpRateLimitDetails_base: Schema.Class<HttpRateLimitDetails, Schema.Struct<{

@@ -27,74 +21,66 @@ readonly retryAfterMs: Schema.optional<Schema.Number>;

}
declare const HttpContext_base: Schema.Class<HttpContext, Schema.Struct<{
readonly request: typeof HttpRequestDetails;
readonly response: Schema.optional<typeof HttpResponseDetails>;
readonly body: Schema.optional<Schema.String>;
readonly bodyTruncated: Schema.optional<Schema.Boolean>;
readonly rateLimit: Schema.optional<typeof HttpRateLimitDetails>;
}>, {}>;
export declare class HttpContext extends HttpContext_base {
}
declare const InvalidRequestReason_base: Schema.Class<InvalidRequestReason, Schema.Struct<{
readonly _tag: Schema.tag<"InvalidRequest">;
readonly message: Schema.String;
declare const InvalidRequestError_base: Schema.Class<InvalidRequestError, Schema.TaggedStruct<"InvalidRequest", {
readonly parameter: Schema.optional<Schema.String>;
readonly classification: Schema.optional<Schema.Literals<readonly ["context-overflow", "payload-too-large"]>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly message: Schema.String;
readonly body: Schema.optional<Schema.String>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class InvalidRequestReason extends InvalidRequestReason_base {
readonly cause: Schema.optional<Schema.Defect>;
}>, import("effect/Cause").YieldableError>;
export declare class InvalidRequestError extends InvalidRequestError_base {
}
declare const NoRouteReason_base: Schema.Class<NoRouteReason, Schema.Struct<{
readonly _tag: Schema.tag<"NoRoute">;
declare const NoRouteError_base: Schema.Class<NoRouteError, Schema.TaggedStruct<"NoRoute", {
readonly route: Schema.String;
readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
readonly model: Schema.brand<Schema.String, "AI.ModelID">;
}>, {}>;
export declare class NoRouteReason extends NoRouteReason_base {
get message(): string;
readonly message: Schema.String;
readonly body: Schema.optional<Schema.String>;
readonly http: Schema.optional<typeof HttpContext>;
readonly cause: Schema.optional<Schema.Defect>;
}>, import("effect/Cause").YieldableError>;
export declare class NoRouteError extends NoRouteError_base {
}
declare const AuthenticationReason_base: Schema.Class<AuthenticationReason, Schema.Struct<{
readonly _tag: Schema.tag<"Authentication">;
declare const AuthenticationError_base: Schema.Class<AuthenticationError, Schema.TaggedStruct<"Authentication", {
readonly kind: Schema.Literals<readonly ["missing", "invalid", "expired", "insufficient-permissions", "unknown"]>;
readonly message: Schema.String;
readonly kind: Schema.Literals<readonly ["missing", "invalid", "expired", "insufficient-permissions", "unknown"]>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly body: Schema.optional<Schema.String>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class AuthenticationReason extends AuthenticationReason_base {
readonly cause: Schema.optional<Schema.Defect>;
}>, import("effect/Cause").YieldableError>;
export declare class AuthenticationError extends AuthenticationError_base {
}
declare const RateLimitReason_base: Schema.Class<RateLimitReason, Schema.Struct<{
readonly _tag: Schema.tag<"RateLimit">;
readonly message: Schema.String;
declare const RateLimitError_base: Schema.Class<RateLimitError, Schema.TaggedStruct<"RateLimit", {
readonly retryAfterMs: Schema.optional<Schema.Number>;
readonly rateLimit: Schema.optional<typeof HttpRateLimitDetails>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class RateLimitReason extends RateLimitReason_base {
}
declare const QuotaExceededReason_base: Schema.Class<QuotaExceededReason, Schema.Struct<{
readonly _tag: Schema.tag<"QuotaExceeded">;
readonly message: Schema.String;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly body: Schema.optional<Schema.String>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class QuotaExceededReason extends QuotaExceededReason_base {
readonly cause: Schema.optional<Schema.Defect>;
}>, import("effect/Cause").YieldableError>;
export declare class RateLimitError extends RateLimitError_base {
}
declare const ContentPolicyReason_base: Schema.Class<ContentPolicyReason, Schema.Struct<{
readonly _tag: Schema.tag<"ContentPolicy">;
readonly message: Schema.String;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class ContentPolicyReason extends ContentPolicyReason_base {
declare const QuotaExceededError_base: Schema.Class<QuotaExceededError, Schema.TaggedStruct<"QuotaExceeded", {
message: Schema.String;
body: Schema.optional<Schema.String>;
http: Schema.optional<typeof HttpContext>;
cause: Schema.optional<Schema.Defect>;
}>, import("effect/Cause").YieldableError>;
export declare class QuotaExceededError extends QuotaExceededError_base {
}
declare const ProviderInternalReason_base: Schema.Class<ProviderInternalReason, Schema.Struct<{
readonly _tag: Schema.tag<"ProviderInternal">;
declare const ContentPolicyError_base: Schema.Class<ContentPolicyError, Schema.TaggedStruct<"ContentPolicy", {
message: Schema.String;
body: Schema.optional<Schema.String>;
http: Schema.optional<typeof HttpContext>;
cause: Schema.optional<Schema.Defect>;
}>, import("effect/Cause").YieldableError>;
export declare class ContentPolicyError extends ContentPolicyError_base {
}
declare const ProviderInternalError_base: Schema.Class<ProviderInternalError, Schema.TaggedStruct<"ProviderInternal", {
readonly retryAfterMs: Schema.optional<Schema.Number>;
readonly message: Schema.String;
readonly status: Schema.optional<Schema.Number>;
readonly retryAfterMs: Schema.optional<Schema.Number>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly body: Schema.optional<Schema.String>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class ProviderInternalReason extends ProviderInternalReason_base {
readonly cause: Schema.optional<Schema.Defect>;
}>, import("effect/Cause").YieldableError>;
export declare class ProviderInternalError extends ProviderInternalError_base {
}

@@ -105,5 +91,3 @@ export declare const TransportType: Schema.Literals<readonly ["http", "websocket"]>;

export type TransportOperation = typeof TransportOperation.Type;
declare const TransportReason_base: Schema.Class<TransportReason, Schema.Struct<{
readonly _tag: Schema.tag<"Transport">;
readonly message: Schema.String;
declare const TransportError_base: Schema.Class<TransportError, Schema.TaggedStruct<"Transport", {
readonly transport: Schema.Literals<readonly ["http", "websocket"]>;

@@ -113,38 +97,37 @@ readonly operation: Schema.Literals<readonly ["request", "read", "write"]>;

readonly url: Schema.optional<Schema.String>;
readonly http: Schema.optional<typeof HttpContext>;
readonly phase: Schema.optional<Schema.Literals<readonly ["prepare", "queue", "connect", "send", "receive", "decode", "complete", "fallback", "close"]>>;
readonly delivery: Schema.optional<Schema.Literals<readonly ["not-sent", "rejected", "ambiguous", "accepted"]>>;
readonly recovery: Schema.optional<Schema.Literals<readonly ["retry-connect", "retry-full", "rotate-and-retry-full", "fallback-http", "fail"]>>;
}>, {}>;
export declare class TransportReason extends TransportReason_base {
readonly message: Schema.String;
readonly body: Schema.optional<Schema.String>;
readonly http: Schema.optional<typeof HttpContext>;
readonly cause: Schema.optional<Schema.Defect>;
}>, import("effect/Cause").YieldableError>;
export declare class TransportError extends TransportError_base {
}
declare const InvalidProviderOutputReason_base: Schema.Class<InvalidProviderOutputReason, Schema.Struct<{
readonly _tag: Schema.tag<"InvalidProviderOutput">;
readonly message: Schema.String;
declare const InvalidProviderOutputError_base: Schema.Class<InvalidProviderOutputError, Schema.TaggedStruct<"InvalidProviderOutput", {
readonly classification: Schema.optional<Schema.Literals<readonly ["incomplete-stream"]>>;
readonly route: Schema.optional<Schema.String>;
readonly raw: Schema.optional<Schema.String>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}>, {}>;
export declare class InvalidProviderOutputReason extends InvalidProviderOutputReason_base {
}
declare const UnknownProviderReason_base: Schema.Class<UnknownProviderReason, Schema.Struct<{
readonly _tag: Schema.tag<"UnknownProvider">;
readonly message: Schema.String;
readonly status: Schema.optional<Schema.Number>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly body: Schema.optional<Schema.String>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class UnknownProviderReason extends UnknownProviderReason_base {
readonly cause: Schema.optional<Schema.Defect>;
}>, import("effect/Cause").YieldableError>;
export declare class InvalidProviderOutputError extends InvalidProviderOutputError_base {
}
export declare const AIErrorReason: Schema.toTaggedUnion<"_tag", readonly [typeof InvalidRequestReason, typeof NoRouteReason, typeof AuthenticationReason, typeof RateLimitReason, typeof QuotaExceededReason, typeof ContentPolicyReason, typeof ProviderInternalReason, typeof TransportReason, typeof InvalidProviderOutputReason, typeof UnknownProviderReason]>;
declare const UnknownProviderError_base: Schema.Class<UnknownProviderError, Schema.TaggedStruct<"UnknownProvider", {
message: Schema.String;
body: Schema.optional<Schema.String>;
http: Schema.optional<typeof HttpContext>;
cause: Schema.optional<Schema.Defect>;
}>, import("effect/Cause").YieldableError>;
export declare class UnknownProviderError extends UnknownProviderError_base {
}
export declare const AIErrorReason: Schema.toTaggedUnion<"_tag", readonly [typeof InvalidRequestError, typeof NoRouteError, typeof AuthenticationError, typeof RateLimitError, typeof QuotaExceededError, typeof ContentPolicyError, typeof ProviderInternalError, typeof TransportError, typeof InvalidProviderOutputError, typeof UnknownProviderError]>;
export type AIErrorReason = Schema.Schema.Type<typeof AIErrorReason>;
declare const AIError_base: Schema.Class<AIError, Schema.TaggedStruct<"AI.Error", {
readonly module: Schema.String;
readonly method: Schema.String;
readonly reason: Schema.toTaggedUnion<"_tag", readonly [typeof InvalidRequestReason, typeof NoRouteReason, typeof AuthenticationReason, typeof RateLimitReason, typeof QuotaExceededReason, typeof ContentPolicyReason, typeof ProviderInternalReason, typeof TransportReason, typeof InvalidProviderOutputReason, typeof UnknownProviderReason]>;
readonly body: Schema.optional<Schema.String>;
readonly reason: Schema.toTaggedUnion<"_tag", readonly [typeof InvalidRequestError, typeof NoRouteError, typeof AuthenticationError, typeof RateLimitError, typeof QuotaExceededError, typeof ContentPolicyError, typeof ProviderInternalError, typeof TransportError, typeof InvalidProviderOutputError, typeof UnknownProviderError]>;
}>, import("effect/Cause").YieldableError>;
export declare class AIError extends AIError_base {
readonly cause: InvalidRequestReason | NoRouteReason | AuthenticationReason | RateLimitReason | QuotaExceededReason | ContentPolicyReason | ProviderInternalReason | TransportReason | InvalidProviderOutputReason | UnknownProviderReason;
readonly cause: InvalidRequestError | NoRouteError | AuthenticationError | RateLimitError | QuotaExceededError | ContentPolicyError | ProviderInternalError | TransportError | InvalidProviderOutputError | UnknownProviderError;
get message(): string;

@@ -151,0 +134,0 @@ }

import { Schema } from "effect";
import { Tool } from "@opencode-ai/schema/tool";
import { ModelID, ProviderID, RouteID } from "./ids.js";
import { ProviderMetadata } from "./messages.js";
export const ProviderFailureClassification = Schema.Literals(["context-overflow", "payload-too-large"]);
export class HttpRequestDetails extends Schema.Class("AI.HttpRequestDetails")({
method: Schema.String,
export class HttpContext extends Schema.Class("AI.HttpContext")({
url: Schema.String,
status: Schema.Int.check(Schema.isBetween({ minimum: 100, maximum: 599 })),
headers: Schema.Record(Schema.String, Schema.String),
}) {
}
export class HttpResponseDetails extends Schema.Class("AI.HttpResponseDetails")({
status: Schema.Number,
headers: Schema.Record(Schema.String, Schema.String),
}) {
}
export class HttpRateLimitDetails extends Schema.Class("AI.HttpRateLimitDetails")({

@@ -24,21 +18,17 @@ retryAfterMs: Schema.optional(Schema.Number),

}
export class HttpContext extends Schema.Class("AI.HttpContext")({
request: HttpRequestDetails,
response: Schema.optional(HttpResponseDetails),
const ReasonFields = {
message: Schema.String,
// Preserve the complete original response or triggering event before decoding narrows it.
body: Schema.optional(Schema.String),
bodyTruncated: Schema.optional(Schema.Boolean),
rateLimit: Schema.optional(HttpRateLimitDetails),
}) {
}
export class InvalidRequestReason extends Schema.Class("AI.Error.InvalidRequest")({
_tag: Schema.tag("InvalidRequest"),
message: Schema.String,
http: Schema.optional(HttpContext),
cause: Schema.optional(Schema.Defect({ includeStack: true })),
};
export class InvalidRequestError extends Schema.TaggedError("AI.Error.InvalidRequest")("InvalidRequest", {
...ReasonFields,
parameter: Schema.optional(Schema.String),
classification: Schema.optional(ProviderFailureClassification),
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
}
export class NoRouteReason extends Schema.Class("AI.Error.NoRoute")({
_tag: Schema.tag("NoRoute"),
export class NoRouteError extends Schema.TaggedError("AI.Error.NoRoute")("NoRoute", {
...ReasonFields,
route: RouteID,

@@ -48,44 +38,21 @@ provider: ProviderID,

}) {
get message() {
return `No AI route for ${this.provider}/${this.model} using ${this.route}`;
}
}
export class AuthenticationReason extends Schema.Class("AI.Error.Authentication")({
_tag: Schema.tag("Authentication"),
message: Schema.String,
export class AuthenticationError extends Schema.TaggedError("AI.Error.Authentication")("Authentication", {
...ReasonFields,
kind: Schema.Literals(["missing", "invalid", "expired", "insufficient-permissions", "unknown"]),
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
}
export class RateLimitReason extends Schema.Class("AI.Error.RateLimit")({
_tag: Schema.tag("RateLimit"),
message: Schema.String,
export class RateLimitError extends Schema.TaggedError("AI.Error.RateLimit")("RateLimit", {
...ReasonFields,
retryAfterMs: Schema.optional(Schema.Number),
rateLimit: Schema.optional(HttpRateLimitDetails),
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
}
export class QuotaExceededReason extends Schema.Class("AI.Error.QuotaExceeded")({
_tag: Schema.tag("QuotaExceeded"),
message: Schema.String,
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
export class QuotaExceededError extends Schema.TaggedError("AI.Error.QuotaExceeded")("QuotaExceeded", ReasonFields) {
}
export class ContentPolicyReason extends Schema.Class("AI.Error.ContentPolicy")({
_tag: Schema.tag("ContentPolicy"),
message: Schema.String,
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
export class ContentPolicyError extends Schema.TaggedError("AI.Error.ContentPolicy")("ContentPolicy", ReasonFields) {
}
export class ProviderInternalReason extends Schema.Class("AI.Error.ProviderInternal")({
_tag: Schema.tag("ProviderInternal"),
message: Schema.String,
status: Schema.optional(Schema.Number),
export class ProviderInternalError extends Schema.TaggedError("AI.Error.ProviderInternal")("ProviderInternal", {
...ReasonFields,
retryAfterMs: Schema.optional(Schema.Number),
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {

@@ -95,5 +62,4 @@ }

export const TransportOperation = Schema.Literals(["request", "read", "write"]);
export class TransportReason extends Schema.Class("AI.Error.Transport")({
_tag: Schema.tag("Transport"),
message: Schema.String,
export class TransportError extends Schema.TaggedError("AI.Error.Transport")("Transport", {
...ReasonFields,
transport: TransportType,

@@ -103,3 +69,2 @@ operation: TransportOperation,

url: Schema.optional(Schema.String),
http: Schema.optional(HttpContext),
phase: Schema.optional(Schema.Literals(["prepare", "queue", "connect", "send", "receive", "decode", "complete", "fallback", "close"])),

@@ -110,42 +75,28 @@ delivery: Schema.optional(Schema.Literals(["not-sent", "rejected", "ambiguous", "accepted"])),

}
export class InvalidProviderOutputReason extends Schema.Class("AI.Error.InvalidProviderOutput")({
_tag: Schema.tag("InvalidProviderOutput"),
message: Schema.String,
export class InvalidProviderOutputError extends Schema.TaggedError("AI.Error.InvalidProviderOutput")("InvalidProviderOutput", {
...ReasonFields,
classification: Schema.optional(Schema.Literals(["incomplete-stream"])),
route: Schema.optional(Schema.String),
raw: Schema.optional(Schema.String),
providerMetadata: Schema.optional(ProviderMetadata),
}) {
}
export class UnknownProviderReason extends Schema.Class("AI.Error.UnknownProvider")({
_tag: Schema.tag("UnknownProvider"),
message: Schema.String,
status: Schema.optional(Schema.Number),
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
export class UnknownProviderError extends Schema.TaggedError("AI.Error.UnknownProvider")("UnknownProvider", ReasonFields) {
}
export const AIErrorReason = Schema.Union([
InvalidRequestReason,
NoRouteReason,
AuthenticationReason,
RateLimitReason,
QuotaExceededReason,
ContentPolicyReason,
ProviderInternalReason,
TransportReason,
InvalidProviderOutputReason,
UnknownProviderReason,
InvalidRequestError,
NoRouteError,
AuthenticationError,
RateLimitError,
QuotaExceededError,
ContentPolicyError,
ProviderInternalError,
TransportError,
InvalidProviderOutputError,
UnknownProviderError,
]).pipe(Schema.toTaggedUnion("_tag"));
export class AIError extends Schema.TaggedError()("AI.Error", {
module: Schema.String,
method: Schema.String,
reason: AIErrorReason,
// Raw provider payload as a string, so classified failures never lose the
// original error detail even when the pretty message is a summary.
body: Schema.optional(Schema.String),
}) {
cause = this.reason;
get message() {
return `${this.module}.${this.method}: ${this.reason.message}`;
return this.reason.message;
}

@@ -152,0 +103,0 @@ }

{
"$schema": "https://json.schemastore.org/package.json",
"version": "0.0.0-dev-18409",
"version": "0.0.0-dev-18411",
"name": "@opencode-ai/ai",

@@ -33,3 +33,3 @@ "type": "module",

"@effect/platform-node": "4.0.0-rc.111",
"@opencode-ai/http-recorder": "0.0.0-dev-18409",
"@opencode-ai/http-recorder": "0.0.0-dev-18411",
"@tsconfig/bun": "1.0.9",

@@ -43,3 +43,3 @@ "@types/bun": "1.3.13",

"@smithy/util-utf8": "4.2.2",
"@opencode-ai/schema": "0.0.0-dev-18409",
"@opencode-ai/schema": "0.0.0-dev-18411",
"aws4fetch": "1.0.20",

@@ -46,0 +46,0 @@ "effect": "4.0.0-rc.111",