@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
package/dist/schema/events.js
CHANGED
|
@@ -91,6 +91,8 @@ export const TextDelta = Schema.Struct({
|
|
|
91
91
|
export const TextEnd = Schema.Struct({
|
|
92
92
|
type: Schema.tag("text-end"),
|
|
93
93
|
id: ContentBlockID,
|
|
94
|
+
/** Authoritative complete value; replaces accumulated deltas when present. */
|
|
95
|
+
text: Schema.optional(Schema.String),
|
|
94
96
|
providerMetadata: Schema.optional(ProviderMetadata),
|
|
95
97
|
}).annotate({ identifier: "LLM.Event.TextEnd" });
|
|
96
98
|
export const ReasoningStart = Schema.Struct({
|
|
@@ -107,6 +109,8 @@ export const ReasoningDelta = Schema.Struct({
|
|
|
107
109
|
export const ReasoningEnd = Schema.Struct({
|
|
108
110
|
type: Schema.tag("reasoning-end"),
|
|
109
111
|
id: ContentBlockID,
|
|
112
|
+
/** Authoritative complete value; replaces accumulated deltas when present. */
|
|
113
|
+
text: Schema.optional(Schema.String),
|
|
110
114
|
providerMetadata: Schema.optional(ProviderMetadata),
|
|
111
115
|
}).annotate({ identifier: "LLM.Event.ReasoningEnd" });
|
|
112
116
|
export const ToolInputStart = Schema.Struct({
|
|
@@ -259,14 +263,26 @@ export const LLMEvent = Object.assign(llmEventTagged, {
|
|
|
259
263
|
providerError: llmEventTagged.guards["provider-error"],
|
|
260
264
|
},
|
|
261
265
|
});
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
/** Joins deltas per fragment, letting an authoritative end value replace that fragment's accumulated deltas. */
|
|
267
|
+
const joinFragments = (events, isDelta, isEnd) => {
|
|
268
|
+
const order = [];
|
|
269
|
+
const parts = new Map();
|
|
270
|
+
for (const event of events) {
|
|
271
|
+
if (isDelta(event)) {
|
|
272
|
+
if (!parts.has(event.id))
|
|
273
|
+
order.push(event.id);
|
|
274
|
+
parts.set(event.id, (parts.get(event.id) ?? "") + event.text);
|
|
275
|
+
}
|
|
276
|
+
if (isEnd(event) && event.text !== undefined) {
|
|
277
|
+
if (!parts.has(event.id))
|
|
278
|
+
order.push(event.id);
|
|
279
|
+
parts.set(event.id, event.text);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return order.map((id) => parts.get(id)).join("");
|
|
283
|
+
};
|
|
284
|
+
const responseText = (events) => joinFragments(events, LLMEvent.is.textDelta, LLMEvent.is.textEnd);
|
|
285
|
+
const responseReasoning = (events) => joinFragments(events, LLMEvent.is.reasoningDelta, LLMEvent.is.reasoningEnd);
|
|
270
286
|
const responseUsage = (events) => events.reduce((usage, event) => ("usage" in event && event.usage !== undefined ? event.usage : usage), undefined);
|
|
271
287
|
const emptyResponseState = () => ({
|
|
272
288
|
events: [],
|
|
@@ -333,10 +349,11 @@ const reduceTextEnd = (state, event) => {
|
|
|
333
349
|
const current = state.textParts[event.id];
|
|
334
350
|
if (!current)
|
|
335
351
|
return state;
|
|
352
|
+
const text = event.text ?? current.text;
|
|
336
353
|
const providerMetadata = event.providerMetadata ?? current.providerMetadata;
|
|
337
354
|
return {
|
|
338
|
-
...replaceContent(state, current.contentIndex, textContent(
|
|
339
|
-
textParts: { ...state.textParts, [event.id]: { ...current, providerMetadata } },
|
|
355
|
+
...replaceContent(state, current.contentIndex, textContent(text, providerMetadata)),
|
|
356
|
+
textParts: { ...state.textParts, [event.id]: { ...current, text, providerMetadata } },
|
|
340
357
|
};
|
|
341
358
|
};
|
|
342
359
|
const ensureReasoning = (state, id, providerMetadata) => {
|
|
@@ -366,10 +383,11 @@ const reduceReasoningEnd = (state, event) => {
|
|
|
366
383
|
const current = state.reasoningParts[event.id];
|
|
367
384
|
if (!current)
|
|
368
385
|
return state;
|
|
386
|
+
const text = event.text ?? current.text;
|
|
369
387
|
const providerMetadata = event.providerMetadata ?? current.providerMetadata;
|
|
370
388
|
return {
|
|
371
|
-
...replaceContent(state, current.contentIndex, reasoningContent(
|
|
372
|
-
reasoningParts: { ...state.reasoningParts, [event.id]: { ...current, providerMetadata } },
|
|
389
|
+
...replaceContent(state, current.contentIndex, reasoningContent(text, providerMetadata)),
|
|
390
|
+
reasoningParts: { ...state.reasoningParts, [event.id]: { ...current, text, providerMetadata } },
|
|
373
391
|
};
|
|
374
392
|
};
|
|
375
393
|
const reduceToolInputStart = (state, event) => ({
|
|
@@ -457,11 +475,11 @@ export class LLMResponse extends Schema.Class("LLM.Response")({
|
|
|
457
475
|
usage: Schema.optional(Usage),
|
|
458
476
|
finishReason: FinishReasonDetails,
|
|
459
477
|
}) {
|
|
460
|
-
/** Concatenated assistant text
|
|
478
|
+
/** Concatenated assistant text; each fragment's `text-end` value replaces its accumulated deltas when present. */
|
|
461
479
|
get text() {
|
|
462
480
|
return responseText(this.events);
|
|
463
481
|
}
|
|
464
|
-
/** Concatenated reasoning text
|
|
482
|
+
/** Concatenated reasoning text; each fragment's `reasoning-end` value replaces its accumulated deltas when present. */
|
|
465
483
|
get reasoning() {
|
|
466
484
|
return responseReasoning(this.events);
|
|
467
485
|
}
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * as TestLLM from "./testing.js";
|
|
2
|
-
import {
|
|
2
|
+
import { LLMClient } from "./route/client.js";
|
|
3
3
|
import { LLMEvent, type FinishReasonDetails, type AIError, type LLMRequest, type ProviderMetadata, type UsageInput } from "./schema/index.js";
|
|
4
4
|
import { Context, Effect, Layer, Scope, Stream } from "effect";
|
|
5
5
|
export type Response = readonly LLMEvent[] | Stream.Stream<LLMEvent, AIError>;
|
|
@@ -7,13 +7,31 @@ export type Gate = Readonly<{
|
|
|
7
7
|
started: Effect.Effect<void>;
|
|
8
8
|
release: Effect.Effect<void>;
|
|
9
9
|
}>;
|
|
10
|
+
type ClientInterface = Context.Service.Shape<typeof LLMClient.Service>;
|
|
11
|
+
export type Responder = (request: LLMRequest) => Response;
|
|
12
|
+
export interface TestInterface extends ClientInterface {
|
|
13
|
+
/** Returns a snapshot of requests observed at execution time. */
|
|
14
|
+
readonly requests: () => Effect.Effect<readonly LLMRequest[]>;
|
|
15
|
+
readonly push: (...responses: readonly Response[]) => Effect.Effect<void>;
|
|
16
|
+
/** Replaces the fallback without changing queued responses. */
|
|
17
|
+
readonly always: (response: Response) => Effect.Effect<void>;
|
|
18
|
+
/** Answers requests after the one-shot queue is exhausted; receives the original request. */
|
|
19
|
+
readonly serve: (responder: Responder) => Effect.Effect<void>;
|
|
20
|
+
/** Waits for request arrivals, not output or completion. */
|
|
21
|
+
readonly wait: (count: number) => Effect.Effect<void>;
|
|
22
|
+
readonly gate: () => Effect.Effect<Gate, never, Scope.Scope>;
|
|
23
|
+
}
|
|
24
|
+
declare const Test_base: Context.ServiceClass<Test, "@opencode/ai/TestLLM/Test", TestInterface>;
|
|
25
|
+
export declare class Test extends Test_base {
|
|
26
|
+
}
|
|
27
|
+
/** @deprecated Use TestInterface through Test and testLayer. */
|
|
10
28
|
export interface Interface {
|
|
11
29
|
readonly requests: LLMRequest[];
|
|
12
30
|
readonly push: (...responses: readonly Response[]) => Effect.Effect<void>;
|
|
13
31
|
readonly always: (response: Response) => Effect.Effect<void>;
|
|
14
32
|
readonly wait: (count: number) => Effect.Effect<void>;
|
|
15
33
|
readonly gate: Effect.Effect<Gate, never, Scope.Scope>;
|
|
16
|
-
readonly client:
|
|
34
|
+
readonly client: ClientInterface;
|
|
17
35
|
}
|
|
18
36
|
export interface LayerOptions {
|
|
19
37
|
readonly transformRequest?: (request: LLMRequest) => LLMRequest;
|
|
@@ -21,6 +39,7 @@ export interface LayerOptions {
|
|
|
21
39
|
readonly fallback?: Response;
|
|
22
40
|
}
|
|
23
41
|
declare const Service_base: Context.ServiceClass<Service, "@opencode/ai/TestLLM", Interface>;
|
|
42
|
+
/** @deprecated Use Test and testLayer for normal client methods and test controls. */
|
|
24
43
|
export declare class Service extends Service_base {
|
|
25
44
|
}
|
|
26
45
|
export declare const complete: (options: {
|
|
@@ -50,6 +69,7 @@ export declare const complete: (options: {
|
|
|
50
69
|
} | {
|
|
51
70
|
readonly id: string;
|
|
52
71
|
readonly type: "text-end";
|
|
72
|
+
readonly text?: string | undefined;
|
|
53
73
|
readonly providerMetadata?: {
|
|
54
74
|
readonly [x: string]: {
|
|
55
75
|
readonly [x: string]: unknown;
|
|
@@ -75,6 +95,7 @@ export declare const complete: (options: {
|
|
|
75
95
|
} | {
|
|
76
96
|
readonly id: string;
|
|
77
97
|
readonly type: "reasoning-end";
|
|
98
|
+
readonly text?: string | undefined;
|
|
78
99
|
readonly providerMetadata?: {
|
|
79
100
|
readonly [x: string]: {
|
|
80
101
|
readonly [x: string]: unknown;
|
|
@@ -233,6 +254,7 @@ export declare const stop: (...events: readonly LLMEvent[]) => ({
|
|
|
233
254
|
} | {
|
|
234
255
|
readonly id: string;
|
|
235
256
|
readonly type: "text-end";
|
|
257
|
+
readonly text?: string | undefined;
|
|
236
258
|
readonly providerMetadata?: {
|
|
237
259
|
readonly [x: string]: {
|
|
238
260
|
readonly [x: string]: unknown;
|
|
@@ -258,6 +280,7 @@ export declare const stop: (...events: readonly LLMEvent[]) => ({
|
|
|
258
280
|
} | {
|
|
259
281
|
readonly id: string;
|
|
260
282
|
readonly type: "reasoning-end";
|
|
283
|
+
readonly text?: string | undefined;
|
|
261
284
|
readonly providerMetadata?: {
|
|
262
285
|
readonly [x: string]: {
|
|
263
286
|
readonly [x: string]: unknown;
|
|
@@ -416,6 +439,7 @@ export declare const toolCalls: (...events: readonly LLMEvent[]) => ({
|
|
|
416
439
|
} | {
|
|
417
440
|
readonly id: string;
|
|
418
441
|
readonly type: "text-end";
|
|
442
|
+
readonly text?: string | undefined;
|
|
419
443
|
readonly providerMetadata?: {
|
|
420
444
|
readonly [x: string]: {
|
|
421
445
|
readonly [x: string]: unknown;
|
|
@@ -441,6 +465,7 @@ export declare const toolCalls: (...events: readonly LLMEvent[]) => ({
|
|
|
441
465
|
} | {
|
|
442
466
|
readonly id: string;
|
|
443
467
|
readonly type: "reasoning-end";
|
|
468
|
+
readonly text?: string | undefined;
|
|
444
469
|
readonly providerMetadata?: {
|
|
445
470
|
readonly [x: string]: {
|
|
446
471
|
readonly [x: string]: unknown;
|
|
@@ -599,6 +624,7 @@ export declare const text: (value: string, id: string) => ({
|
|
|
599
624
|
} | {
|
|
600
625
|
readonly id: string;
|
|
601
626
|
readonly type: "text-end";
|
|
627
|
+
readonly text?: string | undefined;
|
|
602
628
|
readonly providerMetadata?: {
|
|
603
629
|
readonly [x: string]: {
|
|
604
630
|
readonly [x: string]: unknown;
|
|
@@ -624,6 +650,7 @@ export declare const text: (value: string, id: string) => ({
|
|
|
624
650
|
} | {
|
|
625
651
|
readonly id: string;
|
|
626
652
|
readonly type: "reasoning-end";
|
|
653
|
+
readonly text?: string | undefined;
|
|
627
654
|
readonly providerMetadata?: {
|
|
628
655
|
readonly [x: string]: {
|
|
629
656
|
readonly [x: string]: unknown;
|
|
@@ -782,6 +809,7 @@ export declare const textWithUsage: (value: string, id: string, inputTokens: num
|
|
|
782
809
|
} | {
|
|
783
810
|
readonly id: string;
|
|
784
811
|
readonly type: "text-end";
|
|
812
|
+
readonly text?: string | undefined;
|
|
785
813
|
readonly providerMetadata?: {
|
|
786
814
|
readonly [x: string]: {
|
|
787
815
|
readonly [x: string]: unknown;
|
|
@@ -807,6 +835,7 @@ export declare const textWithUsage: (value: string, id: string, inputTokens: num
|
|
|
807
835
|
} | {
|
|
808
836
|
readonly id: string;
|
|
809
837
|
readonly type: "reasoning-end";
|
|
838
|
+
readonly text?: string | undefined;
|
|
810
839
|
readonly providerMetadata?: {
|
|
811
840
|
readonly [x: string]: {
|
|
812
841
|
readonly [x: string]: unknown;
|
|
@@ -965,6 +994,7 @@ export declare const tool: (id: string, name: string, input: unknown) => ({
|
|
|
965
994
|
} | {
|
|
966
995
|
readonly id: string;
|
|
967
996
|
readonly type: "text-end";
|
|
997
|
+
readonly text?: string | undefined;
|
|
968
998
|
readonly providerMetadata?: {
|
|
969
999
|
readonly [x: string]: {
|
|
970
1000
|
readonly [x: string]: unknown;
|
|
@@ -990,6 +1020,7 @@ export declare const tool: (id: string, name: string, input: unknown) => ({
|
|
|
990
1020
|
} | {
|
|
991
1021
|
readonly id: string;
|
|
992
1022
|
readonly type: "reasoning-end";
|
|
1023
|
+
readonly text?: string | undefined;
|
|
993
1024
|
readonly providerMetadata?: {
|
|
994
1025
|
readonly [x: string]: {
|
|
995
1026
|
readonly [x: string]: unknown;
|
|
@@ -1148,6 +1179,7 @@ export declare const failAfter: (error: AIError, ...events: readonly LLMEvent[])
|
|
|
1148
1179
|
} | {
|
|
1149
1180
|
readonly id: string;
|
|
1150
1181
|
readonly type: "text-end";
|
|
1182
|
+
readonly text?: string | undefined;
|
|
1151
1183
|
readonly providerMetadata?: {
|
|
1152
1184
|
readonly [x: string]: {
|
|
1153
1185
|
readonly [x: string]: unknown;
|
|
@@ -1173,6 +1205,7 @@ export declare const failAfter: (error: AIError, ...events: readonly LLMEvent[])
|
|
|
1173
1205
|
} | {
|
|
1174
1206
|
readonly id: string;
|
|
1175
1207
|
readonly type: "reasoning-end";
|
|
1208
|
+
readonly text?: string | undefined;
|
|
1176
1209
|
readonly providerMetadata?: {
|
|
1177
1210
|
readonly [x: string]: {
|
|
1178
1211
|
readonly [x: string]: unknown;
|
|
@@ -1331,6 +1364,7 @@ export declare const hangAfter: (...events: readonly LLMEvent[]) => Stream.Strea
|
|
|
1331
1364
|
} | {
|
|
1332
1365
|
readonly id: string;
|
|
1333
1366
|
readonly type: "text-end";
|
|
1367
|
+
readonly text?: string | undefined;
|
|
1334
1368
|
readonly providerMetadata?: {
|
|
1335
1369
|
readonly [x: string]: {
|
|
1336
1370
|
readonly [x: string]: unknown;
|
|
@@ -1356,6 +1390,7 @@ export declare const hangAfter: (...events: readonly LLMEvent[]) => Stream.Strea
|
|
|
1356
1390
|
} | {
|
|
1357
1391
|
readonly id: string;
|
|
1358
1392
|
readonly type: "reasoning-end";
|
|
1393
|
+
readonly text?: string | undefined;
|
|
1359
1394
|
readonly providerMetadata?: {
|
|
1360
1395
|
readonly [x: string]: {
|
|
1361
1396
|
readonly [x: string]: unknown;
|
|
@@ -1491,7 +1526,11 @@ export declare const hangAfter: (...events: readonly LLMEvent[]) => Stream.Strea
|
|
|
1491
1526
|
} | undefined;
|
|
1492
1527
|
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
|
|
1493
1528
|
}, never, never>;
|
|
1529
|
+
/** Provides one shared implementation under the normal client and test-control tags. */
|
|
1530
|
+
export declare const testLayer: (options?: LayerOptions) => Layer.Layer<import("./route/client.js").Service | Test, never, never>;
|
|
1531
|
+
/** @deprecated Use testLayer; retained for published callers of the legacy control interface. */
|
|
1494
1532
|
export declare const layer: (options?: LayerOptions) => Layer.Layer<Service, never, never>;
|
|
1533
|
+
/** @deprecated testLayer provides LLMClient.Service directly. */
|
|
1495
1534
|
export declare const clientLayer: Layer.Layer<import("./route/client.js").Service, never, Service>;
|
|
1496
1535
|
export declare const push: (...responses: readonly Response[]) => Effect.Effect<void, never, Service>;
|
|
1497
1536
|
export declare const always: (response: Response) => Effect.Effect<void, never, Service>;
|
package/dist/testing.js
CHANGED
|
@@ -2,6 +2,9 @@ export * as TestLLM from "./testing.js";
|
|
|
2
2
|
import { LLMClient } from "./route/client.js";
|
|
3
3
|
import { LLMEvent, LLMResponse, } from "./schema/index.js";
|
|
4
4
|
import { Context, Deferred, Effect, Latch, Layer, Queue, Scope, Stream } from "effect";
|
|
5
|
+
export class Test extends Context.Service()("@opencode/ai/TestLLM/Test") {
|
|
6
|
+
}
|
|
7
|
+
/** @deprecated Use Test and testLayer for normal client methods and test controls. */
|
|
5
8
|
export class Service extends Context.Service()("@opencode/ai/TestLLM") {
|
|
6
9
|
}
|
|
7
10
|
export const complete = (options, ...events) => [
|
|
@@ -28,28 +31,33 @@ export const tool = (id, name, input) => toolCalls(LLMEvent.toolCall({ id, name,
|
|
|
28
31
|
export const failAfter = (error, ...events) => Stream.fromIterable(events).pipe(Stream.concat(Stream.fail(error)));
|
|
29
32
|
export const hangAfter = (...events) => Stream.concat(Stream.fromIterable(events), Stream.never);
|
|
30
33
|
const toStream = (response) => (Stream.isStream(response) ? response : Stream.fromIterable(response));
|
|
31
|
-
|
|
34
|
+
const make = (options) => Effect.sync(() => {
|
|
32
35
|
const requests = [];
|
|
33
36
|
const responses = [];
|
|
34
37
|
let started = Deferred.makeUnsafe();
|
|
35
38
|
let fallback = options.fallback;
|
|
36
39
|
let activeGate;
|
|
37
40
|
const wait = (count) => Effect.suspend(() => requests.length >= count ? Effect.void : Deferred.await(started).pipe(Effect.andThen(wait(count))));
|
|
38
|
-
const stream = (
|
|
39
|
-
requests.push(options.transformRequest?.(request) ?? request);
|
|
41
|
+
const stream = (request) => Stream.suspend(() => {
|
|
42
|
+
const count = requests.push(options.transformRequest?.(request) ?? request);
|
|
40
43
|
const waiting = started;
|
|
41
44
|
started = Deferred.makeUnsafe();
|
|
42
|
-
Deferred.doneUnsafe(waiting, Effect.void);
|
|
43
|
-
const response = responses.shift() ?? fallback;
|
|
44
|
-
if (!response)
|
|
45
|
-
return Stream.die(new Error(`TestLLM has no response for request ${requests.length}`));
|
|
46
|
-
const streamed = toStream(response);
|
|
47
45
|
const gate = activeGate;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
try {
|
|
47
|
+
const response = responses.shift() ?? (typeof fallback === "function" ? fallback(request) : fallback);
|
|
48
|
+
if (!response)
|
|
49
|
+
return Stream.die(new Error(`TestLLM has no response for request ${count}`));
|
|
50
|
+
const streamed = toStream(response);
|
|
51
|
+
if (!gate)
|
|
52
|
+
return streamed;
|
|
53
|
+
return Stream.unwrap(Queue.offer(gate.started, undefined).pipe(Effect.andThen(gate.release.await), Effect.as(streamed)));
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
// Waiters can resume synchronously; assign the reply and gate before notifying them.
|
|
57
|
+
Deferred.doneUnsafe(waiting, Effect.void);
|
|
58
|
+
}
|
|
51
59
|
});
|
|
52
|
-
const
|
|
60
|
+
const test = Test.of({
|
|
53
61
|
stream,
|
|
54
62
|
generate: (request) => stream(request).pipe(Stream.runFold(LLMResponse.empty, LLMResponse.reduce), Effect.flatMap((state) => {
|
|
55
63
|
const response = LLMResponse.complete(state);
|
|
@@ -57,17 +65,18 @@ export const layer = (options = {}) => Layer.effect(Service, Effect.gen(function
|
|
|
57
65
|
return Effect.succeed(response);
|
|
58
66
|
return Effect.die("TestLLM response ended without a terminal finish event");
|
|
59
67
|
})),
|
|
60
|
-
|
|
61
|
-
return Service.of({
|
|
62
|
-
requests,
|
|
68
|
+
requests: () => Effect.sync(() => [...requests]),
|
|
63
69
|
push: (...input) => Effect.sync(() => {
|
|
64
70
|
responses.push(...input);
|
|
65
71
|
}),
|
|
66
72
|
always: (response) => Effect.sync(() => {
|
|
67
73
|
fallback = response;
|
|
68
74
|
}),
|
|
75
|
+
serve: (responder) => Effect.sync(() => {
|
|
76
|
+
fallback = responder;
|
|
77
|
+
}),
|
|
69
78
|
wait,
|
|
70
|
-
gate: Effect.gen(function* () {
|
|
79
|
+
gate: () => Effect.gen(function* () {
|
|
71
80
|
const gate = {
|
|
72
81
|
started: yield* Effect.acquireRelease(Queue.unbounded(), Queue.shutdown),
|
|
73
82
|
release: yield* Latch.make(),
|
|
@@ -83,9 +92,21 @@ export const layer = (options = {}) => Layer.effect(Service, Effect.gen(function
|
|
|
83
92
|
release,
|
|
84
93
|
};
|
|
85
94
|
}),
|
|
86
|
-
client,
|
|
87
95
|
});
|
|
88
|
-
}
|
|
96
|
+
return { test, requests };
|
|
97
|
+
});
|
|
98
|
+
/** Provides one shared implementation under the normal client and test-control tags. */
|
|
99
|
+
export const testLayer = (options = {}) => Layer.effectContext(Effect.map(make(options), (implementation) => Context.make(LLMClient.Service, implementation.test).pipe(Context.add(Test, implementation.test))));
|
|
100
|
+
/** @deprecated Use testLayer; retained for published callers of the legacy control interface. */
|
|
101
|
+
export const layer = (options = {}) => Layer.effect(Service, Effect.map(make(options), (implementation) => Service.of({
|
|
102
|
+
requests: implementation.requests,
|
|
103
|
+
push: implementation.test.push,
|
|
104
|
+
always: implementation.test.always,
|
|
105
|
+
wait: implementation.test.wait,
|
|
106
|
+
gate: implementation.test.gate(),
|
|
107
|
+
client: implementation.test,
|
|
108
|
+
})));
|
|
109
|
+
/** @deprecated testLayer provides LLMClient.Service directly. */
|
|
89
110
|
export const clientLayer = Layer.effect(LLMClient.Service, Effect.map(Service, (service) => service.client));
|
|
90
111
|
export const push = (...responses) => Service.use((service) => service.push(...responses));
|
|
91
112
|
export const always = (response) => Service.use((service) => service.always(response));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-beta-
|
|
3
|
+
"version": "0.0.0-beta-18593",
|
|
4
4
|
"name": "@opencode-ai/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
32
|
-
"@effect/platform-node": "4.0.0-rc.
|
|
33
|
-
"@opencode-ai/http-recorder": "0.0.0-beta-
|
|
32
|
+
"@effect/platform-node": "4.0.0-rc.112",
|
|
33
|
+
"@opencode-ai/http-recorder": "0.0.0-beta-18593",
|
|
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,9 +39,9 @@
|
|
|
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-beta-
|
|
42
|
+
"@opencode-ai/schema": "0.0.0-beta-18593",
|
|
43
43
|
"aws4fetch": "1.0.20",
|
|
44
|
-
"effect": "4.0.0-rc.
|
|
44
|
+
"effect": "4.0.0-rc.112",
|
|
45
45
|
"google-auth-library": "10.5.0"
|
|
46
46
|
}
|
|
47
47
|
}
|