@effect-uai/core 0.15.0 → 0.17.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.
Files changed (114) hide show
  1. package/README.md +30 -16
  2. package/dist/{AiError-eLV9t1lU.d.mts → AiError-DvhLIbmd.d.mts} +15 -4
  3. package/dist/{AiError-eLV9t1lU.d.mts.map → AiError-DvhLIbmd.d.mts.map} +1 -1
  4. package/dist/{Loop-B54AB1ME.d.mts → Loop-ChpaO-ya.d.mts} +2 -2
  5. package/dist/{Loop-B54AB1ME.d.mts.map → Loop-ChpaO-ya.d.mts.map} +1 -1
  6. package/dist/{Toolkit-DAjJ9Tvl.d.mts → Toolkit-tlL0F2YR.d.mts} +2 -2
  7. package/dist/{Toolkit-DAjJ9Tvl.d.mts.map → Toolkit-tlL0F2YR.d.mts.map} +1 -1
  8. package/dist/chunker/Chunker.d.mts +1 -1
  9. package/dist/decision-model/Decision.d.mts +155 -0
  10. package/dist/decision-model/Decision.d.mts.map +1 -0
  11. package/dist/decision-model/Decision.mjs +118 -0
  12. package/dist/decision-model/Decision.mjs.map +1 -0
  13. package/dist/decision-model/Decision.test.mjs +164 -0
  14. package/dist/decision-model/Decision.test.mjs.map +1 -0
  15. package/dist/decision-model/DecisionModel.d.mts +45 -0
  16. package/dist/decision-model/DecisionModel.d.mts.map +1 -0
  17. package/dist/decision-model/DecisionModel.mjs +25 -0
  18. package/dist/decision-model/DecisionModel.mjs.map +1 -0
  19. package/dist/domain/AiError.d.mts +2 -2
  20. package/dist/domain/AiError.mjs +9 -2
  21. package/dist/domain/AiError.mjs.map +1 -1
  22. package/dist/domain/Realtime.d.mts +616 -0
  23. package/dist/domain/Realtime.d.mts.map +1 -0
  24. package/dist/domain/Realtime.mjs +21 -0
  25. package/dist/domain/Realtime.mjs.map +1 -0
  26. package/dist/domain/Transcript.d.mts +17 -3
  27. package/dist/domain/Transcript.d.mts.map +1 -1
  28. package/dist/domain/Transcript.mjs +30 -1
  29. package/dist/domain/Transcript.mjs.map +1 -1
  30. package/dist/domain/Transcript.test.d.mts +1 -0
  31. package/dist/domain/Transcript.test.mjs +110 -0
  32. package/dist/domain/Transcript.test.mjs.map +1 -0
  33. package/dist/embedding-model/EmbeddingModel.d.mts +1 -1
  34. package/dist/image-generator/ImageGenerator.d.mts +1 -1
  35. package/dist/index.d.mts +11 -5
  36. package/dist/index.mjs +9 -3
  37. package/dist/job/Job.d.mts +1 -1
  38. package/dist/language-model/LanguageModel.d.mts +2 -2
  39. package/dist/loop/Loop.d.mts +1 -1
  40. package/dist/math/Probability.d.mts +21 -0
  41. package/dist/math/Probability.d.mts.map +1 -0
  42. package/dist/math/Probability.mjs +31 -0
  43. package/dist/math/Probability.mjs.map +1 -0
  44. package/dist/math/Probability.test.d.mts +1 -0
  45. package/dist/math/Probability.test.mjs +95 -0
  46. package/dist/math/Probability.test.mjs.map +1 -0
  47. package/dist/music-generator/MusicGenerator.d.mts +1 -1
  48. package/dist/realtime/RealtimeSession.d.mts +50 -0
  49. package/dist/realtime/RealtimeSession.d.mts.map +1 -0
  50. package/dist/realtime/RealtimeSession.mjs +45 -0
  51. package/dist/realtime/RealtimeSession.mjs.map +1 -0
  52. package/dist/realtime/RealtimeSession.test.d.mts +1 -0
  53. package/dist/realtime/RealtimeSession.test.mjs +95 -0
  54. package/dist/realtime/RealtimeSession.test.mjs.map +1 -0
  55. package/dist/reranker/Reranker.d.mts +1 -1
  56. package/dist/research/DeepResearch.d.mts +1 -1
  57. package/dist/retry/Retry.d.mts +1 -1
  58. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +1 -1
  59. package/dist/streaming/Settle.d.mts +45 -0
  60. package/dist/streaming/Settle.d.mts.map +1 -0
  61. package/dist/streaming/Settle.mjs +95 -0
  62. package/dist/streaming/Settle.mjs.map +1 -0
  63. package/dist/streaming/Settle.test.d.mts +1 -0
  64. package/dist/streaming/Settle.test.mjs +252 -0
  65. package/dist/streaming/Settle.test.mjs.map +1 -0
  66. package/dist/streaming/WebSocketSession.d.mts +36 -0
  67. package/dist/streaming/WebSocketSession.d.mts.map +1 -0
  68. package/dist/streaming/WebSocketSession.mjs +89 -0
  69. package/dist/streaming/WebSocketSession.mjs.map +1 -0
  70. package/dist/streaming/WebSocketSession.test.d.mts +1 -0
  71. package/dist/streaming/WebSocketSession.test.mjs +51 -0
  72. package/dist/streaming/WebSocketSession.test.mjs.map +1 -0
  73. package/dist/testing/FakeWebSocket.d.mts +25 -0
  74. package/dist/testing/FakeWebSocket.d.mts.map +1 -0
  75. package/dist/testing/FakeWebSocket.mjs +79 -0
  76. package/dist/testing/FakeWebSocket.mjs.map +1 -0
  77. package/dist/testing/MockRealtimeSession.d.mts +30 -0
  78. package/dist/testing/MockRealtimeSession.d.mts.map +1 -0
  79. package/dist/testing/MockRealtimeSession.mjs +52 -0
  80. package/dist/testing/MockRealtimeSession.mjs.map +1 -0
  81. package/dist/tool/Toolkit.d.mts +1 -1
  82. package/dist/transcriber/Transcriber.d.mts +1 -1
  83. package/dist/web-read/WebRead.d.mts +1 -1
  84. package/dist/web-read/WebReadTool.d.mts +1 -1
  85. package/dist/web-search/WebSearch.d.mts +1 -1
  86. package/dist/web-search/WebSearchTool.d.mts +1 -1
  87. package/package.json +36 -4
  88. package/src/decision-model/Decision.test.ts +146 -0
  89. package/src/decision-model/Decision.ts +273 -0
  90. package/src/decision-model/DecisionModel.ts +53 -0
  91. package/src/domain/AiError.ts +11 -0
  92. package/src/domain/Realtime.ts +101 -0
  93. package/src/domain/Transcript.test.ts +98 -0
  94. package/src/domain/Transcript.ts +53 -1
  95. package/src/index.ts +7 -1
  96. package/src/math/Probability.test.ts +61 -0
  97. package/src/math/Probability.ts +44 -0
  98. package/src/realtime/RealtimeSession.test.ts +100 -0
  99. package/src/realtime/RealtimeSession.ts +77 -0
  100. package/src/streaming/Settle.test.ts +344 -0
  101. package/src/streaming/Settle.ts +196 -0
  102. package/src/streaming/WebSocketSession.test.ts +52 -0
  103. package/src/streaming/WebSocketSession.ts +132 -0
  104. package/src/testing/FakeWebSocket.ts +120 -0
  105. package/src/testing/MockRealtimeSession.ts +92 -0
  106. package/dist/inbox/Inbox.d.mts +0 -16
  107. package/dist/inbox/Inbox.d.mts.map +0 -1
  108. package/dist/inbox/Inbox.mjs +0 -28
  109. package/dist/inbox/Inbox.mjs.map +0 -1
  110. package/dist/inbox/Inbox.test.mjs +0 -42
  111. package/dist/inbox/Inbox.test.mjs.map +0 -1
  112. package/src/inbox/Inbox.test.ts +0 -52
  113. package/src/inbox/Inbox.ts +0 -33
  114. /package/dist/{inbox/Inbox.test.d.mts → decision-model/Decision.test.d.mts} +0 -0
