@opencode-ai/ai 0.0.0-next-16389 → 0.0.0-next-16396
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/testing.d.ts +1493 -0
- package/dist/testing.js +89 -0
- package/package.json +7 -3
package/dist/testing.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export * as TestLLM from "./testing";
|
|
2
|
+
import { LLMClient } from "./route/client";
|
|
3
|
+
import { LLMEvent, LLMResponse, } from "./schema";
|
|
4
|
+
import { Context, Deferred, Effect, Latch, Layer, Queue, Scope, Stream } from "effect";
|
|
5
|
+
export class Service extends Context.Service()("@opencode/ai/TestLLM") {
|
|
6
|
+
}
|
|
7
|
+
export const complete = (options, ...events) => [
|
|
8
|
+
LLMEvent.stepStart({ index: 0 }),
|
|
9
|
+
...events,
|
|
10
|
+
LLMEvent.stepFinish({ index: 0, reason: options.reason, usage: options.usage }),
|
|
11
|
+
LLMEvent.finish({ reason: options.reason }),
|
|
12
|
+
];
|
|
13
|
+
export const stop = (...events) => complete({ reason: { normalized: "stop" } }, ...events);
|
|
14
|
+
export const toolCalls = (...events) => complete({ reason: { normalized: "tool-calls" } }, ...events);
|
|
15
|
+
const textEvents = (value, id) => [
|
|
16
|
+
LLMEvent.textStart({ id }),
|
|
17
|
+
LLMEvent.textDelta({ id, text: value }),
|
|
18
|
+
LLMEvent.textEnd({ id }),
|
|
19
|
+
];
|
|
20
|
+
export const text = (value, id) => stop(...textEvents(value, id));
|
|
21
|
+
export const textWithUsage = (value, id, inputTokens) => complete({ reason: { normalized: "stop" }, usage: { inputTokens, nonCachedInputTokens: inputTokens } }, ...textEvents(value, id));
|
|
22
|
+
export const tool = (id, name, input) => toolCalls(LLMEvent.toolCall({ id, name, input }));
|
|
23
|
+
export const failAfter = (error, ...events) => Stream.fromIterable(events).pipe(Stream.concat(Stream.fail(error)));
|
|
24
|
+
export const hangAfter = (...events) => Stream.concat(Stream.fromIterable(events), Stream.never);
|
|
25
|
+
const toStream = (response) => (Stream.isStream(response) ? response : Stream.fromIterable(response));
|
|
26
|
+
export const layer = (options = {}) => Layer.effect(Service, Effect.gen(function* () {
|
|
27
|
+
const requests = [];
|
|
28
|
+
const responses = [];
|
|
29
|
+
let started = Deferred.makeUnsafe();
|
|
30
|
+
let fallback = options.fallback;
|
|
31
|
+
let activeGate;
|
|
32
|
+
const wait = (count) => Effect.suspend(() => requests.length >= count ? Effect.void : Deferred.await(started).pipe(Effect.andThen(wait(count))));
|
|
33
|
+
const stream = ((request) => {
|
|
34
|
+
requests.push(options.transformRequest?.(request) ?? request);
|
|
35
|
+
const waiting = started;
|
|
36
|
+
started = Deferred.makeUnsafe();
|
|
37
|
+
Deferred.doneUnsafe(waiting, Effect.void);
|
|
38
|
+
const response = responses.shift() ?? fallback;
|
|
39
|
+
if (!response)
|
|
40
|
+
return Stream.die(new Error(`TestLLM has no response for request ${requests.length}`));
|
|
41
|
+
const streamed = toStream(response);
|
|
42
|
+
const gate = activeGate;
|
|
43
|
+
if (!gate)
|
|
44
|
+
return streamed;
|
|
45
|
+
return Stream.unwrap(Queue.offer(gate.started, undefined).pipe(Effect.andThen(gate.release.await), Effect.as(streamed)));
|
|
46
|
+
});
|
|
47
|
+
const client = LLMClient.Service.of({
|
|
48
|
+
prepare: () => Effect.die("TestLLM does not prepare provider-native requests"),
|
|
49
|
+
stream,
|
|
50
|
+
generate: (request) => stream(request).pipe(Stream.runFold(LLMResponse.empty, LLMResponse.reduce), Effect.flatMap((state) => {
|
|
51
|
+
const response = LLMResponse.complete(state);
|
|
52
|
+
if (response)
|
|
53
|
+
return Effect.succeed(response);
|
|
54
|
+
return Effect.die("TestLLM response ended without a terminal finish event");
|
|
55
|
+
})),
|
|
56
|
+
});
|
|
57
|
+
return Service.of({
|
|
58
|
+
requests,
|
|
59
|
+
push: (...input) => Effect.sync(() => {
|
|
60
|
+
responses.push(...input);
|
|
61
|
+
}),
|
|
62
|
+
always: (response) => Effect.sync(() => {
|
|
63
|
+
fallback = response;
|
|
64
|
+
}),
|
|
65
|
+
wait,
|
|
66
|
+
gate: Effect.gen(function* () {
|
|
67
|
+
const gate = {
|
|
68
|
+
started: yield* Effect.acquireRelease(Queue.unbounded(), Queue.shutdown),
|
|
69
|
+
release: yield* Latch.make(),
|
|
70
|
+
};
|
|
71
|
+
activeGate = gate;
|
|
72
|
+
const release = Effect.sync(() => {
|
|
73
|
+
if (activeGate === gate)
|
|
74
|
+
activeGate = undefined;
|
|
75
|
+
}).pipe(Effect.andThen(gate.release.open), Effect.asVoid);
|
|
76
|
+
yield* Effect.addFinalizer(() => release);
|
|
77
|
+
return {
|
|
78
|
+
started: Queue.take(gate.started),
|
|
79
|
+
release,
|
|
80
|
+
};
|
|
81
|
+
}),
|
|
82
|
+
client,
|
|
83
|
+
});
|
|
84
|
+
}));
|
|
85
|
+
export const clientLayer = Layer.effect(LLMClient.Service, Effect.map(Service, (service) => service.client));
|
|
86
|
+
export const push = (...responses) => Service.use((service) => service.push(...responses));
|
|
87
|
+
export const always = (response) => Service.use((service) => service.always(response));
|
|
88
|
+
export const wait = (count) => Service.use((service) => service.wait(count));
|
|
89
|
+
export const gate = Service.use((service) => service.gate);
|
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-16396",
|
|
4
4
|
"name": "@opencode-ai/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"import": "./dist/index.js",
|
|
19
19
|
"types": "./dist/index.d.ts"
|
|
20
20
|
},
|
|
21
|
+
"./testing": {
|
|
22
|
+
"import": "./dist/testing.js",
|
|
23
|
+
"types": "./dist/testing.d.ts"
|
|
24
|
+
},
|
|
21
25
|
"./*": {
|
|
22
26
|
"import": "./dist/*.js",
|
|
23
27
|
"types": "./dist/*.d.ts"
|
|
@@ -26,7 +30,7 @@
|
|
|
26
30
|
"devDependencies": {
|
|
27
31
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
28
32
|
"@effect/platform-node": "4.0.0-beta.101",
|
|
29
|
-
"@opencode-ai/http-recorder": "0.0.0-next-
|
|
33
|
+
"@opencode-ai/http-recorder": "0.0.0-next-16396",
|
|
30
34
|
"@tsconfig/bun": "1.0.9",
|
|
31
35
|
"@types/bun": "1.3.13",
|
|
32
36
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
|
@@ -35,7 +39,7 @@
|
|
|
35
39
|
"dependencies": {
|
|
36
40
|
"@smithy/eventstream-codec": "4.2.14",
|
|
37
41
|
"@smithy/util-utf8": "4.2.2",
|
|
38
|
-
"@opencode-ai/schema": "0.0.0-next-
|
|
42
|
+
"@opencode-ai/schema": "0.0.0-next-16396",
|
|
39
43
|
"aws4fetch": "1.0.20",
|
|
40
44
|
"effect": "4.0.0-beta.101",
|
|
41
45
|
"google-auth-library": "10.5.0"
|