@mcpmesh/sdk 2.3.0 → 2.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 (166) hide show
  1. package/dist/__tests__/agent-single-instance.spec.d.ts +2 -0
  2. package/dist/__tests__/agent-single-instance.spec.d.ts.map +1 -0
  3. package/dist/__tests__/agent-single-instance.spec.js +80 -0
  4. package/dist/__tests__/agent-single-instance.spec.js.map +1 -0
  5. package/dist/__tests__/event-loop-resilience.spec.d.ts +2 -0
  6. package/dist/__tests__/event-loop-resilience.spec.d.ts.map +1 -0
  7. package/dist/__tests__/event-loop-resilience.spec.js +321 -0
  8. package/dist/__tests__/event-loop-resilience.spec.js.map +1 -0
  9. package/dist/__tests__/llm-agent-model-params.test.js +83 -0
  10. package/dist/__tests__/llm-agent-model-params.test.js.map +1 -1
  11. package/dist/__tests__/llm-max-iterations.test.d.ts +20 -0
  12. package/dist/__tests__/llm-max-iterations.test.d.ts.map +1 -0
  13. package/dist/__tests__/llm-max-iterations.test.js +252 -0
  14. package/dist/__tests__/llm-max-iterations.test.js.map +1 -0
  15. package/dist/__tests__/llm-mesh-error-mapping.test.d.ts +16 -0
  16. package/dist/__tests__/llm-mesh-error-mapping.test.d.ts.map +1 -0
  17. package/dist/__tests__/llm-mesh-error-mapping.test.js +135 -0
  18. package/dist/__tests__/llm-mesh-error-mapping.test.js.map +1 -0
  19. package/dist/__tests__/llm-provider-multistep.test.d.ts +20 -0
  20. package/dist/__tests__/llm-provider-multistep.test.d.ts.map +1 -0
  21. package/dist/__tests__/llm-provider-multistep.test.js +138 -0
  22. package/dist/__tests__/llm-provider-multistep.test.js.map +1 -0
  23. package/dist/__tests__/llm-provider-output-mode.test.d.ts +21 -0
  24. package/dist/__tests__/llm-provider-output-mode.test.d.ts.map +1 -0
  25. package/dist/__tests__/llm-provider-output-mode.test.js +116 -0
  26. package/dist/__tests__/llm-provider-output-mode.test.js.map +1 -0
  27. package/dist/__tests__/llm-provider-stopwhen.test.d.ts +22 -0
  28. package/dist/__tests__/llm-provider-stopwhen.test.d.ts.map +1 -0
  29. package/dist/__tests__/llm-provider-stopwhen.test.js +127 -0
  30. package/dist/__tests__/llm-provider-stopwhen.test.js.map +1 -0
  31. package/dist/__tests__/llm-provider-system-synthesis.test.d.ts +20 -0
  32. package/dist/__tests__/llm-provider-system-synthesis.test.d.ts.map +1 -0
  33. package/dist/__tests__/llm-provider-system-synthesis.test.js +168 -0
  34. package/dist/__tests__/llm-provider-system-synthesis.test.js.map +1 -0
  35. package/dist/__tests__/llm-provider-vertex-settings.test.d.ts +18 -0
  36. package/dist/__tests__/llm-provider-vertex-settings.test.d.ts.map +1 -0
  37. package/dist/__tests__/llm-provider-vertex-settings.test.js +128 -0
  38. package/dist/__tests__/llm-provider-vertex-settings.test.js.map +1 -0
  39. package/dist/__tests__/llm-response-model.test.d.ts +10 -0
  40. package/dist/__tests__/llm-response-model.test.d.ts.map +1 -0
  41. package/dist/__tests__/llm-response-model.test.js +92 -0
  42. package/dist/__tests__/llm-response-model.test.js.map +1 -0
  43. package/dist/__tests__/port-conflict-fallback.spec.d.ts +2 -0
  44. package/dist/__tests__/port-conflict-fallback.spec.d.ts.map +1 -0
  45. package/dist/__tests__/port-conflict-fallback.spec.js +123 -0
  46. package/dist/__tests__/port-conflict-fallback.spec.js.map +1 -0
  47. package/dist/__tests__/port-probe-errors.spec.d.ts +2 -0
  48. package/dist/__tests__/port-probe-errors.spec.d.ts.map +1 -0
  49. package/dist/__tests__/port-probe-errors.spec.js +100 -0
  50. package/dist/__tests__/port-probe-errors.spec.js.map +1 -0
  51. package/dist/__tests__/provider-handler-registry.test.d.ts +0 -1
  52. package/dist/__tests__/provider-handler-registry.test.d.ts.map +1 -1
  53. package/dist/__tests__/provider-handler-registry.test.js +23 -1
  54. package/dist/__tests__/provider-handler-registry.test.js.map +1 -1
  55. package/dist/__tests__/proxy-sse-no-data.test.d.ts +13 -0
  56. package/dist/__tests__/proxy-sse-no-data.test.d.ts.map +1 -0
  57. package/dist/__tests__/proxy-sse-no-data.test.js +147 -0
  58. package/dist/__tests__/proxy-sse-no-data.test.js.map +1 -0
  59. package/dist/__tests__/proxy-stream.test.js +26 -0
  60. package/dist/__tests__/proxy-stream.test.js.map +1 -1
  61. package/dist/__tests__/proxy-timeout-guard.test.d.ts +12 -0
  62. package/dist/__tests__/proxy-timeout-guard.test.d.ts.map +1 -0
  63. package/dist/__tests__/proxy-timeout-guard.test.js +85 -0
  64. package/dist/__tests__/proxy-timeout-guard.test.js.map +1 -0
  65. package/dist/__tests__/proxy-timer-leak.test.d.ts +16 -0
  66. package/dist/__tests__/proxy-timer-leak.test.d.ts.map +1 -0
  67. package/dist/__tests__/proxy-timer-leak.test.js +97 -0
  68. package/dist/__tests__/proxy-timer-leak.test.js.map +1 -0
  69. package/dist/__tests__/proxy-tool-error.test.d.ts +13 -0
  70. package/dist/__tests__/proxy-tool-error.test.d.ts.map +1 -0
  71. package/dist/__tests__/proxy-tool-error.test.js +313 -0
  72. package/dist/__tests__/proxy-tool-error.test.js.map +1 -0
  73. package/dist/__tests__/registry-disconnect-retains-deps.spec.d.ts +2 -0
  74. package/dist/__tests__/registry-disconnect-retains-deps.spec.d.ts.map +1 -0
  75. package/dist/__tests__/registry-disconnect-retains-deps.spec.js +101 -0
  76. package/dist/__tests__/registry-disconnect-retains-deps.spec.js.map +1 -0
  77. package/dist/__tests__/response-parser.test.js +29 -0
  78. package/dist/__tests__/response-parser.test.js.map +1 -1
  79. package/dist/__tests__/route.test.js +21 -1
  80. package/dist/__tests__/route.test.js.map +1 -1
  81. package/dist/__tests__/settle-window.spec.d.ts +2 -0
  82. package/dist/__tests__/settle-window.spec.d.ts.map +1 -0
  83. package/dist/__tests__/settle-window.spec.js +324 -0
  84. package/dist/__tests__/settle-window.spec.js.map +1 -0
  85. package/dist/__tests__/sse.test.js +12 -0
  86. package/dist/__tests__/sse.test.js.map +1 -1
  87. package/dist/__tests__/stop-dispatchers.spec.d.ts +2 -0
  88. package/dist/__tests__/stop-dispatchers.spec.d.ts.map +1 -0
  89. package/dist/__tests__/stop-dispatchers.spec.js +227 -0
  90. package/dist/__tests__/stop-dispatchers.spec.js.map +1 -0
  91. package/dist/agent.d.ts +65 -5
  92. package/dist/agent.d.ts.map +1 -1
  93. package/dist/agent.js +317 -78
  94. package/dist/agent.js.map +1 -1
  95. package/dist/api-runtime.d.ts +33 -3
  96. package/dist/api-runtime.d.ts.map +1 -1
  97. package/dist/api-runtime.js +133 -33
  98. package/dist/api-runtime.js.map +1 -1
  99. package/dist/claim-dispatcher.d.ts +25 -0
  100. package/dist/claim-dispatcher.d.ts.map +1 -1
  101. package/dist/claim-dispatcher.js +59 -1
  102. package/dist/claim-dispatcher.js.map +1 -1
  103. package/dist/config.d.ts +73 -1
  104. package/dist/config.d.ts.map +1 -1
  105. package/dist/config.js +108 -2
  106. package/dist/config.js.map +1 -1
  107. package/dist/debug.d.ts +1 -1
  108. package/dist/debug.d.ts.map +1 -1
  109. package/dist/express.d.ts +33 -0
  110. package/dist/express.d.ts.map +1 -1
  111. package/dist/express.js +157 -32
  112. package/dist/express.js.map +1 -1
  113. package/dist/index.d.ts +1 -1
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +1 -1
  116. package/dist/index.js.map +1 -1
  117. package/dist/llm-agent.d.ts +34 -0
  118. package/dist/llm-agent.d.ts.map +1 -1
  119. package/dist/llm-agent.js +239 -434
  120. package/dist/llm-agent.js.map +1 -1
  121. package/dist/llm-provider.d.ts +51 -4
  122. package/dist/llm-provider.d.ts.map +1 -1
  123. package/dist/llm-provider.js +175 -36
  124. package/dist/llm-provider.js.map +1 -1
  125. package/dist/llm.d.ts +1 -1
  126. package/dist/llm.d.ts.map +1 -1
  127. package/dist/llm.js +8 -5
  128. package/dist/llm.js.map +1 -1
  129. package/dist/provider-handlers/gemini-handler.d.ts.map +1 -1
  130. package/dist/provider-handlers/gemini-handler.js +8 -14
  131. package/dist/provider-handlers/gemini-handler.js.map +1 -1
  132. package/dist/provider-handlers/openai-handler.d.ts.map +1 -1
  133. package/dist/provider-handlers/openai-handler.js +2 -15
  134. package/dist/provider-handlers/openai-handler.js.map +1 -1
  135. package/dist/provider-handlers/provider-handler-registry.d.ts +10 -1
  136. package/dist/provider-handlers/provider-handler-registry.d.ts.map +1 -1
  137. package/dist/provider-handlers/provider-handler-registry.js +4 -1
  138. package/dist/provider-handlers/provider-handler-registry.js.map +1 -1
  139. package/dist/provider-handlers/provider-handler.d.ts +12 -0
  140. package/dist/provider-handlers/provider-handler.d.ts.map +1 -1
  141. package/dist/provider-handlers/provider-handler.js +24 -0
  142. package/dist/provider-handlers/provider-handler.js.map +1 -1
  143. package/dist/proxy.d.ts.map +1 -1
  144. package/dist/proxy.js +360 -287
  145. package/dist/proxy.js.map +1 -1
  146. package/dist/response-parser.d.ts +10 -0
  147. package/dist/response-parser.d.ts.map +1 -1
  148. package/dist/response-parser.js +55 -0
  149. package/dist/response-parser.js.map +1 -1
  150. package/dist/route.d.ts.map +1 -1
  151. package/dist/route.js +38 -0
  152. package/dist/route.js.map +1 -1
  153. package/dist/settle.d.ts +129 -0
  154. package/dist/settle.d.ts.map +1 -0
  155. package/dist/settle.js +284 -0
  156. package/dist/settle.js.map +1 -0
  157. package/dist/sse.d.ts.map +1 -1
  158. package/dist/sse.js +5 -2
  159. package/dist/sse.js.map +1 -1
  160. package/dist/tracing.d.ts +13 -0
  161. package/dist/tracing.d.ts.map +1 -1
  162. package/dist/tracing.js +40 -0
  163. package/dist/tracing.js.map +1 -1
  164. package/dist/types.d.ts +10 -2
  165. package/dist/types.d.ts.map +1 -1
  166. package/package.json +2 -2
