retestkit 1.4.1 → 1.5.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 (238) hide show
  1. package/README.md +59 -40
  2. package/dist/config.js +8 -8
  3. package/dist/config.js.map +1 -1
  4. package/dist/logger.js +1 -1
  5. package/dist/logger.js.map +1 -1
  6. package/dist/prompts/index.d.ts +1 -1
  7. package/dist/prompts/index.d.ts.map +1 -1
  8. package/dist/prompts/index.js +21 -21
  9. package/dist/prompts/index.js.map +1 -1
  10. package/dist/prompts/templates/mcp/retest-crawl.md +7 -0
  11. package/{src/prompts/templates/mcp/webtest-discover-flows.md → dist/prompts/templates/mcp/retest-discover-flows.md} +1 -1
  12. package/{src/prompts/templates/mcp/webtest-discover.md → dist/prompts/templates/mcp/retest-discover.md} +2 -2
  13. package/dist/prompts/templates/mcp/retest-full-workflow.md +12 -0
  14. package/{src/prompts/templates/mcp/webtest-generate-tests.md → dist/prompts/templates/mcp/retest-generate-tests.md} +1 -1
  15. package/{src/prompts/templates/mcp/webtest-run-test.md → dist/prompts/templates/mcp/retest-run-test.md} +1 -1
  16. package/{src/prompts/templates/mcp/webtest-start.md → dist/prompts/templates/mcp/retest-start.md} +1 -1
  17. package/{src → dist}/prompts/templates/sampling/system-prefix.md +1 -1
  18. package/dist/resources/index.js +7 -7
  19. package/dist/resources/index.js.map +1 -1
  20. package/dist/schemas/config.js +2 -2
  21. package/dist/schemas/config.js.map +1 -1
  22. package/dist/security/index.js +1 -1
  23. package/dist/security/index.js.map +1 -1
  24. package/dist/server.js +3 -3
  25. package/dist/server.js.map +1 -1
  26. package/dist/test-utils/mock-context.js +22 -22
  27. package/dist/test-utils/mock-context.js.map +1 -1
  28. package/dist/tools/index.d.ts +1 -1
  29. package/dist/tools/index.d.ts.map +1 -1
  30. package/dist/tools/index.js +5 -5
  31. package/dist/tools/index.js.map +1 -1
  32. package/dist/tools/retest/crawl.d.ts.map +1 -0
  33. package/dist/tools/{webtest → retest}/crawl.js +7 -7
  34. package/dist/tools/retest/crawl.js.map +1 -0
  35. package/dist/tools/retest/discover-features.d.ts.map +1 -0
  36. package/dist/tools/{webtest → retest}/discover-features.js +6 -6
  37. package/dist/tools/retest/discover-features.js.map +1 -0
  38. package/dist/tools/retest/discover-flows.d.ts.map +1 -0
  39. package/dist/tools/{webtest → retest}/discover-flows.js +6 -6
  40. package/dist/tools/retest/discover-flows.js.map +1 -0
  41. package/dist/tools/retest/generate-tests.d.ts.map +1 -0
  42. package/dist/tools/{webtest → retest}/generate-tests.js +5 -5
  43. package/dist/tools/retest/generate-tests.js.map +1 -0
  44. package/dist/tools/retest/index.d.ts.map +1 -0
  45. package/dist/tools/retest/index.js.map +1 -0
  46. package/dist/tools/retest/run-test-case.d.ts.map +1 -0
  47. package/dist/tools/{webtest → retest}/run-test-case.js +3 -3
  48. package/dist/tools/retest/run-test-case.js.map +1 -0
  49. package/dist/tools/retest/schemas.d.ts.map +1 -0
  50. package/dist/tools/retest/schemas.js.map +1 -0
  51. package/dist/tools/retest/start-analysis.d.ts.map +1 -0
  52. package/dist/tools/{webtest → retest}/start-analysis.js +5 -5
  53. package/dist/tools/retest/start-analysis.js.map +1 -0
  54. package/dist/workspace/index.js +8 -8
  55. package/dist/workspace/index.js.map +1 -1
  56. package/dist/workspace/types.d.ts +2 -2
  57. package/dist/workspace/types.d.ts.map +1 -1
  58. package/package.json +6 -2
  59. package/.claude/commands/openspec/apply.md +0 -23
  60. package/.claude/commands/openspec/archive.md +0 -27
  61. package/.claude/commands/openspec/proposal.md +0 -28
  62. package/.gemini/commands/openspec/apply.toml +0 -21
  63. package/.gemini/commands/openspec/archive.toml +0 -25
  64. package/.gemini/commands/openspec/proposal.toml +0 -26
  65. package/.github/prompts/openspec-apply.prompt.md +0 -22
  66. package/.github/prompts/openspec-archive.prompt.md +0 -26
  67. package/.github/prompts/openspec-proposal.prompt.md +0 -27
  68. package/.github/workflows/release.yml +0 -33
  69. package/.kilocode/workflows/openspec-apply.md +0 -17
  70. package/.kilocode/workflows/openspec-archive.md +0 -21
  71. package/.kilocode/workflows/openspec-proposal.md +0 -22
  72. package/.mcp.json +0 -23
  73. package/.opencode/command/openspec-apply.md +0 -25
  74. package/.opencode/command/openspec-archive.md +0 -28
  75. package/.opencode/command/openspec-proposal.md +0 -30
  76. package/.roo/commands/openspec-apply.md +0 -20
  77. package/.roo/commands/openspec-archive.md +0 -24
  78. package/.roo/commands/openspec-proposal.md +0 -25
  79. package/.vscode/mcp.json +0 -23
  80. package/AGENTS.md +0 -18
  81. package/CLAUDE.md +0 -18
  82. package/dist/tools/webtest/crawl.d.ts.map +0 -1
  83. package/dist/tools/webtest/crawl.js.map +0 -1
  84. package/dist/tools/webtest/discover-features.d.ts.map +0 -1
  85. package/dist/tools/webtest/discover-features.js.map +0 -1
  86. package/dist/tools/webtest/discover-flows.d.ts.map +0 -1
  87. package/dist/tools/webtest/discover-flows.js.map +0 -1
  88. package/dist/tools/webtest/generate-tests.d.ts.map +0 -1
  89. package/dist/tools/webtest/generate-tests.js.map +0 -1
  90. package/dist/tools/webtest/index.d.ts.map +0 -1
  91. package/dist/tools/webtest/index.js.map +0 -1
  92. package/dist/tools/webtest/run-test-case.d.ts.map +0 -1
  93. package/dist/tools/webtest/run-test-case.js.map +0 -1
  94. package/dist/tools/webtest/schemas.d.ts.map +0 -1
  95. package/dist/tools/webtest/schemas.js.map +0 -1
  96. package/dist/tools/webtest/start-analysis.d.ts.map +0 -1
  97. package/dist/tools/webtest/start-analysis.js.map +0 -1
  98. package/openspec/AGENTS.md +0 -456
  99. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/proposal.md +0 -33
  100. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-resources/spec.md +0 -27
  101. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-tools/spec.md +0 -304
  102. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/tasks.md +0 -43
  103. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/design.md +0 -209
  104. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/proposal.md +0 -41
  105. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/specs/mcp-server-core/spec.md +0 -183
  106. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/tasks.md +0 -112
  107. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/design.md +0 -333
  108. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/proposal.md +0 -66
  109. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/mcp-server-core/spec.md +0 -129
  110. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-lifecycle/spec.md +0 -138
  111. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-logging/spec.md +0 -211
  112. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-prompts/spec.md +0 -157
  113. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-resources/spec.md +0 -213
  114. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-sampling/spec.md +0 -257
  115. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-tools/spec.md +0 -501
  116. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/tasks.md +0 -264
  117. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/proposal.md +0 -24
  118. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/specs/webtest-tools/spec.md +0 -80
  119. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/tasks.md +0 -8
  120. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/design.md +0 -90
  121. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/proposal.md +0 -28
  122. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/specs/webtest-sampling/spec.md +0 -90
  123. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/tasks.md +0 -33
  124. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/design.md +0 -558
  125. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/proposal.md +0 -119
  126. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-resources/spec.md +0 -109
  127. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-tools/spec.md +0 -121
  128. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/tasks.md +0 -133
  129. package/openspec/changes/extract-prompts-to-markdown/design.md +0 -86
  130. package/openspec/changes/extract-prompts-to-markdown/proposal.md +0 -50
  131. package/openspec/changes/extract-prompts-to-markdown/specs/webtest-prompts/spec.md +0 -74
  132. package/openspec/changes/extract-prompts-to-markdown/tasks.md +0 -40
  133. package/openspec/changes/refactor-webtest-naming/design.md +0 -95
  134. package/openspec/changes/refactor-webtest-naming/proposal.md +0 -66
  135. package/openspec/changes/refactor-webtest-naming/specs/webtest-prompts/spec.md +0 -79
  136. package/openspec/changes/refactor-webtest-naming/specs/webtest-resources/spec.md +0 -80
  137. package/openspec/changes/refactor-webtest-naming/specs/webtest-sampling/spec.md +0 -122
  138. package/openspec/changes/refactor-webtest-naming/specs/webtest-tools/spec.md +0 -113
  139. package/openspec/changes/refactor-webtest-naming/tasks.md +0 -119
  140. package/openspec/changes/rename-package-to-retest/proposal.md +0 -52
  141. package/openspec/changes/rename-package-to-retest/specs/mcp-server-core/spec.md +0 -53
  142. package/openspec/changes/rename-package-to-retest/specs/retest-lifecycle/spec.md +0 -68
  143. package/openspec/changes/rename-package-to-retest/specs/retest-logging/spec.md +0 -35
  144. package/openspec/changes/rename-package-to-retest/specs/retest-prompts/spec.md +0 -159
  145. package/openspec/changes/rename-package-to-retest/specs/retest-resources/spec.md +0 -251
  146. package/openspec/changes/rename-package-to-retest/specs/retest-sampling/spec.md +0 -99
  147. package/openspec/changes/rename-package-to-retest/specs/retest-tools/spec.md +0 -295
  148. package/openspec/changes/rename-package-to-retest/tasks.md +0 -71
  149. package/openspec/project.md +0 -31
  150. package/openspec/specs/mcp-server-core/spec.md +0 -178
  151. package/openspec/specs/webtest-lifecycle/spec.md +0 -136
  152. package/openspec/specs/webtest-logging/spec.md +0 -209
  153. package/openspec/specs/webtest-prompts/spec.md +0 -155
  154. package/openspec/specs/webtest-resources/spec.md +0 -248
  155. package/openspec/specs/webtest-sampling/spec.md +0 -344
  156. package/openspec/specs/webtest-tools/spec.md +0 -282
  157. package/release.config.js +0 -9
  158. package/src/config.test.ts +0 -96
  159. package/src/config.ts +0 -32
  160. package/src/elicitation/index.test.ts +0 -399
  161. package/src/elicitation/index.ts +0 -171
  162. package/src/elicitation/types.ts +0 -68
  163. package/src/index.ts +0 -83
  164. package/src/lifecycle/index.test.ts +0 -260
  165. package/src/lifecycle/index.ts +0 -101
  166. package/src/logger.redaction.test.ts +0 -322
  167. package/src/logger.test.ts +0 -123
  168. package/src/logger.ts +0 -229
  169. package/src/playwright-client/index.ts +0 -392
  170. package/src/playwright-client/types.ts +0 -99
  171. package/src/progress/index.test.ts +0 -327
  172. package/src/progress/index.ts +0 -170
  173. package/src/progress/types.ts +0 -25
  174. package/src/prompts/index.test.ts +0 -451
  175. package/src/prompts/index.ts +0 -246
  176. package/src/prompts/loader.test.ts +0 -100
  177. package/src/prompts/loader.ts +0 -59
  178. package/src/prompts/templates/mcp/webtest-crawl.md +0 -7
  179. package/src/prompts/templates/mcp/webtest-full-workflow.md +0 -12
  180. package/src/resources/index.ts +0 -250
  181. package/src/resources/subscriptions.ts +0 -37
  182. package/src/sampling/index.test.ts +0 -414
  183. package/src/sampling/index.ts +0 -286
  184. package/src/sampling/prompts.ts +0 -194
  185. package/src/sampling/types.ts +0 -60
  186. package/src/schemas/config.ts +0 -39
  187. package/src/security/index.test.ts +0 -441
  188. package/src/security/index.ts +0 -361
  189. package/src/security/security-scenarios.test.ts +0 -468
  190. package/src/server.ts +0 -211
  191. package/src/test-utils/index.ts +0 -6
  192. package/src/test-utils/mock-context.ts +0 -426
  193. package/src/test-utils/mock-playwright-client.ts +0 -422
  194. package/src/tools/index.ts +0 -11
  195. package/src/tools/webtest/crawl.test.ts +0 -834
  196. package/src/tools/webtest/crawl.ts +0 -901
  197. package/src/tools/webtest/discover-features.ts +0 -412
  198. package/src/tools/webtest/discover-flows.ts +0 -408
  199. package/src/tools/webtest/generate-tests.test.ts +0 -532
  200. package/src/tools/webtest/generate-tests.ts +0 -425
  201. package/src/tools/webtest/index.ts +0 -7
  202. package/src/tools/webtest/integration.test.ts +0 -536
  203. package/src/tools/webtest/run-test-case.test.ts +0 -659
  204. package/src/tools/webtest/run-test-case.ts +0 -508
  205. package/src/tools/webtest/schemas.ts +0 -201
  206. package/src/tools/webtest/start-analysis.test.ts +0 -151
  207. package/src/tools/webtest/start-analysis.ts +0 -158
  208. package/src/transports/http.ts +0 -19
  209. package/src/transports/index.ts +0 -30
  210. package/src/transports/stdio.ts +0 -7
  211. package/src/types/capabilities.test.ts +0 -193
  212. package/src/types/capabilities.ts +0 -50
  213. package/src/types/context.ts +0 -21
  214. package/src/types/tool.ts +0 -11
  215. package/src/workspace/index.ts +0 -945
  216. package/src/workspace/markdown.ts +0 -272
  217. package/src/workspace/types.ts +0 -186
  218. package/tests/integration/server.test.ts +0 -89
  219. package/tests/integration/tools.test.ts +0 -99
  220. package/tsconfig.json +0 -20
  221. package/vitest.config.ts +0 -9
  222. package/vitest.integration.config.ts +0 -10
  223. /package/{src → dist}/prompts/templates/sampling/crawl-action.md +0 -0
  224. /package/{src → dist}/prompts/templates/sampling/feature-discovery.md +0 -0
  225. /package/{src → dist}/prompts/templates/sampling/flow-discovery.md +0 -0
  226. /package/{src → dist}/prompts/templates/sampling/page-content-wrapper.md +0 -0
  227. /package/{src → dist}/prompts/templates/sampling/test-evaluation.md +0 -0
  228. /package/{src → dist}/prompts/templates/sampling/test-generation.md +0 -0
  229. /package/dist/tools/{webtest → retest}/crawl.d.ts +0 -0
  230. /package/dist/tools/{webtest → retest}/discover-features.d.ts +0 -0
  231. /package/dist/tools/{webtest → retest}/discover-flows.d.ts +0 -0
  232. /package/dist/tools/{webtest → retest}/generate-tests.d.ts +0 -0
  233. /package/dist/tools/{webtest → retest}/index.d.ts +0 -0
  234. /package/dist/tools/{webtest → retest}/index.js +0 -0
  235. /package/dist/tools/{webtest → retest}/run-test-case.d.ts +0 -0
  236. /package/dist/tools/{webtest → retest}/schemas.d.ts +0 -0
  237. /package/dist/tools/{webtest → retest}/schemas.js +0 -0
  238. /package/dist/tools/{webtest → retest}/start-analysis.d.ts +0 -0
