ai-world-sdk 1.5.11 → 1.5.13

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 (156) hide show
  1. package/dist/cli/commands/config-cmd.js +1 -1
  2. package/dist/cli/config.js +3 -2
  3. package/dist/config.d.ts +3 -3
  4. package/dist/config.js +11 -5
  5. package/dist/llm.js +3 -3
  6. package/dist/vite-plugin.js +3 -1
  7. package/package.json +3 -2
  8. package/skills/ai-world-sdk/SKILL.md +1 -0
  9. package/skills/ai-world-sdk/docs/cli-usage.md +7 -4
  10. package/pacakges/ai-sdk-provider/.changeset/README.md +0 -34
  11. package/pacakges/ai-sdk-provider/.changeset/config.json +0 -13
  12. package/pacakges/ai-sdk-provider/.github/PULL_REQUEST_TEMPLATE.md +0 -16
  13. package/pacakges/ai-sdk-provider/.github/workflows/ci.yaml +0 -24
  14. package/pacakges/ai-sdk-provider/.github/workflows/publish-manual.yaml +0 -36
  15. package/pacakges/ai-sdk-provider/.github/workflows/publish.yaml +0 -107
  16. package/pacakges/ai-sdk-provider/.vscode/settings.json +0 -32
  17. package/pacakges/ai-sdk-provider/CHANGELOG.md +0 -631
  18. package/pacakges/ai-sdk-provider/CLAUDE.md +0 -79
  19. package/pacakges/ai-sdk-provider/CONTRIBUTING.md +0 -241
  20. package/pacakges/ai-sdk-provider/LICENSE +0 -201
  21. package/pacakges/ai-sdk-provider/README.md +0 -426
  22. package/pacakges/ai-sdk-provider/biome.json +0 -122
  23. package/pacakges/ai-sdk-provider/e2e/cache-control.test.ts +0 -74
  24. package/pacakges/ai-sdk-provider/e2e/embeddings/index.test.ts +0 -128
  25. package/pacakges/ai-sdk-provider/e2e/fixtures/pdfs/generate-pdfs.sh +0 -181
  26. package/pacakges/ai-sdk-provider/e2e/fixtures/pdfs/large.json +0 -7
  27. package/pacakges/ai-sdk-provider/e2e/fixtures/pdfs/large.pdf +41 -12894
  28. package/pacakges/ai-sdk-provider/e2e/gemini/reasoning-multiturn.test.ts +0 -221
  29. package/pacakges/ai-sdk-provider/e2e/issues/issue-160-toolcallid-uniqueness.test.ts +0 -88
  30. package/pacakges/ai-sdk-provider/e2e/issues/issue-166-finish-reason-null.test.ts +0 -129
  31. package/pacakges/ai-sdk-provider/e2e/issues/issue-171-cache-tool-key-ordering.test.ts +0 -70
  32. package/pacakges/ai-sdk-provider/e2e/issues/issue-181-tool-response-image.test.ts +0 -199
  33. package/pacakges/ai-sdk-provider/e2e/issues/issue-190-streamobject-flush-error.test.ts +0 -114
  34. package/pacakges/ai-sdk-provider/e2e/issues/issue-194-grok-invalid-json.test.ts +0 -93
  35. package/pacakges/ai-sdk-provider/e2e/issues/issue-196-anthropic-1h-cache-ttl.test.ts +0 -85
  36. package/pacakges/ai-sdk-provider/e2e/issues/issue-199-openai-pdf-processing.test.ts +0 -102
  37. package/pacakges/ai-sdk-provider/e2e/issues/issue-212-anthropic-web-search-online.test.ts +0 -128
  38. package/pacakges/ai-sdk-provider/e2e/issues/issue-234-prompt-caching.test.ts +0 -201
  39. package/pacakges/ai-sdk-provider/e2e/issues/issue-237-reasoning-linebreaks.test.ts +0 -103
  40. package/pacakges/ai-sdk-provider/e2e/issues/issue-248-gemini-web-search-empty-response.test.ts +0 -210
  41. package/pacakges/ai-sdk-provider/e2e/issues/issue-269-image-size-parameter.test.ts +0 -208
  42. package/pacakges/ai-sdk-provider/e2e/issues/issue-287-tool-calls-missing-arguments.test.ts +0 -87
  43. package/pacakges/ai-sdk-provider/e2e/issues/issue-341-cache-control-last-text-part.test.ts +0 -138
  44. package/pacakges/ai-sdk-provider/e2e/issues/issue-383-video-url-support.test.ts +0 -120
  45. package/pacakges/ai-sdk-provider/e2e/issues/issue-386-image-files-parameter.test.ts +0 -109
  46. package/pacakges/ai-sdk-provider/e2e/issues/issue-387-temperature-settings.test.ts +0 -113
  47. package/pacakges/ai-sdk-provider/e2e/issues/issue-389-system-cache-control.test.ts +0 -107
  48. package/pacakges/ai-sdk-provider/e2e/issues/issue-391-reasoning-effort-values.test.ts +0 -121
  49. package/pacakges/ai-sdk-provider/e2e/issues/issue-392-auto-router-plugin.test.ts +0 -84
  50. package/pacakges/ai-sdk-provider/e2e/issues/issue-394-reasoning-end-signature.test.ts +0 -157
  51. package/pacakges/ai-sdk-provider/e2e/issues/issue-407-token-usage-details.test.ts +0 -141
  52. package/pacakges/ai-sdk-provider/e2e/issues/issue-411-output-object-tools-conflict.test.ts +0 -75
  53. package/pacakges/ai-sdk-provider/e2e/issues/issue-412-mid-stream-termination.test.ts +0 -217
  54. package/pacakges/ai-sdk-provider/e2e/issues/issue-413-reasoning-metadata-bloat.test.ts +0 -292
  55. package/pacakges/ai-sdk-provider/e2e/issues/issue-413-tool-input-end-streaming.test.ts +0 -167
  56. package/pacakges/ai-sdk-provider/e2e/issues/issue-418-gemini-thought-signature.test.ts +0 -171
  57. package/pacakges/ai-sdk-provider/e2e/issues/issue-419-420-finish-reason-usage-fallback.test.ts +0 -87
  58. package/pacakges/ai-sdk-provider/e2e/issues/issue-419-usage-fallback.test.ts +0 -106
  59. package/pacakges/ai-sdk-provider/e2e/issues/issue-422-incomplete-error-information.test.ts +0 -46
  60. package/pacakges/ai-sdk-provider/e2e/issues/issue-423-signature-stripped.test.ts +0 -165
  61. package/pacakges/ai-sdk-provider/e2e/issues/issue-423-streaming-signature-loss.test.ts +0 -161
  62. package/pacakges/ai-sdk-provider/e2e/issues/issue-423-uimessage-roundtrip.test.ts +0 -111
  63. package/pacakges/ai-sdk-provider/e2e/issues/issue-424-anthropic-auto-cache.test.ts +0 -66
  64. package/pacakges/ai-sdk-provider/e2e/issues/issue-432-raw-response-body.test.ts +0 -52
  65. package/pacakges/ai-sdk-provider/e2e/issues/issue-438-gemini-reasoning-redacted.test.ts +0 -120
  66. package/pacakges/ai-sdk-provider/e2e/issues/issue-439-exact-payload.test.ts +0 -108
  67. package/pacakges/ai-sdk-provider/e2e/issues/issue-443-eager-input-streaming.test.ts +0 -204
  68. package/pacakges/ai-sdk-provider/e2e/issues/issue-453-signature-reopen.test.ts +0 -203
  69. package/pacakges/ai-sdk-provider/e2e/issues/issue-474-web-search-server-tool.test.ts +0 -102
  70. package/pacakges/ai-sdk-provider/e2e/issues/issue-483-response-format-strict-option.test.ts +0 -199
  71. package/pacakges/ai-sdk-provider/e2e/issues/issue-484-image-url-query-params.test.ts +0 -165
  72. package/pacakges/ai-sdk-provider/e2e/issues/issue-63-web-search-annotations.test.ts +0 -159
  73. package/pacakges/ai-sdk-provider/e2e/parallel-tool-calls.test.ts +0 -425
  74. package/pacakges/ai-sdk-provider/e2e/pdf-blob/index.test.ts +0 -139
  75. package/pacakges/ai-sdk-provider/e2e/pdf-url/index.test.ts +0 -60
  76. package/pacakges/ai-sdk-provider/e2e/reasoning-effort.test.ts +0 -139
  77. package/pacakges/ai-sdk-provider/e2e/reasoning-multiturn/index.test.ts +0 -69
  78. package/pacakges/ai-sdk-provider/e2e/tools-with-reasoning.test.ts +0 -76
  79. package/pacakges/ai-sdk-provider/e2e/tools.ts +0 -61
  80. package/pacakges/ai-sdk-provider/e2e/usage-accounting.test.ts +0 -55
  81. package/pacakges/ai-sdk-provider/e2e/utils.ts +0 -15
  82. package/pacakges/ai-sdk-provider/e2e/video-generation.test.ts +0 -43
  83. package/pacakges/ai-sdk-provider/e2e/web-search/index.test.ts +0 -50
  84. package/pacakges/ai-sdk-provider/example.env.e2e +0 -3
  85. package/pacakges/ai-sdk-provider/pnpm-lock.yaml +0 -3075
  86. package/pacakges/ai-sdk-provider/pnpm-workspace.yaml +0 -6
  87. package/pacakges/ai-sdk-provider/src/chat/convert-to-openrouter-chat-messages.test.ts +0 -3079
  88. package/pacakges/ai-sdk-provider/src/chat/convert-to-openrouter-chat-messages.ts +0 -618
  89. package/pacakges/ai-sdk-provider/src/chat/errors.test.ts +0 -97
  90. package/pacakges/ai-sdk-provider/src/chat/file-parser-schema.test.ts +0 -112
  91. package/pacakges/ai-sdk-provider/src/chat/file-url-utils.ts +0 -167
  92. package/pacakges/ai-sdk-provider/src/chat/get-tool-choice.ts +0 -42
  93. package/pacakges/ai-sdk-provider/src/chat/index.test.ts +0 -6093
  94. package/pacakges/ai-sdk-provider/src/chat/index.ts +0 -1300
  95. package/pacakges/ai-sdk-provider/src/chat/is-url.ts +0 -15
  96. package/pacakges/ai-sdk-provider/src/chat/large-pdf-response.test.ts +0 -108
  97. package/pacakges/ai-sdk-provider/src/chat/payload-comparison.test.ts +0 -154
  98. package/pacakges/ai-sdk-provider/src/chat/schemas.ts +0 -288
  99. package/pacakges/ai-sdk-provider/src/chat/signature-roundtrip.test.ts +0 -777
  100. package/pacakges/ai-sdk-provider/src/completion/convert-to-openrouter-completion-prompt.ts +0 -150
  101. package/pacakges/ai-sdk-provider/src/completion/index.test.ts +0 -958
  102. package/pacakges/ai-sdk-provider/src/completion/index.ts +0 -441
  103. package/pacakges/ai-sdk-provider/src/completion/schemas.ts +0 -67
  104. package/pacakges/ai-sdk-provider/src/embedding/index.test.ts +0 -262
  105. package/pacakges/ai-sdk-provider/src/embedding/index.ts +0 -113
  106. package/pacakges/ai-sdk-provider/src/embedding/schemas.ts +0 -26
  107. package/pacakges/ai-sdk-provider/src/facade.ts +0 -131
  108. package/pacakges/ai-sdk-provider/src/image/index.test.ts +0 -769
  109. package/pacakges/ai-sdk-provider/src/image/index.ts +0 -206
  110. package/pacakges/ai-sdk-provider/src/image/schemas.ts +0 -48
  111. package/pacakges/ai-sdk-provider/src/index.ts +0 -3
  112. package/pacakges/ai-sdk-provider/src/internal/index.ts +0 -10
  113. package/pacakges/ai-sdk-provider/src/provider.ts +0 -327
  114. package/pacakges/ai-sdk-provider/src/schemas/error-response.test.ts +0 -171
  115. package/pacakges/ai-sdk-provider/src/schemas/error-response.ts +0 -105
  116. package/pacakges/ai-sdk-provider/src/schemas/format.ts +0 -12
  117. package/pacakges/ai-sdk-provider/src/schemas/image.ts +0 -23
  118. package/pacakges/ai-sdk-provider/src/schemas/provider-metadata.ts +0 -91
  119. package/pacakges/ai-sdk-provider/src/schemas/reasoning-details.ts +0 -92
  120. package/pacakges/ai-sdk-provider/src/tests/provider-options.test.ts +0 -234
  121. package/pacakges/ai-sdk-provider/src/tests/stream-usage-accounting.test.ts +0 -409
  122. package/pacakges/ai-sdk-provider/src/tests/usage-accounting.test.ts +0 -555
  123. package/pacakges/ai-sdk-provider/src/tests/web-search-tool.test.ts +0 -319
  124. package/pacakges/ai-sdk-provider/src/tool/web-search.ts +0 -52
  125. package/pacakges/ai-sdk-provider/src/types/index.ts +0 -114
  126. package/pacakges/ai-sdk-provider/src/types/openrouter-api-types.ts +0 -83
  127. package/pacakges/ai-sdk-provider/src/types/openrouter-chat-completions-input.ts +0 -116
  128. package/pacakges/ai-sdk-provider/src/types/openrouter-chat-settings.ts +0 -233
  129. package/pacakges/ai-sdk-provider/src/types/openrouter-completion-settings.ts +0 -39
  130. package/pacakges/ai-sdk-provider/src/types/openrouter-embedding-settings.ts +0 -56
  131. package/pacakges/ai-sdk-provider/src/types/openrouter-image-settings.ts +0 -49
  132. package/pacakges/ai-sdk-provider/src/types/openrouter-video-settings.ts +0 -26
  133. package/pacakges/ai-sdk-provider/src/utils/compute-token-usage.test.ts +0 -186
  134. package/pacakges/ai-sdk-provider/src/utils/compute-token-usage.ts +0 -55
  135. package/pacakges/ai-sdk-provider/src/utils/deterministic-stringify.test.ts +0 -87
  136. package/pacakges/ai-sdk-provider/src/utils/deterministic-stringify.ts +0 -35
  137. package/pacakges/ai-sdk-provider/src/utils/map-finish-reason.ts +0 -43
  138. package/pacakges/ai-sdk-provider/src/utils/reasoning-details-duplicate-tracker.test.ts +0 -324
  139. package/pacakges/ai-sdk-provider/src/utils/reasoning-details-duplicate-tracker.ts +0 -70
  140. package/pacakges/ai-sdk-provider/src/utils/remove-undefined.ts +0 -12
  141. package/pacakges/ai-sdk-provider/src/utils/type-guards.ts +0 -6
  142. package/pacakges/ai-sdk-provider/src/utils/with-stream-error-handling.test.ts +0 -119
  143. package/pacakges/ai-sdk-provider/src/utils/with-stream-error-handling.ts +0 -25
  144. package/pacakges/ai-sdk-provider/src/utils/with-user-agent-suffix.test.ts +0 -133
  145. package/pacakges/ai-sdk-provider/src/utils/with-user-agent-suffix.ts +0 -78
  146. package/pacakges/ai-sdk-provider/src/version.ts +0 -4
  147. package/pacakges/ai-sdk-provider/src/video/index.test.ts +0 -515
  148. package/pacakges/ai-sdk-provider/src/video/index.ts +0 -275
  149. package/pacakges/ai-sdk-provider/src/video/schemas.ts +0 -36
  150. package/pacakges/ai-sdk-provider/tsconfig.json +0 -42
  151. package/pacakges/ai-sdk-provider/tsup.config.ts +0 -28
  152. package/pacakges/ai-sdk-provider/turbo.json +0 -8
  153. package/pacakges/ai-sdk-provider/vitest.e2e.config.ts +0 -21
  154. package/pacakges/ai-sdk-provider/vitest.edge.config.ts +0 -16
  155. package/pacakges/ai-sdk-provider/vitest.issues.config.ts +0 -28
  156. package/pacakges/ai-sdk-provider/vitest.node.config.ts +0 -16