package/README.md CHANGED
@@ -6,22 +6,36 @@ The core thesis: **the user owns the loop**. State is a plain record;
6
6
  `Loop` events control iteration; the body is a `Stream`. There is no
7
7
  agent runtime, no orchestrator, no provider lock-in.
8
8
 
9
- This package exposes:
10
-
11
- - **Domain types** - `Items`, `Turn`, `AiError`
12
- - **Provider contract** - `LanguageModel`
13
- - **Loop primitive** - `Loop` (`loop`, `loopWithState`, `value`, `next`, `stop`, `nextAfter`, `onTurnComplete`)
14
- - **Tools** - `Tool`, `Toolkit`, `Outcome`, `ToolEvent`, `Resolvers`, `HistoryCheck`
15
- - **Streaming codecs** - `SSE`, `JSONL`, `Lines`
16
- - **Structured output** - `StructuredFormat`
17
- - **Observability** - `Metrics`
18
- - **Testing** - `testing/MockProvider`
19
-
20
- No provider deps. Pair with one of:
21
-
22
- - [`@effect-uai/responses`](https://www.npmjs.com/package/@effect-uai/responses) - OpenAI Responses API
23
- - [`@effect-uai/anthropic`](https://www.npmjs.com/package/@effect-uai/anthropic) - Anthropic Messages
24
- - [`@effect-uai/google`](https://www.npmjs.com/package/@effect-uai/google) - Google Gemini
9
+ ## Capabilities
10
+
11
+ Each is a service tag that provider packages implement, so switching
12
+ provider is swapping a Layer:
13
+
14
+ - **Language models** - `LanguageModel`, with `Loop`, `Items`, `Turn`,
15
+ `Tool`, `Toolkit` and `StructuredFormat`
16
+ - **Speech** - `Transcriber` (file and streaming STT),
17
+ `SpeechSynthesizer` (finished and incremental TTS)
18
+ - **Realtime** - `RealtimeSession`: a live conversation with a realtime
19
+ model, voice both ways and camera frames where the provider takes them
20
+ - **Embeddings and retrieval** - `EmbeddingModel`, `Reranker`,
21
+ `Chunker`, `Tokenizer`
22
+ - **Images and music** - `ImageGenerator`, `MusicGenerator`
23
+ - **Web** - `WebSearch`, `WebRead`, `DeepResearch`, `Browser`
24
+ - **Runtime** - `Sandbox` for running model-written code
25
+ - **Messaging** - `Messenger`: the agent as a Telegram, Discord or
26
+ Slack bot
27
+
28
+ Plus the pieces underneath: `AiError` (typed failures across every
29
+ capability), streaming codecs (`SSE`, `JSONL`, `Lines`), `Settle` for
30
+ acting on a pause in a stream of arrivals, `Metrics`, and a mock layer
31
+ per capability under `testing/`.
32
+
33
+ No provider deps. Pair with one or more provider packages:
34
+ [`@effect-uai/openai`](https://www.npmjs.com/package/@effect-uai/openai),
35
+ [`@effect-uai/anthropic`](https://www.npmjs.com/package/@effect-uai/anthropic),
36
+ [`@effect-uai/google`](https://www.npmjs.com/package/@effect-uai/google),
37
+ [`@effect-uai/mistral`](https://www.npmjs.com/package/@effect-uai/mistral)
38
+ and [the rest](https://effect-uai.betalyra.com/providers/).
25
39
 
26
40
  ## Install
27
41
 
@@ -1,6 +1,6 @@
1
1
  import { Duration } from "effect";
2
2
  declare namespace AiError_d_exports {
3
- export { AiError, AuthFailed, AuthSubtype, Cancelled, ContentFiltered, ContextLengthExceeded, GenerationFailed, IncompleteTurn, InvalidRequest, RateLimited, Scope$1 as Scope, Timeout, Unavailable, Unsupported, describe };
3
+ export { AiError, AuthFailed, AuthSubtype, Cancelled, ContentFiltered, ContextLengthExceeded, GenerationFailed, IncompleteTurn, InvalidRequest, RateLimited, Scope$1 as Scope, SessionExpired, Timeout, Unavailable, Unsupported, describe };
4
4
  }
5
5
  type Scope$1 = "rpm" | "tpm" | "rpd" | "tpd";
6
6
  declare const RateLimited_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
@@ -122,12 +122,23 @@ declare class Unsupported extends Unsupported_base<{
122
122
  capability: string;
123
123
  reason?: string;
124
124
  }> {}
125
- type AiError = RateLimited | Unavailable | Timeout | ContentFiltered | ContextLengthExceeded | InvalidRequest | AuthFailed | Cancelled | IncompleteTurn | GenerationFailed | Unsupported;
125
+ declare const SessionExpired_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
126
+ readonly _tag: "SessionExpired";
127
+ } & Readonly<A>;
128
+ /**
129
+ * A long-lived session hit the provider's lifetime cap. Distinct from
130
+ * `Unavailable`: reconnecting works, retrying the same session does not.
131
+ */
132
+ declare class SessionExpired extends SessionExpired_base<{
133
+ provider: string;
134
+ raw?: unknown;
135
+ }> {}
136
+ type AiError = RateLimited | Unavailable | Timeout | ContentFiltered | ContextLengthExceeded | InvalidRequest | AuthFailed | Cancelled | IncompleteTurn | GenerationFailed | Unsupported | SessionExpired;
126
137
  /**
127
138
  * Short human-readable description of an error, for logs and model-facing
128
139
  * failure messages. Prose, not a contract; branch on `_tag` instead.
129
140
  */
130
141
  declare const describe: (e: AiError) => string;
131
142
  //#endregion
132
- export { Cancelled as a, GenerationFailed as c, RateLimited as d, Scope$1 as f, describe as g, Unsupported as h, AuthSubtype as i, IncompleteTurn as l, Unavailable as m, AiError_d_exports as n, ContentFiltered as o, Timeout as p, AuthFailed as r, ContextLengthExceeded as s, AiError as t, InvalidRequest as u };
133
- //# sourceMappingURL=AiError-eLV9t1lU.d.mts.map
143
+ export { describe as _, Cancelled as a, GenerationFailed as c, RateLimited as d, Scope$1 as f, Unsupported as g, Unavailable as h, AuthSubtype as i, IncompleteTurn as l, Timeout as m, AiError_d_exports as n, ContentFiltered as o, SessionExpired as p, AuthFailed as r, ContextLengthExceeded as s, AiError as t, InvalidRequest as u };
144
+ //# sourceMappingURL=AiError-DvhLIbmd.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiError-eLV9t1lU.d.mts","names":[],"sources":["../src/domain/AiError.ts"],"mappings":";;;;KAEY;;;;cAEC,oBAAoB;EAC/B;EACA,aAAa,SAAS;EACtB,QAAQ;EACR;EACA;;;;;cAGW,oBAAoB;EAC/B;EACA,aAAa,SAAS;EACtB;EACA;EACA;;;;;cAGW,gBAAgB;EAC3B;EACA;EACA;;;;;cAGW,wBAAwB;EACnC;EACA;EACA;EACA;;;;;cAGW,8BAA8B;EACzC;EACA;EACA;EACA;;;;;cAGW,uBAAuB;EAClC;EACA;EACA;EACA;;KAGU;;;;cAEC,mBAAmB;EAC9B;EACA,SAAS;EACT;;;;;cAGW,kBAAkB;EAC7B;;;;;;;;;;;cASW,yBAAyB;EACpC;EACA;EACA;EACA;EACA;;;;;;;;;;cAQW,uBAAuB;EAClC;;;;;;;;;;;;;;;;;;;cAiBW,oBAAoB;EAC/B;EACA;EACA;;KAGU,UACR,cACA,cACA,UACA,kBACA,wBACA,iBACA,aACA,YACA,iBACA,mBACA;;;;;cASS,WAAW,GAAG"}
1
+ {"version":3,"file":"AiError-DvhLIbmd.d.mts","names":[],"sources":["../src/domain/AiError.ts"],"mappings":";;;;KAEY;;;;cAEC,oBAAoB;EAC/B;EACA,aAAa,SAAS;EACtB,QAAQ;EACR;EACA;;;;;cAGW,oBAAoB;EAC/B;EACA,aAAa,SAAS;EACtB;EACA;EACA;;;;;cAGW,gBAAgB;EAC3B;EACA;EACA;;;;;cAGW,wBAAwB;EACnC;EACA;EACA;EACA;;;;;cAGW,8BAA8B;EACzC;EACA;EACA;EACA;;;;;cAGW,uBAAuB;EAClC;EACA;EACA;EACA;;KAGU;;;;cAEC,mBAAmB;EAC9B;EACA,SAAS;EACT;;;;;cAGW,kBAAkB;EAC7B;;;;;;;;;;;cASW,yBAAyB;EACpC;EACA;EACA;EACA;EACA;;;;;;;;;;cAQW,uBAAuB;EAClC;;;;;;;;;;;;;;;;;;;cAiBW,oBAAoB;EAC/B;EACA;EACA;;;;;;;;;cAOW,uBAAuB;EAClC;EACA;;KAGU,UACR,cACA,cACA,UACA,kBACA,wBACA,iBACA,aACA,YACA,iBACA,mBACA,cACA;;;;;cASS,WAAW,GAAG"}
@@ -1,4 +1,4 @@
1
- import { l as IncompleteTurn } from "./AiError-eLV9t1lU.mjs";
1
+ import { l as IncompleteTurn } from "./AiError-DvhLIbmd.mjs";
2
2
  import { Turn, TurnEvent } from "./domain/Turn.mjs";
3
3
  import { Data, Effect, Stream, SubscriptionRef } from "effect";
4
4
  declare namespace Loop_d_exports {
@@ -168,4 +168,4 @@ declare const loopWithState: <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>
168
168
  }>;
169
169
  //#endregion
170
170
  export { StopWith as a, loopOver as c, onTurnComplete as d, stop as f, Stop as i, loopWithState as l, Next as n, Value as o, value as p, Step as r, loop as s, Loop_d_exports as t, next as u };
171
- //# sourceMappingURL=Loop-B54AB1ME.d.mts.map
171
+ //# sourceMappingURL=Loop-ChpaO-ya.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Loop-B54AB1ME.d.mts","names":[],"sources":["../src/loop/Loop.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;KAwDY,KAAK,GAAG,KAAK,KAAK;EAC5B;aAAkB,OAAO;;EACzB;aAAiB,OAAO;;EACxB;EACA;aAAqB,OAAO;;;;;;;;;;;cAiBf,QAAK,GAAA,GAAA;;;;;GAAE,OAAI,GAAA,GAAA;;;;;GAAE,OAAI,GAAA,GAAA;;GAAE,WAAQ,GAAA,GAAA;;;;;;;cAG7B,QAAS,GAAC,GAAK,MAAI,KAAK;;;;;;cAOxB,OAAQ,GAAC,OAAS,MAAI,OAAO,OAAO,YAAY;;;;;;;;cAUhD,OAAQ,WAAS,QAAU,MAAI,OAAO,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;;;;KA8BjE,iBAAiB,GAAG,GAAG,IAAI,MAC5B,OAAO,OAAO,KAAK,GAAG,IAAI,IAAI,MAC9B,OAAO,OAAO,OAAO,OAAO,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI;cAE5C;GACV,GAAG,GAAG,YAAY,YACjB,OAAO,MAAM,SAAS,iBAAiB,GAAG,GAAG,IAAI,OAC/C,GAAG,GACL,QAAQ,OAAO,OAAO,WAAW,GAAG,OACjC,OAAO,OAAO,KAAK,YAAY,GAAG,IAAI,IAAI,KAAK,gBAAgB,IAAI;GACvE,GAAG,GAAG,GAAG,GAAG,YAAY,YACvB,QAAQ,OAAO,OAAO,WAAW,GAAG,IACpC,OAAO,MAAM,SAAS,iBAAiB,GAAG,GAAG,IAAI,MAChD,OAAO,OAAO,KAAK,YAAY,GAAG,IAAI,IAAI,KAAK,gBAAgB,IAAI;;KA2EnE,SAAS,GAAG,GAAG,GAAG,MACrB,OAAO,MACJ,OAAO,OAAO,KAAK,GAAG,IAAI,GAAG,KAAK,OAAO,OAAO,OAAO,OAAO,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG;;;;;;;;;;cAW5E;GACV,GAAG,GAAG,GAAG,GAAG,MAAM,SAAS,GAAG,GAAG,GAAG,MAAM,SAAS,MAAM,OAAO,OAAO,GAAG,GAAG;GAC7E,GAAG,GAAG,GAAG,GAAG,SAAS,GAAG,MAAM,SAAS,GAAG,GAAG,GAAG,KAAK,OAAO,OAAO,GAAG,GAAG;;KA2FvE,aAAa,GAAG,GAAG,GAAG,GAAG,MAC5B,OAAO,GACP,OAAO,MACJ,OAAO,OAAO,KAAK,GAAG,IAAI,GAAG,KAAK,OAAO,OAAO,OAAO,OAAO,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;cAyB5E;GACV,GAAG,GAAG,GAAG,GAAG,GACX,SAAS,GACT,MAAM,aAAa,GAAG,GAAG,GAAG,GAAG,MAC7B,IAAI,IAAI,OAAO,OAAO,OAAO,GAAG,IAAI,QAAQ,OAAO,OAAO,GAAG,IAAI,IAAI,IAAI;GAC5E,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,IAClB,OAAO,OAAO,OAAO,GAAG,IAAI,KAC5B,SAAS,GACT,MAAM,aAAa,GAAG,GAAG,GAAG,GAAG,KAC9B,OAAO,OAAO,GAAG,IAAI,IAAI,IAAI;;;;;;;;;;;;;;;;;;;;;;cAmErB,gBAAiB,GAAG,GAAG,GAAG,GAAC,SAC7B,GAAC,MACJ,SAAS,GAAG,GAAG,GAAG,OACvB,OAAO;WACC,QAAQ,OAAO,OAAO,GAAG,GAAG;WAC5B,OAAO,gBAAgB,gBAAgB"}
1
+ {"version":3,"file":"Loop-ChpaO-ya.d.mts","names":[],"sources":["../src/loop/Loop.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;KAwDY,KAAK,GAAG,KAAK,KAAK;EAC5B;aAAkB,OAAO;;EACzB;aAAiB,OAAO;;EACxB;EACA;aAAqB,OAAO;;;;;;;;;;;cAiBf,QAAK,GAAA,GAAA;;;;;GAAE,OAAI,GAAA,GAAA;;;;;GAAE,OAAI,GAAA,GAAA;;GAAE,WAAQ,GAAA,GAAA;;;;;;;cAG7B,QAAS,GAAC,GAAK,MAAI,KAAK;;;;;;cAOxB,OAAQ,GAAC,OAAS,MAAI,OAAO,OAAO,YAAY;;;;;;;;cAUhD,OAAQ,WAAS,QAAU,MAAI,OAAO,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;;;;KA8BjE,iBAAiB,GAAG,GAAG,IAAI,MAC5B,OAAO,OAAO,KAAK,GAAG,IAAI,IAAI,MAC9B,OAAO,OAAO,OAAO,OAAO,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI;cAE5C;GACV,GAAG,GAAG,YAAY,YACjB,OAAO,MAAM,SAAS,iBAAiB,GAAG,GAAG,IAAI,OAC/C,GAAG,GACL,QAAQ,OAAO,OAAO,WAAW,GAAG,OACjC,OAAO,OAAO,KAAK,YAAY,GAAG,IAAI,IAAI,KAAK,gBAAgB,IAAI;GACvE,GAAG,GAAG,GAAG,GAAG,YAAY,YACvB,QAAQ,OAAO,OAAO,WAAW,GAAG,IACpC,OAAO,MAAM,SAAS,iBAAiB,GAAG,GAAG,IAAI,MAChD,OAAO,OAAO,KAAK,YAAY,GAAG,IAAI,IAAI,KAAK,gBAAgB,IAAI;;KA2EnE,SAAS,GAAG,GAAG,GAAG,MACrB,OAAO,MACJ,OAAO,OAAO,KAAK,GAAG,IAAI,GAAG,KAAK,OAAO,OAAO,OAAO,OAAO,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG;;;;;;;;;;cAW5E;GACV,GAAG,GAAG,GAAG,GAAG,MAAM,SAAS,GAAG,GAAG,GAAG,MAAM,SAAS,MAAM,OAAO,OAAO,GAAG,GAAG;GAC7E,GAAG,GAAG,GAAG,GAAG,SAAS,GAAG,MAAM,SAAS,GAAG,GAAG,GAAG,KAAK,OAAO,OAAO,GAAG,GAAG;;KA2FvE,aAAa,GAAG,GAAG,GAAG,GAAG,MAC5B,OAAO,GACP,OAAO,MACJ,OAAO,OAAO,KAAK,GAAG,IAAI,GAAG,KAAK,OAAO,OAAO,OAAO,OAAO,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;cAyB5E;GACV,GAAG,GAAG,GAAG,GAAG,GACX,SAAS,GACT,MAAM,aAAa,GAAG,GAAG,GAAG,GAAG,MAC7B,IAAI,IAAI,OAAO,OAAO,OAAO,GAAG,IAAI,QAAQ,OAAO,OAAO,GAAG,IAAI,IAAI,IAAI;GAC5E,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,IAClB,OAAO,OAAO,OAAO,GAAG,IAAI,KAC5B,SAAS,GACT,MAAM,aAAa,GAAG,GAAG,GAAG,GAAG,KAC9B,OAAO,OAAO,GAAG,IAAI,IAAI,IAAI;;;;;;;;;;;;;;;;;;;;;;cAmErB,gBAAiB,GAAG,GAAG,GAAG,GAAC,SAC7B,GAAC,MACJ,SAAS,GAAG,GAAG,GAAG,OACvB,OAAO;WACC,QAAQ,OAAO,OAAO,GAAG,GAAG;WAC5B,OAAO,gBAAgB,gBAAgB"}
@@ -1,7 +1,7 @@
1
1
  import { o as HistoryItem, y as ToolCall } from "./Items-C7MyrYPX.mjs";
2
2
  import { a as Emit, c as SignalTool, d as ToolE, h as ToolR, l as Tool, n as AnyTool, o as InteractionTool, p as ToolFailed, s as ProviderTool, u as ToolDescriptor } from "./Tool-BF78QWtn.mjs";
3
3
  import { Turn } from "./domain/Turn.mjs";
4
- import { r as Step } from "./Loop-B54AB1ME.mjs";
4
+ import { r as Step } from "./Loop-ChpaO-ya.mjs";
5
5
  import { t as ToolResult } from "./ToolResult-BsYQRjmW.mjs";
6
6
  import { t as ToolEvent } from "./ToolEvent-Di4a723B.mjs";
7
7
  import { Cause, Effect, Stream } from "effect";
@@ -190,4 +190,4 @@ declare const continueWithResults: {
190
190
  };
191
191
  //#endregion
192
192
  export { wrap as S, fromArray as _, ToolMap as a, namespace as b, ToolkitR as c, appendToolResults as d, collectResults as f, descriptors as g, describeFailures as h, NoDuplicateNames as i, Toolkit_d_exports as l, continueWithResults as m, ExecuteOptions as n, Toolkit as o, compose as p, Middleware as r, ToolkitE as s, DuplicateToolName as t, UniqueTools as u, make as v, run as x, makeNamespaced as y };
193
- //# sourceMappingURL=Toolkit-DAjJ9Tvl.d.mts.map
193
+ //# sourceMappingURL=Toolkit-tlL0F2YR.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Toolkit-DAjJ9Tvl.d.mts","names":[],"sources":["../src/tool/Toolkit.ts"],"mappings":"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"}
1
+ {"version":3,"file":"Toolkit-tlL0F2YR.d.mts","names":[],"sources":["../src/tool/Toolkit.ts"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as AiError } from "../AiError-eLV9t1lU.mjs";
1
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
2
2
  import { Context, Effect } from "effect";
3
3
  declare namespace Chunker_d_exports {
4
4
  export { Chunk, Chunker, ChunkerService, chunk };
@@ -0,0 +1,155 @@
1
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
2
+ import { Effect, Option, Schema } from "effect";
3
+ declare namespace Decision_d_exports {
4
+ export { Answer, AnswerFor, Answers, Boundary, ClassifyAnswer, ClassifyDecision, Decision, Decisions, Definition, Labels, Levels, ProbabilityAnswer, ProbabilityDecision, RateAnswer, RateDecision, assertKinds, classify, confidence, expectedLevel, make, margin, probabilitiesOf, probability, ranked, rate, topLevel, winner };
5
+ }
6
+ /** Labels mapped to what each one means. */
7
+ type Labels = Readonly<Record<string, string>>;
8
+ /** An ordered rubric, lowest level first. */
9
+ type Levels = readonly [string, string, ...ReadonlyArray<string>];
10
+ /** What a true and a false answer mean, when the boundary is subtle. */
11
+ type Boundary = {
12
+ readonly true?: string;
13
+ readonly false?: string;
14
+ };
15
+ type ClassifyDecision<L extends Labels = Labels> = {
16
+ readonly _tag: "Classify";
17
+ readonly instructions: string;
18
+ readonly criteria: L;
19
+ };
20
+ type RateDecision<V extends Levels = Levels> = {
21
+ readonly _tag: "Rate";
22
+ readonly instructions: string;
23
+ readonly criteria: V;
24
+ };
25
+ type ProbabilityDecision = {
26
+ readonly _tag: "Probability";
27
+ readonly instructions: string;
28
+ readonly criteria?: Boundary;
29
+ };
30
+ type Decision = ClassifyDecision | RateDecision | ProbabilityDecision;
31
+ /** Pick one label. Use {@link rate} when the options form a spectrum. */
32
+ declare const classify: <const L extends Labels>(decision: {
33
+ readonly instructions: string;
34
+ readonly criteria: L;
35
+ }) => ClassifyDecision<L>;
36
+ /** Place the input on an ordered rubric. */
37
+ declare const rate: <const V extends Levels>(decision: {
38
+ readonly instructions: string;
39
+ readonly criteria: V;
40
+ }) => RateDecision<V>;
41
+ /**
42
+ * How likely a statement is to hold. Named for its answer rather than for an
43
+ * act, unlike its siblings, so nobody reads the result as a boolean.
44
+ */
45
+ declare const probability: (decision: {
46
+ readonly instructions: string;
47
+ readonly criteria?: Boundary;
48
+ }) => ProbabilityDecision;
49
+ /** Decisions keyed by the name their answer comes back under. */
50
+ type Decisions = {
51
+ readonly [name: string]: Decision;
52
+ };
53
+ /**
54
+ * A reusable decision set bound to the shape it reads.
55
+ *
56
+ * `inputSchema` encodes the input on the way out, so a schema that omits a
57
+ * field keeps it from the model. That projection is the supported way to hold
58
+ * irrelevant or sensitive context out of a request.
59
+ */
60
+ type Definition<A_, D extends Decisions = Decisions> = {
61
+ readonly inputSchema: Schema.Codec<A_, unknown, any, never>;
62
+ readonly decisions: D;
63
+ };
64
+ /**
65
+ * Build a decision set. Declare it once at module scope: the questions and
66
+ * their rubrics are the reviewed artifact, the input changes per call.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const triage = Decision.make({
71
+ * inputSchema: Schema.Struct({ message: Schema.String }),
72
+ * decisions: {
73
+ * department: Decision.classify({
74
+ * instructions: "Which team should handle this?",
75
+ * criteria: { billing: "Payments, invoicing", technical: "Bugs, outages" },
76
+ * }),
77
+ * },
78
+ * })
79
+ * ```
80
+ */
81
+ declare const make: <S extends Schema.Codec<any, any, any, never>, const D extends Decisions>(definition: {
82
+ readonly inputSchema: S;
83
+ readonly decisions: D;
84
+ }) => Definition<S["Type"], D>;
85
+ /**
86
+ * Score contract for {@link ClassifyAnswer.labels} and
87
+ * {@link RateAnswer.levels}: probabilities sum to 1 within floating-point
88
+ * error and are comparable within one answer. Whether they are *calibrated*,
89
+ * so a stated 0.8 means right about 80% of the time, is a property of the
90
+ * Layer and is documented per provider. No implementor promises calibration
91
+ * across requests, across decision kinds, or between a statement and its
92
+ * negation: `P(claim) != 1 - P(not claim)`. A threshold fitted against one
93
+ * model does not transfer to another.
94
+ */
95
+ type ClassifyAnswer<L extends Labels = Labels> = {
96
+ readonly _tag: "Classify";
97
+ readonly labels: Readonly<Record<keyof L & string, number>>;
98
+ };
99
+ type RateAnswer<V extends Levels = Levels> = {
100
+ readonly _tag: "Rate";
101
+ /** Index-aligned with {@link RateAnswer.legend}. */
102
+ readonly levels: { readonly [K in keyof V]: number; };
103
+ /** The rubric, echoed so an index reads back as prose. */
104
+ readonly legend: Readonly<V>;
105
+ };
106
+ type ProbabilityAnswer = {
107
+ readonly _tag: "Probability";
108
+ readonly probability: number;
109
+ };
110
+ type Answer = ClassifyAnswer | RateAnswer | ProbabilityAnswer;
111
+ /** The answer a decision determines, keeping its labels or rubric. */
112
+ type AnswerFor<D> = D extends {
113
+ readonly _tag: "Classify";
114
+ readonly criteria: infer L extends Labels;
115
+ } ? ClassifyAnswer<L> : D extends {
116
+ readonly _tag: "Rate";
117
+ readonly criteria: infer V extends Levels;
118
+ } ? RateAnswer<V> : ProbabilityAnswer;
119
+ /** Answers keyed by their decision's name. */
120
+ type Answers<D extends Decisions> = { readonly [K in keyof D]: AnswerFor<D[K]>; };
121
+ /**
122
+ * Fail `AiError.Unsupported` naming every decision whose kind this provider
123
+ * cannot express. Bucket 1: omitting an answer would leave a hole where
124
+ * {@link Answers} promises a key.
125
+ *
126
+ * Reports all offenders at once, since the definition is known up front.
127
+ */
128
+ declare const assertKinds: (decisions: Decisions, supported: ReadonlyArray<Decision["_tag"]>, provider: string) => Effect.Effect<void, AiError>;
129
+ /** A `Probability` answer's implied distribution is `[p, 1 - p]`. */
130
+ declare const probabilitiesOf: (answer: Answer) => ReadonlyArray<number>;
131
+ /** See {@link Probability.confidence}. Defined for every kind. */
132
+ declare const confidence: (answer: Answer) => number;
133
+ /** See {@link Probability.margin}. Defined for every kind. */
134
+ declare const margin: (answer: Answer) => number;
135
+ /**
136
+ * Labels best first. Ties keep first-seen order. Also the bridge to rank
137
+ * fusion: map to `label` and pass alongside another retriever's ordering.
138
+ */
139
+ declare const ranked: <L extends Labels>(answer: ClassifyAnswer<L>) => ReadonlyArray<{
140
+ readonly label: keyof L & string;
141
+ readonly probability: number;
142
+ }>;
143
+ /** Highest-probability label. `None` only when the label set is empty. */
144
+ declare const winner: <L extends Labels>(answer: ClassifyAnswer<L>) => Option.Option<keyof L & string>;
145
+ /** Index of the highest-probability level. Ties resolve to the lowest. */
146
+ declare const topLevel: (answer: RateAnswer) => number;
147
+ /**
148
+ * Probability-weighted mean of the level indices, so it can land between
149
+ * them. Weakly calibrated in practice: a summary, not a magnitude to
150
+ * interpolate. Use {@link topLevel} with `legend` to name a level instead.
151
+ */
152
+ declare const expectedLevel: (answer: RateAnswer) => number;
153
+ //#endregion
154
+ export { Answer, AnswerFor, Answers, Boundary, ClassifyAnswer, ClassifyDecision, Decision, Decisions, Definition, Labels, Levels, ProbabilityAnswer, ProbabilityDecision, RateAnswer, RateDecision, assertKinds, classify, confidence, expectedLevel, make, margin, probabilitiesOf, probability, ranked, rate, Decision_d_exports as t, topLevel, winner };
155
+ //# sourceMappingURL=Decision.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Decision.d.mts","names":[],"sources":["../../src/decision-model/Decision.ts"],"mappings":";;;;;;KA4BY,SAAS,SAAS;;KAGlB,sCAAsC;;KAGtC;WAAsB;WAAwB;;KAM9C,iBAAiB,UAAU,SAAS;WACrC;WACA;WACA,UAAU;;KAGT,aAAa,UAAU,SAAS;WACjC;WACA;WACA,UAAU;;KAGT;WACD;WACA;WACA,WAAW;;KAGV,WAAW,mBAAmB,eAAe;;cAG5C,iBAAkB,UAAU,QAAM;WACpC;WACA,UAAU;MACjB,iBAAiB;;cAGR,aAAc,UAAU,QAAM;WAChC;WACA,UAAU;MACjB,aAAa;;;;;cAMJ,cAAW;WACb;WACA,WAAW;MAClB;;KAGQ;YAAwB,eAAe;;;;;;;;;KAavC,WAAW,IAAI,UAAU,YAAY;WACtC,aAAa,OAAO,MAAM;WAC1B,WAAW;;;;;;;;;;;;;;;;;;;cAoBT,OACX,UAAU,OAAO,mCACX,UAAU,WAAS;WAEhB,aAAa;WACb,WAAW;MAClB,WAAW,WAAW;;;;;;;;;;;KAgBd,eAAe,UAAU,SAAS;WACnC;WACA,QAAQ,SAAS,aAAa;;KAG7B,WAAW,UAAU,SAAS;WAC/B;;WAEA,oBAAoB,WAAW;;WAE/B,QAAQ,SAAS;;KAGhB;WACD;WACA;;KAGC,SAAS,iBAAiB,aAAa;;KAGvC,UAAU,KAAK;WAChB;WACA,gBAAgB,UAAU;IAEjC,eAAe,KACf;WAAqB;WAAuB,gBAAgB,UAAU;IACpE,WAAW,KACX;;KAGM,QAAQ,UAAU,yBAAyB,WAAW,IAAI,UAAU,EAAE;;;;;;;;cAarE,cAAW,WACX,WAAS,WACT,cAAc,mBAAiB,qBAEzC,OAAO,aAAa;;cAsBV,kBAAkB,QAAQ,WAAW;;cASrC,aAAU,QAAY;;cAItB,SAAM,QAAY;;;;;cAWlB,SAAU,UAAU,QAAM,QAC7B,eAAe,OACtB;WAAyB,aAAa;WAAqB;;;cAQjD,SAAU,UAAU,QAAM,QAC7B,eAAe,OACtB,OAAO,aAAa;;cAQV,WAAQ,QAAY;;;;;;cAcpB,gBAAa,QAAY"}
@@ -0,0 +1,118 @@
1
+ import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
+ import { Unsupported } from "../domain/AiError.mjs";
3
+ import { confidence as confidence$1, margin as margin$1 } from "../math/Probability.mjs";
4
+ import { Array, Effect, Match, Number, Option, Order, Record, pipe } from "effect";
5
+ //#region src/decision-model/Decision.ts
6
+ /**
7
+ * Typed questions about one input, answered with probabilities.
8
+ *
9
+ * A {@link Definition} pairs the shape being read with the questions asked of
10
+ * it. Build one at module scope and apply it per call with
11
+ * `DecisionModel.decide`.
12
+ *
13
+ * @experimental The model class this abstracts is new and the shape may change.
14
+ */
15
+ var Decision_exports = /* @__PURE__ */ __exportAll({
16
+ assertKinds: () => assertKinds,
17
+ classify: () => classify,
18
+ confidence: () => confidence,
19
+ expectedLevel: () => expectedLevel,
20
+ make: () => make,
21
+ margin: () => margin,
22
+ probabilitiesOf: () => probabilitiesOf,
23
+ probability: () => probability,
24
+ ranked: () => ranked,
25
+ rate: () => rate,
26
+ topLevel: () => topLevel,
27
+ winner: () => winner
28
+ });
29
+ /** Pick one label. Use {@link rate} when the options form a spectrum. */
30
+ const classify = (decision) => ({
31
+ _tag: "Classify",
32
+ ...decision
33
+ });
34
+ /** Place the input on an ordered rubric. */
35
+ const rate = (decision) => ({
36
+ _tag: "Rate",
37
+ ...decision
38
+ });
39
+ /**
40
+ * How likely a statement is to hold. Named for its answer rather than for an
41
+ * act, unlike its siblings, so nobody reads the result as a boolean.
42
+ */
43
+ const probability = (decision) => ({
44
+ _tag: "Probability",
45
+ ...decision
46
+ });
47
+ /**
48
+ * Build a decision set. Declare it once at module scope: the questions and
49
+ * their rubrics are the reviewed artifact, the input changes per call.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const triage = Decision.make({
54
+ * inputSchema: Schema.Struct({ message: Schema.String }),
55
+ * decisions: {
56
+ * department: Decision.classify({
57
+ * instructions: "Which team should handle this?",
58
+ * criteria: { billing: "Payments, invoicing", technical: "Bugs, outages" },
59
+ * }),
60
+ * },
61
+ * })
62
+ * ```
63
+ */
64
+ const make = (definition) => definition;
65
+ /**
66
+ * Fail `AiError.Unsupported` naming every decision whose kind this provider
67
+ * cannot express. Bucket 1: omitting an answer would leave a hole where
68
+ * {@link Answers} promises a key.
69
+ *
70
+ * Reports all offenders at once, since the definition is known up front.
71
+ */
72
+ const assertKinds = (decisions, supported, provider) => {
73
+ const offenders = pipe(Record.toEntries(decisions), Array.filter(([, decision]) => !supported.includes(decision._tag)), Array.map(([name, decision]) => `"${name}" is a ${decision._tag}`));
74
+ return offenders.length === 0 ? Effect.void : Effect.fail(new Unsupported({
75
+ provider,
76
+ capability: "decision",
77
+ reason: `${provider} answers ${supported.join(" / ")} only; ${offenders.join(", ")}.`
78
+ }));
79
+ };
80
+ /** A `Probability` answer's implied distribution is `[p, 1 - p]`. */
81
+ const probabilitiesOf = Match.type().pipe(Match.discriminatorsExhaustive("_tag")({
82
+ Classify: (answer) => Record.values(answer.labels),
83
+ Rate: (answer) => answer.levels,
84
+ Probability: (answer) => [answer.probability, 1 - answer.probability]
85
+ }));
86
+ /** See {@link Probability.confidence}. Defined for every kind. */
87
+ const confidence = (answer) => confidence$1(probabilitiesOf(answer));
88
+ /** See {@link Probability.margin}. Defined for every kind. */
89
+ const margin = (answer) => margin$1(probabilitiesOf(answer));
90
+ const byProbability = Order.mapInput(Order.flip(Order.Number), (outcome) => outcome.probability);
91
+ /**
92
+ * Labels best first. Ties keep first-seen order. Also the bridge to rank
93
+ * fusion: map to `label` and pass alongside another retriever's ordering.
94
+ */
95
+ const ranked = (answer) => pipe(Record.toEntries(answer.labels), Array.map(([label, probability]) => ({
96
+ label,
97
+ probability
98
+ })), Array.sort(byProbability));
99
+ /** Highest-probability label. `None` only when the label set is empty. */
100
+ const winner = (answer) => pipe(ranked(answer), Array.head, Option.map((outcome) => outcome.label));
101
+ /** Index of the highest-probability level. Ties resolve to the lowest. */
102
+ const topLevel = (answer) => pipe(answer.levels, Array.reduce({
103
+ index: 0,
104
+ probability: -Infinity
105
+ }, (best, probability, index) => probability > best.probability ? {
106
+ index,
107
+ probability
108
+ } : best), (best) => best.index);
109
+ /**
110
+ * Probability-weighted mean of the level indices, so it can land between
111
+ * them. Weakly calibrated in practice: a summary, not a magnitude to
112
+ * interpolate. Use {@link topLevel} with `legend` to name a level instead.
113
+ */
114
+ const expectedLevel = (answer) => pipe(answer.levels, Array.map((probability, index) => index * probability), Number.sumAll);
115
+ //#endregion
116
+ export { assertKinds, classify, confidence, expectedLevel, make, margin, probabilitiesOf, probability, ranked, rate, Decision_exports as t, topLevel, winner };
117
+
118
+ //# sourceMappingURL=Decision.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Decision.mjs","names":["A","AiError.Unsupported","Probability.confidence","Probability.margin","N"],"sources":["../../src/decision-model/Decision.ts"],"sourcesContent":["/**\n * Typed questions about one input, answered with probabilities.\n *\n * A {@link Definition} pairs the shape being read with the questions asked of\n * it. Build one at module scope and apply it per call with\n * `DecisionModel.decide`.\n *\n * @experimental The model class this abstracts is new and the shape may change.\n */\nimport {\n Array as A,\n Effect,\n Match,\n Number as N,\n Option,\n Order,\n Record,\n type Schema,\n pipe,\n} from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport * as Probability from \"../math/Probability.js\"\n\n// ---------------------------------------------------------------------------\n// Criteria\n// ---------------------------------------------------------------------------\n\n/** Labels mapped to what each one means. */\nexport type Labels = Readonly<Record<string, string>>\n\n/** An ordered rubric, lowest level first. */\nexport type Levels = readonly [string, string, ...ReadonlyArray<string>]\n\n/** What a true and a false answer mean, when the boundary is subtle. */\nexport type Boundary = { readonly true?: string; readonly false?: string }\n\n// ---------------------------------------------------------------------------\n// Decisions\n// ---------------------------------------------------------------------------\n\nexport type ClassifyDecision<L extends Labels = Labels> = {\n readonly _tag: \"Classify\"\n readonly instructions: string\n readonly criteria: L\n}\n\nexport type RateDecision<V extends Levels = Levels> = {\n readonly _tag: \"Rate\"\n readonly instructions: string\n readonly criteria: V\n}\n\nexport type ProbabilityDecision = {\n readonly _tag: \"Probability\"\n readonly instructions: string\n readonly criteria?: Boundary\n}\n\nexport type Decision = ClassifyDecision | RateDecision | ProbabilityDecision\n\n/** Pick one label. Use {@link rate} when the options form a spectrum. */\nexport const classify = <const L extends Labels>(decision: {\n readonly instructions: string\n readonly criteria: L\n}): ClassifyDecision<L> => ({ _tag: \"Classify\", ...decision })\n\n/** Place the input on an ordered rubric. */\nexport const rate = <const V extends Levels>(decision: {\n readonly instructions: string\n readonly criteria: V\n}): RateDecision<V> => ({ _tag: \"Rate\", ...decision })\n\n/**\n * How likely a statement is to hold. Named for its answer rather than for an\n * act, unlike its siblings, so nobody reads the result as a boolean.\n */\nexport const probability = (decision: {\n readonly instructions: string\n readonly criteria?: Boundary\n}): ProbabilityDecision => ({ _tag: \"Probability\", ...decision })\n\n/** Decisions keyed by the name their answer comes back under. */\nexport type Decisions = { readonly [name: string]: Decision }\n\n// ---------------------------------------------------------------------------\n// Definition\n// ---------------------------------------------------------------------------\n\n/**\n * A reusable decision set bound to the shape it reads.\n *\n * `inputSchema` encodes the input on the way out, so a schema that omits a\n * field keeps it from the model. That projection is the supported way to hold\n * irrelevant or sensitive context out of a request.\n */\nexport type Definition<A_, D extends Decisions = Decisions> = {\n readonly inputSchema: Schema.Codec<A_, unknown, any, never>\n readonly decisions: D\n}\n\n/**\n * Build a decision set. Declare it once at module scope: the questions and\n * their rubrics are the reviewed artifact, the input changes per call.\n *\n * @example\n * ```ts\n * const triage = Decision.make({\n * inputSchema: Schema.Struct({ message: Schema.String }),\n * decisions: {\n * department: Decision.classify({\n * instructions: \"Which team should handle this?\",\n * criteria: { billing: \"Payments, invoicing\", technical: \"Bugs, outages\" },\n * }),\n * },\n * })\n * ```\n */\nexport const make = <\n S extends Schema.Codec<any, any, any, never>,\n const D extends Decisions,\n>(definition: {\n readonly inputSchema: S\n readonly decisions: D\n}): Definition<S[\"Type\"], D> => definition\n\n// ---------------------------------------------------------------------------\n// Answers\n// ---------------------------------------------------------------------------\n\n/**\n * Score contract for {@link ClassifyAnswer.labels} and\n * {@link RateAnswer.levels}: probabilities sum to 1 within floating-point\n * error and are comparable within one answer. Whether they are *calibrated*,\n * so a stated 0.8 means right about 80% of the time, is a property of the\n * Layer and is documented per provider. No implementor promises calibration\n * across requests, across decision kinds, or between a statement and its\n * negation: `P(claim) != 1 - P(not claim)`. A threshold fitted against one\n * model does not transfer to another.\n */\nexport type ClassifyAnswer<L extends Labels = Labels> = {\n readonly _tag: \"Classify\"\n readonly labels: Readonly<Record<keyof L & string, number>>\n}\n\nexport type RateAnswer<V extends Levels = Levels> = {\n readonly _tag: \"Rate\"\n /** Index-aligned with {@link RateAnswer.legend}. */\n readonly levels: { readonly [K in keyof V]: number }\n /** The rubric, echoed so an index reads back as prose. */\n readonly legend: Readonly<V>\n}\n\nexport type ProbabilityAnswer = {\n readonly _tag: \"Probability\"\n readonly probability: number\n}\n\nexport type Answer = ClassifyAnswer | RateAnswer | ProbabilityAnswer\n\n/** The answer a decision determines, keeping its labels or rubric. */\nexport type AnswerFor<D> = D extends {\n readonly _tag: \"Classify\"\n readonly criteria: infer L extends Labels\n}\n ? ClassifyAnswer<L>\n : D extends { readonly _tag: \"Rate\"; readonly criteria: infer V extends Levels }\n ? RateAnswer<V>\n : ProbabilityAnswer\n\n/** Answers keyed by their decision's name. */\nexport type Answers<D extends Decisions> = { readonly [K in keyof D]: AnswerFor<D[K]> }\n\n// ---------------------------------------------------------------------------\n// Guards\n// ---------------------------------------------------------------------------\n\n/**\n * Fail `AiError.Unsupported` naming every decision whose kind this provider\n * cannot express. Bucket 1: omitting an answer would leave a hole where\n * {@link Answers} promises a key.\n *\n * Reports all offenders at once, since the definition is known up front.\n */\nexport const assertKinds = (\n decisions: Decisions,\n supported: ReadonlyArray<Decision[\"_tag\"]>,\n provider: string,\n): Effect.Effect<void, AiError.AiError> => {\n const offenders = pipe(\n Record.toEntries(decisions),\n A.filter(([, decision]) => !supported.includes(decision._tag)),\n A.map(([name, decision]) => `\"${name}\" is a ${decision._tag}`),\n )\n return offenders.length === 0\n ? Effect.void\n : Effect.fail(\n new AiError.Unsupported({\n provider,\n capability: \"decision\",\n reason: `${provider} answers ${supported.join(\" / \")} only; ${offenders.join(\", \")}.`,\n }),\n )\n}\n\n// ---------------------------------------------------------------------------\n// Reading an answer\n// ---------------------------------------------------------------------------\n\n/** A `Probability` answer's implied distribution is `[p, 1 - p]`. */\nexport const probabilitiesOf: (answer: Answer) => ReadonlyArray<number> = Match.type<Answer>().pipe(\n Match.discriminatorsExhaustive(\"_tag\")({\n Classify: (answer) => Record.values(answer.labels),\n Rate: (answer) => answer.levels,\n Probability: (answer) => [answer.probability, 1 - answer.probability],\n }),\n)\n\n/** See {@link Probability.confidence}. Defined for every kind. */\nexport const confidence = (answer: Answer): number =>\n Probability.confidence(probabilitiesOf(answer))\n\n/** See {@link Probability.margin}. Defined for every kind. */\nexport const margin = (answer: Answer): number => Probability.margin(probabilitiesOf(answer))\n\nconst byProbability = Order.mapInput(\n Order.flip(Order.Number),\n (outcome: { readonly probability: number }) => outcome.probability,\n)\n\n/**\n * Labels best first. Ties keep first-seen order. Also the bridge to rank\n * fusion: map to `label` and pass alongside another retriever's ordering.\n */\nexport const ranked = <L extends Labels>(\n answer: ClassifyAnswer<L>,\n): ReadonlyArray<{ readonly label: keyof L & string; readonly probability: number }> =>\n pipe(\n Record.toEntries(answer.labels),\n A.map(([label, probability]) => ({ label, probability })),\n A.sort(byProbability),\n )\n\n/** Highest-probability label. `None` only when the label set is empty. */\nexport const winner = <L extends Labels>(\n answer: ClassifyAnswer<L>,\n): Option.Option<keyof L & string> =>\n pipe(\n ranked(answer),\n A.head,\n Option.map((outcome) => outcome.label),\n )\n\n/** Index of the highest-probability level. Ties resolve to the lowest. */\nexport const topLevel = (answer: RateAnswer): number =>\n pipe(\n answer.levels as ReadonlyArray<number>,\n A.reduce({ index: 0, probability: -Infinity }, (best, probability, index) =>\n probability > best.probability ? { index, probability } : best,\n ),\n (best) => best.index,\n )\n\n/**\n * Probability-weighted mean of the level indices, so it can land between\n * them. Weakly calibrated in practice: a summary, not a magnitude to\n * interpolate. Use {@link topLevel} with `legend` to name a level instead.\n */\nexport const expectedLevel = (answer: RateAnswer): number =>\n pipe(\n answer.levels as ReadonlyArray<number>,\n A.map((probability, index) => index * probability),\n N.sumAll,\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,MAAa,YAAoC,cAGrB;CAAE,MAAM;CAAY,GAAG;AAAS;;AAG5D,MAAa,QAAgC,cAGrB;CAAE,MAAM;CAAQ,GAAG;AAAS;;;;;AAMpD,MAAa,eAAe,cAGA;CAAE,MAAM;CAAe,GAAG;AAAS;;;;;;;;;;;;;;;;;;AAsC/D,MAAa,QAGX,eAG8B;;;;;;;;AA4DhC,MAAa,eACX,WACA,WACA,aACyC;CACzC,MAAM,YAAY,KAChB,OAAO,UAAU,SAAS,GAC1BA,MAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,SAAS,SAAS,IAAI,CAAC,GAC7DA,MAAE,KAAK,CAAC,MAAM,cAAc,IAAI,KAAK,SAAS,SAAS,MAAM,CAC/D;CACA,OAAO,UAAU,WAAW,IACxB,OAAO,OACP,OAAO,KACL,IAAIC,YAAoB;EACtB;EACA,YAAY;EACZ,QAAQ,GAAG,SAAS,WAAW,UAAU,KAAK,KAAK,EAAE,SAAS,UAAU,KAAK,IAAI,EAAE;CACrF,CAAC,CACH;AACN;;AAOA,MAAa,kBAA6D,MAAM,KAAa,CAAC,CAAC,KAC7F,MAAM,yBAAyB,MAAM,CAAC,CAAC;CACrC,WAAW,WAAW,OAAO,OAAO,OAAO,MAAM;CACjD,OAAO,WAAW,OAAO;CACzB,cAAc,WAAW,CAAC,OAAO,aAAa,IAAI,OAAO,WAAW;AACtE,CAAC,CACH;;AAGA,MAAa,cAAc,WACzBC,aAAuB,gBAAgB,MAAM,CAAC;;AAGhD,MAAa,UAAU,WAA2BC,SAAmB,gBAAgB,MAAM,CAAC;AAE5F,MAAM,gBAAgB,MAAM,SAC1B,MAAM,KAAK,MAAM,MAAM,IACtB,YAA8C,QAAQ,WACzD;;;;;AAMA,MAAa,UACX,WAEA,KACE,OAAO,UAAU,OAAO,MAAM,GAC9BH,MAAE,KAAK,CAAC,OAAO,kBAAkB;CAAE;CAAO;AAAY,EAAE,GACxDA,MAAE,KAAK,aAAa,CACtB;;AAGF,MAAa,UACX,WAEA,KACE,OAAO,MAAM,GACbA,MAAE,MACF,OAAO,KAAK,YAAY,QAAQ,KAAK,CACvC;;AAGF,MAAa,YAAY,WACvB,KACE,OAAO,QACPA,MAAE,OAAO;CAAE,OAAO;CAAG,aAAa;AAAU,IAAI,MAAM,aAAa,UACjE,cAAc,KAAK,cAAc;CAAE;CAAO;AAAY,IAAI,IAC5D,IACC,SAAS,KAAK,KACjB;;;;;;AAOF,MAAa,iBAAiB,WAC5B,KACE,OAAO,QACPA,MAAE,KAAK,aAAa,UAAU,QAAQ,WAAW,GACjDI,OAAE,MACJ"}
@@ -0,0 +1,164 @@
1
+ import { Unsupported } from "../domain/AiError.mjs";
2
+ import { assertKinds, classify, confidence, expectedLevel, make, margin, probabilitiesOf, probability, ranked, rate, topLevel, winner } from "./Decision.mjs";
3
+ import { DecisionModel, decide } from "./DecisionModel.mjs";
4
+ import { o as describe } from "../utils-CiH_DjFW.mjs";
5
+ import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
6
+ import { t as import_dist } from "../dist-BaenoU1A.mjs";
7
+ import { t as it } from "../dist-nohcnkD6.mjs";
8
+ import { Effect, Layer, Option, Ref, Schema } from "effect";
9
+ //#region src/decision-model/Decision.test.ts
10
+ const triage = make({
11
+ inputSchema: Schema.Struct({ message: Schema.String }),
12
+ decisions: {
13
+ department: classify({
14
+ instructions: "Which team should handle this?",
15
+ criteria: {
16
+ billing: "Payments, invoicing",
17
+ technical: "Bugs, outages"
18
+ }
19
+ }),
20
+ severity: rate({
21
+ instructions: "How severe is it?",
22
+ criteria: [
23
+ "Cosmetic",
24
+ "Degraded",
25
+ "Blocking"
26
+ ]
27
+ }),
28
+ urgent: probability({ instructions: "Does this convey urgency?" })
29
+ }
30
+ });
31
+ const fixture = {
32
+ department: {
33
+ _tag: "Classify",
34
+ labels: {
35
+ billing: .62,
36
+ technical: .38
37
+ }
38
+ },
39
+ severity: {
40
+ _tag: "Rate",
41
+ levels: [
42
+ 0,
43
+ .7,
44
+ .3
45
+ ],
46
+ legend: [
47
+ "Cosmetic",
48
+ "Degraded",
49
+ "Blocking"
50
+ ]
51
+ },
52
+ urgent: {
53
+ _tag: "Probability",
54
+ probability: .94
55
+ }
56
+ };
57
+ /**
58
+ * A fixed fixture cannot prove it satisfies the caller's `Answers<D>`. This is
59
+ * the boundary a provider casts at too, once it has decoded the wire.
60
+ */
61
+ const fixed = (answers) => answers;
62
+ /** A `DecisionModel` layer that records what reached it. */
63
+ const recording = Effect.gen(function* () {
64
+ const calls = yield* Ref.make([]);
65
+ return {
66
+ calls,
67
+ layer: Layer.succeed(DecisionModel, { decide: (_definition, request) => Effect.as(Ref.update(calls, (seen) => [...seen, {
68
+ input: request.input,
69
+ model: request.model
70
+ }]), {
71
+ answers: fixed(fixture),
72
+ usage: {}
73
+ }) })
74
+ };
75
+ });
76
+ describe("the answer map is derived from the definition", () => {
77
+ it.effect("keeps each decision's label keys and rejects labels it never had", () => Effect.gen(function* () {
78
+ const { calls, layer } = yield* recording;
79
+ const result = yield* decide(triage, {
80
+ model: "jev-latest",
81
+ input: { message: "hi" }
82
+ }).pipe(Effect.provide(layer));
83
+ (0, import_dist.expectTypeOf)(result.answers.department.labels).toEqualTypeOf();
84
+ result.answers.department.labels.shipping;
85
+ result.answers.urgent.labels;
86
+ globalExpect(yield* Ref.get(calls)).toEqual([{
87
+ input: { message: "hi" },
88
+ model: "jev-latest"
89
+ }]);
90
+ }));
91
+ it("keeps the rubric as a tuple, so levels are positional rather than parsed", () => {
92
+ (0, import_dist.expectTypeOf)().toEqualTypeOf();
93
+ (0, import_dist.expectTypeOf)().toEqualTypeOf();
94
+ });
95
+ });
96
+ describe("reading an answer", () => {
97
+ it("derives a Probability answer's distribution as [p, 1 - p]", () => {
98
+ const [yes, no] = probabilitiesOf(fixture.urgent);
99
+ globalExpect(yes).toBeCloseTo(.94, 10);
100
+ globalExpect(no).toBeCloseTo(.06, 10);
101
+ });
102
+ it("ranks labels best first and names the winner", () => {
103
+ globalExpect(ranked(fixture.department)).toEqual([{
104
+ label: "billing",
105
+ probability: .62
106
+ }, {
107
+ label: "technical",
108
+ probability: .38
109
+ }]);
110
+ globalExpect(winner(fixture.department)).toStrictEqual(Option.some("billing"));
111
+ });
112
+ it("has no winner for an empty label set", () => {
113
+ globalExpect(winner({
114
+ _tag: "Classify",
115
+ labels: {}
116
+ })).toStrictEqual(Option.none());
117
+ });
118
+ it("reads a rubric by index and by expected value", () => {
119
+ globalExpect(topLevel(fixture.severity)).toBe(1);
120
+ globalExpect(expectedLevel(fixture.severity)).toBeCloseTo(1.3, 10);
121
+ globalExpect(fixture.severity.legend[topLevel(fixture.severity)]).toBe("Degraded");
122
+ });
123
+ it("resolves a level tie to the lowest index", () => {
124
+ globalExpect(topLevel({
125
+ _tag: "Rate",
126
+ levels: [.5, .5],
127
+ legend: ["a", "b"]
128
+ })).toBe(0);
129
+ });
130
+ it("computes certainty for every kind, including Probability", () => {
131
+ globalExpect(confidence(fixture.urgent)).toBeCloseTo(.67, 2);
132
+ globalExpect(margin(fixture.urgent)).toBeCloseTo(.88, 10);
133
+ globalExpect(margin(fixture.department)).toBeCloseTo(.24, 10);
134
+ });
135
+ });
136
+ describe("assertKinds", () => {
137
+ const supported = ["Classify", "Probability"];
138
+ it.effect("passes when every kind is supported", () => {
139
+ const { department, urgent } = triage.decisions;
140
+ return assertKinds({
141
+ department,
142
+ urgent
143
+ }, supported, "nli");
144
+ });
145
+ it.effect("fails Unsupported and names every offender at once", () => Effect.gen(function* () {
146
+ const decisions = {
147
+ ...triage.decisions,
148
+ impact: rate({
149
+ instructions: "How bad?",
150
+ criteria: ["low", "high"]
151
+ })
152
+ };
153
+ const error = yield* Effect.flip(assertKinds(decisions, supported, "nli"));
154
+ globalExpect(error).toBeInstanceOf(Unsupported);
155
+ if (error._tag !== "Unsupported") return;
156
+ globalExpect(error.reason).toContain("\"severity\" is a Rate");
157
+ globalExpect(error.reason).toContain("\"impact\" is a Rate");
158
+ globalExpect(error.reason).not.toContain("department");
159
+ }));
160
+ });
161
+ //#endregion
162
+ export {};
163
+
164
+ //# sourceMappingURL=Decision.test.mjs.map