@mcpmesh/sdk 2.3.0 → 2.4.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 (77) hide show
  1. package/dist/__tests__/llm-agent-model-params.test.js +83 -0
  2. package/dist/__tests__/llm-agent-model-params.test.js.map +1 -1
  3. package/dist/__tests__/llm-max-iterations.test.d.ts +20 -0
  4. package/dist/__tests__/llm-max-iterations.test.d.ts.map +1 -0
  5. package/dist/__tests__/llm-max-iterations.test.js +250 -0
  6. package/dist/__tests__/llm-max-iterations.test.js.map +1 -0
  7. package/dist/__tests__/llm-mesh-error-mapping.test.d.ts +16 -0
  8. package/dist/__tests__/llm-mesh-error-mapping.test.d.ts.map +1 -0
  9. package/dist/__tests__/llm-mesh-error-mapping.test.js +135 -0
  10. package/dist/__tests__/llm-mesh-error-mapping.test.js.map +1 -0
  11. package/dist/__tests__/llm-provider-output-mode.test.d.ts +21 -0
  12. package/dist/__tests__/llm-provider-output-mode.test.d.ts.map +1 -0
  13. package/dist/__tests__/llm-provider-output-mode.test.js +115 -0
  14. package/dist/__tests__/llm-provider-output-mode.test.js.map +1 -0
  15. package/dist/__tests__/llm-provider-system-synthesis.test.d.ts +20 -0
  16. package/dist/__tests__/llm-provider-system-synthesis.test.d.ts.map +1 -0
  17. package/dist/__tests__/llm-provider-system-synthesis.test.js +167 -0
  18. package/dist/__tests__/llm-provider-system-synthesis.test.js.map +1 -0
  19. package/dist/__tests__/llm-response-model.test.d.ts +10 -0
  20. package/dist/__tests__/llm-response-model.test.d.ts.map +1 -0
  21. package/dist/__tests__/llm-response-model.test.js +92 -0
  22. package/dist/__tests__/llm-response-model.test.js.map +1 -0
  23. package/dist/__tests__/proxy-timeout-guard.test.d.ts +12 -0
  24. package/dist/__tests__/proxy-timeout-guard.test.d.ts.map +1 -0
  25. package/dist/__tests__/proxy-timeout-guard.test.js +85 -0
  26. package/dist/__tests__/proxy-timeout-guard.test.js.map +1 -0
  27. package/dist/__tests__/registry-disconnect-retains-deps.spec.d.ts +2 -0
  28. package/dist/__tests__/registry-disconnect-retains-deps.spec.d.ts.map +1 -0
  29. package/dist/__tests__/registry-disconnect-retains-deps.spec.js +101 -0
  30. package/dist/__tests__/registry-disconnect-retains-deps.spec.js.map +1 -0
  31. package/dist/__tests__/response-parser.test.js +29 -0
  32. package/dist/__tests__/response-parser.test.js.map +1 -1
  33. package/dist/agent.d.ts.map +1 -1
  34. package/dist/agent.js +4 -0
  35. package/dist/agent.js.map +1 -1
  36. package/dist/api-runtime.d.ts.map +1 -1
  37. package/dist/api-runtime.js +8 -1
  38. package/dist/api-runtime.js.map +1 -1
  39. package/dist/express.d.ts.map +1 -1
  40. package/dist/express.js +8 -1
  41. package/dist/express.js.map +1 -1
  42. package/dist/llm-agent.d.ts +34 -0
  43. package/dist/llm-agent.d.ts.map +1 -1
  44. package/dist/llm-agent.js +239 -434
  45. package/dist/llm-agent.js.map +1 -1
  46. package/dist/llm-provider.d.ts +33 -4
  47. package/dist/llm-provider.d.ts.map +1 -1
  48. package/dist/llm-provider.js +91 -4
  49. package/dist/llm-provider.js.map +1 -1
  50. package/dist/llm.d.ts +1 -1
  51. package/dist/llm.d.ts.map +1 -1
  52. package/dist/llm.js +8 -5
  53. package/dist/llm.js.map +1 -1
  54. package/dist/provider-handlers/gemini-handler.d.ts.map +1 -1
  55. package/dist/provider-handlers/gemini-handler.js +2 -14
  56. package/dist/provider-handlers/gemini-handler.js.map +1 -1
  57. package/dist/provider-handlers/openai-handler.d.ts.map +1 -1
  58. package/dist/provider-handlers/openai-handler.js +2 -15
  59. package/dist/provider-handlers/openai-handler.js.map +1 -1
  60. package/dist/provider-handlers/provider-handler.d.ts +12 -0
  61. package/dist/provider-handlers/provider-handler.d.ts.map +1 -1
  62. package/dist/provider-handlers/provider-handler.js +24 -0
  63. package/dist/provider-handlers/provider-handler.js.map +1 -1
  64. package/dist/proxy.d.ts.map +1 -1
  65. package/dist/proxy.js +189 -254
  66. package/dist/proxy.js.map +1 -1
  67. package/dist/response-parser.d.ts +10 -0
  68. package/dist/response-parser.d.ts.map +1 -1
  69. package/dist/response-parser.js +55 -0
  70. package/dist/response-parser.js.map +1 -1
  71. package/dist/tracing.d.ts +12 -0
  72. package/dist/tracing.d.ts.map +1 -1
  73. package/dist/tracing.js +37 -0
  74. package/dist/tracing.js.map +1 -1
  75. package/dist/types.d.ts +10 -2
  76. package/dist/types.d.ts.map +1 -1
  77. package/package.json +2 -2
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Provider-side honoring of the consumer-supplied output_mode override — #1112.
3
+ *
4
+ * The provider's effective output mode is:
5
+ * effective = (model_params.output_mode is one of strict/hint/text)
6
+ * ? that override
7
+ * : handler.determineOutputMode(outputSchema) // today's auto
8
+ *
9
+ * Observability seam: the strict path drives `generateObject()` while
10
+ * hint/text drive `generateText()` (useStructuredOutput === outputMode ===
11
+ * "strict"). For an OpenAI provider WITH a schema and NO tools, auto-selection
12
+ * is "strict" → generateObject. A "hint"/"text" override flips it to
13
+ * generateText. An absent/invalid override leaves auto (strict) intact.
14
+ *
15
+ * Also asserts output_mode is stripped from the params before they reach the
16
+ * vendor SDK call (generateObject/generateText options).
17
+ *
18
+ * The Vercel `ai` module is mocked — no real LLM is invoked.
19
+ */
20
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
21
+ const generateObjectMock = vi.fn();
22
+ const generateTextMock = vi.fn();
23
+ vi.mock("ai", () => ({
24
+ generateText: (opts) => generateTextMock(opts),
25
+ generateObject: (opts) => generateObjectMock(opts),
26
+ jsonSchema: (schema) => schema,
27
+ tool: (config) => config,
28
+ }));
29
+ // Keep tracing inert/deterministic (publishTraceSpan is best-effort anyway).
30
+ vi.mock("../tracing.js", () => ({
31
+ generateTraceId: () => "trace-mock",
32
+ generateSpanId: () => "span-mock",
33
+ publishTraceSpan: vi.fn(async () => { }),
34
+ matchesPropagateHeader: () => false,
35
+ }));
36
+ import { llmProvider } from "../llm-provider.js";
37
+ const SCHEMA = {
38
+ type: "object",
39
+ properties: { answer: { type: "string" } },
40
+ required: ["answer"],
41
+ };
42
+ function makeProvider() {
43
+ // OpenAI vendor: auto-selects "strict" when a schema is present.
44
+ return llmProvider({ model: "openai/gpt-4o", capability: "llm" });
45
+ }
46
+ function baseRequest(modelParams) {
47
+ return {
48
+ request: {
49
+ messages: [
50
+ { role: "system", content: "You are helpful." },
51
+ { role: "user", content: "hi" },
52
+ ],
53
+ model_params: {
54
+ output_schema: SCHEMA,
55
+ output_type_name: "Answer",
56
+ ...modelParams,
57
+ },
58
+ },
59
+ };
60
+ }
61
+ describe("provider output_mode override (#1112)", () => {
62
+ beforeEach(() => {
63
+ generateObjectMock.mockReset();
64
+ generateTextMock.mockReset();
65
+ generateObjectMock.mockResolvedValue({
66
+ object: { answer: "ok" },
67
+ usage: { inputTokens: 1, outputTokens: 1 },
68
+ finishReason: "stop",
69
+ });
70
+ generateTextMock.mockResolvedValue({
71
+ text: "ok",
72
+ toolCalls: [],
73
+ usage: { inputTokens: 1, outputTokens: 1 },
74
+ finishReason: "stop",
75
+ });
76
+ });
77
+ afterEach(() => {
78
+ vi.restoreAllMocks();
79
+ });
80
+ it("auto-selects strict (generateObject) when no override is present — byte-identical to today", async () => {
81
+ const tool = makeProvider();
82
+ await tool.execute(baseRequest({}));
83
+ expect(generateObjectMock).toHaveBeenCalledTimes(1);
84
+ expect(generateTextMock).not.toHaveBeenCalled();
85
+ });
86
+ it("honors a 'hint' override where it would auto-select strict (uses generateText)", async () => {
87
+ const tool = makeProvider();
88
+ await tool.execute(baseRequest({ output_mode: "hint" }));
89
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
90
+ expect(generateObjectMock).not.toHaveBeenCalled();
91
+ });
92
+ it("honors a 'text' override (uses generateText)", async () => {
93
+ const tool = makeProvider();
94
+ await tool.execute(baseRequest({ output_mode: "text" }));
95
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
96
+ expect(generateObjectMock).not.toHaveBeenCalled();
97
+ });
98
+ it("ignores an invalid override and falls back to auto (strict → generateObject)", async () => {
99
+ const tool = makeProvider();
100
+ await tool.execute(baseRequest({ output_mode: "bogus" }));
101
+ expect(generateObjectMock).toHaveBeenCalledTimes(1);
102
+ expect(generateTextMock).not.toHaveBeenCalled();
103
+ });
104
+ it("strips output_mode from the params reaching the vendor SDK call", async () => {
105
+ const tool = makeProvider();
106
+ await tool.execute(baseRequest({ output_mode: "hint" }));
107
+ // hint → generateText; assert the options object carries no output_mode.
108
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
109
+ const opts = generateTextMock.mock.calls[0][0];
110
+ expect("output_mode" in opts).toBe(false);
111
+ // Nested guard: output_mode must not leak via providerOptions either.
112
+ expect(JSON.stringify(opts)).not.toContain("output_mode");
113
+ });
114
+ });
115
+ //# sourceMappingURL=llm-provider-output-mode.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-provider-output-mode.test.js","sourceRoot":"","sources":["../../src/__tests__/llm-provider-output-mode.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Provider synthesizes a system message when the consumer supplies none but
3
+ * structured-output (hint mode) instructions still need to reach the model —
4
+ * #1112 finding 6.
5
+ *
6
+ * Root cause: the request.messages transform only augmented an EXISTING system
7
+ * message with the schema/JSON hint instructions. A mesh-delegated consumer
8
+ * with a typed schema and NO systemPrompt produced no system message, so the
9
+ * hint instructions were silently dropped. In hint mode there is no native
10
+ * response_format backstop (strict-only), so the model returned prose and the
11
+ * consumer's ResponseParser threw "Could not extract JSON from response."
12
+ *
13
+ * Fix: when NO system message exists AND mode !== "text" AND a schema is
14
+ * present, synthesize a system message via formatSystemPrompt("", ...) and
15
+ * prepend it.
16
+ *
17
+ * The Vercel `ai` module is mocked — no real LLM is invoked.
18
+ */
19
+ export {};
20
+ //# sourceMappingURL=llm-provider-system-synthesis.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-provider-system-synthesis.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/llm-provider-system-synthesis.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Provider synthesizes a system message when the consumer supplies none but
3
+ * structured-output (hint mode) instructions still need to reach the model —
4
+ * #1112 finding 6.
5
+ *
6
+ * Root cause: the request.messages transform only augmented an EXISTING system
7
+ * message with the schema/JSON hint instructions. A mesh-delegated consumer
8
+ * with a typed schema and NO systemPrompt produced no system message, so the
9
+ * hint instructions were silently dropped. In hint mode there is no native
10
+ * response_format backstop (strict-only), so the model returned prose and the
11
+ * consumer's ResponseParser threw "Could not extract JSON from response."
12
+ *
13
+ * Fix: when NO system message exists AND mode !== "text" AND a schema is
14
+ * present, synthesize a system message via formatSystemPrompt("", ...) and
15
+ * prepend it.
16
+ *
17
+ * The Vercel `ai` module is mocked — no real LLM is invoked.
18
+ */
19
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
20
+ const generateObjectMock = vi.fn();
21
+ const generateTextMock = vi.fn();
22
+ vi.mock("ai", () => ({
23
+ generateText: (opts) => generateTextMock(opts),
24
+ generateObject: (opts) => generateObjectMock(opts),
25
+ jsonSchema: (schema) => schema,
26
+ tool: (config) => config,
27
+ }));
28
+ vi.mock("../tracing.js", () => ({
29
+ generateTraceId: () => "trace-mock",
30
+ generateSpanId: () => "span-mock",
31
+ publishTraceSpan: vi.fn(async () => { }),
32
+ matchesPropagateHeader: () => false,
33
+ }));
34
+ import { llmProvider } from "../llm-provider.js";
35
+ const SCHEMA = {
36
+ type: "object",
37
+ properties: { answer: { type: "string" } },
38
+ required: ["answer"],
39
+ };
40
+ function makeProvider() {
41
+ // OpenAI vendor: auto-selects "strict" when a schema is present.
42
+ return llmProvider({ model: "openai/gpt-4o", capability: "llm" });
43
+ }
44
+ function systemMessages(opts) {
45
+ return opts.messages.filter((m) => m.role === "system");
46
+ }
47
+ beforeEach(() => {
48
+ generateObjectMock.mockReset();
49
+ generateTextMock.mockReset();
50
+ generateObjectMock.mockResolvedValue({
51
+ object: { answer: "ok" },
52
+ usage: { inputTokens: 1, outputTokens: 1 },
53
+ finishReason: "stop",
54
+ });
55
+ generateTextMock.mockResolvedValue({
56
+ text: '{"answer":"ok"}',
57
+ toolCalls: [],
58
+ usage: { inputTokens: 1, outputTokens: 1 },
59
+ finishReason: "stop",
60
+ });
61
+ });
62
+ afterEach(() => {
63
+ vi.restoreAllMocks();
64
+ });
65
+ describe("provider system-message synthesis (#1112 finding 6)", () => {
66
+ it("synthesizes a system message with schema/JSON hint instructions when none exists (hint + schema)", async () => {
67
+ const tool = makeProvider();
68
+ await tool.execute({
69
+ request: {
70
+ // NO system message — only a user turn.
71
+ messages: [{ role: "user", content: "hi" }],
72
+ model_params: {
73
+ output_schema: SCHEMA,
74
+ output_type_name: "Answer",
75
+ output_mode: "hint",
76
+ },
77
+ },
78
+ });
79
+ // hint → generateText.
80
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
81
+ const opts = generateTextMock.mock.calls[0][0];
82
+ const systems = systemMessages(opts);
83
+ // A system message was synthesized and prepended.
84
+ expect(systems).toHaveLength(1);
85
+ expect(opts.messages[0].role).toBe("system");
86
+ const content = systems[0].content;
87
+ // Carries the hint-mode JSON instructions + schema field.
88
+ expect(content).toContain("OUTPUT FORMAT:");
89
+ expect(content).toContain("ONLY valid JSON");
90
+ expect(content).toContain("answer");
91
+ });
92
+ it("augments an EXISTING system message (hint + schema) without double-adding", async () => {
93
+ const tool = makeProvider();
94
+ await tool.execute({
95
+ request: {
96
+ messages: [
97
+ { role: "system", content: "You are helpful." },
98
+ { role: "user", content: "hi" },
99
+ ],
100
+ model_params: {
101
+ output_schema: SCHEMA,
102
+ output_type_name: "Answer",
103
+ output_mode: "hint",
104
+ },
105
+ },
106
+ });
107
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
108
+ const opts = generateTextMock.mock.calls[0][0];
109
+ const systems = systemMessages(opts);
110
+ // Exactly one system message — the original, augmented (not double-added).
111
+ expect(systems).toHaveLength(1);
112
+ const content = systems[0].content;
113
+ expect(content).toContain("You are helpful.");
114
+ expect(content).toContain("OUTPUT FORMAT:");
115
+ expect(content).toContain("answer");
116
+ });
117
+ it("does NOT synthesize for strict mode with no system message (native response_format backstop)", async () => {
118
+ const tool = makeProvider();
119
+ await tool.execute({
120
+ request: {
121
+ messages: [{ role: "user", content: "hi" }],
122
+ model_params: {
123
+ output_schema: SCHEMA,
124
+ output_type_name: "Answer",
125
+ // No override → OpenAI auto-selects strict (no tools, has schema).
126
+ },
127
+ },
128
+ });
129
+ // strict + schema + no tools → generateObject (structured output intact).
130
+ expect(generateObjectMock).toHaveBeenCalledTimes(1);
131
+ expect(generateTextMock).not.toHaveBeenCalled();
132
+ const opts = generateObjectMock.mock.calls[0][0];
133
+ // No system message was synthesized — strict relies on response_format.
134
+ expect(systemMessages(opts)).toHaveLength(0);
135
+ });
136
+ it("does NOT synthesize for text mode with no system message", async () => {
137
+ const tool = makeProvider();
138
+ await tool.execute({
139
+ request: {
140
+ messages: [{ role: "user", content: "hi" }],
141
+ model_params: {
142
+ output_schema: SCHEMA,
143
+ output_type_name: "Answer",
144
+ output_mode: "text",
145
+ },
146
+ },
147
+ });
148
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
149
+ const opts = generateTextMock.mock.calls[0][0];
150
+ expect(systemMessages(opts)).toHaveLength(0);
151
+ });
152
+ it("does NOT synthesize when there is no schema (hint mode, no system message)", async () => {
153
+ const tool = makeProvider();
154
+ await tool.execute({
155
+ request: {
156
+ messages: [{ role: "user", content: "hi" }],
157
+ model_params: {
158
+ output_mode: "hint",
159
+ },
160
+ },
161
+ });
162
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
163
+ const opts = generateTextMock.mock.calls[0][0];
164
+ expect(systemMessages(opts)).toHaveLength(0);
165
+ });
166
+ });
167
+ //# sourceMappingURL=llm-provider-system-synthesis.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-provider-system-synthesis.test.js","sourceRoot":"","sources":["../../src/__tests__/llm-provider-system-synthesis.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Unit tests for mesh.llm() responseModel option (issue #1094).
3
+ *
4
+ * `responseModel` specifies the schema the LLM must emit and is validated
5
+ * against (it feeds the provider's structured-output schema and the response
6
+ * parser). When omitted it falls back to `returns`. `returns` continues to
7
+ * type what `execute` returns to callers.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=llm-response-model.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-response-model.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/llm-response-model.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Unit tests for mesh.llm() responseModel option (issue #1094).
3
+ *
4
+ * `responseModel` specifies the schema the LLM must emit and is validated
5
+ * against (it feeds the provider's structured-output schema and the response
6
+ * parser). When omitted it falls back to `returns`. `returns` continues to
7
+ * type what `execute` returns to callers.
8
+ */
9
+ import { describe, it, expect, expectTypeOf } from "vitest";
10
+ import { z } from "zod";
11
+ import { llm } from "../llm.js";
12
+ const BigSchema = z.object({
13
+ id: z.string(),
14
+ title: z.string(),
15
+ body: z.string(),
16
+ metadata: z.record(z.string(), z.unknown()),
17
+ });
18
+ const SmallSchema = z.object({
19
+ id: z.string(),
20
+ });
21
+ const baseConfig = {
22
+ provider: { capability: "llm-service" },
23
+ parameters: z.object({ query: z.string() }),
24
+ };
25
+ describe("mesh.llm responseModel", () => {
26
+ it("uses responseModel as returnSchema when both responseModel and returns are provided", () => {
27
+ const tool = llm({
28
+ ...baseConfig,
29
+ name: "tool-with-response-model",
30
+ responseModel: SmallSchema,
31
+ returns: BigSchema,
32
+ execute: async () => ({
33
+ id: "1",
34
+ title: "t",
35
+ body: "b",
36
+ metadata: {},
37
+ }),
38
+ });
39
+ // returnSchema drives provider output schema + ResponseParser; must be the
40
+ // responseModel, not the (broader) returns type.
41
+ expect(tool._meshLlmConfig.returnSchema).toBe(SmallSchema);
42
+ expect(tool._meshLlmConfig.returnSchema).not.toBe(BigSchema);
43
+ });
44
+ it("falls back to returns when responseModel is omitted (back-compat)", () => {
45
+ const tool = llm({
46
+ ...baseConfig,
47
+ name: "tool-with-returns-only",
48
+ returns: BigSchema,
49
+ execute: async () => ({
50
+ id: "1",
51
+ title: "t",
52
+ body: "b",
53
+ metadata: {},
54
+ }),
55
+ });
56
+ expect(tool._meshLlmConfig.returnSchema).toBe(BigSchema);
57
+ });
58
+ it("leaves returnSchema undefined when neither is provided (string mode)", () => {
59
+ const tool = llm({
60
+ ...baseConfig,
61
+ name: "tool-with-neither",
62
+ execute: async () => "hello",
63
+ });
64
+ expect(tool._meshLlmConfig.returnSchema).toBeUndefined();
65
+ });
66
+ it("types the injected llm callable by responseModel when provided", () => {
67
+ llm({
68
+ ...baseConfig,
69
+ name: "tool-type-response-model",
70
+ responseModel: SmallSchema,
71
+ returns: BigSchema,
72
+ execute: async (_args, { llm: llmCallable }) => {
73
+ // The injected callable is typed by responseModel (SmallSchema), not returns.
74
+ expectTypeOf(llmCallable).returns.resolves.toEqualTypeOf();
75
+ // execute's own return type still follows `returns` (BigSchema).
76
+ return { id: "1", title: "t", body: "b", metadata: {} };
77
+ },
78
+ });
79
+ });
80
+ it("types the injected llm callable by returns when no responseModel", () => {
81
+ llm({
82
+ ...baseConfig,
83
+ name: "tool-type-returns",
84
+ returns: BigSchema,
85
+ execute: async (_args, { llm: llmCallable }) => {
86
+ expectTypeOf(llmCallable).returns.resolves.toEqualTypeOf();
87
+ return { id: "1", title: "t", body: "b", metadata: {} };
88
+ },
89
+ });
90
+ });
91
+ });
92
+ //# sourceMappingURL=llm-response-model.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-response-model.test.js","sourceRoot":"","sources":["../../src/__tests__/llm-response-model.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Unit test for the buffered-path effective-timeout guard in callMcpTool.
3
+ *
4
+ * PR-B (#1116) item 1 hardening: the streaming path already fell back to a
5
+ * positive default when the resolved timeout was non-positive; the buffered
6
+ * path did not. After extracting the shared `buildMcpRequest`, BOTH paths apply
7
+ * the guard. This test asserts callMcpTool no longer aborts-immediately when a
8
+ * caller passes a zero/negative timeout (a setTimeout(…, 0) would otherwise fire
9
+ * on the next tick and abort the in-flight fetch).
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=proxy-timeout-guard.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-timeout-guard.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/proxy-timeout-guard.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Unit test for the buffered-path effective-timeout guard in callMcpTool.
3
+ *
4
+ * PR-B (#1116) item 1 hardening: the streaming path already fell back to a
5
+ * positive default when the resolved timeout was non-positive; the buffered
6
+ * path did not. After extracting the shared `buildMcpRequest`, BOTH paths apply
7
+ * the guard. This test asserts callMcpTool no longer aborts-immediately when a
8
+ * caller passes a zero/negative timeout (a setTimeout(…, 0) would otherwise fire
9
+ * on the next tick and abort the in-flight fetch).
10
+ */
11
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
12
+ import { callMcpTool, DEFAULT_CALL_OPTIONS } from "../proxy.js";
13
+ vi.mock("@mcpmesh/core", () => ({
14
+ generateTraceId: () => "trace-mock",
15
+ generateSpanId: () => "span-mock",
16
+ injectTraceContext: (argsJson) => argsJson,
17
+ publishSpan: vi.fn(async () => false),
18
+ parseSseResponse: (s) => s,
19
+ parseSseResponseToObject: (s) => JSON.parse(s),
20
+ awaitJobCancel: vi.fn(() => new Promise(() => { })),
21
+ matchesPropagateHeader: () => false,
22
+ }));
23
+ vi.mock("../http-pool.js", () => ({
24
+ getDispatcher: () => undefined,
25
+ }));
26
+ const ENDPOINT = "http://producer.local:9000";
27
+ const TOOL = "echo";
28
+ const CAPABILITY = "echoer";
29
+ function jsonResponse(result) {
30
+ const body = JSON.stringify({ jsonrpc: "2.0", id: "x", result });
31
+ return {
32
+ ok: true,
33
+ status: 200,
34
+ statusText: "OK",
35
+ text: async () => body,
36
+ headers: {
37
+ get: (name) => name.toLowerCase() === "content-type" ? "application/json" : null,
38
+ },
39
+ };
40
+ }
41
+ describe("callMcpTool effective-timeout guard (buffered path)", () => {
42
+ let originalFetch;
43
+ beforeEach(() => {
44
+ originalFetch = globalThis.fetch;
45
+ });
46
+ afterEach(() => {
47
+ globalThis.fetch = originalFetch;
48
+ vi.restoreAllMocks();
49
+ });
50
+ it("does not abort-immediately when timeout is 0 (falls back to default)", async () => {
51
+ let aborted = false;
52
+ const fetchMock = vi.fn(async (_url, init) => {
53
+ const signal = init.signal;
54
+ // Yield a couple of microtasks + a macrotask so any setTimeout(…, 0)
55
+ // abort would have had a chance to fire before we resolve.
56
+ await new Promise((resolve) => setTimeout(resolve, 5));
57
+ if (signal?.aborted) {
58
+ aborted = true;
59
+ }
60
+ return jsonResponse({ content: [{ type: "text", text: "ok" }] });
61
+ });
62
+ globalThis.fetch = fetchMock;
63
+ const options = { ...DEFAULT_CALL_OPTIONS, timeout: 0 };
64
+ const result = await callMcpTool(ENDPOINT, TOOL, { foo: 1 }, options, CAPABILITY);
65
+ expect(aborted).toBe(false);
66
+ expect(result).toBe("ok");
67
+ });
68
+ it("does not abort-immediately when timeout is negative", async () => {
69
+ let aborted = false;
70
+ const fetchMock = vi.fn(async (_url, init) => {
71
+ const signal = init.signal;
72
+ await new Promise((resolve) => setTimeout(resolve, 5));
73
+ if (signal?.aborted) {
74
+ aborted = true;
75
+ }
76
+ return jsonResponse({ content: [{ type: "text", text: "ok" }] });
77
+ });
78
+ globalThis.fetch = fetchMock;
79
+ const options = { ...DEFAULT_CALL_OPTIONS, timeout: -100 };
80
+ const result = await callMcpTool(ENDPOINT, TOOL, { foo: 1 }, options, CAPABILITY);
81
+ expect(aborted).toBe(false);
82
+ expect(result).toBe("ok");
83
+ });
84
+ });
85
+ //# sourceMappingURL=proxy-timeout-guard.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-timeout-guard.test.js","sourceRoot":"","sources":["../../src/__tests__/proxy-timeout-guard.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAoB,MAAM,aAAa,CAAC;AAElF,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9B,eAAe,EAAE,GAAG,EAAE,CAAC,YAAY;IACnC,cAAc,EAAE,GAAG,EAAE,CAAC,WAAW;IACjC,kBAAkB,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,QAAQ;IAClD,WAAW,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC;IACrC,gBAAgB,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC;IAClC,wBAAwB,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACtD,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACxD,sBAAsB,EAAE,GAAG,EAAE,CAAC,KAAK;CACpC,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC;IAChC,aAAa,EAAE,GAAG,EAAE,CAAC,SAAS;CAC/B,CAAC,CAAC,CAAC;AAEJ,MAAM,QAAQ,GAAG,4BAA4B,CAAC;AAC9C,MAAM,IAAI,GAAG,MAAM,CAAC;AACpB,MAAM,UAAU,GAAG,QAAQ,CAAC;AAE5B,SAAS,YAAY,CAAC,MAAe;IACnC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;IACjE,OAAO;QACL,EAAE,EAAE,IAAI;QACR,MAAM,EAAE,GAAG;QACX,UAAU,EAAE,IAAI;QAChB,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI;QACtB,OAAO,EAAE;YACP,GAAG,EAAE,CAAC,IAAY,EAAE,EAAE,CACpB,IAAI,CAAC,WAAW,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI;SACpE;KACqB,CAAC;AAC3B,CAAC;AAED,QAAQ,CAAC,qDAAqD,EAAE,GAAG,EAAE;IACnE,IAAI,aAA2B,CAAC;IAEhC,UAAU,CAAC,GAAG,EAAE;QACd,aAAa,GAAG,UAAU,CAAC,KAAK,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,CAAC,KAAK,GAAG,aAAa,CAAC;QACjC,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sEAAsE,EAAE,KAAK,IAAI,EAAE;QACpF,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,IAAiB,EAAE,EAAE;YAChE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAiC,CAAC;YACtD,qEAAqE;YACrE,2DAA2D;YAC3D,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;YACvD,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,OAAO,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QACH,UAAU,CAAC,KAAK,GAAG,SAAoC,CAAC;QAExD,MAAM,OAAO,GAAgB,EAAE,GAAG,oBAAoB,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;QACrE,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;QAElF,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,IAAiB,EAAE,EAAE;YAChE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAiC,CAAC;YACtD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;YACvD,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,OAAO,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QACH,UAAU,CAAC,KAAK,GAAG,SAAoC,CAAC;QAExD,MAAM,OAAO,GAAgB,EAAE,GAAG,oBAAoB,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC;QACxE,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;QAElF,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=registry-disconnect-retains-deps.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-disconnect-retains-deps.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/registry-disconnect-retains-deps.spec.ts"],"names":[],"mappings":""}