bingocode 1.0.28 → 1.0.30

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 (53) hide show
  1. package/adapters/common/__tests__/chat-queue.test.ts +61 -0
  2. package/adapters/common/__tests__/format.test.ts +148 -0
  3. package/adapters/common/__tests__/http-client.test.ts +105 -0
  4. package/adapters/common/__tests__/message-buffer.test.ts +84 -0
  5. package/adapters/common/__tests__/message-dedup.test.ts +57 -0
  6. package/adapters/common/__tests__/session-store.test.ts +62 -0
  7. package/adapters/common/__tests__/ws-bridge.test.ts +177 -0
  8. package/adapters/common/attachment/__tests__/attachment-limits.test.ts +52 -0
  9. package/adapters/common/attachment/__tests__/attachment-store.test.ts +108 -0
  10. package/adapters/common/attachment/__tests__/image-block-watcher.test.ts +115 -0
  11. package/adapters/feishu/__tests__/card-errors.test.ts +194 -0
  12. package/adapters/feishu/__tests__/cardkit.test.ts +295 -0
  13. package/adapters/feishu/__tests__/extract-payload.test.ts +77 -0
  14. package/adapters/feishu/__tests__/feishu.test.ts +907 -0
  15. package/adapters/feishu/__tests__/flush-controller.test.ts +290 -0
  16. package/adapters/feishu/__tests__/markdown-style.test.ts +353 -0
  17. package/adapters/feishu/__tests__/media.test.ts +120 -0
  18. package/adapters/feishu/__tests__/streaming-card.test.ts +914 -0
  19. package/adapters/telegram/__tests__/media.test.ts +86 -0
  20. package/adapters/telegram/__tests__/telegram.test.ts +115 -0
  21. package/adapters/tsconfig.json +18 -0
  22. package/bunfig.toml +1 -0
  23. package/package.json +1 -1
  24. package/preload.ts +30 -0
  25. package/scripts/count-app-loc.ts +256 -0
  26. package/scripts/release.ts +130 -0
  27. package/src/server/__tests__/conversation-service.test.ts +173 -0
  28. package/src/server/__tests__/conversations.test.ts +458 -0
  29. package/src/server/__tests__/cron-scheduler.test.ts +575 -0
  30. package/src/server/__tests__/e2e/business-flow.test.ts +841 -0
  31. package/src/server/__tests__/e2e/full-flow.test.ts +357 -0
  32. package/src/server/__tests__/fixtures/mock-sdk-cli.ts +123 -0
  33. package/src/server/__tests__/haha-oauth-api.test.ts +146 -0
  34. package/src/server/__tests__/haha-oauth-service.test.ts +185 -0
  35. package/src/server/__tests__/providers-real.test.ts +244 -0
  36. package/src/server/__tests__/providers.test.ts +579 -0
  37. package/src/server/__tests__/proxy-streaming.test.ts +317 -0
  38. package/src/server/__tests__/proxy-transform.test.ts +469 -0
  39. package/src/server/__tests__/real-llm-test.ts +526 -0
  40. package/src/server/__tests__/scheduled-tasks.test.ts +371 -0
  41. package/src/server/__tests__/sessions.test.ts +786 -0
  42. package/src/server/__tests__/settings.test.ts +376 -0
  43. package/src/server/__tests__/skills.test.ts +125 -0
  44. package/src/server/__tests__/tasks.test.ts +171 -0
  45. package/src/server/__tests__/team-watcher.test.ts +400 -0
  46. package/src/server/__tests__/teams.test.ts +627 -0
  47. package/src/server/middleware/cors.test.ts +27 -0
  48. package/src/utils/__tests__/cronFrequency.test.ts +153 -0
  49. package/src/utils/__tests__/cronTasks.test.ts +204 -0
  50. package/src/utils/computerUse/permissions.test.ts +44 -0
  51. package/stubs/ant-claude-for-chrome-mcp.ts +24 -0
  52. package/stubs/color-diff-napi.ts +45 -0
  53. package/tsconfig.json +24 -0
