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.
- package/README.md +59 -40
- package/dist/config.js +8 -8
- package/dist/config.js.map +1 -1
- package/dist/logger.js +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +21 -21
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/templates/mcp/retest-crawl.md +7 -0
- package/{src/prompts/templates/mcp/webtest-discover-flows.md → dist/prompts/templates/mcp/retest-discover-flows.md} +1 -1
- package/{src/prompts/templates/mcp/webtest-discover.md → dist/prompts/templates/mcp/retest-discover.md} +2 -2
- package/dist/prompts/templates/mcp/retest-full-workflow.md +12 -0
- package/{src/prompts/templates/mcp/webtest-generate-tests.md → dist/prompts/templates/mcp/retest-generate-tests.md} +1 -1
- package/{src/prompts/templates/mcp/webtest-run-test.md → dist/prompts/templates/mcp/retest-run-test.md} +1 -1
- package/{src/prompts/templates/mcp/webtest-start.md → dist/prompts/templates/mcp/retest-start.md} +1 -1
- package/{src → dist}/prompts/templates/sampling/system-prefix.md +1 -1
- package/dist/resources/index.js +7 -7
- package/dist/resources/index.js.map +1 -1
- package/dist/schemas/config.js +2 -2
- package/dist/schemas/config.js.map +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server.js +3 -3
- package/dist/server.js.map +1 -1
- package/dist/test-utils/mock-context.js +22 -22
- package/dist/test-utils/mock-context.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -5
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/retest/crawl.d.ts.map +1 -0
- package/dist/tools/{webtest → retest}/crawl.js +7 -7
- package/dist/tools/retest/crawl.js.map +1 -0
- package/dist/tools/retest/discover-features.d.ts.map +1 -0
- package/dist/tools/{webtest → retest}/discover-features.js +6 -6
- package/dist/tools/retest/discover-features.js.map +1 -0
- package/dist/tools/retest/discover-flows.d.ts.map +1 -0
- package/dist/tools/{webtest → retest}/discover-flows.js +6 -6
- package/dist/tools/retest/discover-flows.js.map +1 -0
- package/dist/tools/retest/generate-tests.d.ts.map +1 -0
- package/dist/tools/{webtest → retest}/generate-tests.js +5 -5
- package/dist/tools/retest/generate-tests.js.map +1 -0
- package/dist/tools/retest/index.d.ts.map +1 -0
- package/dist/tools/retest/index.js.map +1 -0
- package/dist/tools/retest/run-test-case.d.ts.map +1 -0
- package/dist/tools/{webtest → retest}/run-test-case.js +3 -3
- package/dist/tools/retest/run-test-case.js.map +1 -0
- package/dist/tools/retest/schemas.d.ts.map +1 -0
- package/dist/tools/retest/schemas.js.map +1 -0
- package/dist/tools/retest/start-analysis.d.ts.map +1 -0
- package/dist/tools/{webtest → retest}/start-analysis.js +5 -5
- package/dist/tools/retest/start-analysis.js.map +1 -0
- package/dist/workspace/index.js +8 -8
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/types.d.ts +2 -2
- package/dist/workspace/types.d.ts.map +1 -1
- package/package.json +6 -2
- package/.claude/commands/openspec/apply.md +0 -23
- package/.claude/commands/openspec/archive.md +0 -27
- package/.claude/commands/openspec/proposal.md +0 -28
- package/.gemini/commands/openspec/apply.toml +0 -21
- package/.gemini/commands/openspec/archive.toml +0 -25
- package/.gemini/commands/openspec/proposal.toml +0 -26
- package/.github/prompts/openspec-apply.prompt.md +0 -22
- package/.github/prompts/openspec-archive.prompt.md +0 -26
- package/.github/prompts/openspec-proposal.prompt.md +0 -27
- package/.github/workflows/release.yml +0 -33
- package/.kilocode/workflows/openspec-apply.md +0 -17
- package/.kilocode/workflows/openspec-archive.md +0 -21
- package/.kilocode/workflows/openspec-proposal.md +0 -22
- package/.mcp.json +0 -23
- package/.opencode/command/openspec-apply.md +0 -25
- package/.opencode/command/openspec-archive.md +0 -28
- package/.opencode/command/openspec-proposal.md +0 -30
- package/.roo/commands/openspec-apply.md +0 -20
- package/.roo/commands/openspec-archive.md +0 -24
- package/.roo/commands/openspec-proposal.md +0 -25
- package/.vscode/mcp.json +0 -23
- package/AGENTS.md +0 -18
- package/CLAUDE.md +0 -18
- package/dist/tools/webtest/crawl.d.ts.map +0 -1
- package/dist/tools/webtest/crawl.js.map +0 -1
- package/dist/tools/webtest/discover-features.d.ts.map +0 -1
- package/dist/tools/webtest/discover-features.js.map +0 -1
- package/dist/tools/webtest/discover-flows.d.ts.map +0 -1
- package/dist/tools/webtest/discover-flows.js.map +0 -1
- package/dist/tools/webtest/generate-tests.d.ts.map +0 -1
- package/dist/tools/webtest/generate-tests.js.map +0 -1
- package/dist/tools/webtest/index.d.ts.map +0 -1
- package/dist/tools/webtest/index.js.map +0 -1
- package/dist/tools/webtest/run-test-case.d.ts.map +0 -1
- package/dist/tools/webtest/run-test-case.js.map +0 -1
- package/dist/tools/webtest/schemas.d.ts.map +0 -1
- package/dist/tools/webtest/schemas.js.map +0 -1
- package/dist/tools/webtest/start-analysis.d.ts.map +0 -1
- package/dist/tools/webtest/start-analysis.js.map +0 -1
- package/openspec/AGENTS.md +0 -456
- package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/proposal.md +0 -33
- package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-resources/spec.md +0 -27
- package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-tools/spec.md +0 -304
- package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/tasks.md +0 -43
- package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/design.md +0 -209
- package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/proposal.md +0 -41
- package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/specs/mcp-server-core/spec.md +0 -183
- package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/tasks.md +0 -112
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/design.md +0 -333
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/proposal.md +0 -66
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/mcp-server-core/spec.md +0 -129
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-lifecycle/spec.md +0 -138
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-logging/spec.md +0 -211
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-prompts/spec.md +0 -157
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-resources/spec.md +0 -213
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-sampling/spec.md +0 -257
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-tools/spec.md +0 -501
- package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/tasks.md +0 -264
- package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/proposal.md +0 -24
- package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/specs/webtest-tools/spec.md +0 -80
- package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/tasks.md +0 -8
- package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/design.md +0 -90
- package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/proposal.md +0 -28
- package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/specs/webtest-sampling/spec.md +0 -90
- package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/tasks.md +0 -33
- package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/design.md +0 -558
- package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/proposal.md +0 -119
- package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-resources/spec.md +0 -109
- package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-tools/spec.md +0 -121
- package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/tasks.md +0 -133
- package/openspec/changes/extract-prompts-to-markdown/design.md +0 -86
- package/openspec/changes/extract-prompts-to-markdown/proposal.md +0 -50
- package/openspec/changes/extract-prompts-to-markdown/specs/webtest-prompts/spec.md +0 -74
- package/openspec/changes/extract-prompts-to-markdown/tasks.md +0 -40
- package/openspec/changes/refactor-webtest-naming/design.md +0 -95
- package/openspec/changes/refactor-webtest-naming/proposal.md +0 -66
- package/openspec/changes/refactor-webtest-naming/specs/webtest-prompts/spec.md +0 -79
- package/openspec/changes/refactor-webtest-naming/specs/webtest-resources/spec.md +0 -80
- package/openspec/changes/refactor-webtest-naming/specs/webtest-sampling/spec.md +0 -122
- package/openspec/changes/refactor-webtest-naming/specs/webtest-tools/spec.md +0 -113
- package/openspec/changes/refactor-webtest-naming/tasks.md +0 -119
- package/openspec/changes/rename-package-to-retest/proposal.md +0 -52
- package/openspec/changes/rename-package-to-retest/specs/mcp-server-core/spec.md +0 -53
- package/openspec/changes/rename-package-to-retest/specs/retest-lifecycle/spec.md +0 -68
- package/openspec/changes/rename-package-to-retest/specs/retest-logging/spec.md +0 -35
- package/openspec/changes/rename-package-to-retest/specs/retest-prompts/spec.md +0 -159
- package/openspec/changes/rename-package-to-retest/specs/retest-resources/spec.md +0 -251
- package/openspec/changes/rename-package-to-retest/specs/retest-sampling/spec.md +0 -99
- package/openspec/changes/rename-package-to-retest/specs/retest-tools/spec.md +0 -295
- package/openspec/changes/rename-package-to-retest/tasks.md +0 -71
- package/openspec/project.md +0 -31
- package/openspec/specs/mcp-server-core/spec.md +0 -178
- package/openspec/specs/webtest-lifecycle/spec.md +0 -136
- package/openspec/specs/webtest-logging/spec.md +0 -209
- package/openspec/specs/webtest-prompts/spec.md +0 -155
- package/openspec/specs/webtest-resources/spec.md +0 -248
- package/openspec/specs/webtest-sampling/spec.md +0 -344
- package/openspec/specs/webtest-tools/spec.md +0 -282
- package/release.config.js +0 -9
- package/src/config.test.ts +0 -96
- package/src/config.ts +0 -32
- package/src/elicitation/index.test.ts +0 -399
- package/src/elicitation/index.ts +0 -171
- package/src/elicitation/types.ts +0 -68
- package/src/index.ts +0 -83
- package/src/lifecycle/index.test.ts +0 -260
- package/src/lifecycle/index.ts +0 -101
- package/src/logger.redaction.test.ts +0 -322
- package/src/logger.test.ts +0 -123
- package/src/logger.ts +0 -229
- package/src/playwright-client/index.ts +0 -392
- package/src/playwright-client/types.ts +0 -99
- package/src/progress/index.test.ts +0 -327
- package/src/progress/index.ts +0 -170
- package/src/progress/types.ts +0 -25
- package/src/prompts/index.test.ts +0 -451
- package/src/prompts/index.ts +0 -246
- package/src/prompts/loader.test.ts +0 -100
- package/src/prompts/loader.ts +0 -59
- package/src/prompts/templates/mcp/webtest-crawl.md +0 -7
- package/src/prompts/templates/mcp/webtest-full-workflow.md +0 -12
- package/src/resources/index.ts +0 -250
- package/src/resources/subscriptions.ts +0 -37
- package/src/sampling/index.test.ts +0 -414
- package/src/sampling/index.ts +0 -286
- package/src/sampling/prompts.ts +0 -194
- package/src/sampling/types.ts +0 -60
- package/src/schemas/config.ts +0 -39
- package/src/security/index.test.ts +0 -441
- package/src/security/index.ts +0 -361
- package/src/security/security-scenarios.test.ts +0 -468
- package/src/server.ts +0 -211
- package/src/test-utils/index.ts +0 -6
- package/src/test-utils/mock-context.ts +0 -426
- package/src/test-utils/mock-playwright-client.ts +0 -422
- package/src/tools/index.ts +0 -11
- package/src/tools/webtest/crawl.test.ts +0 -834
- package/src/tools/webtest/crawl.ts +0 -901
- package/src/tools/webtest/discover-features.ts +0 -412
- package/src/tools/webtest/discover-flows.ts +0 -408
- package/src/tools/webtest/generate-tests.test.ts +0 -532
- package/src/tools/webtest/generate-tests.ts +0 -425
- package/src/tools/webtest/index.ts +0 -7
- package/src/tools/webtest/integration.test.ts +0 -536
- package/src/tools/webtest/run-test-case.test.ts +0 -659
- package/src/tools/webtest/run-test-case.ts +0 -508
- package/src/tools/webtest/schemas.ts +0 -201
- package/src/tools/webtest/start-analysis.test.ts +0 -151
- package/src/tools/webtest/start-analysis.ts +0 -158
- package/src/transports/http.ts +0 -19
- package/src/transports/index.ts +0 -30
- package/src/transports/stdio.ts +0 -7
- package/src/types/capabilities.test.ts +0 -193
- package/src/types/capabilities.ts +0 -50
- package/src/types/context.ts +0 -21
- package/src/types/tool.ts +0 -11
- package/src/workspace/index.ts +0 -945
- package/src/workspace/markdown.ts +0 -272
- package/src/workspace/types.ts +0 -186
- package/tests/integration/server.test.ts +0 -89
- package/tests/integration/tools.test.ts +0 -99
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -9
- package/vitest.integration.config.ts +0 -10
- /package/{src → dist}/prompts/templates/sampling/crawl-action.md +0 -0
- /package/{src → dist}/prompts/templates/sampling/feature-discovery.md +0 -0
- /package/{src → dist}/prompts/templates/sampling/flow-discovery.md +0 -0
- /package/{src → dist}/prompts/templates/sampling/page-content-wrapper.md +0 -0
- /package/{src → dist}/prompts/templates/sampling/test-evaluation.md +0 -0
- /package/{src → dist}/prompts/templates/sampling/test-generation.md +0 -0
- /package/dist/tools/{webtest → retest}/crawl.d.ts +0 -0
- /package/dist/tools/{webtest → retest}/discover-features.d.ts +0 -0
- /package/dist/tools/{webtest → retest}/discover-flows.d.ts +0 -0
- /package/dist/tools/{webtest → retest}/generate-tests.d.ts +0 -0
- /package/dist/tools/{webtest → retest}/index.d.ts +0 -0
- /package/dist/tools/{webtest → retest}/index.js +0 -0
- /package/dist/tools/{webtest → retest}/run-test-case.d.ts +0 -0
- /package/dist/tools/{webtest → retest}/schemas.d.ts +0 -0
- /package/dist/tools/{webtest → retest}/schemas.js +0 -0
- /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
|
-
});
|
package/src/sampling/index.ts
DELETED
|
@@ -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
|
-
}
|