@effect-uai/core 0.5.1 → 0.6.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 (137) hide show
  1. package/README.md +1 -1
  2. package/dist/{Items-BH8xUkoR.d.mts → Items-pXR5BAgW.d.mts} +12 -12
  3. package/dist/Items-pXR5BAgW.d.mts.map +1 -0
  4. package/dist/SandboxError-BvKypkJm.d.mts +158 -0
  5. package/dist/SandboxError-BvKypkJm.d.mts.map +1 -0
  6. package/dist/{Tool-87ViKCCO.d.mts → Tool-DP-AwKOf.d.mts} +11 -11
  7. package/dist/{Tool-87ViKCCO.d.mts.map → Tool-DP-AwKOf.d.mts.map} +1 -1
  8. package/dist/domain/Items.d.mts +2 -2
  9. package/dist/domain/Items.mjs +15 -15
  10. package/dist/domain/Items.mjs.map +1 -1
  11. package/dist/domain/Turn.d.mts +388 -2
  12. package/dist/domain/Turn.d.mts.map +1 -0
  13. package/dist/domain/Turn.mjs +13 -13
  14. package/dist/domain/Turn.mjs.map +1 -1
  15. package/dist/index.d.mts +12 -6
  16. package/dist/index.mjs +9 -3
  17. package/dist/language-model/LanguageModel.d.mts +29 -27
  18. package/dist/language-model/LanguageModel.d.mts.map +1 -1
  19. package/dist/language-model/LanguageModel.mjs +16 -25
  20. package/dist/language-model/LanguageModel.mjs.map +1 -1
  21. package/dist/language-model/LanguageModel.test.mjs +10 -81
  22. package/dist/language-model/LanguageModel.test.mjs.map +1 -1
  23. package/dist/loop/Loop.d.mts +37 -90
  24. package/dist/loop/Loop.d.mts.map +1 -1
  25. package/dist/loop/Loop.mjs +39 -93
  26. package/dist/loop/Loop.mjs.map +1 -1
  27. package/dist/loop/Loop.test.mjs +46 -54
  28. package/dist/loop/Loop.test.mjs.map +1 -1
  29. package/dist/retry/Retry.d.mts +64 -0
  30. package/dist/retry/Retry.d.mts.map +1 -0
  31. package/dist/retry/Retry.mjs +69 -0
  32. package/dist/retry/Retry.mjs.map +1 -0
  33. package/dist/retry/Retry.test.mjs +142 -0
  34. package/dist/retry/Retry.test.mjs.map +1 -0
  35. package/dist/sandbox/Memory.d.mts +53 -0
  36. package/dist/sandbox/Memory.d.mts.map +1 -0
  37. package/dist/sandbox/Memory.mjs +68 -0
  38. package/dist/sandbox/Memory.mjs.map +1 -0
  39. package/dist/sandbox/Sandbox.d.mts +717 -0
  40. package/dist/sandbox/Sandbox.d.mts.map +1 -0
  41. package/dist/sandbox/Sandbox.mjs +192 -0
  42. package/dist/sandbox/Sandbox.mjs.map +1 -0
  43. package/dist/sandbox/SandboxError.d.mts +2 -0
  44. package/dist/sandbox/SandboxError.mjs +89 -0
  45. package/dist/sandbox/SandboxError.mjs.map +1 -0
  46. package/dist/sandbox/SandboxImage.d.mts +28 -0
  47. package/dist/sandbox/SandboxImage.d.mts.map +1 -0
  48. package/dist/sandbox/SandboxImage.mjs +32 -0
  49. package/dist/sandbox/SandboxImage.mjs.map +1 -0
  50. package/dist/sandbox/SandboxNetwork.d.mts +34 -0
  51. package/dist/sandbox/SandboxNetwork.d.mts.map +1 -0
  52. package/dist/sandbox/SandboxNetwork.mjs +36 -0
  53. package/dist/sandbox/SandboxNetwork.mjs.map +1 -0
  54. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +98 -2
  55. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -1
  56. package/dist/speech-synthesizer/SpeechSynthesizer.mjs +32 -2
  57. package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
  58. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +56 -2
  59. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -1
  60. package/dist/testing/MockProvider.d.mts +3 -3
  61. package/dist/testing/MockProvider.d.mts.map +1 -1
  62. package/dist/testing/MockProvider.mjs +14 -8
  63. package/dist/testing/MockProvider.mjs.map +1 -1
  64. package/dist/testing/MockSandbox.d.mts +604 -0
  65. package/dist/testing/MockSandbox.d.mts.map +1 -0
  66. package/dist/testing/MockSandbox.mjs +160 -0
  67. package/dist/testing/MockSandbox.mjs.map +1 -0
  68. package/dist/testing/MockSpeechSynthesizer.d.mts +26 -11
  69. package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -1
  70. package/dist/testing/MockSpeechSynthesizer.mjs +75 -51
  71. package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -1
  72. package/dist/tool/{Resolvers.d.mts → Approval.d.mts} +27 -27
  73. package/dist/tool/Approval.d.mts.map +1 -0
  74. package/dist/tool/{Resolvers.mjs → Approval.mjs} +18 -18
  75. package/dist/tool/Approval.mjs.map +1 -0
  76. package/dist/tool/Approval.test.d.mts +1 -0
  77. package/dist/tool/{Resolvers.test.mjs → Approval.test.mjs} +40 -40
  78. package/dist/tool/Approval.test.mjs.map +1 -0
  79. package/dist/tool/HistoryCheck.d.mts +6 -6
  80. package/dist/tool/HistoryCheck.d.mts.map +1 -1
  81. package/dist/tool/HistoryCheck.mjs +5 -5
  82. package/dist/tool/HistoryCheck.mjs.map +1 -1
  83. package/dist/tool/Tool.d.mts +2 -2
  84. package/dist/tool/Tool.mjs +4 -4
  85. package/dist/tool/Tool.mjs.map +1 -1
  86. package/dist/tool/ToolEvent.d.mts +19 -19
  87. package/dist/tool/ToolEvent.d.mts.map +1 -1
  88. package/dist/tool/ToolEvent.mjs +8 -8
  89. package/dist/tool/ToolEvent.mjs.map +1 -1
  90. package/dist/tool/{Outcome.d.mts → ToolResult.d.mts} +29 -29
  91. package/dist/tool/ToolResult.d.mts.map +1 -0
  92. package/dist/tool/ToolResult.mjs +61 -0
  93. package/dist/tool/ToolResult.mjs.map +1 -0
  94. package/dist/tool/Toolkit.d.mts +58 -26
  95. package/dist/tool/Toolkit.d.mts.map +1 -1
  96. package/dist/tool/Toolkit.mjs +62 -34
  97. package/dist/tool/Toolkit.mjs.map +1 -1
  98. package/dist/tool/Toolkit.test.mjs +13 -13
  99. package/dist/tool/Toolkit.test.mjs.map +1 -1
  100. package/package.json +35 -7
  101. package/src/domain/Items.ts +9 -9
  102. package/src/domain/Turn.ts +16 -16
  103. package/src/index.ts +8 -2
  104. package/src/language-model/LanguageModel.test.ts +21 -102
  105. package/src/language-model/LanguageModel.ts +36 -59
  106. package/src/loop/Loop.test.ts +93 -91
  107. package/src/loop/Loop.ts +72 -170
  108. package/src/retry/Retry.test.ts +163 -0
  109. package/src/retry/Retry.ts +84 -0
  110. package/src/sandbox/Memory.ts +128 -0
  111. package/src/sandbox/Sandbox.ts +639 -0
  112. package/src/sandbox/SandboxError.ts +140 -0
  113. package/src/sandbox/SandboxImage.ts +26 -0
  114. package/src/sandbox/SandboxNetwork.ts +34 -0
  115. package/src/speech-synthesizer/SpeechSynthesizer.test.ts +89 -0
  116. package/src/speech-synthesizer/SpeechSynthesizer.ts +124 -0
  117. package/src/testing/MockProvider.ts +31 -25
  118. package/src/testing/MockSandbox.ts +342 -0
  119. package/src/testing/MockSpeechSynthesizer.ts +137 -77
  120. package/src/tool/{Resolvers.test.ts → Approval.test.ts} +45 -54
  121. package/src/tool/{Resolvers.ts → Approval.ts} +25 -25
  122. package/src/tool/HistoryCheck.ts +10 -13
  123. package/src/tool/Tool.ts +13 -15
  124. package/src/tool/ToolEvent.ts +7 -7
  125. package/src/tool/{Outcome.ts → ToolResult.ts} +20 -20
  126. package/src/tool/Toolkit.test.ts +13 -13
  127. package/src/tool/Toolkit.ts +97 -73
  128. package/dist/Items-BH8xUkoR.d.mts.map +0 -1
  129. package/dist/Turn-0CwCAyVe.d.mts +0 -388
  130. package/dist/Turn-0CwCAyVe.d.mts.map +0 -1
  131. package/dist/tool/Outcome.d.mts.map +0 -1
  132. package/dist/tool/Outcome.mjs +0 -61
  133. package/dist/tool/Outcome.mjs.map +0 -1
  134. package/dist/tool/Resolvers.d.mts.map +0 -1
  135. package/dist/tool/Resolvers.mjs.map +0 -1
  136. package/dist/tool/Resolvers.test.mjs.map +0 -1
  137. /package/dist/{tool/Resolvers.test.d.mts → retry/Retry.test.d.mts} +0 -0
