@sundaeswap/sprinkles 0.6.0 → 0.7.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 (158) hide show
  1. package/dist/cjs/Sprinkle/__tests__/action-integration.test.js +590 -0
  2. package/dist/cjs/Sprinkle/__tests__/action-integration.test.js.map +1 -0
  3. package/dist/cjs/Sprinkle/__tests__/action-registry.test.js +193 -0
  4. package/dist/cjs/Sprinkle/__tests__/action-registry.test.js.map +1 -0
  5. package/dist/cjs/Sprinkle/__tests__/action-runner.test.js +304 -0
  6. package/dist/cjs/Sprinkle/__tests__/action-runner.test.js.map +1 -0
  7. package/dist/cjs/Sprinkle/__tests__/builtin-actions.test.js +1110 -0
  8. package/dist/cjs/Sprinkle/__tests__/builtin-actions.test.js.map +1 -0
  9. package/dist/cjs/Sprinkle/__tests__/cli-adapter.test.js +722 -0
  10. package/dist/cjs/Sprinkle/__tests__/cli-adapter.test.js.map +1 -0
  11. package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js +138 -0
  12. package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
  13. package/dist/cjs/Sprinkle/__tests__/mcp-adapter.test.js +713 -0
  14. package/dist/cjs/Sprinkle/__tests__/mcp-adapter.test.js.map +1 -0
  15. package/dist/cjs/Sprinkle/__tests__/tui-helpers.test.js +334 -0
  16. package/dist/cjs/Sprinkle/__tests__/tui-helpers.test.js.map +1 -0
  17. package/dist/cjs/Sprinkle/__tests__/wallet-transaction-actions.test.js +749 -0
  18. package/dist/cjs/Sprinkle/__tests__/wallet-transaction-actions.test.js.map +1 -0
  19. package/dist/cjs/Sprinkle/actions/builtin/blaze-helper.js +61 -0
  20. package/dist/cjs/Sprinkle/actions/builtin/blaze-helper.js.map +1 -0
  21. package/dist/cjs/Sprinkle/actions/builtin/index.js +117 -0
  22. package/dist/cjs/Sprinkle/actions/builtin/index.js.map +1 -0
  23. package/dist/cjs/Sprinkle/actions/builtin/profile-actions.js +202 -0
  24. package/dist/cjs/Sprinkle/actions/builtin/profile-actions.js.map +1 -0
  25. package/dist/cjs/Sprinkle/actions/builtin/settings-actions.js +87 -0
  26. package/dist/cjs/Sprinkle/actions/builtin/settings-actions.js.map +1 -0
  27. package/dist/cjs/Sprinkle/actions/builtin/transaction-actions.js +345 -0
  28. package/dist/cjs/Sprinkle/actions/builtin/transaction-actions.js.map +1 -0
  29. package/dist/cjs/Sprinkle/actions/builtin/wallet-actions.js +212 -0
  30. package/dist/cjs/Sprinkle/actions/builtin/wallet-actions.js.map +1 -0
  31. package/dist/cjs/Sprinkle/actions/cli-adapter.js +372 -0
  32. package/dist/cjs/Sprinkle/actions/cli-adapter.js.map +1 -0
  33. package/dist/cjs/Sprinkle/actions/index.js +127 -0
  34. package/dist/cjs/Sprinkle/actions/index.js.map +1 -0
  35. package/dist/cjs/Sprinkle/actions/mcp-adapter.js +415 -0
  36. package/dist/cjs/Sprinkle/actions/mcp-adapter.js.map +1 -0
  37. package/dist/cjs/Sprinkle/actions/registry.js +92 -0
  38. package/dist/cjs/Sprinkle/actions/registry.js.map +1 -0
  39. package/dist/cjs/Sprinkle/actions/runner.js +190 -0
  40. package/dist/cjs/Sprinkle/actions/runner.js.map +1 -0
  41. package/dist/cjs/Sprinkle/actions/tui-helpers.js +96 -0
  42. package/dist/cjs/Sprinkle/actions/tui-helpers.js.map +1 -0
  43. package/dist/cjs/Sprinkle/actions/types.js +68 -0
  44. package/dist/cjs/Sprinkle/actions/types.js.map +1 -0
  45. package/dist/cjs/Sprinkle/index.js +451 -4
  46. package/dist/cjs/Sprinkle/index.js.map +1 -1
  47. package/dist/cjs/Sprinkle/prompts.js +12 -7
  48. package/dist/cjs/Sprinkle/prompts.js.map +1 -1
  49. package/dist/cjs/Sprinkle/type-guards.js +7 -1
  50. package/dist/cjs/Sprinkle/type-guards.js.map +1 -1
  51. package/dist/esm/Sprinkle/__tests__/action-integration.test.js +588 -0
  52. package/dist/esm/Sprinkle/__tests__/action-integration.test.js.map +1 -0
  53. package/dist/esm/Sprinkle/__tests__/action-registry.test.js +192 -0
  54. package/dist/esm/Sprinkle/__tests__/action-registry.test.js.map +1 -0
  55. package/dist/esm/Sprinkle/__tests__/action-runner.test.js +302 -0
  56. package/dist/esm/Sprinkle/__tests__/action-runner.test.js.map +1 -0
  57. package/dist/esm/Sprinkle/__tests__/builtin-actions.test.js +1107 -0
  58. package/dist/esm/Sprinkle/__tests__/builtin-actions.test.js.map +1 -0
  59. package/dist/esm/Sprinkle/__tests__/cli-adapter.test.js +720 -0
  60. package/dist/esm/Sprinkle/__tests__/cli-adapter.test.js.map +1 -0
  61. package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js +138 -0
  62. package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
  63. package/dist/esm/Sprinkle/__tests__/mcp-adapter.test.js +712 -0
  64. package/dist/esm/Sprinkle/__tests__/mcp-adapter.test.js.map +1 -0
  65. package/dist/esm/Sprinkle/__tests__/tui-helpers.test.js +332 -0
  66. package/dist/esm/Sprinkle/__tests__/tui-helpers.test.js.map +1 -0
  67. package/dist/esm/Sprinkle/__tests__/wallet-transaction-actions.test.js +747 -0
  68. package/dist/esm/Sprinkle/__tests__/wallet-transaction-actions.test.js.map +1 -0
  69. package/dist/esm/Sprinkle/actions/builtin/blaze-helper.js +55 -0
  70. package/dist/esm/Sprinkle/actions/builtin/blaze-helper.js.map +1 -0
  71. package/dist/esm/Sprinkle/actions/builtin/index.js +32 -0
  72. package/dist/esm/Sprinkle/actions/builtin/index.js.map +1 -0
  73. package/dist/esm/Sprinkle/actions/builtin/profile-actions.js +197 -0
  74. package/dist/esm/Sprinkle/actions/builtin/profile-actions.js.map +1 -0
  75. package/dist/esm/Sprinkle/actions/builtin/settings-actions.js +81 -0
  76. package/dist/esm/Sprinkle/actions/builtin/settings-actions.js.map +1 -0
  77. package/dist/esm/Sprinkle/actions/builtin/transaction-actions.js +340 -0
  78. package/dist/esm/Sprinkle/actions/builtin/transaction-actions.js.map +1 -0
  79. package/dist/esm/Sprinkle/actions/builtin/wallet-actions.js +207 -0
  80. package/dist/esm/Sprinkle/actions/builtin/wallet-actions.js.map +1 -0
  81. package/dist/esm/Sprinkle/actions/cli-adapter.js +361 -0
  82. package/dist/esm/Sprinkle/actions/cli-adapter.js.map +1 -0
  83. package/dist/esm/Sprinkle/actions/index.js +12 -0
  84. package/dist/esm/Sprinkle/actions/index.js.map +1 -0
  85. package/dist/esm/Sprinkle/actions/mcp-adapter.js +407 -0
  86. package/dist/esm/Sprinkle/actions/mcp-adapter.js.map +1 -0
  87. package/dist/esm/Sprinkle/actions/registry.js +85 -0
  88. package/dist/esm/Sprinkle/actions/registry.js.map +1 -0
  89. package/dist/esm/Sprinkle/actions/runner.js +182 -0
  90. package/dist/esm/Sprinkle/actions/runner.js.map +1 -0
  91. package/dist/esm/Sprinkle/actions/tui-helpers.js +91 -0
  92. package/dist/esm/Sprinkle/actions/tui-helpers.js.map +1 -0
  93. package/dist/esm/Sprinkle/actions/types.js +61 -0
  94. package/dist/esm/Sprinkle/actions/types.js.map +1 -0
  95. package/dist/esm/Sprinkle/index.js +299 -4
  96. package/dist/esm/Sprinkle/index.js.map +1 -1
  97. package/dist/esm/Sprinkle/prompts.js +12 -7
  98. package/dist/esm/Sprinkle/prompts.js.map +1 -1
  99. package/dist/esm/Sprinkle/type-guards.js +3 -0
  100. package/dist/esm/Sprinkle/type-guards.js.map +1 -1
  101. package/dist/types/Sprinkle/actions/builtin/blaze-helper.d.ts +39 -0
  102. package/dist/types/Sprinkle/actions/builtin/blaze-helper.d.ts.map +1 -0
  103. package/dist/types/Sprinkle/actions/builtin/index.d.ts +26 -0
  104. package/dist/types/Sprinkle/actions/builtin/index.d.ts.map +1 -0
  105. package/dist/types/Sprinkle/actions/builtin/profile-actions.d.ts +55 -0
  106. package/dist/types/Sprinkle/actions/builtin/profile-actions.d.ts.map +1 -0
  107. package/dist/types/Sprinkle/actions/builtin/settings-actions.d.ts +32 -0
  108. package/dist/types/Sprinkle/actions/builtin/settings-actions.d.ts.map +1 -0
  109. package/dist/types/Sprinkle/actions/builtin/transaction-actions.d.ts +70 -0
  110. package/dist/types/Sprinkle/actions/builtin/transaction-actions.d.ts.map +1 -0
  111. package/dist/types/Sprinkle/actions/builtin/wallet-actions.d.ts +50 -0
  112. package/dist/types/Sprinkle/actions/builtin/wallet-actions.d.ts.map +1 -0
  113. package/dist/types/Sprinkle/actions/cli-adapter.d.ts +104 -0
  114. package/dist/types/Sprinkle/actions/cli-adapter.d.ts.map +1 -0
  115. package/dist/types/Sprinkle/actions/index.d.ts +12 -0
  116. package/dist/types/Sprinkle/actions/index.d.ts.map +1 -0
  117. package/dist/types/Sprinkle/actions/mcp-adapter.d.ts +92 -0
  118. package/dist/types/Sprinkle/actions/mcp-adapter.d.ts.map +1 -0
  119. package/dist/types/Sprinkle/actions/registry.d.ts +42 -0
  120. package/dist/types/Sprinkle/actions/registry.d.ts.map +1 -0
  121. package/dist/types/Sprinkle/actions/runner.d.ts +45 -0
  122. package/dist/types/Sprinkle/actions/runner.d.ts.map +1 -0
  123. package/dist/types/Sprinkle/actions/tui-helpers.d.ts +53 -0
  124. package/dist/types/Sprinkle/actions/tui-helpers.d.ts.map +1 -0
  125. package/dist/types/Sprinkle/actions/types.d.ts +76 -0
  126. package/dist/types/Sprinkle/actions/types.d.ts.map +1 -0
  127. package/dist/types/Sprinkle/index.d.ts +81 -1
  128. package/dist/types/Sprinkle/index.d.ts.map +1 -1
  129. package/dist/types/Sprinkle/prompts.d.ts.map +1 -1
  130. package/dist/types/Sprinkle/type-guards.d.ts +4 -1
  131. package/dist/types/Sprinkle/type-guards.d.ts.map +1 -1
  132. package/dist/types/tsconfig.build.tsbuildinfo +1 -1
  133. package/package.json +9 -2
  134. package/src/Sprinkle/__tests__/action-integration.test.ts +558 -0
  135. package/src/Sprinkle/__tests__/action-registry.test.ts +187 -0
  136. package/src/Sprinkle/__tests__/action-runner.test.ts +324 -0
  137. package/src/Sprinkle/__tests__/builtin-actions.test.ts +1022 -0
  138. package/src/Sprinkle/__tests__/cli-adapter.test.ts +715 -0
  139. package/src/Sprinkle/__tests__/fill-in-struct.test.ts +144 -0
  140. package/src/Sprinkle/__tests__/mcp-adapter.test.ts +718 -0
  141. package/src/Sprinkle/__tests__/tui-helpers.test.ts +325 -0
  142. package/src/Sprinkle/__tests__/wallet-transaction-actions.test.ts +695 -0
  143. package/src/Sprinkle/actions/builtin/blaze-helper.ts +89 -0
  144. package/src/Sprinkle/actions/builtin/index.ts +86 -0
  145. package/src/Sprinkle/actions/builtin/profile-actions.ts +229 -0
  146. package/src/Sprinkle/actions/builtin/settings-actions.ts +99 -0
  147. package/src/Sprinkle/actions/builtin/transaction-actions.ts +381 -0
  148. package/src/Sprinkle/actions/builtin/wallet-actions.ts +233 -0
  149. package/src/Sprinkle/actions/cli-adapter.ts +430 -0
  150. package/src/Sprinkle/actions/index.ts +32 -0
  151. package/src/Sprinkle/actions/mcp-adapter.ts +463 -0
  152. package/src/Sprinkle/actions/registry.ts +97 -0
  153. package/src/Sprinkle/actions/runner.ts +200 -0
  154. package/src/Sprinkle/actions/tui-helpers.ts +114 -0
  155. package/src/Sprinkle/actions/types.ts +91 -0
  156. package/src/Sprinkle/index.ts +395 -3
  157. package/src/Sprinkle/prompts.ts +118 -72
  158. package/src/Sprinkle/type-guards.ts +9 -0
