diff --git a/package-lock.json b/package-lock.json index 9c69837..c3fd15c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,11 +22,11 @@ "sqlite-vec": "0.1.9" }, "devDependencies": { - "@ai-sdk/anthropic": "^3.0.125", - "@ai-sdk/google": "^3.0.129", - "@ai-sdk/openai": "^3.0.121", - "@ai-sdk/openai-compatible": "^2.0.80", - "@ai-sdk/provider": "^3.0.18", + "@ai-sdk/anthropic": "^4.0.69", + "@ai-sdk/google": "^4.0.86", + "@ai-sdk/openai": "^4.0.81", + "@ai-sdk/openai-compatible": "^3.0.60", + "@ai-sdk/provider": "^4.0.20", "@dagrejs/dagre": "^1.1.8", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", @@ -68,7 +68,7 @@ "@types/turndown": "^5.0.6", "@vitejs/plugin-react": "^5.1.2", "@xyflow/react": "^12.10.0", - "ai": "^6.0.297", + "ai": "^7.0.123", "autoprefixer": "^10.4.23", "cheerio": "^1.2.0", "class-variance-authority": "^0.7.1", @@ -132,123 +132,177 @@ "license": "MIT" }, "node_modules/@ai-sdk/anthropic": { - "version": "3.0.125", - "resolved": "https://registry.npmmirror.com/@ai-sdk/anthropic/-/anthropic-3.0.125.tgz", - "integrity": "sha512-oCBy62UBmf94aptAkb+0kB5ac4503cta2SXEGAGNweEscRmeSWsDVx8YWVdJ5vZqFoyJdyWTrKrZ2J9IodWbwA==", + "version": "4.0.69", + "resolved": "https://registry.npmmirror.com/@ai-sdk/anthropic/-/anthropic-4.0.69.tgz", + "integrity": "sha512-8qO73NwcxpUb4onZ4V85RGZbXYSyy2AhOw4lgs1BF8MY0iGk9ti00Qt//y1wxCMXK0IvB2YGDcA0vxigWfi+/Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "3.0.18", - "@ai-sdk/provider-utils": "4.0.56" + "@ai-sdk/provider": "4.0.20", + "@ai-sdk/provider-utils": "5.0.52" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "node_modules/@ai-sdk/gateway": { - "version": "3.0.206", - "resolved": "https://registry.npmmirror.com/@ai-sdk/gateway/-/gateway-3.0.206.tgz", - "integrity": "sha512-AdfedgmKysARpRA1oTW9r/yYyA1kHw1MDL9LzYV1aTCKGZxj2xltxybxzMK4ioE6Dh0dhAXnQ2JSTA7HKYFlaQ==", + "version": "4.0.101", + "resolved": "https://registry.npmmirror.com/@ai-sdk/gateway/-/gateway-4.0.101.tgz", + "integrity": "sha512-lBMXWbNSAb3eQD/F8ja9Egi32PsL9lLajC4DUvionnWO8V+X0nYw9hWJVBJdZ0SftfE/OyKySkoHRfIg/UafYg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "3.0.18", - "@ai-sdk/provider-utils": "4.0.56", + "@ai-sdk/provider": "4.0.20", + "@ai-sdk/provider-utils": "5.0.52", "@vercel/oidc": "3.2.0" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "node_modules/@ai-sdk/google": { - "version": "3.0.129", - "resolved": "https://registry.npmmirror.com/@ai-sdk/google/-/google-3.0.129.tgz", - "integrity": "sha512-WmPqvn/+y4i6YAvYPttprB8SEuyj0c/o1hqve0p3u5CuxnZ0NV1lt6PCHrnnAi4yP1EEV5+QJAAR1P7tB45bsQ==", + "version": "4.0.86", + "resolved": "https://registry.npmmirror.com/@ai-sdk/google/-/google-4.0.86.tgz", + "integrity": "sha512-LfYXASE06h7u0WWI9G5Zb6/7HscVg3+V+0UHZUTgrD17lZi6R5LfsHHJiPRO9aiAd1DbbIvXRvHr/dL9eKbf4g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "3.0.18", - "@ai-sdk/provider-utils": "4.0.56" + "@ai-sdk/provider": "4.0.20", + "@ai-sdk/provider-utils": "5.0.52" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "node_modules/@ai-sdk/openai": { - "version": "3.0.121", - "resolved": "https://registry.npmmirror.com/@ai-sdk/openai/-/openai-3.0.121.tgz", - "integrity": "sha512-3ZBWdXcwC35W6Gj4FbhogEmAvVxyArzGEHvoADWiSMJffyirKYE30Yy5oM0Ct0fpmHu26mgTrkNugSA/Nm6pXA==", + "version": "4.0.81", + "resolved": "https://registry.npmmirror.com/@ai-sdk/openai/-/openai-4.0.81.tgz", + "integrity": "sha512-xLKx+4pbRvKF1gaXckfg0gQxDppi5SVYQsB/gkkzElCjAZ1OYDJB7xp+CvXIZGI6fzspricSfOl7MxEDvEdilg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "3.0.18", - "@ai-sdk/provider-utils": "4.0.56" + "@ai-sdk/provider": "4.0.19", + "@ai-sdk/provider-utils": "5.0.51" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "node_modules/@ai-sdk/openai-compatible": { - "version": "2.0.80", - "resolved": "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.80.tgz", - "integrity": "sha512-v/X0WZYuQ3qrWkHAI3pbnkx1NMS1T7XGAQvpZxlbela7QwYJ/vAK9Og6mio82DbTbrSLIpspdPxx0itWrWRaYw==", + "version": "3.0.60", + "resolved": "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-3.0.60.tgz", + "integrity": "sha512-+yuu4jzanve1HQI0YQXS+nvhX6kc4v5O1f7u355fSxytrLrFfxR42Mgl1uJPaEQTVxR+cyyA2NV5lUFxv7Maiw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "3.0.18", - "@ai-sdk/provider-utils": "4.0.56" + "@ai-sdk/provider": "4.0.20", + "@ai-sdk/provider-utils": "5.0.52" }, "engines": { - "node": ">=18" + "node": ">=22" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/openai/node_modules/@ai-sdk/provider": { + "version": "4.0.19", + "resolved": "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-4.0.19.tgz", + "integrity": "sha512-ryVZSSfmpEsr4bM8rbt9cSBWal+KGYRrm57C+k/hiPQHMiMahaqg2bPF0MpxSUWY8mGpt7s/ajM6PAqNTMKqPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@ai-sdk/openai/node_modules/@ai-sdk/provider-utils": { + "version": "5.0.51", + "resolved": "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-5.0.51.tgz", + "integrity": "sha512-z0IsEsDx+dxlZqyCtDjMbBj16NGmxFVTgQcP3+n3EFm9ttNU6wjOgL/kgvaPwtmZM6F7JbtRuLyzr6WOfo5DMw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "4.0.19", + "@standard-schema/spec": "^1.1.0", + "@workflow/serde": "4.1.0", + "eventsource-parser": "^3.0.8", + "undici": "^7.29.0" + }, + "engines": { + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, + "node_modules/@ai-sdk/openai/node_modules/undici": { + "version": "7.30.0", + "resolved": "https://registry.npmmirror.com/undici/-/undici-7.30.0.tgz", + "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/@ai-sdk/provider": { - "version": "3.0.18", - "resolved": "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-3.0.18.tgz", - "integrity": "sha512-IpefF5ssZVOZCD4Wu7zxt6PwZoGT2HLVjxpJ10Bjx6EAmRfLYJPjyrfxb0RYDlwvXnDb9nkQ7Do/oJVDuRFzew==", + "version": "4.0.20", + "resolved": "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-4.0.20.tgz", + "integrity": "sha512-daWW1Jxnvcn6+VhUuPqR7i5DtbdontcPCC5pH3QYU2r/x0hl31AIGiejWThGU499pJYA9DzwnBm+tc3EMKQeLQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "json-schema": "^0.4.0" }, "engines": { - "node": ">=18" + "node": ">=22" } }, "node_modules/@ai-sdk/provider-utils": { - "version": "4.0.56", - "resolved": "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.56.tgz", - "integrity": "sha512-Svfg3OwsLRqkDDn/lTk6x3Dmrlft+u3Xs2M5YvVqpuDY9sftY125xJ9jnref63W/CrFln0j7Ij3/YgG9WHpOqA==", + "version": "5.0.52", + "resolved": "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-5.0.52.tgz", + "integrity": "sha512-PWWa14kVJlBj/G4E85D+IMM28H9W9SC3cQ/yyRHTMzRmtLkNgmDOLhSYBJ9ZT+w83a+DQ9+QIcCaY36iuD89mw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "3.0.18", + "@ai-sdk/provider": "4.0.20", "@standard-schema/spec": "^1.1.0", + "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", - "undici": "^6.28.0" + "undici": "^7.29.0" }, "engines": { - "node": ">=18.17" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, + "node_modules/@ai-sdk/provider-utils/node_modules/undici": { + "version": "7.30.0", + "resolved": "https://registry.npmmirror.com/undici/-/undici-7.30.0.tgz", + "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "4.1.2", "resolved": "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", @@ -3697,16 +3751,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@peculiar/asn1-schema": { "version": "2.9.5", "resolved": "https://registry.npmmirror.com/@peculiar/asn1-schema/-/asn1-schema-2.9.5.tgz", @@ -6650,6 +6694,13 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/@workflow/serde": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/@workflow/serde/-/serde-4.1.0.tgz", + "integrity": "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@xmldom/xmldom": { "version": "0.8.15", "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.15.tgz", @@ -6798,19 +6849,18 @@ } }, "node_modules/ai": { - "version": "6.0.297", - "resolved": "https://registry.npmmirror.com/ai/-/ai-6.0.297.tgz", - "integrity": "sha512-cy6To6NMD+BokL0Jv0iQFDdPv7mCEaJ3y+/22reehtwBgJvFAaBjCQC1vv1jjgcVHoSAstBkbaoKNIHLc7ZSeg==", + "version": "7.0.123", + "resolved": "https://registry.npmmirror.com/ai/-/ai-7.0.123.tgz", + "integrity": "sha512-blyApVMNpQfendoGLAQRjQgh0EdbwhAI4SJYBTS4Dw+BygPzdj/Pcgt6AaClvy5+ibV8v+d+k8PABam0QxORtw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@ai-sdk/gateway": "3.0.206", - "@ai-sdk/provider": "3.0.18", - "@ai-sdk/provider-utils": "4.0.56", - "@opentelemetry/api": "^1.9.0" + "@ai-sdk/gateway": "4.0.101", + "@ai-sdk/provider": "4.0.20", + "@ai-sdk/provider-utils": "5.0.52" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" diff --git a/package.json b/package.json index 324596d..964f3b5 100644 --- a/package.json +++ b/package.json @@ -119,11 +119,11 @@ "rather than in app.asar." ], "devDependencies": { - "@ai-sdk/anthropic": "^3.0.125", - "@ai-sdk/google": "^3.0.129", - "@ai-sdk/openai": "^3.0.121", - "@ai-sdk/openai-compatible": "^2.0.80", - "@ai-sdk/provider": "^3.0.18", + "@ai-sdk/anthropic": "^4.0.69", + "@ai-sdk/google": "^4.0.86", + "@ai-sdk/openai": "^4.0.81", + "@ai-sdk/openai-compatible": "^3.0.60", + "@ai-sdk/provider": "^4.0.20", "@dagrejs/dagre": "^1.1.8", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", @@ -165,7 +165,7 @@ "@types/turndown": "^5.0.6", "@vitejs/plugin-react": "^5.1.2", "@xyflow/react": "^12.10.0", - "ai": "^6.0.297", + "ai": "^7.0.123", "autoprefixer": "^10.4.23", "cheerio": "^1.2.0", "class-variance-authority": "^0.7.1", diff --git a/src/main/models/ModelClient.ts b/src/main/models/ModelClient.ts index 739a1f8..6e409f3 100644 --- a/src/main/models/ModelClient.ts +++ b/src/main/models/ModelClient.ts @@ -5,7 +5,7 @@ * 提供对话流式生成与 embedding 能力。业务代码只依赖它,不感知厂商。 */ -import { embed, embedMany, streamText } from 'ai' +import { embed, embedMany, streamText, toUIMessageStream } from 'ai' import type { AsyncIterableStream, LanguageModel, LanguageModelUsage, UIMessageChunk } from 'ai' import type { SharedV2ProviderOptions } from '@ai-sdk/provider' import type { APIMessage, ChatTokenUsage } from '../../shared/types/chat' @@ -119,6 +119,12 @@ export class ModelClient { const result = streamText({ model: this.getAIModel(), + // KnowNote puts the retrieved context (and the title-generation prompt) into + // the history as a `system` message. AI SDK 7 rejects those by default, and + // it does not throw: the answer comes back empty. Every message here is + // built by this app, so the untrusted-injection case the default guards + // against does not apply. + allowSystemInMessages: true, messages: toModelMessages(messages), temperature: DEFAULT_TEMPERATURE, // Only when the connection asks for a ceiling. Absent means the model's own @@ -135,7 +141,10 @@ export class ModelClient { }) return { - events: result.toUIMessageStream({ + // The stateless helper, not `result.toUIMessageStream`: the result method is + // deprecated in v7 and logs a warning on every turn. + events: toUIMessageStream({ + stream: result.stream, sendReasoning: true, // The SDK replaces a provider error with a generic sentence by default; the // reader is owed the real one, which is what the transcript has always shown. diff --git a/src/main/models/protocols/googleGenerativeAi.ts b/src/main/models/protocols/googleGenerativeAi.ts index 3ea05b5..3b36d06 100644 --- a/src/main/models/protocols/googleGenerativeAi.ts +++ b/src/main/models/protocols/googleGenerativeAi.ts @@ -4,7 +4,7 @@ * Google Generative AI (Gemini) API,含 embedding 端点。 */ -import { createGoogleGenerativeAI } from '@ai-sdk/google' +import { createGoogle } from '@ai-sdk/google' import type { EmbeddingModel, LanguageModel } from 'ai' import type { ModelConnection } from '../../../shared/types/connection' import type { ProtocolAdapter } from './types' @@ -13,7 +13,7 @@ import { listGoogleModels } from './modelListing' const PROVIDER_OPTIONS_KEY = 'google' function createProvider(connection: ModelConnection) { - return createGoogleGenerativeAI({ + return createGoogle({ baseURL: connection.baseUrl, apiKey: connection.apiKey }) diff --git a/test/streamChat.test.ts b/test/streamChat.test.ts index a0dc4f8..503d3df 100644 --- a/test/streamChat.test.ts +++ b/test/streamChat.test.ts @@ -231,3 +231,23 @@ test('no ceiling is sent unless the connection asks for one', async () => { await collect(withCeiling.client.streamChat([{ role: 'user', content: 'hi' }]).events) assert.equal(withCeiling.model.doStreamCalls[0]?.maxOutputTokens, 8192) }) + +test('a system message in the history still reaches the provider', async () => { + // AI SDK 7 rejects system messages in `messages` by default. It does not throw: + // the answer comes back empty, which would make every RAG turn silently blank. + // KnowNote puts the retrieved context and the title prompt there, so the model + // has to receive them. + const inspecting = inspectingClient({}) + await collect( + inspecting.client.streamChat([ + { role: 'system', content: 'retrieved context' }, + { role: 'user', content: 'question' } + ]).events + ) + + const system = inspecting.model.doStreamCalls[0]?.prompt.find( + (message) => message.role === 'system' + ) + assert.ok(system, 'the system message never reached the provider') + assert.equal(system.content, 'retrieved context') +})