@opencode-ai/ai 0.0.0-beta-18387 → 0.0.0-beta-18593
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -11
- package/dist/image.js +5 -4
- package/dist/llm.d.ts +2 -0
- package/dist/llm.js +4 -7
- package/dist/protocols/anthropic-messages.js +7 -5
- package/dist/protocols/bedrock-converse.d.ts +1 -0
- package/dist/protocols/bedrock-converse.js +17 -7
- package/dist/protocols/bedrock-event-stream.js +16 -6
- package/dist/protocols/gemini.d.ts +1 -0
- package/dist/protocols/gemini.js +13 -1
- package/dist/protocols/google-images.js +8 -18
- package/dist/protocols/open-responses-channel.js +20 -9
- package/dist/protocols/open-responses-continuation.js +9 -8
- package/dist/protocols/open-responses.d.ts +9 -3
- package/dist/protocols/open-responses.js +139 -78
- package/dist/protocols/openai-chat.d.ts +3 -1
- package/dist/protocols/openai-chat.js +39 -28
- package/dist/protocols/openai-compatible-chat.js +1 -2
- package/dist/protocols/openai-images.js +11 -16
- package/dist/protocols/openai-responses.js +1 -1
- package/dist/protocols/shared.d.ts +11 -7
- package/dist/protocols/shared.js +31 -18
- package/dist/protocols/utils/image-input.d.ts +4 -4
- package/dist/protocols/utils/image-input.js +6 -8
- package/dist/protocols/utils/lifecycle.d.ts +6 -2
- package/dist/protocols/utils/lifecycle.js +11 -7
- package/dist/protocols/utils/tool-stream.d.ts +6 -0
- package/dist/protocols/xai-images.js +7 -12
- package/dist/protocols/zai-images.js +5 -10
- package/dist/provider-error.d.ts +4 -4
- package/dist/provider-error.js +39 -55
- package/dist/providers/groq.d.ts +1 -1
- package/dist/providers/groq.js +1 -2
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +1 -2
- package/dist/route/auth.js +3 -5
- package/dist/route/client.d.ts +2 -0
- package/dist/route/client.js +29 -11
- package/dist/route/executor.d.ts +9 -5
- package/dist/route/executor.js +33 -66
- package/dist/route/framing.d.ts +6 -2
- package/dist/route/framing.js +5 -0
- package/dist/route/transport/http.js +7 -2
- package/dist/route/transport/index.d.ts +3 -1
- package/dist/route/transport/websocket-channel.d.ts +2 -1
- package/dist/route/transport/websocket.d.ts +2 -1
- package/dist/route/transport/websocket.js +70 -30
- package/dist/schema/errors.d.ts +71 -89
- package/dist/schema/errors.js +35 -87
- package/dist/schema/events.d.ts +2835 -351
- package/dist/schema/events.js +32 -14
- package/dist/testing.d.ts +41 -2
- package/dist/testing.js +39 -18
- package/package.json +5 -5
|
@@ -2,7 +2,7 @@ import { Effect, Encoding, Schema } from "effect";
|
|
|
2
2
|
import { Headers, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
|
3
3
|
import { ImageModel, GeneratedImage, ImageResponse, } from "../image.js";
|
|
4
4
|
import { Auth } from "../route/auth.js";
|
|
5
|
-
import {
|
|
5
|
+
import { Usage, mergeHttpOptions, mergeJsonRecords } from "../schema/index.js";
|
|
6
6
|
import { ProviderShared } from "./shared.js";
|
|
7
7
|
import { ImageInputs } from "./utils/image-input.js";
|
|
8
8
|
import { OpenAIImage } from "./utils/openai-image.js";
|
|
@@ -35,11 +35,6 @@ const nativeOptions = (options) => {
|
|
|
35
35
|
...native,
|
|
36
36
|
};
|
|
37
37
|
};
|
|
38
|
-
const invalidOutput = (message) => new AIError({
|
|
39
|
-
module: ADAPTER,
|
|
40
|
-
method: "generate",
|
|
41
|
-
reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
|
|
42
|
-
});
|
|
43
38
|
const applyQuery = (url, query) => {
|
|
44
39
|
if (!query)
|
|
45
40
|
return url;
|
|
@@ -53,14 +48,14 @@ export const model = (input) => {
|
|
|
53
48
|
generate: Effect.fn("OpenAIImages.generate")(function* (request, execute) {
|
|
54
49
|
const mask = request.options?.mask;
|
|
55
50
|
if (mask !== undefined && (request.images?.length ?? 0) === 0)
|
|
56
|
-
return yield* ImageInputs.invalid(
|
|
51
|
+
return yield* ImageInputs.invalid("An OpenAI image mask requires at least one input image");
|
|
57
52
|
const http = mergeHttpOptions(request.model.http, request.http);
|
|
58
53
|
const sourceImages = request.images ?? [];
|
|
59
54
|
const multipartImages = yield* Effect.forEach(sourceImages, (image) => {
|
|
60
55
|
if (image.type === "bytes")
|
|
61
56
|
return Effect.succeed({ data: image.data, mediaType: image.mediaType });
|
|
62
57
|
if (image.type === "url")
|
|
63
|
-
return ImageInputs.decodeDataUrl(image.url
|
|
58
|
+
return ImageInputs.decodeDataUrl(image.url);
|
|
64
59
|
return Effect.undefined;
|
|
65
60
|
});
|
|
66
61
|
const multipartMask = mask === undefined
|
|
@@ -68,7 +63,7 @@ export const model = (input) => {
|
|
|
68
63
|
: mask.type === "bytes"
|
|
69
64
|
? { data: mask.data, mediaType: mask.mediaType }
|
|
70
65
|
: mask.type === "url"
|
|
71
|
-
? yield* ImageInputs.decodeDataUrl(mask.url
|
|
66
|
+
? yield* ImageInputs.decodeDataUrl(mask.url)
|
|
72
67
|
: undefined;
|
|
73
68
|
const useMultipart = sourceImages.length > 0 &&
|
|
74
69
|
multipartImages.every((image) => image !== undefined) &&
|
|
@@ -111,7 +106,7 @@ export const model = (input) => {
|
|
|
111
106
|
return undefined;
|
|
112
107
|
});
|
|
113
108
|
if (references.some((image) => image === undefined))
|
|
114
|
-
return yield* ImageInputs.invalid(
|
|
109
|
+
return yield* ImageInputs.invalid("OpenAI Images accepts image URLs, data URLs, bytes, and file IDs");
|
|
115
110
|
const maskReference = mask === undefined
|
|
116
111
|
? undefined
|
|
117
112
|
: mask.type === "bytes"
|
|
@@ -122,7 +117,7 @@ export const model = (input) => {
|
|
|
122
117
|
? { file_id: mask.id }
|
|
123
118
|
: undefined;
|
|
124
119
|
if (mask !== undefined && maskReference === undefined)
|
|
125
|
-
return yield* ImageInputs.invalid(
|
|
120
|
+
return yield* ImageInputs.invalid("OpenAI Images accepts masks as URLs, data URLs, bytes, or file IDs");
|
|
126
121
|
const requestBody = mergeJsonRecords({
|
|
127
122
|
model: request.model.id,
|
|
128
123
|
prompt: request.prompt,
|
|
@@ -144,13 +139,13 @@ export const model = (input) => {
|
|
|
144
139
|
return ImageModel.make({ id: input.id, provider: "openai", route, http: input.http });
|
|
145
140
|
};
|
|
146
141
|
const parseResponse = Effect.fn("OpenAIImages.parseResponse")(function* (response, options, overlay) {
|
|
147
|
-
const
|
|
148
|
-
const decoded = yield* Schema.decodeUnknownEffect(OpenAIImageResponse)(
|
|
142
|
+
const output = yield* ProviderShared.imageResponse(ADAPTER, "OpenAI Images", response);
|
|
143
|
+
const decoded = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(OpenAIImageResponse))(output.body).pipe(Effect.mapError((cause) => output.invalid("OpenAI Images returned an invalid response", cause)));
|
|
149
144
|
const requestBody = mergeJsonRecords(nativeOptions(options), overlay);
|
|
150
145
|
const format = decoded.output_format ?? (typeof requestBody?.output_format === "string" ? requestBody.output_format : "png");
|
|
151
146
|
const images = yield* Effect.forEach(decoded.data, (item, index) => {
|
|
152
147
|
if (item.b64_json)
|
|
153
|
-
return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(Effect.mapError(() =>
|
|
148
|
+
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({
|
|
154
149
|
mediaType: `image/${format}`,
|
|
155
150
|
data,
|
|
156
151
|
providerMetadata: item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } },
|
|
@@ -161,10 +156,10 @@ const parseResponse = Effect.fn("OpenAIImages.parseResponse")(function* (respons
|
|
|
161
156
|
data: item.url,
|
|
162
157
|
providerMetadata: item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } },
|
|
163
158
|
}));
|
|
164
|
-
return Effect.fail(
|
|
159
|
+
return Effect.fail(output.invalid(`OpenAI Images result ${index} has neither image data nor a URL`));
|
|
165
160
|
});
|
|
166
161
|
if (images.length === 0)
|
|
167
|
-
return yield*
|
|
162
|
+
return yield* output.invalid("OpenAI Images returned no images");
|
|
168
163
|
return new ImageResponse({
|
|
169
164
|
images,
|
|
170
165
|
usage: decoded.usage === undefined
|
|
@@ -115,7 +115,7 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request)
|
|
|
115
115
|
const hostedToolResult = Effect.fn("OpenAIResponses.hostedToolResult")(function* (item) {
|
|
116
116
|
const isError = item.error !== undefined && item.error !== null;
|
|
117
117
|
if (item.type === "image_generation_call" && item.result) {
|
|
118
|
-
yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(Effect.mapError(() => ProviderShared.eventError(ADAPTER, "OpenAI Responses returned invalid image base64")));
|
|
118
|
+
yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(Effect.mapError((cause) => ProviderShared.eventError(ADAPTER, "OpenAI Responses returned invalid image base64", undefined, cause)));
|
|
119
119
|
const format = item.output_format ?? "png";
|
|
120
120
|
return {
|
|
121
121
|
type: "content",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Tool } from "@opencode-ai/schema/tool";
|
|
2
2
|
import { Effect, Schema, Stream } from "effect";
|
|
3
|
-
import { Headers, HttpClientRequest } from "effect/unstable/http";
|
|
3
|
+
import { Headers, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
|
4
4
|
import { AIError, type ContentPart, type LLMRequest, type MediaPart, type ToolResultPart } from "../schema/index.js";
|
|
5
5
|
import { isRecord } from "../utils/record.js";
|
|
6
6
|
export { isRecord };
|
|
@@ -54,7 +54,7 @@ export declare const subtractTokens: (total: number | undefined, subtrahend: num
|
|
|
54
54
|
* (e.g. Anthropic, whose `input_tokens` is already non-cached only).
|
|
55
55
|
*/
|
|
56
56
|
export declare const sumTokens: (...values: ReadonlyArray<number | undefined>) => number | undefined;
|
|
57
|
-
export declare const eventError: (route: string, message: string,
|
|
57
|
+
export declare const eventError: (route: string, message: string, body?: string, cause?: unknown) => AIError;
|
|
58
58
|
export declare const parseJson: (route: string, input: string, message: string) => Effect.Effect<unknown, AIError, never>;
|
|
59
59
|
/**
|
|
60
60
|
* Join the `text` field of a list of parts with newlines. Used by routes
|
|
@@ -117,17 +117,21 @@ export declare const toolResultText: (part: ToolResultPart) => string;
|
|
|
117
117
|
export declare const errorText: (error: unknown) => string;
|
|
118
118
|
/**
|
|
119
119
|
* `framing` step for Server-Sent Events. Decodes UTF-8, runs the SSE channel
|
|
120
|
-
* decoder, optionally filters named events, and drops empty
|
|
121
|
-
*
|
|
122
|
-
*
|
|
120
|
+
* decoder, optionally filters named events, and drops empty events. `[DONE]`
|
|
121
|
+
* is dropped by default or retained for protocols that use it as their stream
|
|
122
|
+
* boundary. Retry control events are ignored without interrupting the stream.
|
|
123
123
|
* Decoder failures become provider output errors so the public error channel
|
|
124
124
|
* stays `AIError`.
|
|
125
125
|
*/
|
|
126
|
-
export declare const sseFraming: (bytes: Stream.Stream<Uint8Array, AIError>, events?: ReadonlySet<string
|
|
126
|
+
export declare const sseFraming: (bytes: Stream.Stream<Uint8Array, AIError>, events?: ReadonlySet<string>, includeDone?: boolean) => Stream.Stream<string, AIError>;
|
|
127
127
|
/**
|
|
128
128
|
* Canonical invalid-request constructor shared by protocol lowering.
|
|
129
129
|
*/
|
|
130
|
-
export declare const invalidRequest: (message: string) => AIError;
|
|
130
|
+
export declare const invalidRequest: (message: string, cause?: unknown) => AIError;
|
|
131
|
+
export declare const imageResponse: (route: string, name: string, response: HttpClientResponse.HttpClientResponse) => Effect.Effect<{
|
|
132
|
+
body: string;
|
|
133
|
+
invalid: (message: string, cause?: unknown) => AIError;
|
|
134
|
+
}, AIError, never>;
|
|
131
135
|
export declare const matchToolChoice: <Auto, None, Required, Tool>(route: string, toolChoice: NonNullable<LLMRequest["toolChoice"]>, cases: {
|
|
132
136
|
readonly auto: () => Auto;
|
|
133
137
|
readonly none: () => None;
|
package/dist/protocols/shared.js
CHANGED
|
@@ -2,8 +2,8 @@ import { Buffer } from "node:buffer";
|
|
|
2
2
|
import { Tool } from "@opencode-ai/schema/tool";
|
|
3
3
|
import { Effect, Schema, Stream } from "effect";
|
|
4
4
|
import * as Sse from "effect/unstable/encoding/Sse";
|
|
5
|
-
import { Headers, HttpClientRequest } from "effect/unstable/http";
|
|
6
|
-
import {
|
|
5
|
+
import { Headers, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
|
6
|
+
import { InvalidProviderOutputError, InvalidRequestError, AIError, HttpContext, } from "../schema/index.js";
|
|
7
7
|
import { isRecord } from "../utils/record.js";
|
|
8
8
|
export { isRecord };
|
|
9
9
|
export const Json = Schema.fromJsonString(Schema.Unknown);
|
|
@@ -72,14 +72,12 @@ export const sumTokens = (...values) => {
|
|
|
72
72
|
return undefined;
|
|
73
73
|
return values.reduce((acc, value) => acc + (value ?? 0), 0);
|
|
74
74
|
};
|
|
75
|
-
export const eventError = (route, message,
|
|
76
|
-
|
|
77
|
-
method: "stream",
|
|
78
|
-
reason: new InvalidProviderOutputReason({ route, message, raw }),
|
|
75
|
+
export const eventError = (route, message, body, cause) => new AIError({
|
|
76
|
+
reason: new InvalidProviderOutputError({ route, message, body, cause }),
|
|
79
77
|
});
|
|
80
78
|
export const parseJson = (route, input, message) => Effect.try({
|
|
81
79
|
try: () => decodeJson(input),
|
|
82
|
-
catch: () => eventError(route, message, input),
|
|
80
|
+
catch: (cause) => eventError(route, message, input, cause),
|
|
83
81
|
});
|
|
84
82
|
/**
|
|
85
83
|
* Join the `text` field of a list of parts with newlines. Used by routes
|
|
@@ -160,13 +158,13 @@ export const errorText = (error) => {
|
|
|
160
158
|
};
|
|
161
159
|
/**
|
|
162
160
|
* `framing` step for Server-Sent Events. Decodes UTF-8, runs the SSE channel
|
|
163
|
-
* decoder, optionally filters named events, and drops empty
|
|
164
|
-
*
|
|
165
|
-
*
|
|
161
|
+
* decoder, optionally filters named events, and drops empty events. `[DONE]`
|
|
162
|
+
* is dropped by default or retained for protocols that use it as their stream
|
|
163
|
+
* boundary. Retry control events are ignored without interrupting the stream.
|
|
166
164
|
* Decoder failures become provider output errors so the public error channel
|
|
167
165
|
* stays `AIError`.
|
|
168
166
|
*/
|
|
169
|
-
export const sseFraming = (bytes, events) => bytes.pipe(Stream.decodeText(), Stream.mapAccumEffect(() => {
|
|
167
|
+
export const sseFraming = (bytes, events, includeDone = false) => bytes.pipe(Stream.decodeText(), Stream.mapAccumEffect(() => {
|
|
170
168
|
const output = [];
|
|
171
169
|
return {
|
|
172
170
|
output,
|
|
@@ -178,18 +176,33 @@ export const sseFraming = (bytes, events) => bytes.pipe(Stream.decodeText(), Str
|
|
|
178
176
|
}, (state, chunk) => Effect.gen(function* () {
|
|
179
177
|
const error = state.parser.feed(chunk);
|
|
180
178
|
if (error)
|
|
181
|
-
return yield* eventError("sse", error.message);
|
|
179
|
+
return yield* eventError("sse", error.message, chunk, error);
|
|
182
180
|
return [state, state.output.splice(0)];
|
|
183
181
|
})), Stream.filter((event) => (events === undefined || events.has(event.event)) &&
|
|
184
182
|
event.data.length > 0 &&
|
|
185
|
-
(event.data !== "[DONE]" || (events !== undefined && event.event !== "message"))), Stream.map((event) => event.data));
|
|
183
|
+
(event.data !== "[DONE]" || includeDone || (events !== undefined && event.event !== "message"))), Stream.map((event) => event.data));
|
|
186
184
|
/**
|
|
187
185
|
* Canonical invalid-request constructor shared by protocol lowering.
|
|
188
186
|
*/
|
|
189
|
-
export const invalidRequest = (message) => new AIError({
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
187
|
+
export const invalidRequest = (message, cause) => new AIError({
|
|
188
|
+
reason: new InvalidRequestError({ message, cause }),
|
|
189
|
+
});
|
|
190
|
+
export const imageResponse = Effect.fn("ProviderShared.imageResponse")(function* (route, name, response) {
|
|
191
|
+
const http = new HttpContext({ url: response.request.url, status: response.status, headers: response.headers });
|
|
192
|
+
const body = yield* response.text.pipe(Effect.mapError((cause) => new AIError({
|
|
193
|
+
reason: new InvalidProviderOutputError({
|
|
194
|
+
route,
|
|
195
|
+
message: `Failed to read the ${name} response`,
|
|
196
|
+
http,
|
|
197
|
+
cause,
|
|
198
|
+
}),
|
|
199
|
+
})));
|
|
200
|
+
return {
|
|
201
|
+
body,
|
|
202
|
+
invalid: (message, cause) => new AIError({
|
|
203
|
+
reason: new InvalidProviderOutputError({ route, message, body, http, cause }),
|
|
204
|
+
}),
|
|
205
|
+
};
|
|
193
206
|
});
|
|
194
207
|
export const matchToolChoice = (route, toolChoice, cases) => Effect.gen(function* () {
|
|
195
208
|
if (toolChoice.type === "auto")
|
|
@@ -217,7 +230,7 @@ export const unsupportedContent = (route, role, types) => invalidRequest(`${rout
|
|
|
217
230
|
* invalid(e.message)))`. Any decode error is translated into
|
|
218
231
|
* `AIError` carrying the original parse-error message.
|
|
219
232
|
*/
|
|
220
|
-
export const validateWith = (decode) => (payload) => decode(payload).pipe(Effect.mapError((error) => invalidRequest(error.message)));
|
|
233
|
+
export const validateWith = (decode) => (payload) => decode(payload).pipe(Effect.mapError((error) => invalidRequest(error.message, error)));
|
|
221
234
|
/**
|
|
222
235
|
* Build an HTTP POST with a JSON body. Sets `content-type: application/json`
|
|
223
236
|
* automatically after caller-supplied headers so routes cannot accidentally
|
|
@@ -4,18 +4,18 @@ import { AIError } from "../../schema/index.js";
|
|
|
4
4
|
export declare const dataUrl: (input: Extract<ImageInput, {
|
|
5
5
|
readonly type: "bytes";
|
|
6
6
|
}>) => string;
|
|
7
|
-
export declare const decodeDataUrl: (url: string
|
|
7
|
+
export declare const decodeDataUrl: (url: string) => Effect.Effect<{
|
|
8
8
|
readonly mediaType: string;
|
|
9
9
|
readonly data: Uint8Array;
|
|
10
10
|
} | undefined, AIError>;
|
|
11
|
-
export declare const invalidImageInput: (
|
|
11
|
+
export declare const invalidImageInput: (message: string, cause?: unknown) => AIError;
|
|
12
12
|
export declare const ImageInputs: {
|
|
13
13
|
readonly dataUrl: (input: Extract<ImageInput, {
|
|
14
14
|
readonly type: "bytes";
|
|
15
15
|
}>) => string;
|
|
16
|
-
readonly decodeDataUrl: (url: string
|
|
16
|
+
readonly decodeDataUrl: (url: string) => Effect.Effect<{
|
|
17
17
|
readonly mediaType: string;
|
|
18
18
|
readonly data: Uint8Array;
|
|
19
19
|
} | undefined, AIError>;
|
|
20
|
-
readonly invalid: (
|
|
20
|
+
readonly invalid: (message: string, cause?: unknown) => AIError;
|
|
21
21
|
};
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import { Effect, Encoding } from "effect";
|
|
2
|
-
import {
|
|
3
|
-
const invalid = (
|
|
4
|
-
|
|
5
|
-
method: "generate",
|
|
6
|
-
reason: new InvalidRequestReason({ message }),
|
|
2
|
+
import { InvalidRequestError, AIError } from "../../schema/index.js";
|
|
3
|
+
const invalid = (message, cause) => new AIError({
|
|
4
|
+
reason: new InvalidRequestError({ message, cause }),
|
|
7
5
|
});
|
|
8
6
|
export const dataUrl = (input) => `data:${input.mediaType};base64,${Encoding.encodeBase64(input.data)}`;
|
|
9
|
-
export const decodeDataUrl = (url
|
|
7
|
+
export const decodeDataUrl = (url) => {
|
|
10
8
|
if (!url.startsWith("data:"))
|
|
11
9
|
return Effect.undefined;
|
|
12
10
|
const match = /^data:([^;,]+);base64,(.*)$/s.exec(url);
|
|
13
11
|
if (!match)
|
|
14
|
-
return Effect.fail(invalid(
|
|
15
|
-
return Effect.fromResult(Encoding.decodeBase64(match[2])).pipe(Effect.mapError(() => invalid(
|
|
12
|
+
return Effect.fail(invalid("Image data URLs must contain a MIME type and base64 data"));
|
|
13
|
+
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 })));
|
|
16
14
|
};
|
|
17
15
|
export const invalidImageInput = invalid;
|
|
18
16
|
export const ImageInputs = {
|
|
@@ -10,8 +10,12 @@ export declare const textStart: (state: State, events: LLMEvent[], id: string, p
|
|
|
10
10
|
export declare const textDelta: (state: State, events: LLMEvent[], id: string, text: string, providerMetadata?: ProviderMetadata) => State;
|
|
11
11
|
export declare const reasoningStart: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
|
|
12
12
|
export declare const reasoningDelta: (state: State, events: LLMEvent[], id: string, text: string, providerMetadata?: ProviderMetadata) => State;
|
|
13
|
-
export declare const reasoningEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata
|
|
14
|
-
|
|
13
|
+
export declare const reasoningEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata,
|
|
14
|
+
/** Authoritative complete value; replaces accumulated deltas when present. */
|
|
15
|
+
text?: string) => State;
|
|
16
|
+
export declare const textEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata,
|
|
17
|
+
/** Authoritative complete value; replaces accumulated deltas when present. */
|
|
18
|
+
text?: string) => State;
|
|
15
19
|
export declare const finish: (state: State, events: LLMEvent[], input: {
|
|
16
20
|
readonly reason: FinishReasonDetails;
|
|
17
21
|
readonly usage?: Usage;
|
|
@@ -30,23 +30,27 @@ export const reasoningDelta = (state, events, id, text, providerMetadata) => {
|
|
|
30
30
|
events.push(LLMEvent.reasoningDelta({ id, text, providerMetadata }));
|
|
31
31
|
return started;
|
|
32
32
|
};
|
|
33
|
-
export const reasoningEnd = (state, events, id, providerMetadata
|
|
33
|
+
export const reasoningEnd = (state, events, id, providerMetadata,
|
|
34
|
+
/** Authoritative complete value; replaces accumulated deltas when present. */
|
|
35
|
+
text) => {
|
|
34
36
|
if (!state.reasoning.has(id))
|
|
35
37
|
return state;
|
|
36
38
|
const stepped = stepStart(state, events);
|
|
37
|
-
events.push(LLMEvent.reasoningEnd({ id, providerMetadata }));
|
|
39
|
+
events.push(LLMEvent.reasoningEnd({ id, text, providerMetadata }));
|
|
38
40
|
const reasoning = new Set(stepped.reasoning);
|
|
39
41
|
reasoning.delete(id);
|
|
40
42
|
return { ...stepped, reasoning };
|
|
41
43
|
};
|
|
42
|
-
export const textEnd = (state, events, id, providerMetadata
|
|
44
|
+
export const textEnd = (state, events, id, providerMetadata,
|
|
45
|
+
/** Authoritative complete value; replaces accumulated deltas when present. */
|
|
46
|
+
text) => {
|
|
43
47
|
if (!state.text.has(id))
|
|
44
48
|
return state;
|
|
45
49
|
const stepped = stepStart(state, events);
|
|
46
|
-
events.push(LLMEvent.textEnd({ id, providerMetadata }));
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
return { ...stepped, text };
|
|
50
|
+
events.push(LLMEvent.textEnd({ id, text, providerMetadata }));
|
|
51
|
+
const open = new Set(stepped.text);
|
|
52
|
+
open.delete(id);
|
|
53
|
+
return { ...stepped, text: open };
|
|
50
54
|
};
|
|
51
55
|
const closeOpenBlocks = (state, events) => {
|
|
52
56
|
for (const id of state.reasoning)
|
|
@@ -93,6 +93,7 @@ export declare const finish: <K extends StreamKey>(route: string, tools: State<K
|
|
|
93
93
|
} | {
|
|
94
94
|
readonly id: string;
|
|
95
95
|
readonly type: "text-end";
|
|
96
|
+
readonly text?: string | undefined;
|
|
96
97
|
readonly providerMetadata?: {
|
|
97
98
|
readonly [x: string]: {
|
|
98
99
|
readonly [x: string]: unknown;
|
|
@@ -118,6 +119,7 @@ export declare const finish: <K extends StreamKey>(route: string, tools: State<K
|
|
|
118
119
|
} | {
|
|
119
120
|
readonly id: string;
|
|
120
121
|
readonly type: "reasoning-end";
|
|
122
|
+
readonly text?: string | undefined;
|
|
121
123
|
readonly providerMetadata?: {
|
|
122
124
|
readonly [x: string]: {
|
|
123
125
|
readonly [x: string]: unknown;
|
|
@@ -287,6 +289,7 @@ export declare const finishWithInput: <K extends StreamKey>(route: string, tools
|
|
|
287
289
|
} | {
|
|
288
290
|
readonly id: string;
|
|
289
291
|
readonly type: "text-end";
|
|
292
|
+
readonly text?: string | undefined;
|
|
290
293
|
readonly providerMetadata?: {
|
|
291
294
|
readonly [x: string]: {
|
|
292
295
|
readonly [x: string]: unknown;
|
|
@@ -312,6 +315,7 @@ export declare const finishWithInput: <K extends StreamKey>(route: string, tools
|
|
|
312
315
|
} | {
|
|
313
316
|
readonly id: string;
|
|
314
317
|
readonly type: "reasoning-end";
|
|
318
|
+
readonly text?: string | undefined;
|
|
315
319
|
readonly providerMetadata?: {
|
|
316
320
|
readonly [x: string]: {
|
|
317
321
|
readonly [x: string]: unknown;
|
|
@@ -478,6 +482,7 @@ export declare const finishAll: <K extends StreamKey>(route: string, tools: Stat
|
|
|
478
482
|
} | {
|
|
479
483
|
readonly id: string;
|
|
480
484
|
readonly type: "text-end";
|
|
485
|
+
readonly text?: string | undefined;
|
|
481
486
|
readonly providerMetadata?: {
|
|
482
487
|
readonly [x: string]: {
|
|
483
488
|
readonly [x: string]: unknown;
|
|
@@ -503,6 +508,7 @@ export declare const finishAll: <K extends StreamKey>(route: string, tools: Stat
|
|
|
503
508
|
} | {
|
|
504
509
|
readonly id: string;
|
|
505
510
|
readonly type: "reasoning-end";
|
|
511
|
+
readonly text?: string | undefined;
|
|
506
512
|
readonly providerMetadata?: {
|
|
507
513
|
readonly [x: string]: {
|
|
508
514
|
readonly [x: string]: unknown;
|
|
@@ -2,7 +2,7 @@ import { Effect, Encoding, Schema } from "effect";
|
|
|
2
2
|
import { Headers, HttpClientRequest } from "effect/unstable/http";
|
|
3
3
|
import { GeneratedImage, ImageModel, ImageResponse } from "../image.js";
|
|
4
4
|
import { Auth } from "../route/auth.js";
|
|
5
|
-
import {
|
|
5
|
+
import { Usage, mergeHttpOptions, mergeJsonRecords } from "../schema/index.js";
|
|
6
6
|
import { ProviderShared, optionalNull } from "./shared.js";
|
|
7
7
|
import { ImageInputs } from "./utils/image-input.js";
|
|
8
8
|
const ADAPTER = "xai-images";
|
|
@@ -28,11 +28,6 @@ const nativeOptions = (options) => {
|
|
|
28
28
|
...native,
|
|
29
29
|
};
|
|
30
30
|
};
|
|
31
|
-
const invalidOutput = (message) => new AIError({
|
|
32
|
-
module: ADAPTER,
|
|
33
|
-
method: "generate",
|
|
34
|
-
reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
|
|
35
|
-
});
|
|
36
31
|
const applyQuery = (url, query) => {
|
|
37
32
|
if (!query)
|
|
38
33
|
return url;
|
|
@@ -55,7 +50,7 @@ export const model = (input) => {
|
|
|
55
50
|
return undefined;
|
|
56
51
|
});
|
|
57
52
|
if (imageReferences.some((image) => image === undefined))
|
|
58
|
-
return yield* ImageInputs.invalid(
|
|
53
|
+
return yield* ImageInputs.invalid("xAI Images accepts image URLs, data URLs, bytes, and file IDs");
|
|
59
54
|
const requestBody = mergeJsonRecords({
|
|
60
55
|
model: request.model.id,
|
|
61
56
|
prompt: request.prompt,
|
|
@@ -72,12 +67,12 @@ export const model = (input) => {
|
|
|
72
67
|
headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
|
|
73
68
|
});
|
|
74
69
|
const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
|
|
75
|
-
const
|
|
76
|
-
const decoded = yield* Schema.decodeUnknownEffect(XAIImageResponse)(
|
|
70
|
+
const output = yield* ProviderShared.imageResponse(ADAPTER, "xAI Images", response);
|
|
71
|
+
const decoded = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(XAIImageResponse))(output.body).pipe(Effect.mapError((cause) => output.invalid("xAI Images returned an invalid response", cause)));
|
|
77
72
|
const images = yield* Effect.forEach(decoded.data, (item, index) => {
|
|
78
73
|
const mediaType = item.mime_type ?? "application/octet-stream";
|
|
79
74
|
if (item.b64_json)
|
|
80
|
-
return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(Effect.mapError(() =>
|
|
75
|
+
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({
|
|
81
76
|
mediaType,
|
|
82
77
|
data,
|
|
83
78
|
providerMetadata: item.revised_prompt === undefined || item.revised_prompt === null
|
|
@@ -92,10 +87,10 @@ export const model = (input) => {
|
|
|
92
87
|
? undefined
|
|
93
88
|
: { xai: { revisedPrompt: item.revised_prompt } },
|
|
94
89
|
}));
|
|
95
|
-
return Effect.fail(
|
|
90
|
+
return Effect.fail(output.invalid(`xAI Images result ${index} has neither image data nor a URL`));
|
|
96
91
|
});
|
|
97
92
|
if (images.length === 0)
|
|
98
|
-
return yield*
|
|
93
|
+
return yield* output.invalid("xAI Images returned no images");
|
|
99
94
|
const usage = ProviderShared.isRecord(decoded.usage) ? decoded.usage : undefined;
|
|
100
95
|
return new ImageResponse({
|
|
101
96
|
images,
|
|
@@ -2,7 +2,7 @@ import { Effect, Schema } from "effect";
|
|
|
2
2
|
import { Headers, HttpClientRequest } from "effect/unstable/http";
|
|
3
3
|
import { GeneratedImage, ImageModel, ImageResponse } from "../image.js";
|
|
4
4
|
import { Auth } from "../route/auth.js";
|
|
5
|
-
import {
|
|
5
|
+
import { mergeHttpOptions, mergeJsonRecords } from "../schema/index.js";
|
|
6
6
|
import { ProviderShared } from "./shared.js";
|
|
7
7
|
import { ImageInputs } from "./utils/image-input.js";
|
|
8
8
|
const ADAPTER = "zai-images";
|
|
@@ -27,11 +27,6 @@ const nativeOptions = (options) => {
|
|
|
27
27
|
...native,
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
|
-
const invalidOutput = (message) => new AIError({
|
|
31
|
-
module: ADAPTER,
|
|
32
|
-
method: "generate",
|
|
33
|
-
reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
|
|
34
|
-
});
|
|
35
30
|
const applyQuery = (url, query) => {
|
|
36
31
|
if (!query)
|
|
37
32
|
return url;
|
|
@@ -44,7 +39,7 @@ export const model = (input) => {
|
|
|
44
39
|
id: ADAPTER,
|
|
45
40
|
generate: Effect.fn("ZAIImages.generate")(function* (request, execute) {
|
|
46
41
|
if ((request.images?.length ?? 0) > 0)
|
|
47
|
-
return yield* ImageInputs.invalid(
|
|
42
|
+
return yield* ImageInputs.invalid("Z.ai hosted image generation does not support image inputs");
|
|
48
43
|
const http = mergeHttpOptions(request.model.http, request.http);
|
|
49
44
|
const requestBody = mergeJsonRecords({ model: request.model.id, prompt: request.prompt }, nativeOptions(request.options), http?.body);
|
|
50
45
|
const text = ProviderShared.encodeJson(requestBody);
|
|
@@ -57,10 +52,10 @@ export const model = (input) => {
|
|
|
57
52
|
headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
|
|
58
53
|
});
|
|
59
54
|
const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
|
|
60
|
-
const
|
|
61
|
-
const decoded = yield* Schema.decodeUnknownEffect(ZAIImageResponse)(
|
|
55
|
+
const output = yield* ProviderShared.imageResponse(ADAPTER, "Z.ai Images", response);
|
|
56
|
+
const decoded = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(ZAIImageResponse))(output.body).pipe(Effect.mapError((cause) => output.invalid("Z.ai Images returned an invalid response", cause)));
|
|
62
57
|
if (decoded.data.length === 0)
|
|
63
|
-
return yield*
|
|
58
|
+
return yield* output.invalid("Z.ai Images returned no images");
|
|
64
59
|
return new ImageResponse({
|
|
65
60
|
images: decoded.data.map((item) => new GeneratedImage({
|
|
66
61
|
mediaType: "application/octet-stream",
|
package/dist/provider-error.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { AIError, type HttpContext, type HttpRateLimitDetails
|
|
1
|
+
import { AIError, type HttpContext, type HttpRateLimitDetails } from "./schema/index.js";
|
|
2
2
|
export declare const isContextOverflow: (message: string) => boolean;
|
|
3
3
|
export declare const isPayloadTooLarge: (message: string) => boolean;
|
|
4
4
|
export declare const isContextOverflowFailure: (failure: unknown) => boolean;
|
|
5
5
|
export interface ProviderFailure {
|
|
6
6
|
readonly message: string;
|
|
7
7
|
readonly status?: number | undefined;
|
|
8
|
-
readonly code?: string | undefined;
|
|
9
8
|
readonly rawBody?: string | undefined;
|
|
9
|
+
readonly data?: unknown;
|
|
10
|
+
readonly http?: HttpContext | undefined;
|
|
11
|
+
readonly cause?: unknown;
|
|
10
12
|
readonly retryAfterMs?: number | undefined;
|
|
11
13
|
readonly rateLimit?: HttpRateLimitDetails | undefined;
|
|
12
|
-
readonly http?: HttpContext | undefined;
|
|
13
|
-
readonly providerMetadata?: ProviderMetadata | undefined;
|
|
14
14
|
}
|
|
15
15
|
export declare function classifyProviderFailure(input: ProviderFailure): AIError["reason"];
|