@@ -0,0 +1,325 @@
1
+ import { describe, expect, test, mock, beforeEach } from "bun:test";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { promptAndExecute } from "../actions/tui-helpers.js";
4
+ import { UserCancelledError } from "../types.js";
5
+ import type { IAction } from "../actions/types.js";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Fixtures
9
+ // ---------------------------------------------------------------------------
10
+
11
+ // Minimal action used throughout these tests
12
+ const greetAction: IAction<{ name: string }, { greeting: string }, any> = {
13
+ name: "greet",
14
+ description: "Greets someone",
15
+ inputSchema: Type.Object({ name: Type.String() }),
16
+ outputSchema: Type.Object({ greeting: Type.String() }),
17
+ execute: async (input: { name: string }) => ({
18
+ greeting: `Hello, ${input.name}!`,
19
+ }),
20
+ };
21
+
22
+ // Build a minimal Sprinkle-shaped mock. promptAndExecute only calls:
23
+ // - sprinkle.FillInStruct(schema, defaults)
24
+ // - sprinkle.settings (read once for context)
25
+ // so we only need those two members.
26
+ function makeMockSprinkle(fillInStructImpl: (schema: any, defaults?: any) => Promise<any>) {
27
+ return {
28
+ FillInStruct: mock(fillInStructImpl),
29
+ settings: { apiUrl: "https://example.com" },
30
+ } as any;
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // describe: promptAndExecute – success path
35
+ // ---------------------------------------------------------------------------
36
+
37
+ describe("promptAndExecute – success path", () => {
38
+ test("returns success result when FillInStruct resolves", async () => {
39
+ const sprinkle = makeMockSprinkle(async () => ({ name: "Alice" }));
40
+
41
+ const result = await promptAndExecute(sprinkle, greetAction);
42
+
43
+ expect(result.success).toBe(true);
44
+ if (result.success) {
45
+ expect(result.data).toEqual({ greeting: "Hello, Alice!" });
46
+ }
47
+ });
48
+
49
+ test("passes the schema from the action to FillInStruct", async () => {
50
+ let capturedSchema: any;
51
+ const sprinkle = makeMockSprinkle(async (schema: any) => {
52
+ capturedSchema = schema;
53
+ return { name: "Bob" };
54
+ });
55
+
56
+ await promptAndExecute(sprinkle, greetAction);
57
+
58
+ expect(capturedSchema).toBe(greetAction.inputSchema);
59
+ });
60
+
61
+ test("passes action execute function the validated input", async () => {
62
+ let capturedInput: any;
63
+ const action = {
64
+ ...greetAction,
65
+ execute: async (input: { name: string }) => {
66
+ capturedInput = input;
67
+ return { greeting: `Hi, ${input.name}!` };
68
+ },
69
+ };
70
+ const sprinkle = makeMockSprinkle(async () => ({ name: "Charlie" }));
71
+
72
+ await promptAndExecute(sprinkle, action);
73
+
74
+ expect(capturedInput).toEqual({ name: "Charlie" });
75
+ });
76
+
77
+ test("provides the sprinkle instance via context to execute", async () => {
78
+ let capturedContext: any;
79
+ const action = {
80
+ ...greetAction,
81
+ execute: async (_input: any, ctx: any) => {
82
+ capturedContext = ctx;
83
+ return { greeting: "hi" };
84
+ },
85
+ };
86
+ const sprinkle = makeMockSprinkle(async () => ({ name: "Dan" }));
87
+
88
+ await promptAndExecute(sprinkle, action);
89
+
90
+ // The context's sprinkle should refer to the same mock instance
91
+ expect(capturedContext).toBeDefined();
92
+ expect(capturedContext.sprinkle).toBe(sprinkle);
93
+ });
94
+
95
+ test("provides sprinkle.settings via context to execute", async () => {
96
+ let capturedSettings: any;
97
+ const action = {
98
+ ...greetAction,
99
+ execute: async (_input: any, ctx: any) => {
100
+ capturedSettings = ctx.settings;
101
+ return { greeting: "hi" };
102
+ },
103
+ };
104
+ const sprinkle = makeMockSprinkle(async () => ({ name: "Eve" }));
105
+
106
+ await promptAndExecute(sprinkle, action);
107
+
108
+ expect(capturedSettings).toEqual({ apiUrl: "https://example.com" });
109
+ });
110
+
111
+ test("returns failure when execute throws an Error", async () => {
112
+ const action = {
113
+ ...greetAction,
114
+ execute: async () => {
115
+ throw new Error("execute failed");
116
+ },
117
+ };
118
+ const sprinkle = makeMockSprinkle(async () => ({ name: "Fail" }));
119
+
120
+ const result = await promptAndExecute(sprinkle, action);
121
+
122
+ expect(result.success).toBe(false);
123
+ if (!result.success) {
124
+ expect(result.error.code).toBe("EXECUTION_ERROR");
125
+ }
126
+ });
127
+ });
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // describe: promptAndExecute – defaults parameter
131
+ // ---------------------------------------------------------------------------
132
+
133
+ describe("promptAndExecute – defaults parameter", () => {
134
+ test("passes defaults to FillInStruct when provided", async () => {
135
+ let capturedDefaults: any;
136
+ const sprinkle = makeMockSprinkle(async (_schema: any, defaults: any) => {
137
+ capturedDefaults = defaults;
138
+ return { name: "Frank" };
139
+ });
140
+
141
+ await promptAndExecute(sprinkle, greetAction, { name: "Frank" });
142
+
143
+ expect(capturedDefaults).toEqual({ name: "Frank" });
144
+ });
145
+
146
+ test("passes undefined to FillInStruct when defaults are omitted", async () => {
147
+ let capturedDefaults: any = "sentinel";
148
+ const sprinkle = makeMockSprinkle(async (_schema: any, defaults: any) => {
149
+ capturedDefaults = defaults;
150
+ return { name: "Grace" };
151
+ });
152
+
153
+ await promptAndExecute(sprinkle, greetAction);
154
+
155
+ expect(capturedDefaults).toBeUndefined();
156
+ });
157
+
158
+ test("passes partial defaults to FillInStruct", async () => {
159
+ // Action with multiple fields so we can supply only some defaults
160
+ const multiAction: IAction<{ name: string; count: number }, { result: string }, any> = {
161
+ name: "multi",
162
+ description: "Takes multiple fields",
163
+ inputSchema: Type.Object({ name: Type.String(), count: Type.Number() }),
164
+ outputSchema: Type.Object({ result: Type.String() }),
165
+ execute: async (input) => ({ result: `${input.name}:${input.count}` }),
166
+ };
167
+
168
+ let capturedDefaults: any;
169
+ const sprinkle = makeMockSprinkle(async (_schema: any, defaults: any) => {
170
+ capturedDefaults = defaults;
171
+ return { name: "Henry", count: 42 };
172
+ });
173
+
174
+ await promptAndExecute(sprinkle, multiAction, { name: "Henry" });
175
+
176
+ expect(capturedDefaults).toEqual({ name: "Henry" });
177
+ });
178
+ });
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // describe: promptAndExecute – UserCancelledError handling
182
+ // ---------------------------------------------------------------------------
183
+
184
+ describe("promptAndExecute – UserCancelledError handling", () => {
185
+ test("returns IActionFailure with code USER_CANCELLED when FillInStruct throws UserCancelledError", async () => {
186
+ const sprinkle = makeMockSprinkle(async () => {
187
+ throw new UserCancelledError("cancelled");
188
+ });
189
+
190
+ const result = await promptAndExecute(sprinkle, greetAction);
191
+
192
+ expect(result.success).toBe(false);
193
+ if (!result.success) {
194
+ expect(result.error.code).toBe("USER_CANCELLED");
195
+ }
196
+ });
197
+
198
+ test("does not throw when FillInStruct throws UserCancelledError", async () => {
199
+ const sprinkle = makeMockSprinkle(async () => {
200
+ throw new UserCancelledError();
201
+ });
202
+
203
+ // Should resolve (not reject)
204
+ await expect(promptAndExecute(sprinkle, greetAction)).resolves.toBeDefined();
205
+ });
206
+
207
+ test("USER_CANCELLED failure has a descriptive message", async () => {
208
+ const sprinkle = makeMockSprinkle(async () => {
209
+ throw new UserCancelledError("user pressed escape");
210
+ });
211
+
212
+ const result = await promptAndExecute(sprinkle, greetAction);
213
+
214
+ expect(result.success).toBe(false);
215
+ if (!result.success) {
216
+ expect(typeof result.error.message).toBe("string");
217
+ expect(result.error.message.length).toBeGreaterThan(0);
218
+ }
219
+ });
220
+
221
+ test("USER_CANCELLED failure carries the original UserCancelledError as details", async () => {
222
+ const cancelled = new UserCancelledError("esc pressed");
223
+ const sprinkle = makeMockSprinkle(async () => {
224
+ throw cancelled;
225
+ });
226
+
227
+ const result = await promptAndExecute(sprinkle, greetAction);
228
+
229
+ expect(result.success).toBe(false);
230
+ if (!result.success) {
231
+ expect(result.error.details).toBe(cancelled);
232
+ }
233
+ });
234
+
235
+ test("does not call execute when FillInStruct throws UserCancelledError", async () => {
236
+ let executeCalled = false;
237
+ const action = {
238
+ ...greetAction,
239
+ execute: async (_input: any) => {
240
+ executeCalled = true;
241
+ return { greeting: "hi" };
242
+ },
243
+ };
244
+ const sprinkle = makeMockSprinkle(async () => {
245
+ throw new UserCancelledError();
246
+ });
247
+
248
+ await promptAndExecute(sprinkle, action);
249
+
250
+ expect(executeCalled).toBe(false);
251
+ });
252
+ });
253
+
254
+ // ---------------------------------------------------------------------------
255
+ // describe: promptAndExecute – unexpected FillInStruct errors
256
+ // ---------------------------------------------------------------------------
257
+
258
+ describe("promptAndExecute – unexpected FillInStruct errors", () => {
259
+ test("returns IActionFailure with code PROMPT_ERROR when FillInStruct throws a non-UserCancelledError", async () => {
260
+ const sprinkle = makeMockSprinkle(async () => {
261
+ throw new Error("TTY not available");
262
+ });
263
+
264
+ const result = await promptAndExecute(sprinkle, greetAction);
265
+
266
+ expect(result.success).toBe(false);
267
+ if (!result.success) {
268
+ expect(result.error.code).toBe("PROMPT_ERROR");
269
+ }
270
+ });
271
+
272
+ test("PROMPT_ERROR message includes the original error message", async () => {
273
+ const sprinkle = makeMockSprinkle(async () => {
274
+ throw new Error("underlying prompt failure");
275
+ });
276
+
277
+ const result = await promptAndExecute(sprinkle, greetAction);
278
+
279
+ expect(result.success).toBe(false);
280
+ if (!result.success) {
281
+ expect(result.error.message).toContain("underlying prompt failure");
282
+ }
283
+ });
284
+
285
+ test("does not throw when FillInStruct throws an unexpected error", async () => {
286
+ const sprinkle = makeMockSprinkle(async () => {
287
+ throw new Error("unexpected");
288
+ });
289
+
290
+ await expect(promptAndExecute(sprinkle, greetAction)).resolves.toBeDefined();
291
+ });
292
+
293
+ test("PROMPT_ERROR result includes non-Error thrown values in message", async () => {
294
+ const sprinkle = makeMockSprinkle(async () => {
295
+ // eslint-disable-next-line @typescript-eslint/only-throw-error
296
+ throw "some string error";
297
+ });
298
+
299
+ const result = await promptAndExecute(sprinkle, greetAction);
300
+
301
+ expect(result.success).toBe(false);
302
+ if (!result.success) {
303
+ expect(result.error.code).toBe("PROMPT_ERROR");
304
+ expect(result.error.message).toContain("some string error");
305
+ }
306
+ });
307
+ });
308
+
309
+ // ---------------------------------------------------------------------------
310
+ // describe: promptAndExecute – input validation
311
+ // ---------------------------------------------------------------------------
312
+
313
+ describe("promptAndExecute – input validation", () => {
314
+ test("returns VALIDATION_ERROR when FillInStruct returns data that fails schema validation", async () => {
315
+ // FillInStruct returns a value that does not conform to the inputSchema
316
+ const sprinkle = makeMockSprinkle(async () => ({ name: 42 })); // name should be a string
317
+
318
+ const result = await promptAndExecute(sprinkle, greetAction);
319
+
320
+ expect(result.success).toBe(false);
321
+ if (!result.success) {
322
+ expect(result.error.code).toBe("VALIDATION_ERROR");
323
+ }
324
+ });
325
+ });