@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.
Files changed (66) hide show
  1. package/.wingman/agents/README.md +7 -0
  2. package/.wingman/agents/coding/agent.md +1 -0
  3. package/.wingman/agents/main/agent.md +1 -0
  4. package/.wingman/agents/researcher/agent.md +1 -0
  5. package/.wingman/agents/stock-trader/agent.md +1 -0
  6. package/dist/agent/config/agentConfig.cjs +12 -1
  7. package/dist/agent/config/agentConfig.d.ts +14 -0
  8. package/dist/agent/config/agentConfig.js +12 -1
  9. package/dist/agent/config/agentLoader.cjs +37 -1
  10. package/dist/agent/config/agentLoader.d.ts +2 -1
  11. package/dist/agent/config/agentLoader.js +37 -1
  12. package/dist/agent/config/modelFactory.cjs +2 -1
  13. package/dist/agent/config/modelFactory.js +2 -1
  14. package/dist/agent/config/toolRegistry.cjs +6 -4
  15. package/dist/agent/config/toolRegistry.d.ts +1 -0
  16. package/dist/agent/config/toolRegistry.js +6 -4
  17. package/dist/agent/middleware/additional-messages.cjs +8 -1
  18. package/dist/agent/middleware/additional-messages.d.ts +1 -0
  19. package/dist/agent/middleware/additional-messages.js +8 -1
  20. package/dist/agent/tests/agentConfig.test.cjs +25 -0
  21. package/dist/agent/tests/agentConfig.test.js +25 -0
  22. package/dist/agent/tests/agentLoader.test.cjs +18 -0
  23. package/dist/agent/tests/agentLoader.test.js +18 -0
  24. package/dist/agent/tests/modelFactory.test.cjs +13 -0
  25. package/dist/agent/tests/modelFactory.test.js +14 -1
  26. package/dist/agent/tests/toolRegistry.test.cjs +15 -0
  27. package/dist/agent/tests/toolRegistry.test.js +15 -0
  28. package/dist/agent/tools/code_search.cjs +1 -1
  29. package/dist/agent/tools/code_search.js +1 -1
  30. package/dist/agent/tools/command_execute.cjs +1 -1
  31. package/dist/agent/tools/command_execute.js +1 -1
  32. package/dist/agent/tools/ui_registry.d.ts +3 -3
  33. package/dist/cli/core/agentInvoker.cjs +212 -21
  34. package/dist/cli/core/agentInvoker.d.ts +55 -20
  35. package/dist/cli/core/agentInvoker.js +197 -21
  36. package/dist/cli/core/sessionManager.cjs +93 -4
  37. package/dist/cli/core/sessionManager.d.ts +1 -1
  38. package/dist/cli/core/sessionManager.js +93 -4
  39. package/dist/gateway/http/agents.cjs +121 -10
  40. package/dist/gateway/http/agents.js +121 -10
  41. package/dist/gateway/index.cjs +2 -2
  42. package/dist/gateway/server.cjs +55 -17
  43. package/dist/gateway/server.js +55 -17
  44. package/dist/gateway/types.d.ts +9 -1
  45. package/dist/tests/additionalMessageMiddleware.test.cjs +26 -0
  46. package/dist/tests/additionalMessageMiddleware.test.js +26 -0
  47. package/dist/tests/agentInvokerAttachments.test.cjs +123 -0
  48. package/dist/tests/agentInvokerAttachments.test.js +123 -0
  49. package/dist/tests/agentInvokerWorkdir.test.cjs +100 -0
  50. package/dist/tests/agentInvokerWorkdir.test.d.ts +1 -0
  51. package/dist/tests/agentInvokerWorkdir.test.js +72 -0
  52. package/dist/tests/agents-api.test.cjs +232 -0
  53. package/dist/tests/agents-api.test.d.ts +1 -0
  54. package/dist/tests/agents-api.test.js +226 -0
  55. package/dist/tests/gateway.test.cjs +21 -0
  56. package/dist/tests/gateway.test.js +21 -0
  57. package/dist/tests/sessionMessageAttachments.test.cjs +59 -0
  58. package/dist/tests/sessionMessageAttachments.test.js +59 -0
  59. package/dist/types/agents.d.ts +5 -0
  60. package/dist/webui/assets/index-BytPznA_.css +1 -0
  61. package/dist/webui/assets/index-u_5qlVip.js +176 -0
  62. package/dist/webui/index.html +2 -2
  63. package/package.json +3 -3
  64. package/.wingman/agents/wingman/agent.json +0 -12
  65. package/dist/webui/assets/index-CyE7T5pV.js +0 -162
  66. package/dist/webui/assets/index-DMEHdune.css +0 -1
@@ -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
- clientType: "webui",
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)if (!options?.exclude || ws !== options.exclude) {
594
- if (!options?.clientType || ws.data.clientType === options.clientType) {
595
- if (options?.skipSessionId) {
596
- const subscribers = this.sessionSubscriptions.get(options.skipSessionId);
597
- if (subscribers?.has(ws)) continue;
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)if (isAudioAttachment(attachment)) hasAudio = true;
1133
- else hasImage = true;
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 };
@@ -45,7 +45,15 @@ export interface AudioAttachment {
45
45
  name?: string;
46
46
  size?: number;
47
47
  }
48
- export type MediaAttachment = ImageAttachment | AudioAttachment;
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
+ });