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.
- package/dist/cli/commands/config-cmd.js +1 -1
- package/dist/cli/config.js +3 -2
- package/dist/config.d.ts +3 -3
- package/dist/config.js +11 -5
- package/dist/llm.js +3 -3
- package/dist/vite-plugin.js +3 -1
- package/package.json +3 -2
- package/skills/ai-world-sdk/SKILL.md +1 -0
- package/skills/ai-world-sdk/docs/cli-usage.md +7 -4
- package/pacakges/ai-sdk-provider/.changeset/README.md +0 -34
- package/pacakges/ai-sdk-provider/.changeset/config.json +0 -13
- package/pacakges/ai-sdk-provider/.github/PULL_REQUEST_TEMPLATE.md +0 -16
- package/pacakges/ai-sdk-provider/.github/workflows/ci.yaml +0 -24
- package/pacakges/ai-sdk-provider/.github/workflows/publish-manual.yaml +0 -36
- package/pacakges/ai-sdk-provider/.github/workflows/publish.yaml +0 -107
- package/pacakges/ai-sdk-provider/.vscode/settings.json +0 -32
- package/pacakges/ai-sdk-provider/CHANGELOG.md +0 -631
- package/pacakges/ai-sdk-provider/CLAUDE.md +0 -79
- package/pacakges/ai-sdk-provider/CONTRIBUTING.md +0 -241
- package/pacakges/ai-sdk-provider/LICENSE +0 -201
- package/pacakges/ai-sdk-provider/README.md +0 -426
- package/pacakges/ai-sdk-provider/biome.json +0 -122
- package/pacakges/ai-sdk-provider/e2e/cache-control.test.ts +0 -74
- package/pacakges/ai-sdk-provider/e2e/embeddings/index.test.ts +0 -128
- package/pacakges/ai-sdk-provider/e2e/fixtures/pdfs/generate-pdfs.sh +0 -181
- package/pacakges/ai-sdk-provider/e2e/fixtures/pdfs/large.json +0 -7
- package/pacakges/ai-sdk-provider/e2e/fixtures/pdfs/large.pdf +41 -12894
- package/pacakges/ai-sdk-provider/e2e/gemini/reasoning-multiturn.test.ts +0 -221
- package/pacakges/ai-sdk-provider/e2e/issues/issue-160-toolcallid-uniqueness.test.ts +0 -88
- package/pacakges/ai-sdk-provider/e2e/issues/issue-166-finish-reason-null.test.ts +0 -129
- package/pacakges/ai-sdk-provider/e2e/issues/issue-171-cache-tool-key-ordering.test.ts +0 -70
- package/pacakges/ai-sdk-provider/e2e/issues/issue-181-tool-response-image.test.ts +0 -199
- package/pacakges/ai-sdk-provider/e2e/issues/issue-190-streamobject-flush-error.test.ts +0 -114
- package/pacakges/ai-sdk-provider/e2e/issues/issue-194-grok-invalid-json.test.ts +0 -93
- package/pacakges/ai-sdk-provider/e2e/issues/issue-196-anthropic-1h-cache-ttl.test.ts +0 -85
- package/pacakges/ai-sdk-provider/e2e/issues/issue-199-openai-pdf-processing.test.ts +0 -102
- package/pacakges/ai-sdk-provider/e2e/issues/issue-212-anthropic-web-search-online.test.ts +0 -128
- package/pacakges/ai-sdk-provider/e2e/issues/issue-234-prompt-caching.test.ts +0 -201
- package/pacakges/ai-sdk-provider/e2e/issues/issue-237-reasoning-linebreaks.test.ts +0 -103
- package/pacakges/ai-sdk-provider/e2e/issues/issue-248-gemini-web-search-empty-response.test.ts +0 -210
- package/pacakges/ai-sdk-provider/e2e/issues/issue-269-image-size-parameter.test.ts +0 -208
- package/pacakges/ai-sdk-provider/e2e/issues/issue-287-tool-calls-missing-arguments.test.ts +0 -87
- package/pacakges/ai-sdk-provider/e2e/issues/issue-341-cache-control-last-text-part.test.ts +0 -138
- package/pacakges/ai-sdk-provider/e2e/issues/issue-383-video-url-support.test.ts +0 -120
- package/pacakges/ai-sdk-provider/e2e/issues/issue-386-image-files-parameter.test.ts +0 -109
- package/pacakges/ai-sdk-provider/e2e/issues/issue-387-temperature-settings.test.ts +0 -113
- package/pacakges/ai-sdk-provider/e2e/issues/issue-389-system-cache-control.test.ts +0 -107
- package/pacakges/ai-sdk-provider/e2e/issues/issue-391-reasoning-effort-values.test.ts +0 -121
- package/pacakges/ai-sdk-provider/e2e/issues/issue-392-auto-router-plugin.test.ts +0 -84
- package/pacakges/ai-sdk-provider/e2e/issues/issue-394-reasoning-end-signature.test.ts +0 -157
- package/pacakges/ai-sdk-provider/e2e/issues/issue-407-token-usage-details.test.ts +0 -141
- package/pacakges/ai-sdk-provider/e2e/issues/issue-411-output-object-tools-conflict.test.ts +0 -75
- package/pacakges/ai-sdk-provider/e2e/issues/issue-412-mid-stream-termination.test.ts +0 -217
- package/pacakges/ai-sdk-provider/e2e/issues/issue-413-reasoning-metadata-bloat.test.ts +0 -292
- package/pacakges/ai-sdk-provider/e2e/issues/issue-413-tool-input-end-streaming.test.ts +0 -167
- package/pacakges/ai-sdk-provider/e2e/issues/issue-418-gemini-thought-signature.test.ts +0 -171
- package/pacakges/ai-sdk-provider/e2e/issues/issue-419-420-finish-reason-usage-fallback.test.ts +0 -87
- package/pacakges/ai-sdk-provider/e2e/issues/issue-419-usage-fallback.test.ts +0 -106
- package/pacakges/ai-sdk-provider/e2e/issues/issue-422-incomplete-error-information.test.ts +0 -46
- package/pacakges/ai-sdk-provider/e2e/issues/issue-423-signature-stripped.test.ts +0 -165
- package/pacakges/ai-sdk-provider/e2e/issues/issue-423-streaming-signature-loss.test.ts +0 -161
- package/pacakges/ai-sdk-provider/e2e/issues/issue-423-uimessage-roundtrip.test.ts +0 -111
- package/pacakges/ai-sdk-provider/e2e/issues/issue-424-anthropic-auto-cache.test.ts +0 -66
- package/pacakges/ai-sdk-provider/e2e/issues/issue-432-raw-response-body.test.ts +0 -52
- package/pacakges/ai-sdk-provider/e2e/issues/issue-438-gemini-reasoning-redacted.test.ts +0 -120
- package/pacakges/ai-sdk-provider/e2e/issues/issue-439-exact-payload.test.ts +0 -108
- package/pacakges/ai-sdk-provider/e2e/issues/issue-443-eager-input-streaming.test.ts +0 -204
- package/pacakges/ai-sdk-provider/e2e/issues/issue-453-signature-reopen.test.ts +0 -203
- package/pacakges/ai-sdk-provider/e2e/issues/issue-474-web-search-server-tool.test.ts +0 -102
- package/pacakges/ai-sdk-provider/e2e/issues/issue-483-response-format-strict-option.test.ts +0 -199
- package/pacakges/ai-sdk-provider/e2e/issues/issue-484-image-url-query-params.test.ts +0 -165
- package/pacakges/ai-sdk-provider/e2e/issues/issue-63-web-search-annotations.test.ts +0 -159
- package/pacakges/ai-sdk-provider/e2e/parallel-tool-calls.test.ts +0 -425
- package/pacakges/ai-sdk-provider/e2e/pdf-blob/index.test.ts +0 -139
- package/pacakges/ai-sdk-provider/e2e/pdf-url/index.test.ts +0 -60
- package/pacakges/ai-sdk-provider/e2e/reasoning-effort.test.ts +0 -139
- package/pacakges/ai-sdk-provider/e2e/reasoning-multiturn/index.test.ts +0 -69
- package/pacakges/ai-sdk-provider/e2e/tools-with-reasoning.test.ts +0 -76
- package/pacakges/ai-sdk-provider/e2e/tools.ts +0 -61
- package/pacakges/ai-sdk-provider/e2e/usage-accounting.test.ts +0 -55
- package/pacakges/ai-sdk-provider/e2e/utils.ts +0 -15
- package/pacakges/ai-sdk-provider/e2e/video-generation.test.ts +0 -43
- package/pacakges/ai-sdk-provider/e2e/web-search/index.test.ts +0 -50
- package/pacakges/ai-sdk-provider/example.env.e2e +0 -3
- package/pacakges/ai-sdk-provider/pnpm-lock.yaml +0 -3075
- package/pacakges/ai-sdk-provider/pnpm-workspace.yaml +0 -6
- package/pacakges/ai-sdk-provider/src/chat/convert-to-openrouter-chat-messages.test.ts +0 -3079
- package/pacakges/ai-sdk-provider/src/chat/convert-to-openrouter-chat-messages.ts +0 -618
- package/pacakges/ai-sdk-provider/src/chat/errors.test.ts +0 -97
- package/pacakges/ai-sdk-provider/src/chat/file-parser-schema.test.ts +0 -112
- package/pacakges/ai-sdk-provider/src/chat/file-url-utils.ts +0 -167
- package/pacakges/ai-sdk-provider/src/chat/get-tool-choice.ts +0 -42
- package/pacakges/ai-sdk-provider/src/chat/index.test.ts +0 -6093
- package/pacakges/ai-sdk-provider/src/chat/index.ts +0 -1300
- package/pacakges/ai-sdk-provider/src/chat/is-url.ts +0 -15
- package/pacakges/ai-sdk-provider/src/chat/large-pdf-response.test.ts +0 -108
- package/pacakges/ai-sdk-provider/src/chat/payload-comparison.test.ts +0 -154
- package/pacakges/ai-sdk-provider/src/chat/schemas.ts +0 -288
- package/pacakges/ai-sdk-provider/src/chat/signature-roundtrip.test.ts +0 -777
- package/pacakges/ai-sdk-provider/src/completion/convert-to-openrouter-completion-prompt.ts +0 -150
- package/pacakges/ai-sdk-provider/src/completion/index.test.ts +0 -958
- package/pacakges/ai-sdk-provider/src/completion/index.ts +0 -441
- package/pacakges/ai-sdk-provider/src/completion/schemas.ts +0 -67
- package/pacakges/ai-sdk-provider/src/embedding/index.test.ts +0 -262
- package/pacakges/ai-sdk-provider/src/embedding/index.ts +0 -113
- package/pacakges/ai-sdk-provider/src/embedding/schemas.ts +0 -26
- package/pacakges/ai-sdk-provider/src/facade.ts +0 -131
- package/pacakges/ai-sdk-provider/src/image/index.test.ts +0 -769
- package/pacakges/ai-sdk-provider/src/image/index.ts +0 -206
- package/pacakges/ai-sdk-provider/src/image/schemas.ts +0 -48
- package/pacakges/ai-sdk-provider/src/index.ts +0 -3
- package/pacakges/ai-sdk-provider/src/internal/index.ts +0 -10
- package/pacakges/ai-sdk-provider/src/provider.ts +0 -327
- package/pacakges/ai-sdk-provider/src/schemas/error-response.test.ts +0 -171
- package/pacakges/ai-sdk-provider/src/schemas/error-response.ts +0 -105
- package/pacakges/ai-sdk-provider/src/schemas/format.ts +0 -12
- package/pacakges/ai-sdk-provider/src/schemas/image.ts +0 -23
- package/pacakges/ai-sdk-provider/src/schemas/provider-metadata.ts +0 -91
- package/pacakges/ai-sdk-provider/src/schemas/reasoning-details.ts +0 -92
- package/pacakges/ai-sdk-provider/src/tests/provider-options.test.ts +0 -234
- package/pacakges/ai-sdk-provider/src/tests/stream-usage-accounting.test.ts +0 -409
- package/pacakges/ai-sdk-provider/src/tests/usage-accounting.test.ts +0 -555
- package/pacakges/ai-sdk-provider/src/tests/web-search-tool.test.ts +0 -319
- package/pacakges/ai-sdk-provider/src/tool/web-search.ts +0 -52
- package/pacakges/ai-sdk-provider/src/types/index.ts +0 -114
- package/pacakges/ai-sdk-provider/src/types/openrouter-api-types.ts +0 -83
- package/pacakges/ai-sdk-provider/src/types/openrouter-chat-completions-input.ts +0 -116
- package/pacakges/ai-sdk-provider/src/types/openrouter-chat-settings.ts +0 -233
- package/pacakges/ai-sdk-provider/src/types/openrouter-completion-settings.ts +0 -39
- package/pacakges/ai-sdk-provider/src/types/openrouter-embedding-settings.ts +0 -56
- package/pacakges/ai-sdk-provider/src/types/openrouter-image-settings.ts +0 -49
- package/pacakges/ai-sdk-provider/src/types/openrouter-video-settings.ts +0 -26
- package/pacakges/ai-sdk-provider/src/utils/compute-token-usage.test.ts +0 -186
- package/pacakges/ai-sdk-provider/src/utils/compute-token-usage.ts +0 -55
- package/pacakges/ai-sdk-provider/src/utils/deterministic-stringify.test.ts +0 -87
- package/pacakges/ai-sdk-provider/src/utils/deterministic-stringify.ts +0 -35
- package/pacakges/ai-sdk-provider/src/utils/map-finish-reason.ts +0 -43
- package/pacakges/ai-sdk-provider/src/utils/reasoning-details-duplicate-tracker.test.ts +0 -324
- package/pacakges/ai-sdk-provider/src/utils/reasoning-details-duplicate-tracker.ts +0 -70
- package/pacakges/ai-sdk-provider/src/utils/remove-undefined.ts +0 -12
- package/pacakges/ai-sdk-provider/src/utils/type-guards.ts +0 -6
- package/pacakges/ai-sdk-provider/src/utils/with-stream-error-handling.test.ts +0 -119
- package/pacakges/ai-sdk-provider/src/utils/with-stream-error-handling.ts +0 -25
- package/pacakges/ai-sdk-provider/src/utils/with-user-agent-suffix.test.ts +0 -133
- package/pacakges/ai-sdk-provider/src/utils/with-user-agent-suffix.ts +0 -78
- package/pacakges/ai-sdk-provider/src/version.ts +0 -4
- package/pacakges/ai-sdk-provider/src/video/index.test.ts +0 -515
- package/pacakges/ai-sdk-provider/src/video/index.ts +0 -275
- package/pacakges/ai-sdk-provider/src/video/schemas.ts +0 -36
- package/pacakges/ai-sdk-provider/tsconfig.json +0 -42
- package/pacakges/ai-sdk-provider/tsup.config.ts +0 -28
- package/pacakges/ai-sdk-provider/turbo.json +0 -8
- package/pacakges/ai-sdk-provider/vitest.e2e.config.ts +0 -21
- package/pacakges/ai-sdk-provider/vitest.edge.config.ts +0 -16
- package/pacakges/ai-sdk-provider/vitest.issues.config.ts +0 -28
- 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
|
-
});
|