universal-llm-client 4.2.0 → 4.5.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 (108) hide show
  1. package/CHANGELOG.md +142 -103
  2. package/LICENSE +21 -21
  3. package/README.md +640 -591
  4. package/dist/ai-model.d.ts +12 -1
  5. package/dist/ai-model.d.ts.map +1 -1
  6. package/dist/ai-model.js +36 -1
  7. package/dist/ai-model.js.map +1 -1
  8. package/dist/gemma-channel.d.ts +14 -0
  9. package/dist/gemma-channel.d.ts.map +1 -0
  10. package/dist/gemma-channel.js +38 -0
  11. package/dist/gemma-channel.js.map +1 -0
  12. package/dist/gemma-diffusion.d.ts +49 -0
  13. package/dist/gemma-diffusion.d.ts.map +1 -0
  14. package/dist/gemma-diffusion.js +147 -0
  15. package/dist/gemma-diffusion.js.map +1 -0
  16. package/dist/http.d.ts +4 -0
  17. package/dist/http.d.ts.map +1 -1
  18. package/dist/http.js +14 -1
  19. package/dist/http.js.map +1 -1
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +4 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/interfaces.d.ts +183 -7
  25. package/dist/interfaces.d.ts.map +1 -1
  26. package/dist/interfaces.js.map +1 -1
  27. package/dist/providers/anthropic.d.ts.map +1 -1
  28. package/dist/providers/anthropic.js +28 -3
  29. package/dist/providers/anthropic.js.map +1 -1
  30. package/dist/providers/google.d.ts +22 -1
  31. package/dist/providers/google.d.ts.map +1 -1
  32. package/dist/providers/google.js +225 -13
  33. package/dist/providers/google.js.map +1 -1
  34. package/dist/providers/ollama.d.ts +2 -0
  35. package/dist/providers/ollama.d.ts.map +1 -1
  36. package/dist/providers/ollama.js +59 -30
  37. package/dist/providers/ollama.js.map +1 -1
  38. package/dist/providers/openai.d.ts +14 -0
  39. package/dist/providers/openai.d.ts.map +1 -1
  40. package/dist/providers/openai.js +200 -22
  41. package/dist/providers/openai.js.map +1 -1
  42. package/dist/router.d.ts +2 -0
  43. package/dist/router.d.ts.map +1 -1
  44. package/dist/router.js +4 -0
  45. package/dist/router.js.map +1 -1
  46. package/dist/stream-decoder.d.ts +12 -0
  47. package/dist/stream-decoder.d.ts.map +1 -1
  48. package/dist/stream-decoder.js +182 -5
  49. package/dist/stream-decoder.js.map +1 -1
  50. package/dist/thinking.d.ts +36 -0
  51. package/dist/thinking.d.ts.map +1 -0
  52. package/dist/thinking.js +52 -0
  53. package/dist/thinking.js.map +1 -0
  54. package/package.json +118 -116
  55. package/src/ai-model.ts +400 -350
  56. package/src/auditor.ts +213 -213
  57. package/src/client.ts +402 -402
  58. package/src/debug/debug-google-streaming.ts +1 -1
  59. package/src/demos/basic/universal-llm-examples.ts +3 -3
  60. package/src/demos/diffusion-gemma/.env +29 -0
  61. package/src/demos/diffusion-gemma/.env.example +27 -0
  62. package/src/demos/diffusion-gemma/CLAUDE.md +95 -0
  63. package/src/demos/diffusion-gemma/README.md +59 -0
  64. package/src/demos/diffusion-gemma/canvas.ts +1606 -0
  65. package/src/demos/diffusion-gemma/docker-compose.yml +29 -0
  66. package/src/demos/diffusion-gemma/probe-stream.ts +51 -0
  67. package/src/demos/diffusion-gemma/probe-tools.ts +55 -0
  68. package/src/demos/diffusion-gemma/server.ts +1205 -0
  69. package/src/demos/diffusion-gemma/start-vllm.sh +98 -0
  70. package/src/gemma-channel.ts +47 -0
  71. package/src/gemma-diffusion.ts +167 -0
  72. package/src/http.ts +261 -247
  73. package/src/index.ts +180 -161
  74. package/src/interfaces.ts +843 -657
  75. package/src/mcp.ts +345 -345
  76. package/src/providers/anthropic.ts +796 -762
  77. package/src/providers/google.ts +840 -620
  78. package/src/providers/index.ts +8 -8
  79. package/src/providers/ollama.ts +503 -469
  80. package/src/providers/openai.ts +587 -392
  81. package/src/router.ts +785 -780
  82. package/src/stream-decoder.ts +535 -361
  83. package/src/structured-output.ts +759 -759
  84. package/src/test-scripts/test-google-deep-research.ts +33 -0
  85. package/src/test-scripts/test-google-streaming-enhanced.ts +147 -147
  86. package/src/test-scripts/test-google-streaming.ts +1 -1
  87. package/src/test-scripts/test-google-system-prompt-comprehensive.ts +189 -189
  88. package/src/test-scripts/test-google-thinking.ts +46 -0
  89. package/src/test-scripts/test-system-message-positions.ts +163 -163
  90. package/src/test-scripts/test-system-prompt-improvement-demo.ts +83 -83
  91. package/src/test-scripts/test-vllm-qwen36.ts +256 -0
  92. package/src/tests/ai-model.test.ts +1614 -1614
  93. package/src/tests/auditor.test.ts +224 -224
  94. package/src/tests/gemma-diffusion.test.ts +115 -0
  95. package/src/tests/http.test.ts +200 -200
  96. package/src/tests/interfaces.test.ts +117 -117
  97. package/src/tests/providers/anthropic.test.ts +118 -0
  98. package/src/tests/providers/google.test.ts +841 -660
  99. package/src/tests/providers/ollama.test.ts +1034 -954
  100. package/src/tests/providers/openai.test.ts +1511 -1122
  101. package/src/tests/router.test.ts +254 -254
  102. package/src/tests/stream-decoder.test.ts +263 -179
  103. package/src/tests/structured-output.test.ts +1450 -1450
  104. package/src/tests/thinking.test.ts +65 -0
  105. package/src/tests/tools.test.ts +175 -175
  106. package/src/thinking.ts +73 -0
  107. package/src/tools.ts +246 -246
  108. package/src/zod-adapter.ts +72 -72
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Anthropic Provider — unified thinking flag tests.
3
+ *
4
+ * Validates that the model-level `thinking` config and per-call
5
+ * `ChatOptions.thinking` map to Anthropic extended thinking
6
+ * (`thinking: { type: 'enabled', budget_tokens }`), with the API's
7
+ * constraints handled (budget < max_tokens; temperature omitted when on).
8
+ */
9
+
10
+ import { describe, test, expect, mock, beforeEach, afterEach } from 'bun:test';
11
+ import { AnthropicClient } from '../../providers/anthropic.js';
12
+ import type { LLMClientOptions } from '../../interfaces.js';
13
+ import { AIModelApiType } from '../../interfaces.js';
14
+
15
+ function createClient(overrides?: Partial<LLMClientOptions>): AnthropicClient {
16
+ return new AnthropicClient({
17
+ model: 'claude-sonnet-4-5',
18
+ apiKey: 'test-api-key',
19
+ apiType: AIModelApiType.Anthropic,
20
+ ...overrides,
21
+ });
22
+ }
23
+
24
+ const ANTHROPIC_RESPONSE = {
25
+ id: 'msg_1',
26
+ type: 'message',
27
+ role: 'assistant',
28
+ content: [{ type: 'text', text: 'Hi there.' }],
29
+ model: 'claude-sonnet-4-5',
30
+ stop_reason: 'end_turn',
31
+ usage: { input_tokens: 5, output_tokens: 3 },
32
+ };
33
+
34
+ describe('AnthropicClient thinking flag', () => {
35
+ let originalFetch: typeof globalThis.fetch;
36
+ beforeEach(() => { originalFetch = globalThis.fetch; });
37
+ afterEach(() => { globalThis.fetch = originalFetch; });
38
+
39
+ function mockFetchAndCapture(response: unknown = ANTHROPIC_RESPONSE, status = 200) {
40
+ let capturedBody: Record<string, unknown> | null = null;
41
+ globalThis.fetch = mock(async (_input: string | URL | Request, init?: RequestInit) => {
42
+ if (init?.body) capturedBody = JSON.parse(init.body as string);
43
+ return new Response(JSON.stringify(response), {
44
+ status,
45
+ headers: { 'Content-Type': 'application/json' },
46
+ });
47
+ }) as typeof fetch;
48
+ return () => capturedBody;
49
+ }
50
+
51
+ test('enables extended thinking when thinking:true (budget < max_tokens, temperature omitted)', async () => {
52
+ const getBody = mockFetchAndCapture();
53
+ const client = createClient();
54
+
55
+ await client.chat([{ role: 'user', content: 'hi' }], { thinking: true, maxTokens: 4096, temperature: 0.7 });
56
+
57
+ const body = getBody()!;
58
+ const thinking = body['thinking'] as Record<string, unknown> | undefined;
59
+ expect(thinking).toBeDefined();
60
+ expect(thinking!['type']).toBe('enabled');
61
+ expect(thinking!['budget_tokens']).toBe(2048);
62
+ expect(thinking!['budget_tokens'] as number).toBeLessThan(body['max_tokens'] as number);
63
+ // The API forbids a custom temperature while thinking is on — must be omitted.
64
+ expect(body['temperature']).toBeUndefined();
65
+ });
66
+
67
+ test('does not request thinking when flag is unset (temperature preserved)', async () => {
68
+ const getBody = mockFetchAndCapture();
69
+ const client = createClient();
70
+
71
+ await client.chat([{ role: 'user', content: 'hi' }], { temperature: 0.5 });
72
+
73
+ const body = getBody()!;
74
+ expect(body['thinking']).toBeUndefined();
75
+ expect(body['temperature']).toBe(0.5);
76
+ });
77
+
78
+ test('does not request thinking when thinking:false (overrides client config)', async () => {
79
+ const getBody = mockFetchAndCapture();
80
+ const client = createClient({ thinking: true });
81
+
82
+ await client.chat([{ role: 'user', content: 'hi' }], { thinking: false });
83
+
84
+ expect(getBody()!['thinking']).toBeUndefined();
85
+ });
86
+
87
+ test('per-call thinking:true overrides unset client config', async () => {
88
+ const getBody = mockFetchAndCapture();
89
+ const client = createClient();
90
+
91
+ await client.chat([{ role: 'user', content: 'hi' }], { thinking: true });
92
+
93
+ expect((getBody()!['thinking'] as Record<string, unknown>)['type']).toBe('enabled');
94
+ });
95
+
96
+ test('maps a thinking level to budget_tokens (high, clamped < max_tokens)', async () => {
97
+ const getBody = mockFetchAndCapture();
98
+ const client = createClient();
99
+
100
+ await client.chat([{ role: 'user', content: 'hi' }], { thinking: 'high', maxTokens: 32000 });
101
+
102
+ const thinking = getBody()!['thinking'] as Record<string, unknown>;
103
+ expect(thinking['type']).toBe('enabled');
104
+ expect(thinking['budget_tokens']).toBe(16384);
105
+ });
106
+
107
+ test('bumps max_tokens so budget_tokens stays below it when maxTokens is small', async () => {
108
+ const getBody = mockFetchAndCapture();
109
+ const client = createClient();
110
+
111
+ await client.chat([{ role: 'user', content: 'hi' }], { thinking: 'low', maxTokens: 1024 });
112
+
113
+ const body = getBody()!;
114
+ const budget = (body['thinking'] as Record<string, unknown>)['budget_tokens'] as number;
115
+ expect(budget).toBeGreaterThanOrEqual(1024);
116
+ expect(budget).toBeLessThan(body['max_tokens'] as number);
117
+ });
118
+ });