@effect-uai/core 0.14.0 → 0.16.0
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 +30 -16
- package/dist/{AiError-eLV9t1lU.d.mts → AiError-DvhLIbmd.d.mts} +15 -4
- package/dist/{AiError-eLV9t1lU.d.mts.map → AiError-DvhLIbmd.d.mts.map} +1 -1
- package/dist/{Loop-B54AB1ME.d.mts → Loop-ChpaO-ya.d.mts} +2 -2
- package/dist/{Loop-B54AB1ME.d.mts.map → Loop-ChpaO-ya.d.mts.map} +1 -1
- package/dist/MessengerError-CeK6VAcu.d.mts +64 -0
- package/dist/MessengerError-CeK6VAcu.d.mts.map +1 -0
- package/dist/{Toolkit-DAjJ9Tvl.d.mts → Toolkit-tlL0F2YR.d.mts} +2 -2
- package/dist/{Toolkit-DAjJ9Tvl.d.mts.map → Toolkit-tlL0F2YR.d.mts.map} +1 -1
- package/dist/chunker/Chunker.d.mts +1 -1
- package/dist/domain/AiError.d.mts +2 -2
- package/dist/domain/AiError.mjs +9 -2
- package/dist/domain/AiError.mjs.map +1 -1
- package/dist/domain/Realtime.d.mts +616 -0
- package/dist/domain/Realtime.d.mts.map +1 -0
- package/dist/domain/Realtime.mjs +21 -0
- package/dist/domain/Realtime.mjs.map +1 -0
- package/dist/domain/Transcript.d.mts +17 -3
- package/dist/domain/Transcript.d.mts.map +1 -1
- package/dist/domain/Transcript.mjs +30 -1
- package/dist/domain/Transcript.mjs.map +1 -1
- package/dist/domain/Transcript.test.d.mts +1 -0
- package/dist/domain/Transcript.test.mjs +110 -0
- package/dist/domain/Transcript.test.mjs.map +1 -0
- package/dist/embedding-model/EmbeddingModel.d.mts +1 -1
- package/dist/image-generator/ImageGenerator.d.mts +1 -1
- package/dist/index.d.mts +11 -4
- package/dist/index.mjs +9 -2
- package/dist/job/Job.d.mts +1 -1
- package/dist/language-model/LanguageModel.d.mts +2 -2
- package/dist/loop/Loop.d.mts +1 -1
- package/dist/messenger/Messenger.d.mts +472 -0
- package/dist/messenger/Messenger.d.mts.map +1 -0
- package/dist/messenger/Messenger.mjs +66 -0
- package/dist/messenger/Messenger.mjs.map +1 -0
- package/dist/messenger/MessengerAdapter.d.mts +35 -0
- package/dist/messenger/MessengerAdapter.d.mts.map +1 -0
- package/dist/messenger/MessengerAdapter.mjs +102 -0
- package/dist/messenger/MessengerAdapter.mjs.map +1 -0
- package/dist/messenger/MessengerAdapter.test.d.mts +1 -0
- package/dist/messenger/MessengerAdapter.test.mjs +138 -0
- package/dist/messenger/MessengerAdapter.test.mjs.map +1 -0
- package/dist/messenger/MessengerError.d.mts +2 -0
- package/dist/messenger/MessengerError.mjs +39 -0
- package/dist/messenger/MessengerError.mjs.map +1 -0
- package/dist/music-generator/MusicGenerator.d.mts +1 -1
- package/dist/realtime/RealtimeSession.d.mts +50 -0
- package/dist/realtime/RealtimeSession.d.mts.map +1 -0
- package/dist/realtime/RealtimeSession.mjs +45 -0
- package/dist/realtime/RealtimeSession.mjs.map +1 -0
- package/dist/realtime/RealtimeSession.test.d.mts +1 -0
- package/dist/realtime/RealtimeSession.test.mjs +95 -0
- package/dist/realtime/RealtimeSession.test.mjs.map +1 -0
- package/dist/reranker/Reranker.d.mts +1 -1
- package/dist/research/DeepResearch.d.mts +1 -1
- package/dist/retry/Retry.d.mts +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +1 -1
- package/dist/streaming/Settle.d.mts +45 -0
- package/dist/streaming/Settle.d.mts.map +1 -0
- package/dist/streaming/Settle.mjs +95 -0
- package/dist/streaming/Settle.mjs.map +1 -0
- package/dist/streaming/Settle.test.d.mts +1 -0
- package/dist/streaming/Settle.test.mjs +252 -0
- package/dist/streaming/Settle.test.mjs.map +1 -0
- package/dist/streaming/WebSocketSession.d.mts +36 -0
- package/dist/streaming/WebSocketSession.d.mts.map +1 -0
- package/dist/streaming/WebSocketSession.mjs +89 -0
- package/dist/streaming/WebSocketSession.mjs.map +1 -0
- package/dist/streaming/WebSocketSession.test.d.mts +1 -0
- package/dist/streaming/WebSocketSession.test.mjs +51 -0
- package/dist/streaming/WebSocketSession.test.mjs.map +1 -0
- package/dist/testing/FakeWebSocket.d.mts +25 -0
- package/dist/testing/FakeWebSocket.d.mts.map +1 -0
- package/dist/testing/FakeWebSocket.mjs +79 -0
- package/dist/testing/FakeWebSocket.mjs.map +1 -0
- package/dist/testing/MockMessenger.d.mts +192 -0
- package/dist/testing/MockMessenger.d.mts.map +1 -0
- package/dist/testing/MockMessenger.mjs +76 -0
- package/dist/testing/MockMessenger.mjs.map +1 -0
- package/dist/testing/MockRealtimeSession.d.mts +30 -0
- package/dist/testing/MockRealtimeSession.d.mts.map +1 -0
- package/dist/testing/MockRealtimeSession.mjs +52 -0
- package/dist/testing/MockRealtimeSession.mjs.map +1 -0
- package/dist/tool/Toolkit.d.mts +1 -1
- package/dist/transcriber/Transcriber.d.mts +1 -1
- package/dist/web-read/WebRead.d.mts +1 -1
- package/dist/web-read/WebReadTool.d.mts +1 -1
- package/dist/web-search/WebSearch.d.mts +1 -1
- package/dist/web-search/WebSearchTool.d.mts +1 -1
- package/package.json +41 -1
- package/src/domain/AiError.ts +11 -0
- package/src/domain/Realtime.ts +101 -0
- package/src/domain/Transcript.test.ts +98 -0
- package/src/domain/Transcript.ts +53 -1
- package/src/index.ts +7 -0
- package/src/messenger/Messenger.ts +293 -0
- package/src/messenger/MessengerAdapter.test.ts +179 -0
- package/src/messenger/MessengerAdapter.ts +189 -0
- package/src/messenger/MessengerError.ts +67 -0
- package/src/realtime/RealtimeSession.test.ts +100 -0
- package/src/realtime/RealtimeSession.ts +77 -0
- package/src/streaming/Settle.test.ts +344 -0
- package/src/streaming/Settle.ts +196 -0
- package/src/streaming/WebSocketSession.test.ts +52 -0
- package/src/streaming/WebSocketSession.ts +132 -0
- package/src/testing/FakeWebSocket.ts +120 -0
- package/src/testing/MockMessenger.ts +147 -0
- package/src/testing/MockRealtimeSession.ts +92 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe } from "../domain/AiError.mjs";
|
|
2
|
+
import { toAiError } from "./WebSocketSession.mjs";
|
|
3
|
+
import { o as describe$1 } from "../utils-CiH_DjFW.mjs";
|
|
4
|
+
import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
|
|
5
|
+
import { t as it } from "../dist-nohcnkD6.mjs";
|
|
6
|
+
import * as Socket from "effect/unstable/socket/Socket";
|
|
7
|
+
//#region src/streaming/WebSocketSession.test.ts
|
|
8
|
+
const KEY = "AIzaSyEXAMPLEKEY";
|
|
9
|
+
const URL = `wss://generativelanguage.googleapis.com/ws/BidiGenerateContent?key=${KEY}`;
|
|
10
|
+
/**
|
|
11
|
+
* What Bun hands the `error` listener when the upgrade is rejected: the whole
|
|
12
|
+
* socket URL, in prose, on a prototype getter. Node and Deno say less, but the
|
|
13
|
+
* adapter cannot know which runtime it is on.
|
|
14
|
+
*/
|
|
15
|
+
var BunErrorEvent = class {
|
|
16
|
+
get message() {
|
|
17
|
+
return `WebSocket connection to '${URL}' failed: Connection ended`;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/** Deno names the status but not the URL, also on a prototype getter. */
|
|
21
|
+
var DenoErrorEvent = class {
|
|
22
|
+
detail;
|
|
23
|
+
constructor(detail) {
|
|
24
|
+
this.detail = detail;
|
|
25
|
+
}
|
|
26
|
+
get message() {
|
|
27
|
+
return `NetworkError: failed to connect to WebSocket: ${this.detail}`;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const openFailure = (cause) => new Socket.SocketError({ reason: new Socket.SocketOpenError({
|
|
31
|
+
kind: "Unknown",
|
|
32
|
+
cause
|
|
33
|
+
}) });
|
|
34
|
+
describe$1("WebSocketSession errors", () => {
|
|
35
|
+
it("keeps the key out of the error a rejected upgrade produces", () => {
|
|
36
|
+
const error = toAiError("google")(openFailure(new BunErrorEvent()));
|
|
37
|
+
globalExpect(JSON.stringify(error)).not.toContain(KEY);
|
|
38
|
+
globalExpect(describe(error)).not.toContain(KEY);
|
|
39
|
+
globalExpect(JSON.stringify(error)).toContain("generativelanguage.googleapis.com");
|
|
40
|
+
});
|
|
41
|
+
it("reads the rejected status out of the prose, wherever the runtime put it", () => {
|
|
42
|
+
const forbidden = toAiError("google")(openFailure(new DenoErrorEvent("Invalid status code: 403")));
|
|
43
|
+
const ended = toAiError("google")(openFailure(new BunErrorEvent()));
|
|
44
|
+
globalExpect(forbidden._tag).toBe("AuthFailed");
|
|
45
|
+
globalExpect(ended._tag).toBe("Unavailable");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
//#endregion
|
|
49
|
+
export {};
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=WebSocketSession.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebSocketSession.test.mjs","names":["describe","AiError.describe"],"sources":["../../src/streaming/WebSocketSession.test.ts"],"sourcesContent":["import { describe, it } from \"@effect/vitest\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport { expect } from \"vitest\"\nimport * as AiError from \"../domain/AiError.js\"\nimport { toAiError } from \"./WebSocketSession.js\"\n\nconst KEY = \"AIzaSyEXAMPLEKEY\"\nconst URL = `wss://generativelanguage.googleapis.com/ws/BidiGenerateContent?key=${KEY}`\n\n/**\n * What Bun hands the `error` listener when the upgrade is rejected: the whole\n * socket URL, in prose, on a prototype getter. Node and Deno say less, but the\n * adapter cannot know which runtime it is on.\n */\nclass BunErrorEvent {\n get message(): string {\n return `WebSocket connection to '${URL}' failed: Connection ended`\n }\n}\n\n/** Deno names the status but not the URL, also on a prototype getter. */\nclass DenoErrorEvent {\n constructor(private readonly detail: string) {}\n get message(): string {\n return `NetworkError: failed to connect to WebSocket: ${this.detail}`\n }\n}\n\nconst openFailure = (cause: unknown) =>\n new Socket.SocketError({ reason: new Socket.SocketOpenError({ kind: \"Unknown\", cause }) })\n\ndescribe(\"WebSocketSession errors\", () => {\n it(\"keeps the key out of the error a rejected upgrade produces\", () => {\n const error = toAiError(\"google\")(openFailure(new BunErrorEvent()))\n\n expect(JSON.stringify(error)).not.toContain(KEY)\n expect(AiError.describe(error)).not.toContain(KEY)\n // The host is still there: an error that says nothing is its own problem.\n expect(JSON.stringify(error)).toContain(\"generativelanguage.googleapis.com\")\n })\n\n it(\"reads the rejected status out of the prose, wherever the runtime put it\", () => {\n // Deno's wording. The status is the only hint a rejected upgrade gives.\n const forbidden = toAiError(\"google\")(\n openFailure(new DenoErrorEvent(\"Invalid status code: 403\")),\n )\n const ended = toAiError(\"google\")(openFailure(new BunErrorEvent()))\n\n expect(forbidden._tag).toBe(\"AuthFailed\")\n expect(ended._tag).toBe(\"Unavailable\")\n })\n})\n"],"mappings":";;;;;;;AAMA,MAAM,MAAM;AACZ,MAAM,MAAM,sEAAsE;;;;;;AAOlF,IAAM,gBAAN,MAAoB;CAClB,IAAI,UAAkB;EACpB,OAAO,4BAA4B,IAAI;CACzC;AACF;;AAGA,IAAM,iBAAN,MAAqB;CACU;CAA7B,YAAY,QAAiC;EAAhB,KAAA,SAAA;CAAiB;CAC9C,IAAI,UAAkB;EACpB,OAAO,iDAAiD,KAAK;CAC/D;AACF;AAEA,MAAM,eAAe,UACnB,IAAI,OAAO,YAAY,EAAE,QAAQ,IAAI,OAAO,gBAAgB;CAAE,MAAM;CAAW;AAAM,CAAC,EAAE,CAAC;AAE3FA,WAAS,iCAAiC;CACxC,GAAG,oEAAoE;EACrE,MAAM,QAAQ,UAAU,QAAQ,CAAC,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;EAElE,aAAO,KAAK,UAAU,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,GAAG;EAC/C,aAAOC,SAAiB,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,GAAG;EAEjD,aAAO,KAAK,UAAU,KAAK,CAAC,CAAC,CAAC,UAAU,mCAAmC;CAC7E,CAAC;CAED,GAAG,iFAAiF;EAElF,MAAM,YAAY,UAAU,QAAQ,CAAC,CACnC,YAAY,IAAI,eAAe,0BAA0B,CAAC,CAC5D;EACA,MAAM,QAAQ,UAAU,QAAQ,CAAC,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;EAElE,aAAO,UAAU,IAAI,CAAC,CAAC,KAAK,YAAY;EACxC,aAAO,MAAM,IAAI,CAAC,CAAC,KAAK,aAAa;CACvC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Effect, Layer, Scope } from "effect";
|
|
2
|
+
import * as Socket from "effect/unstable/socket/Socket";
|
|
3
|
+
//#region src/testing/FakeWebSocket.d.ts
|
|
4
|
+
type FakeWebSocketServer = {
|
|
5
|
+
/** Every frame the client has sent, JSON-parsed, oldest first. */
|
|
6
|
+
readonly sent: Effect.Effect<ReadonlyArray<unknown>>;
|
|
7
|
+
/** Deliver one frame to the client. */
|
|
8
|
+
readonly push: (frame: unknown) => Effect.Effect<void>;
|
|
9
|
+
/** Close the socket. 1000, 1001 and 1005 are the clean codes. */
|
|
10
|
+
readonly close: (code?: number) => Effect.Effect<void>;
|
|
11
|
+
/** Hand this to an adapter that takes a socket constructor directly. */
|
|
12
|
+
readonly connect: Socket.WebSocketConstructor["Service"];
|
|
13
|
+
/** Provide this in place of the real constructor. */
|
|
14
|
+
readonly layer: Layer.Layer<Socket.WebSocketConstructor>;
|
|
15
|
+
};
|
|
16
|
+
type FakeWebSocketOptions = {
|
|
17
|
+
/** Pushed once the client subscribes, for a protocol whose server speaks first. */
|
|
18
|
+
readonly greeting?: ReadonlyArray<unknown>;
|
|
19
|
+
/** How the server answers each client frame. */
|
|
20
|
+
readonly reply?: (frame: unknown) => Effect.Effect<ReadonlyArray<unknown>>;
|
|
21
|
+
};
|
|
22
|
+
declare const make: (options?: FakeWebSocketOptions) => Effect.Effect<FakeWebSocketServer, never, Scope.Scope>;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { FakeWebSocketOptions, FakeWebSocketServer, make };
|
|
25
|
+
//# sourceMappingURL=FakeWebSocket.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FakeWebSocket.d.mts","names":[],"sources":["../../src/testing/FakeWebSocket.ts"],"mappings":";;;KAeY;;WAED,MAAM,OAAO,OAAO;;WAEpB,OAAO,mBAAmB,OAAO;;WAEjC,QAAQ,kBAAkB,OAAO;;WAEjC,SAAS,OAAO;;WAEhB,OAAO,MAAM,MAAM,OAAO;;KAGzB;;WAED,WAAW;;WAEX,SAAS,mBAAmB,OAAO,OAAO;;cAWxC,OAAI,UACL,yBACT,OAAO,OAAO,4BAA4B,MAAM"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Effect, Layer, Queue, Ref, Stream } from "effect";
|
|
2
|
+
import * as Socket from "effect/unstable/socket/Socket";
|
|
3
|
+
//#region src/testing/FakeWebSocket.ts
|
|
4
|
+
/**
|
|
5
|
+
* An in-memory WebSocket for testing socket adapters without a server.
|
|
6
|
+
*
|
|
7
|
+
* Provide `layer` in place of the real `Socket.WebSocketConstructor` and the
|
|
8
|
+
* adapter runs end to end: its own connect, handshake, decode and teardown.
|
|
9
|
+
* The test plays the other end with `reply`, `push`, `close` and `sent`.
|
|
10
|
+
*
|
|
11
|
+
* Client frames land on a queue, so the scripted server is a forked fiber and
|
|
12
|
+
* `reply` is a real Effect. What stays imperative is the listener table the
|
|
13
|
+
* shim registers, since `Socket.makeWebSocket` drives it with plain callbacks.
|
|
14
|
+
* The shim is a plain object, so nothing here touches a platform global.
|
|
15
|
+
*/
|
|
16
|
+
const OPEN = 1;
|
|
17
|
+
const CLOSED = 3;
|
|
18
|
+
/** Run after the current call unwinds, so the client is never re-entered. */
|
|
19
|
+
const soon = (run) => void Promise.resolve().then(run);
|
|
20
|
+
const make = (options) => Effect.gen(function* () {
|
|
21
|
+
const inbound = yield* Queue.unbounded();
|
|
22
|
+
const sent = yield* Ref.make([]);
|
|
23
|
+
const wiring = {
|
|
24
|
+
listeners: {},
|
|
25
|
+
readyState: OPEN,
|
|
26
|
+
greeted: false
|
|
27
|
+
};
|
|
28
|
+
const emit = (type, event) => (wiring.listeners[type] ?? []).forEach((listener) => listener(event));
|
|
29
|
+
const deliverSync = (frames) => frames.forEach((f) => emit("message", { data: JSON.stringify(f) }));
|
|
30
|
+
const deliver = (frames) => Effect.sync(() => deliverSync(frames));
|
|
31
|
+
const shutdown = (code) => {
|
|
32
|
+
if (wiring.readyState === CLOSED) return;
|
|
33
|
+
wiring.readyState = CLOSED;
|
|
34
|
+
emit("close", {
|
|
35
|
+
code,
|
|
36
|
+
reason: ""
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
yield* Stream.fromQueue(inbound).pipe(Stream.runForEach((frame) => Effect.gen(function* () {
|
|
40
|
+
yield* Ref.update(sent, (frames) => [...frames, frame]);
|
|
41
|
+
const answer = yield* options?.reply?.(frame) ?? Effect.succeed([]);
|
|
42
|
+
yield* deliver(answer);
|
|
43
|
+
})), Effect.forkScoped);
|
|
44
|
+
const socket = {
|
|
45
|
+
get readyState() {
|
|
46
|
+
return wiring.readyState;
|
|
47
|
+
},
|
|
48
|
+
addEventListener: (type, fn) => {
|
|
49
|
+
wiring.listeners = {
|
|
50
|
+
...wiring.listeners,
|
|
51
|
+
[type]: [...wiring.listeners[type] ?? [], fn]
|
|
52
|
+
};
|
|
53
|
+
if (type === "message" && !wiring.greeted) {
|
|
54
|
+
wiring.greeted = true;
|
|
55
|
+
soon(() => deliverSync(options?.greeting ?? []));
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
removeEventListener: (type, fn) => {
|
|
59
|
+
wiring.listeners = {
|
|
60
|
+
...wiring.listeners,
|
|
61
|
+
[type]: (wiring.listeners[type] ?? []).filter((l) => l !== fn)
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
send: (data) => void Queue.offerUnsafe(inbound, JSON.parse(data)),
|
|
65
|
+
close: (code = 1e3) => shutdown(code)
|
|
66
|
+
};
|
|
67
|
+
const connect = () => socket;
|
|
68
|
+
return {
|
|
69
|
+
sent: Ref.get(sent),
|
|
70
|
+
push: (frame) => deliver([frame]),
|
|
71
|
+
close: (code = 1e3) => Effect.sync(() => shutdown(code)),
|
|
72
|
+
connect,
|
|
73
|
+
layer: Layer.succeed(Socket.WebSocketConstructor, connect)
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
//#endregion
|
|
77
|
+
export { make };
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=FakeWebSocket.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FakeWebSocket.mjs","names":[],"sources":["../../src/testing/FakeWebSocket.ts"],"sourcesContent":["/**\n * An in-memory WebSocket for testing socket adapters without a server.\n *\n * Provide `layer` in place of the real `Socket.WebSocketConstructor` and the\n * adapter runs end to end: its own connect, handshake, decode and teardown.\n * The test plays the other end with `reply`, `push`, `close` and `sent`.\n *\n * Client frames land on a queue, so the scripted server is a forked fiber and\n * `reply` is a real Effect. What stays imperative is the listener table the\n * shim registers, since `Socket.makeWebSocket` drives it with plain callbacks.\n * The shim is a plain object, so nothing here touches a platform global.\n */\nimport { Effect, Layer, Queue, Ref, type Scope, Stream } from \"effect\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\n\nexport type FakeWebSocketServer = {\n /** Every frame the client has sent, JSON-parsed, oldest first. */\n readonly sent: Effect.Effect<ReadonlyArray<unknown>>\n /** Deliver one frame to the client. */\n readonly push: (frame: unknown) => Effect.Effect<void>\n /** Close the socket. 1000, 1001 and 1005 are the clean codes. */\n readonly close: (code?: number) => Effect.Effect<void>\n /** Hand this to an adapter that takes a socket constructor directly. */\n readonly connect: Socket.WebSocketConstructor[\"Service\"]\n /** Provide this in place of the real constructor. */\n readonly layer: Layer.Layer<Socket.WebSocketConstructor>\n}\n\nexport type FakeWebSocketOptions = {\n /** Pushed once the client subscribes, for a protocol whose server speaks first. */\n readonly greeting?: ReadonlyArray<unknown>\n /** How the server answers each client frame. */\n readonly reply?: (frame: unknown) => Effect.Effect<ReadonlyArray<unknown>>\n}\n\ntype Listener = (event: any) => void\n\nconst OPEN = 1\nconst CLOSED = 3\n\n/** Run after the current call unwinds, so the client is never re-entered. */\nconst soon = (run: () => void): void => void Promise.resolve().then(run)\n\nexport const make = (\n options?: FakeWebSocketOptions,\n): Effect.Effect<FakeWebSocketServer, never, Scope.Scope> =>\n Effect.gen(function* () {\n const inbound = yield* Queue.unbounded<unknown>()\n const sent = yield* Ref.make<ReadonlyArray<unknown>>([])\n\n const wiring = {\n listeners: {} as Record<string, ReadonlyArray<Listener>>,\n readyState: OPEN,\n greeted: false,\n }\n\n const emit = (type: string, event: unknown): void =>\n (wiring.listeners[type] ?? []).forEach((listener) => listener(event))\n\n const deliverSync = (frames: ReadonlyArray<unknown>): void =>\n frames.forEach((f) => emit(\"message\", { data: JSON.stringify(f) }))\n\n const deliver = (frames: ReadonlyArray<unknown>): Effect.Effect<void> =>\n Effect.sync(() => deliverSync(frames))\n\n const shutdown = (code: number): void => {\n if (wiring.readyState === CLOSED) return\n wiring.readyState = CLOSED\n emit(\"close\", { code, reason: \"\" })\n }\n\n // The scripted server. Answers land on their own turn, like a real one.\n yield* Stream.fromQueue(inbound).pipe(\n Stream.runForEach((frame) =>\n Effect.gen(function* () {\n yield* Ref.update(sent, (frames) => [...frames, frame])\n const answer = yield* options?.reply?.(frame) ?? Effect.succeed([])\n yield* deliver(answer)\n }),\n ),\n Effect.forkScoped,\n )\n\n const socket = {\n get readyState() {\n return wiring.readyState\n },\n addEventListener: (type: string, fn: Listener): void => {\n wiring.listeners = {\n ...wiring.listeners,\n [type]: [...(wiring.listeners[type] ?? []), fn],\n }\n // The greeting waits for a subscriber before it lands.\n if (type === \"message\" && !wiring.greeted) {\n wiring.greeted = true\n soon(() => deliverSync(options?.greeting ?? []))\n }\n },\n removeEventListener: (type: string, fn: Listener): void => {\n wiring.listeners = {\n ...wiring.listeners,\n [type]: (wiring.listeners[type] ?? []).filter((l) => l !== fn),\n }\n },\n // Safe from a synchronous callback, and no runtime needed.\n send: (data: string): void => void Queue.offerUnsafe(inbound, JSON.parse(data)),\n close: (code = 1000): void => shutdown(code),\n }\n\n const connect: Socket.WebSocketConstructor[\"Service\"] = () =>\n socket as unknown as globalThis.WebSocket\n\n return {\n sent: Ref.get(sent),\n push: (frame) => deliver([frame]),\n close: (code = 1000) => Effect.sync(() => shutdown(code)),\n connect,\n layer: Layer.succeed(Socket.WebSocketConstructor, connect),\n }\n })\n"],"mappings":";;;;;;;;;;;;;;;AAqCA,MAAM,OAAO;AACb,MAAM,SAAS;;AAGf,MAAM,QAAQ,QAA0B,KAAK,QAAQ,QAAQ,CAAC,CAAC,KAAK,GAAG;AAEvE,MAAa,QACX,YAEA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,MAAM,UAAmB;CAChD,MAAM,OAAO,OAAO,IAAI,KAA6B,CAAC,CAAC;CAEvD,MAAM,SAAS;EACb,WAAW,CAAC;EACZ,YAAY;EACZ,SAAS;CACX;CAEA,MAAM,QAAQ,MAAc,WACzB,OAAO,UAAU,SAAS,CAAC,EAAA,CAAG,SAAS,aAAa,SAAS,KAAK,CAAC;CAEtE,MAAM,eAAe,WACnB,OAAO,SAAS,MAAM,KAAK,WAAW,EAAE,MAAM,KAAK,UAAU,CAAC,EAAE,CAAC,CAAC;CAEpE,MAAM,WAAW,WACf,OAAO,WAAW,YAAY,MAAM,CAAC;CAEvC,MAAM,YAAY,SAAuB;EACvC,IAAI,OAAO,eAAe,QAAQ;EAClC,OAAO,aAAa;EACpB,KAAK,SAAS;GAAE;GAAM,QAAQ;EAAG,CAAC;CACpC;CAGA,OAAO,OAAO,UAAU,OAAO,CAAC,CAAC,KAC/B,OAAO,YAAY,UACjB,OAAO,IAAI,aAAa;EACtB,OAAO,IAAI,OAAO,OAAO,WAAW,CAAC,GAAG,QAAQ,KAAK,CAAC;EACtD,MAAM,SAAS,OAAO,SAAS,QAAQ,KAAK,KAAK,OAAO,QAAQ,CAAC,CAAC;EAClE,OAAO,QAAQ,MAAM;CACvB,CAAC,CACH,GACA,OAAO,UACT;CAEA,MAAM,SAAS;EACb,IAAI,aAAa;GACf,OAAO,OAAO;EAChB;EACA,mBAAmB,MAAc,OAAuB;GACtD,OAAO,YAAY;IACjB,GAAG,OAAO;KACT,OAAO,CAAC,GAAI,OAAO,UAAU,SAAS,CAAC,GAAI,EAAE;GAChD;GAEA,IAAI,SAAS,aAAa,CAAC,OAAO,SAAS;IACzC,OAAO,UAAU;IACjB,WAAW,YAAY,SAAS,YAAY,CAAC,CAAC,CAAC;GACjD;EACF;EACA,sBAAsB,MAAc,OAAuB;GACzD,OAAO,YAAY;IACjB,GAAG,OAAO;KACT,QAAQ,OAAO,UAAU,SAAS,CAAC,EAAA,CAAG,QAAQ,MAAM,MAAM,EAAE;GAC/D;EACF;EAEA,OAAO,SAAuB,KAAK,MAAM,YAAY,SAAS,KAAK,MAAM,IAAI,CAAC;EAC9E,QAAQ,OAAO,QAAe,SAAS,IAAI;CAC7C;CAEA,MAAM,gBACJ;CAEF,OAAO;EACL,MAAM,IAAI,IAAI,IAAI;EAClB,OAAO,UAAU,QAAQ,CAAC,KAAK,CAAC;EAChC,QAAQ,OAAO,QAAS,OAAO,WAAW,SAAS,IAAI,CAAC;EACxD;EACA,OAAO,MAAM,QAAQ,OAAO,sBAAsB,OAAO;CAC3D;AACF,CAAC"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { n as MessengerError } from "../MessengerError-CeK6VAcu.mjs";
|
|
2
|
+
import { ConversationRef, InboundEvent, MessageRef, Messenger, MessengerLimits, MessengerService, Outbound } from "../messenger/Messenger.mjs";
|
|
3
|
+
import { StreamViaEditsOptions } from "../messenger/MessengerAdapter.mjs";
|
|
4
|
+
import { Data, Effect, Layer, Stream } from "effect";
|
|
5
|
+
//#region src/testing/MockMessenger.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* One entry per outbound call. `Post` is logged once even when the text was
|
|
8
|
+
* long enough to split, so a caller that rolled over on its own is
|
|
9
|
+
* distinguishable from one that leaned on `post` to do it.
|
|
10
|
+
*/
|
|
11
|
+
type Call = Data.TaggedEnum<{
|
|
12
|
+
Post: {
|
|
13
|
+
readonly conversation: ConversationRef;
|
|
14
|
+
readonly message: Outbound;
|
|
15
|
+
};
|
|
16
|
+
Edit: {
|
|
17
|
+
readonly message: MessageRef;
|
|
18
|
+
readonly next: Outbound;
|
|
19
|
+
};
|
|
20
|
+
React: {
|
|
21
|
+
readonly message: MessageRef;
|
|
22
|
+
readonly emoji: string;
|
|
23
|
+
};
|
|
24
|
+
TypingStart: {
|
|
25
|
+
readonly conversation: ConversationRef;
|
|
26
|
+
};
|
|
27
|
+
TypingStop: {
|
|
28
|
+
readonly conversation: ConversationRef;
|
|
29
|
+
};
|
|
30
|
+
}>;
|
|
31
|
+
declare const Call: {
|
|
32
|
+
readonly $is: <Tag extends "Edit" | "Post" | "React" | "TypingStart" | "TypingStop">(tag: Tag) => (u: unknown) => u is Extract<{
|
|
33
|
+
readonly _tag: "Edit";
|
|
34
|
+
readonly message: MessageRef;
|
|
35
|
+
readonly next: Outbound;
|
|
36
|
+
}, {
|
|
37
|
+
readonly _tag: Tag;
|
|
38
|
+
}> | Extract<{
|
|
39
|
+
readonly _tag: "Post";
|
|
40
|
+
readonly conversation: ConversationRef;
|
|
41
|
+
readonly message: Outbound;
|
|
42
|
+
}, {
|
|
43
|
+
readonly _tag: Tag;
|
|
44
|
+
}> | Extract<{
|
|
45
|
+
readonly _tag: "React";
|
|
46
|
+
readonly message: MessageRef;
|
|
47
|
+
readonly emoji: string;
|
|
48
|
+
}, {
|
|
49
|
+
readonly _tag: Tag;
|
|
50
|
+
}> | Extract<{
|
|
51
|
+
readonly _tag: "TypingStart";
|
|
52
|
+
readonly conversation: ConversationRef;
|
|
53
|
+
}, {
|
|
54
|
+
readonly _tag: Tag;
|
|
55
|
+
}> | Extract<{
|
|
56
|
+
readonly _tag: "TypingStop";
|
|
57
|
+
readonly conversation: ConversationRef;
|
|
58
|
+
}, {
|
|
59
|
+
readonly _tag: Tag;
|
|
60
|
+
}>;
|
|
61
|
+
readonly $match: {
|
|
62
|
+
<Cases extends { readonly [Tag in "Edit" | "Post" | "React" | "TypingStart" | "TypingStop"]: (args: Extract<{
|
|
63
|
+
readonly _tag: "Edit";
|
|
64
|
+
readonly message: MessageRef;
|
|
65
|
+
readonly next: Outbound;
|
|
66
|
+
} | {
|
|
67
|
+
readonly _tag: "Post";
|
|
68
|
+
readonly conversation: ConversationRef;
|
|
69
|
+
readonly message: Outbound;
|
|
70
|
+
} | {
|
|
71
|
+
readonly _tag: "React";
|
|
72
|
+
readonly message: MessageRef;
|
|
73
|
+
readonly emoji: string;
|
|
74
|
+
} | {
|
|
75
|
+
readonly _tag: "TypingStart";
|
|
76
|
+
readonly conversation: ConversationRef;
|
|
77
|
+
} | {
|
|
78
|
+
readonly _tag: "TypingStop";
|
|
79
|
+
readonly conversation: ConversationRef;
|
|
80
|
+
}, {
|
|
81
|
+
readonly _tag: Tag;
|
|
82
|
+
}>) => any; }>(cases: Cases): (value: {
|
|
83
|
+
readonly _tag: "Edit";
|
|
84
|
+
readonly message: MessageRef;
|
|
85
|
+
readonly next: Outbound;
|
|
86
|
+
} | {
|
|
87
|
+
readonly _tag: "Post";
|
|
88
|
+
readonly conversation: ConversationRef;
|
|
89
|
+
readonly message: Outbound;
|
|
90
|
+
} | {
|
|
91
|
+
readonly _tag: "React";
|
|
92
|
+
readonly message: MessageRef;
|
|
93
|
+
readonly emoji: string;
|
|
94
|
+
} | {
|
|
95
|
+
readonly _tag: "TypingStart";
|
|
96
|
+
readonly conversation: ConversationRef;
|
|
97
|
+
} | {
|
|
98
|
+
readonly _tag: "TypingStop";
|
|
99
|
+
readonly conversation: ConversationRef;
|
|
100
|
+
}) => import("effect/Unify").Unify<ReturnType<Cases["Edit" | "Post" | "React" | "TypingStart" | "TypingStop"]>>;
|
|
101
|
+
<Cases extends { readonly [Tag in "Edit" | "Post" | "React" | "TypingStart" | "TypingStop"]: (args: Extract<{
|
|
102
|
+
readonly _tag: "Edit";
|
|
103
|
+
readonly message: MessageRef;
|
|
104
|
+
readonly next: Outbound;
|
|
105
|
+
} | {
|
|
106
|
+
readonly _tag: "Post";
|
|
107
|
+
readonly conversation: ConversationRef;
|
|
108
|
+
readonly message: Outbound;
|
|
109
|
+
} | {
|
|
110
|
+
readonly _tag: "React";
|
|
111
|
+
readonly message: MessageRef;
|
|
112
|
+
readonly emoji: string;
|
|
113
|
+
} | {
|
|
114
|
+
readonly _tag: "TypingStart";
|
|
115
|
+
readonly conversation: ConversationRef;
|
|
116
|
+
} | {
|
|
117
|
+
readonly _tag: "TypingStop";
|
|
118
|
+
readonly conversation: ConversationRef;
|
|
119
|
+
}, {
|
|
120
|
+
readonly _tag: Tag;
|
|
121
|
+
}>) => any; }>(value: {
|
|
122
|
+
readonly _tag: "Edit";
|
|
123
|
+
readonly message: MessageRef;
|
|
124
|
+
readonly next: Outbound;
|
|
125
|
+
} | {
|
|
126
|
+
readonly _tag: "Post";
|
|
127
|
+
readonly conversation: ConversationRef;
|
|
128
|
+
readonly message: Outbound;
|
|
129
|
+
} | {
|
|
130
|
+
readonly _tag: "React";
|
|
131
|
+
readonly message: MessageRef;
|
|
132
|
+
readonly emoji: string;
|
|
133
|
+
} | {
|
|
134
|
+
readonly _tag: "TypingStart";
|
|
135
|
+
readonly conversation: ConversationRef;
|
|
136
|
+
} | {
|
|
137
|
+
readonly _tag: "TypingStop";
|
|
138
|
+
readonly conversation: ConversationRef;
|
|
139
|
+
}, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["Edit" | "Post" | "React" | "TypingStart" | "TypingStop"]>>;
|
|
140
|
+
};
|
|
141
|
+
readonly Edit: Data.TaggedEnum.ConstructorFrom<{
|
|
142
|
+
readonly _tag: "Edit";
|
|
143
|
+
readonly message: MessageRef;
|
|
144
|
+
readonly next: Outbound;
|
|
145
|
+
}, "_tag">;
|
|
146
|
+
readonly Post: Data.TaggedEnum.ConstructorFrom<{
|
|
147
|
+
readonly _tag: "Post";
|
|
148
|
+
readonly conversation: ConversationRef;
|
|
149
|
+
readonly message: Outbound;
|
|
150
|
+
}, "_tag">;
|
|
151
|
+
readonly React: Data.TaggedEnum.ConstructorFrom<{
|
|
152
|
+
readonly _tag: "React";
|
|
153
|
+
readonly message: MessageRef;
|
|
154
|
+
readonly emoji: string;
|
|
155
|
+
}, "_tag">;
|
|
156
|
+
readonly TypingStart: Data.TaggedEnum.ConstructorFrom<{
|
|
157
|
+
readonly _tag: "TypingStart";
|
|
158
|
+
readonly conversation: ConversationRef;
|
|
159
|
+
}, "_tag">;
|
|
160
|
+
readonly TypingStop: Data.TaggedEnum.ConstructorFrom<{
|
|
161
|
+
readonly _tag: "TypingStop";
|
|
162
|
+
readonly conversation: ConversationRef;
|
|
163
|
+
}, "_tag">;
|
|
164
|
+
};
|
|
165
|
+
type MockMessengerRecorder = {
|
|
166
|
+
readonly calls: ReadonlyArray<Call>;
|
|
167
|
+
};
|
|
168
|
+
type MockMessengerScript = {
|
|
169
|
+
/** Inbound events to replay. Empty by default. */
|
|
170
|
+
readonly events?: Stream.Stream<InboundEvent, MessengerError>;
|
|
171
|
+
readonly limits?: MessengerLimits;
|
|
172
|
+
/**
|
|
173
|
+
* Failures handed back one per `post` / `edit` call, in order; `undefined`
|
|
174
|
+
* entries succeed, and everything past the end succeeds.
|
|
175
|
+
*/
|
|
176
|
+
readonly failures?: ReadonlyArray<MessengerError | undefined>;
|
|
177
|
+
/** Passed through to `streamViaEdits`, which backs `stream`. */
|
|
178
|
+
readonly streamOptions?: StreamViaEditsOptions;
|
|
179
|
+
};
|
|
180
|
+
/** Layer registering the mock against the `Messenger` tag, plus its recorder. */
|
|
181
|
+
declare const layer: (script?: MockMessengerScript) => {
|
|
182
|
+
layer: Layer.Layer<Messenger, never, never>;
|
|
183
|
+
recorder: Effect.Effect<MockMessengerRecorder, never, never>;
|
|
184
|
+
};
|
|
185
|
+
/** Bare service value + recorder. Use with `Effect.provideService`. */
|
|
186
|
+
declare const make: (script?: MockMessengerScript) => {
|
|
187
|
+
service: MessengerService;
|
|
188
|
+
recorder: Effect.Effect<MockMessengerRecorder, never, never>;
|
|
189
|
+
};
|
|
190
|
+
//#endregion
|
|
191
|
+
export { Call, MockMessengerRecorder, MockMessengerScript, layer, make };
|
|
192
|
+
//# sourceMappingURL=MockMessenger.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockMessenger.d.mts","names":[],"sources":["../../src/testing/MockMessenger.ts"],"mappings":";;;;;;;;;;KA4BY,OAAO,KAAK;EACtB;aAAiB,cAAc;aAA0B,SAAS;;EAClE;aAAiB,SAAS;aAAqB,MAAM;;EACrD;aAAkB,SAAS;aAAqB;;EAChD;aAAwB,cAAc;;EACtC;aAAuB,cAAc;;;cAG1B;;;aANe,SAAA;aAA2B,MAAA;;;;;aADtB,cAAA;aAAmC,SAAA;;;;;aAEvC,SAAA;aAA4B;;;;;aACjB,cAAA;;;;;aACD,cAAA;;;;;;;eAHX,SAAA;eAA2B,MAAA;;;eADtB,cAAA;eAAmC,SAAA;;;eAEvC,SAAA;eAA4B;;;eACjB,cAAA;;;eACD,cAAA;;;;;;;;;;;;;;;;;;;;;;;;eAHX,SAAA;eAA2B,MAAA;;;eADtB,cAAA;eAAmC,SAAA;;;eAEvC,SAAA;eAA4B;;;eACjB,cAAA;;;eACD,cAAA;;;;;;;;;;;;;;;;;;;;;;;;;aAHX,SAAA;aAA2B,MAAA;;;;aADtB,cAAA;aAAmC,SAAA;;;;aAEvC,SAAA;aAA4B;;;;aACjB,cAAA;;;;aACD,cAAA;;;KAK3B;WACD,OAAO,cAAc;;KAOpB;;WAED,SAAS,OAAO,OAAO,cAAc;WACrC,SAAS;;;;;WAKT,WAAW,cAAc;;WAEzB,gBAAgB;;;cAoFd,QAAK,SAAY;;;;;cAMjB,OAAI,SAAY"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CurrentConversation, MessageId, Messenger } from "../messenger/Messenger.mjs";
|
|
2
|
+
import { splitForLimit, streamViaEdits } from "../messenger/MessengerAdapter.mjs";
|
|
3
|
+
import { Array, Data, Effect, Layer, Option, Ref, Stream } from "effect";
|
|
4
|
+
//#region src/testing/MockMessenger.ts
|
|
5
|
+
const Call = Data.taggedEnum();
|
|
6
|
+
const defaultLimits = {
|
|
7
|
+
maxText: 4096,
|
|
8
|
+
maxCaption: 1024
|
|
9
|
+
};
|
|
10
|
+
const bodyText = (msg) => msg.body._tag === "Text" ? Option.some(msg.body.text) : Option.none();
|
|
11
|
+
const buildService = (script, record, cursor, minted) => {
|
|
12
|
+
const limits = script.limits ?? defaultLimits;
|
|
13
|
+
const nextFailure = Ref.getAndUpdate(cursor, (n) => n + 1).pipe(Effect.flatMap((i) => Option.match(Option.fromNullishOr((script.failures ?? [])[i]), {
|
|
14
|
+
onNone: () => Effect.void,
|
|
15
|
+
onSome: Effect.fail
|
|
16
|
+
})));
|
|
17
|
+
const mint = Ref.updateAndGet(minted, (n) => n + 1).pipe(Effect.map((n) => MessageId(`m${n}`)));
|
|
18
|
+
const post = (msg) => Effect.gen(function* () {
|
|
19
|
+
const conversation = yield* CurrentConversation;
|
|
20
|
+
yield* record(Call.Post({
|
|
21
|
+
conversation,
|
|
22
|
+
message: msg
|
|
23
|
+
}));
|
|
24
|
+
yield* nextFailure;
|
|
25
|
+
const chunks = Option.match(bodyText(msg), {
|
|
26
|
+
onNone: () => 1,
|
|
27
|
+
onSome: (t) => splitForLimit(t, limits.maxText).length
|
|
28
|
+
});
|
|
29
|
+
return yield* Effect.map(Effect.forEach(Array.range(1, chunks), () => mint), Array.lastNonEmpty);
|
|
30
|
+
});
|
|
31
|
+
const edit = (msg, next) => record(Call.Edit({
|
|
32
|
+
message: msg,
|
|
33
|
+
next
|
|
34
|
+
})).pipe(Effect.andThen(nextFailure));
|
|
35
|
+
return {
|
|
36
|
+
events: script.events ?? Stream.empty,
|
|
37
|
+
post,
|
|
38
|
+
edit,
|
|
39
|
+
react: (msg, emoji) => record(Call.React({
|
|
40
|
+
message: msg,
|
|
41
|
+
emoji
|
|
42
|
+
})),
|
|
43
|
+
typing: Effect.gen(function* () {
|
|
44
|
+
const conversation = yield* CurrentConversation;
|
|
45
|
+
yield* record(Call.TypingStart({ conversation }));
|
|
46
|
+
yield* Effect.addFinalizer(() => record(Call.TypingStop({ conversation })));
|
|
47
|
+
}),
|
|
48
|
+
stream: streamViaEdits({
|
|
49
|
+
post,
|
|
50
|
+
edit,
|
|
51
|
+
limits
|
|
52
|
+
}, script.streamOptions),
|
|
53
|
+
limits
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
const makeUnsafe = (script) => {
|
|
57
|
+
const calls = Ref.makeUnsafe([]);
|
|
58
|
+
return {
|
|
59
|
+
service: buildService(script, (call) => Ref.update(calls, Array.append(call)), Ref.makeUnsafe(0), Ref.makeUnsafe(0)),
|
|
60
|
+
recorder: Ref.get(calls).pipe(Effect.map((c) => ({ calls: c })))
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
/** Layer registering the mock against the `Messenger` tag, plus its recorder. */
|
|
64
|
+
const layer = (script = {}) => {
|
|
65
|
+
const { service, recorder } = makeUnsafe(script);
|
|
66
|
+
return {
|
|
67
|
+
layer: Layer.succeed(Messenger, service),
|
|
68
|
+
recorder
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
/** Bare service value + recorder. Use with `Effect.provideService`. */
|
|
72
|
+
const make = (script = {}) => makeUnsafe(script);
|
|
73
|
+
//#endregion
|
|
74
|
+
export { Call, layer, make };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=MockMessenger.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockMessenger.mjs","names":["Arr"],"sources":["../../src/testing/MockMessenger.ts"],"sourcesContent":["import { Array as Arr, Data, Effect, Layer, Option, Ref, Stream } from \"effect\"\nimport {\n type ConversationRef,\n CurrentConversation,\n type InboundEvent,\n MessageId,\n type MessageRef,\n Messenger,\n type MessengerLimits,\n type MessengerService,\n type Outbound,\n} from \"../messenger/Messenger.js\"\nimport {\n type StreamViaEditsOptions,\n splitForLimit,\n streamViaEdits,\n} from \"../messenger/MessengerAdapter.js\"\nimport * as MessengerError from \"../messenger/MessengerError.js\"\n\n// ---------------------------------------------------------------------------\n// Call log\n// ---------------------------------------------------------------------------\n\n/**\n * One entry per outbound call. `Post` is logged once even when the text was\n * long enough to split, so a caller that rolled over on its own is\n * distinguishable from one that leaned on `post` to do it.\n */\nexport type Call = Data.TaggedEnum<{\n Post: { readonly conversation: ConversationRef; readonly message: Outbound }\n Edit: { readonly message: MessageRef; readonly next: Outbound }\n React: { readonly message: MessageRef; readonly emoji: string }\n TypingStart: { readonly conversation: ConversationRef }\n TypingStop: { readonly conversation: ConversationRef }\n}>\n\nexport const Call = Data.taggedEnum<Call>()\n\nexport type MockMessengerRecorder = {\n readonly calls: ReadonlyArray<Call>\n}\n\n// ---------------------------------------------------------------------------\n// Script\n// ---------------------------------------------------------------------------\n\nexport type MockMessengerScript = {\n /** Inbound events to replay. Empty by default. */\n readonly events?: Stream.Stream<InboundEvent, MessengerError.MessengerError>\n readonly limits?: MessengerLimits\n /**\n * Failures handed back one per `post` / `edit` call, in order; `undefined`\n * entries succeed, and everything past the end succeeds.\n */\n readonly failures?: ReadonlyArray<MessengerError.MessengerError | undefined>\n /** Passed through to `streamViaEdits`, which backs `stream`. */\n readonly streamOptions?: StreamViaEditsOptions\n}\n\nconst defaultLimits: MessengerLimits = { maxText: 4096, maxCaption: 1024 }\n\nconst bodyText = (msg: Outbound): Option.Option<string> =>\n msg.body._tag === \"Text\" ? Option.some(msg.body.text) : Option.none()\n\n// ---------------------------------------------------------------------------\n// Service builder\n// ---------------------------------------------------------------------------\n\nconst buildService = (\n script: MockMessengerScript,\n record: (call: Call) => Effect.Effect<void>,\n cursor: Ref.Ref<number>,\n minted: Ref.Ref<number>,\n): MessengerService => {\n const limits = script.limits ?? defaultLimits\n\n const nextFailure = Ref.getAndUpdate(cursor, (n) => n + 1).pipe(\n Effect.flatMap((i) =>\n Option.match(Option.fromNullishOr((script.failures ?? [])[i]), {\n onNone: () => Effect.void,\n onSome: Effect.fail<MessengerError.MessengerError>,\n }),\n ),\n )\n\n const mint = Ref.updateAndGet(minted, (n) => n + 1).pipe(Effect.map((n) => MessageId(`m${n}`)))\n\n const post: MessengerService[\"post\"] = (msg) =>\n Effect.gen(function* () {\n const conversation = yield* CurrentConversation\n yield* record(Call.Post({ conversation, message: msg }))\n yield* nextFailure\n // One id per chunk, mirroring an adapter that splits at the ceiling.\n const chunks = Option.match(bodyText(msg), {\n onNone: () => 1,\n onSome: (t) => splitForLimit(t, limits.maxText).length,\n })\n return yield* Effect.map(\n Effect.forEach(Arr.range(1, chunks), () => mint),\n Arr.lastNonEmpty,\n )\n })\n\n const edit: MessengerService[\"edit\"] = (msg, next) =>\n record(Call.Edit({ message: msg, next })).pipe(Effect.andThen(nextFailure))\n\n return {\n events: script.events ?? Stream.empty,\n post,\n edit,\n react: (msg, emoji) => record(Call.React({ message: msg, emoji })),\n typing: Effect.gen(function* () {\n const conversation = yield* CurrentConversation\n yield* record(Call.TypingStart({ conversation }))\n yield* Effect.addFinalizer(() => record(Call.TypingStop({ conversation })))\n }),\n stream: streamViaEdits({ post, edit, limits }, script.streamOptions),\n limits,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nconst makeUnsafe = (script: MockMessengerScript) => {\n const calls = Ref.makeUnsafe<ReadonlyArray<Call>>([])\n const service = buildService(\n script,\n (call) => Ref.update(calls, Arr.append(call)),\n Ref.makeUnsafe(0),\n Ref.makeUnsafe(0),\n )\n return {\n service,\n recorder: Ref.get(calls).pipe(Effect.map((c): MockMessengerRecorder => ({ calls: c }))),\n }\n}\n\n/** Layer registering the mock against the `Messenger` tag, plus its recorder. */\nexport const layer = (script: MockMessengerScript = {}) => {\n const { service, recorder } = makeUnsafe(script)\n return { layer: Layer.succeed(Messenger, service), recorder }\n}\n\n/** Bare service value + recorder. Use with `Effect.provideService`. */\nexport const make = (script: MockMessengerScript = {}) => makeUnsafe(script)\n"],"mappings":";;;;AAoCA,MAAa,OAAO,KAAK,WAAiB;AAuB1C,MAAM,gBAAiC;CAAE,SAAS;CAAM,YAAY;AAAK;AAEzE,MAAM,YAAY,QAChB,IAAI,KAAK,SAAS,SAAS,OAAO,KAAK,IAAI,KAAK,IAAI,IAAI,OAAO,KAAK;AAMtE,MAAM,gBACJ,QACA,QACA,QACA,WACqB;CACrB,MAAM,SAAS,OAAO,UAAU;CAEhC,MAAM,cAAc,IAAI,aAAa,SAAS,MAAM,IAAI,CAAC,CAAC,CAAC,KACzD,OAAO,SAAS,MACd,OAAO,MAAM,OAAO,eAAe,OAAO,YAAY,CAAC,EAAA,CAAG,EAAE,GAAG;EAC7D,cAAc,OAAO;EACrB,QAAQ,OAAO;CACjB,CAAC,CACH,CACF;CAEA,MAAM,OAAO,IAAI,aAAa,SAAS,MAAM,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,KAAK,MAAM,UAAU,IAAI,GAAG,CAAC,CAAC;CAE9F,MAAM,QAAkC,QACtC,OAAO,IAAI,aAAa;EACtB,MAAM,eAAe,OAAO;EAC5B,OAAO,OAAO,KAAK,KAAK;GAAE;GAAc,SAAS;EAAI,CAAC,CAAC;EACvD,OAAO;EAEP,MAAM,SAAS,OAAO,MAAM,SAAS,GAAG,GAAG;GACzC,cAAc;GACd,SAAS,MAAM,cAAc,GAAG,OAAO,OAAO,CAAC,CAAC;EAClD,CAAC;EACD,OAAO,OAAO,OAAO,IACnB,OAAO,QAAQA,MAAI,MAAM,GAAG,MAAM,SAAS,IAAI,GAC/CA,MAAI,YACN;CACF,CAAC;CAEH,MAAM,QAAkC,KAAK,SAC3C,OAAO,KAAK,KAAK;EAAE,SAAS;EAAK;CAAK,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,QAAQ,WAAW,CAAC;CAE5E,OAAO;EACL,QAAQ,OAAO,UAAU,OAAO;EAChC;EACA;EACA,QAAQ,KAAK,UAAU,OAAO,KAAK,MAAM;GAAE,SAAS;GAAK;EAAM,CAAC,CAAC;EACjE,QAAQ,OAAO,IAAI,aAAa;GAC9B,MAAM,eAAe,OAAO;GAC5B,OAAO,OAAO,KAAK,YAAY,EAAE,aAAa,CAAC,CAAC;GAChD,OAAO,OAAO,mBAAmB,OAAO,KAAK,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;EAC5E,CAAC;EACD,QAAQ,eAAe;GAAE;GAAM;GAAM;EAAO,GAAG,OAAO,aAAa;EACnE;CACF;AACF;AAMA,MAAM,cAAc,WAAgC;CAClD,MAAM,QAAQ,IAAI,WAAgC,CAAC,CAAC;CAOpD,OAAO;EACL,SAPc,aACd,SACC,SAAS,IAAI,OAAO,OAAOA,MAAI,OAAO,IAAI,CAAC,GAC5C,IAAI,WAAW,CAAC,GAChB,IAAI,WAAW,CAAC,CAGV;EACN,UAAU,IAAI,IAAI,KAAK,CAAC,CAAC,KAAK,OAAO,KAAK,OAA8B,EAAE,OAAO,EAAE,EAAE,CAAC;CACxF;AACF;;AAGA,MAAa,SAAS,SAA8B,CAAC,MAAM;CACzD,MAAM,EAAE,SAAS,aAAa,WAAW,MAAM;CAC/C,OAAO;EAAE,OAAO,MAAM,QAAQ,WAAW,OAAO;EAAG;CAAS;AAC9D;;AAGA,MAAa,QAAQ,SAA8B,CAAC,MAAM,WAAW,MAAM"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CommonSessionRequest, RealtimeEvent, RealtimeInput } from "../domain/Realtime.mjs";
|
|
2
|
+
import { RealtimeSession, RealtimeVideoInput } from "../realtime/RealtimeSession.mjs";
|
|
3
|
+
import { Effect, Layer } from "effect";
|
|
4
|
+
//#region src/testing/MockRealtimeSession.d.ts
|
|
5
|
+
type MockRealtimeSessionRecorder = {
|
|
6
|
+
readonly openCalls: ReadonlyArray<CommonSessionRequest>;
|
|
7
|
+
readonly sent: ReadonlyArray<RealtimeInput>;
|
|
8
|
+
};
|
|
9
|
+
type MockRealtimeSessionScript = {
|
|
10
|
+
/** Emitted as soon as the session opens. */
|
|
11
|
+
readonly initial?: ReadonlyArray<RealtimeEvent>;
|
|
12
|
+
/** The server's answer to each input. */
|
|
13
|
+
readonly onInput?: (input: RealtimeInput) => ReadonlyArray<RealtimeEvent>;
|
|
14
|
+
};
|
|
15
|
+
/** Scripted session that also ships the `RealtimeVideoInput` marker. */
|
|
16
|
+
declare const layer: (script: MockRealtimeSessionScript) => {
|
|
17
|
+
readonly layer: Layer.Layer<RealtimeSession | RealtimeVideoInput>;
|
|
18
|
+
readonly recorder: Effect.Effect<MockRealtimeSessionRecorder>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The same without the marker, to test that a caller sending video against an
|
|
22
|
+
* audio-only provider fails to compile.
|
|
23
|
+
*/
|
|
24
|
+
declare const layerAudioOnly: (script: MockRealtimeSessionScript) => {
|
|
25
|
+
readonly layer: Layer.Layer<RealtimeSession>;
|
|
26
|
+
readonly recorder: Effect.Effect<MockRealtimeSessionRecorder>;
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { MockRealtimeSessionRecorder, MockRealtimeSessionScript, layer, layerAudioOnly };
|
|
30
|
+
//# sourceMappingURL=MockRealtimeSession.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockRealtimeSession.d.mts","names":[],"sources":["../../src/testing/MockRealtimeSession.ts"],"mappings":";;;;KASY;WACD,WAAW,cAAc;WACzB,MAAM,cAAc;;KAGnB;;WAED,UAAU,cAAc;;WAExB,WAAW,OAAO,kBAAkB,cAAc;;;cAgDhD,QAAK,QACR;WAEC,OAAO,MAAM,MAAM,kBAAkB;WACrC,UAAU,OAAO,OAAO;;;;;;cAatB,iBAAc,QACjB;WAEC,OAAO,MAAM,MAAM;WACnB,UAAU,OAAO,OAAO"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { RealtimeSession, RealtimeVideoInput } from "../realtime/RealtimeSession.mjs";
|
|
2
|
+
import { Effect, Layer, Queue, Ref, Stream } from "effect";
|
|
3
|
+
//#region src/testing/MockRealtimeSession.ts
|
|
4
|
+
const makeService = (script, record) => ({ open: (request) => Effect.gen(function* () {
|
|
5
|
+
yield* record.open(request);
|
|
6
|
+
const queue = yield* Queue.unbounded();
|
|
7
|
+
yield* Effect.addFinalizer(() => Queue.end(queue));
|
|
8
|
+
yield* Queue.offerAll(queue, script.initial ?? []);
|
|
9
|
+
return {
|
|
10
|
+
send: (input) => Effect.andThen(record.send(input), Queue.offerAll(queue, script.onInput?.(input) ?? [])).pipe(Effect.asVoid),
|
|
11
|
+
events: Stream.fromQueue(queue)
|
|
12
|
+
};
|
|
13
|
+
}) });
|
|
14
|
+
const build = (script) => {
|
|
15
|
+
const openCalls = Ref.makeUnsafe([]);
|
|
16
|
+
const sent = Ref.makeUnsafe([]);
|
|
17
|
+
return {
|
|
18
|
+
sessionLayer: Layer.succeed(RealtimeSession, makeService(script, {
|
|
19
|
+
open: (request) => Ref.update(openCalls, (xs) => [...xs, request]),
|
|
20
|
+
send: (input) => Ref.update(sent, (xs) => [...xs, input])
|
|
21
|
+
})),
|
|
22
|
+
recorder: Effect.gen(function* () {
|
|
23
|
+
return {
|
|
24
|
+
openCalls: yield* Ref.get(openCalls),
|
|
25
|
+
sent: yield* Ref.get(sent)
|
|
26
|
+
};
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/** Scripted session that also ships the `RealtimeVideoInput` marker. */
|
|
31
|
+
const layer = (script) => {
|
|
32
|
+
const { sessionLayer, recorder } = build(script);
|
|
33
|
+
return {
|
|
34
|
+
layer: Layer.merge(sessionLayer, Layer.succeed(RealtimeVideoInput, void 0)),
|
|
35
|
+
recorder
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The same without the marker, to test that a caller sending video against an
|
|
40
|
+
* audio-only provider fails to compile.
|
|
41
|
+
*/
|
|
42
|
+
const layerAudioOnly = (script) => {
|
|
43
|
+
const { sessionLayer, recorder } = build(script);
|
|
44
|
+
return {
|
|
45
|
+
layer: sessionLayer,
|
|
46
|
+
recorder
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
50
|
+
export { layer, layerAudioOnly };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=MockRealtimeSession.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockRealtimeSession.mjs","names":[],"sources":["../../src/testing/MockRealtimeSession.ts"],"sourcesContent":["import { Cause, Effect, Layer, Queue, Ref, Stream } from \"effect\"\nimport type { CommonSessionRequest, RealtimeEvent, RealtimeInput } from \"../domain/Realtime.js\"\nimport {\n RealtimeSession,\n RealtimeVideoInput,\n type RealtimeSessionHandle,\n type RealtimeSessionService,\n} from \"../realtime/RealtimeSession.js\"\n\nexport type MockRealtimeSessionRecorder = {\n readonly openCalls: ReadonlyArray<CommonSessionRequest>\n readonly sent: ReadonlyArray<RealtimeInput>\n}\n\nexport type MockRealtimeSessionScript = {\n /** Emitted as soon as the session opens. */\n readonly initial?: ReadonlyArray<RealtimeEvent>\n /** The server's answer to each input. */\n readonly onInput?: (input: RealtimeInput) => ReadonlyArray<RealtimeEvent>\n}\n\ntype Recorders = {\n readonly open: (request: CommonSessionRequest) => Effect.Effect<void>\n readonly send: (input: RealtimeInput) => Effect.Effect<void>\n}\n\nconst makeService = (\n script: MockRealtimeSessionScript,\n record: Recorders,\n): RealtimeSessionService => ({\n open: (request) =>\n Effect.gen(function* () {\n yield* record.open(request)\n const queue = yield* Queue.unbounded<RealtimeEvent, Cause.Done>()\n // Closing the scope ends `events`, leaving anything already queued to drain.\n yield* Effect.addFinalizer(() => Queue.end(queue))\n yield* Queue.offerAll(queue, script.initial ?? [])\n const handle: RealtimeSessionHandle = {\n send: (input) =>\n Effect.andThen(\n record.send(input),\n Queue.offerAll(queue, script.onInput?.(input) ?? []),\n ).pipe(Effect.asVoid),\n events: Stream.fromQueue(queue),\n }\n return handle\n }),\n})\n\nconst build = (script: MockRealtimeSessionScript) => {\n const openCalls = Ref.makeUnsafe<ReadonlyArray<CommonSessionRequest>>([])\n const sent = Ref.makeUnsafe<ReadonlyArray<RealtimeInput>>([])\n const sessionLayer = Layer.succeed(\n RealtimeSession,\n makeService(script, {\n open: (request) => Ref.update(openCalls, (xs) => [...xs, request]),\n send: (input) => Ref.update(sent, (xs) => [...xs, input]),\n }),\n )\n const recorder: Effect.Effect<MockRealtimeSessionRecorder> = Effect.gen(function* () {\n return { openCalls: yield* Ref.get(openCalls), sent: yield* Ref.get(sent) }\n })\n return { sessionLayer, recorder }\n}\n\n/** Scripted session that also ships the `RealtimeVideoInput` marker. */\nexport const layer = (\n script: MockRealtimeSessionScript,\n): {\n readonly layer: Layer.Layer<RealtimeSession | RealtimeVideoInput>\n readonly recorder: Effect.Effect<MockRealtimeSessionRecorder>\n} => {\n const { sessionLayer, recorder } = build(script)\n return {\n layer: Layer.merge(sessionLayer, Layer.succeed(RealtimeVideoInput, undefined)),\n recorder,\n }\n}\n\n/**\n * The same without the marker, to test that a caller sending video against an\n * audio-only provider fails to compile.\n */\nexport const layerAudioOnly = (\n script: MockRealtimeSessionScript,\n): {\n readonly layer: Layer.Layer<RealtimeSession>\n readonly recorder: Effect.Effect<MockRealtimeSessionRecorder>\n} => {\n const { sessionLayer, recorder } = build(script)\n return { layer: sessionLayer, recorder }\n}\n"],"mappings":";;;AA0BA,MAAM,eACJ,QACA,YAC4B,EAC5B,OAAO,YACL,OAAO,IAAI,aAAa;CACtB,OAAO,OAAO,KAAK,OAAO;CAC1B,MAAM,QAAQ,OAAO,MAAM,UAAqC;CAEhE,OAAO,OAAO,mBAAmB,MAAM,IAAI,KAAK,CAAC;CACjD,OAAO,MAAM,SAAS,OAAO,OAAO,WAAW,CAAC,CAAC;CASjD,OAAO;EAPL,OAAO,UACL,OAAO,QACL,OAAO,KAAK,KAAK,GACjB,MAAM,SAAS,OAAO,OAAO,UAAU,KAAK,KAAK,CAAC,CAAC,CACrD,CAAC,CAAC,KAAK,OAAO,MAAM;EACtB,QAAQ,OAAO,UAAU,KAAK;CAEpB;AACd,CAAC,EACL;AAEA,MAAM,SAAS,WAAsC;CACnD,MAAM,YAAY,IAAI,WAAgD,CAAC,CAAC;CACxE,MAAM,OAAO,IAAI,WAAyC,CAAC,CAAC;CAW5D,OAAO;EAAE,cAVY,MAAM,QACzB,iBACA,YAAY,QAAQ;GAClB,OAAO,YAAY,IAAI,OAAO,YAAY,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC;GACjE,OAAO,UAAU,IAAI,OAAO,OAAO,OAAO,CAAC,GAAG,IAAI,KAAK,CAAC;EAC1D,CAAC,CAKiB;EAAG,UAHsC,OAAO,IAAI,aAAa;GACnF,OAAO;IAAE,WAAW,OAAO,IAAI,IAAI,SAAS;IAAG,MAAM,OAAO,IAAI,IAAI,IAAI;GAAE;EAC5E,CAC8B;CAAE;AAClC;;AAGA,MAAa,SACX,WAIG;CACH,MAAM,EAAE,cAAc,aAAa,MAAM,MAAM;CAC/C,OAAO;EACL,OAAO,MAAM,MAAM,cAAc,MAAM,QAAQ,oBAAoB,KAAA,CAAS,CAAC;EAC7E;CACF;AACF;;;;;AAMA,MAAa,kBACX,WAIG;CACH,MAAM,EAAE,cAAc,aAAa,MAAM,MAAM;CAC/C,OAAO;EAAE,OAAO;EAAc;CAAS;AACzC"}
|
package/dist/tool/Toolkit.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { S as wrap, _ as fromArray, a as ToolMap, b as namespace, c as ToolkitR, d as appendToolResults, f as collectResults, g as descriptors, h as describeFailures, i as NoDuplicateNames, m as continueWithResults, n as ExecuteOptions, o as Toolkit, p as compose, r as Middleware, s as ToolkitE, t as DuplicateToolName, u as UniqueTools, v as make, x as run, y as makeNamespaced } from "../Toolkit-
|
|
1
|
+
import { S as wrap, _ as fromArray, a as ToolMap, b as namespace, c as ToolkitR, d as appendToolResults, f as collectResults, g as descriptors, h as describeFailures, i as NoDuplicateNames, m as continueWithResults, n as ExecuteOptions, o as Toolkit, p as compose, r as Middleware, s as ToolkitE, t as DuplicateToolName, u as UniqueTools, v as make, x as run, y as makeNamespaced } from "../Toolkit-tlL0F2YR.mjs";
|
|
2
2
|
export { DuplicateToolName, ExecuteOptions, Middleware, NoDuplicateNames, ToolMap, Toolkit, ToolkitE, ToolkitR, UniqueTools, appendToolResults, collectResults, compose, continueWithResults, describeFailures, descriptors, fromArray, make, makeNamespaced, namespace, run, wrap };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as AiError } from "../AiError-
|
|
1
|
+
import { t as AiError } from "../AiError-DvhLIbmd.mjs";
|
|
2
2
|
import { a as AudioSource, r as AudioFormat } from "../Audio-_nGR4V0X.mjs";
|
|
3
3
|
import { TranscriptEvent, TranscriptResult } from "../domain/Transcript.mjs";
|
|
4
4
|
import { Context, Effect, Stream } from "effect";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as AiError } from "../AiError-
|
|
1
|
+
import { t as AiError } from "../AiError-DvhLIbmd.mjs";
|
|
2
2
|
import { Context, Duration, Effect } from "effect";
|
|
3
3
|
declare namespace WebRead_d_exports {
|
|
4
4
|
export { CommonReadRequest, ReadFormat, ReadResponse, WebRead, WebReadService, read };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { l as Tool } from "../Tool-BF78QWtn.mjs";
|
|
2
|
-
import { t as AiError } from "../AiError-
|
|
2
|
+
import { t as AiError } from "../AiError-DvhLIbmd.mjs";
|
|
3
3
|
import { ReadFormat, ReadResponse, WebRead } from "./WebRead.mjs";
|
|
4
4
|
import { Schema } from "effect";
|
|
5
5
|
declare namespace WebReadTool_d_exports {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as AiError } from "../AiError-
|
|
1
|
+
import { t as AiError } from "../AiError-DvhLIbmd.mjs";
|
|
2
2
|
import { Context, DateTime, Effect, Schema } from "effect";
|
|
3
3
|
declare namespace WebSearch_d_exports {
|
|
4
4
|
export { CommonSearchRequest, SearchRecency, SearchResponse, SearchResult, WebSearch, WebSearchService, search };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { l as Tool } from "../Tool-BF78QWtn.mjs";
|
|
2
|
-
import { t as AiError } from "../AiError-
|
|
2
|
+
import { t as AiError } from "../AiError-DvhLIbmd.mjs";
|
|
3
3
|
import { SearchResult, WebSearch } from "./WebSearch.mjs";
|
|
4
4
|
import { Schema } from "effect";
|
|
5
5
|
declare namespace WebSearchTool_d_exports {
|