@@ -1,414 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { z } from "zod";
3
- import { createSamplingClient } from "./index.js";
4
- import type { Logger } from "../logger.js";
5
- import type { ClientCapabilities } from "../types/capabilities.js";
6
- import type { SamplingRequest, SamplingResponse } from "./types.js";
7
-
8
- describe("sampling", () => {
9
- let mockLogger: Logger;
10
-
11
- beforeEach(() => {
12
- mockLogger = {
13
- debug: vi.fn(),
14
- info: vi.fn(),
15
- warn: vi.fn(),
16
- error: vi.fn(),
17
- setLevel: vi.fn(),
18
- withCorrelation: vi.fn().mockReturnThis(),
19
- } as unknown as Logger;
20
- });
21
-
22
- const capabilitiesWithSampling: ClientCapabilities = {
23
- sampling: true,
24
- elicitation: false,
25
- logging: false,
26
- progress: true,
27
- resourcesListChanged: false,
28
- resourcesSubscribe: false,
29
- protocolVersion: "2025-06-18",
30
- };
31
-
32
- const capabilitiesWithoutSampling: ClientCapabilities = {
33
- sampling: false,
34
- elicitation: false,
35
- logging: false,
36
- progress: false,
37
- resourcesListChanged: false,
38
- resourcesSubscribe: false,
39
- protocolVersion: "2025-06-18",
40
- };
41
-
42
- describe("createSamplingClient", () => {
43
- it("reports sampling availability correctly", () => {
44
- const clientWith = createSamplingClient(
45
- vi.fn(),
46
- capabilitiesWithSampling,
47
- mockLogger
48
- );
49
-
50
- const clientWithout = createSamplingClient(
51
- vi.fn(),
52
- capabilitiesWithoutSampling,
53
- mockLogger
54
- );
55
-
56
- expect(clientWith.hasSampling()).toBe(true);
57
- expect(clientWithout.hasSampling()).toBe(false);
58
- });
59
- });
60
-
61
- describe("createMessage", () => {
62
- const testSchema = z.object({
63
- action: z.string(),
64
- value: z.number(),
65
- });
66
-
67
- it("returns fallback when sampling unavailable", async () => {
68
- const requestSampling = vi.fn();
69
- const client = createSamplingClient(
70
- requestSampling,
71
- capabilitiesWithoutSampling,
72
- mockLogger
73
- );
74
-
75
- const result = await client.createMessage({
76
- systemPrompt: "You are a test assistant",
77
- userPrompt: "Do something",
78
- schema: testSchema,
79
- });
80
-
81
- expect(result.success).toBe(false);
82
- expect(result.error).toBe("Sampling not available");
83
- expect(result.promptResource).toBeDefined();
84
- expect(requestSampling).not.toHaveBeenCalled();
85
- });
86
-
87
- it("parses valid JSON response successfully", async () => {
88
- const mockResponse: SamplingResponse = {
89
- model: "test-model",
90
- stopReason: "end_turn",
91
- content: {
92
- type: "text",
93
- text: JSON.stringify({ action: "test", value: 42 }),
94
- },
95
- };
96
-
97
- const requestSampling = vi.fn().mockResolvedValue(mockResponse);
98
- const client = createSamplingClient(
99
- requestSampling,
100
- capabilitiesWithSampling,
101
- mockLogger
102
- );
103
-
104
- const result = await client.createMessage({
105
- systemPrompt: "You are a test assistant",
106
- userPrompt: "Do something",
107
- schema: testSchema,
108
- });
109
-
110
- expect(result.success).toBe(true);
111
- expect(result.data).toEqual({ action: "test", value: 42 });
112
- });
113
-
114
- it("handles JSON wrapped in markdown code blocks", async () => {
115
- const mockResponse: SamplingResponse = {
116
- model: "test-model",
117
- stopReason: "end_turn",
118
- content: {
119
- type: "text",
120
- text: '```json\n{"action": "test", "value": 42}\n```',
121
- },
122
- };
123
-
124
- const requestSampling = vi.fn().mockResolvedValue(mockResponse);
125
- const client = createSamplingClient(
126
- requestSampling,
127
- capabilitiesWithSampling,
128
- mockLogger
129
- );
130
-
131
- const result = await client.createMessage({
132
- systemPrompt: "You are a test assistant",
133
- userPrompt: "Do something",
134
- schema: testSchema,
135
- });
136
-
137
- expect(result.success).toBe(true);
138
- expect(result.data).toEqual({ action: "test", value: 42 });
139
- });
140
-
141
- it("handles JSON wrapped in generic code blocks", async () => {
142
- const mockResponse: SamplingResponse = {
143
- model: "test-model",
144
- stopReason: "end_turn",
145
- content: {
146
- type: "text",
147
- text: '```\n{"action": "test", "value": 42}\n```',
148
- },
149
- };
150
-
151
- const requestSampling = vi.fn().mockResolvedValue(mockResponse);
152
- const client = createSamplingClient(
153
- requestSampling,
154
- capabilitiesWithSampling,
155
- mockLogger
156
- );
157
-
158
- const result = await client.createMessage({
159
- systemPrompt: "You are a test assistant",
160
- userPrompt: "Do something",
161
- schema: testSchema,
162
- });
163
-
164
- expect(result.success).toBe(true);
165
- expect(result.data).toEqual({ action: "test", value: 42 });
166
- });
167
-
168
- it("retries on invalid JSON when retryOnFailure is true", async () => {
169
- const requestSampling = vi
170
- .fn()
171
- .mockResolvedValueOnce({
172
- model: "test-model",
173
- stopReason: "end_turn",
174
- content: { type: "text", text: "not valid json" },
175
- })
176
- .mockResolvedValueOnce({
177
- model: "test-model",
178
- stopReason: "end_turn",
179
- content: {
180
- type: "text",
181
- text: JSON.stringify({ action: "test", value: 42 }),
182
- },
183
- });
184
-
185
- const client = createSamplingClient(
186
- requestSampling,
187
- capabilitiesWithSampling,
188
- mockLogger
189
- );
190
-
191
- const result = await client.createMessage({
192
- systemPrompt: "You are a test assistant",
193
- userPrompt: "Do something",
194
- schema: testSchema,
195
- retryOnFailure: true,
196
- });
197
-
198
- expect(result.success).toBe(true);
199
- expect(requestSampling).toHaveBeenCalledTimes(2);
200
- });
201
-
202
- it("does not retry when retryOnFailure is false", async () => {
203
- const requestSampling = vi.fn().mockResolvedValue({
204
- model: "test-model",
205
- stopReason: "end_turn",
206
- content: { type: "text", text: "not valid json" },
207
- });
208
-
209
- const client = createSamplingClient(
210
- requestSampling,
211
- capabilitiesWithSampling,
212
- mockLogger
213
- );
214
-
215
- const result = await client.createMessage({
216
- systemPrompt: "You are a test assistant",
217
- userPrompt: "Do something",
218
- schema: testSchema,
219
- retryOnFailure: false,
220
- });
221
-
222
- expect(result.success).toBe(false);
223
- expect(result.error).toContain("Failed to parse response as JSON");
224
- expect(requestSampling).toHaveBeenCalledTimes(1);
225
- });
226
-
227
- it("retries on schema validation failure", async () => {
228
- const requestSampling = vi
229
- .fn()
230
- .mockResolvedValueOnce({
231
- model: "test-model",
232
- stopReason: "end_turn",
233
- content: {
234
- type: "text",
235
- text: JSON.stringify({ action: "test", value: "not a number" }),
236
- },
237
- })
238
- .mockResolvedValueOnce({
239
- model: "test-model",
240
- stopReason: "end_turn",
241
- content: {
242
- type: "text",
243
- text: JSON.stringify({ action: "test", value: 42 }),
244
- },
245
- });
246
-
247
- const client = createSamplingClient(
248
- requestSampling,
249
- capabilitiesWithSampling,
250
- mockLogger
251
- );
252
-
253
- const result = await client.createMessage({
254
- systemPrompt: "You are a test assistant",
255
- userPrompt: "Do something",
256
- schema: testSchema,
257
- retryOnFailure: true,
258
- });
259
-
260
- expect(result.success).toBe(true);
261
- expect(result.data).toEqual({ action: "test", value: 42 });
262
- });
263
-
264
- it("fails after retry exhausted", async () => {
265
- const requestSampling = vi.fn().mockResolvedValue({
266
- model: "test-model",
267
- stopReason: "end_turn",
268
- content: { type: "text", text: "still not valid json" },
269
- });
270
-
271
- const client = createSamplingClient(
272
- requestSampling,
273
- capabilitiesWithSampling,
274
- mockLogger
275
- );
276
-
277
- const result = await client.createMessage({
278
- systemPrompt: "You are a test assistant",
279
- userPrompt: "Do something",
280
- schema: testSchema,
281
- retryOnFailure: true,
282
- });
283
-
284
- expect(result.success).toBe(false);
285
- expect(result.error).toContain("Retry failed");
286
- });
287
-
288
- it("handles request errors gracefully", async () => {
289
- const requestSampling = vi
290
- .fn()
291
- .mockRejectedValue(new Error("Network error"));
292
-
293
- const client = createSamplingClient(
294
- requestSampling,
295
- capabilitiesWithSampling,
296
- mockLogger
297
- );
298
-
299
- const result = await client.createMessage({
300
- systemPrompt: "You are a test assistant",
301
- userPrompt: "Do something",
302
- schema: testSchema,
303
- });
304
-
305
- expect(result.success).toBe(false);
306
- expect(result.error).toBe("Network error");
307
- });
308
-
309
- it("uses custom maxTokens and temperature", async () => {
310
- const requestSampling = vi.fn().mockResolvedValue({
311
- model: "test-model",
312
- stopReason: "end_turn",
313
- content: {
314
- type: "text",
315
- text: JSON.stringify({ action: "test", value: 42 }),
316
- },
317
- });
318
-
319
- const client = createSamplingClient(
320
- requestSampling,
321
- capabilitiesWithSampling,
322
- mockLogger
323
- );
324
-
325
- await client.createMessage({
326
- systemPrompt: "You are a test assistant",
327
- userPrompt: "Do something",
328
- schema: testSchema,
329
- maxTokens: 1000,
330
- temperature: 0.5,
331
- });
332
-
333
- const request = requestSampling.mock.calls[0][0] as SamplingRequest;
334
- expect(request.maxTokens).toBe(1000);
335
- expect(request.temperature).toBe(0.5);
336
- });
337
-
338
- it("includes system prefix in the system prompt", async () => {
339
- const requestSampling = vi.fn().mockResolvedValue({
340
- model: "test-model",
341
- stopReason: "end_turn",
342
- content: {
343
- type: "text",
344
- text: JSON.stringify({ action: "test", value: 42 }),
345
- },
346
- });
347
-
348
- const client = createSamplingClient(
349
- requestSampling,
350
- capabilitiesWithSampling,
351
- mockLogger
352
- );
353
-
354
- await client.createMessage({
355
- systemPrompt: "You are a test assistant",
356
- userPrompt: "Do something",
357
- schema: testSchema,
358
- });
359
-
360
- const request = requestSampling.mock.calls[0][0] as SamplingRequest;
361
- expect(request.systemPrompt).toContain("[WEBTEST-SYSTEM]");
362
- expect(request.systemPrompt).toContain("You are a test assistant");
363
- });
364
-
365
- it("sets includeContext to thisServer", async () => {
366
- const requestSampling = vi.fn().mockResolvedValue({
367
- model: "test-model",
368
- stopReason: "end_turn",
369
- content: {
370
- type: "text",
371
- text: JSON.stringify({ action: "test", value: 42 }),
372
- },
373
- });
374
-
375
- const client = createSamplingClient(
376
- requestSampling,
377
- capabilitiesWithSampling,
378
- mockLogger
379
- );
380
-
381
- await client.createMessage({
382
- systemPrompt: "You are a test assistant",
383
- userPrompt: "Do something",
384
- schema: testSchema,
385
- });
386
-
387
- const request = requestSampling.mock.calls[0][0] as SamplingRequest;
388
- expect(request.includeContext).toBe("thisServer");
389
- });
390
-
391
- it("returns rawResponse on success", async () => {
392
- const rawJson = JSON.stringify({ action: "test", value: 42 });
393
- const requestSampling = vi.fn().mockResolvedValue({
394
- model: "test-model",
395
- stopReason: "end_turn",
396
- content: { type: "text", text: rawJson },
397
- });
398
-
399
- const client = createSamplingClient(
400
- requestSampling,
401
- capabilitiesWithSampling,
402
- mockLogger
403
- );
404
-
405
- const result = await client.createMessage({
406
- systemPrompt: "You are a test assistant",
407
- userPrompt: "Do something",
408
- schema: testSchema,
409
- });
410
-
411
- expect(result.rawResponse).toBe(rawJson);
412
- });
413
- });
414
- });
@@ -1,286 +0,0 @@
1
- import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
- import type { z } from "zod";
3
- import type { Logger } from "../logger.js";
4
- import type { ClientCapabilities } from "../types/capabilities.js";
5
- import {
6
- type SamplingRequest,
7
- type SamplingResponse,
8
- type SamplingResult,
9
- type SamplingOptions,
10
- type FallbackPromptResource,
11
- } from "./types.js";
12
- import { SYSTEM_PREFIX } from "./prompts.js";
13
-
14
- export * from "./types.js";
15
- export * from "./prompts.js";
16
-
17
- export interface SamplingClient {
18
- createMessage<T>(options: SamplingOptions<T>): Promise<SamplingResult<T>>;
19
- hasSampling(): boolean;
20
- }
21
-
22
- export function createSamplingClient(
23
- requestSampling: (request: SamplingRequest) => Promise<SamplingResponse>,
24
- capabilities: ClientCapabilities,
25
- logger: Logger
26
- ): SamplingClient {
27
- const hasSamplingCapability = capabilities.sampling;
28
-
29
- async function createMessage<T>(
30
- options: SamplingOptions<T>
31
- ): Promise<SamplingResult<T>> {
32
- const {
33
- systemPrompt,
34
- userPrompt,
35
- schema,
36
- maxTokens = 4096,
37
- temperature = 0.7,
38
- retryOnFailure = true,
39
- fallbackResourceId,
40
- } = options;
41
-
42
- // If sampling not available, return fallback
43
- if (!hasSamplingCapability) {
44
- logger.info("Sampling unavailable, returning fallback prompt resource");
45
-
46
- const fallback: FallbackPromptResource = {
47
- type: "fallback",
48
- prompt: `${SYSTEM_PREFIX}\n\n${systemPrompt}\n\n${userPrompt}`,
49
- expectedSchema: schema._def,
50
- instructions:
51
- "Execute this prompt with your LLM and provide the JSON response via manualNextActions input",
52
- };
53
-
54
- return {
55
- success: false,
56
- error: "Sampling not available",
57
- promptResource: JSON.stringify(fallback, null, 2),
58
- };
59
- }
60
-
61
- // Build the full system prompt with security prefix
62
- const fullSystemPrompt = `${SYSTEM_PREFIX}\n\n${systemPrompt}`;
63
-
64
- const request: SamplingRequest = {
65
- messages: [
66
- {
67
- role: "user",
68
- content: {
69
- type: "text",
70
- text: userPrompt,
71
- },
72
- },
73
- ],
74
- systemPrompt: fullSystemPrompt,
75
- maxTokens,
76
- temperature,
77
- includeContext: "thisServer",
78
- };
79
-
80
- // Log sampling request for audit (with truncation)
81
- logger.debug("Sampling request", {
82
- systemPromptLength: fullSystemPrompt.length,
83
- userPromptLength: userPrompt.length,
84
- maxTokens,
85
- });
86
-
87
- try {
88
- const response = await requestSampling(request);
89
-
90
- // Log sampling response for audit
91
- logger.debug("Sampling response received", {
92
- model: response.model,
93
- stopReason: response.stopReason,
94
- responseLength: response.content.text.length,
95
- });
96
-
97
- const rawText = response.content.text;
98
-
99
- // Try to parse and validate JSON response
100
- let parsed: unknown;
101
- try {
102
- // Handle potential markdown code blocks
103
- let jsonText = rawText.trim();
104
- if (jsonText.startsWith("```json")) {
105
- jsonText = jsonText.slice(7);
106
- } else if (jsonText.startsWith("```")) {
107
- jsonText = jsonText.slice(3);
108
- }
109
- if (jsonText.endsWith("```")) {
110
- jsonText = jsonText.slice(0, -3);
111
- }
112
- jsonText = jsonText.trim();
113
-
114
- parsed = JSON.parse(jsonText);
115
- } catch (parseError) {
116
- logger.warn("Failed to parse sampling response as JSON", {
117
- error:
118
- parseError instanceof Error ? parseError.message : "Parse error",
119
- rawResponse: rawText.slice(0, 500),
120
- });
121
-
122
- // Retry if enabled
123
- if (retryOnFailure) {
124
- return retryWithFeedback(
125
- options,
126
- rawText,
127
- "Response was not valid JSON. Please respond with valid JSON only.",
128
- requestSampling,
129
- logger
130
- );
131
- }
132
-
133
- return {
134
- success: false,
135
- error: "Failed to parse response as JSON",
136
- rawResponse: rawText,
137
- };
138
- }
139
-
140
- // Validate against schema
141
- const validationResult = schema.safeParse(parsed);
142
-
143
- if (!validationResult.success) {
144
- const errors = validationResult.error.issues
145
- .map((i) => `${i.path.join(".")}: ${i.message}`)
146
- .join("; ");
147
-
148
- logger.warn("Sampling response failed schema validation", {
149
- errors,
150
- parsed,
151
- });
152
-
153
- // Retry if enabled
154
- if (retryOnFailure) {
155
- return retryWithFeedback(
156
- options,
157
- rawText,
158
- `Response did not match expected schema: ${errors}`,
159
- requestSampling,
160
- logger
161
- );
162
- }
163
-
164
- return {
165
- success: false,
166
- error: `Schema validation failed: ${errors}`,
167
- rawResponse: rawText,
168
- };
169
- }
170
-
171
- return {
172
- success: true,
173
- data: validationResult.data,
174
- rawResponse: rawText,
175
- };
176
- } catch (error) {
177
- const message = error instanceof Error ? error.message : "Unknown error";
178
- logger.error("Sampling request failed", { error: message });
179
-
180
- return {
181
- success: false,
182
- error: message,
183
- };
184
- }
185
- }
186
-
187
- return {
188
- createMessage,
189
- hasSampling: () => hasSamplingCapability,
190
- };
191
- }
192
-
193
- async function retryWithFeedback<T>(
194
- originalOptions: SamplingOptions<T>,
195
- previousResponse: string,
196
- feedbackMessage: string,
197
- requestSampling: (request: SamplingRequest) => Promise<SamplingResponse>,
198
- logger: Logger
199
- ): Promise<SamplingResult<T>> {
200
- logger.info("Retrying sampling with error feedback");
201
-
202
- const {
203
- systemPrompt,
204
- userPrompt,
205
- schema,
206
- maxTokens = 4096,
207
- temperature = 0.7,
208
- } = originalOptions;
209
-
210
- const fullSystemPrompt = `${SYSTEM_PREFIX}\n\n${systemPrompt}`;
211
-
212
- const request: SamplingRequest = {
213
- messages: [
214
- {
215
- role: "user",
216
- content: {
217
- type: "text",
218
- text: userPrompt,
219
- },
220
- },
221
- {
222
- role: "assistant",
223
- content: {
224
- type: "text",
225
- text: previousResponse,
226
- },
227
- },
228
- {
229
- role: "user",
230
- content: {
231
- type: "text",
232
- text: `ERROR: ${feedbackMessage}\n\nPlease provide a corrected response with valid JSON matching the expected schema.`,
233
- },
234
- },
235
- ],
236
- systemPrompt: fullSystemPrompt,
237
- maxTokens,
238
- temperature,
239
- includeContext: "thisServer",
240
- };
241
-
242
- try {
243
- const response = await requestSampling(request);
244
- const rawText = response.content.text;
245
-
246
- let jsonText = rawText.trim();
247
- if (jsonText.startsWith("```json")) {
248
- jsonText = jsonText.slice(7);
249
- } else if (jsonText.startsWith("```")) {
250
- jsonText = jsonText.slice(3);
251
- }
252
- if (jsonText.endsWith("```")) {
253
- jsonText = jsonText.slice(0, -3);
254
- }
255
- jsonText = jsonText.trim();
256
-
257
- const parsed = JSON.parse(jsonText);
258
- const validationResult = schema.safeParse(parsed);
259
-
260
- if (!validationResult.success) {
261
- const errors = validationResult.error.issues
262
- .map((i) => `${i.path.join(".")}: ${i.message}`)
263
- .join("; ");
264
-
265
- return {
266
- success: false,
267
- error: `Retry also failed schema validation: ${errors}`,
268
- rawResponse: rawText,
269
- };
270
- }
271
-
272
- return {
273
- success: true,
274
- data: validationResult.data,
275
- rawResponse: rawText,
276
- };
277
- } catch (error) {
278
- const message = error instanceof Error ? error.message : "Unknown error";
279
- logger.error("Sampling retry failed", { error: message });
280
-
281
- return {
282
- success: false,
283
- error: `Retry failed: ${message}`,
284
- };
285
- }
286
- }