@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,69 @@
1
+ import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
2
+ import { Data, Effect, Stream } from "effect";
3
+ //#region src/retry/Retry.ts
4
+ var Retry_exports = /* @__PURE__ */ __exportAll({
5
+ Retryable: () => Retryable,
6
+ effect: () => effect,
7
+ isRetryable: () => isRetryable,
8
+ stream: () => stream
9
+ });
10
+ /**
11
+ * Subset-aware retry helpers for `AiError`.
12
+ *
13
+ * Effect's own `Effect.retry` / `Stream.retry` apply the schedule to
14
+ * every failure. For AI calls that's the wrong policy — replaying a
15
+ * `ContentFiltered` or `InvalidRequest` won't change the outcome and
16
+ * just wastes quota. These helpers gate the schedule on the
17
+ * {@link Retryable} subset (rate limits, transport hiccups, timeouts);
18
+ * everything else propagates unchanged.
19
+ *
20
+ * Two carriers:
21
+ * - {@link stream} — for `Stream<A, AiError, R>` (e.g. `streamTurn`,
22
+ * `streamSynthesis`, `streamTranscriptionFrom`). Caveat: the entire
23
+ * stream re-runs on retry, so deltas before the failure replay on
24
+ * the next attempt.
25
+ * - {@link effect} — for `Effect<A, AiError, R>` (e.g. `turn`, `embed`,
26
+ * `embedMany`, `synthesize`, `transcribe`). At-most-once.
27
+ *
28
+ * Composes with `Stream.retry` / `Effect.retry` directly (no naming
29
+ * collision since these live under the `Retry` namespace).
30
+ */
31
+ /**
32
+ * Wrapper around the retryable subset of `AiError`. Internal to the
33
+ * lift/retry/unlift dance — exported so callers can build their own
34
+ * variants if needed (e.g. a custom schedule that observes `cause`).
35
+ */
36
+ var Retryable = class extends Data.TaggedError("RetryableAi") {};
37
+ /** Type-narrowing predicate for the retryable subset. */
38
+ const isRetryable = (e) => e._tag === "RateLimited" || e._tag === "Unavailable" || e._tag === "Timeout";
39
+ /**
40
+ * Retry a `Stream<A, AiError, R>` on the retryable subset. Other
41
+ * failures bypass the schedule and propagate unchanged. The whole
42
+ * stream replays on each attempt — deltas emitted before the failure
43
+ * are re-emitted on the next try.
44
+ */
45
+ const stream = (schedule) => (s) => s.pipe(Stream.map((value) => ({
46
+ _tag: "Item",
47
+ value
48
+ })), Stream.catchIf(isRetryable, (cause) => Stream.fail(new Retryable({ cause })), (cause) => Stream.succeed({
49
+ _tag: "Terminal",
50
+ cause
51
+ })), Stream.retry(schedule), Stream.catchTag("RetryableAi", (e) => Stream.fail(e.cause)), Stream.flatMap((item) => item._tag === "Item" ? Stream.succeed(item.value) : Stream.fail(item.cause)));
52
+ /**
53
+ * Retry an `Effect<A, AiError, R>` on the retryable subset. Other
54
+ * failures bypass the schedule and propagate unchanged. At-most-once
55
+ * — the underlying call only re-runs if the previous attempt failed
56
+ * with a retryable tag.
57
+ *
58
+ * Skips the lift/unlift dance the Stream variant needs: `Effect.retry`
59
+ * exposes a `while:` predicate so the error channel stays a plain
60
+ * `AiError` throughout.
61
+ */
62
+ const effect = (schedule) => (eff) => Effect.retry(eff, {
63
+ schedule,
64
+ while: isRetryable
65
+ });
66
+ //#endregion
67
+ export { Retryable, effect, isRetryable, stream, Retry_exports as t };
68
+
69
+ //# sourceMappingURL=Retry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Retry.mjs","names":[],"sources":["../../src/retry/Retry.ts"],"sourcesContent":["import { Data, Effect, type Schedule, Stream } from \"effect\"\nimport type * as AiError from \"../domain/AiError.js\"\n\n/**\n * Subset-aware retry helpers for `AiError`.\n *\n * Effect's own `Effect.retry` / `Stream.retry` apply the schedule to\n * every failure. For AI calls that's the wrong policy — replaying a\n * `ContentFiltered` or `InvalidRequest` won't change the outcome and\n * just wastes quota. These helpers gate the schedule on the\n * {@link Retryable} subset (rate limits, transport hiccups, timeouts);\n * everything else propagates unchanged.\n *\n * Two carriers:\n * - {@link stream} — for `Stream<A, AiError, R>` (e.g. `streamTurn`,\n * `streamSynthesis`, `streamTranscriptionFrom`). Caveat: the entire\n * stream re-runs on retry, so deltas before the failure replay on\n * the next attempt.\n * - {@link effect} — for `Effect<A, AiError, R>` (e.g. `turn`, `embed`,\n * `embedMany`, `synthesize`, `transcribe`). At-most-once.\n *\n * Composes with `Stream.retry` / `Effect.retry` directly (no naming\n * collision since these live under the `Retry` namespace).\n */\n\n/**\n * Wrapper around the retryable subset of `AiError`. Internal to the\n * lift/retry/unlift dance — exported so callers can build their own\n * variants if needed (e.g. a custom schedule that observes `cause`).\n */\nexport class Retryable extends Data.TaggedError(\"RetryableAi\")<{\n readonly cause: AiError.RateLimited | AiError.Unavailable | AiError.Timeout\n}> {}\n\n/** Type-narrowing predicate for the retryable subset. */\nexport const isRetryable = (\n e: AiError.AiError,\n): e is AiError.RateLimited | AiError.Unavailable | AiError.Timeout =>\n e._tag === \"RateLimited\" || e._tag === \"Unavailable\" || e._tag === \"Timeout\"\n\n// Lift events to Items, non-retryable failures to Terminal values\n// (escape retry), retryable failures to wrapped errors (only thing\n// retry sees).\ntype Lifted<A> =\n | { readonly _tag: \"Item\"; readonly value: A }\n | { readonly _tag: \"Terminal\"; readonly cause: AiError.AiError }\n\n/**\n * Retry a `Stream<A, AiError, R>` on the retryable subset. Other\n * failures bypass the schedule and propagate unchanged. The whole\n * stream replays on each attempt — deltas emitted before the failure\n * are re-emitted on the next try.\n */\nexport const stream =\n <Out>(schedule: Schedule.Schedule<Out, Retryable>) =>\n <A, R>(s: Stream.Stream<A, AiError.AiError, R>): Stream.Stream<A, AiError.AiError, R> =>\n s.pipe(\n Stream.map((value): Lifted<A> => ({ _tag: \"Item\", value })),\n Stream.catchIf(\n isRetryable,\n (cause) => Stream.fail(new Retryable({ cause })),\n (cause) => Stream.succeed<Lifted<A>>({ _tag: \"Terminal\", cause }),\n ),\n Stream.retry(schedule),\n Stream.catchTag(\"RetryableAi\", (e) => Stream.fail<AiError.AiError>(e.cause)),\n Stream.flatMap((item) =>\n item._tag === \"Item\" ? Stream.succeed(item.value) : Stream.fail(item.cause),\n ),\n )\n\n/**\n * Retry an `Effect<A, AiError, R>` on the retryable subset. Other\n * failures bypass the schedule and propagate unchanged. At-most-once\n * — the underlying call only re-runs if the previous attempt failed\n * with a retryable tag.\n *\n * Skips the lift/unlift dance the Stream variant needs: `Effect.retry`\n * exposes a `while:` predicate so the error channel stays a plain\n * `AiError` throughout.\n */\nexport const effect =\n <Out>(schedule: Schedule.Schedule<Out, AiError.AiError>) =>\n <A, R>(eff: Effect.Effect<A, AiError.AiError, R>): Effect.Effect<A, AiError.AiError, R> =>\n Effect.retry(eff, { schedule, while: isRetryable })\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,YAAb,cAA+B,KAAK,YAAY,cAAc,CAE3D;;AAGH,MAAa,eACX,MAEA,EAAE,SAAS,iBAAiB,EAAE,SAAS,iBAAiB,EAAE,SAAS;;;;;;;AAerE,MAAa,UACL,cACC,MACL,EAAE,KACA,OAAO,KAAK,WAAsB;CAAE,MAAM;CAAQ;CAAO,EAAE,EAC3D,OAAO,QACL,cACC,UAAU,OAAO,KAAK,IAAI,UAAU,EAAE,OAAO,CAAC,CAAC,GAC/C,UAAU,OAAO,QAAmB;CAAE,MAAM;CAAY;CAAO,CAAC,CAClE,EACD,OAAO,MAAM,SAAS,EACtB,OAAO,SAAS,gBAAgB,MAAM,OAAO,KAAsB,EAAE,MAAM,CAAC,EAC5E,OAAO,SAAS,SACd,KAAK,SAAS,SAAS,OAAO,QAAQ,KAAK,MAAM,GAAG,OAAO,KAAK,KAAK,MAAM,CAC5E,CACF;;;;;;;;;;;AAYL,MAAa,UACL,cACC,QACL,OAAO,MAAM,KAAK;CAAE;CAAU,OAAO;CAAa,CAAC"}
@@ -0,0 +1,142 @@
1
+ import { ContentFiltered, RateLimited, Timeout, Unavailable } from "../domain/AiError.mjs";
2
+ import { effect, stream } from "./Retry.mjs";
3
+ import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
4
+ import { Cause, Effect, Exit, Option, Ref, Schedule, Stream } from "effect";
5
+ //#region src/retry/Retry.test.ts
6
+ const planned = (attempts, plan) => Stream.unwrap(Ref.getAndUpdate(attempts, (n) => n + 1).pipe(Effect.map((n) => plan[Math.min(n, plan.length - 1)])));
7
+ const plannedEffect = (attempts, plan) => Ref.getAndUpdate(attempts, (n) => n + 1).pipe(Effect.flatMap((n) => plan[Math.min(n, plan.length - 1)]));
8
+ describe("Retry.stream", () => {
9
+ it("retries on RateLimited and yields success on the next attempt", async () => {
10
+ const program = Effect.gen(function* () {
11
+ const attempts = yield* Ref.make(0);
12
+ const s = planned(attempts, [Stream.fail(new RateLimited({
13
+ provider: "mock",
14
+ raw: null
15
+ })), Stream.fromIterable(["a", "b"])]).pipe(stream(Schedule.recurs(3)));
16
+ const out = yield* Stream.runCollect(s);
17
+ const count = yield* Ref.get(attempts);
18
+ return {
19
+ out: Array.from(out),
20
+ count
21
+ };
22
+ });
23
+ const { out, count } = await Effect.runPromise(program);
24
+ globalExpect(count).toBe(2);
25
+ globalExpect(out).toEqual(["a", "b"]);
26
+ });
27
+ it("surfaces the underlying retryable failure when retries are exhausted", async () => {
28
+ const cause = new Unavailable({
29
+ provider: "mock",
30
+ raw: null
31
+ });
32
+ const s = Stream.fail(cause).pipe(stream(Schedule.recurs(2)));
33
+ const exit = await Effect.runPromise(Effect.exit(Stream.runCollect(s)));
34
+ globalExpect(Exit.isFailure(exit)).toBe(true);
35
+ if (Exit.isFailure(exit)) globalExpect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause));
36
+ });
37
+ it("bypasses retry for non-retryable AiError (ContentFiltered)", async () => {
38
+ const program = Effect.gen(function* () {
39
+ const attempts = yield* Ref.make(0);
40
+ const cause = new ContentFiltered({
41
+ provider: "mock",
42
+ raw: null
43
+ });
44
+ const s = planned(attempts, [Stream.fail(cause)]).pipe(stream(Schedule.recurs(5)));
45
+ return {
46
+ exit: yield* Effect.exit(Stream.runCollect(s)),
47
+ count: yield* Ref.get(attempts),
48
+ cause
49
+ };
50
+ });
51
+ const { exit, count, cause } = await Effect.runPromise(program);
52
+ globalExpect(count).toBe(1);
53
+ globalExpect(Exit.isFailure(exit)).toBe(true);
54
+ if (Exit.isFailure(exit)) globalExpect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause));
55
+ });
56
+ it("preserves items emitted before a retryable failure (and replays on retry)", async () => {
57
+ const program = Effect.gen(function* () {
58
+ const s = planned(yield* Ref.make(0), [Stream.concat(Stream.succeed("partial"), Stream.fail(new Timeout({
59
+ provider: "mock",
60
+ raw: null
61
+ }))), Stream.fromIterable(["partial", "done"])]).pipe(stream(Schedule.recurs(1)));
62
+ const out = yield* Stream.runCollect(s);
63
+ return Array.from(out);
64
+ });
65
+ globalExpect(await Effect.runPromise(program)).toEqual([
66
+ "partial",
67
+ "partial",
68
+ "done"
69
+ ]);
70
+ });
71
+ });
72
+ describe("Retry.effect", () => {
73
+ it("retries on RateLimited and yields success on the next attempt", async () => {
74
+ const program = Effect.gen(function* () {
75
+ const attempts = yield* Ref.make(0);
76
+ return {
77
+ value: yield* plannedEffect(attempts, [Effect.fail(new RateLimited({
78
+ provider: "mock",
79
+ raw: null
80
+ })), Effect.succeed("ok")]).pipe(effect(Schedule.recurs(3))),
81
+ count: yield* Ref.get(attempts)
82
+ };
83
+ });
84
+ const { value, count } = await Effect.runPromise(program);
85
+ globalExpect(count).toBe(2);
86
+ globalExpect(value).toBe("ok");
87
+ });
88
+ it("surfaces the underlying retryable failure when retries are exhausted", async () => {
89
+ const cause = new Unavailable({
90
+ provider: "mock",
91
+ raw: null
92
+ });
93
+ const eff = Effect.fail(cause).pipe(effect(Schedule.recurs(2)));
94
+ const exit = await Effect.runPromise(Effect.exit(eff));
95
+ globalExpect(Exit.isFailure(exit)).toBe(true);
96
+ if (Exit.isFailure(exit)) globalExpect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause));
97
+ });
98
+ it("bypasses retry for non-retryable AiError (ContentFiltered)", async () => {
99
+ const program = Effect.gen(function* () {
100
+ const attempts = yield* Ref.make(0);
101
+ const cause = new ContentFiltered({
102
+ provider: "mock",
103
+ raw: null
104
+ });
105
+ return {
106
+ exit: yield* Effect.exit(plannedEffect(attempts, [Effect.fail(cause)]).pipe(effect(Schedule.recurs(5)))),
107
+ count: yield* Ref.get(attempts),
108
+ cause
109
+ };
110
+ });
111
+ const { exit, count, cause } = await Effect.runPromise(program);
112
+ globalExpect(count).toBe(1);
113
+ globalExpect(Exit.isFailure(exit)).toBe(true);
114
+ if (Exit.isFailure(exit)) globalExpect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause));
115
+ });
116
+ it("does NOT replay successful prior work — at-most-once semantics", async () => {
117
+ const program = Effect.gen(function* () {
118
+ const attempts = yield* Ref.make(0);
119
+ return {
120
+ value: yield* plannedEffect(attempts, [
121
+ Effect.fail(new Timeout({
122
+ provider: "mock",
123
+ raw: null
124
+ })),
125
+ Effect.fail(new Timeout({
126
+ provider: "mock",
127
+ raw: null
128
+ })),
129
+ Effect.succeed("done")
130
+ ]).pipe(effect(Schedule.recurs(5))),
131
+ count: yield* Ref.get(attempts)
132
+ };
133
+ });
134
+ const { value, count } = await Effect.runPromise(program);
135
+ globalExpect(count).toBe(3);
136
+ globalExpect(value).toBe("done");
137
+ });
138
+ });
139
+ //#endregion
140
+ export {};
141
+
142
+ //# sourceMappingURL=Retry.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Retry.test.mjs","names":["AiError.RateLimited","Retry.stream","AiError.Unavailable","AiError.ContentFiltered","AiError.Timeout","Retry.effect"],"sources":["../../src/retry/Retry.test.ts"],"sourcesContent":["import { Cause, Effect, Exit, Option, Ref, Schedule, Stream } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as AiError from \"../domain/AiError.js\"\nimport * as Retry from \"./Retry.js\"\n\n// Each call pulls the next plan entry; the last entry repeats forever\n// (so \"succeed on attempt N\" plans naturally re-yield success after).\nconst planned = <A, E>(\n attempts: Ref.Ref<number>,\n plan: ReadonlyArray<Stream.Stream<A, E>>,\n): Stream.Stream<A, E> =>\n Stream.unwrap(\n Ref.getAndUpdate(attempts, (n) => n + 1).pipe(\n Effect.map((n) => plan[Math.min(n, plan.length - 1)]!),\n ),\n )\n\nconst plannedEffect = <A, E>(\n attempts: Ref.Ref<number>,\n plan: ReadonlyArray<Effect.Effect<A, E>>,\n): Effect.Effect<A, E> =>\n Ref.getAndUpdate(attempts, (n) => n + 1).pipe(\n Effect.flatMap((n) => plan[Math.min(n, plan.length - 1)]!),\n )\n\ndescribe(\"Retry.stream\", () => {\n it(\"retries on RateLimited and yields success on the next attempt\", async () => {\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const s = planned(attempts, [\n Stream.fail<AiError.AiError>(new AiError.RateLimited({ provider: \"mock\", raw: null })),\n Stream.fromIterable([\"a\", \"b\"]),\n ]).pipe(Retry.stream(Schedule.recurs(3)))\n const out = yield* Stream.runCollect(s)\n const count = yield* Ref.get(attempts)\n return { out: Array.from(out), count }\n })\n\n const { out, count } = await Effect.runPromise(program)\n expect(count).toBe(2)\n expect(out).toEqual([\"a\", \"b\"])\n })\n\n it(\"surfaces the underlying retryable failure when retries are exhausted\", async () => {\n const cause = new AiError.Unavailable({ provider: \"mock\", raw: null })\n const s = Stream.fail<AiError.AiError>(cause).pipe(Retry.stream(Schedule.recurs(2)))\n\n const exit = await Effect.runPromise(Effect.exit(Stream.runCollect(s)))\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n expect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause))\n }\n })\n\n it(\"bypasses retry for non-retryable AiError (ContentFiltered)\", async () => {\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const cause = new AiError.ContentFiltered({ provider: \"mock\", raw: null })\n const s = planned(attempts, [Stream.fail<AiError.AiError>(cause)]).pipe(\n Retry.stream(Schedule.recurs(5)),\n )\n const exit = yield* Effect.exit(Stream.runCollect(s))\n const count = yield* Ref.get(attempts)\n return { exit, count, cause }\n })\n\n const { exit, count, cause } = await Effect.runPromise(program)\n expect(count).toBe(1)\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n expect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause))\n }\n })\n\n it(\"preserves items emitted before a retryable failure (and replays on retry)\", async () => {\n // Documents the 'replays on retry' caveat: the entire stream re-runs.\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const s = planned(attempts, [\n Stream.concat(\n Stream.succeed(\"partial\"),\n Stream.fail<AiError.AiError>(new AiError.Timeout({ provider: \"mock\", raw: null })),\n ),\n Stream.fromIterable([\"partial\", \"done\"]),\n ]).pipe(Retry.stream(Schedule.recurs(1)))\n const out = yield* Stream.runCollect(s)\n return Array.from(out)\n })\n\n const out = await Effect.runPromise(program)\n expect(out).toEqual([\"partial\", \"partial\", \"done\"])\n })\n})\n\ndescribe(\"Retry.effect\", () => {\n it(\"retries on RateLimited and yields success on the next attempt\", async () => {\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const value = yield* plannedEffect(attempts, [\n Effect.fail<AiError.AiError>(new AiError.RateLimited({ provider: \"mock\", raw: null })),\n Effect.succeed(\"ok\"),\n ]).pipe(Retry.effect(Schedule.recurs(3)))\n const count = yield* Ref.get(attempts)\n return { value, count }\n })\n\n const { value, count } = await Effect.runPromise(program)\n expect(count).toBe(2)\n expect(value).toBe(\"ok\")\n })\n\n it(\"surfaces the underlying retryable failure when retries are exhausted\", async () => {\n const cause = new AiError.Unavailable({ provider: \"mock\", raw: null })\n const eff = Effect.fail<AiError.AiError>(cause).pipe(Retry.effect(Schedule.recurs(2)))\n\n const exit = await Effect.runPromise(Effect.exit(eff))\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n expect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause))\n }\n })\n\n it(\"bypasses retry for non-retryable AiError (ContentFiltered)\", async () => {\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const cause = new AiError.ContentFiltered({ provider: \"mock\", raw: null })\n const exit = yield* Effect.exit(\n plannedEffect(attempts, [Effect.fail<AiError.AiError>(cause)]).pipe(\n Retry.effect(Schedule.recurs(5)),\n ),\n )\n const count = yield* Ref.get(attempts)\n return { exit, count, cause }\n })\n\n const { exit, count, cause } = await Effect.runPromise(program)\n expect(count).toBe(1)\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n expect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause))\n }\n })\n\n it(\"does NOT replay successful prior work — at-most-once semantics\", async () => {\n // Contrast with Retry.stream: an Effect.retry only re-invokes the\n // failing computation. No replay of work done before the failure\n // (there's nothing to replay — Effects are single-shot).\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const value = yield* plannedEffect(attempts, [\n Effect.fail<AiError.AiError>(new AiError.Timeout({ provider: \"mock\", raw: null })),\n Effect.fail<AiError.AiError>(new AiError.Timeout({ provider: \"mock\", raw: null })),\n Effect.succeed(\"done\"),\n ]).pipe(Retry.effect(Schedule.recurs(5)))\n const count = yield* Ref.get(attempts)\n return { value, count }\n })\n\n const { value, count } = await Effect.runPromise(program)\n expect(count).toBe(3)\n expect(value).toBe(\"done\")\n })\n})\n"],"mappings":";;;;;AAOA,MAAM,WACJ,UACA,SAEA,OAAO,OACL,IAAI,aAAa,WAAW,MAAM,IAAI,EAAE,CAAC,KACvC,OAAO,KAAK,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,SAAS,EAAE,EAAG,CACvD,CACF;AAEH,MAAM,iBACJ,UACA,SAEA,IAAI,aAAa,WAAW,MAAM,IAAI,EAAE,CAAC,KACvC,OAAO,SAAS,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,SAAS,EAAE,EAAG,CAC3D;AAEH,SAAS,sBAAsB;AAC7B,IAAG,iEAAiE,YAAY;EAC9E,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,WAAW,OAAO,IAAI,KAAK,EAAE;GACnC,MAAM,IAAI,QAAQ,UAAU,CAC1B,OAAO,KAAsB,IAAIA,YAAoB;IAAE,UAAU;IAAQ,KAAK;IAAM,CAAC,CAAC,EACtF,OAAO,aAAa,CAAC,KAAK,IAAI,CAAC,CAChC,CAAC,CAAC,KAAKC,OAAa,SAAS,OAAO,EAAE,CAAC,CAAC;GACzC,MAAM,MAAM,OAAO,OAAO,WAAW,EAAE;GACvC,MAAM,QAAQ,OAAO,IAAI,IAAI,SAAS;AACtC,UAAO;IAAE,KAAK,MAAM,KAAK,IAAI;IAAE;IAAO;IACtC;EAEF,MAAM,EAAE,KAAK,UAAU,MAAM,OAAO,WAAW,QAAQ;AACvD,eAAO,MAAM,CAAC,KAAK,EAAE;AACrB,eAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;GAC/B;AAEF,IAAG,wEAAwE,YAAY;EACrF,MAAM,QAAQ,IAAIC,YAAoB;GAAE,UAAU;GAAQ,KAAK;GAAM,CAAC;EACtE,MAAM,IAAI,OAAO,KAAsB,MAAM,CAAC,KAAKD,OAAa,SAAS,OAAO,EAAE,CAAC,CAAC;EAEpF,MAAM,OAAO,MAAM,OAAO,WAAW,OAAO,KAAK,OAAO,WAAW,EAAE,CAAC,CAAC;AACvE,eAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAK,KAAK;AACvC,MAAI,KAAK,UAAU,KAAK,CACtB,cAAO,MAAM,gBAAgB,KAAK,MAAM,CAAC,CAAC,QAAQ,OAAO,KAAK,MAAM,CAAC;GAEvE;AAEF,IAAG,8DAA8D,YAAY;EAC3E,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,WAAW,OAAO,IAAI,KAAK,EAAE;GACnC,MAAM,QAAQ,IAAIE,gBAAwB;IAAE,UAAU;IAAQ,KAAK;IAAM,CAAC;GAC1E,MAAM,IAAI,QAAQ,UAAU,CAAC,OAAO,KAAsB,MAAM,CAAC,CAAC,CAAC,KACjEF,OAAa,SAAS,OAAO,EAAE,CAAC,CACjC;AAGD,UAAO;IAAE,MAAA,OAFW,OAAO,KAAK,OAAO,WAAW,EAAE,CAAC;IAEtC,OAAA,OADM,IAAI,IAAI,SAAS;IAChB;IAAO;IAC7B;EAEF,MAAM,EAAE,MAAM,OAAO,UAAU,MAAM,OAAO,WAAW,QAAQ;AAC/D,eAAO,MAAM,CAAC,KAAK,EAAE;AACrB,eAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAK,KAAK;AACvC,MAAI,KAAK,UAAU,KAAK,CACtB,cAAO,MAAM,gBAAgB,KAAK,MAAM,CAAC,CAAC,QAAQ,OAAO,KAAK,MAAM,CAAC;GAEvE;AAEF,IAAG,6EAA6E,YAAY;EAE1F,MAAM,UAAU,OAAO,IAAI,aAAa;GAEtC,MAAM,IAAI,QAAQ,OADM,IAAI,KAAK,EAAE,EACP,CAC1B,OAAO,OACL,OAAO,QAAQ,UAAU,EACzB,OAAO,KAAsB,IAAIG,QAAgB;IAAE,UAAU;IAAQ,KAAK;IAAM,CAAC,CAAC,CACnF,EACD,OAAO,aAAa,CAAC,WAAW,OAAO,CAAC,CACzC,CAAC,CAAC,KAAKH,OAAa,SAAS,OAAO,EAAE,CAAC,CAAC;GACzC,MAAM,MAAM,OAAO,OAAO,WAAW,EAAE;AACvC,UAAO,MAAM,KAAK,IAAI;IACtB;AAGF,eAAO,MADW,OAAO,WAAW,QAAQ,CACjC,CAAC,QAAQ;GAAC;GAAW;GAAW;GAAO,CAAC;GACnD;EACF;AAEF,SAAS,sBAAsB;AAC7B,IAAG,iEAAiE,YAAY;EAC9E,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,WAAW,OAAO,IAAI,KAAK,EAAE;AAMnC,UAAO;IAAE,OAAA,OALY,cAAc,UAAU,CAC3C,OAAO,KAAsB,IAAID,YAAoB;KAAE,UAAU;KAAQ,KAAK;KAAM,CAAC,CAAC,EACtF,OAAO,QAAQ,KAAK,CACrB,CAAC,CAAC,KAAKK,OAAa,SAAS,OAAO,EAAE,CAAC,CAAC;IAEzB,OAAA,OADK,IAAI,IAAI,SAAS;IACf;IACvB;EAEF,MAAM,EAAE,OAAO,UAAU,MAAM,OAAO,WAAW,QAAQ;AACzD,eAAO,MAAM,CAAC,KAAK,EAAE;AACrB,eAAO,MAAM,CAAC,KAAK,KAAK;GACxB;AAEF,IAAG,wEAAwE,YAAY;EACrF,MAAM,QAAQ,IAAIH,YAAoB;GAAE,UAAU;GAAQ,KAAK;GAAM,CAAC;EACtE,MAAM,MAAM,OAAO,KAAsB,MAAM,CAAC,KAAKG,OAAa,SAAS,OAAO,EAAE,CAAC,CAAC;EAEtF,MAAM,OAAO,MAAM,OAAO,WAAW,OAAO,KAAK,IAAI,CAAC;AACtD,eAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAK,KAAK;AACvC,MAAI,KAAK,UAAU,KAAK,CACtB,cAAO,MAAM,gBAAgB,KAAK,MAAM,CAAC,CAAC,QAAQ,OAAO,KAAK,MAAM,CAAC;GAEvE;AAEF,IAAG,8DAA8D,YAAY;EAC3E,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,WAAW,OAAO,IAAI,KAAK,EAAE;GACnC,MAAM,QAAQ,IAAIF,gBAAwB;IAAE,UAAU;IAAQ,KAAK;IAAM,CAAC;AAO1E,UAAO;IAAE,MAAA,OANW,OAAO,KACzB,cAAc,UAAU,CAAC,OAAO,KAAsB,MAAM,CAAC,CAAC,CAAC,KAC7DE,OAAa,SAAS,OAAO,EAAE,CAAC,CACjC,CACF;IAEc,OAAA,OADM,IAAI,IAAI,SAAS;IAChB;IAAO;IAC7B;EAEF,MAAM,EAAE,MAAM,OAAO,UAAU,MAAM,OAAO,WAAW,QAAQ;AAC/D,eAAO,MAAM,CAAC,KAAK,EAAE;AACrB,eAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAK,KAAK;AACvC,MAAI,KAAK,UAAU,KAAK,CACtB,cAAO,MAAM,gBAAgB,KAAK,MAAM,CAAC,CAAC,QAAQ,OAAO,KAAK,MAAM,CAAC;GAEvE;AAEF,IAAG,kEAAkE,YAAY;EAI/E,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,WAAW,OAAO,IAAI,KAAK,EAAE;AAOnC,UAAO;IAAE,OAAA,OANY,cAAc,UAAU;KAC3C,OAAO,KAAsB,IAAID,QAAgB;MAAE,UAAU;MAAQ,KAAK;MAAM,CAAC,CAAC;KAClF,OAAO,KAAsB,IAAIA,QAAgB;MAAE,UAAU;MAAQ,KAAK;MAAM,CAAC,CAAC;KAClF,OAAO,QAAQ,OAAO;KACvB,CAAC,CAAC,KAAKC,OAAa,SAAS,OAAO,EAAE,CAAC,CAAC;IAEzB,OAAA,OADK,IAAI,IAAI,SAAS;IACf;IACvB;EAEF,MAAM,EAAE,OAAO,UAAU,MAAM,OAAO,WAAW,QAAQ;AACzD,eAAO,MAAM,CAAC,KAAK,EAAE;AACrB,eAAO,MAAM,CAAC,KAAK,OAAO;GAC1B;EACF"}
@@ -0,0 +1,53 @@
1
+ import { Brand } from "effect";
2
+
3
+ //#region src/sandbox/Memory.d.ts
4
+ declare namespace Memory_d_exports {
5
+ export { Input, Memory, bytes, fromInputUnsafe, gb, gib, kb, kib, mb, mib, toBytes };
6
+ }
7
+ /**
8
+ * A memory / data size, always represented internally as a number of
9
+ * bytes. Mirrors the `Duration` shape from `effect` — a branded value
10
+ * type with a wider `Input` union for ergonomic construction at call
11
+ * sites.
12
+ *
13
+ * `effect` itself has no byte-count primitive (only `Duration`), so we
14
+ * own this here rather than leak provider-specific shapes (Deno's
15
+ * template-literal `Memory`, Vercel's `mb`, Modal's `gpuMemory`, …)
16
+ * through the cross-provider `Sandbox` request types.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import * as Memory from "@effect-uai/core/Memory"
21
+ *
22
+ * Memory.gib(2) // 2 GiB → 2_147_483_648 bytes
23
+ * Memory.fromInputUnsafe("1.5 GiB") // human-string parsing
24
+ * Memory.fromInputUnsafe(1_280_000_000) // raw bytes
25
+ * Memory.toBytes(m) // unwrap
26
+ * ```
27
+ */
28
+ type Memory = Brand.Branded<number, "Memory">;
29
+ type Input = number | Memory | string;
30
+ /**
31
+ * Coerce a {@link Input} to a {@link Memory}. Throws on malformed
32
+ * strings or non-finite numbers. Mirrors `Duration.fromInputUnsafe`.
33
+ */
34
+ declare const fromInputUnsafe: (input: Input) => Memory;
35
+ /** Return the underlying byte count. */
36
+ declare const toBytes: (m: Memory | Input) => number;
37
+ /** N bytes. */
38
+ declare const bytes: (n: number) => Memory;
39
+ /** N KiB (binary kibibytes, `n * 1024`). */
40
+ declare const kib: (n: number) => Memory;
41
+ /** N MiB (binary mebibytes, `n * 1024^2`). */
42
+ declare const mib: (n: number) => Memory;
43
+ /** N GiB (binary gibibytes, `n * 1024^3`). */
44
+ declare const gib: (n: number) => Memory;
45
+ /** N kB (decimal kilobytes, `n * 1000`). */
46
+ declare const kb: (n: number) => Memory;
47
+ /** N MB (decimal megabytes, `n * 1_000_000`). */
48
+ declare const mb: (n: number) => Memory;
49
+ /** N GB (decimal gigabytes, `n * 1_000_000_000`). */
50
+ declare const gb: (n: number) => Memory;
51
+ //#endregion
52
+ export { Input, Memory, bytes, fromInputUnsafe, gb, gib, kb, kib, mb, mib, Memory_d_exports as t, toBytes };
53
+ //# sourceMappingURL=Memory.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Memory.d.mts","names":[],"sources":["../../src/sandbox/Memory.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwBA;;;;;KAAY,MAAA,GAAS,KAAA,CAAM,OAAA;AAAA,KAuBf,KAAA,YAAiB,MAAA;;;;AAqC7B;cAAa,eAAA,GAAmB,KAAA,EAAO,KAAA,KAAQ,MAAA;;cAelC,OAAA,GAAW,CAAA,EAAG,MAAA,GAAS,KAAA;;cAUvB,KAAA,GAAS,CAAA,aAAY,MAAA;;cAGrB,GAAA,GAAO,CAAA,aAAY,MAAA;;cAGnB,GAAA,GAAO,CAAA,aAAY,MAAA;;cAGnB,GAAA,GAAO,CAAA,aAAY,MAAA;;cAGnB,EAAA,GAAM,CAAA,aAAY,MAAA;;cAGlB,EAAA,GAAM,CAAA,aAAY,MAAA;;cAGlB,EAAA,GAAM,CAAA,aAAY,MAAA"}
@@ -0,0 +1,68 @@
1
+ import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
2
+ import { Brand } from "effect";
3
+ //#region src/sandbox/Memory.ts
4
+ var Memory_exports = /* @__PURE__ */ __exportAll({
5
+ bytes: () => bytes,
6
+ fromInputUnsafe: () => fromInputUnsafe,
7
+ gb: () => gb,
8
+ gib: () => gib,
9
+ kb: () => kb,
10
+ kib: () => kib,
11
+ mb: () => mb,
12
+ mib: () => mib,
13
+ toBytes: () => toBytes
14
+ });
15
+ const make = Brand.nominal();
16
+ const UNIT_BYTES = {
17
+ b: 1,
18
+ kb: 1e3,
19
+ mb: 1e6,
20
+ gb: 1e9,
21
+ tb: 0xe8d4a51000,
22
+ kib: 1024,
23
+ mib: 1024 * 1024,
24
+ gib: 1024 * 1024 * 1024,
25
+ tib: 1024 * 1024 * 1024 * 1024
26
+ };
27
+ const PARSE_RE = /^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]*)\s*$/;
28
+ const parseString = (s) => {
29
+ const m = PARSE_RE.exec(s);
30
+ if (m === null) throw new Error(`Memory: cannot parse "${s}" — expected forms like "1024", "10 MB", "1GiB"`);
31
+ const value = Number(m[1]);
32
+ const unit = (m[2] ?? "").toLowerCase();
33
+ if (unit === "") return value;
34
+ const factor = UNIT_BYTES[unit];
35
+ if (factor === void 0) throw new Error(`Memory: unknown unit "${m[2]}" in "${s}" — supported: ${Object.keys(UNIT_BYTES).join(", ")}`);
36
+ return value * factor;
37
+ };
38
+ /**
39
+ * Coerce a {@link Input} to a {@link Memory}. Throws on malformed
40
+ * strings or non-finite numbers. Mirrors `Duration.fromInputUnsafe`.
41
+ */
42
+ const fromInputUnsafe = (input) => {
43
+ if (typeof input === "number") {
44
+ if (!Number.isFinite(input) || input < 0) throw new Error(`Memory: bytes must be a non-negative finite number, got ${input}`);
45
+ return make(Math.floor(input));
46
+ }
47
+ return make(Math.floor(parseString(input)));
48
+ };
49
+ /** Return the underlying byte count. */
50
+ const toBytes = (m) => typeof m === "number" && m === Math.floor(m) && m >= 0 ? m : fromInputUnsafe(m);
51
+ /** N bytes. */
52
+ const bytes = (n) => make(Math.floor(n));
53
+ /** N KiB (binary kibibytes, `n * 1024`). */
54
+ const kib = (n) => make(Math.floor(n * 1024));
55
+ /** N MiB (binary mebibytes, `n * 1024^2`). */
56
+ const mib = (n) => make(Math.floor(n * 1024 * 1024));
57
+ /** N GiB (binary gibibytes, `n * 1024^3`). */
58
+ const gib = (n) => make(Math.floor(n * 1024 * 1024 * 1024));
59
+ /** N kB (decimal kilobytes, `n * 1000`). */
60
+ const kb = (n) => make(Math.floor(n * 1e3));
61
+ /** N MB (decimal megabytes, `n * 1_000_000`). */
62
+ const mb = (n) => make(Math.floor(n * 1e6));
63
+ /** N GB (decimal gigabytes, `n * 1_000_000_000`). */
64
+ const gb = (n) => make(Math.floor(n * 1e9));
65
+ //#endregion
66
+ export { bytes, fromInputUnsafe, gb, gib, kb, kib, mb, mib, Memory_exports as t, toBytes };
67
+
68
+ //# sourceMappingURL=Memory.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Memory.mjs","names":[],"sources":["../../src/sandbox/Memory.ts"],"sourcesContent":["import { Brand } from \"effect\"\n\n/**\n * A memory / data size, always represented internally as a number of\n * bytes. Mirrors the `Duration` shape from `effect` — a branded value\n * type with a wider `Input` union for ergonomic construction at call\n * sites.\n *\n * `effect` itself has no byte-count primitive (only `Duration`), so we\n * own this here rather than leak provider-specific shapes (Deno's\n * template-literal `Memory`, Vercel's `mb`, Modal's `gpuMemory`, …)\n * through the cross-provider `Sandbox` request types.\n *\n * @example\n * ```ts\n * import * as Memory from \"@effect-uai/core/Memory\"\n *\n * Memory.gib(2) // 2 GiB → 2_147_483_648 bytes\n * Memory.fromInputUnsafe(\"1.5 GiB\") // human-string parsing\n * Memory.fromInputUnsafe(1_280_000_000) // raw bytes\n * Memory.toBytes(m) // unwrap\n * ```\n */\n\nexport type Memory = Brand.Branded<number, \"Memory\">\n\nconst make = Brand.nominal<Memory>()\n\n// ---------------------------------------------------------------------------\n// Input — what the public APIs accept.\n//\n// Supported string forms (case-insensitive on the unit, whitespace\n// optional between number and unit):\n//\n// \"<n>\" bytes (no unit)\n// \"<n> B\" bytes\n// \"<n> kB\" 1_000 bytes (decimal)\n// \"<n> MB\" 1_000_000 (decimal)\n// \"<n> GB\" 1_000_000_000 (decimal)\n// \"<n> KiB\" 1_024 (binary)\n// \"<n> MiB\" 1_048_576 (binary)\n// \"<n> GiB\" 1_073_741_824 (binary)\n//\n// Decimal vs binary follows the SI / IEC convention (`MB` = 10^6,\n// `MiB` = 2^20). When in doubt, prefer the binary `*iB` units.\n// ---------------------------------------------------------------------------\n\nexport type Input = number | Memory | string\n\nconst UNIT_BYTES: Record<string, number> = {\n b: 1,\n kb: 1_000,\n mb: 1_000_000,\n gb: 1_000_000_000,\n tb: 1_000_000_000_000,\n kib: 1_024,\n mib: 1_024 * 1_024,\n gib: 1_024 * 1_024 * 1_024,\n tib: 1_024 * 1_024 * 1_024 * 1_024,\n}\n\nconst PARSE_RE = /^\\s*(\\d+(?:\\.\\d+)?)\\s*([a-zA-Z]*)\\s*$/\n\nconst parseString = (s: string): number => {\n const m = PARSE_RE.exec(s)\n if (m === null) {\n throw new Error(`Memory: cannot parse \"${s}\" — expected forms like \"1024\", \"10 MB\", \"1GiB\"`)\n }\n const value = Number(m[1])\n const unit = (m[2] ?? \"\").toLowerCase()\n if (unit === \"\") return value\n const factor = UNIT_BYTES[unit]\n if (factor === undefined) {\n throw new Error(\n `Memory: unknown unit \"${m[2]}\" in \"${s}\" — supported: ${Object.keys(UNIT_BYTES).join(\", \")}`,\n )\n }\n return value * factor\n}\n\n/**\n * Coerce a {@link Input} to a {@link Memory}. Throws on malformed\n * strings or non-finite numbers. Mirrors `Duration.fromInputUnsafe`.\n */\nexport const fromInputUnsafe = (input: Input): Memory => {\n if (typeof input === \"number\") {\n if (!Number.isFinite(input) || input < 0) {\n throw new Error(`Memory: bytes must be a non-negative finite number, got ${input}`)\n }\n return make(Math.floor(input))\n }\n return make(Math.floor(parseString(input)))\n}\n\n// ---------------------------------------------------------------------------\n// Unwrap\n// ---------------------------------------------------------------------------\n\n/** Return the underlying byte count. */\nexport const toBytes = (m: Memory | Input): number =>\n typeof m === \"number\" && (m as number) === Math.floor(m as number) && (m as number) >= 0\n ? (m as number)\n : (fromInputUnsafe(m) as unknown as number)\n\n// ---------------------------------------------------------------------------\n// Unit constructors — terse for use at call sites.\n// ---------------------------------------------------------------------------\n\n/** N bytes. */\nexport const bytes = (n: number): Memory => make(Math.floor(n))\n\n/** N KiB (binary kibibytes, `n * 1024`). */\nexport const kib = (n: number): Memory => make(Math.floor(n * 1_024))\n\n/** N MiB (binary mebibytes, `n * 1024^2`). */\nexport const mib = (n: number): Memory => make(Math.floor(n * 1_024 * 1_024))\n\n/** N GiB (binary gibibytes, `n * 1024^3`). */\nexport const gib = (n: number): Memory => make(Math.floor(n * 1_024 * 1_024 * 1_024))\n\n/** N kB (decimal kilobytes, `n * 1000`). */\nexport const kb = (n: number): Memory => make(Math.floor(n * 1_000))\n\n/** N MB (decimal megabytes, `n * 1_000_000`). */\nexport const mb = (n: number): Memory => make(Math.floor(n * 1_000_000))\n\n/** N GB (decimal gigabytes, `n * 1_000_000_000`). */\nexport const gb = (n: number): Memory => make(Math.floor(n * 1_000_000_000))\n"],"mappings":";;;;;;;;;;;;;;AA0BA,MAAM,OAAO,MAAM,SAAiB;AAuBpC,MAAM,aAAqC;CACzC,GAAG;CACH,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,KAAK;CACL,KAAK,OAAQ;CACb,KAAK,OAAQ,OAAQ;CACrB,KAAK,OAAQ,OAAQ,OAAQ;CAC9B;AAED,MAAM,WAAW;AAEjB,MAAM,eAAe,MAAsB;CACzC,MAAM,IAAI,SAAS,KAAK,EAAE;AAC1B,KAAI,MAAM,KACR,OAAM,IAAI,MAAM,yBAAyB,EAAE,iDAAiD;CAE9F,MAAM,QAAQ,OAAO,EAAE,GAAG;CAC1B,MAAM,QAAQ,EAAE,MAAM,IAAI,aAAa;AACvC,KAAI,SAAS,GAAI,QAAO;CACxB,MAAM,SAAS,WAAW;AAC1B,KAAI,WAAW,KAAA,EACb,OAAM,IAAI,MACR,yBAAyB,EAAE,GAAG,QAAQ,EAAE,iBAAiB,OAAO,KAAK,WAAW,CAAC,KAAK,KAAK,GAC5F;AAEH,QAAO,QAAQ;;;;;;AAOjB,MAAa,mBAAmB,UAAyB;AACvD,KAAI,OAAO,UAAU,UAAU;AAC7B,MAAI,CAAC,OAAO,SAAS,MAAM,IAAI,QAAQ,EACrC,OAAM,IAAI,MAAM,2DAA2D,QAAQ;AAErF,SAAO,KAAK,KAAK,MAAM,MAAM,CAAC;;AAEhC,QAAO,KAAK,KAAK,MAAM,YAAY,MAAM,CAAC,CAAC;;;AAQ7C,MAAa,WAAW,MACtB,OAAO,MAAM,YAAa,MAAiB,KAAK,MAAM,EAAY,IAAK,KAAgB,IAClF,IACA,gBAAgB,EAAE;;AAOzB,MAAa,SAAS,MAAsB,KAAK,KAAK,MAAM,EAAE,CAAC;;AAG/D,MAAa,OAAO,MAAsB,KAAK,KAAK,MAAM,IAAI,KAAM,CAAC;;AAGrE,MAAa,OAAO,MAAsB,KAAK,KAAK,MAAM,IAAI,OAAQ,KAAM,CAAC;;AAG7E,MAAa,OAAO,MAAsB,KAAK,KAAK,MAAM,IAAI,OAAQ,OAAQ,KAAM,CAAC;;AAGrF,MAAa,MAAM,MAAsB,KAAK,KAAK,MAAM,IAAI,IAAM,CAAC;;AAGpE,MAAa,MAAM,MAAsB,KAAK,KAAK,MAAM,IAAI,IAAU,CAAC;;AAGxE,MAAa,MAAM,MAAsB,KAAK,KAAK,MAAM,IAAI,IAAc,CAAC"}