@opencode-ai/ai 0.0.0-next-17274 → 0.0.0-next-17288
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/dist/protocols/anthropic-messages.js +5 -1
- package/dist/route/executor.d.ts +2 -1
- package/dist/route/executor.js +52 -26
- package/dist/route/transport/http.js +3 -4
- package/dist/route/transport/websocket.js +35 -12
- package/dist/schema/errors.d.ts +7 -1
- package/dist/schema/errors.js +5 -1
- package/package.json +3 -3
|
@@ -448,7 +448,11 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (re
|
|
|
448
448
|
cache_control: cacheControl(breakpoints, part.cache),
|
|
449
449
|
});
|
|
450
450
|
}
|
|
451
|
-
messages.
|
|
451
|
+
const previous = messages.at(-1);
|
|
452
|
+
if (previous?.role === "user" && previous.content.every((block) => block.type === "tool_result"))
|
|
453
|
+
messages[messages.length - 1] = { role: "user", content: [...previous.content, ...content] };
|
|
454
|
+
else
|
|
455
|
+
messages.push({ role: "user", content });
|
|
452
456
|
}
|
|
453
457
|
return messages;
|
|
454
458
|
});
|
package/dist/route/executor.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Effect, Layer } from "effect";
|
|
1
|
+
import { Context, Effect, Layer, Stream } from "effect";
|
|
2
2
|
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
|
3
3
|
import { AIError, TransportReason } from "../schema/index.js";
|
|
4
4
|
export interface Interface {
|
|
@@ -17,6 +17,7 @@ export declare const classifyHttpFailure: (input: {
|
|
|
17
17
|
readonly responseHeaders?: Record<string, string> | undefined;
|
|
18
18
|
readonly responseBody?: string | undefined;
|
|
19
19
|
}) => 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;
|
|
20
|
+
export declare const stream: (executor: Interface, request: HttpClientRequest.HttpClientRequest, middleware?: HttpMiddleware) => Stream.Stream<Uint8Array, AIError>;
|
|
20
21
|
export declare const layer: Layer.Layer<Service, never, HttpClient.HttpClient>;
|
|
21
22
|
export declare const fetchLayer: Layer.Layer<Service, never, never>;
|
|
22
23
|
export * as RequestExecutor from "./executor.js";
|
package/dist/route/executor.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Cause, Context, Effect, Layer, Option, Schema } from "effect";
|
|
1
|
+
import { Cause, Context, Effect, Layer, Option, Schema, Stream } from "effect";
|
|
2
2
|
import { FetchHttpClient, Headers, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, } from "effect/unstable/http";
|
|
3
3
|
import { HttpContext, HttpRateLimitDetails, HttpRequestDetails, HttpResponseDetails, AIError, TransportReason, } from "../schema/index.js";
|
|
4
4
|
import { classifyProviderFailure } from "../provider-error.js";
|
|
@@ -215,48 +215,74 @@ export const classifyHttpFailure = (input) => {
|
|
|
215
215
|
}),
|
|
216
216
|
});
|
|
217
217
|
};
|
|
218
|
-
const
|
|
219
|
-
|
|
218
|
+
const NativeTransportFailure = Schema.Struct({
|
|
219
|
+
message: Schema.String,
|
|
220
|
+
code: Schema.optionalKey(Schema.String),
|
|
221
|
+
cause: Schema.optionalKey(Schema.Unknown),
|
|
222
|
+
});
|
|
223
|
+
const decodeNativeTransportFailure = Schema.decodeUnknownOption(NativeTransportFailure);
|
|
224
|
+
const nativeTransportFailure = (error) => {
|
|
225
|
+
const failure = Option.getOrUndefined(decodeNativeTransportFailure(error));
|
|
226
|
+
if (!failure)
|
|
227
|
+
return undefined;
|
|
228
|
+
if (failure.code !== undefined)
|
|
229
|
+
return failure;
|
|
230
|
+
const cause = Option.getOrUndefined(decodeNativeTransportFailure(failure.cause));
|
|
231
|
+
if (cause?.code !== undefined)
|
|
232
|
+
return cause;
|
|
233
|
+
return failure;
|
|
234
|
+
};
|
|
235
|
+
const httpError = (input) => {
|
|
236
|
+
const request = HttpClientError.isHttpClientError(input.error) ? input.error.request : input.request;
|
|
237
|
+
const transportError = (failure) => new AIError({
|
|
220
238
|
module: "RequestExecutor",
|
|
221
|
-
method:
|
|
239
|
+
method: input.operation,
|
|
222
240
|
reason: new TransportReason({
|
|
223
|
-
message:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
241
|
+
message: failure.message,
|
|
242
|
+
transport: "http",
|
|
243
|
+
operation: input.operation,
|
|
244
|
+
code: failure.code,
|
|
245
|
+
url: redactUrl(request.url),
|
|
246
|
+
http: new HttpContext({ request: requestDetails(request, input.redactedNames) }),
|
|
227
247
|
}),
|
|
228
248
|
});
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
249
|
+
const source = HttpClientError.isHttpClientError(input.error) && "cause" in input.error.reason
|
|
250
|
+
? input.error.reason.cause
|
|
251
|
+
: input.error;
|
|
252
|
+
const native = nativeTransportFailure(source);
|
|
253
|
+
const code = native?.code;
|
|
254
|
+
const raw = native?.message ?? (input.error instanceof Error ? input.error.message : undefined);
|
|
255
|
+
const detail = raw ? redactBody(raw, secretValues(request)) : undefined;
|
|
256
|
+
const message = code && detail && !detail.includes(code) ? `${code}: ${detail}` : detail;
|
|
257
|
+
if (Cause.isTimeoutError(input.error) || Cause.isTimeoutError(source))
|
|
258
|
+
return transportError({ message: message ?? "HTTP transport timed out", code: code ?? "Timeout" });
|
|
259
|
+
if (!HttpClientError.isHttpClientError(input.error))
|
|
260
|
+
return transportError({ message: message ?? "HTTP transport failed", code });
|
|
261
|
+
if (input.error.reason._tag === "TransportError") {
|
|
237
262
|
return transportError({
|
|
238
|
-
message: error.reason.description ?? "HTTP transport failed",
|
|
239
|
-
|
|
240
|
-
request,
|
|
263
|
+
message: message ?? input.error.reason.description ?? "HTTP transport failed",
|
|
264
|
+
code: code ?? input.error.reason._tag,
|
|
241
265
|
});
|
|
242
266
|
}
|
|
243
267
|
return transportError({
|
|
244
|
-
message: `HTTP transport failed: ${error.reason._tag}`,
|
|
245
|
-
|
|
246
|
-
request,
|
|
268
|
+
message: message ?? `HTTP transport failed: ${input.error.reason._tag}`,
|
|
269
|
+
code: code ?? input.error.reason._tag,
|
|
247
270
|
});
|
|
248
271
|
};
|
|
272
|
+
export const stream = (executor, request, middleware) => Stream.unwrap(Effect.gen(function* () {
|
|
273
|
+
const redactedNames = yield* Headers.CurrentRedactedNames;
|
|
274
|
+
const response = yield* executor.execute(request, middleware);
|
|
275
|
+
return response.stream.pipe(Stream.mapError((error) => httpError({ error, request: response.request, operation: "read", redactedNames })));
|
|
276
|
+
}));
|
|
249
277
|
export const layer = Layer.effect(Service, Effect.gen(function* () {
|
|
250
278
|
const http = yield* HttpClient.HttpClient;
|
|
251
279
|
const executeOnce = (request, middleware) => Effect.gen(function* () {
|
|
252
280
|
const redactedNames = yield* Headers.CurrentRedactedNames;
|
|
253
281
|
if (!middleware)
|
|
254
|
-
return yield* http
|
|
255
|
-
.execute(request)
|
|
256
|
-
.pipe(Effect.mapError(toHttpError(redactedNames)), Effect.flatMap(statusError(request, redactedNames)));
|
|
282
|
+
return yield* http.execute(request).pipe(Effect.mapError((error) => httpError({ error, request, operation: "request", redactedNames })), Effect.flatMap(statusError(request, redactedNames)));
|
|
257
283
|
const response = yield* middleware(request, (input) => http
|
|
258
284
|
.execute(input)
|
|
259
|
-
.pipe(Effect.mapError((cause) => (cause instanceof Error ? cause : new Error(String(cause)))))).pipe(Effect.mapError(
|
|
285
|
+
.pipe(Effect.mapError((cause) => (cause instanceof Error ? cause : new Error(String(cause)))))).pipe(Effect.mapError((error) => httpError({ error, request, operation: "request", redactedNames })));
|
|
260
286
|
return yield* statusError(response.request, redactedNames)(response);
|
|
261
287
|
});
|
|
262
288
|
return Service.of({
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Effect
|
|
1
|
+
import { Effect } from "effect";
|
|
2
2
|
import { Headers, HttpClientRequest } from "effect/unstable/http";
|
|
3
3
|
import { Auth } from "../auth.js";
|
|
4
4
|
import { render as renderEndpoint } from "../endpoint.js";
|
|
5
5
|
import { Framing } from "../framing.js";
|
|
6
6
|
import * as ProviderShared from "../../protocols/shared.js";
|
|
7
7
|
import { mergeJsonRecords } from "../../schema/index.js";
|
|
8
|
+
import { RequestExecutor } from "../executor.js";
|
|
8
9
|
const applyQuery = (url, query) => {
|
|
9
10
|
if (!query)
|
|
10
11
|
return url;
|
|
@@ -52,9 +53,7 @@ export const httpJson = (input) => ({
|
|
|
52
53
|
middleware: prepareInput.middleware,
|
|
53
54
|
};
|
|
54
55
|
}),
|
|
55
|
-
frames: (prepared,
|
|
56
|
-
.execute(prepared.request, prepared.middleware)
|
|
57
|
-
.pipe(Effect.map((response) => prepared.framing.frame(response.stream.pipe(Stream.mapError((error) => ProviderShared.eventError(`${request.model.provider}/${request.model.route.id}`, `Failed to read ${request.model.provider}/${request.model.route.id} stream`, ProviderShared.errorText(error)))))))),
|
|
56
|
+
frames: (prepared, _request, runtime) => prepared.framing.frame(RequestExecutor.stream(runtime.http, prepared.request, prepared.middleware)),
|
|
58
57
|
});
|
|
59
58
|
export const sseJson = {
|
|
60
59
|
id: "http-json/sse",
|
|
@@ -4,10 +4,16 @@ import { AIError, TransportReason } from "../../schema/index.js";
|
|
|
4
4
|
import * as HttpTransport from "./http.js";
|
|
5
5
|
export class Service extends Context.Service()("@opencode/AI/WebSocketExecutor") {
|
|
6
6
|
}
|
|
7
|
-
const transportError = (method, message, input
|
|
7
|
+
const transportError = (method, message, input) => new AIError({
|
|
8
8
|
module: "WebSocketExecutor",
|
|
9
9
|
method,
|
|
10
|
-
reason: new TransportReason({
|
|
10
|
+
reason: new TransportReason({
|
|
11
|
+
message,
|
|
12
|
+
transport: "websocket",
|
|
13
|
+
operation: input.operation,
|
|
14
|
+
url: input.url,
|
|
15
|
+
code: input.code,
|
|
16
|
+
}),
|
|
11
17
|
});
|
|
12
18
|
const eventMessage = (event) => {
|
|
13
19
|
if ("message" in event && typeof event.message === "string")
|
|
@@ -29,7 +35,8 @@ const waitOpen = (ws, input) => {
|
|
|
29
35
|
if (ws.readyState === globalThis.WebSocket.CLOSING || ws.readyState === globalThis.WebSocket.CLOSED) {
|
|
30
36
|
return Effect.fail(transportError("open", `WebSocket closed before opening (state ${ws.readyState})`, {
|
|
31
37
|
url: input.url,
|
|
32
|
-
|
|
38
|
+
operation: "request",
|
|
39
|
+
code: "closed",
|
|
33
40
|
}));
|
|
34
41
|
}
|
|
35
42
|
return Effect.callback((resume, signal) => {
|
|
@@ -50,13 +57,17 @@ const waitOpen = (ws, input) => {
|
|
|
50
57
|
};
|
|
51
58
|
const onError = (event) => {
|
|
52
59
|
cleanup();
|
|
53
|
-
resume(Effect.fail(transportError("open", `Failed to open WebSocket: ${eventMessage(event)}`, {
|
|
60
|
+
resume(Effect.fail(transportError("open", `Failed to open WebSocket: ${eventMessage(event)}`, {
|
|
61
|
+
url: input.url,
|
|
62
|
+
operation: "request",
|
|
63
|
+
})));
|
|
54
64
|
};
|
|
55
65
|
const onClose = (event) => {
|
|
56
66
|
cleanup();
|
|
57
67
|
resume(Effect.fail(transportError("open", `WebSocket closed before opening with code ${event.code}`, {
|
|
58
68
|
url: input.url,
|
|
59
|
-
|
|
69
|
+
operation: "request",
|
|
70
|
+
code: String(event.code),
|
|
60
71
|
})));
|
|
61
72
|
};
|
|
62
73
|
ws.addEventListener("open", onOpen, { once: true });
|
|
@@ -80,14 +91,15 @@ const webSocketUrl = (value) => Effect.try({
|
|
|
80
91
|
},
|
|
81
92
|
catch: (error) => transportError("prepare", error instanceof Error ? error.message : "Invalid WebSocket URL", {
|
|
82
93
|
url: value,
|
|
83
|
-
|
|
94
|
+
operation: "request",
|
|
95
|
+
code: "invalid-url",
|
|
84
96
|
}),
|
|
85
97
|
});
|
|
86
98
|
export const open = (input) => Effect.try({
|
|
87
99
|
try: () => new globalThis.WebSocket(input.url, { headers: input.headers }),
|
|
88
100
|
catch: (error) => transportError("open", error instanceof Error ? error.message : "Failed to construct WebSocket", {
|
|
89
101
|
url: input.url,
|
|
90
|
-
|
|
102
|
+
operation: "request",
|
|
91
103
|
}),
|
|
92
104
|
}).pipe(Effect.flatMap((ws) => fromWebSocket(ws, input)));
|
|
93
105
|
export const layer = Layer.succeed(Service, Service.of({ open }));
|
|
@@ -100,15 +112,25 @@ export const fromWebSocket = (ws, input) => Effect.gen(function* () {
|
|
|
100
112
|
const binary = binaryMessage(event.data);
|
|
101
113
|
if (binary)
|
|
102
114
|
return Queue.offerUnsafe(messages, binary);
|
|
103
|
-
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", "Unsupported WebSocket message payload", {
|
|
115
|
+
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", "Unsupported WebSocket message payload", {
|
|
116
|
+
url: input.url,
|
|
117
|
+
operation: "read",
|
|
118
|
+
})));
|
|
104
119
|
};
|
|
105
120
|
const onError = (event) => {
|
|
106
|
-
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", `WebSocket error: ${eventMessage(event)}`, {
|
|
121
|
+
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", `WebSocket error: ${eventMessage(event)}`, {
|
|
122
|
+
url: input.url,
|
|
123
|
+
operation: "read",
|
|
124
|
+
})));
|
|
107
125
|
};
|
|
108
126
|
const onClose = (event) => {
|
|
109
127
|
if (event.code === 1000 || event.code === 1005)
|
|
110
128
|
return Queue.endUnsafe(messages);
|
|
111
|
-
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", `WebSocket closed with code ${event.code}`, {
|
|
129
|
+
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", `WebSocket closed with code ${event.code}`, {
|
|
130
|
+
url: input.url,
|
|
131
|
+
operation: "read",
|
|
132
|
+
code: String(event.code),
|
|
133
|
+
})));
|
|
112
134
|
};
|
|
113
135
|
const cleanup = Effect.sync(() => {
|
|
114
136
|
ws.removeEventListener("message", onMessage);
|
|
@@ -123,7 +145,7 @@ export const fromWebSocket = (ws, input) => Effect.gen(function* () {
|
|
|
123
145
|
try: () => ws.send(message),
|
|
124
146
|
catch: (error) => transportError("sendText", error instanceof Error ? error.message : "Failed to send WebSocket message", {
|
|
125
147
|
url: input.url,
|
|
126
|
-
|
|
148
|
+
operation: "write",
|
|
127
149
|
}),
|
|
128
150
|
}),
|
|
129
151
|
messages: Stream.fromQueue(messages),
|
|
@@ -153,7 +175,8 @@ export const json = (input) => ({
|
|
|
153
175
|
if (!webSocket) {
|
|
154
176
|
return Stream.fail(transportError("json", "WebSocket JSON transport requires WebSocketExecutor.Service", {
|
|
155
177
|
url: prepared.url,
|
|
156
|
-
|
|
178
|
+
operation: "request",
|
|
179
|
+
code: "unavailable",
|
|
157
180
|
}));
|
|
158
181
|
}
|
|
159
182
|
const decoder = new TextDecoder();
|
package/dist/schema/errors.d.ts
CHANGED
|
@@ -97,10 +97,16 @@ declare const ProviderInternalReason_base: Schema.Class<ProviderInternalReason,
|
|
|
97
97
|
}>, {}>;
|
|
98
98
|
export declare class ProviderInternalReason extends ProviderInternalReason_base {
|
|
99
99
|
}
|
|
100
|
+
export declare const TransportType: Schema.Literals<readonly ["http", "websocket"]>;
|
|
101
|
+
export type TransportType = typeof TransportType.Type;
|
|
102
|
+
export declare const TransportOperation: Schema.Literals<readonly ["request", "read", "write"]>;
|
|
103
|
+
export type TransportOperation = typeof TransportOperation.Type;
|
|
100
104
|
declare const TransportReason_base: Schema.Class<TransportReason, Schema.Struct<{
|
|
101
105
|
readonly _tag: Schema.tag<"Transport">;
|
|
102
106
|
readonly message: Schema.String;
|
|
103
|
-
readonly
|
|
107
|
+
readonly transport: Schema.Literals<readonly ["http", "websocket"]>;
|
|
108
|
+
readonly operation: Schema.Literals<readonly ["request", "read", "write"]>;
|
|
109
|
+
readonly code: Schema.optional<Schema.String>;
|
|
104
110
|
readonly url: Schema.optional<Schema.String>;
|
|
105
111
|
readonly http: Schema.optional<typeof HttpContext>;
|
|
106
112
|
}>, {}>;
|
package/dist/schema/errors.js
CHANGED
|
@@ -88,10 +88,14 @@ export class ProviderInternalReason extends Schema.Class("AI.Error.ProviderInter
|
|
|
88
88
|
http: Schema.optional(HttpContext),
|
|
89
89
|
}) {
|
|
90
90
|
}
|
|
91
|
+
export const TransportType = Schema.Literals(["http", "websocket"]);
|
|
92
|
+
export const TransportOperation = Schema.Literals(["request", "read", "write"]);
|
|
91
93
|
export class TransportReason extends Schema.Class("AI.Error.Transport")({
|
|
92
94
|
_tag: Schema.tag("Transport"),
|
|
93
95
|
message: Schema.String,
|
|
94
|
-
|
|
96
|
+
transport: TransportType,
|
|
97
|
+
operation: TransportOperation,
|
|
98
|
+
code: Schema.optional(Schema.String),
|
|
95
99
|
url: Schema.optional(Schema.String),
|
|
96
100
|
http: Schema.optional(HttpContext),
|
|
97
101
|
}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-next-
|
|
3
|
+
"version": "0.0.0-next-17288",
|
|
4
4
|
"name": "@opencode-ai/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
32
32
|
"@effect/platform-node": "4.0.0-beta.101",
|
|
33
|
-
"@opencode-ai/http-recorder": "0.0.0-next-
|
|
33
|
+
"@opencode-ai/http-recorder": "0.0.0-next-17288",
|
|
34
34
|
"@tsconfig/bun": "1.0.9",
|
|
35
35
|
"@types/bun": "1.3.13",
|
|
36
36
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@smithy/eventstream-codec": "4.2.14",
|
|
41
41
|
"@smithy/util-utf8": "4.2.2",
|
|
42
|
-
"@opencode-ai/schema": "0.0.0-next-
|
|
42
|
+
"@opencode-ai/schema": "0.0.0-next-17288",
|
|
43
43
|
"aws4fetch": "1.0.20",
|
|
44
44
|
"effect": "4.0.0-beta.101",
|
|
45
45
|
"google-auth-library": "10.5.0"
|