@@ -0,0 +1,469 @@
1
+ /**
2
+ * Unit tests for proxy protocol transformation
3
+ */
4
+
5
+ import { describe, test, expect } from 'bun:test'
6
+ import { anthropicToOpenaiChat } from '../proxy/transform/anthropicToOpenaiChat.js'
7
+ import { anthropicToOpenaiResponses } from '../proxy/transform/anthropicToOpenaiResponses.js'
8
+ import { openaiChatToAnthropic } from '../proxy/transform/openaiChatToAnthropic.js'
9
+ import { openaiResponsesToAnthropic } from '../proxy/transform/openaiResponsesToAnthropic.js'
10
+ import type { AnthropicRequest, OpenAIChatResponse, OpenAIResponsesResponse } from '../proxy/transform/types.js'
11
+
12
+ // ─── anthropicToOpenaiChat ──────────────────────────────────────
13
+
14
+ describe('anthropicToOpenaiChat', () => {
15
+ test('basic text message', () => {
16
+ const req: AnthropicRequest = {
17
+ model: 'gpt-4',
18
+ max_tokens: 1024,
19
+ messages: [{ role: 'user', content: 'Hello' }],
20
+ }
21
+ const result = anthropicToOpenaiChat(req)
22
+ expect(result.model).toBe('gpt-4')
23
+ expect(result.max_tokens).toBeUndefined()
24
+ expect(result.messages).toEqual([{ role: 'user', content: 'Hello' }])
25
+ })
26
+
27
+ test('system prompt string', () => {
28
+ const req: AnthropicRequest = {
29
+ model: 'gpt-4',
30
+ max_tokens: 100,
31
+ system: 'You are helpful',
32
+ messages: [{ role: 'user', content: 'Hi' }],
33
+ }
34
+ const result = anthropicToOpenaiChat(req)
35
+ expect(result.messages[0]).toEqual({ role: 'system', content: 'You are helpful' })
36
+ expect(result.messages[1]).toEqual({ role: 'user', content: 'Hi' })
37
+ })
38
+
39
+ test('system prompt array', () => {
40
+ const req: AnthropicRequest = {
41
+ model: 'gpt-4',
42
+ max_tokens: 100,
43
+ system: [{ type: 'text', text: 'Part 1' }, { type: 'text', text: 'Part 2' }],
44
+ messages: [{ role: 'user', content: 'Hi' }],
45
+ }
46
+ const result = anthropicToOpenaiChat(req)
47
+ expect(result.messages[0]).toEqual({ role: 'system', content: 'Part 1\nPart 2' })
48
+ })
49
+
50
+ test('stop_sequences → stop', () => {
51
+ const req: AnthropicRequest = {
52
+ model: 'gpt-4',
53
+ max_tokens: 100,
54
+ stop_sequences: ['END', 'STOP'],
55
+ messages: [{ role: 'user', content: 'Hi' }],
56
+ }
57
+ const result = anthropicToOpenaiChat(req)
58
+ expect(result.stop).toEqual(['END', 'STOP'])
59
+ })
60
+
61
+ test('tools conversion', () => {
62
+ const req: AnthropicRequest = {
63
+ model: 'gpt-4',
64
+ max_tokens: 100,
65
+ messages: [{ role: 'user', content: 'Hi' }],
66
+ tools: [{
67
+ name: 'get_weather',
68
+ description: 'Get weather',
69
+ input_schema: { type: 'object', properties: { city: { type: 'string' } } },
70
+ }],
71
+ }
72
+ const result = anthropicToOpenaiChat(req)
73
+ expect(result.tools).toHaveLength(1)
74
+ expect(result.tools![0].type).toBe('function')
75
+ expect(result.tools![0].function.name).toBe('get_weather')
76
+ expect(result.tools![0].function.parameters).toEqual({ type: 'object', properties: { city: { type: 'string' } } })
77
+ })
78
+
79
+ test('filters BatchTool', () => {
80
+ const req: AnthropicRequest = {
81
+ model: 'gpt-4',
82
+ max_tokens: 100,
83
+ messages: [{ role: 'user', content: 'Hi' }],
84
+ tools: [
85
+ { name: 'BatchTool', input_schema: {} },
86
+ { name: 'real_tool', input_schema: {} },
87
+ ],
88
+ }
89
+ const result = anthropicToOpenaiChat(req)
90
+ expect(result.tools).toHaveLength(1)
91
+ expect(result.tools![0].function.name).toBe('real_tool')
92
+ })
93
+
94
+ test('tool_choice conversion', () => {
95
+ const req: AnthropicRequest = {
96
+ model: 'gpt-4',
97
+ max_tokens: 100,
98
+ messages: [{ role: 'user', content: 'Hi' }],
99
+ tool_choice: { type: 'any' },
100
+ }
101
+ const result = anthropicToOpenaiChat(req)
102
+ expect(result.tool_choice).toBe('required')
103
+ })
104
+
105
+ test('tool_choice type=tool', () => {
106
+ const req: AnthropicRequest = {
107
+ model: 'gpt-4',
108
+ max_tokens: 100,
109
+ messages: [{ role: 'user', content: 'Hi' }],
110
+ tool_choice: { type: 'tool', name: 'get_weather' },
111
+ }
112
+ const result = anthropicToOpenaiChat(req)
113
+ expect(result.tool_choice).toEqual({ type: 'function', function: { name: 'get_weather' } })
114
+ })
115
+
116
+ test('thinking budget → reasoning_effort', () => {
117
+ const lowReq: AnthropicRequest = {
118
+ model: 'gpt-4',
119
+ max_tokens: 100,
120
+ messages: [{ role: 'user', content: 'Hi' }],
121
+ thinking: { type: 'enabled', budget_tokens: 512 },
122
+ }
123
+ expect(anthropicToOpenaiChat(lowReq).reasoning_effort).toBe('low')
124
+
125
+ const medReq: AnthropicRequest = {
126
+ model: 'gpt-4',
127
+ max_tokens: 100,
128
+ messages: [{ role: 'user', content: 'Hi' }],
129
+ thinking: { type: 'enabled', budget_tokens: 4096 },
130
+ }
131
+ expect(anthropicToOpenaiChat(medReq).reasoning_effort).toBe('medium')
132
+
133
+ const highReq: AnthropicRequest = {
134
+ model: 'gpt-4',
135
+ max_tokens: 100,
136
+ messages: [{ role: 'user', content: 'Hi' }],
137
+ thinking: { type: 'enabled', budget_tokens: 16000 },
138
+ }
139
+ expect(anthropicToOpenaiChat(highReq).reasoning_effort).toBe('high')
140
+ })
141
+
142
+ test('assistant message with tool_use', () => {
143
+ const req: AnthropicRequest = {
144
+ model: 'gpt-4',
145
+ max_tokens: 100,
146
+ messages: [{
147
+ role: 'assistant',
148
+ content: [
149
+ { type: 'text', text: 'Let me check' },
150
+ { type: 'tool_use', id: 'tc_1', name: 'get_weather', input: { city: 'NYC' } },
151
+ ],
152
+ }],
153
+ }
154
+ const result = anthropicToOpenaiChat(req)
155
+ const msg = result.messages[0]
156
+ expect(msg.role).toBe('assistant')
157
+ expect(msg.content).toBe('Let me check')
158
+ expect(msg.tool_calls).toHaveLength(1)
159
+ expect(msg.tool_calls![0].id).toBe('tc_1')
160
+ expect(msg.tool_calls![0].function.name).toBe('get_weather')
161
+ expect(msg.tool_calls![0].function.arguments).toBe('{"city":"NYC"}')
162
+ })
163
+
164
+ test('user message with tool_result', () => {
165
+ const req: AnthropicRequest = {
166
+ model: 'gpt-4',
167
+ max_tokens: 100,
168
+ messages: [{
169
+ role: 'user',
170
+ content: [
171
+ { type: 'tool_result', tool_use_id: 'tc_1', content: 'Sunny, 72°F' },
172
+ ],
173
+ }],
174
+ }
175
+ const result = anthropicToOpenaiChat(req)
176
+ expect(result.messages[0].role).toBe('tool')
177
+ expect(result.messages[0].tool_call_id).toBe('tc_1')
178
+ expect(result.messages[0].content).toBe('Sunny, 72°F')
179
+ })
180
+
181
+ test('image content conversion', () => {
182
+ const req: AnthropicRequest = {
183
+ model: 'gpt-4',
184
+ max_tokens: 100,
185
+ messages: [{
186
+ role: 'user',
187
+ content: [
188
+ { type: 'image', source: { type: 'base64', media_type: 'image/png', data: 'abc123' } },
189
+ ],
190
+ }],
191
+ }
192
+ const result = anthropicToOpenaiChat(req)
193
+ const content = result.messages[0].content as Array<{ type: string; image_url?: { url: string } }>
194
+ expect(content[0].type).toBe('image_url')
195
+ expect(content[0].image_url!.url).toBe('data:image/png;base64,abc123')
196
+ })
197
+ })
198
+
199
+ // ─── openaiChatToAnthropic ──────────────────────────────────────
200
+
201
+ describe('openaiChatToAnthropic', () => {
202
+ test('basic text response', () => {
203
+ const res: OpenAIChatResponse = {
204
+ id: 'chatcmpl-1',
205
+ object: 'chat.completion',
206
+ created: 1234567890,
207
+ model: 'gpt-4',
208
+ choices: [{
209
+ index: 0,
210
+ message: { role: 'assistant', content: 'Hello!' },
211
+ finish_reason: 'stop',
212
+ }],
213
+ usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
214
+ }
215
+ const result = openaiChatToAnthropic(res, 'gpt-4')
216
+ expect(result.type).toBe('message')
217
+ expect(result.role).toBe('assistant')
218
+ expect(result.content).toEqual([{ type: 'text', text: 'Hello!' }])
219
+ expect(result.stop_reason).toBe('end_turn')
220
+ expect(result.usage.input_tokens).toBe(10)
221
+ expect(result.usage.output_tokens).toBe(5)
222
+ })
223
+
224
+ test('tool_calls response', () => {
225
+ const res: OpenAIChatResponse = {
226
+ id: 'chatcmpl-2',
227
+ object: 'chat.completion',
228
+ created: 1234567890,
229
+ model: 'gpt-4',
230
+ choices: [{
231
+ index: 0,
232
+ message: {
233
+ role: 'assistant',
234
+ content: null,
235
+ tool_calls: [{
236
+ id: 'call_1',
237
+ type: 'function',
238
+ function: { name: 'get_weather', arguments: '{"city":"NYC"}' },
239
+ }],
240
+ },
241
+ finish_reason: 'tool_calls',
242
+ }],
243
+ }
244
+ const result = openaiChatToAnthropic(res, 'gpt-4')
245
+ expect(result.stop_reason).toBe('tool_use')
246
+ expect(result.content).toHaveLength(1)
247
+ expect(result.content[0].type).toBe('tool_use')
248
+ if (result.content[0].type === 'tool_use') {
249
+ expect(result.content[0].id).toBe('call_1')
250
+ expect(result.content[0].name).toBe('get_weather')
251
+ expect(result.content[0].input).toEqual({ city: 'NYC' })
252
+ }
253
+ })
254
+
255
+ test('finish_reason mapping', () => {
256
+ const make = (reason: string) => ({
257
+ id: 'x', object: 'chat.completion', created: 0, model: 'gpt-4',
258
+ choices: [{ index: 0, message: { role: 'assistant', content: 'hi' }, finish_reason: reason }],
259
+ } as OpenAIChatResponse)
260
+
261
+ expect(openaiChatToAnthropic(make('stop'), 'gpt-4').stop_reason).toBe('end_turn')
262
+ expect(openaiChatToAnthropic(make('length'), 'gpt-4').stop_reason).toBe('max_tokens')
263
+ expect(openaiChatToAnthropic(make('tool_calls'), 'gpt-4').stop_reason).toBe('tool_use')
264
+ expect(openaiChatToAnthropic(make('content_filter'), 'gpt-4').stop_reason).toBe('end_turn')
265
+ })
266
+
267
+ test('empty choices', () => {
268
+ const res: OpenAIChatResponse = {
269
+ id: 'x', object: 'chat.completion', created: 0, model: 'gpt-4',
270
+ choices: [],
271
+ }
272
+ const result = openaiChatToAnthropic(res, 'gpt-4')
273
+ expect(result.content).toEqual([{ type: 'text', text: '' }])
274
+ expect(result.stop_reason).toBe('end_turn')
275
+ })
276
+
277
+ test('cached tokens mapping', () => {
278
+ const res: OpenAIChatResponse = {
279
+ id: 'x', object: 'chat.completion', created: 0, model: 'gpt-4',
280
+ choices: [{ index: 0, message: { role: 'assistant', content: 'hi' }, finish_reason: 'stop' }],
281
+ usage: {
282
+ prompt_tokens: 100,
283
+ completion_tokens: 50,
284
+ total_tokens: 150,
285
+ prompt_tokens_details: { cached_tokens: 80 },
286
+ },
287
+ }
288
+ const result = openaiChatToAnthropic(res, 'gpt-4')
289
+ expect(result.usage.cache_read_input_tokens).toBe(80)
290
+ })
291
+ })
292
+
293
+ // ─── anthropicToOpenaiResponses ─────────────────────────────────
294
+
295
+ describe('anthropicToOpenaiResponses', () => {
296
+ test('basic message', () => {
297
+ const req: AnthropicRequest = {
298
+ model: 'gpt-4o',
299
+ max_tokens: 1024,
300
+ system: 'Be helpful',
301
+ messages: [{ role: 'user', content: 'Hello' }],
302
+ }
303
+ const result = anthropicToOpenaiResponses(req)
304
+ expect(result.model).toBe('gpt-4o')
305
+ expect(result.instructions).toBe('Be helpful')
306
+ expect(result.max_output_tokens).toBeUndefined()
307
+ expect(result.input).toEqual([{ type: 'message', role: 'user', content: 'Hello' }])
308
+ })
309
+
310
+ test('tool_use lifted to function_call', () => {
311
+ const req: AnthropicRequest = {
312
+ model: 'gpt-4o',
313
+ max_tokens: 100,
314
+ messages: [{
315
+ role: 'assistant',
316
+ content: [
317
+ { type: 'tool_use', id: 'tc_1', name: 'search', input: { q: 'test' } },
318
+ ],
319
+ }],
320
+ }
321
+ const result = anthropicToOpenaiResponses(req)
322
+ const fc = result.input.find((i) => i.type === 'function_call')
323
+ expect(fc).toBeDefined()
324
+ if (fc && fc.type === 'function_call') {
325
+ expect(fc.call_id).toBe('tc_1')
326
+ expect(fc.name).toBe('search')
327
+ expect(fc.arguments).toBe('{"q":"test"}')
328
+ }
329
+ })
330
+
331
+ test('tool_result lifted to function_call_output', () => {
332
+ const req: AnthropicRequest = {
333
+ model: 'gpt-4o',
334
+ max_tokens: 100,
335
+ messages: [{
336
+ role: 'user',
337
+ content: [
338
+ { type: 'tool_result', tool_use_id: 'tc_1', content: 'found it' },
339
+ ],
340
+ }],
341
+ }
342
+ const result = anthropicToOpenaiResponses(req)
343
+ const fco = result.input.find((i) => i.type === 'function_call_output')
344
+ expect(fco).toBeDefined()
345
+ if (fco && fco.type === 'function_call_output') {
346
+ expect(fco.call_id).toBe('tc_1')
347
+ expect(fco.output).toBe('found it')
348
+ }
349
+ })
350
+
351
+ test('thinking → reasoning', () => {
352
+ const req: AnthropicRequest = {
353
+ model: 'gpt-4o',
354
+ max_tokens: 100,
355
+ messages: [{ role: 'user', content: 'Hi' }],
356
+ thinking: { type: 'enabled', budget_tokens: 10000 },
357
+ }
358
+ const result = anthropicToOpenaiResponses(req)
359
+ expect(result.reasoning).toEqual({ effort: 'high' })
360
+ })
361
+
362
+ test('stop_sequences dropped', () => {
363
+ const req: AnthropicRequest = {
364
+ model: 'gpt-4o',
365
+ max_tokens: 100,
366
+ messages: [{ role: 'user', content: 'Hi' }],
367
+ stop_sequences: ['END'],
368
+ }
369
+ const result = anthropicToOpenaiResponses(req)
370
+ expect((result as Record<string, unknown>).stop).toBeUndefined()
371
+ expect((result as Record<string, unknown>).stop_sequences).toBeUndefined()
372
+ })
373
+ })
374
+
375
+ // ─── openaiResponsesToAnthropic ─────────────────────────────────
376
+
377
+ describe('openaiResponsesToAnthropic', () => {
378
+ test('basic text response', () => {
379
+ const res: OpenAIResponsesResponse = {
380
+ id: 'resp_1',
381
+ object: 'response',
382
+ created_at: 1234567890,
383
+ model: 'gpt-4o',
384
+ status: 'completed',
385
+ output: [{
386
+ type: 'message',
387
+ role: 'assistant',
388
+ content: [{ type: 'output_text', text: 'Hello!' }],
389
+ }],
390
+ usage: { input_tokens: 10, output_tokens: 5, total_tokens: 15 },
391
+ }
392
+ const result = openaiResponsesToAnthropic(res, 'gpt-4o')
393
+ expect(result.content).toEqual([{ type: 'text', text: 'Hello!' }])
394
+ expect(result.stop_reason).toBe('end_turn')
395
+ expect(result.usage.input_tokens).toBe(10)
396
+ expect(result.usage.output_tokens).toBe(5)
397
+ })
398
+
399
+ test('function_call → tool_use', () => {
400
+ const res: OpenAIResponsesResponse = {
401
+ id: 'resp_2',
402
+ object: 'response',
403
+ created_at: 0,
404
+ model: 'gpt-4o',
405
+ status: 'completed',
406
+ output: [{
407
+ type: 'function_call',
408
+ id: 'fc_1',
409
+ call_id: 'call_1',
410
+ name: 'search',
411
+ arguments: '{"q":"test"}',
412
+ }],
413
+ }
414
+ const result = openaiResponsesToAnthropic(res, 'gpt-4o')
415
+ expect(result.stop_reason).toBe('tool_use')
416
+ expect(result.content[0].type).toBe('tool_use')
417
+ if (result.content[0].type === 'tool_use') {
418
+ expect(result.content[0].id).toBe('call_1')
419
+ expect(result.content[0].input).toEqual({ q: 'test' })
420
+ }
421
+ })
422
+
423
+ test('reasoning → thinking', () => {
424
+ const res: OpenAIResponsesResponse = {
425
+ id: 'resp_3',
426
+ object: 'response',
427
+ created_at: 0,
428
+ model: 'gpt-4o',
429
+ status: 'completed',
430
+ output: [
431
+ { type: 'reasoning', id: 'r_1', summary: [{ type: 'text', text: 'Thinking...' }] },
432
+ { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'Result' }] },
433
+ ],
434
+ }
435
+ const result = openaiResponsesToAnthropic(res, 'gpt-4o')
436
+ expect(result.content).toHaveLength(2)
437
+ expect(result.content[0].type).toBe('thinking')
438
+ if (result.content[0].type === 'thinking') {
439
+ expect(result.content[0].thinking).toBe('Thinking...')
440
+ }
441
+ expect(result.content[1].type).toBe('text')
442
+ })
443
+
444
+ test('status incomplete → max_tokens', () => {
445
+ const res: OpenAIResponsesResponse = {
446
+ id: 'resp_4',
447
+ object: 'response',
448
+ created_at: 0,
449
+ model: 'gpt-4o',
450
+ status: 'incomplete',
451
+ output: [{ type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'partial' }] }],
452
+ }
453
+ const result = openaiResponsesToAnthropic(res, 'gpt-4o')
454
+ expect(result.stop_reason).toBe('max_tokens')
455
+ })
456
+
457
+ test('empty output', () => {
458
+ const res: OpenAIResponsesResponse = {
459
+ id: 'resp_5',
460
+ object: 'response',
461
+ created_at: 0,
462
+ model: 'gpt-4o',
463
+ status: 'completed',
464
+ output: [],
465
+ }
466
+ const result = openaiResponsesToAnthropic(res, 'gpt-4o')
467
+ expect(result.content).toEqual([{ type: 'text', text: '' }])
468
+ })
469
+ })