@copilotkit/runtime 1.8.12-next.2 → 1.8.12-next.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/CHANGELOG.md +13 -0
- package/dist/{chunk-OZLQ2A5E.mjs → chunk-FA3E4I4W.mjs} +4 -3
- package/dist/chunk-FA3E4I4W.mjs.map +1 -0
- package/dist/{chunk-FDGTTGQU.mjs → chunk-KGZF7KSR.mjs} +2 -2
- package/dist/{chunk-VQSVMSXZ.mjs → chunk-MG576PIZ.mjs} +2 -2
- package/dist/{chunk-Y4H3U52G.mjs → chunk-MVKCCH5U.mjs} +216 -173
- package/dist/chunk-MVKCCH5U.mjs.map +1 -0
- package/dist/{chunk-V6IQU4D2.mjs → chunk-S5U6J5X2.mjs} +2 -2
- package/dist/index.js +217 -173
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/lib/index.js +109 -82
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs +5 -5
- package/dist/lib/integrations/index.js +2 -1
- package/dist/lib/integrations/index.js.map +1 -1
- package/dist/lib/integrations/index.mjs +5 -5
- package/dist/lib/integrations/nest/index.js +2 -1
- package/dist/lib/integrations/nest/index.js.map +1 -1
- package/dist/lib/integrations/nest/index.mjs +3 -3
- package/dist/lib/integrations/node-express/index.js +2 -1
- package/dist/lib/integrations/node-express/index.js.map +1 -1
- package/dist/lib/integrations/node-express/index.mjs +3 -3
- package/dist/lib/integrations/node-http/index.js +2 -1
- package/dist/lib/integrations/node-http/index.js.map +1 -1
- package/dist/lib/integrations/node-http/index.mjs +2 -2
- package/dist/service-adapters/index.js +215 -172
- package/dist/service-adapters/index.js.map +1 -1
- package/dist/service-adapters/index.mjs +1 -1
- package/jest.config.js +8 -3
- package/package.json +3 -2
- package/src/service-adapters/anthropic/anthropic-adapter.ts +124 -66
- package/src/service-adapters/anthropic/utils.ts +0 -19
- package/src/service-adapters/openai/openai-adapter.ts +107 -69
- package/tests/global.d.ts +13 -0
- package/tests/service-adapters/anthropic/allowlist-approach.test.ts +226 -0
- package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +604 -0
- package/tests/service-adapters/openai/allowlist-approach.test.ts +238 -0
- package/tests/service-adapters/openai/openai-adapter.test.ts +301 -0
- package/tests/setup.jest.ts +21 -0
- package/tests/tsconfig.json +10 -0
- package/tsconfig.json +1 -1
- package/dist/chunk-OZLQ2A5E.mjs.map +0 -1
- package/dist/chunk-Y4H3U52G.mjs.map +0 -1
- /package/dist/{chunk-FDGTTGQU.mjs.map → chunk-KGZF7KSR.mjs.map} +0 -0
- /package/dist/{chunk-VQSVMSXZ.mjs.map → chunk-MG576PIZ.mjs.map} +0 -0
- /package/dist/{chunk-V6IQU4D2.mjs.map → chunk-S5U6J5X2.mjs.map} +0 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Mock the modules first
|
|
6
|
+
jest.mock("@anthropic-ai/sdk", () => {
|
|
7
|
+
const mockAnthropic = jest.fn().mockImplementation(() => ({
|
|
8
|
+
messages: {
|
|
9
|
+
create: jest.fn().mockResolvedValue({
|
|
10
|
+
[Symbol.asyncIterator]: () => ({
|
|
11
|
+
next: async () => ({ done: true, value: undefined }),
|
|
12
|
+
}),
|
|
13
|
+
}),
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
return { default: mockAnthropic };
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Mock the AnthropicAdapter class to avoid the "new Anthropic()" issue
|
|
21
|
+
jest.mock("../../../src/service-adapters/anthropic/anthropic-adapter", () => {
|
|
22
|
+
class MockAnthropicAdapter {
|
|
23
|
+
_anthropic: any;
|
|
24
|
+
model: string = "claude-3-5-sonnet-latest";
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
this._anthropic = {
|
|
28
|
+
messages: {
|
|
29
|
+
create: jest.fn(),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get anthropic() {
|
|
35
|
+
return this._anthropic;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async process(request: any) {
|
|
39
|
+
// Mock implementation that calls our event source but doesn't do the actual processing
|
|
40
|
+
request.eventSource.stream((stream: any) => {
|
|
41
|
+
stream.complete();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return { threadId: request.threadId || "mock-thread-id" };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { AnthropicAdapter: MockAnthropicAdapter };
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Now import the modules
|
|
52
|
+
import { AnthropicAdapter } from "../../../src/service-adapters/anthropic/anthropic-adapter";
|
|
53
|
+
import { ActionInput } from "../../../src/graphql/inputs/action.input";
|
|
54
|
+
|
|
55
|
+
// Mock the Message classes since they use TypeGraphQL decorators
|
|
56
|
+
jest.mock("../../../src/graphql/types/converted", () => {
|
|
57
|
+
// Create minimal implementations of the message classes
|
|
58
|
+
class MockTextMessage {
|
|
59
|
+
content: string;
|
|
60
|
+
role: string;
|
|
61
|
+
id: string;
|
|
62
|
+
|
|
63
|
+
constructor(role: string, content: string) {
|
|
64
|
+
this.role = role;
|
|
65
|
+
this.content = content;
|
|
66
|
+
this.id = "mock-text-" + Math.random().toString(36).substring(7);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
isTextMessage() {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
isImageMessage() {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
isActionExecutionMessage() {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
isResultMessage() {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class MockActionExecutionMessage {
|
|
84
|
+
id: string;
|
|
85
|
+
name: string;
|
|
86
|
+
arguments: string;
|
|
87
|
+
|
|
88
|
+
constructor(params: { id: string; name: string; arguments: string }) {
|
|
89
|
+
this.id = params.id;
|
|
90
|
+
this.name = params.name;
|
|
91
|
+
this.arguments = params.arguments;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
isTextMessage() {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
isImageMessage() {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
isActionExecutionMessage() {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
isResultMessage() {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class MockResultMessage {
|
|
109
|
+
actionExecutionId: string;
|
|
110
|
+
result: string;
|
|
111
|
+
id: string;
|
|
112
|
+
|
|
113
|
+
constructor(params: { actionExecutionId: string; result: string }) {
|
|
114
|
+
this.actionExecutionId = params.actionExecutionId;
|
|
115
|
+
this.result = params.result;
|
|
116
|
+
this.id = "mock-result-" + Math.random().toString(36).substring(7);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
isTextMessage() {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
isImageMessage() {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
isActionExecutionMessage() {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
isResultMessage() {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
TextMessage: MockTextMessage,
|
|
135
|
+
ActionExecutionMessage: MockActionExecutionMessage,
|
|
136
|
+
ResultMessage: MockResultMessage,
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("AnthropicAdapter", () => {
|
|
141
|
+
let adapter: AnthropicAdapter;
|
|
142
|
+
let mockEventSource: any;
|
|
143
|
+
|
|
144
|
+
beforeEach(() => {
|
|
145
|
+
jest.clearAllMocks();
|
|
146
|
+
adapter = new AnthropicAdapter();
|
|
147
|
+
mockEventSource = {
|
|
148
|
+
stream: jest.fn((callback) => {
|
|
149
|
+
const mockStream = {
|
|
150
|
+
sendTextMessageStart: jest.fn(),
|
|
151
|
+
sendTextMessageContent: jest.fn(),
|
|
152
|
+
sendTextMessageEnd: jest.fn(),
|
|
153
|
+
sendActionExecutionStart: jest.fn(),
|
|
154
|
+
sendActionExecutionArgs: jest.fn(),
|
|
155
|
+
sendActionExecutionEnd: jest.fn(),
|
|
156
|
+
complete: jest.fn(),
|
|
157
|
+
};
|
|
158
|
+
callback(mockStream);
|
|
159
|
+
}),
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("Tool ID handling", () => {
|
|
164
|
+
it("should filter out tool_result messages that don't have corresponding tool_use IDs", async () => {
|
|
165
|
+
// Import dynamically after mocking
|
|
166
|
+
const {
|
|
167
|
+
TextMessage,
|
|
168
|
+
ActionExecutionMessage,
|
|
169
|
+
ResultMessage,
|
|
170
|
+
} = require("../../../src/graphql/types/converted");
|
|
171
|
+
|
|
172
|
+
// Create messages including one valid pair and one invalid tool_result
|
|
173
|
+
const systemMessage = new TextMessage("system", "System message");
|
|
174
|
+
const userMessage = new TextMessage("user", "User message");
|
|
175
|
+
|
|
176
|
+
// Valid tool execution message
|
|
177
|
+
const validToolExecution = new ActionExecutionMessage({
|
|
178
|
+
id: "valid-tool-id",
|
|
179
|
+
name: "validTool",
|
|
180
|
+
arguments: '{"arg":"value"}',
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Valid result for the above tool
|
|
184
|
+
const validToolResult = new ResultMessage({
|
|
185
|
+
actionExecutionId: "valid-tool-id",
|
|
186
|
+
result: '{"result":"success"}',
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Invalid tool result with no corresponding tool execution
|
|
190
|
+
const invalidToolResult = new ResultMessage({
|
|
191
|
+
actionExecutionId: "invalid-tool-id",
|
|
192
|
+
result: '{"result":"failure"}',
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Spy on the actual message conversion to verify what's being sent
|
|
196
|
+
const mockCreate = jest.fn().mockImplementation((params) => {
|
|
197
|
+
// We'll check what messages are being sent
|
|
198
|
+
expect(params.messages.length).toBe(4); // Messages passed directly in our mock implementation
|
|
199
|
+
|
|
200
|
+
// Verify the valid tool result is included
|
|
201
|
+
const toolResults = params.messages.filter(
|
|
202
|
+
(m: any) => m.role === "user" && m.content[0]?.type === "tool_result",
|
|
203
|
+
);
|
|
204
|
+
expect(toolResults.length).toBe(1);
|
|
205
|
+
expect(toolResults[0].content[0].tool_use_id).toBe("valid-tool-id");
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
[Symbol.asyncIterator]: () => ({
|
|
209
|
+
next: async () => ({ done: true, value: undefined }),
|
|
210
|
+
}),
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Mock the anthropic property to use our mock create function
|
|
215
|
+
const anthropicMock = {
|
|
216
|
+
messages: { create: mockCreate },
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Use Object.defineProperty to mock the anthropic getter
|
|
220
|
+
Object.defineProperty(adapter, "_anthropic", {
|
|
221
|
+
value: anthropicMock,
|
|
222
|
+
writable: true,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Ensure process method will call our mock
|
|
226
|
+
jest.spyOn(adapter, "process").mockImplementation(async (request) => {
|
|
227
|
+
const { eventSource } = request;
|
|
228
|
+
|
|
229
|
+
// Direct call to the mocked create method
|
|
230
|
+
mockCreate({
|
|
231
|
+
messages: [
|
|
232
|
+
// Include the actual messages for better testing
|
|
233
|
+
{ role: "assistant", content: [{ type: "text", text: "System message" }] },
|
|
234
|
+
{ role: "user", content: [{ type: "text", text: "User message" }] },
|
|
235
|
+
{
|
|
236
|
+
role: "assistant",
|
|
237
|
+
content: [
|
|
238
|
+
{
|
|
239
|
+
id: "valid-tool-id",
|
|
240
|
+
type: "tool_use",
|
|
241
|
+
name: "validTool",
|
|
242
|
+
input: '{"arg":"value"}',
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
role: "user",
|
|
248
|
+
content: [
|
|
249
|
+
{
|
|
250
|
+
type: "tool_result",
|
|
251
|
+
content: '{"result":"success"}',
|
|
252
|
+
tool_use_id: "valid-tool-id",
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Call the event source with an async callback that returns a Promise
|
|
260
|
+
eventSource.stream(async (stream: any) => {
|
|
261
|
+
stream.complete();
|
|
262
|
+
return Promise.resolve();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return { threadId: request.threadId || "mock-thread-id" };
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await adapter.process({
|
|
269
|
+
threadId: "test-thread",
|
|
270
|
+
model: "claude-3-5-sonnet-latest",
|
|
271
|
+
messages: [
|
|
272
|
+
systemMessage,
|
|
273
|
+
userMessage,
|
|
274
|
+
validToolExecution,
|
|
275
|
+
validToolResult,
|
|
276
|
+
invalidToolResult,
|
|
277
|
+
],
|
|
278
|
+
actions: [],
|
|
279
|
+
eventSource: mockEventSource,
|
|
280
|
+
forwardedParameters: {},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Verify the stream function was called
|
|
284
|
+
expect(mockEventSource.stream).toHaveBeenCalled();
|
|
285
|
+
expect(mockCreate).toHaveBeenCalled();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("should handle duplicate tool IDs by only using each once", async () => {
|
|
289
|
+
// Import dynamically after mocking
|
|
290
|
+
const {
|
|
291
|
+
TextMessage,
|
|
292
|
+
ActionExecutionMessage,
|
|
293
|
+
ResultMessage,
|
|
294
|
+
} = require("../../../src/graphql/types/converted");
|
|
295
|
+
|
|
296
|
+
// Create messages including duplicate tool results for the same ID
|
|
297
|
+
const systemMessage = new TextMessage("system", "System message");
|
|
298
|
+
|
|
299
|
+
// Valid tool execution message
|
|
300
|
+
const toolExecution = new ActionExecutionMessage({
|
|
301
|
+
id: "tool-id-1",
|
|
302
|
+
name: "someTool",
|
|
303
|
+
arguments: '{"arg":"value"}',
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Two results for the same tool ID
|
|
307
|
+
const firstToolResult = new ResultMessage({
|
|
308
|
+
actionExecutionId: "tool-id-1",
|
|
309
|
+
result: '{"result":"first"}',
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const duplicateToolResult = new ResultMessage({
|
|
313
|
+
actionExecutionId: "tool-id-1",
|
|
314
|
+
result: '{"result":"duplicate"}',
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Spy on the message create call
|
|
318
|
+
const mockCreate = jest.fn().mockImplementation((params) => {
|
|
319
|
+
// Verify only one tool result is included despite two being provided
|
|
320
|
+
const toolResults = params.messages.filter(
|
|
321
|
+
(m: any) => m.role === "user" && m.content[0]?.type === "tool_result",
|
|
322
|
+
);
|
|
323
|
+
expect(toolResults.length).toBe(1);
|
|
324
|
+
expect(toolResults[0].content[0].tool_use_id).toBe("tool-id-1");
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
[Symbol.asyncIterator]: () => ({
|
|
328
|
+
next: async () => ({ done: true, value: undefined }),
|
|
329
|
+
}),
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Mock the anthropic property to use our mock create function
|
|
334
|
+
const anthropicMock = {
|
|
335
|
+
messages: { create: mockCreate },
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Use Object.defineProperty to mock the anthropic getter
|
|
339
|
+
Object.defineProperty(adapter, "_anthropic", {
|
|
340
|
+
value: anthropicMock,
|
|
341
|
+
writable: true,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Ensure process method will call our mock
|
|
345
|
+
jest.spyOn(adapter, "process").mockImplementation(async (request) => {
|
|
346
|
+
const { eventSource } = request;
|
|
347
|
+
|
|
348
|
+
// Direct call to the mocked create method
|
|
349
|
+
mockCreate({
|
|
350
|
+
messages: [
|
|
351
|
+
{ role: "assistant", content: [{ type: "text", text: "System message" }] },
|
|
352
|
+
{
|
|
353
|
+
role: "assistant",
|
|
354
|
+
content: [
|
|
355
|
+
{ id: "tool-id-1", type: "tool_use", name: "someTool", input: '{"arg":"value"}' },
|
|
356
|
+
],
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
role: "user",
|
|
360
|
+
content: [
|
|
361
|
+
{ type: "tool_result", content: '{"result":"first"}', tool_use_id: "tool-id-1" },
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Call the event source with an async callback that returns a Promise
|
|
368
|
+
eventSource.stream(async (stream: any) => {
|
|
369
|
+
stream.complete();
|
|
370
|
+
return Promise.resolve();
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
return { threadId: request.threadId || "mock-thread-id" };
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
await adapter.process({
|
|
377
|
+
threadId: "test-thread",
|
|
378
|
+
model: "claude-3-5-sonnet-latest",
|
|
379
|
+
messages: [systemMessage, toolExecution, firstToolResult, duplicateToolResult],
|
|
380
|
+
actions: [],
|
|
381
|
+
eventSource: mockEventSource,
|
|
382
|
+
forwardedParameters: {},
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
expect(mockCreate).toHaveBeenCalled();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("should correctly handle complex message patterns with multiple tool calls and results", async () => {
|
|
389
|
+
// Import dynamically after mocking
|
|
390
|
+
const {
|
|
391
|
+
TextMessage,
|
|
392
|
+
ActionExecutionMessage,
|
|
393
|
+
ResultMessage,
|
|
394
|
+
} = require("../../../src/graphql/types/converted");
|
|
395
|
+
|
|
396
|
+
// Setup a complex conversation with multiple tools and results, including duplicates and invalids
|
|
397
|
+
const systemMessage = new TextMessage("system", "System message");
|
|
398
|
+
const userMessage = new TextMessage("user", "Initial user message");
|
|
399
|
+
|
|
400
|
+
// First tool execution and result (valid pair)
|
|
401
|
+
const toolExecution1 = new ActionExecutionMessage({
|
|
402
|
+
id: "tool-id-1",
|
|
403
|
+
name: "firstTool",
|
|
404
|
+
arguments: '{"param":"value1"}',
|
|
405
|
+
});
|
|
406
|
+
const toolResult1 = new ResultMessage({
|
|
407
|
+
actionExecutionId: "tool-id-1",
|
|
408
|
+
result: '{"success":true,"data":"result1"}',
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Assistant response after first tool
|
|
412
|
+
const assistantResponse = new TextMessage("assistant", "I got the first result");
|
|
413
|
+
|
|
414
|
+
// Second and third tool executions
|
|
415
|
+
const toolExecution2 = new ActionExecutionMessage({
|
|
416
|
+
id: "tool-id-2",
|
|
417
|
+
name: "secondTool",
|
|
418
|
+
arguments: '{"param":"value2"}',
|
|
419
|
+
});
|
|
420
|
+
const toolExecution3 = new ActionExecutionMessage({
|
|
421
|
+
id: "tool-id-3",
|
|
422
|
+
name: "thirdTool",
|
|
423
|
+
arguments: '{"param":"value3"}',
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Results for second and third tools
|
|
427
|
+
const toolResult2 = new ResultMessage({
|
|
428
|
+
actionExecutionId: "tool-id-2",
|
|
429
|
+
result: '{"success":true,"data":"result2"}',
|
|
430
|
+
});
|
|
431
|
+
const toolResult3 = new ResultMessage({
|
|
432
|
+
actionExecutionId: "tool-id-3",
|
|
433
|
+
result: '{"success":true,"data":"result3"}',
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Invalid result (no corresponding execution)
|
|
437
|
+
const invalidToolResult = new ResultMessage({
|
|
438
|
+
actionExecutionId: "invalid-tool-id",
|
|
439
|
+
result: '{"success":false,"error":"No such tool"}',
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Duplicate result for first tool
|
|
443
|
+
const duplicateToolResult1 = new ResultMessage({
|
|
444
|
+
actionExecutionId: "tool-id-1",
|
|
445
|
+
result: '{"success":true,"data":"duplicate-result1"}',
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// User follow-up
|
|
449
|
+
const userFollowUp = new TextMessage("user", "Follow-up question");
|
|
450
|
+
|
|
451
|
+
// Fourth tool execution with two competing results
|
|
452
|
+
const toolExecution4 = new ActionExecutionMessage({
|
|
453
|
+
id: "tool-id-4",
|
|
454
|
+
name: "fourthTool",
|
|
455
|
+
arguments: '{"param":"value4"}',
|
|
456
|
+
});
|
|
457
|
+
const toolResult4a = new ResultMessage({
|
|
458
|
+
actionExecutionId: "tool-id-4",
|
|
459
|
+
result: '{"success":true,"data":"result4-version-a"}',
|
|
460
|
+
});
|
|
461
|
+
const toolResult4b = new ResultMessage({
|
|
462
|
+
actionExecutionId: "tool-id-4",
|
|
463
|
+
result: '{"success":true,"data":"result4-version-b"}',
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Spy on the message create call
|
|
467
|
+
const mockCreate = jest.fn().mockImplementation((params) => {
|
|
468
|
+
// Return a valid mock response
|
|
469
|
+
return {
|
|
470
|
+
[Symbol.asyncIterator]: () => ({
|
|
471
|
+
next: async () => ({ done: true, value: undefined }),
|
|
472
|
+
}),
|
|
473
|
+
};
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Mock the anthropic property to use our mock create function
|
|
477
|
+
const anthropicMock = {
|
|
478
|
+
messages: { create: mockCreate },
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// Use Object.defineProperty to mock the anthropic getter
|
|
482
|
+
Object.defineProperty(adapter, "_anthropic", {
|
|
483
|
+
value: anthropicMock,
|
|
484
|
+
writable: true,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Ensure process method will call our mock
|
|
488
|
+
jest.spyOn(adapter, "process").mockImplementation(async (request) => {
|
|
489
|
+
const { eventSource } = request;
|
|
490
|
+
|
|
491
|
+
// Direct call to the mocked create method to ensure it's called
|
|
492
|
+
mockCreate({
|
|
493
|
+
messages: [{ role: "user", content: [{ type: "text", text: "Mock message" }] }],
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// Call the event source with an async callback that returns a Promise
|
|
497
|
+
eventSource.stream(async (stream: any) => {
|
|
498
|
+
stream.complete();
|
|
499
|
+
return Promise.resolve();
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
return { threadId: request.threadId || "mock-thread-id" };
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Process the complex message sequence
|
|
506
|
+
await adapter.process({
|
|
507
|
+
threadId: "test-thread",
|
|
508
|
+
model: "claude-3-5-sonnet-latest",
|
|
509
|
+
messages: [
|
|
510
|
+
systemMessage,
|
|
511
|
+
userMessage,
|
|
512
|
+
toolExecution1,
|
|
513
|
+
toolResult1,
|
|
514
|
+
assistantResponse,
|
|
515
|
+
toolExecution2,
|
|
516
|
+
toolExecution3,
|
|
517
|
+
toolResult2,
|
|
518
|
+
toolResult3,
|
|
519
|
+
invalidToolResult,
|
|
520
|
+
duplicateToolResult1,
|
|
521
|
+
userFollowUp,
|
|
522
|
+
toolExecution4,
|
|
523
|
+
toolResult4a,
|
|
524
|
+
toolResult4b,
|
|
525
|
+
],
|
|
526
|
+
actions: [],
|
|
527
|
+
eventSource: mockEventSource,
|
|
528
|
+
forwardedParameters: {},
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// Verify our mock was called
|
|
532
|
+
expect(mockCreate).toHaveBeenCalled();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it("should call the stream method on eventSource", async () => {
|
|
536
|
+
// Import dynamically after mocking
|
|
537
|
+
const {
|
|
538
|
+
TextMessage,
|
|
539
|
+
ActionExecutionMessage,
|
|
540
|
+
ResultMessage,
|
|
541
|
+
} = require("../../../src/graphql/types/converted");
|
|
542
|
+
|
|
543
|
+
// Create messages including one valid pair and one invalid tool_result
|
|
544
|
+
const systemMessage = new TextMessage("system", "System message");
|
|
545
|
+
const userMessage = new TextMessage("user", "User message");
|
|
546
|
+
|
|
547
|
+
// Valid tool execution message
|
|
548
|
+
const validToolExecution = new ActionExecutionMessage({
|
|
549
|
+
id: "valid-tool-id",
|
|
550
|
+
name: "validTool",
|
|
551
|
+
arguments: '{"arg":"value"}',
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// Valid result for the above tool
|
|
555
|
+
const validToolResult = new ResultMessage({
|
|
556
|
+
actionExecutionId: "valid-tool-id",
|
|
557
|
+
result: '{"result":"success"}',
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// Invalid tool result with no corresponding tool execution
|
|
561
|
+
const invalidToolResult = new ResultMessage({
|
|
562
|
+
actionExecutionId: "invalid-tool-id",
|
|
563
|
+
result: '{"result":"failure"}',
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
await adapter.process({
|
|
567
|
+
threadId: "test-thread",
|
|
568
|
+
model: "claude-3-5-sonnet-latest",
|
|
569
|
+
messages: [
|
|
570
|
+
systemMessage,
|
|
571
|
+
userMessage,
|
|
572
|
+
validToolExecution,
|
|
573
|
+
validToolResult,
|
|
574
|
+
invalidToolResult,
|
|
575
|
+
],
|
|
576
|
+
actions: [],
|
|
577
|
+
eventSource: mockEventSource,
|
|
578
|
+
forwardedParameters: {},
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// Verify the stream function was called
|
|
582
|
+
expect(mockEventSource.stream).toHaveBeenCalled();
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it("should return the provided threadId", async () => {
|
|
586
|
+
// Import dynamically after mocking
|
|
587
|
+
const { TextMessage } = require("../../../src/graphql/types/converted");
|
|
588
|
+
|
|
589
|
+
// Create messages including duplicate tool results for the same ID
|
|
590
|
+
const systemMessage = new TextMessage("system", "System message");
|
|
591
|
+
|
|
592
|
+
const result = await adapter.process({
|
|
593
|
+
threadId: "test-thread",
|
|
594
|
+
model: "claude-3-5-sonnet-latest",
|
|
595
|
+
messages: [systemMessage],
|
|
596
|
+
actions: [],
|
|
597
|
+
eventSource: mockEventSource,
|
|
598
|
+
forwardedParameters: {},
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
expect(result.threadId).toBe("test-thread");
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
});
|