@@ -0,0 +1,84 @@
1
+ import { Data, Effect, type Schedule, Stream } from "effect"
2
+ import type * as AiError from "../domain/AiError.js"
3
+
4
+ /**
5
+ * Subset-aware retry helpers for `AiError`.
6
+ *
7
+ * Effect's own `Effect.retry` / `Stream.retry` apply the schedule to
8
+ * every failure. For AI calls that's the wrong policy — replaying a
9
+ * `ContentFiltered` or `InvalidRequest` won't change the outcome and
10
+ * just wastes quota. These helpers gate the schedule on the
11
+ * {@link Retryable} subset (rate limits, transport hiccups, timeouts);
12
+ * everything else propagates unchanged.
13
+ *
14
+ * Two carriers:
15
+ * - {@link stream} — for `Stream<A, AiError, R>` (e.g. `streamTurn`,
16
+ * `streamSynthesis`, `streamTranscriptionFrom`). Caveat: the entire
17
+ * stream re-runs on retry, so deltas before the failure replay on
18
+ * the next attempt.
19
+ * - {@link effect} — for `Effect<A, AiError, R>` (e.g. `turn`, `embed`,
20
+ * `embedMany`, `synthesize`, `transcribe`). At-most-once.
21
+ *
22
+ * Composes with `Stream.retry` / `Effect.retry` directly (no naming
23
+ * collision since these live under the `Retry` namespace).
24
+ */
25
+
26
+ /**
27
+ * Wrapper around the retryable subset of `AiError`. Internal to the
28
+ * lift/retry/unlift dance — exported so callers can build their own
29
+ * variants if needed (e.g. a custom schedule that observes `cause`).
30
+ */
31
+ export class Retryable extends Data.TaggedError("RetryableAi")<{
32
+ readonly cause: AiError.RateLimited | AiError.Unavailable | AiError.Timeout
33
+ }> {}
34
+
35
+ /** Type-narrowing predicate for the retryable subset. */
36
+ export const isRetryable = (
37
+ e: AiError.AiError,
38
+ ): e is AiError.RateLimited | AiError.Unavailable | AiError.Timeout =>
39
+ e._tag === "RateLimited" || e._tag === "Unavailable" || e._tag === "Timeout"
40
+
41
+ // Lift events to Items, non-retryable failures to Terminal values
42
+ // (escape retry), retryable failures to wrapped errors (only thing
43
+ // retry sees).
44
+ type Lifted<A> =
45
+ | { readonly _tag: "Item"; readonly value: A }
46
+ | { readonly _tag: "Terminal"; readonly cause: AiError.AiError }
47
+
48
+ /**
49
+ * Retry a `Stream<A, AiError, R>` on the retryable subset. Other
50
+ * failures bypass the schedule and propagate unchanged. The whole
51
+ * stream replays on each attempt — deltas emitted before the failure
52
+ * are re-emitted on the next try.
53
+ */
54
+ export const stream =
55
+ <Out>(schedule: Schedule.Schedule<Out, Retryable>) =>
56
+ <A, R>(s: Stream.Stream<A, AiError.AiError, R>): Stream.Stream<A, AiError.AiError, R> =>
57
+ s.pipe(
58
+ Stream.map((value): Lifted<A> => ({ _tag: "Item", value })),
59
+ Stream.catchIf(
60
+ isRetryable,
61
+ (cause) => Stream.fail(new Retryable({ cause })),
62
+ (cause) => Stream.succeed<Lifted<A>>({ _tag: "Terminal", cause }),
63
+ ),
64
+ Stream.retry(schedule),
65
+ Stream.catchTag("RetryableAi", (e) => Stream.fail<AiError.AiError>(e.cause)),
66
+ Stream.flatMap((item) =>
67
+ item._tag === "Item" ? Stream.succeed(item.value) : Stream.fail(item.cause),
68
+ ),
69
+ )
70
+
71
+ /**
72
+ * Retry an `Effect<A, AiError, R>` on the retryable subset. Other
73
+ * failures bypass the schedule and propagate unchanged. At-most-once
74
+ * — the underlying call only re-runs if the previous attempt failed
75
+ * with a retryable tag.
76
+ *
77
+ * Skips the lift/unlift dance the Stream variant needs: `Effect.retry`
78
+ * exposes a `while:` predicate so the error channel stays a plain
79
+ * `AiError` throughout.
80
+ */
81
+ export const effect =
82
+ <Out>(schedule: Schedule.Schedule<Out, AiError.AiError>) =>
83
+ <A, R>(eff: Effect.Effect<A, AiError.AiError, R>): Effect.Effect<A, AiError.AiError, R> =>
84
+ Effect.retry(eff, { schedule, while: isRetryable })
@@ -0,0 +1,128 @@
1
+ import { Brand } from "effect"
2
+
3
+ /**
4
+ * A memory / data size, always represented internally as a number of
5
+ * bytes. Mirrors the `Duration` shape from `effect` — a branded value
6
+ * type with a wider `Input` union for ergonomic construction at call
7
+ * sites.
8
+ *
9
+ * `effect` itself has no byte-count primitive (only `Duration`), so we
10
+ * own this here rather than leak provider-specific shapes (Deno's
11
+ * template-literal `Memory`, Vercel's `mb`, Modal's `gpuMemory`, …)
12
+ * through the cross-provider `Sandbox` request types.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import * as Memory from "@effect-uai/core/Memory"
17
+ *
18
+ * Memory.gib(2) // 2 GiB → 2_147_483_648 bytes
19
+ * Memory.fromInputUnsafe("1.5 GiB") // human-string parsing
20
+ * Memory.fromInputUnsafe(1_280_000_000) // raw bytes
21
+ * Memory.toBytes(m) // unwrap
22
+ * ```
23
+ */
24
+
25
+ export type Memory = Brand.Branded<number, "Memory">
26
+
27
+ const make = Brand.nominal<Memory>()
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Input — what the public APIs accept.
31
+ //
32
+ // Supported string forms (case-insensitive on the unit, whitespace
33
+ // optional between number and unit):
34
+ //
35
+ // "<n>" bytes (no unit)
36
+ // "<n> B" bytes
37
+ // "<n> kB" 1_000 bytes (decimal)
38
+ // "<n> MB" 1_000_000 (decimal)
39
+ // "<n> GB" 1_000_000_000 (decimal)
40
+ // "<n> KiB" 1_024 (binary)
41
+ // "<n> MiB" 1_048_576 (binary)
42
+ // "<n> GiB" 1_073_741_824 (binary)
43
+ //
44
+ // Decimal vs binary follows the SI / IEC convention (`MB` = 10^6,
45
+ // `MiB` = 2^20). When in doubt, prefer the binary `*iB` units.
46
+ // ---------------------------------------------------------------------------
47
+
48
+ export type Input = number | Memory | string
49
+
50
+ const UNIT_BYTES: Record<string, number> = {
51
+ b: 1,
52
+ kb: 1_000,
53
+ mb: 1_000_000,
54
+ gb: 1_000_000_000,
55
+ tb: 1_000_000_000_000,
56
+ kib: 1_024,
57
+ mib: 1_024 * 1_024,
58
+ gib: 1_024 * 1_024 * 1_024,
59
+ tib: 1_024 * 1_024 * 1_024 * 1_024,
60
+ }
61
+
62
+ const PARSE_RE = /^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]*)\s*$/
63
+
64
+ const parseString = (s: string): number => {
65
+ const m = PARSE_RE.exec(s)
66
+ if (m === null) {
67
+ throw new Error(`Memory: cannot parse "${s}" — expected forms like "1024", "10 MB", "1GiB"`)
68
+ }
69
+ const value = Number(m[1])
70
+ const unit = (m[2] ?? "").toLowerCase()
71
+ if (unit === "") return value
72
+ const factor = UNIT_BYTES[unit]
73
+ if (factor === undefined) {
74
+ throw new Error(
75
+ `Memory: unknown unit "${m[2]}" in "${s}" — supported: ${Object.keys(UNIT_BYTES).join(", ")}`,
76
+ )
77
+ }
78
+ return value * factor
79
+ }
80
+
81
+ /**
82
+ * Coerce a {@link Input} to a {@link Memory}. Throws on malformed
83
+ * strings or non-finite numbers. Mirrors `Duration.fromInputUnsafe`.
84
+ */
85
+ export const fromInputUnsafe = (input: Input): Memory => {
86
+ if (typeof input === "number") {
87
+ if (!Number.isFinite(input) || input < 0) {
88
+ throw new Error(`Memory: bytes must be a non-negative finite number, got ${input}`)
89
+ }
90
+ return make(Math.floor(input))
91
+ }
92
+ return make(Math.floor(parseString(input)))
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Unwrap
97
+ // ---------------------------------------------------------------------------
98
+
99
+ /** Return the underlying byte count. */
100
+ export const toBytes = (m: Memory | Input): number =>
101
+ typeof m === "number" && (m as number) === Math.floor(m as number) && (m as number) >= 0
102
+ ? (m as number)
103
+ : (fromInputUnsafe(m) as unknown as number)
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Unit constructors — terse for use at call sites.
107
+ // ---------------------------------------------------------------------------
108
+
109
+ /** N bytes. */
110
+ export const bytes = (n: number): Memory => make(Math.floor(n))
111
+
112
+ /** N KiB (binary kibibytes, `n * 1024`). */
113
+ export const kib = (n: number): Memory => make(Math.floor(n * 1_024))
114
+
115
+ /** N MiB (binary mebibytes, `n * 1024^2`). */
116
+ export const mib = (n: number): Memory => make(Math.floor(n * 1_024 * 1_024))
117
+
118
+ /** N GiB (binary gibibytes, `n * 1024^3`). */
119
+ export const gib = (n: number): Memory => make(Math.floor(n * 1_024 * 1_024 * 1_024))
120
+
121
+ /** N kB (decimal kilobytes, `n * 1000`). */
122
+ export const kb = (n: number): Memory => make(Math.floor(n * 1_000))
123
+
124
+ /** N MB (decimal megabytes, `n * 1_000_000`). */
125
+ export const mb = (n: number): Memory => make(Math.floor(n * 1_000_000))
126
+
127
+ /** N GB (decimal gigabytes, `n * 1_000_000_000`). */
128
+ export const gb = (n: number): Memory => make(Math.floor(n * 1_000_000_000))