@@ -0,0 +1,116 @@
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
+ stepCountIs: (n) => ({ steps }) => steps.length === n,
29
+ }));
30
+ // Keep tracing inert/deterministic (publishTraceSpan is best-effort anyway).
31
+ vi.mock("../tracing.js", () => ({
32
+ generateTraceId: () => "trace-mock",
33
+ generateSpanId: () => "span-mock",
34
+ publishTraceSpan: vi.fn(async () => { }),
35
+ matchesPropagateHeader: () => false,
36
+ }));
37
+ import { llmProvider } from "../llm-provider.js";
38
+ const SCHEMA = {
39
+ type: "object",
40
+ properties: { answer: { type: "string" } },
41
+ required: ["answer"],
42
+ };
43
+ function makeProvider() {
44
+ // OpenAI vendor: auto-selects "strict" when a schema is present.
45
+ return llmProvider({ model: "openai/gpt-4o", capability: "llm" });
46
+ }
47
+ function baseRequest(modelParams) {
48
+ return {
49
+ request: {
50
+ messages: [
51
+ { role: "system", content: "You are helpful." },
52
+ { role: "user", content: "hi" },
53
+ ],
54
+ model_params: {
55
+ output_schema: SCHEMA,
56
+ output_type_name: "Answer",
57
+ ...modelParams,
58
+ },
59
+ },
60
+ };
61
+ }
62
+ describe("provider output_mode override (#1112)", () => {
63
+ beforeEach(() => {
64
+ generateObjectMock.mockReset();
65
+ generateTextMock.mockReset();
66
+ generateObjectMock.mockResolvedValue({
67
+ object: { answer: "ok" },
68
+ usage: { inputTokens: 1, outputTokens: 1 },
69
+ finishReason: "stop",
70
+ });
71
+ generateTextMock.mockResolvedValue({
72
+ text: "ok",
73
+ toolCalls: [],
74
+ usage: { inputTokens: 1, outputTokens: 1 },
75
+ finishReason: "stop",
76
+ });
77
+ });
78
+ afterEach(() => {
79
+ vi.restoreAllMocks();
80
+ });
81
+ it("auto-selects strict (generateObject) when no override is present — byte-identical to today", async () => {
82
+ const tool = makeProvider();
83
+ await tool.execute(baseRequest({}));
84
+ expect(generateObjectMock).toHaveBeenCalledTimes(1);
85
+ expect(generateTextMock).not.toHaveBeenCalled();
86
+ });
87
+ it("honors a 'hint' override where it would auto-select strict (uses generateText)", async () => {
88
+ const tool = makeProvider();
89
+ await tool.execute(baseRequest({ output_mode: "hint" }));
90
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
91
+ expect(generateObjectMock).not.toHaveBeenCalled();
92
+ });
93
+ it("honors a 'text' override (uses generateText)", async () => {
94
+ const tool = makeProvider();
95
+ await tool.execute(baseRequest({ output_mode: "text" }));
96
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
97
+ expect(generateObjectMock).not.toHaveBeenCalled();
98
+ });
99
+ it("ignores an invalid override and falls back to auto (strict → generateObject)", async () => {
100
+ const tool = makeProvider();
101
+ await tool.execute(baseRequest({ output_mode: "bogus" }));
102
+ expect(generateObjectMock).toHaveBeenCalledTimes(1);
103
+ expect(generateTextMock).not.toHaveBeenCalled();
104
+ });
105
+ it("strips output_mode from the params reaching the vendor SDK call", async () => {
106
+ const tool = makeProvider();
107
+ await tool.execute(baseRequest({ output_mode: "hint" }));
108
+ // hint → generateText; assert the options object carries no output_mode.
109
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
110
+ const opts = generateTextMock.mock.calls[0][0];
111
+ expect("output_mode" in opts).toBe(false);
112
+ // Nested guard: output_mode must not leak via providerOptions either.
113
+ expect(JSON.stringify(opts)).not.toContain("output_mode");
114
+ });
115
+ });
116
+ //# 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":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAEzE,MAAM,kBAAkB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AACnC,MAAM,gBAAgB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AAEjC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACnB,YAAY,EAAE,CAAC,IAAa,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC;IACvD,cAAc,EAAE,CAAC,IAAa,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC;IAC3D,UAAU,EAAE,CAAC,MAA+B,EAAE,EAAE,CAAC,MAAM;IACvD,IAAI,EAAE,CAAC,MAAe,EAAE,EAAE,CAAC,MAAM;IACjC,WAAW,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAwB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;CACpF,CAAC,CAAC,CAAC;AAEJ,6EAA6E;AAC7E,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,gBAAgB,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,GAAE,CAAC,CAAC;IACvC,sBAAsB,EAAE,GAAG,EAAE,CAAC,KAAK;CACpC,CAAC,CAAC,CAAC;AAEJ,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,MAAM,GAAG;IACb,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAC1C,QAAQ,EAAE,CAAC,QAAQ,CAAC;CACrB,CAAC;AAEF,SAAS,YAAY;IACnB,iEAAiE;IACjE,OAAO,WAAW,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,WAAW,CAAC,WAAoC;IACvD,OAAO;QACL,OAAO,EAAE;YACP,QAAQ,EAAE;gBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,kBAAkB,EAAE;gBAC/C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;aAChC;YACD,YAAY,EAAE;gBACZ,aAAa,EAAE,MAAM;gBACrB,gBAAgB,EAAE,QAAQ;gBAC1B,GAAG,WAAW;aACf;SACF;KACF,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACrD,UAAU,CAAC,GAAG,EAAE;QACd,kBAAkB,CAAC,SAAS,EAAE,CAAC;QAC/B,gBAAgB,CAAC,SAAS,EAAE,CAAC;QAC7B,kBAAkB,CAAC,iBAAiB,CAAC;YACnC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;YACxB,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE;YAC1C,YAAY,EAAE,MAAM;SACrB,CAAC,CAAC;QACH,gBAAgB,CAAC,iBAAiB,CAAC;YACjC,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,EAAE;YACb,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE;YAC1C,YAAY,EAAE,MAAM;SACrB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4FAA4F,EAAE,KAAK,IAAI,EAAE;QAC1G,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAU,CAAC,CAAC;QAE7C,MAAM,CAAC,kBAAkB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACpD,MAAM,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gFAAgF,EAAE,KAAK,IAAI,EAAE;QAC9F,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAU,CAAC,CAAC;QAElE,MAAM,CAAC,gBAAgB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAClD,MAAM,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAU,CAAC,CAAC;QAElE,MAAM,CAAC,gBAAgB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAClD,MAAM,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8EAA8E,EAAE,KAAK,IAAI,EAAE;QAC5F,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAU,CAAC,CAAC;QAEnE,MAAM,CAAC,kBAAkB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACpD,MAAM,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAU,CAAC,CAAC;QAElE,yEAAyE;QACzE,MAAM,CAAC,gBAAgB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAA4B,CAAC;QAC1E,MAAM,CAAC,aAAa,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,sEAAsE;QACtE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Gemini SDK-managed agentic loop wiring — issue #1160.
3
+ *
4
+ * AI SDK v6 removed `maxSteps` from generateText; loop control is now
5
+ * `stopWhen` with a default of `stepCountIs(1)`. The provider previously set
6
+ * `requestOptions.maxSteps`, which landed in the `...settings` rest and was
7
+ * silently ignored — Gemini executed the tool-call step but never did the
8
+ * follow-up generation, returning an empty assistant message.
9
+ *
10
+ * These tests mock `generateText` (keeping the REAL `stepCountIs`/`tool`/
11
+ * `jsonSchema` via importOriginal) and assert the options object that reaches
12
+ * the AI SDK:
13
+ *
14
+ * - Gemini + `_mesh_endpoint` tools: `stopWhen` is set to the result of
15
+ * `stepCountIs(resolvedMaxIterations)`. `stepCountIs(n)` returns a
16
+ * predicate `({ steps }) => steps.length === n`, so we assert the
17
+ * predicate's behavior at the boundary (true at n steps, false at n-1).
18
+ * - The removed `maxSteps` key is NOT present (regression guard).
19
+ * - Non-Gemini vendors (manual provider-managed loop) do NOT set `stopWhen`.
20
+ */
21
+ export {};
22
+ //# sourceMappingURL=llm-provider-stopwhen.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-provider-stopwhen.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/llm-provider-stopwhen.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Gemini SDK-managed agentic loop wiring — issue #1160.
3
+ *
4
+ * AI SDK v6 removed `maxSteps` from generateText; loop control is now
5
+ * `stopWhen` with a default of `stepCountIs(1)`. The provider previously set
6
+ * `requestOptions.maxSteps`, which landed in the `...settings` rest and was
7
+ * silently ignored — Gemini executed the tool-call step but never did the
8
+ * follow-up generation, returning an empty assistant message.
9
+ *
10
+ * These tests mock `generateText` (keeping the REAL `stepCountIs`/`tool`/
11
+ * `jsonSchema` via importOriginal) and assert the options object that reaches
12
+ * the AI SDK:
13
+ *
14
+ * - Gemini + `_mesh_endpoint` tools: `stopWhen` is set to the result of
15
+ * `stepCountIs(resolvedMaxIterations)`. `stepCountIs(n)` returns a
16
+ * predicate `({ steps }) => steps.length === n`, so we assert the
17
+ * predicate's behavior at the boundary (true at n steps, false at n-1).
18
+ * - The removed `maxSteps` key is NOT present (regression guard).
19
+ * - Non-Gemini vendors (manual provider-managed loop) do NOT set `stopWhen`.
20
+ */
21
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
22
+ const generateTextMock = vi.fn();
23
+ const generateObjectMock = vi.fn();
24
+ vi.mock("ai", async (importOriginal) => {
25
+ const actual = await importOriginal();
26
+ return {
27
+ ...actual,
28
+ generateText: (opts) => generateTextMock(opts),
29
+ generateObject: (opts) => generateObjectMock(opts),
30
+ };
31
+ });
32
+ // Keep tracing inert/deterministic (publishTraceSpan is best-effort anyway).
33
+ vi.mock("../tracing.js", async (importOriginal) => {
34
+ const actual = await importOriginal();
35
+ return {
36
+ ...actual,
37
+ generateTraceId: () => "trace-mock",
38
+ generateSpanId: () => "span-mock",
39
+ publishTraceSpan: vi.fn(async () => false),
40
+ matchesPropagateHeader: () => false,
41
+ };
42
+ });
43
+ import { llmProvider } from "../llm-provider.js";
44
+ function meshToolRequest(modelParams) {
45
+ return {
46
+ request: {
47
+ messages: [{ role: "user", content: "What's the weather in Paris?" }],
48
+ tools: [
49
+ {
50
+ type: "function",
51
+ function: {
52
+ name: "get_weather",
53
+ description: "Get the current weather for a city",
54
+ parameters: {
55
+ type: "object",
56
+ properties: { city: { type: "string" } },
57
+ required: ["city"],
58
+ },
59
+ // Mesh-enriched endpoint → provider-managed tool execution
60
+ _mesh_endpoint: "http://weather-agent.local:9100",
61
+ },
62
+ },
63
+ ],
64
+ model_params: { ...modelParams },
65
+ },
66
+ };
67
+ }
68
+ async function stepsAtCount(stopWhen, count) {
69
+ return await stopWhen({ steps: new Array(count).fill({}) });
70
+ }
71
+ describe("Gemini SDK-managed loop wires stopWhen (#1160)", () => {
72
+ beforeEach(() => {
73
+ generateTextMock.mockReset();
74
+ generateObjectMock.mockReset();
75
+ generateTextMock.mockResolvedValue({
76
+ text: "It is sunny in Paris.",
77
+ toolCalls: [],
78
+ usage: { inputTokens: 1, outputTokens: 1 },
79
+ finishReason: "stop",
80
+ });
81
+ // Model creation must not depend on real credentials at request time
82
+ // (generateText is mocked), but keep keys set for construction safety.
83
+ process.env.GOOGLE_GENERATIVE_AI_API_KEY = "test-key";
84
+ process.env.ANTHROPIC_API_KEY = "test-key";
85
+ });
86
+ afterEach(() => {
87
+ delete process.env.GOOGLE_GENERATIVE_AI_API_KEY;
88
+ delete process.env.ANTHROPIC_API_KEY;
89
+ vi.restoreAllMocks();
90
+ });
91
+ it("sets stopWhen=stepCountIs(max_iterations) and never the removed maxSteps", async () => {
92
+ const tool = llmProvider({ model: "gemini/gemini-2.5-flash", capability: "llm" });
93
+ await tool.execute(meshToolRequest({ max_iterations: 25 }));
94
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
95
+ const opts = generateTextMock.mock.calls[0][0];
96
+ // AI SDK v6 removed maxSteps — it must not be passed at all.
97
+ expect("maxSteps" in opts).toBe(false);
98
+ // stopWhen must be the stepCountIs(25) predicate: stops exactly when the
99
+ // step count reaches the resolved cap.
100
+ const stopWhen = opts.stopWhen;
101
+ expect(typeof stopWhen).toBe("function");
102
+ expect(await stepsAtCount(stopWhen, 25)).toBe(true);
103
+ expect(await stepsAtCount(stopWhen, 24)).toBe(false);
104
+ expect(await stepsAtCount(stopWhen, 1)).toBe(false);
105
+ });
106
+ it("defaults stopWhen to stepCountIs(10) when no cap is forwarded", async () => {
107
+ delete process.env.MESH_LLM_MAX_ITERATIONS;
108
+ const tool = llmProvider({ model: "gemini/gemini-2.5-flash", capability: "llm" });
109
+ await tool.execute(meshToolRequest({}));
110
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
111
+ const opts = generateTextMock.mock.calls[0][0];
112
+ expect("maxSteps" in opts).toBe(false);
113
+ const stopWhen = opts.stopWhen;
114
+ expect(typeof stopWhen).toBe("function");
115
+ expect(await stepsAtCount(stopWhen, 10)).toBe(true);
116
+ expect(await stepsAtCount(stopWhen, 9)).toBe(false);
117
+ });
118
+ it("non-Gemini vendors keep the manual loop: no stopWhen, no maxSteps", async () => {
119
+ const tool = llmProvider({ model: "anthropic/claude-sonnet-4-5", capability: "llm" });
120
+ await tool.execute(meshToolRequest({ max_iterations: 25 }));
121
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
122
+ const opts = generateTextMock.mock.calls[0][0];
123
+ expect(opts.stopWhen).toBeUndefined();
124
+ expect("maxSteps" in opts).toBe(false);
125
+ });
126
+ });
127
+ //# sourceMappingURL=llm-provider-stopwhen.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-provider-stopwhen.test.js","sourceRoot":"","sources":["../../src/__tests__/llm-provider-stopwhen.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,168 @@
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
+ stepCountIs: (n) => ({ steps }) => steps.length === n,
28
+ }));
29
+ vi.mock("../tracing.js", () => ({
30
+ generateTraceId: () => "trace-mock",
31
+ generateSpanId: () => "span-mock",
32
+ publishTraceSpan: vi.fn(async () => { }),
33
+ matchesPropagateHeader: () => false,
34
+ }));
35
+ import { llmProvider } from "../llm-provider.js";
36
+ const SCHEMA = {
37
+ type: "object",
38
+ properties: { answer: { type: "string" } },
39
+ required: ["answer"],
40
+ };
41
+ function makeProvider() {
42
+ // OpenAI vendor: auto-selects "strict" when a schema is present.
43
+ return llmProvider({ model: "openai/gpt-4o", capability: "llm" });
44
+ }
45
+ function systemMessages(opts) {
46
+ return opts.messages.filter((m) => m.role === "system");
47
+ }
48
+ beforeEach(() => {
49
+ generateObjectMock.mockReset();
50
+ generateTextMock.mockReset();
51
+ generateObjectMock.mockResolvedValue({
52
+ object: { answer: "ok" },
53
+ usage: { inputTokens: 1, outputTokens: 1 },
54
+ finishReason: "stop",
55
+ });
56
+ generateTextMock.mockResolvedValue({
57
+ text: '{"answer":"ok"}',
58
+ toolCalls: [],
59
+ usage: { inputTokens: 1, outputTokens: 1 },
60
+ finishReason: "stop",
61
+ });
62
+ });
63
+ afterEach(() => {
64
+ vi.restoreAllMocks();
65
+ });
66
+ describe("provider system-message synthesis (#1112 finding 6)", () => {
67
+ it("synthesizes a system message with schema/JSON hint instructions when none exists (hint + schema)", async () => {
68
+ const tool = makeProvider();
69
+ await tool.execute({
70
+ request: {
71
+ // NO system message — only a user turn.
72
+ messages: [{ role: "user", content: "hi" }],
73
+ model_params: {
74
+ output_schema: SCHEMA,
75
+ output_type_name: "Answer",
76
+ output_mode: "hint",
77
+ },
78
+ },
79
+ });
80
+ // hint → generateText.
81
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
82
+ const opts = generateTextMock.mock.calls[0][0];
83
+ const systems = systemMessages(opts);
84
+ // A system message was synthesized and prepended.
85
+ expect(systems).toHaveLength(1);
86
+ expect(opts.messages[0].role).toBe("system");
87
+ const content = systems[0].content;
88
+ // Carries the hint-mode JSON instructions + schema field.
89
+ expect(content).toContain("OUTPUT FORMAT:");
90
+ expect(content).toContain("ONLY valid JSON");
91
+ expect(content).toContain("answer");
92
+ });
93
+ it("augments an EXISTING system message (hint + schema) without double-adding", async () => {
94
+ const tool = makeProvider();
95
+ await tool.execute({
96
+ request: {
97
+ messages: [
98
+ { role: "system", content: "You are helpful." },
99
+ { role: "user", content: "hi" },
100
+ ],
101
+ model_params: {
102
+ output_schema: SCHEMA,
103
+ output_type_name: "Answer",
104
+ output_mode: "hint",
105
+ },
106
+ },
107
+ });
108
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
109
+ const opts = generateTextMock.mock.calls[0][0];
110
+ const systems = systemMessages(opts);
111
+ // Exactly one system message — the original, augmented (not double-added).
112
+ expect(systems).toHaveLength(1);
113
+ const content = systems[0].content;
114
+ expect(content).toContain("You are helpful.");
115
+ expect(content).toContain("OUTPUT FORMAT:");
116
+ expect(content).toContain("answer");
117
+ });
118
+ it("does NOT synthesize for strict mode with no system message (native response_format backstop)", async () => {
119
+ const tool = makeProvider();
120
+ await tool.execute({
121
+ request: {
122
+ messages: [{ role: "user", content: "hi" }],
123
+ model_params: {
124
+ output_schema: SCHEMA,
125
+ output_type_name: "Answer",
126
+ // No override → OpenAI auto-selects strict (no tools, has schema).
127
+ },
128
+ },
129
+ });
130
+ // strict + schema + no tools → generateObject (structured output intact).
131
+ expect(generateObjectMock).toHaveBeenCalledTimes(1);
132
+ expect(generateTextMock).not.toHaveBeenCalled();
133
+ const opts = generateObjectMock.mock.calls[0][0];
134
+ // No system message was synthesized — strict relies on response_format.
135
+ expect(systemMessages(opts)).toHaveLength(0);
136
+ });
137
+ it("does NOT synthesize for text mode with no system message", async () => {
138
+ const tool = makeProvider();
139
+ await tool.execute({
140
+ request: {
141
+ messages: [{ role: "user", content: "hi" }],
142
+ model_params: {
143
+ output_schema: SCHEMA,
144
+ output_type_name: "Answer",
145
+ output_mode: "text",
146
+ },
147
+ },
148
+ });
149
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
150
+ const opts = generateTextMock.mock.calls[0][0];
151
+ expect(systemMessages(opts)).toHaveLength(0);
152
+ });
153
+ it("does NOT synthesize when there is no schema (hint mode, no system message)", async () => {
154
+ const tool = makeProvider();
155
+ await tool.execute({
156
+ request: {
157
+ messages: [{ role: "user", content: "hi" }],
158
+ model_params: {
159
+ output_mode: "hint",
160
+ },
161
+ },
162
+ });
163
+ expect(generateTextMock).toHaveBeenCalledTimes(1);
164
+ const opts = generateTextMock.mock.calls[0][0];
165
+ expect(systemMessages(opts)).toHaveLength(0);
166
+ });
167
+ });
168
+ //# 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":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAEzE,MAAM,kBAAkB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AACnC,MAAM,gBAAgB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AAEjC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACnB,YAAY,EAAE,CAAC,IAAa,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC;IACvD,cAAc,EAAE,CAAC,IAAa,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC;IAC3D,UAAU,EAAE,CAAC,MAA+B,EAAE,EAAE,CAAC,MAAM;IACvD,IAAI,EAAE,CAAC,MAAe,EAAE,EAAE,CAAC,MAAM;IACjC,WAAW,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAwB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;CACpF,CAAC,CAAC,CAAC;AAEJ,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,gBAAgB,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,GAAE,CAAC,CAAC;IACvC,sBAAsB,EAAE,GAAG,EAAE,CAAC,KAAK;CACpC,CAAC,CAAC,CAAC;AAEJ,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,MAAM,GAAG;IACb,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAC1C,QAAQ,EAAE,CAAC,QAAQ,CAAC;CACrB,CAAC;AAEF,SAAS,YAAY;IACnB,iEAAiE;IACjE,OAAO,WAAW,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;AACpE,CAAC;AAOD,SAAS,cAAc,CAAC,IAA6B;IACnD,OAAQ,IAAI,CAAC,QAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;AACrE,CAAC;AAED,UAAU,CAAC,GAAG,EAAE;IACd,kBAAkB,CAAC,SAAS,EAAE,CAAC;IAC/B,gBAAgB,CAAC,SAAS,EAAE,CAAC;IAC7B,kBAAkB,CAAC,iBAAiB,CAAC;QACnC,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QACxB,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE;QAC1C,YAAY,EAAE,MAAM;KACrB,CAAC,CAAC;IACH,gBAAgB,CAAC,iBAAiB,CAAC;QACjC,IAAI,EAAE,iBAAiB;QACvB,SAAS,EAAE,EAAE;QACb,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE;QAC1C,YAAY,EAAE,MAAM;KACrB,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,EAAE,CAAC,eAAe,EAAE,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qDAAqD,EAAE,GAAG,EAAE;IACnE,EAAE,CAAC,kGAAkG,EAAE,KAAK,IAAI,EAAE;QAChH,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC;YACjB,OAAO,EAAE;gBACP,wCAAwC;gBACxC,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;gBAC3C,YAAY,EAAE;oBACZ,aAAa,EAAE,MAAM;oBACrB,gBAAgB,EAAE,QAAQ;oBAC1B,WAAW,EAAE,MAAM;iBACpB;aACF;SACO,CAAC,CAAC;QAEZ,uBAAuB;QACvB,MAAM,CAAC,gBAAgB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAA4B,CAAC;QAC1E,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QAErC,kDAAkD;QAClD,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAE,IAAI,CAAC,QAAkB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAExD,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAiB,CAAC;QAC7C,0DAA0D;QAC1D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAC5C,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAC7C,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;QACzF,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC;YACjB,OAAO,EAAE;gBACP,QAAQ,EAAE;oBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,kBAAkB,EAAE;oBAC/C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;iBAChC;gBACD,YAAY,EAAE;oBACZ,aAAa,EAAE,MAAM;oBACrB,gBAAgB,EAAE,QAAQ;oBAC1B,WAAW,EAAE,MAAM;iBACpB;aACF;SACO,CAAC,CAAC;QAEZ,MAAM,CAAC,gBAAgB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAA4B,CAAC;QAC1E,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QAErC,2EAA2E;QAC3E,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAiB,CAAC;QAC7C,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;QAC9C,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAC5C,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8FAA8F,EAAE,KAAK,IAAI,EAAE;QAC5G,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC;YACjB,OAAO,EAAE;gBACP,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;gBAC3C,YAAY,EAAE;oBACZ,aAAa,EAAE,MAAM;oBACrB,gBAAgB,EAAE,QAAQ;oBAC1B,mEAAmE;iBACpE;aACF;SACO,CAAC,CAAC;QAEZ,0EAA0E;QAC1E,MAAM,CAAC,kBAAkB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACpD,MAAM,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAEhD,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAA4B,CAAC;QAC5E,wEAAwE;QACxE,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC;YACjB,OAAO,EAAE;gBACP,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;gBAC3C,YAAY,EAAE;oBACZ,aAAa,EAAE,MAAM;oBACrB,gBAAgB,EAAE,QAAQ;oBAC1B,WAAW,EAAE,MAAM;iBACpB;aACF;SACO,CAAC,CAAC;QAEZ,MAAM,CAAC,gBAAgB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAA4B,CAAC;QAC1E,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4EAA4E,EAAE,KAAK,IAAI,EAAE;QAC1F,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,OAAO,CAAC;YACjB,OAAO,EAAE;gBACP,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;gBAC3C,YAAY,EAAE;oBACZ,WAAW,EAAE,MAAM;iBACpB;aACF;SACO,CAAC,CAAC;QAEZ,MAAM,CAAC,gBAAgB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAA4B,CAAC;QAC1E,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Vertex AI provider settings resolution — issue #1181.
3
+ *
4
+ * The mesh contract (#834) is GOOGLE_CLOUD_PROJECT / GOOGLE_CLOUD_LOCATION
5
+ * (honored by the Python runtime). @ai-sdk/google-vertex's default `vertex`
6
+ * instance only reads GOOGLE_VERTEX_PROJECT / GOOGLE_VERTEX_LOCATION at call
7
+ * time, so identically-configured environments worked on Python and threw
8
+ * `AI_LoadSettingError` on TS.
9
+ *
10
+ * loadProvider("vertex_ai") must now ALWAYS construct the provider via
11
+ * createVertex() with explicitly resolved settings:
12
+ * - mesh-standard names map through,
13
+ * - the vendor-specific GOOGLE_VERTEX_* name wins when both are set,
14
+ * - settings that resolve to nothing are OMITTED (the SDK surfaces its own
15
+ * clear error), and the debug warn mentions BOTH accepted names.
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=llm-provider-vertex-settings.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-provider-vertex-settings.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/llm-provider-vertex-settings.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Vertex AI provider settings resolution — issue #1181.
3
+ *
4
+ * The mesh contract (#834) is GOOGLE_CLOUD_PROJECT / GOOGLE_CLOUD_LOCATION
5
+ * (honored by the Python runtime). @ai-sdk/google-vertex's default `vertex`
6
+ * instance only reads GOOGLE_VERTEX_PROJECT / GOOGLE_VERTEX_LOCATION at call
7
+ * time, so identically-configured environments worked on Python and threw
8
+ * `AI_LoadSettingError` on TS.
9
+ *
10
+ * loadProvider("vertex_ai") must now ALWAYS construct the provider via
11
+ * createVertex() with explicitly resolved settings:
12
+ * - mesh-standard names map through,
13
+ * - the vendor-specific GOOGLE_VERTEX_* name wins when both are set,
14
+ * - settings that resolve to nothing are OMITTED (the SDK surfaces its own
15
+ * clear error), and the debug warn mentions BOTH accepted names.
16
+ */
17
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
18
+ const createVertexMock = vi.fn((options) => {
19
+ // Mimic the real factory: returns a callable provider that resolves models.
20
+ return (modelId) => ({ modelId, options });
21
+ });
22
+ vi.mock("@ai-sdk/google-vertex", () => ({
23
+ createVertex: (options) => createVertexMock(options),
24
+ // The default instance reads GOOGLE_VERTEX_* env vars at call time and must
25
+ // never be used by the mesh provider path (#1181 regression guard).
26
+ vertex: () => {
27
+ throw new Error("default `vertex` instance must not be used");
28
+ },
29
+ }));
30
+ import { loadProvider, resolveVertexSettings } from "../llm-provider.js";
31
+ const ENV_KEYS = [
32
+ "GOOGLE_VERTEX_PROJECT",
33
+ "GOOGLE_VERTEX_LOCATION",
34
+ "GOOGLE_CLOUD_PROJECT",
35
+ "GOOGLE_CLOUD_LOCATION",
36
+ "MCP_MESH_DEBUG_MODE",
37
+ "MCP_MESH_LOG_LEVEL",
38
+ ];
39
+ describe("vertex_ai provider settings resolution (#1181)", () => {
40
+ let savedEnv;
41
+ beforeEach(() => {
42
+ createVertexMock.mockClear();
43
+ // Capture-and-restore: save current values, clear for a clean slate.
44
+ savedEnv = {};
45
+ for (const key of ENV_KEYS) {
46
+ savedEnv[key] = process.env[key];
47
+ delete process.env[key];
48
+ }
49
+ });
50
+ afterEach(() => {
51
+ for (const key of ENV_KEYS) {
52
+ if (savedEnv[key] === undefined) {
53
+ delete process.env[key];
54
+ }
55
+ else {
56
+ process.env[key] = savedEnv[key];
57
+ }
58
+ }
59
+ vi.restoreAllMocks();
60
+ });
61
+ it("maps mesh-standard GOOGLE_CLOUD_PROJECT/GOOGLE_CLOUD_LOCATION through to createVertex", async () => {
62
+ process.env.GOOGLE_CLOUD_PROJECT = "mesh-project";
63
+ process.env.GOOGLE_CLOUD_LOCATION = "us-central1";
64
+ const provider = await loadProvider("vertex_ai");
65
+ expect(createVertexMock).toHaveBeenCalledTimes(1);
66
+ expect(createVertexMock).toHaveBeenCalledWith({
67
+ project: "mesh-project",
68
+ location: "us-central1",
69
+ });
70
+ // The swap must be seamless: loadProvider returns the factory's provider,
71
+ // which resolves model IDs exactly like the previous default instance.
72
+ expect(typeof provider).toBe("function");
73
+ const model = provider("gemini-2.5-flash");
74
+ expect(model.modelId).toBe("gemini-2.5-flash");
75
+ expect(model.options).toEqual({
76
+ project: "mesh-project",
77
+ location: "us-central1",
78
+ });
79
+ });
80
+ it("prefers vendor-specific GOOGLE_VERTEX_* when both names are set", async () => {
81
+ process.env.GOOGLE_VERTEX_PROJECT = "vertex-project";
82
+ process.env.GOOGLE_VERTEX_LOCATION = "europe-west4";
83
+ process.env.GOOGLE_CLOUD_PROJECT = "cloud-project";
84
+ process.env.GOOGLE_CLOUD_LOCATION = "us-central1";
85
+ await loadProvider("vertex_ai");
86
+ expect(createVertexMock).toHaveBeenCalledWith({
87
+ project: "vertex-project",
88
+ location: "europe-west4",
89
+ });
90
+ });
91
+ it("resolves each setting independently (mixed naming)", async () => {
92
+ process.env.GOOGLE_VERTEX_PROJECT = "vertex-project";
93
+ process.env.GOOGLE_CLOUD_LOCATION = "us-east1";
94
+ await loadProvider("vertex_ai");
95
+ expect(createVertexMock).toHaveBeenCalledWith({
96
+ project: "vertex-project",
97
+ location: "us-east1",
98
+ });
99
+ });
100
+ it("omits unresolved settings and warns mentioning BOTH accepted names", async () => {
101
+ process.env.MCP_MESH_DEBUG_MODE = "true";
102
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => { });
103
+ await loadProvider("vertex_ai");
104
+ // Factory still called (single code path), but without project/location
105
+ // keys — the SDK throws its own clear LoadSettingError at call time.
106
+ expect(createVertexMock).toHaveBeenCalledTimes(1);
107
+ const opts = createVertexMock.mock.calls[0][0];
108
+ expect(opts).not.toHaveProperty("project");
109
+ expect(opts).not.toHaveProperty("location");
110
+ const logged = logSpy.mock.calls.map((call) => call.join(" ")).join("\n");
111
+ expect(logged).toContain("GOOGLE_VERTEX_PROJECT");
112
+ expect(logged).toContain("GOOGLE_CLOUD_PROJECT");
113
+ expect(logged).toContain("GOOGLE_VERTEX_LOCATION");
114
+ expect(logged).toContain("GOOGLE_CLOUD_LOCATION");
115
+ });
116
+ it("resolveVertexSettings treats empty string as unset (falls through, never blank)", () => {
117
+ process.env.GOOGLE_VERTEX_PROJECT = "";
118
+ process.env.GOOGLE_CLOUD_PROJECT = "cloud-project";
119
+ process.env.GOOGLE_VERTEX_LOCATION = "";
120
+ const settings = resolveVertexSettings();
121
+ // An empty vendor-specific name must not shadow-and-blank the setting: it
122
+ // falls through to the mesh-standard name, and a setting with no non-empty
123
+ // source is omitted entirely.
124
+ expect(settings).toEqual({ project: "cloud-project" });
125
+ expect(settings).not.toHaveProperty("location");
126
+ });
127
+ });
128
+ //# sourceMappingURL=llm-provider-vertex-settings.test.js.map