@mcpmesh/sdk 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/llm-provider-sampling-gating.test.d.ts +20 -0
- package/dist/__tests__/llm-provider-sampling-gating.test.d.ts.map +1 -0
- package/dist/__tests__/llm-provider-sampling-gating.test.js +192 -0
- package/dist/__tests__/llm-provider-sampling-gating.test.js.map +1 -0
- package/dist/__tests__/llm-token-telemetry.test.d.ts +17 -0
- package/dist/__tests__/llm-token-telemetry.test.d.ts.map +1 -0
- package/dist/__tests__/llm-token-telemetry.test.js +199 -0
- package/dist/__tests__/llm-token-telemetry.test.js.map +1 -0
- package/dist/__tests__/unwired-slot-warn.spec.d.ts +2 -0
- package/dist/__tests__/unwired-slot-warn.spec.d.ts.map +1 -0
- package/dist/__tests__/unwired-slot-warn.spec.js +167 -0
- package/dist/__tests__/unwired-slot-warn.spec.js.map +1 -0
- package/dist/agent.d.ts +19 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +55 -16
- package/dist/agent.js.map +1 -1
- package/dist/jobs.d.ts +8 -4
- package/dist/jobs.d.ts.map +1 -1
- package/dist/jobs.js +8 -4
- package/dist/jobs.js.map +1 -1
- package/dist/llm-provider.d.ts +31 -0
- package/dist/llm-provider.d.ts.map +1 -1
- package/dist/llm-provider.js +85 -5
- package/dist/llm-provider.js.map +1 -1
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +34 -2
- package/dist/llm.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mesh-level OpenAI sampling-param gating (cross-runtime parity with Java
|
|
3
|
+
* `OpenAiHandler.restrictsSamplingParams` and Python `restricts_sampling_params`).
|
|
4
|
+
*
|
|
5
|
+
* OpenAI o-series (o1/o3/o4) and gpt-5 (except gpt-5-chat) reject non-default
|
|
6
|
+
* `temperature`/`top_p` with HTTP 400. This is version-independent HARDENING:
|
|
7
|
+
* mesh omits temperature/topP for restricted models BEFORE handing off to the
|
|
8
|
+
* SDK (rather than silently relying on @ai-sdk/openai to strip them) and logs
|
|
9
|
+
* its own warning. maxOutputTokens is intentionally NOT touched.
|
|
10
|
+
*
|
|
11
|
+
* Two layers:
|
|
12
|
+
* - the `restrictsSamplingParams` classifier truth table, and
|
|
13
|
+
* - behavioral: the options reaching the mocked vendor call (generateText /
|
|
14
|
+
* generateObject) carry NO temperature/topP for a restricted model, while a
|
|
15
|
+
* non-restricted model (gpt-4o) keeps them.
|
|
16
|
+
*
|
|
17
|
+
* The Vercel `ai` module is mocked — no real LLM is invoked.
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=llm-provider-sampling-gating.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-provider-sampling-gating.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/llm-provider-sampling-gating.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mesh-level OpenAI sampling-param gating (cross-runtime parity with Java
|
|
3
|
+
* `OpenAiHandler.restrictsSamplingParams` and Python `restricts_sampling_params`).
|
|
4
|
+
*
|
|
5
|
+
* OpenAI o-series (o1/o3/o4) and gpt-5 (except gpt-5-chat) reject non-default
|
|
6
|
+
* `temperature`/`top_p` with HTTP 400. This is version-independent HARDENING:
|
|
7
|
+
* mesh omits temperature/topP for restricted models BEFORE handing off to the
|
|
8
|
+
* SDK (rather than silently relying on @ai-sdk/openai to strip them) and logs
|
|
9
|
+
* its own warning. maxOutputTokens is intentionally NOT touched.
|
|
10
|
+
*
|
|
11
|
+
* Two layers:
|
|
12
|
+
* - the `restrictsSamplingParams` classifier truth table, and
|
|
13
|
+
* - behavioral: the options reaching the mocked vendor call (generateText /
|
|
14
|
+
* generateObject) carry NO temperature/topP for a restricted model, while a
|
|
15
|
+
* non-restricted model (gpt-4o) keeps them.
|
|
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
|
+
// 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, restrictsSamplingParams } from "../llm-provider.js";
|
|
37
|
+
// ----------------------------------------------------------------------------
|
|
38
|
+
// Classifier truth table
|
|
39
|
+
// ----------------------------------------------------------------------------
|
|
40
|
+
describe("restrictsSamplingParams truth table", () => {
|
|
41
|
+
it.each([
|
|
42
|
+
"o1",
|
|
43
|
+
"o3-mini",
|
|
44
|
+
"o4-mini",
|
|
45
|
+
"gpt-5",
|
|
46
|
+
"gpt-5-mini",
|
|
47
|
+
"gpt-5-nano",
|
|
48
|
+
"openai/o3-mini",
|
|
49
|
+
"openai/gpt-5",
|
|
50
|
+
"O3-MINI", // case-insensitive
|
|
51
|
+
])("restricts %s", (model) => {
|
|
52
|
+
expect(restrictsSamplingParams(model)).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
it.each([
|
|
55
|
+
"gpt-4o",
|
|
56
|
+
"gpt-4.1",
|
|
57
|
+
"gpt-5-chat-latest",
|
|
58
|
+
"openai/gpt-4o",
|
|
59
|
+
"gemini/gemini-2.0-flash",
|
|
60
|
+
"anthropic/claude-sonnet-4-5",
|
|
61
|
+
"o5", // not an o1/o3/o4 series
|
|
62
|
+
undefined,
|
|
63
|
+
null,
|
|
64
|
+
"",
|
|
65
|
+
])("does not restrict %s", (model) => {
|
|
66
|
+
expect(restrictsSamplingParams(model)).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
// ----------------------------------------------------------------------------
|
|
70
|
+
// Behavioral — the options reaching the vendor SDK call
|
|
71
|
+
// ----------------------------------------------------------------------------
|
|
72
|
+
const SCHEMA = {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: { answer: { type: "string" } },
|
|
75
|
+
required: ["answer"],
|
|
76
|
+
};
|
|
77
|
+
function baseRequest(modelParams, opts = {}) {
|
|
78
|
+
const mp = { ...modelParams };
|
|
79
|
+
if (opts.withSchema) {
|
|
80
|
+
mp.output_schema = SCHEMA;
|
|
81
|
+
mp.output_type_name = "Answer";
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
request: {
|
|
85
|
+
messages: [
|
|
86
|
+
{ role: "system", content: "You are helpful." },
|
|
87
|
+
{ role: "user", content: "hi" },
|
|
88
|
+
],
|
|
89
|
+
model_params: mp,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
describe("OpenAI sampling-param gating — vendor call options", () => {
|
|
94
|
+
let warnSpy;
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
generateObjectMock.mockReset();
|
|
97
|
+
generateTextMock.mockReset();
|
|
98
|
+
generateObjectMock.mockResolvedValue({
|
|
99
|
+
object: { answer: "ok" },
|
|
100
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
101
|
+
finishReason: "stop",
|
|
102
|
+
});
|
|
103
|
+
generateTextMock.mockResolvedValue({
|
|
104
|
+
text: "ok",
|
|
105
|
+
toolCalls: [],
|
|
106
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
107
|
+
finishReason: "stop",
|
|
108
|
+
});
|
|
109
|
+
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
110
|
+
});
|
|
111
|
+
afterEach(() => {
|
|
112
|
+
vi.restoreAllMocks();
|
|
113
|
+
});
|
|
114
|
+
// --- generateText (plain / no schema) path ---
|
|
115
|
+
it("omits temperature/topP for a restricted model (o3-mini) — generateText", async () => {
|
|
116
|
+
const tool = llmProvider({
|
|
117
|
+
model: "openai/o3-mini",
|
|
118
|
+
capability: "llm",
|
|
119
|
+
temperature: 0.7,
|
|
120
|
+
topP: 0.9,
|
|
121
|
+
});
|
|
122
|
+
await tool.execute(baseRequest({}));
|
|
123
|
+
expect(generateTextMock).toHaveBeenCalledTimes(1);
|
|
124
|
+
const opts = generateTextMock.mock.calls[0][0];
|
|
125
|
+
expect("temperature" in opts).toBe(false);
|
|
126
|
+
expect("topP" in opts).toBe(false);
|
|
127
|
+
// mesh surfaces its own warning (one per omitted param).
|
|
128
|
+
expect(warnSpy).toHaveBeenCalledTimes(2);
|
|
129
|
+
});
|
|
130
|
+
it("keeps temperature/topP for a non-restricted model (gpt-4o) — generateText", async () => {
|
|
131
|
+
const tool = llmProvider({
|
|
132
|
+
model: "openai/gpt-4o",
|
|
133
|
+
capability: "llm",
|
|
134
|
+
temperature: 0.7,
|
|
135
|
+
topP: 0.9,
|
|
136
|
+
});
|
|
137
|
+
await tool.execute(baseRequest({}));
|
|
138
|
+
expect(generateTextMock).toHaveBeenCalledTimes(1);
|
|
139
|
+
const opts = generateTextMock.mock.calls[0][0];
|
|
140
|
+
expect(opts.temperature).toBe(0.7);
|
|
141
|
+
expect(opts.topP).toBe(0.9);
|
|
142
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
it("omits temperature/topP supplied via model_params for a restricted model", async () => {
|
|
145
|
+
const tool = llmProvider({ model: "openai/gpt-5", capability: "llm" });
|
|
146
|
+
await tool.execute(baseRequest({ temperature: 0.3, top_p: 0.5 }));
|
|
147
|
+
expect(generateTextMock).toHaveBeenCalledTimes(1);
|
|
148
|
+
const opts = generateTextMock.mock.calls[0][0];
|
|
149
|
+
expect("temperature" in opts).toBe(false);
|
|
150
|
+
expect("topP" in opts).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
it("does not touch maxOutputTokens for a restricted model", async () => {
|
|
153
|
+
const tool = llmProvider({
|
|
154
|
+
model: "openai/o3-mini",
|
|
155
|
+
capability: "llm",
|
|
156
|
+
maxOutputTokens: 4096,
|
|
157
|
+
temperature: 0.7,
|
|
158
|
+
});
|
|
159
|
+
await tool.execute(baseRequest({}));
|
|
160
|
+
const opts = generateTextMock.mock.calls[0][0];
|
|
161
|
+
expect(opts.maxOutputTokens).toBe(4096);
|
|
162
|
+
expect("temperature" in opts).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
// --- generateObject (structured output) path ---
|
|
165
|
+
it("omits temperature/topP for a restricted model (o3-mini) — generateObject", async () => {
|
|
166
|
+
const tool = llmProvider({
|
|
167
|
+
model: "openai/o3-mini",
|
|
168
|
+
capability: "llm",
|
|
169
|
+
temperature: 0.7,
|
|
170
|
+
topP: 0.9,
|
|
171
|
+
});
|
|
172
|
+
await tool.execute(baseRequest({}, { withSchema: true }));
|
|
173
|
+
expect(generateObjectMock).toHaveBeenCalledTimes(1);
|
|
174
|
+
const opts = generateObjectMock.mock.calls[0][0];
|
|
175
|
+
expect("temperature" in opts).toBe(false);
|
|
176
|
+
expect("topP" in opts).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
it("keeps temperature/topP for a non-restricted model (gpt-4o) — generateObject", async () => {
|
|
179
|
+
const tool = llmProvider({
|
|
180
|
+
model: "openai/gpt-4o",
|
|
181
|
+
capability: "llm",
|
|
182
|
+
temperature: 0.7,
|
|
183
|
+
topP: 0.9,
|
|
184
|
+
});
|
|
185
|
+
await tool.execute(baseRequest({}, { withSchema: true }));
|
|
186
|
+
expect(generateObjectMock).toHaveBeenCalledTimes(1);
|
|
187
|
+
const opts = generateObjectMock.mock.calls[0][0];
|
|
188
|
+
expect(opts.temperature).toBe(0.7);
|
|
189
|
+
expect(opts.topP).toBe(0.9);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
//# sourceMappingURL=llm-provider-sampling-gating.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-provider-sampling-gating.test.js","sourceRoot":"","sources":["../../src/__tests__/llm-provider-sampling-gating.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer-span LLM token telemetry — regression test for #1228.
|
|
3
|
+
*
|
|
4
|
+
* The dashboard aggregates per-agent / per-model token usage from `mesh:trace`
|
|
5
|
+
* span fields `llm_input_tokens` / `llm_output_tokens` / `llm_total_tokens` /
|
|
6
|
+
* `llm_model`. Python already stamps the consumer's own execution span (via the
|
|
7
|
+
* `set_llm_metadata` contextvar read in ExecutionTracer); this asserts the TS
|
|
8
|
+
* `@mesh.llm` consumer span reaches parity.
|
|
9
|
+
*
|
|
10
|
+
* The mesh-delegated provider is reached through `callMcpTool`, mocked here to
|
|
11
|
+
* return the canned provider response JSON (incl. `_mesh_usage`). The agentic
|
|
12
|
+
* loop in MeshLlmAgent parses `_mesh_usage`, accumulates totals across
|
|
13
|
+
* iterations, finalizes `_meta`, and the `@mesh.llm` consumer span published in
|
|
14
|
+
* llm.ts stamps the llm_* SpanData fields from it.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=llm-token-telemetry.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-token-telemetry.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/llm-token-telemetry.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer-span LLM token telemetry — regression test for #1228.
|
|
3
|
+
*
|
|
4
|
+
* The dashboard aggregates per-agent / per-model token usage from `mesh:trace`
|
|
5
|
+
* span fields `llm_input_tokens` / `llm_output_tokens` / `llm_total_tokens` /
|
|
6
|
+
* `llm_model`. Python already stamps the consumer's own execution span (via the
|
|
7
|
+
* `set_llm_metadata` contextvar read in ExecutionTracer); this asserts the TS
|
|
8
|
+
* `@mesh.llm` consumer span reaches parity.
|
|
9
|
+
*
|
|
10
|
+
* The mesh-delegated provider is reached through `callMcpTool`, mocked here to
|
|
11
|
+
* return the canned provider response JSON (incl. `_mesh_usage`). The agentic
|
|
12
|
+
* loop in MeshLlmAgent parses `_mesh_usage`, accumulates totals across
|
|
13
|
+
* iterations, finalizes `_meta`, and the `@mesh.llm` consumer span published in
|
|
14
|
+
* llm.ts stamps the llm_* SpanData fields from it.
|
|
15
|
+
*/
|
|
16
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
const callMcpToolMock = vi.hoisted(() => vi.fn());
|
|
19
|
+
const publishTraceSpanMock = vi.hoisted(() => vi.fn(async () => true));
|
|
20
|
+
// Replace only callMcpTool — keep runWithTraceContext etc. real so the
|
|
21
|
+
// consumer span's trace context propagation behaves normally.
|
|
22
|
+
vi.mock("../proxy.js", async (importOriginal) => {
|
|
23
|
+
const actual = await importOriginal();
|
|
24
|
+
return { ...actual, callMcpTool: callMcpToolMock };
|
|
25
|
+
});
|
|
26
|
+
// Capture published spans; keep everything else real.
|
|
27
|
+
vi.mock("../tracing.js", async (importOriginal) => {
|
|
28
|
+
const actual = await importOriginal();
|
|
29
|
+
return { ...actual, publishTraceSpan: publishTraceSpanMock };
|
|
30
|
+
});
|
|
31
|
+
import { llm, LlmToolRegistry } from "../llm.js";
|
|
32
|
+
import { createLlmToolProxy } from "../llm-agent.js";
|
|
33
|
+
const FUNCTION_ID = "summarize";
|
|
34
|
+
const PROVIDER_ENDPOINT = "http://llm-provider.local:9200";
|
|
35
|
+
/** Provider response carrying token usage and (optionally) a tool call. */
|
|
36
|
+
function providerResponse(opts) {
|
|
37
|
+
return JSON.stringify({
|
|
38
|
+
role: "assistant",
|
|
39
|
+
content: opts.content,
|
|
40
|
+
...(opts.toolCall
|
|
41
|
+
? {
|
|
42
|
+
tool_calls: [
|
|
43
|
+
{
|
|
44
|
+
id: opts.toolCall.id,
|
|
45
|
+
type: "function",
|
|
46
|
+
function: { name: opts.toolCall.name, arguments: opts.toolCall.args },
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}
|
|
50
|
+
: {}),
|
|
51
|
+
_mesh_usage: {
|
|
52
|
+
prompt_tokens: opts.inputTokens,
|
|
53
|
+
completion_tokens: opts.outputTokens,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** The consumer's own `@mesh.llm` execution span (function_name === FUNCTION_ID). */
|
|
58
|
+
function consumerSpan() {
|
|
59
|
+
const calls = publishTraceSpanMock.mock.calls;
|
|
60
|
+
return calls.map((c) => c[0]).find((s) => s.functionName === FUNCTION_ID);
|
|
61
|
+
}
|
|
62
|
+
describe("consumer-span LLM token telemetry (#1228)", () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
LlmToolRegistry.reset();
|
|
65
|
+
callMcpToolMock.mockReset();
|
|
66
|
+
publishTraceSpanMock.mockClear();
|
|
67
|
+
});
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
vi.restoreAllMocks();
|
|
70
|
+
});
|
|
71
|
+
it("stamps llm_* token fields on the consumer span for a single-iteration call", async () => {
|
|
72
|
+
const tool = llm({
|
|
73
|
+
name: FUNCTION_ID,
|
|
74
|
+
provider: { capability: "llm-service" },
|
|
75
|
+
parameters: z.object({}),
|
|
76
|
+
execute: async (_args, { llm: llmCallable }) => {
|
|
77
|
+
return llmCallable("Summarize the doc");
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
LlmToolRegistry.getInstance().setResolvedProvider(FUNCTION_ID, {
|
|
81
|
+
endpoint: PROVIDER_ENDPOINT,
|
|
82
|
+
functionName: "process_chat",
|
|
83
|
+
model: "claude-sonnet-4",
|
|
84
|
+
agentId: "provider-agent",
|
|
85
|
+
});
|
|
86
|
+
callMcpToolMock.mockResolvedValue(providerResponse({ content: "A short summary.", inputTokens: 100, outputTokens: 40 }));
|
|
87
|
+
await tool.execute({});
|
|
88
|
+
const span = consumerSpan();
|
|
89
|
+
expect(span).toBeDefined();
|
|
90
|
+
expect(span.llmInputTokens).toBe(100);
|
|
91
|
+
expect(span.llmOutputTokens).toBe(40);
|
|
92
|
+
expect(span.llmTotalTokens).toBe(140);
|
|
93
|
+
expect(span.llmModel).toBe("claude-sonnet-4");
|
|
94
|
+
expect(span.llmProvider).toBe(`mesh:${PROVIDER_ENDPOINT}`);
|
|
95
|
+
});
|
|
96
|
+
it("accumulates token usage across a multi-iteration agentic loop", async () => {
|
|
97
|
+
const tool = llm({
|
|
98
|
+
name: FUNCTION_ID,
|
|
99
|
+
provider: { capability: "llm-service" },
|
|
100
|
+
parameters: z.object({}),
|
|
101
|
+
execute: async (_args, { llm: llmCallable }) => {
|
|
102
|
+
return llmCallable("Look something up then answer");
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
LlmToolRegistry.getInstance().setResolvedProvider(FUNCTION_ID, {
|
|
106
|
+
endpoint: PROVIDER_ENDPOINT,
|
|
107
|
+
functionName: "process_chat",
|
|
108
|
+
model: "claude-sonnet-4",
|
|
109
|
+
agentId: "provider-agent",
|
|
110
|
+
});
|
|
111
|
+
// Register a resolved tool so the loop has a tool to call on iteration 1.
|
|
112
|
+
// The proxy routes through the mocked callMcpTool for its result.
|
|
113
|
+
LlmToolRegistry.getInstance().setResolvedTools(FUNCTION_ID, [
|
|
114
|
+
createLlmToolProxy({
|
|
115
|
+
functionName: "lookup",
|
|
116
|
+
capability: "lookup",
|
|
117
|
+
endpoint: "http://tool-agent.local:9300",
|
|
118
|
+
agentId: "tool-agent",
|
|
119
|
+
}, "Look up a value"),
|
|
120
|
+
]);
|
|
121
|
+
// Iteration 1: provider asks for a tool call (usage 50/20).
|
|
122
|
+
// Tool result returned by the proxy/callMcpTool.
|
|
123
|
+
// Iteration 2: provider returns final text (usage 70/30).
|
|
124
|
+
callMcpToolMock
|
|
125
|
+
.mockResolvedValueOnce(providerResponse({
|
|
126
|
+
content: "",
|
|
127
|
+
inputTokens: 50,
|
|
128
|
+
outputTokens: 20,
|
|
129
|
+
toolCall: { id: "call-1", name: "lookup", args: "{}" },
|
|
130
|
+
}))
|
|
131
|
+
.mockResolvedValueOnce(JSON.stringify({ value: 42 })) // tool result
|
|
132
|
+
.mockResolvedValueOnce(providerResponse({ content: "The answer is 42.", inputTokens: 70, outputTokens: 30 }));
|
|
133
|
+
await tool.execute({});
|
|
134
|
+
const span = consumerSpan();
|
|
135
|
+
expect(span).toBeDefined();
|
|
136
|
+
// Accumulated across both provider calls: 50+70 input, 20+30 output.
|
|
137
|
+
expect(span.llmInputTokens).toBe(120);
|
|
138
|
+
expect(span.llmOutputTokens).toBe(50);
|
|
139
|
+
expect(span.llmTotalTokens).toBe(170);
|
|
140
|
+
expect(span.llmModel).toBe("claude-sonnet-4");
|
|
141
|
+
});
|
|
142
|
+
it("stamps llm_* token fields when the handler throws AFTER an LLM call recorded usage", async () => {
|
|
143
|
+
const boom = new Error("handler failed after LLM call");
|
|
144
|
+
const tool = llm({
|
|
145
|
+
name: FUNCTION_ID,
|
|
146
|
+
provider: { capability: "llm-service" },
|
|
147
|
+
parameters: z.object({}),
|
|
148
|
+
execute: async (_args, { llm: llmCallable }) => {
|
|
149
|
+
// Real LLM call records usage, THEN the handler fails.
|
|
150
|
+
await llmCallable("Summarize the doc");
|
|
151
|
+
throw boom;
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
LlmToolRegistry.getInstance().setResolvedProvider(FUNCTION_ID, {
|
|
155
|
+
endpoint: PROVIDER_ENDPOINT,
|
|
156
|
+
functionName: "process_chat",
|
|
157
|
+
model: "claude-sonnet-4",
|
|
158
|
+
agentId: "provider-agent",
|
|
159
|
+
});
|
|
160
|
+
callMcpToolMock.mockResolvedValue(providerResponse({ content: "A short summary.", inputTokens: 100, outputTokens: 40 }));
|
|
161
|
+
// The handler's exception must still propagate.
|
|
162
|
+
await expect(tool.execute({})).rejects.toThrow("handler failed after LLM call");
|
|
163
|
+
const span = consumerSpan();
|
|
164
|
+
expect(span).toBeDefined();
|
|
165
|
+
expect(span.success).toBe(false);
|
|
166
|
+
// Accumulated usage from the LLM call before the throw is preserved.
|
|
167
|
+
expect(span.llmInputTokens).toBe(100);
|
|
168
|
+
expect(span.llmOutputTokens).toBe(40);
|
|
169
|
+
expect(span.llmTotalTokens).toBe(140);
|
|
170
|
+
expect(span.llmModel).toBe("claude-sonnet-4");
|
|
171
|
+
expect(span.llmProvider).toBe(`mesh:${PROVIDER_ENDPOINT}`);
|
|
172
|
+
});
|
|
173
|
+
it("publishes the consumer span WITHOUT llm_* fields when execute never calls the llm", async () => {
|
|
174
|
+
const tool = llm({
|
|
175
|
+
name: FUNCTION_ID,
|
|
176
|
+
provider: { capability: "llm-service" },
|
|
177
|
+
parameters: z.object({}),
|
|
178
|
+
// Non-LLM path through the same wrapper: returns directly, no llm() call.
|
|
179
|
+
execute: async () => "static result",
|
|
180
|
+
});
|
|
181
|
+
LlmToolRegistry.getInstance().setResolvedProvider(FUNCTION_ID, {
|
|
182
|
+
endpoint: PROVIDER_ENDPOINT,
|
|
183
|
+
functionName: "process_chat",
|
|
184
|
+
model: "claude-sonnet-4",
|
|
185
|
+
agentId: "provider-agent",
|
|
186
|
+
});
|
|
187
|
+
await tool.execute({});
|
|
188
|
+
const span = consumerSpan();
|
|
189
|
+
expect(span).toBeDefined();
|
|
190
|
+
expect(span.llmInputTokens).toBeUndefined();
|
|
191
|
+
expect(span.llmOutputTokens).toBeUndefined();
|
|
192
|
+
expect(span.llmTotalTokens).toBeUndefined();
|
|
193
|
+
expect(span.llmModel).toBeUndefined();
|
|
194
|
+
expect(span.llmProvider).toBeUndefined();
|
|
195
|
+
// callMcpTool must not have been invoked on this non-LLM path.
|
|
196
|
+
expect(callMcpToolMock).not.toHaveBeenCalled();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
//# sourceMappingURL=llm-token-telemetry.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-token-telemetry.test.js","sourceRoot":"","sources":["../../src/__tests__/llm-token-telemetry.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unwired-slot-warn.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/unwired-slot-warn.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unwired typed-dependency-slot diagnostics (issue #1231).
|
|
3
|
+
*
|
|
4
|
+
* A declared typed dependency slot can remain null after the settle window
|
|
5
|
+
* closes — no proxy was ever injected — leaving the parameter silently null.
|
|
6
|
+
* The deps array is rebuilt PER CALL, so the diagnostic must warn ONCE per
|
|
7
|
+
* tool+slot (a per-call warning would spam) and only AFTER the settle latch
|
|
8
|
+
* has flipped (during settling the proxy may still land). The same warn-once
|
|
9
|
+
* dedupe spans BOTH the inbound HTTP path and the claim-dispatch path.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { MeshAgent, __resetUnwiredSlotWarnedForTests } from "../agent.js";
|
|
14
|
+
import { RouteRegistry } from "../route.js";
|
|
15
|
+
import { resetSettleStateForTests } from "../settle.js";
|
|
16
|
+
function makeFastMCPStub() {
|
|
17
|
+
return {
|
|
18
|
+
addTool: vi.fn(),
|
|
19
|
+
start: vi.fn(),
|
|
20
|
+
getApp: vi.fn(),
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
let autoStartSpy = null;
|
|
25
|
+
let warnSpy = null;
|
|
26
|
+
const savedEnv = {};
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
autoStartSpy = vi
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
.spyOn(MeshAgent.prototype, "_autoStart")
|
|
31
|
+
.mockImplementation(async () => {
|
|
32
|
+
/* no-op */
|
|
33
|
+
});
|
|
34
|
+
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {
|
|
35
|
+
/* swallow */
|
|
36
|
+
});
|
|
37
|
+
savedEnv.MCP_MESH_SETTLE_TIMEOUT = process.env.MCP_MESH_SETTLE_TIMEOUT;
|
|
38
|
+
savedEnv.MCP_MESH_TOOL_ISOLATION = process.env.MCP_MESH_TOOL_ISOLATION;
|
|
39
|
+
process.env.MCP_MESH_TOOL_ISOLATION = "false";
|
|
40
|
+
// Disable the settle grace so the agent is settled immediately — a null
|
|
41
|
+
// slot here is genuinely unwired, exactly the case the warning targets.
|
|
42
|
+
process.env.MCP_MESH_SETTLE_TIMEOUT = "0";
|
|
43
|
+
resetSettleStateForTests();
|
|
44
|
+
RouteRegistry.reset();
|
|
45
|
+
__resetUnwiredSlotWarnedForTests();
|
|
46
|
+
});
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
if (autoStartSpy) {
|
|
49
|
+
autoStartSpy.mockRestore();
|
|
50
|
+
autoStartSpy = null;
|
|
51
|
+
}
|
|
52
|
+
if (warnSpy) {
|
|
53
|
+
warnSpy.mockRestore();
|
|
54
|
+
warnSpy = null;
|
|
55
|
+
}
|
|
56
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
57
|
+
if (value === undefined)
|
|
58
|
+
delete process.env[key];
|
|
59
|
+
else
|
|
60
|
+
process.env[key] = value;
|
|
61
|
+
}
|
|
62
|
+
resetSettleStateForTests();
|
|
63
|
+
RouteRegistry.reset();
|
|
64
|
+
__resetUnwiredSlotWarnedForTests();
|
|
65
|
+
});
|
|
66
|
+
function unwiredWarnings() {
|
|
67
|
+
return (warnSpy?.mock.calls ?? [])
|
|
68
|
+
.map((call) => String(call[0]))
|
|
69
|
+
.filter((msg) => msg.includes("no proxy was injected"));
|
|
70
|
+
}
|
|
71
|
+
describe("unwired typed-slot warning (#1231)", () => {
|
|
72
|
+
it("warns ONCE per tool+slot across multiple calls, not per-call", async () => {
|
|
73
|
+
const fastmcp = makeFastMCPStub();
|
|
74
|
+
const agent = new MeshAgent(fastmcp, {
|
|
75
|
+
name: "unwired-agent",
|
|
76
|
+
httpPort: 0,
|
|
77
|
+
});
|
|
78
|
+
agent.addTool({
|
|
79
|
+
name: "consumer_tool",
|
|
80
|
+
parameters: z.object({}),
|
|
81
|
+
dependencies: [{ capability: "missing_cap" }],
|
|
82
|
+
// Defensive user idiom — the dep slot is never resolved, so it stays
|
|
83
|
+
// null on every call.
|
|
84
|
+
execute: async (_args, dep) => dep ? "wired" : "unwired",
|
|
85
|
+
});
|
|
86
|
+
const execute = fastmcp.addTool.mock.calls[0][0].execute;
|
|
87
|
+
for (let i = 0; i < 3; i++) {
|
|
88
|
+
expect(await execute({})).toBe("unwired");
|
|
89
|
+
}
|
|
90
|
+
const warnings = unwiredWarnings();
|
|
91
|
+
expect(warnings).toHaveLength(1);
|
|
92
|
+
expect(warnings[0]).toContain("missing_cap");
|
|
93
|
+
expect(warnings[0]).toContain("consumer_tool");
|
|
94
|
+
expect(warnings[0]).toContain("slot 0");
|
|
95
|
+
});
|
|
96
|
+
it("does not warn when the slot actually wires", async () => {
|
|
97
|
+
const fastmcp = makeFastMCPStub();
|
|
98
|
+
const agent = new MeshAgent(fastmcp, {
|
|
99
|
+
name: "wired-agent",
|
|
100
|
+
httpPort: 0,
|
|
101
|
+
});
|
|
102
|
+
agent.addTool({
|
|
103
|
+
name: "wired_tool",
|
|
104
|
+
parameters: z.object({}),
|
|
105
|
+
dependencies: [{ capability: "present_cap" }],
|
|
106
|
+
execute: async (_args, dep) => dep ? "wired" : "unwired",
|
|
107
|
+
});
|
|
108
|
+
const execute = fastmcp.addTool.mock.calls[0][0].execute;
|
|
109
|
+
// Resolve the dependency so a real proxy lands in the slot.
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
+
agent.handleDependencyAvailable("present_cap", "http://localhost:19999", "remote_fn", "provider-agent", "wired_tool", 0);
|
|
112
|
+
expect(await execute({})).toBe("wired");
|
|
113
|
+
expect(unwiredWarnings()).toHaveLength(0);
|
|
114
|
+
});
|
|
115
|
+
it("warns ONCE for an unwired slot on the claim-dispatch path", async () => {
|
|
116
|
+
const fastmcp = makeFastMCPStub();
|
|
117
|
+
const agent = new MeshAgent(fastmcp, {
|
|
118
|
+
name: "claim-agent",
|
|
119
|
+
httpPort: 0,
|
|
120
|
+
});
|
|
121
|
+
agent.addTool({
|
|
122
|
+
name: "task_tool",
|
|
123
|
+
task: true,
|
|
124
|
+
parameters: z.object({}),
|
|
125
|
+
dependencies: [{ capability: "missing_cap" }],
|
|
126
|
+
execute: async (_args, dep) => dep ? "wired" : "unwired",
|
|
127
|
+
});
|
|
128
|
+
// The claim-dispatch path is exercised via the registered ClaimHandler
|
|
129
|
+
// (one per task: true tool), not via FastMCP's execute().
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
131
|
+
const { handler } = agent._taskHandlers.get("task_tool");
|
|
132
|
+
// meshJobParamIndex is unset, so the dummy controller is never spliced
|
|
133
|
+
// into callArgs — the user execute() only sees (payload, dep).
|
|
134
|
+
for (let i = 0; i < 3; i++) {
|
|
135
|
+
expect(await handler({}, {})).toBe("unwired");
|
|
136
|
+
}
|
|
137
|
+
const warnings = unwiredWarnings();
|
|
138
|
+
expect(warnings).toHaveLength(1);
|
|
139
|
+
expect(warnings[0]).toContain("missing_cap");
|
|
140
|
+
expect(warnings[0]).toContain("task_tool");
|
|
141
|
+
expect(warnings[0]).toContain("slot 0");
|
|
142
|
+
});
|
|
143
|
+
it("dedupes the unwired warning ACROSS the inbound and claim paths", async () => {
|
|
144
|
+
const fastmcp = makeFastMCPStub();
|
|
145
|
+
const agent = new MeshAgent(fastmcp, {
|
|
146
|
+
name: "both-paths-agent",
|
|
147
|
+
httpPort: 0,
|
|
148
|
+
});
|
|
149
|
+
agent.addTool({
|
|
150
|
+
name: "task_tool",
|
|
151
|
+
task: true,
|
|
152
|
+
parameters: z.object({}),
|
|
153
|
+
dependencies: [{ capability: "missing_cap" }],
|
|
154
|
+
execute: async (_args, dep) => dep ? "wired" : "unwired",
|
|
155
|
+
});
|
|
156
|
+
const execute = fastmcp.addTool.mock.calls[0][0].execute;
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
+
const { handler } = agent._taskHandlers.get("task_tool");
|
|
159
|
+
// Exercise the same tool+slot via BOTH paths — the shared
|
|
160
|
+
// _unwiredSlotWarned Set must dedupe to a single warning total.
|
|
161
|
+
expect(await execute({})).toBe("unwired");
|
|
162
|
+
expect(await handler({}, {})).toBe("unwired");
|
|
163
|
+
expect(await execute({})).toBe("unwired");
|
|
164
|
+
expect(unwiredWarnings()).toHaveLength(1);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
//# sourceMappingURL=unwired-slot-warn.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unwired-slot-warn.spec.js","sourceRoot":"","sources":["../../src/__tests__/unwired-slot-warn.spec.ts"],"names":[],"mappings":"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"}
|