@@ -1,221 +0,0 @@
1
- /**
2
- * Test for Gemini multi-turn tool calls with reasoning/thinking enabled
3
- *
4
- * This test verifies that when using Gemini models with thinking enabled,
5
- * the reasoning_details (thoughtSignature) are properly preserved in tool calls
6
- * and can be sent back in multi-turn conversations.
7
- *
8
- * The providerOptions containing reasoning_details must be preserved when
9
- * passing response.messages from a first request to a second request,
10
- * otherwise Gemini will reject the continuation.
11
- */
12
- import { generateText, streamText, tool } from 'ai';
13
- import { describe, expect, it, vi } from 'vitest';
14
- import { z } from 'zod/v4';
15
- import { writeOutputJsonFile } from '@/e2e/utils';
16
- import { createOpenRouter } from '@/src';
17
-
18
- vi.setConfig({
19
- testTimeout: 120_000,
20
- });
21
-
22
- const weatherTool = tool({
23
- description: 'Gets the current weather for a location',
24
- inputSchema: z.object({
25
- location: z.string().describe('The city and state, e.g. San Francisco, CA'),
26
- }),
27
- execute: async () => {
28
- return {
29
- temperature: 72,
30
- conditions: 'Sunny',
31
- };
32
- },
33
- });
34
-
35
- describe('Gemini multi-turn tool calls with reasoning', () => {
36
- it('should preserve reasoning_details with streamText', async () => {
37
- const openrouter = createOpenRouter({
38
- apiKey: process.env.OPENROUTER_API_KEY,
39
- baseUrl: `${process.env.OPENROUTER_API_BASE}/api/v1`,
40
- });
41
-
42
- const model = openrouter('google/gemini-3-flash-preview');
43
-
44
- const firstResult = await streamText({
45
- model,
46
- system:
47
- 'You are a helpful weather assistant. Use the weatherTool to answer weather questions.',
48
- prompt: 'What is the weather in San Francisco?',
49
- tools: { weatherTool },
50
- providerOptions: {
51
- openrouter: {
52
- includeReasoning: true,
53
- },
54
- },
55
- });
56
-
57
- const firstResponse = await firstResult.response;
58
-
59
- await writeOutputJsonFile({
60
- fileName: 'streamText-firstResponse.ignore.json',
61
- fileData: {
62
- messages: firstResponse.messages,
63
- text: firstResult.text,
64
- },
65
- baseUrl: import.meta.url,
66
- });
67
-
68
- const assistantMessage = firstResponse.messages.find(
69
- (m) => m.role === 'assistant',
70
- );
71
- expect(assistantMessage).toBeDefined();
72
-
73
- const assistantContent = assistantMessage?.content;
74
- expect(Array.isArray(assistantContent)).toBe(true);
75
-
76
- const contentArray = assistantContent as Array<{
77
- type: string;
78
- providerOptions?: Record<string, unknown>;
79
- }>;
80
- const toolCallContent = contentArray.find((c) => c.type === 'tool-call');
81
- expect(toolCallContent).toBeDefined();
82
-
83
- const toolCallProviderOptions = toolCallContent?.providerOptions as
84
- | Record<string, Record<string, unknown>>
85
- | undefined;
86
-
87
- expect(toolCallProviderOptions).toBeDefined();
88
-
89
- const reasoningDetails = toolCallProviderOptions?.openrouter
90
- ?.reasoning_details as
91
- | Array<{ type: string; data?: string; format?: string }>
92
- | undefined;
93
-
94
- expect(reasoningDetails).toBeDefined();
95
- expect(Array.isArray(reasoningDetails)).toBe(true);
96
- expect(reasoningDetails?.length).toBeGreaterThan(0);
97
-
98
- const firstReasoningDetail = reasoningDetails?.[0];
99
- expect(firstReasoningDetail?.type).toBe('reasoning.encrypted');
100
- expect(firstReasoningDetail?.data).toBeDefined();
101
- expect(firstReasoningDetail?.format).toBe('google-gemini-v1');
102
-
103
- const secondResult = await streamText({
104
- model,
105
- system:
106
- 'You are a helpful weather assistant. Use the weatherTool to answer weather questions.',
107
- messages: firstResponse.messages,
108
- tools: { weatherTool },
109
- providerOptions: {
110
- openrouter: {
111
- includeReasoning: true,
112
- },
113
- },
114
- });
115
-
116
- const secondText = await secondResult.text;
117
-
118
- expect(secondText).toBeDefined();
119
- expect(secondText.length).toBeGreaterThan(0);
120
-
121
- const lowerText = secondText.toLowerCase();
122
- const hasWeatherInfo =
123
- lowerText.includes('72') ||
124
- lowerText.includes('sunny') ||
125
- lowerText.includes('weather') ||
126
- lowerText.includes('san francisco');
127
- expect(hasWeatherInfo).toBe(true);
128
- });
129
-
130
- it('should preserve reasoning_details with generateText', async () => {
131
- const openrouter = createOpenRouter({
132
- apiKey: process.env.OPENROUTER_API_KEY,
133
- baseUrl: `${process.env.OPENROUTER_API_BASE}/api/v1`,
134
- });
135
-
136
- const model = openrouter('google/gemini-3-flash-preview');
137
-
138
- const firstResult = await generateText({
139
- model,
140
- system:
141
- 'You are a helpful weather assistant. Use the weatherTool to answer weather questions.',
142
- prompt: 'What is the weather in San Francisco?',
143
- tools: { weatherTool },
144
- providerOptions: {
145
- openrouter: {
146
- includeReasoning: true,
147
- },
148
- },
149
- });
150
-
151
- const firstResponse = await firstResult.response;
152
-
153
- await writeOutputJsonFile({
154
- fileName: 'generateText-firstResponse.ignore.json',
155
- fileData: {
156
- messages: firstResponse.messages,
157
- text: firstResult.text,
158
- },
159
- baseUrl: import.meta.url,
160
- });
161
-
162
- const assistantMessage = firstResponse.messages.find(
163
- (m) => m.role === 'assistant',
164
- );
165
- expect(assistantMessage).toBeDefined();
166
-
167
- const assistantContent = assistantMessage?.content;
168
- expect(Array.isArray(assistantContent)).toBe(true);
169
-
170
- const contentArray = assistantContent as Array<{
171
- type: string;
172
- providerOptions?: Record<string, unknown>;
173
- }>;
174
- const toolCallContent = contentArray.find((c) => c.type === 'tool-call');
175
- expect(toolCallContent).toBeDefined();
176
-
177
- const toolCallProviderOptions = toolCallContent?.providerOptions as
178
- | Record<string, Record<string, unknown>>
179
- | undefined;
180
-
181
- expect(toolCallProviderOptions).toBeDefined();
182
-
183
- const reasoningDetails = toolCallProviderOptions?.openrouter
184
- ?.reasoning_details as
185
- | Array<{ type: string; data?: string; format?: string }>
186
- | undefined;
187
-
188
- expect(reasoningDetails).toBeDefined();
189
- expect(Array.isArray(reasoningDetails)).toBe(true);
190
- expect(reasoningDetails?.length).toBeGreaterThan(0);
191
-
192
- const firstReasoningDetail = reasoningDetails?.[0];
193
- expect(firstReasoningDetail?.type).toBe('reasoning.encrypted');
194
- expect(firstReasoningDetail?.data).toBeDefined();
195
- expect(firstReasoningDetail?.format).toBe('google-gemini-v1');
196
-
197
- const secondResult = await generateText({
198
- model,
199
- system:
200
- 'You are a helpful weather assistant. Use the weatherTool to answer weather questions.',
201
- messages: firstResponse.messages,
202
- tools: { weatherTool },
203
- providerOptions: {
204
- openrouter: {
205
- includeReasoning: true,
206
- },
207
- },
208
- });
209
-
210
- expect(secondResult.text).toBeDefined();
211
- expect(secondResult.text.length).toBeGreaterThan(0);
212
-
213
- const lowerText = secondResult.text.toLowerCase();
214
- const hasWeatherInfo =
215
- lowerText.includes('72') ||
216
- lowerText.includes('sunny') ||
217
- lowerText.includes('weather') ||
218
- lowerText.includes('san francisco');
219
- expect(hasWeatherInfo).toBe(true);
220
- });
221
- });
@@ -1,88 +0,0 @@
1
- /**
2
- * Regression test for GitHub issue #160
3
- * https://github.com/OpenRouterTeam/ai-sdk-provider/issues/160
4
- *
5
- * Reported error: toolCallId is not unique across models/providers, causing
6
- * tool result matching failures in multi-turn conversations. When tool call
7
- * IDs are empty, null, or duplicated, tool results get mismatched or lost.
8
- *
9
- * A comment from Sept 2025 noted that Gemini added random IDs, resolving
10
- * the issue at the API level. This test verifies that the SDK returns
11
- * unique tool call IDs for parallel tool calls across both streaming and
12
- * non-streaming paths.
13
- */
14
- import { generateText, streamText, tool } from 'ai';
15
- import { describe, expect, it, vi } from 'vitest';
16
- import { z } from 'zod/v4';
17
- import { createOpenRouter } from '@/src';
18
-
19
- vi.setConfig({
20
- testTimeout: 60_000,
21
- });
22
-
23
- describe('Issue #160: toolCallId uniqueness for parallel tool calls', () => {
24
- const openrouter = createOpenRouter({
25
- apiKey: process.env.OPENROUTER_API_KEY,
26
- baseUrl: `${process.env.OPENROUTER_API_BASE}/api/v1`,
27
- });
28
-
29
- // Use the model from the issue report (Gemini) which was known to
30
- // return non-unique tool call IDs.
31
- const model = openrouter('google/gemini-2.5-flash');
32
-
33
- const weatherTool = tool({
34
- description: 'Gets the weather for a given city',
35
- inputSchema: z.object({
36
- city: z.string().describe('The city to get the weather for'),
37
- }),
38
- execute: async ({ city }) => {
39
- return { city, temperature: 22, conditions: 'sunny' };
40
- },
41
- });
42
-
43
- it('should return unique toolCallIds for parallel tool calls (non-streaming)', async () => {
44
- const response = await generateText({
45
- model,
46
- prompt:
47
- 'What is the weather in Tokyo, London, and Paris? Call the get_weather tool for each city in parallel.',
48
- tools: { get_weather: weatherTool },
49
- toolChoice: 'required',
50
- });
51
-
52
- const toolCalls = response.steps.flatMap((step) => step.toolCalls || []);
53
- expect(toolCalls.length).toBeGreaterThanOrEqual(2);
54
-
55
- // All tool call IDs must be unique and non-empty
56
- const ids = toolCalls.map((tc) => tc.toolCallId);
57
- expect(new Set(ids).size).toBe(ids.length);
58
- for (const id of ids) {
59
- expect(id).toBeTruthy();
60
- }
61
- });
62
-
63
- it('should return unique toolCallIds for parallel tool calls (streaming)', async () => {
64
- const response = await streamText({
65
- model,
66
- prompt:
67
- 'What is the weather in Tokyo, London, and Paris? Call the get_weather tool for each city in parallel.',
68
- tools: { get_weather: weatherTool },
69
- toolChoice: 'required',
70
- });
71
-
72
- const toolCalls: Array<{ toolCallId: string }> = [];
73
- for await (const part of response.fullStream) {
74
- if (part.type === 'tool-call') {
75
- toolCalls.push({ toolCallId: part.toolCallId });
76
- }
77
- }
78
-
79
- expect(toolCalls.length).toBeGreaterThanOrEqual(2);
80
-
81
- // All tool call IDs must be unique and non-empty
82
- const ids = toolCalls.map((tc) => tc.toolCallId);
83
- expect(new Set(ids).size).toBe(ids.length);
84
- for (const id of ids) {
85
- expect(id).toBeTruthy();
86
- }
87
- });
88
- });
@@ -1,129 +0,0 @@
1
- /**
2
- * Regression test for GitHub Issue #166 — finish_reason is null for some models
3
- * https://github.com/OpenRouterTeam/ai-sdk-provider/issues/166
4
- *
5
- * Reported error: Some models return `finish_reason: null` in streaming responses
6
- * even when tool calls are present. This causes the AI SDK to see finishReason as
7
- * 'other' instead of 'tool-calls', breaking agentic loops that rely on
8
- * finishReason === 'tool-calls' to continue iterating.
9
- *
10
- * Models mentioned: google/gemini-2.5-flash, various free-tier models.
11
- *
12
- * The fix: In both doGenerate and doStream, when tool calls are present but
13
- * finishReason maps to 'other' (which includes null/undefined/unrecognized values),
14
- * override finishReason to 'tool-calls' so agentic loops continue correctly.
15
- */
16
- import { generateText, stepCountIs, streamText, tool } from 'ai';
17
- import { describe, expect, it, vi } from 'vitest';
18
- import { z } from 'zod/v4';
19
- import { createOpenRouter } from '@/src';
20
-
21
- vi.setConfig({
22
- testTimeout: 120_000,
23
- });
24
-
25
- const weatherTool = tool({
26
- description: 'Get the current weather for a location',
27
- inputSchema: z.object({
28
- city: z.string().describe('The city to get weather for'),
29
- }),
30
- execute: async () => ({
31
- temperature: 22,
32
- condition: 'sunny',
33
- }),
34
- });
35
-
36
- describe('Issue #166: finish_reason null should not break agentic loops', () => {
37
- const provider = createOpenRouter({
38
- apiKey: process.env.OPENROUTER_API_KEY,
39
- baseUrl: `${process.env.OPENROUTER_API_BASE}/api/v1`,
40
- });
41
-
42
- it('should return tool-calls finishReason when streaming with tools (google/gemini-2.5-flash)', async () => {
43
- const response = streamText({
44
- model: provider('google/gemini-2.5-flash'),
45
- messages: [
46
- {
47
- role: 'user',
48
- content: 'What is the weather in Tokyo? Use the get_weather tool.',
49
- },
50
- ],
51
- tools: {
52
- get_weather: weatherTool,
53
- },
54
- });
55
-
56
- await response.consumeStream();
57
- const finishReason = await response.finishReason;
58
- const toolCalls = await response.toolCalls;
59
-
60
- // If the model made tool calls, finishReason must be 'tool-calls'
61
- if (toolCalls && toolCalls.length > 0) {
62
- expect(finishReason).toBe('tool-calls');
63
- }
64
- });
65
-
66
- it('should return tool-calls finishReason when streaming with tools (openai/gpt-4o-mini)', async () => {
67
- const response = streamText({
68
- model: provider('openai/gpt-4o-mini'),
69
- messages: [
70
- {
71
- role: 'user',
72
- content: 'What is the weather in Tokyo? Use the get_weather tool.',
73
- },
74
- ],
75
- tools: {
76
- get_weather: weatherTool,
77
- },
78
- });
79
-
80
- await response.consumeStream();
81
- const finishReason = await response.finishReason;
82
- const toolCalls = await response.toolCalls;
83
-
84
- if (toolCalls && toolCalls.length > 0) {
85
- expect(finishReason).toBe('tool-calls');
86
- }
87
- });
88
-
89
- it('should return tool-calls finishReason in generateText with tools (google/gemini-2.5-flash)', async () => {
90
- const result = await generateText({
91
- model: provider('google/gemini-2.5-flash'),
92
- messages: [
93
- {
94
- role: 'user',
95
- content: 'What is the weather in Tokyo? Use the get_weather tool.',
96
- },
97
- ],
98
- tools: {
99
- get_weather: weatherTool,
100
- },
101
- });
102
-
103
- if (result.toolCalls && result.toolCalls.length > 0) {
104
- expect(result.finishReason).toBe('tool-calls');
105
- }
106
- });
107
-
108
- it('should complete a full agentic loop without breaking (google/gemini-2.5-flash)', async () => {
109
- const result = await generateText({
110
- model: provider('google/gemini-2.5-flash'),
111
- messages: [
112
- {
113
- role: 'user',
114
- content: 'What is the weather in Tokyo?',
115
- },
116
- ],
117
- tools: {
118
- get_weather: weatherTool,
119
- },
120
- stopWhen: stepCountIs(3),
121
- });
122
-
123
- // The agentic loop should complete without errors.
124
- // If finish_reason were incorrectly mapped, the loop would stop
125
- // after the first tool call instead of continuing to produce a final answer.
126
- expect(result.steps.length).toBeGreaterThanOrEqual(1);
127
- expect(result.text.length).toBeGreaterThan(0);
128
- });
129
- });
@@ -1,70 +0,0 @@
1
- /**
2
- * Regression test for GitHub issue #171
3
- * https://github.com/OpenRouterTeam/ai-sdk-provider/issues/171
4
- *
5
- * Issue: "Cache broken when using tools"
6
- *
7
- * When the AI SDK deserializes and re-serializes tool call arguments across
8
- * turns, the key insertion order may change (e.g., {"city":"Tokyo","unit":"celsius"}
9
- * becomes {"unit":"celsius","city":"Tokyo"}). This produces different strings
10
- * for semantically identical objects, which breaks prompt caching because the
11
- * cache key includes the serialized tool call arguments verbatim.
12
- *
13
- * This test verifies that tool call arguments are serialized deterministically
14
- * in a multi-turn conversation where tool results are sent back.
15
- */
16
- import { generateText, tool } from 'ai';
17
- import { describe, expect, it, vi } from 'vitest';
18
- import { z } from 'zod/v4';
19
- import { createOpenRouter } from '@/src';
20
-
21
- vi.setConfig({
22
- testTimeout: 60_000,
23
- });
24
-
25
- describe('Issue #171: Cache broken when using tools - key ordering', () => {
26
- const openrouter = createOpenRouter({
27
- apiKey: process.env.OPENROUTER_API_KEY,
28
- baseUrl: `${process.env.OPENROUTER_API_BASE}/api/v1`,
29
- });
30
-
31
- const model = openrouter('openai/gpt-4.1-mini');
32
-
33
- it('should produce cache-friendly tool call arguments in a multi-turn tool conversation', async () => {
34
- const getWeather = tool({
35
- description:
36
- 'Get the current weather for a given city. Always provide both city and unit.',
37
- inputSchema: z.object({
38
- city: z.string().describe('The city name'),
39
- unit: z.enum(['celsius', 'fahrenheit']).describe('Temperature unit'),
40
- }),
41
- execute: async ({ city, unit }) => {
42
- return {
43
- city,
44
- unit,
45
- temperature: unit === 'celsius' ? 22 : 72,
46
- condition: 'sunny',
47
- };
48
- },
49
- });
50
-
51
- // First turn: model calls the tool
52
- const response = await generateText({
53
- model,
54
- system:
55
- 'You are a weather assistant. When asked about weather, use the getWeather tool with both city and unit parameters.',
56
- prompt:
57
- 'What is the weather in Tokyo in celsius? Use the getWeather tool.',
58
- tools: { getWeather },
59
- toolChoice: 'required',
60
- });
61
-
62
- expect(response.text).toBeDefined();
63
- expect(response.finishReason).toBeDefined();
64
-
65
- // Verify tool was called successfully
66
- const toolCalls = response.steps.flatMap((step) => step.toolCalls || []);
67
- expect(toolCalls.length).toBeGreaterThan(0);
68
- expect(toolCalls[0]?.toolName).toBe('getWeather');
69
- });
70
- });