@wingman-ai/gateway 0.1.5 → 0.2.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/.wingman/agents/README.md +7 -0
- package/.wingman/agents/coding/agent.md +1 -0
- package/.wingman/agents/main/agent.md +1 -0
- package/.wingman/agents/researcher/agent.md +1 -0
- package/.wingman/agents/stock-trader/agent.md +1 -0
- package/dist/agent/config/agentConfig.cjs +12 -1
- package/dist/agent/config/agentConfig.d.ts +14 -0
- package/dist/agent/config/agentConfig.js +12 -1
- package/dist/agent/config/agentLoader.cjs +37 -1
- package/dist/agent/config/agentLoader.d.ts +2 -1
- package/dist/agent/config/agentLoader.js +37 -1
- package/dist/agent/config/modelFactory.cjs +2 -1
- package/dist/agent/config/modelFactory.js +2 -1
- package/dist/agent/config/toolRegistry.cjs +6 -4
- package/dist/agent/config/toolRegistry.d.ts +1 -0
- package/dist/agent/config/toolRegistry.js +6 -4
- package/dist/agent/middleware/additional-messages.cjs +8 -1
- package/dist/agent/middleware/additional-messages.d.ts +1 -0
- package/dist/agent/middleware/additional-messages.js +8 -1
- package/dist/agent/tests/agentConfig.test.cjs +25 -0
- package/dist/agent/tests/agentConfig.test.js +25 -0
- package/dist/agent/tests/agentLoader.test.cjs +18 -0
- package/dist/agent/tests/agentLoader.test.js +18 -0
- package/dist/agent/tests/modelFactory.test.cjs +13 -0
- package/dist/agent/tests/modelFactory.test.js +14 -1
- package/dist/agent/tests/toolRegistry.test.cjs +15 -0
- package/dist/agent/tests/toolRegistry.test.js +15 -0
- package/dist/agent/tools/code_search.cjs +1 -1
- package/dist/agent/tools/code_search.js +1 -1
- package/dist/agent/tools/command_execute.cjs +1 -1
- package/dist/agent/tools/command_execute.js +1 -1
- package/dist/agent/tools/ui_registry.d.ts +3 -3
- package/dist/cli/core/agentInvoker.cjs +212 -21
- package/dist/cli/core/agentInvoker.d.ts +55 -20
- package/dist/cli/core/agentInvoker.js +197 -21
- package/dist/cli/core/sessionManager.cjs +93 -4
- package/dist/cli/core/sessionManager.d.ts +1 -1
- package/dist/cli/core/sessionManager.js +93 -4
- package/dist/gateway/http/agents.cjs +121 -10
- package/dist/gateway/http/agents.js +121 -10
- package/dist/gateway/index.cjs +2 -2
- package/dist/gateway/server.cjs +55 -17
- package/dist/gateway/server.js +55 -17
- package/dist/gateway/types.d.ts +9 -1
- package/dist/tests/additionalMessageMiddleware.test.cjs +26 -0
- package/dist/tests/additionalMessageMiddleware.test.js +26 -0
- package/dist/tests/agentInvokerAttachments.test.cjs +123 -0
- package/dist/tests/agentInvokerAttachments.test.js +123 -0
- package/dist/tests/agentInvokerWorkdir.test.cjs +100 -0
- package/dist/tests/agentInvokerWorkdir.test.d.ts +1 -0
- package/dist/tests/agentInvokerWorkdir.test.js +72 -0
- package/dist/tests/agents-api.test.cjs +232 -0
- package/dist/tests/agents-api.test.d.ts +1 -0
- package/dist/tests/agents-api.test.js +226 -0
- package/dist/tests/gateway.test.cjs +21 -0
- package/dist/tests/gateway.test.js +21 -0
- package/dist/tests/sessionMessageAttachments.test.cjs +59 -0
- package/dist/tests/sessionMessageAttachments.test.js +59 -0
- package/dist/types/agents.d.ts +5 -0
- package/dist/webui/assets/index-BytPznA_.css +1 -0
- package/dist/webui/assets/index-u_5qlVip.js +176 -0
- package/dist/webui/index.html +2 -2
- package/package.json +3 -3
- package/.wingman/agents/wingman/agent.json +0 -12
- package/dist/webui/assets/index-CyE7T5pV.js +0 -162
- package/dist/webui/assets/index-DMEHdune.css +0 -1
package/dist/gateway/server.js
CHANGED
|
@@ -33,6 +33,21 @@ function _define_property(obj, key, value) {
|
|
|
33
33
|
else obj[key] = value;
|
|
34
34
|
return obj;
|
|
35
35
|
}
|
|
36
|
+
const API_CORS_HEADERS = {
|
|
37
|
+
"Access-Control-Allow-Origin": "*",
|
|
38
|
+
"Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS",
|
|
39
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Wingman-Token, X-Wingman-Password",
|
|
40
|
+
"Access-Control-Max-Age": "600"
|
|
41
|
+
};
|
|
42
|
+
function withApiCors(response) {
|
|
43
|
+
const headers = new Headers(response.headers);
|
|
44
|
+
for (const [key, value] of Object.entries(API_CORS_HEADERS))headers.set(key, value);
|
|
45
|
+
return new Response(response.body, {
|
|
46
|
+
status: response.status,
|
|
47
|
+
statusText: response.statusText,
|
|
48
|
+
headers
|
|
49
|
+
});
|
|
50
|
+
}
|
|
36
51
|
class GatewayServer {
|
|
37
52
|
async start() {
|
|
38
53
|
if (void 0 === globalThis.Bun) throw new Error("Gateway server requires Bun runtime. Start with `bun ./bin/wingman gateway start`.");
|
|
@@ -350,7 +365,10 @@ class GatewayServer {
|
|
|
350
365
|
this.broadcastSessionEvent(sessionKey, sessionMessage, ws);
|
|
351
366
|
this.broadcastToClients(sessionMessage, {
|
|
352
367
|
exclude: ws,
|
|
353
|
-
|
|
368
|
+
clientTypes: [
|
|
369
|
+
"webui",
|
|
370
|
+
"desktop"
|
|
371
|
+
],
|
|
354
372
|
skipSessionId: sessionKey
|
|
355
373
|
});
|
|
356
374
|
const outputManager = new OutputManager("interactive");
|
|
@@ -590,14 +608,18 @@ class GatewayServer {
|
|
|
590
608
|
}
|
|
591
609
|
broadcastToClients(message, options) {
|
|
592
610
|
let sent = 0;
|
|
593
|
-
for (const ws of this.connectedClients)
|
|
594
|
-
if (!options?.
|
|
595
|
-
if (options?.
|
|
596
|
-
|
|
597
|
-
|
|
611
|
+
for (const ws of this.connectedClients){
|
|
612
|
+
if (!options?.exclude || ws !== options.exclude) {
|
|
613
|
+
if (!options?.clientType || ws.data.clientType === options.clientType) {
|
|
614
|
+
if (!options?.clientTypes || !(options.clientTypes.length > 0) || options.clientTypes.includes(ws.data.clientType || "")) {
|
|
615
|
+
if (options?.skipSessionId) {
|
|
616
|
+
const subscribers = this.sessionSubscriptions.get(options.skipSessionId);
|
|
617
|
+
if (subscribers?.has(ws)) continue;
|
|
618
|
+
}
|
|
619
|
+
this.sendMessage(ws, message);
|
|
620
|
+
sent++;
|
|
621
|
+
}
|
|
598
622
|
}
|
|
599
|
-
this.sendMessage(ws, message);
|
|
600
|
-
sent++;
|
|
601
623
|
}
|
|
602
624
|
}
|
|
603
625
|
return sent;
|
|
@@ -846,6 +868,9 @@ class GatewayServer {
|
|
|
846
868
|
const webhookResponse = await handleWebhookInvoke(ctx, this.webhookStore, req, url);
|
|
847
869
|
if (webhookResponse) return webhookResponse;
|
|
848
870
|
if (url.pathname.startsWith("/api/")) {
|
|
871
|
+
if ("OPTIONS" === req.method) return withApiCors(new Response(null, {
|
|
872
|
+
status: 204
|
|
873
|
+
}));
|
|
849
874
|
if ("/api/config" === url.pathname) {
|
|
850
875
|
const agents = this.wingmanConfig.agents?.list?.map((agent)=>({
|
|
851
876
|
id: agent.id,
|
|
@@ -853,7 +878,7 @@ class GatewayServer {
|
|
|
853
878
|
default: agent.default
|
|
854
879
|
})) || [];
|
|
855
880
|
const defaultAgentId = this.router.selectAgent();
|
|
856
|
-
return new Response(JSON.stringify({
|
|
881
|
+
return withApiCors(new Response(JSON.stringify({
|
|
857
882
|
gatewayHost: this.config.host,
|
|
858
883
|
gatewayPort: this.config.port,
|
|
859
884
|
requireAuth: this.auth.isAuthRequired(),
|
|
@@ -866,15 +891,15 @@ class GatewayServer {
|
|
|
866
891
|
headers: {
|
|
867
892
|
"Content-Type": "application/json"
|
|
868
893
|
}
|
|
869
|
-
});
|
|
894
|
+
}));
|
|
870
895
|
}
|
|
871
896
|
const apiResponse = await handleWebhooksApi(ctx, this.webhookStore, req, url) || await handleRoutinesApi(ctx, this.routineStore, req, url) || await handleAgentsApi(ctx, req, url) || await handleProvidersApi(ctx, req, url) || await handleVoiceApi(ctx, req, url) || await handleFsApi(ctx, req, url) || await handleSessionsApi(ctx, req, url);
|
|
872
|
-
if (apiResponse) return apiResponse;
|
|
873
|
-
if ("/api/health" === url.pathname) return this.handleHealthCheck();
|
|
874
|
-
if ("/api/stats" === url.pathname) return this.handleStats();
|
|
875
|
-
return new Response("Not Found", {
|
|
897
|
+
if (apiResponse) return withApiCors(apiResponse);
|
|
898
|
+
if ("/api/health" === url.pathname) return withApiCors(this.handleHealthCheck());
|
|
899
|
+
if ("/api/stats" === url.pathname) return withApiCors(this.handleStats());
|
|
900
|
+
return withApiCors(new Response("Not Found", {
|
|
876
901
|
status: 404
|
|
877
|
-
});
|
|
902
|
+
}));
|
|
878
903
|
}
|
|
879
904
|
if ("GET" !== req.method) return new Response("Method Not Allowed", {
|
|
880
905
|
status: 405
|
|
@@ -1127,11 +1152,20 @@ class GatewayServer {
|
|
|
1127
1152
|
}
|
|
1128
1153
|
function buildAttachmentPreview(attachments) {
|
|
1129
1154
|
if (!attachments || 0 === attachments.length) return "Attachment";
|
|
1155
|
+
let hasFile = false;
|
|
1130
1156
|
let hasAudio = false;
|
|
1131
1157
|
let hasImage = false;
|
|
1132
|
-
for (const attachment of attachments)
|
|
1133
|
-
|
|
1158
|
+
for (const attachment of attachments){
|
|
1159
|
+
if (isFileAttachment(attachment)) {
|
|
1160
|
+
hasFile = true;
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (isAudioAttachment(attachment)) hasAudio = true;
|
|
1164
|
+
else hasImage = true;
|
|
1165
|
+
}
|
|
1134
1166
|
const count = attachments.length;
|
|
1167
|
+
if (hasFile && (hasAudio || hasImage)) return count > 1 ? "File and media attachments" : "File and media attachment";
|
|
1168
|
+
if (hasFile) return count > 1 ? "File attachments" : "File attachment";
|
|
1135
1169
|
if (hasAudio && hasImage) return count > 1 ? "Media attachments" : "Media attachment";
|
|
1136
1170
|
if (hasAudio) return count > 1 ? "Audio attachments" : "Audio attachment";
|
|
1137
1171
|
return count > 1 ? "Image attachments" : "Image attachment";
|
|
@@ -1142,4 +1176,8 @@ function isAudioAttachment(attachment) {
|
|
|
1142
1176
|
if (attachment.dataUrl?.startsWith("data:audio/")) return true;
|
|
1143
1177
|
return false;
|
|
1144
1178
|
}
|
|
1179
|
+
function isFileAttachment(attachment) {
|
|
1180
|
+
if ("file" === attachment.kind) return true;
|
|
1181
|
+
return "string" == typeof attachment.textContent;
|
|
1182
|
+
}
|
|
1145
1183
|
export { GatewayServer };
|
package/dist/gateway/types.d.ts
CHANGED
|
@@ -45,7 +45,15 @@ export interface AudioAttachment {
|
|
|
45
45
|
name?: string;
|
|
46
46
|
size?: number;
|
|
47
47
|
}
|
|
48
|
-
export
|
|
48
|
+
export interface FileAttachment {
|
|
49
|
+
kind: "file";
|
|
50
|
+
dataUrl: string;
|
|
51
|
+
textContent: string;
|
|
52
|
+
mimeType?: string;
|
|
53
|
+
name?: string;
|
|
54
|
+
size?: number;
|
|
55
|
+
}
|
|
56
|
+
export type MediaAttachment = ImageAttachment | AudioAttachment | FileAttachment;
|
|
49
57
|
export interface AgentRequestPayload {
|
|
50
58
|
agentId?: string;
|
|
51
59
|
content?: string;
|
|
@@ -60,6 +60,9 @@ const additional_messages_cjs_namespaceObject = require("../agent/middleware/add
|
|
|
60
60
|
const content = injected.content ?? "";
|
|
61
61
|
(0, external_vitest_namespaceObject.expect)(content).toContain("Confidentiality");
|
|
62
62
|
(0, external_vitest_namespaceObject.expect)(content).toContain("Do not disclose");
|
|
63
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("Working Directory");
|
|
64
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("current working directory");
|
|
65
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("Use relative paths");
|
|
63
66
|
(0, external_vitest_namespaceObject.expect)(content).not.toContain("Operating System:");
|
|
64
67
|
(0, external_vitest_namespaceObject.expect)(content).not.toContain("Architecture:");
|
|
65
68
|
(0, external_vitest_namespaceObject.expect)(content).not.toContain("Default Shell:");
|
|
@@ -68,6 +71,29 @@ const additional_messages_cjs_namespaceObject = require("../agent/middleware/add
|
|
|
68
71
|
(0, external_vitest_namespaceObject.expect)(content).not.toContain(workdir);
|
|
69
72
|
(0, external_vitest_namespaceObject.expect)(content).not.toContain(workspaceRoot);
|
|
70
73
|
});
|
|
74
|
+
(0, external_vitest_namespaceObject.it)("uses virtual output path when workdir is outside the workspace root", async ()=>{
|
|
75
|
+
const workspaceRoot = external_node_path_default().resolve("repo");
|
|
76
|
+
const workdir = external_node_path_default().resolve("external-output");
|
|
77
|
+
const middleware = (0, additional_messages_cjs_namespaceObject.additionalMessageMiddleware)({
|
|
78
|
+
workspaceRoot,
|
|
79
|
+
workdir,
|
|
80
|
+
outputVirtualPath: "/workdir/"
|
|
81
|
+
});
|
|
82
|
+
const input = {
|
|
83
|
+
messages: [
|
|
84
|
+
new external_langchain_namespaceObject.HumanMessage("Hello")
|
|
85
|
+
]
|
|
86
|
+
};
|
|
87
|
+
const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
|
|
88
|
+
if (!beforeAgent) throw new Error("beforeAgent hook not configured");
|
|
89
|
+
const result = await beforeAgent(input, {});
|
|
90
|
+
const injected = result.messages[0];
|
|
91
|
+
const content = injected.content ?? "";
|
|
92
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("session output directory");
|
|
93
|
+
(0, external_vitest_namespaceObject.expect)(content).toContain("/workdir/");
|
|
94
|
+
(0, external_vitest_namespaceObject.expect)(content).not.toContain("(path hidden)");
|
|
95
|
+
(0, external_vitest_namespaceObject.expect)(content).not.toContain(workdir);
|
|
96
|
+
});
|
|
71
97
|
(0, external_vitest_namespaceObject.it)("does not inject the additional message twice", async ()=>{
|
|
72
98
|
const middleware = (0, additional_messages_cjs_namespaceObject.additionalMessageMiddleware)();
|
|
73
99
|
const input = {
|
|
@@ -36,6 +36,9 @@ describe("additionalMessageMiddleware", ()=>{
|
|
|
36
36
|
const content = injected.content ?? "";
|
|
37
37
|
expect(content).toContain("Confidentiality");
|
|
38
38
|
expect(content).toContain("Do not disclose");
|
|
39
|
+
expect(content).toContain("Working Directory");
|
|
40
|
+
expect(content).toContain("current working directory");
|
|
41
|
+
expect(content).toContain("Use relative paths");
|
|
39
42
|
expect(content).not.toContain("Operating System:");
|
|
40
43
|
expect(content).not.toContain("Architecture:");
|
|
41
44
|
expect(content).not.toContain("Default Shell:");
|
|
@@ -44,6 +47,29 @@ describe("additionalMessageMiddleware", ()=>{
|
|
|
44
47
|
expect(content).not.toContain(workdir);
|
|
45
48
|
expect(content).not.toContain(workspaceRoot);
|
|
46
49
|
});
|
|
50
|
+
it("uses virtual output path when workdir is outside the workspace root", async ()=>{
|
|
51
|
+
const workspaceRoot = node_path.resolve("repo");
|
|
52
|
+
const workdir = node_path.resolve("external-output");
|
|
53
|
+
const middleware = additionalMessageMiddleware({
|
|
54
|
+
workspaceRoot,
|
|
55
|
+
workdir,
|
|
56
|
+
outputVirtualPath: "/workdir/"
|
|
57
|
+
});
|
|
58
|
+
const input = {
|
|
59
|
+
messages: [
|
|
60
|
+
new HumanMessage("Hello")
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
const beforeAgent = "function" == typeof middleware.beforeAgent ? middleware.beforeAgent : middleware.beforeAgent?.hook;
|
|
64
|
+
if (!beforeAgent) throw new Error("beforeAgent hook not configured");
|
|
65
|
+
const result = await beforeAgent(input, {});
|
|
66
|
+
const injected = result.messages[0];
|
|
67
|
+
const content = injected.content ?? "";
|
|
68
|
+
expect(content).toContain("session output directory");
|
|
69
|
+
expect(content).toContain("/workdir/");
|
|
70
|
+
expect(content).not.toContain("(path hidden)");
|
|
71
|
+
expect(content).not.toContain(workdir);
|
|
72
|
+
});
|
|
47
73
|
it("does not inject the additional message twice", async ()=>{
|
|
48
74
|
const middleware = additionalMessageMiddleware();
|
|
49
75
|
const input = {
|
|
@@ -60,6 +60,129 @@ const agentInvoker_cjs_namespaceObject = require("../cli/core/agentInvoker.cjs")
|
|
|
60
60
|
}
|
|
61
61
|
]);
|
|
62
62
|
});
|
|
63
|
+
(0, external_vitest_namespaceObject.it)("builds text parts for extracted file attachments", ()=>{
|
|
64
|
+
const result = (0, agentInvoker_cjs_namespaceObject.buildUserContent)("Review this", [
|
|
65
|
+
{
|
|
66
|
+
kind: "file",
|
|
67
|
+
dataUrl: "",
|
|
68
|
+
name: "notes.md",
|
|
69
|
+
mimeType: "text/markdown",
|
|
70
|
+
textContent: "# Notes\n- keep media uploads working"
|
|
71
|
+
}
|
|
72
|
+
]);
|
|
73
|
+
(0, external_vitest_namespaceObject.expect)(Array.isArray(result)).toBe(true);
|
|
74
|
+
(0, external_vitest_namespaceObject.expect)(result).toEqual([
|
|
75
|
+
{
|
|
76
|
+
type: "text",
|
|
77
|
+
text: "Review this"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: "[Attached file: notes.md (text/markdown)]\n# Notes\n- keep media uploads working"
|
|
82
|
+
}
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
(0, external_vitest_namespaceObject.it)("falls back when file attachment has no extracted text", ()=>{
|
|
86
|
+
const result = (0, agentInvoker_cjs_namespaceObject.buildUserContent)("", [
|
|
87
|
+
{
|
|
88
|
+
kind: "file",
|
|
89
|
+
dataUrl: "",
|
|
90
|
+
name: "scan.pdf",
|
|
91
|
+
mimeType: "application/pdf",
|
|
92
|
+
textContent: " "
|
|
93
|
+
}
|
|
94
|
+
]);
|
|
95
|
+
(0, external_vitest_namespaceObject.expect)(Array.isArray(result)).toBe(true);
|
|
96
|
+
(0, external_vitest_namespaceObject.expect)(result).toEqual([
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: "[Attached file: scan.pdf (application/pdf)]\n[No extractable text content provided.]"
|
|
100
|
+
}
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
(0, external_vitest_namespaceObject.it)("uses responses input_file blocks for pdfs when responses api is enabled", ()=>{
|
|
104
|
+
const result = (0, agentInvoker_cjs_namespaceObject.buildUserContent)("Summarize this", [
|
|
105
|
+
{
|
|
106
|
+
kind: "file",
|
|
107
|
+
dataUrl: "data:application/pdf;base64,JVBERi0xLjQK",
|
|
108
|
+
name: "report.pdf",
|
|
109
|
+
mimeType: "application/pdf",
|
|
110
|
+
textContent: "fallback text"
|
|
111
|
+
}
|
|
112
|
+
], {
|
|
113
|
+
profile: {
|
|
114
|
+
pdfInputs: true
|
|
115
|
+
},
|
|
116
|
+
useResponsesApi: true
|
|
117
|
+
});
|
|
118
|
+
(0, external_vitest_namespaceObject.expect)(Array.isArray(result)).toBe(true);
|
|
119
|
+
(0, external_vitest_namespaceObject.expect)(result).toEqual([
|
|
120
|
+
{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: "Summarize this"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
type: "input_file",
|
|
126
|
+
file_data: "data:application/pdf;base64,JVBERi0xLjQK",
|
|
127
|
+
filename: "report.pdf"
|
|
128
|
+
}
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
(0, external_vitest_namespaceObject.it)("uses legacy file blocks for pdfs when responses api is not enabled", ()=>{
|
|
132
|
+
const result = (0, agentInvoker_cjs_namespaceObject.buildUserContent)("Summarize this", [
|
|
133
|
+
{
|
|
134
|
+
kind: "file",
|
|
135
|
+
dataUrl: "data:application/pdf;base64,JVBERi0xLjQK",
|
|
136
|
+
name: "report.pdf",
|
|
137
|
+
mimeType: "application/pdf",
|
|
138
|
+
textContent: "fallback text"
|
|
139
|
+
}
|
|
140
|
+
], {
|
|
141
|
+
profile: {
|
|
142
|
+
pdfInputs: true
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
(0, external_vitest_namespaceObject.expect)(Array.isArray(result)).toBe(true);
|
|
146
|
+
(0, external_vitest_namespaceObject.expect)(result).toEqual([
|
|
147
|
+
{
|
|
148
|
+
type: "text",
|
|
149
|
+
text: "Summarize this"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: "file",
|
|
153
|
+
source_type: "base64",
|
|
154
|
+
mime_type: "application/pdf",
|
|
155
|
+
data: "JVBERi0xLjQK",
|
|
156
|
+
metadata: {
|
|
157
|
+
filename: "report.pdf",
|
|
158
|
+
name: "report.pdf",
|
|
159
|
+
title: "report.pdf"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
]);
|
|
163
|
+
});
|
|
164
|
+
(0, external_vitest_namespaceObject.it)("falls back to extracted text when native pdf data is missing", ()=>{
|
|
165
|
+
const result = (0, agentInvoker_cjs_namespaceObject.buildUserContent)("", [
|
|
166
|
+
{
|
|
167
|
+
kind: "file",
|
|
168
|
+
dataUrl: "",
|
|
169
|
+
name: "report.pdf",
|
|
170
|
+
mimeType: "application/pdf",
|
|
171
|
+
textContent: "Parsed fallback content."
|
|
172
|
+
}
|
|
173
|
+
], {
|
|
174
|
+
profile: {
|
|
175
|
+
pdfInputs: true
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
(0, external_vitest_namespaceObject.expect)(Array.isArray(result)).toBe(true);
|
|
179
|
+
(0, external_vitest_namespaceObject.expect)(result).toEqual([
|
|
180
|
+
{
|
|
181
|
+
type: "text",
|
|
182
|
+
text: "[Attached file: report.pdf (application/pdf)]\nParsed fallback content."
|
|
183
|
+
}
|
|
184
|
+
]);
|
|
185
|
+
});
|
|
63
186
|
});
|
|
64
187
|
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
65
188
|
Object.defineProperty(exports, '__esModule', {
|
|
@@ -58,4 +58,127 @@ describe("buildUserContent", ()=>{
|
|
|
58
58
|
}
|
|
59
59
|
]);
|
|
60
60
|
});
|
|
61
|
+
it("builds text parts for extracted file attachments", ()=>{
|
|
62
|
+
const result = buildUserContent("Review this", [
|
|
63
|
+
{
|
|
64
|
+
kind: "file",
|
|
65
|
+
dataUrl: "",
|
|
66
|
+
name: "notes.md",
|
|
67
|
+
mimeType: "text/markdown",
|
|
68
|
+
textContent: "# Notes\n- keep media uploads working"
|
|
69
|
+
}
|
|
70
|
+
]);
|
|
71
|
+
expect(Array.isArray(result)).toBe(true);
|
|
72
|
+
expect(result).toEqual([
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: "Review this"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: "text",
|
|
79
|
+
text: "[Attached file: notes.md (text/markdown)]\n# Notes\n- keep media uploads working"
|
|
80
|
+
}
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
it("falls back when file attachment has no extracted text", ()=>{
|
|
84
|
+
const result = buildUserContent("", [
|
|
85
|
+
{
|
|
86
|
+
kind: "file",
|
|
87
|
+
dataUrl: "",
|
|
88
|
+
name: "scan.pdf",
|
|
89
|
+
mimeType: "application/pdf",
|
|
90
|
+
textContent: " "
|
|
91
|
+
}
|
|
92
|
+
]);
|
|
93
|
+
expect(Array.isArray(result)).toBe(true);
|
|
94
|
+
expect(result).toEqual([
|
|
95
|
+
{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: "[Attached file: scan.pdf (application/pdf)]\n[No extractable text content provided.]"
|
|
98
|
+
}
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
it("uses responses input_file blocks for pdfs when responses api is enabled", ()=>{
|
|
102
|
+
const result = buildUserContent("Summarize this", [
|
|
103
|
+
{
|
|
104
|
+
kind: "file",
|
|
105
|
+
dataUrl: "data:application/pdf;base64,JVBERi0xLjQK",
|
|
106
|
+
name: "report.pdf",
|
|
107
|
+
mimeType: "application/pdf",
|
|
108
|
+
textContent: "fallback text"
|
|
109
|
+
}
|
|
110
|
+
], {
|
|
111
|
+
profile: {
|
|
112
|
+
pdfInputs: true
|
|
113
|
+
},
|
|
114
|
+
useResponsesApi: true
|
|
115
|
+
});
|
|
116
|
+
expect(Array.isArray(result)).toBe(true);
|
|
117
|
+
expect(result).toEqual([
|
|
118
|
+
{
|
|
119
|
+
type: "text",
|
|
120
|
+
text: "Summarize this"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: "input_file",
|
|
124
|
+
file_data: "data:application/pdf;base64,JVBERi0xLjQK",
|
|
125
|
+
filename: "report.pdf"
|
|
126
|
+
}
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
it("uses legacy file blocks for pdfs when responses api is not enabled", ()=>{
|
|
130
|
+
const result = buildUserContent("Summarize this", [
|
|
131
|
+
{
|
|
132
|
+
kind: "file",
|
|
133
|
+
dataUrl: "data:application/pdf;base64,JVBERi0xLjQK",
|
|
134
|
+
name: "report.pdf",
|
|
135
|
+
mimeType: "application/pdf",
|
|
136
|
+
textContent: "fallback text"
|
|
137
|
+
}
|
|
138
|
+
], {
|
|
139
|
+
profile: {
|
|
140
|
+
pdfInputs: true
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
expect(Array.isArray(result)).toBe(true);
|
|
144
|
+
expect(result).toEqual([
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: "Summarize this"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
type: "file",
|
|
151
|
+
source_type: "base64",
|
|
152
|
+
mime_type: "application/pdf",
|
|
153
|
+
data: "JVBERi0xLjQK",
|
|
154
|
+
metadata: {
|
|
155
|
+
filename: "report.pdf",
|
|
156
|
+
name: "report.pdf",
|
|
157
|
+
title: "report.pdf"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
]);
|
|
161
|
+
});
|
|
162
|
+
it("falls back to extracted text when native pdf data is missing", ()=>{
|
|
163
|
+
const result = buildUserContent("", [
|
|
164
|
+
{
|
|
165
|
+
kind: "file",
|
|
166
|
+
dataUrl: "",
|
|
167
|
+
name: "report.pdf",
|
|
168
|
+
mimeType: "application/pdf",
|
|
169
|
+
textContent: "Parsed fallback content."
|
|
170
|
+
}
|
|
171
|
+
], {
|
|
172
|
+
profile: {
|
|
173
|
+
pdfInputs: true
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
expect(Array.isArray(result)).toBe(true);
|
|
177
|
+
expect(result).toEqual([
|
|
178
|
+
{
|
|
179
|
+
type: "text",
|
|
180
|
+
text: "[Attached file: report.pdf (application/pdf)]\nParsed fallback content."
|
|
181
|
+
}
|
|
182
|
+
]);
|
|
183
|
+
});
|
|
61
184
|
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.n = (module)=>{
|
|
5
|
+
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
6
|
+
__webpack_require__.d(getter, {
|
|
7
|
+
a: getter
|
|
8
|
+
});
|
|
9
|
+
return getter;
|
|
10
|
+
};
|
|
11
|
+
})();
|
|
12
|
+
(()=>{
|
|
13
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
14
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: definition[key]
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
})();
|
|
20
|
+
(()=>{
|
|
21
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
22
|
+
})();
|
|
23
|
+
var __webpack_exports__ = {};
|
|
24
|
+
const external_node_path_namespaceObject = require("node:path");
|
|
25
|
+
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
26
|
+
const external_vitest_namespaceObject = require("vitest");
|
|
27
|
+
const agentInvoker_cjs_namespaceObject = require("../cli/core/agentInvoker.cjs");
|
|
28
|
+
(0, external_vitest_namespaceObject.describe)("resolveExternalOutputMount", ()=>{
|
|
29
|
+
const workspace = external_node_path_default().resolve("workspace");
|
|
30
|
+
(0, external_vitest_namespaceObject.it)("mounts external workdir paths", ()=>{
|
|
31
|
+
const workdir = external_node_path_default().resolve("outside", "session-output");
|
|
32
|
+
const mount = (0, agentInvoker_cjs_namespaceObject.resolveExternalOutputMount)(workspace, workdir, null);
|
|
33
|
+
(0, external_vitest_namespaceObject.expect)(mount).toEqual({
|
|
34
|
+
virtualPath: agentInvoker_cjs_namespaceObject.WORKDIR_VIRTUAL_PATH,
|
|
35
|
+
absolutePath: workdir
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
(0, external_vitest_namespaceObject.it)("does not mount workdir when it is inside workspace", ()=>{
|
|
39
|
+
const workdir = external_node_path_default().join(workspace, "outputs");
|
|
40
|
+
const mount = (0, agentInvoker_cjs_namespaceObject.resolveExternalOutputMount)(workspace, workdir, null);
|
|
41
|
+
(0, external_vitest_namespaceObject.expect)(mount).toEqual({
|
|
42
|
+
virtualPath: null,
|
|
43
|
+
absolutePath: null
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
(0, external_vitest_namespaceObject.it)("mounts external default output when no workdir is set", ()=>{
|
|
47
|
+
const defaultOutputDir = external_node_path_default().resolve("external-default-output");
|
|
48
|
+
const mount = (0, agentInvoker_cjs_namespaceObject.resolveExternalOutputMount)(workspace, null, defaultOutputDir);
|
|
49
|
+
(0, external_vitest_namespaceObject.expect)(mount).toEqual({
|
|
50
|
+
virtualPath: agentInvoker_cjs_namespaceObject.OUTPUT_VIRTUAL_PATH,
|
|
51
|
+
absolutePath: defaultOutputDir
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
(0, external_vitest_namespaceObject.it)("prefers workdir mount over default output mount", ()=>{
|
|
55
|
+
const workdir = external_node_path_default().resolve("outside", "session-output");
|
|
56
|
+
const defaultOutputDir = external_node_path_default().resolve("external-default-output");
|
|
57
|
+
const mount = (0, agentInvoker_cjs_namespaceObject.resolveExternalOutputMount)(workspace, workdir, defaultOutputDir);
|
|
58
|
+
(0, external_vitest_namespaceObject.expect)(mount).toEqual({
|
|
59
|
+
virtualPath: agentInvoker_cjs_namespaceObject.WORKDIR_VIRTUAL_PATH,
|
|
60
|
+
absolutePath: workdir
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
(0, external_vitest_namespaceObject.it)("does not request an extra external mount when workdir is execution workspace", ()=>{
|
|
64
|
+
const executionWorkspace = external_node_path_default().resolve("outside", "session-output");
|
|
65
|
+
const mount = (0, agentInvoker_cjs_namespaceObject.resolveExternalOutputMount)(executionWorkspace, executionWorkspace, external_node_path_default().resolve("external-default-output"));
|
|
66
|
+
(0, external_vitest_namespaceObject.expect)(mount).toEqual({
|
|
67
|
+
virtualPath: null,
|
|
68
|
+
absolutePath: null
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
(0, external_vitest_namespaceObject.describe)("resolveExecutionWorkspace", ()=>{
|
|
73
|
+
const workspace = external_node_path_default().resolve("workspace");
|
|
74
|
+
(0, external_vitest_namespaceObject.it)("uses workspace when workdir is not set", ()=>{
|
|
75
|
+
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.resolveExecutionWorkspace)(workspace, null)).toBe(external_node_path_default().normalize(workspace));
|
|
76
|
+
});
|
|
77
|
+
(0, external_vitest_namespaceObject.it)("resolves relative workdir from workspace", ()=>{
|
|
78
|
+
const resolved = (0, agentInvoker_cjs_namespaceObject.resolveExecutionWorkspace)(workspace, "outputs/session");
|
|
79
|
+
(0, external_vitest_namespaceObject.expect)(resolved).toBe(external_node_path_default().normalize(external_node_path_default().join(workspace, "outputs/session")));
|
|
80
|
+
});
|
|
81
|
+
(0, external_vitest_namespaceObject.it)("uses absolute workdir directly", ()=>{
|
|
82
|
+
const absolute = external_node_path_default().resolve("outside", "session-output");
|
|
83
|
+
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.resolveExecutionWorkspace)(workspace, absolute)).toBe(external_node_path_default().normalize(absolute));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
(0, external_vitest_namespaceObject.describe)("toWorkspaceAliasVirtualPath", ()=>{
|
|
87
|
+
(0, external_vitest_namespaceObject.it)("builds an alias path for absolute workspaces", ()=>{
|
|
88
|
+
const absolute = external_node_path_default().resolve("outside", "session-output");
|
|
89
|
+
const alias = (0, agentInvoker_cjs_namespaceObject.toWorkspaceAliasVirtualPath)(absolute);
|
|
90
|
+
const expected = `/${absolute.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "")}/`;
|
|
91
|
+
(0, external_vitest_namespaceObject.expect)(alias).toBe(expected);
|
|
92
|
+
});
|
|
93
|
+
(0, external_vitest_namespaceObject.it)("returns null for non-absolute workspace paths", ()=>{
|
|
94
|
+
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.toWorkspaceAliasVirtualPath)("relative/workspace")).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
98
|
+
Object.defineProperty(exports, '__esModule', {
|
|
99
|
+
value: true
|
|
100
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import node_path from "node:path";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { OUTPUT_VIRTUAL_PATH, WORKDIR_VIRTUAL_PATH, resolveExecutionWorkspace, resolveExternalOutputMount, toWorkspaceAliasVirtualPath } from "../cli/core/agentInvoker.js";
|
|
4
|
+
describe("resolveExternalOutputMount", ()=>{
|
|
5
|
+
const workspace = node_path.resolve("workspace");
|
|
6
|
+
it("mounts external workdir paths", ()=>{
|
|
7
|
+
const workdir = node_path.resolve("outside", "session-output");
|
|
8
|
+
const mount = resolveExternalOutputMount(workspace, workdir, null);
|
|
9
|
+
expect(mount).toEqual({
|
|
10
|
+
virtualPath: WORKDIR_VIRTUAL_PATH,
|
|
11
|
+
absolutePath: workdir
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
it("does not mount workdir when it is inside workspace", ()=>{
|
|
15
|
+
const workdir = node_path.join(workspace, "outputs");
|
|
16
|
+
const mount = resolveExternalOutputMount(workspace, workdir, null);
|
|
17
|
+
expect(mount).toEqual({
|
|
18
|
+
virtualPath: null,
|
|
19
|
+
absolutePath: null
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
it("mounts external default output when no workdir is set", ()=>{
|
|
23
|
+
const defaultOutputDir = node_path.resolve("external-default-output");
|
|
24
|
+
const mount = resolveExternalOutputMount(workspace, null, defaultOutputDir);
|
|
25
|
+
expect(mount).toEqual({
|
|
26
|
+
virtualPath: OUTPUT_VIRTUAL_PATH,
|
|
27
|
+
absolutePath: defaultOutputDir
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
it("prefers workdir mount over default output mount", ()=>{
|
|
31
|
+
const workdir = node_path.resolve("outside", "session-output");
|
|
32
|
+
const defaultOutputDir = node_path.resolve("external-default-output");
|
|
33
|
+
const mount = resolveExternalOutputMount(workspace, workdir, defaultOutputDir);
|
|
34
|
+
expect(mount).toEqual({
|
|
35
|
+
virtualPath: WORKDIR_VIRTUAL_PATH,
|
|
36
|
+
absolutePath: workdir
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
it("does not request an extra external mount when workdir is execution workspace", ()=>{
|
|
40
|
+
const executionWorkspace = node_path.resolve("outside", "session-output");
|
|
41
|
+
const mount = resolveExternalOutputMount(executionWorkspace, executionWorkspace, node_path.resolve("external-default-output"));
|
|
42
|
+
expect(mount).toEqual({
|
|
43
|
+
virtualPath: null,
|
|
44
|
+
absolutePath: null
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe("resolveExecutionWorkspace", ()=>{
|
|
49
|
+
const workspace = node_path.resolve("workspace");
|
|
50
|
+
it("uses workspace when workdir is not set", ()=>{
|
|
51
|
+
expect(resolveExecutionWorkspace(workspace, null)).toBe(node_path.normalize(workspace));
|
|
52
|
+
});
|
|
53
|
+
it("resolves relative workdir from workspace", ()=>{
|
|
54
|
+
const resolved = resolveExecutionWorkspace(workspace, "outputs/session");
|
|
55
|
+
expect(resolved).toBe(node_path.normalize(node_path.join(workspace, "outputs/session")));
|
|
56
|
+
});
|
|
57
|
+
it("uses absolute workdir directly", ()=>{
|
|
58
|
+
const absolute = node_path.resolve("outside", "session-output");
|
|
59
|
+
expect(resolveExecutionWorkspace(workspace, absolute)).toBe(node_path.normalize(absolute));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe("toWorkspaceAliasVirtualPath", ()=>{
|
|
63
|
+
it("builds an alias path for absolute workspaces", ()=>{
|
|
64
|
+
const absolute = node_path.resolve("outside", "session-output");
|
|
65
|
+
const alias = toWorkspaceAliasVirtualPath(absolute);
|
|
66
|
+
const expected = `/${absolute.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "")}/`;
|
|
67
|
+
expect(alias).toBe(expected);
|
|
68
|
+
});
|
|
69
|
+
it("returns null for non-absolute workspace paths", ()=>{
|
|
70
|
+
expect(toWorkspaceAliasVirtualPath("relative/workspace")).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
});
|