@wingman-ai/gateway 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/agent/config/mcpClientManager.cjs +104 -1
- package/dist/agent/config/mcpClientManager.d.ts +30 -0
- package/dist/agent/config/mcpClientManager.js +104 -1
- package/dist/agent/config/modelFactory.cjs +10 -0
- package/dist/agent/config/modelFactory.js +10 -0
- package/dist/agent/config/xaiImageModel.cjs +242 -0
- package/dist/agent/config/xaiImageModel.d.ts +33 -0
- package/dist/agent/config/xaiImageModel.js +202 -0
- package/dist/agent/tests/mcpClientManager.test.cjs +116 -0
- package/dist/agent/tests/mcpClientManager.test.js +117 -1
- package/dist/agent/tests/mcpResourceTools.test.cjs +101 -0
- package/dist/agent/tests/mcpResourceTools.test.d.ts +1 -0
- package/dist/agent/tests/mcpResourceTools.test.js +95 -0
- package/dist/agent/tests/modelFactory.test.cjs +16 -2
- package/dist/agent/tests/modelFactory.test.js +16 -2
- package/dist/agent/tests/xaiImageModel.test.cjs +194 -0
- package/dist/agent/tests/xaiImageModel.test.d.ts +1 -0
- package/dist/agent/tests/xaiImageModel.test.js +188 -0
- package/dist/agent/tools/mcp_resources.cjs +111 -0
- package/dist/agent/tools/mcp_resources.d.ts +3 -0
- package/dist/agent/tools/mcp_resources.js +77 -0
- package/dist/bench/adapters/commandAdapter.cjs +93 -0
- package/dist/bench/adapters/commandAdapter.d.ts +6 -0
- package/dist/bench/adapters/commandAdapter.js +59 -0
- package/dist/bench/adapters/helpers.cjs +170 -0
- package/dist/bench/adapters/helpers.d.ts +7 -0
- package/dist/bench/adapters/helpers.js +133 -0
- package/dist/bench/adapters/index.cjs +41 -0
- package/dist/bench/adapters/index.d.ts +2 -0
- package/dist/bench/adapters/index.js +7 -0
- package/dist/bench/adapters/wingmanCliAdapter.cjs +100 -0
- package/dist/bench/adapters/wingmanCliAdapter.d.ts +6 -0
- package/dist/bench/adapters/wingmanCliAdapter.js +66 -0
- package/dist/bench/cleanup.cjs +122 -0
- package/dist/bench/cleanup.d.ts +9 -0
- package/dist/bench/cleanup.js +85 -0
- package/dist/bench/config.cjs +190 -0
- package/dist/bench/config.d.ts +2 -0
- package/dist/bench/config.js +156 -0
- package/dist/bench/index.cjs +43 -0
- package/dist/bench/index.d.ts +3 -0
- package/dist/bench/index.js +3 -0
- package/dist/bench/official.cjs +616 -0
- package/dist/bench/official.d.ts +80 -0
- package/dist/bench/official.js +546 -0
- package/dist/bench/officialCli.cjs +204 -0
- package/dist/bench/officialCli.d.ts +5 -0
- package/dist/bench/officialCli.js +170 -0
- package/dist/bench/process.cjs +78 -0
- package/dist/bench/process.d.ts +14 -0
- package/dist/bench/process.js +44 -0
- package/dist/bench/runner.cjs +237 -0
- package/dist/bench/runner.d.ts +7 -0
- package/dist/bench/runner.js +197 -0
- package/dist/bench/scoring.cjs +171 -0
- package/dist/bench/scoring.d.ts +9 -0
- package/dist/bench/scoring.js +137 -0
- package/dist/bench/types.cjs +18 -0
- package/dist/bench/types.d.ts +200 -0
- package/dist/bench/types.js +0 -0
- package/dist/bench/validator.cjs +92 -0
- package/dist/bench/validator.d.ts +2 -0
- package/dist/bench/validator.js +58 -0
- package/dist/cli/config/schema.cjs +36 -1
- package/dist/cli/config/schema.d.ts +46 -0
- package/dist/cli/config/schema.js +36 -1
- package/dist/cli/config/warnings.cjs +119 -51
- package/dist/cli/config/warnings.js +119 -51
- package/dist/cli/core/agentInvoker.cjs +9 -2
- package/dist/cli/core/agentInvoker.d.ts +1 -0
- package/dist/cli/core/agentInvoker.js +9 -2
- package/dist/cli/core/imagePersistence.cjs +17 -1
- package/dist/cli/core/imagePersistence.d.ts +2 -0
- package/dist/cli/core/imagePersistence.js +13 -3
- package/dist/cli/core/sessionManager.cjs +2 -0
- package/dist/cli/core/sessionManager.js +3 -1
- package/dist/cli/types.d.ts +18 -0
- package/dist/gateway/adapters/teams.cjs +419 -0
- package/dist/gateway/adapters/teams.d.ts +47 -0
- package/dist/gateway/adapters/teams.js +361 -0
- package/dist/gateway/http/sms.cjs +286 -0
- package/dist/gateway/http/sms.d.ts +4 -0
- package/dist/gateway/http/sms.js +249 -0
- package/dist/gateway/server.cjs +54 -3
- package/dist/gateway/server.d.ts +2 -0
- package/dist/gateway/server.js +54 -3
- package/dist/gateway/sms/commands.cjs +116 -0
- package/dist/gateway/sms/commands.d.ts +15 -0
- package/dist/gateway/sms/commands.js +79 -0
- package/dist/gateway/sms/control.cjs +118 -0
- package/dist/gateway/sms/control.d.ts +18 -0
- package/dist/gateway/sms/control.js +84 -0
- package/dist/gateway/sms/policyStore.cjs +198 -0
- package/dist/gateway/sms/policyStore.d.ts +37 -0
- package/dist/gateway/sms/policyStore.js +161 -0
- package/dist/providers/registry.cjs +1 -0
- package/dist/providers/registry.js +1 -0
- package/dist/tests/cli-config-warnings.test.cjs +41 -0
- package/dist/tests/cli-config-warnings.test.js +41 -0
- package/dist/tests/cli-init.test.cjs +32 -26
- package/dist/tests/cli-init.test.js +32 -26
- package/dist/tests/gateway-http-security.test.cjs +21 -0
- package/dist/tests/gateway-http-security.test.js +21 -0
- package/dist/tests/gateway-origin-policy.test.cjs +22 -0
- package/dist/tests/gateway-origin-policy.test.js +22 -0
- package/dist/tests/gateway.test.cjs +57 -0
- package/dist/tests/gateway.test.js +57 -0
- package/dist/tests/imagePersistence.test.cjs +26 -0
- package/dist/tests/imagePersistence.test.js +27 -1
- package/dist/tests/run-terminal-bench-official-script.test.cjs +61 -0
- package/dist/tests/run-terminal-bench-official-script.test.d.ts +1 -0
- package/dist/tests/run-terminal-bench-official-script.test.js +55 -0
- package/dist/tests/sessions-api.test.cjs +69 -1
- package/dist/tests/sessions-api.test.js +70 -2
- package/dist/tests/sms-api.test.cjs +183 -0
- package/dist/tests/sms-api.test.d.ts +1 -0
- package/dist/tests/sms-api.test.js +177 -0
- package/dist/tests/sms-commands.test.cjs +90 -0
- package/dist/tests/sms-commands.test.d.ts +1 -0
- package/dist/tests/sms-commands.test.js +84 -0
- package/dist/tests/sms-policy-store.test.cjs +69 -0
- package/dist/tests/sms-policy-store.test.d.ts +1 -0
- package/dist/tests/sms-policy-store.test.js +63 -0
- package/dist/tests/teams-adapter.test.cjs +58 -0
- package/dist/tests/teams-adapter.test.d.ts +1 -0
- package/dist/tests/teams-adapter.test.js +52 -0
- package/dist/tests/terminal-bench-adapters-helpers.test.cjs +64 -0
- package/dist/tests/terminal-bench-adapters-helpers.test.d.ts +1 -0
- package/dist/tests/terminal-bench-adapters-helpers.test.js +58 -0
- package/dist/tests/terminal-bench-cleanup.test.cjs +93 -0
- package/dist/tests/terminal-bench-cleanup.test.d.ts +1 -0
- package/dist/tests/terminal-bench-cleanup.test.js +87 -0
- package/dist/tests/terminal-bench-config.test.cjs +62 -0
- package/dist/tests/terminal-bench-config.test.d.ts +1 -0
- package/dist/tests/terminal-bench-config.test.js +56 -0
- package/dist/tests/terminal-bench-official.test.cjs +194 -0
- package/dist/tests/terminal-bench-official.test.d.ts +1 -0
- package/dist/tests/terminal-bench-official.test.js +188 -0
- package/dist/tests/terminal-bench-runner.test.cjs +82 -0
- package/dist/tests/terminal-bench-runner.test.d.ts +1 -0
- package/dist/tests/terminal-bench-runner.test.js +76 -0
- package/dist/tests/terminal-bench-scoring.test.cjs +128 -0
- package/dist/tests/terminal-bench-scoring.test.d.ts +1 -0
- package/dist/tests/terminal-bench-scoring.test.js +122 -0
- package/dist/tools/mcp-fal-ai.cjs +1 -1
- package/dist/tools/mcp-fal-ai.js +1 -1
- package/dist/webui/assets/index-Cyg_Hs57.css +11 -0
- package/dist/webui/assets/{index-BMekSELC.js → index-DZXLLjaA.js} +109 -109
- package/dist/webui/index.html +2 -2
- package/package.json +11 -2
- package/templates/agents/game-dev/agent.md +110 -63
- package/templates/agents/game-dev/art-director.md +106 -0
- package/templates/agents/game-dev/game-designer.md +87 -0
- package/templates/agents/game-dev/scene-engineer.md +474 -0
- package/dist/webui/assets/index-Cwkg4DKj.css +0 -11
- package/templates/agents/game-dev/art-generation.md +0 -38
- package/templates/agents/game-dev/asset-refinement.md +0 -17
- package/templates/agents/game-dev/planning-idea.md +0 -17
- package/templates/agents/game-dev/ui-specialist.md +0 -17
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
2
|
+
import { AIMessage, AIMessageChunk } from "@langchain/core/messages";
|
|
3
|
+
import { ChatGenerationChunk } from "@langchain/core/outputs";
|
|
4
|
+
function _define_property(obj, key, value) {
|
|
5
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
6
|
+
value: value,
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
writable: true
|
|
10
|
+
});
|
|
11
|
+
else obj[key] = value;
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
const DEFAULT_XAI_BASE_URL = "https://api.x.ai/v1";
|
|
15
|
+
const IMAGE_MODEL_NAME_PATTERN = /^grok-imagine-image(?:[-:._].*)?$/i;
|
|
16
|
+
const XAI_IMAGE_PROMPT_MAX_CHARS = 7900;
|
|
17
|
+
function isNativeXAIImageModel(modelName) {
|
|
18
|
+
return IMAGE_MODEL_NAME_PATTERN.test(modelName.trim());
|
|
19
|
+
}
|
|
20
|
+
function normalizeContentText(content) {
|
|
21
|
+
if ("string" == typeof content) return content.trim();
|
|
22
|
+
if (!Array.isArray(content)) return "";
|
|
23
|
+
const parts = content.map((part)=>{
|
|
24
|
+
if (!part || "object" != typeof part || Array.isArray(part)) return "";
|
|
25
|
+
const record = part;
|
|
26
|
+
return "text" === record.type && "string" == typeof record.text ? record.text.trim() : "";
|
|
27
|
+
}).filter(Boolean);
|
|
28
|
+
return parts.join("\n").trim();
|
|
29
|
+
}
|
|
30
|
+
function normalizeRole(message) {
|
|
31
|
+
const type = "string" == typeof message.type ? message.type : "function" == typeof message._getType ? String(message._getType() ?? "").toLowerCase() : "";
|
|
32
|
+
return type.toLowerCase();
|
|
33
|
+
}
|
|
34
|
+
function resolveAdditionalKwargs(message) {
|
|
35
|
+
const direct = message.additional_kwargs;
|
|
36
|
+
if (direct && "object" == typeof direct && !Array.isArray(direct)) return direct;
|
|
37
|
+
const kwargs = message.kwargs;
|
|
38
|
+
if (kwargs && "object" == typeof kwargs && !Array.isArray(kwargs)) {
|
|
39
|
+
const nested = kwargs.additional_kwargs;
|
|
40
|
+
if (nested && "object" == typeof nested && !Array.isArray(nested)) return nested;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function isHiddenMiddlewarePrompt(message, text) {
|
|
44
|
+
const additionalKwargs = resolveAdditionalKwargs(message);
|
|
45
|
+
const uiHidden = additionalKwargs?.ui_hidden === true || additionalKwargs?.uiHidden === true;
|
|
46
|
+
if (uiHidden) return true;
|
|
47
|
+
const source = "string" == typeof additionalKwargs?.source ? additionalKwargs.source.toLowerCase() : "";
|
|
48
|
+
if ("additional-message-middleware" === source) return true;
|
|
49
|
+
const normalized = text.toLowerCase();
|
|
50
|
+
return normalized.includes("# confidentiality (internal)") && normalized.includes("current date time (utc)");
|
|
51
|
+
}
|
|
52
|
+
function clampPromptLength(prompt) {
|
|
53
|
+
if (prompt.length <= XAI_IMAGE_PROMPT_MAX_CHARS) return prompt;
|
|
54
|
+
return prompt.slice(0, XAI_IMAGE_PROMPT_MAX_CHARS);
|
|
55
|
+
}
|
|
56
|
+
function buildPrompt(messages) {
|
|
57
|
+
const systemParts = [];
|
|
58
|
+
const visibleUserParts = [];
|
|
59
|
+
const fallbackUserParts = [];
|
|
60
|
+
for (const message of messages){
|
|
61
|
+
const text = normalizeContentText(message.content);
|
|
62
|
+
if (!text) continue;
|
|
63
|
+
const role = normalizeRole(message);
|
|
64
|
+
if ("system" === role) {
|
|
65
|
+
systemParts.push(text);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if ("human" === role || "user" === role) {
|
|
69
|
+
fallbackUserParts.push(text);
|
|
70
|
+
if (!isHiddenMiddlewarePrompt(message, text)) visibleUserParts.push(text);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const latestUserPrompt = visibleUserParts[visibleUserParts.length - 1] || fallbackUserParts[fallbackUserParts.length - 1];
|
|
74
|
+
if (!latestUserPrompt) throw new Error("xAI image generation requires a user prompt.");
|
|
75
|
+
const systemPrefix = systemParts.join("\n\n").trim();
|
|
76
|
+
if (!systemPrefix) return clampPromptLength(latestUserPrompt);
|
|
77
|
+
const userSection = `User request:\n${latestUserPrompt}`;
|
|
78
|
+
const withSystem = `System instructions:\n${systemPrefix}\n\n${userSection}`;
|
|
79
|
+
if (withSystem.length <= XAI_IMAGE_PROMPT_MAX_CHARS) return withSystem;
|
|
80
|
+
const systemPrefixLabel = "System instructions:\n";
|
|
81
|
+
const userSectionPrefix = `\n\n${userSection}`;
|
|
82
|
+
const availableSystemChars = XAI_IMAGE_PROMPT_MAX_CHARS - systemPrefixLabel.length - userSectionPrefix.length;
|
|
83
|
+
if (availableSystemChars <= 0) return clampPromptLength(userSection);
|
|
84
|
+
const truncatedSystem = systemPrefix.slice(0, availableSystemChars);
|
|
85
|
+
return `${systemPrefixLabel}${truncatedSystem}${userSectionPrefix}`;
|
|
86
|
+
}
|
|
87
|
+
async function readErrorResponse(response) {
|
|
88
|
+
const body = await response.text();
|
|
89
|
+
if (!body.trim()) return `xAI image generation request failed with status ${response.status}.`;
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(body);
|
|
92
|
+
const errorRecord = parsed.error && "object" == typeof parsed.error ? parsed.error : void 0;
|
|
93
|
+
const message = "string" == typeof errorRecord?.message && errorRecord.message || "string" == typeof parsed.message && parsed.message || "string" == typeof parsed.error && parsed.error;
|
|
94
|
+
if (message) return `xAI image generation failed: ${message}`;
|
|
95
|
+
} catch {}
|
|
96
|
+
return `xAI image generation failed: ${body}`;
|
|
97
|
+
}
|
|
98
|
+
class NativeXAIImageModel extends BaseChatModel {
|
|
99
|
+
bindTools(_tools, _options) {
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
_llmType() {
|
|
103
|
+
return "xai-image-native";
|
|
104
|
+
}
|
|
105
|
+
getLsParams() {
|
|
106
|
+
return {
|
|
107
|
+
ls_provider: "xai",
|
|
108
|
+
ls_model_name: this.model,
|
|
109
|
+
ls_model_type: "chat"
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
invocationParams() {
|
|
113
|
+
return {
|
|
114
|
+
provider: "xai",
|
|
115
|
+
model: this.model,
|
|
116
|
+
baseURL: this.baseURL,
|
|
117
|
+
responseFormat: this.responseFormat,
|
|
118
|
+
size: this.size
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async _generate(messages, options, _runManager) {
|
|
122
|
+
if (!this.apiKey) throw new Error("Missing xAI credentials. Configure XAI_API_KEY before using grok-imagine-image.");
|
|
123
|
+
const prompt = buildPrompt(messages);
|
|
124
|
+
const payload = {
|
|
125
|
+
model: this.model,
|
|
126
|
+
prompt,
|
|
127
|
+
response_format: this.responseFormat
|
|
128
|
+
};
|
|
129
|
+
if (this.size) payload.size = this.size;
|
|
130
|
+
const response = await this.caller.callWithOptions({
|
|
131
|
+
signal: options.signal
|
|
132
|
+
}, async ()=>fetch(`${this.baseURL}/images/generations`, {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: {
|
|
135
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
136
|
+
"Content-Type": "application/json"
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify(payload),
|
|
139
|
+
signal: options.signal
|
|
140
|
+
}));
|
|
141
|
+
if (!response.ok) throw new Error(await readErrorResponse(response));
|
|
142
|
+
const data = await response.json();
|
|
143
|
+
const first = Array.isArray(data.data) ? data.data[0] : void 0;
|
|
144
|
+
if (!first) throw new Error("xAI image generation returned no image data.");
|
|
145
|
+
const imageUrl = "string" == typeof first.url && first.url.trim() ? first.url.trim() : "string" == typeof first.b64_json && first.b64_json.trim() ? `data:${first.mime_type || "image/png"};base64,${first.b64_json.trim()}` : "";
|
|
146
|
+
if (!imageUrl) throw new Error("xAI image generation response was missing both url and b64_json.");
|
|
147
|
+
const confirmationText = "Image generated.";
|
|
148
|
+
const message = new AIMessage({
|
|
149
|
+
content: [
|
|
150
|
+
{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: confirmationText
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
type: "output_image",
|
|
156
|
+
image_url: imageUrl
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
response_metadata: {
|
|
160
|
+
model: this.model,
|
|
161
|
+
revised_prompt: first.revised_prompt
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
generations: [
|
|
166
|
+
{
|
|
167
|
+
text: confirmationText,
|
|
168
|
+
message
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
llmOutput: {
|
|
172
|
+
model: this.model
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
async *_streamResponseChunks(messages, options, runManager) {
|
|
177
|
+
const result = await this._generate(messages, options, runManager);
|
|
178
|
+
const generation = result.generations[0];
|
|
179
|
+
if (!generation) return;
|
|
180
|
+
const aiMessage = generation.message;
|
|
181
|
+
const chunk = new AIMessageChunk({
|
|
182
|
+
content: aiMessage.content,
|
|
183
|
+
additional_kwargs: aiMessage.additional_kwargs,
|
|
184
|
+
response_metadata: aiMessage.response_metadata
|
|
185
|
+
});
|
|
186
|
+
if (generation.text) await runManager?.handleLLMNewToken(generation.text);
|
|
187
|
+
yield new ChatGenerationChunk({
|
|
188
|
+
text: generation.text || "",
|
|
189
|
+
message: chunk,
|
|
190
|
+
generationInfo: generation.generationInfo
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
constructor(fields){
|
|
194
|
+
super(fields), _define_property(this, "model", void 0), _define_property(this, "apiKey", void 0), _define_property(this, "baseURL", void 0), _define_property(this, "size", void 0), _define_property(this, "responseFormat", void 0);
|
|
195
|
+
this.model = fields.model;
|
|
196
|
+
this.apiKey = fields.apiKey;
|
|
197
|
+
this.baseURL = (fields.baseURL || DEFAULT_XAI_BASE_URL).replace(/\/+$/, "");
|
|
198
|
+
this.size = fields.size;
|
|
199
|
+
this.responseFormat = fields.responseFormat || "url";
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
export { DEFAULT_XAI_BASE_URL, NativeXAIImageModel, isNativeXAIImageModel };
|
|
@@ -167,6 +167,122 @@ const getClientConfig = (manager)=>manager.buildClientConfig();
|
|
|
167
167
|
(0, external_vitest_namespaceObject.expect)(server.env.INVARIANT_API_URL).toBe("https://explorer.invariantlabs.ai");
|
|
168
168
|
(0, external_vitest_namespaceObject.expect)(server.env.GUARDRAILS_API_URL).toBe("https://explorer.invariantlabs.ai");
|
|
169
169
|
});
|
|
170
|
+
(0, external_vitest_namespaceObject.it)("keeps MCP multimodal output handling in artifact mode", ()=>{
|
|
171
|
+
const configs = [
|
|
172
|
+
{
|
|
173
|
+
servers: [
|
|
174
|
+
{
|
|
175
|
+
name: "fal-ai",
|
|
176
|
+
transport: "stdio",
|
|
177
|
+
command: "bun",
|
|
178
|
+
args: [
|
|
179
|
+
"run",
|
|
180
|
+
"src/tools/mcp-fal-ai.ts"
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
];
|
|
186
|
+
const manager = new mcpClientManager_cjs_namespaceObject.MCPClientManager(configs, testLogger);
|
|
187
|
+
const clientConfig = getClientConfig(manager);
|
|
188
|
+
(0, external_vitest_namespaceObject.expect)(clientConfig.outputHandling).toEqual({
|
|
189
|
+
image: "artifact",
|
|
190
|
+
audio: "artifact",
|
|
191
|
+
resource: "artifact"
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
(0, external_vitest_namespaceObject.it)("lists resources from MCP client", async ()=>{
|
|
195
|
+
const listResources = external_vitest_namespaceObject.vi.fn().mockResolvedValue({
|
|
196
|
+
fal: [
|
|
197
|
+
{
|
|
198
|
+
uri: "fal://jobs/123",
|
|
199
|
+
name: "Job 123",
|
|
200
|
+
description: "Generated asset",
|
|
201
|
+
mimeType: "application/json"
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
});
|
|
205
|
+
const manager = new mcpClientManager_cjs_namespaceObject.MCPClientManager([], testLogger);
|
|
206
|
+
manager.client = {
|
|
207
|
+
listResources
|
|
208
|
+
};
|
|
209
|
+
const result = await manager.listResources([
|
|
210
|
+
"fal"
|
|
211
|
+
]);
|
|
212
|
+
(0, external_vitest_namespaceObject.expect)(listResources).toHaveBeenCalledWith("fal");
|
|
213
|
+
(0, external_vitest_namespaceObject.expect)(result).toEqual({
|
|
214
|
+
fal: [
|
|
215
|
+
{
|
|
216
|
+
uri: "fal://jobs/123",
|
|
217
|
+
name: "Job 123",
|
|
218
|
+
description: "Generated asset",
|
|
219
|
+
mimeType: "application/json"
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
(0, external_vitest_namespaceObject.it)("lists resource templates from MCP client", async ()=>{
|
|
225
|
+
const listResourceTemplates = external_vitest_namespaceObject.vi.fn().mockResolvedValue({
|
|
226
|
+
fal: [
|
|
227
|
+
{
|
|
228
|
+
uriTemplate: "fal://jobs/{jobId}",
|
|
229
|
+
name: "Fal Job",
|
|
230
|
+
description: "Job by ID",
|
|
231
|
+
mimeType: "application/json"
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
});
|
|
235
|
+
const manager = new mcpClientManager_cjs_namespaceObject.MCPClientManager([], testLogger);
|
|
236
|
+
manager.client = {
|
|
237
|
+
listResourceTemplates
|
|
238
|
+
};
|
|
239
|
+
const result = await manager.listResourceTemplates([
|
|
240
|
+
"fal"
|
|
241
|
+
]);
|
|
242
|
+
(0, external_vitest_namespaceObject.expect)(listResourceTemplates).toHaveBeenCalledWith("fal");
|
|
243
|
+
(0, external_vitest_namespaceObject.expect)(result).toEqual({
|
|
244
|
+
fal: [
|
|
245
|
+
{
|
|
246
|
+
uriTemplate: "fal://jobs/{jobId}",
|
|
247
|
+
name: "Fal Job",
|
|
248
|
+
description: "Job by ID",
|
|
249
|
+
mimeType: "application/json"
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
(0, external_vitest_namespaceObject.it)("reads resources from MCP client", async ()=>{
|
|
255
|
+
const readResource = external_vitest_namespaceObject.vi.fn().mockResolvedValue([
|
|
256
|
+
{
|
|
257
|
+
uri: "fal://jobs/123",
|
|
258
|
+
mimeType: "application/json",
|
|
259
|
+
text: '{"status":"completed"}'
|
|
260
|
+
}
|
|
261
|
+
]);
|
|
262
|
+
const manager = new mcpClientManager_cjs_namespaceObject.MCPClientManager([], testLogger);
|
|
263
|
+
manager.client = {
|
|
264
|
+
readResource
|
|
265
|
+
};
|
|
266
|
+
const result = await manager.readResource("fal", "fal://jobs/123");
|
|
267
|
+
(0, external_vitest_namespaceObject.expect)(readResource).toHaveBeenCalledWith("fal", "fal://jobs/123");
|
|
268
|
+
(0, external_vitest_namespaceObject.expect)(result).toEqual([
|
|
269
|
+
{
|
|
270
|
+
uri: "fal://jobs/123",
|
|
271
|
+
mimeType: "application/json",
|
|
272
|
+
text: '{"status":"completed"}'
|
|
273
|
+
}
|
|
274
|
+
]);
|
|
275
|
+
});
|
|
276
|
+
(0, external_vitest_namespaceObject.it)("calls close on cleanup", async ()=>{
|
|
277
|
+
const close = external_vitest_namespaceObject.vi.fn().mockResolvedValue(void 0);
|
|
278
|
+
const manager = new mcpClientManager_cjs_namespaceObject.MCPClientManager([], testLogger);
|
|
279
|
+
manager.client = {
|
|
280
|
+
close
|
|
281
|
+
};
|
|
282
|
+
await manager.cleanup();
|
|
283
|
+
(0, external_vitest_namespaceObject.expect)(close).toHaveBeenCalledTimes(1);
|
|
284
|
+
(0, external_vitest_namespaceObject.expect)(manager.client).toBeNull();
|
|
285
|
+
});
|
|
170
286
|
});
|
|
171
287
|
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
172
288
|
Object.defineProperty(exports, '__esModule', {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { MCPClientManager } from "../config/mcpClientManager.js";
|
|
3
3
|
const testLogger = {
|
|
4
4
|
debug: ()=>{},
|
|
@@ -165,4 +165,120 @@ describe("MCPClientManager runtime env", ()=>{
|
|
|
165
165
|
expect(server.env.INVARIANT_API_URL).toBe("https://explorer.invariantlabs.ai");
|
|
166
166
|
expect(server.env.GUARDRAILS_API_URL).toBe("https://explorer.invariantlabs.ai");
|
|
167
167
|
});
|
|
168
|
+
it("keeps MCP multimodal output handling in artifact mode", ()=>{
|
|
169
|
+
const configs = [
|
|
170
|
+
{
|
|
171
|
+
servers: [
|
|
172
|
+
{
|
|
173
|
+
name: "fal-ai",
|
|
174
|
+
transport: "stdio",
|
|
175
|
+
command: "bun",
|
|
176
|
+
args: [
|
|
177
|
+
"run",
|
|
178
|
+
"src/tools/mcp-fal-ai.ts"
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
];
|
|
184
|
+
const manager = new MCPClientManager(configs, testLogger);
|
|
185
|
+
const clientConfig = getClientConfig(manager);
|
|
186
|
+
expect(clientConfig.outputHandling).toEqual({
|
|
187
|
+
image: "artifact",
|
|
188
|
+
audio: "artifact",
|
|
189
|
+
resource: "artifact"
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
it("lists resources from MCP client", async ()=>{
|
|
193
|
+
const listResources = vi.fn().mockResolvedValue({
|
|
194
|
+
fal: [
|
|
195
|
+
{
|
|
196
|
+
uri: "fal://jobs/123",
|
|
197
|
+
name: "Job 123",
|
|
198
|
+
description: "Generated asset",
|
|
199
|
+
mimeType: "application/json"
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
});
|
|
203
|
+
const manager = new MCPClientManager([], testLogger);
|
|
204
|
+
manager.client = {
|
|
205
|
+
listResources
|
|
206
|
+
};
|
|
207
|
+
const result = await manager.listResources([
|
|
208
|
+
"fal"
|
|
209
|
+
]);
|
|
210
|
+
expect(listResources).toHaveBeenCalledWith("fal");
|
|
211
|
+
expect(result).toEqual({
|
|
212
|
+
fal: [
|
|
213
|
+
{
|
|
214
|
+
uri: "fal://jobs/123",
|
|
215
|
+
name: "Job 123",
|
|
216
|
+
description: "Generated asset",
|
|
217
|
+
mimeType: "application/json"
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
it("lists resource templates from MCP client", async ()=>{
|
|
223
|
+
const listResourceTemplates = vi.fn().mockResolvedValue({
|
|
224
|
+
fal: [
|
|
225
|
+
{
|
|
226
|
+
uriTemplate: "fal://jobs/{jobId}",
|
|
227
|
+
name: "Fal Job",
|
|
228
|
+
description: "Job by ID",
|
|
229
|
+
mimeType: "application/json"
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
});
|
|
233
|
+
const manager = new MCPClientManager([], testLogger);
|
|
234
|
+
manager.client = {
|
|
235
|
+
listResourceTemplates
|
|
236
|
+
};
|
|
237
|
+
const result = await manager.listResourceTemplates([
|
|
238
|
+
"fal"
|
|
239
|
+
]);
|
|
240
|
+
expect(listResourceTemplates).toHaveBeenCalledWith("fal");
|
|
241
|
+
expect(result).toEqual({
|
|
242
|
+
fal: [
|
|
243
|
+
{
|
|
244
|
+
uriTemplate: "fal://jobs/{jobId}",
|
|
245
|
+
name: "Fal Job",
|
|
246
|
+
description: "Job by ID",
|
|
247
|
+
mimeType: "application/json"
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
it("reads resources from MCP client", async ()=>{
|
|
253
|
+
const readResource = vi.fn().mockResolvedValue([
|
|
254
|
+
{
|
|
255
|
+
uri: "fal://jobs/123",
|
|
256
|
+
mimeType: "application/json",
|
|
257
|
+
text: '{"status":"completed"}'
|
|
258
|
+
}
|
|
259
|
+
]);
|
|
260
|
+
const manager = new MCPClientManager([], testLogger);
|
|
261
|
+
manager.client = {
|
|
262
|
+
readResource
|
|
263
|
+
};
|
|
264
|
+
const result = await manager.readResource("fal", "fal://jobs/123");
|
|
265
|
+
expect(readResource).toHaveBeenCalledWith("fal", "fal://jobs/123");
|
|
266
|
+
expect(result).toEqual([
|
|
267
|
+
{
|
|
268
|
+
uri: "fal://jobs/123",
|
|
269
|
+
mimeType: "application/json",
|
|
270
|
+
text: '{"status":"completed"}'
|
|
271
|
+
}
|
|
272
|
+
]);
|
|
273
|
+
});
|
|
274
|
+
it("calls close on cleanup", async ()=>{
|
|
275
|
+
const close = vi.fn().mockResolvedValue(void 0);
|
|
276
|
+
const manager = new MCPClientManager([], testLogger);
|
|
277
|
+
manager.client = {
|
|
278
|
+
close
|
|
279
|
+
};
|
|
280
|
+
await manager.cleanup();
|
|
281
|
+
expect(close).toHaveBeenCalledTimes(1);
|
|
282
|
+
expect(manager.client).toBeNull();
|
|
283
|
+
});
|
|
168
284
|
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_exports__ = {};
|
|
3
|
+
const external_vitest_namespaceObject = require("vitest");
|
|
4
|
+
const mcp_resources_cjs_namespaceObject = require("../tools/mcp_resources.cjs");
|
|
5
|
+
const createManagerStub = ()=>{
|
|
6
|
+
const listResources = external_vitest_namespaceObject.vi.fn();
|
|
7
|
+
const listResourceTemplates = external_vitest_namespaceObject.vi.fn();
|
|
8
|
+
const readResource = external_vitest_namespaceObject.vi.fn();
|
|
9
|
+
const manager = {
|
|
10
|
+
listResources,
|
|
11
|
+
listResourceTemplates,
|
|
12
|
+
readResource
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
manager,
|
|
16
|
+
listResources,
|
|
17
|
+
listResourceTemplates,
|
|
18
|
+
readResource
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
(0, external_vitest_namespaceObject.describe)("mcp resource tools", ()=>{
|
|
22
|
+
(0, external_vitest_namespaceObject.it)("lists resources and templates", async ()=>{
|
|
23
|
+
const { manager, listResources, listResourceTemplates } = createManagerStub();
|
|
24
|
+
listResources.mockResolvedValue({
|
|
25
|
+
fal: [
|
|
26
|
+
{
|
|
27
|
+
uri: "fal://jobs/1",
|
|
28
|
+
name: "Job 1",
|
|
29
|
+
mimeType: "application/json"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
});
|
|
33
|
+
listResourceTemplates.mockResolvedValue({
|
|
34
|
+
fal: [
|
|
35
|
+
{
|
|
36
|
+
uriTemplate: "fal://jobs/{id}",
|
|
37
|
+
name: "Job by id"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
const [listTool] = (0, mcp_resources_cjs_namespaceObject.createMCPResourceTools)(manager);
|
|
42
|
+
const output = await listTool.invoke({
|
|
43
|
+
includeTemplates: true
|
|
44
|
+
});
|
|
45
|
+
const parsed = JSON.parse(String(output));
|
|
46
|
+
(0, external_vitest_namespaceObject.expect)(listResources).toHaveBeenCalledWith([]);
|
|
47
|
+
(0, external_vitest_namespaceObject.expect)(listResourceTemplates).toHaveBeenCalledWith([]);
|
|
48
|
+
(0, external_vitest_namespaceObject.expect)(parsed.resourceCount).toBe(1);
|
|
49
|
+
(0, external_vitest_namespaceObject.expect)(parsed.resourceTemplateCount).toBe(1);
|
|
50
|
+
(0, external_vitest_namespaceObject.expect)(parsed.resources.fal[0].uri).toBe("fal://jobs/1");
|
|
51
|
+
});
|
|
52
|
+
(0, external_vitest_namespaceObject.it)("reads resource content without inlining binary blobs by default", async ()=>{
|
|
53
|
+
const { manager, readResource } = createManagerStub();
|
|
54
|
+
readResource.mockResolvedValue([
|
|
55
|
+
{
|
|
56
|
+
uri: "fal://jobs/1",
|
|
57
|
+
mimeType: "application/json",
|
|
58
|
+
text: "abcdefghijklmnopqrstuvwxyz",
|
|
59
|
+
blob: "YWJjZA=="
|
|
60
|
+
}
|
|
61
|
+
]);
|
|
62
|
+
const tools = (0, mcp_resources_cjs_namespaceObject.createMCPResourceTools)(manager);
|
|
63
|
+
const readTool = tools.find((entry)=>"mcp_read_resource" === entry.name);
|
|
64
|
+
if (!readTool) throw new Error("mcp_read_resource tool missing");
|
|
65
|
+
const output = await readTool.invoke({
|
|
66
|
+
server: "fal",
|
|
67
|
+
uri: "fal://jobs/1",
|
|
68
|
+
maxTextChars: 10
|
|
69
|
+
});
|
|
70
|
+
const parsed = JSON.parse(String(output));
|
|
71
|
+
(0, external_vitest_namespaceObject.expect)(readResource).toHaveBeenCalledWith("fal", "fal://jobs/1");
|
|
72
|
+
(0, external_vitest_namespaceObject.expect)(parsed.content[0].text).toBe("abcdefghij");
|
|
73
|
+
(0, external_vitest_namespaceObject.expect)(parsed.content[0].truncated).toBe(true);
|
|
74
|
+
(0, external_vitest_namespaceObject.expect)(parsed.content[0].blobLength).toBe(8);
|
|
75
|
+
(0, external_vitest_namespaceObject.expect)(parsed.content[0].blob).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
(0, external_vitest_namespaceObject.it)("can include binary blobs when requested", async ()=>{
|
|
78
|
+
const { manager, readResource } = createManagerStub();
|
|
79
|
+
readResource.mockResolvedValue([
|
|
80
|
+
{
|
|
81
|
+
uri: "fal://jobs/2",
|
|
82
|
+
mimeType: "image/png",
|
|
83
|
+
blob: "YWJjZA=="
|
|
84
|
+
}
|
|
85
|
+
]);
|
|
86
|
+
const tools = (0, mcp_resources_cjs_namespaceObject.createMCPResourceTools)(manager);
|
|
87
|
+
const readTool = tools.find((entry)=>"mcp_read_resource" === entry.name);
|
|
88
|
+
if (!readTool) throw new Error("mcp_read_resource tool missing");
|
|
89
|
+
const output = await readTool.invoke({
|
|
90
|
+
server: "fal",
|
|
91
|
+
uri: "fal://jobs/2",
|
|
92
|
+
includeBinary: true
|
|
93
|
+
});
|
|
94
|
+
const parsed = JSON.parse(String(output));
|
|
95
|
+
(0, external_vitest_namespaceObject.expect)(parsed.content[0].blob).toBe("YWJjZA==");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
99
|
+
Object.defineProperty(exports, '__esModule', {
|
|
100
|
+
value: true
|
|
101
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createMCPResourceTools } from "../tools/mcp_resources.js";
|
|
3
|
+
const createManagerStub = ()=>{
|
|
4
|
+
const listResources = vi.fn();
|
|
5
|
+
const listResourceTemplates = vi.fn();
|
|
6
|
+
const readResource = vi.fn();
|
|
7
|
+
const manager = {
|
|
8
|
+
listResources,
|
|
9
|
+
listResourceTemplates,
|
|
10
|
+
readResource
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
manager,
|
|
14
|
+
listResources,
|
|
15
|
+
listResourceTemplates,
|
|
16
|
+
readResource
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
describe("mcp resource tools", ()=>{
|
|
20
|
+
it("lists resources and templates", async ()=>{
|
|
21
|
+
const { manager, listResources, listResourceTemplates } = createManagerStub();
|
|
22
|
+
listResources.mockResolvedValue({
|
|
23
|
+
fal: [
|
|
24
|
+
{
|
|
25
|
+
uri: "fal://jobs/1",
|
|
26
|
+
name: "Job 1",
|
|
27
|
+
mimeType: "application/json"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
listResourceTemplates.mockResolvedValue({
|
|
32
|
+
fal: [
|
|
33
|
+
{
|
|
34
|
+
uriTemplate: "fal://jobs/{id}",
|
|
35
|
+
name: "Job by id"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
const [listTool] = createMCPResourceTools(manager);
|
|
40
|
+
const output = await listTool.invoke({
|
|
41
|
+
includeTemplates: true
|
|
42
|
+
});
|
|
43
|
+
const parsed = JSON.parse(String(output));
|
|
44
|
+
expect(listResources).toHaveBeenCalledWith([]);
|
|
45
|
+
expect(listResourceTemplates).toHaveBeenCalledWith([]);
|
|
46
|
+
expect(parsed.resourceCount).toBe(1);
|
|
47
|
+
expect(parsed.resourceTemplateCount).toBe(1);
|
|
48
|
+
expect(parsed.resources.fal[0].uri).toBe("fal://jobs/1");
|
|
49
|
+
});
|
|
50
|
+
it("reads resource content without inlining binary blobs by default", async ()=>{
|
|
51
|
+
const { manager, readResource } = createManagerStub();
|
|
52
|
+
readResource.mockResolvedValue([
|
|
53
|
+
{
|
|
54
|
+
uri: "fal://jobs/1",
|
|
55
|
+
mimeType: "application/json",
|
|
56
|
+
text: "abcdefghijklmnopqrstuvwxyz",
|
|
57
|
+
blob: "YWJjZA=="
|
|
58
|
+
}
|
|
59
|
+
]);
|
|
60
|
+
const tools = createMCPResourceTools(manager);
|
|
61
|
+
const readTool = tools.find((entry)=>"mcp_read_resource" === entry.name);
|
|
62
|
+
if (!readTool) throw new Error("mcp_read_resource tool missing");
|
|
63
|
+
const output = await readTool.invoke({
|
|
64
|
+
server: "fal",
|
|
65
|
+
uri: "fal://jobs/1",
|
|
66
|
+
maxTextChars: 10
|
|
67
|
+
});
|
|
68
|
+
const parsed = JSON.parse(String(output));
|
|
69
|
+
expect(readResource).toHaveBeenCalledWith("fal", "fal://jobs/1");
|
|
70
|
+
expect(parsed.content[0].text).toBe("abcdefghij");
|
|
71
|
+
expect(parsed.content[0].truncated).toBe(true);
|
|
72
|
+
expect(parsed.content[0].blobLength).toBe(8);
|
|
73
|
+
expect(parsed.content[0].blob).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
it("can include binary blobs when requested", async ()=>{
|
|
76
|
+
const { manager, readResource } = createManagerStub();
|
|
77
|
+
readResource.mockResolvedValue([
|
|
78
|
+
{
|
|
79
|
+
uri: "fal://jobs/2",
|
|
80
|
+
mimeType: "image/png",
|
|
81
|
+
blob: "YWJjZA=="
|
|
82
|
+
}
|
|
83
|
+
]);
|
|
84
|
+
const tools = createMCPResourceTools(manager);
|
|
85
|
+
const readTool = tools.find((entry)=>"mcp_read_resource" === entry.name);
|
|
86
|
+
if (!readTool) throw new Error("mcp_read_resource tool missing");
|
|
87
|
+
const output = await readTool.invoke({
|
|
88
|
+
server: "fal",
|
|
89
|
+
uri: "fal://jobs/2",
|
|
90
|
+
includeBinary: true
|
|
91
|
+
});
|
|
92
|
+
const parsed = JSON.parse(String(output));
|
|
93
|
+
expect(parsed.content[0].blob).toBe("YWJjZA==");
|
|
94
|
+
});
|
|
95
|
+
});
|