Compare commits

...

3 Commits

Author SHA1 Message Date
DarkSky
1a758b5e67 chore(server): improve transcript stability (#13821)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Enhanced audio/video detection for MP4 files to better distinguish
audio-only vs. video.

* **Dependencies**
* Added MP4 parsing dependency and updated AI provider libraries
(Anthropic, Google, OpenAI, etc.).

* **Bug Fixes**
  * Tightened authentication state validation for magic-link/OTP flows.
* Stricter space-join validation to reject invalid client
types/versions.
  * Improved transcript entry deduplication and data handling.

* **API**
* Transcript submit payload now requires infos and removes deprecated
url/mimeType fields.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-30 16:39:18 +08:00
DarkSky
a8194cdad3 fix(server): avoid a snowball effect of email sending failures (#13818)
fix #13802
2025-10-30 16:39:11 +08:00
DarkSky
7b05bff9c6 feat(server): expose mail server name config in admin dashboard (#13792)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added SMTP name configuration field to notification settings.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-30 16:38:50 +08:00
13 changed files with 257 additions and 143 deletions

42
Cargo.lock generated
View File

@@ -163,6 +163,7 @@ dependencies = [
"file-format",
"infer",
"mimalloc",
"mp4parse",
"napi",
"napi-build",
"napi-derive",
@@ -574,6 +575,15 @@ dependencies = [
"serde",
]
[[package]]
name = "bitreader"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "886559b1e163d56c765bc3a985febb4eee8009f625244511d8ee3c432e08c066"
dependencies = [
"cfg-if",
]
[[package]]
name = "bitvec"
version = "1.0.1"
@@ -1483,6 +1493,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365"
[[package]]
name = "fallible_collections"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a88c69768c0a15262df21899142bc6df9b9b823546d4b4b9a7bc2d6c448ec6fd"
dependencies = [
"hashbrown 0.13.2",
]
[[package]]
name = "fancy-regex"
version = "0.13.0"
@@ -1801,6 +1820,15 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -2494,6 +2522,20 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "mp4parse"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63a35203d3c6ce92d5251c77520acb2e57108c88728695aa883f70023624c570"
dependencies = [
"bitreader",
"byteorder",
"fallible_collections",
"log",
"num-traits",
"static_assertions",
]
[[package]]
name = "nanoid"
version = "0.4.0"

View File

@@ -48,6 +48,7 @@ libc = "0.2"
log = "0.4"
loom = { version = "0.7", features = ["checkpoint"] }
mimalloc = "0.1"
mp4parse = "0.17"
nanoid = "0.4"
napi = { version = "3.0.0-beta.3", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
napi-build = { version = "2" }

View File

@@ -11,6 +11,7 @@ affine_common = { workspace = true, features = ["doc-loader"] }
chrono = { workspace = true }
file-format = { workspace = true }
infer = { workspace = true }
mp4parse = { workspace = true }
napi = { workspace = true, features = ["async"] }
napi-derive = { workspace = true }
rand = { workspace = true }

View File

@@ -1,12 +1,41 @@
use mp4parse::{read_mp4, TrackType};
use napi_derive::napi;
#[napi]
pub fn get_mime(input: &[u8]) -> String {
if let Some(kind) = infer::get(&input[..4096.min(input.len())]) {
let mimetype = if let Some(kind) = infer::get(&input[..4096.min(input.len())]) {
kind.mime_type().to_string()
} else {
file_format::FileFormat::from_bytes(input)
.media_type()
.to_string()
};
if mimetype == "video/mp4" {
detect_mp4_flavor(input)
} else {
mimetype
}
}
fn detect_mp4_flavor(input: &[u8]) -> String {
let mut cursor = std::io::Cursor::new(input);
match read_mp4(&mut cursor) {
Ok(ctx) => {
let mut has_video = false;
let mut has_audio = false;
for track in ctx.tracks.iter() {
match track.track_type {
TrackType::Video | TrackType::AuxiliaryVideo | TrackType::Picture => has_video = true,
TrackType::Audio => has_audio = true,
_ => {}
}
}
if !has_video && has_audio {
"audio/m4a".to_string()
} else {
"video/mp4".to_string()
}
}
Err(_) => "video/mp4".to_string(),
}
}

View File

@@ -28,12 +28,12 @@
"dependencies": {
"@affine/reader": "workspace:*",
"@affine/server-native": "workspace:*",
"@ai-sdk/anthropic": "^2.0.1",
"@ai-sdk/google": "^2.0.4",
"@ai-sdk/google-vertex": "^3.0.5",
"@ai-sdk/openai": "^2.0.10",
"@ai-sdk/openai-compatible": "^1.0.5",
"@ai-sdk/perplexity": "^2.0.1",
"@ai-sdk/anthropic": "^2.0.38",
"@ai-sdk/google": "^2.0.24",
"@ai-sdk/google-vertex": "^3.0.54",
"@ai-sdk/openai": "^2.0.56",
"@ai-sdk/openai-compatible": "^1.0.23",
"@ai-sdk/perplexity": "^2.0.14",
"@apollo/server": "^4.11.3",
"@aws-sdk/client-s3": "^3.779.0",
"@aws-sdk/s3-request-presigner": "^3.779.0",
@@ -75,7 +75,7 @@
"@prisma/instrumentation": "^6.7.0",
"@react-email/components": "0.0.38",
"@socket.io/redis-adapter": "^8.3.0",
"ai": "^5.0.10",
"ai": "^5.0.81",
"bullmq": "^5.40.2",
"cookie-parser": "^1.4.7",
"cross-env": "^7.0.3",

View File

@@ -270,14 +270,12 @@ export class AuthController {
validators.assertValidEmail(email);
const cacheKey = OTP_CACHE_KEY(otp);
const cachedToken = await this.cache.get<
{ token: string; clientNonce: string } | string
>(cacheKey);
const cachedToken = await this.cache.get<{
token: string;
clientNonce: string;
}>(cacheKey);
let token: string | undefined;
// TODO(@fengmk2): this is a temporary compatible with cache token is string value, should be removed in 0.22
if (typeof cachedToken === 'string') {
token = cachedToken;
} else if (cachedToken) {
if (cachedToken && typeof cachedToken === 'object') {
token = cachedToken.token;
if (cachedToken.clientNonce && cachedToken.clientNonce !== clientNonce) {
throw new InvalidAuthState();

View File

@@ -1,7 +1,8 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { getStreamAsBuffer } from 'get-stream';
import { JOB_SIGNAL, OnJob, sleep } from '../../base';
import { Cache, JOB_SIGNAL, JobQueue, OnJob, sleep } from '../../base';
import { type MailName, MailProps, Renderers } from '../../mails';
import { UserProps, WorkspaceProps } from '../../mails/components';
import { Models } from '../../models';
@@ -40,17 +41,32 @@ declare global {
}
}
const sendMailKey = 'mailjob:sendMail';
const retryMailKey = 'mailjob:sendMail:retry';
const sendMailCacheKey = (name: string, to: string) =>
`${sendMailKey}:${name}:${to}`;
const retryMaxPerTick = 20;
const retryFirstTime = 3;
@Injectable()
export class MailJob {
private readonly logger = new Logger('MailJob');
constructor(
private readonly cache: Cache,
private readonly queue: JobQueue,
private readonly sender: MailSender,
private readonly doc: DocReader,
private readonly workspaceBlob: WorkspaceBlobStorage,
private readonly models: Models
) {}
@OnJob('notification.sendMail')
async sendMail({
private calculateRetryDelay(startTime: number) {
const elapsed = Date.now() - startTime;
return Math.min(30 * 1000, Math.round(elapsed / 2000) * 1000);
}
private async sendMailInternal({
startTime,
name,
to,
@@ -97,23 +113,29 @@ export class MailJob {
}
}
const result = await this.sender.send(name, {
to,
...(await Renderers[name](
// @ts-expect-error the job trigger part has been typechecked
props
)),
...options,
});
if (result === false) {
try {
const result = await this.sender.send(name, {
to,
...(await Renderers[name](
// @ts-expect-error the job trigger part has been typechecked
props
)),
...options,
});
if (!result) {
// wait for a while before retrying
const retryDelay = this.calculateRetryDelay(startTime);
await sleep(retryDelay);
return JOB_SIGNAL.Retry;
}
return undefined;
} catch (e) {
this.logger.error(`Failed to send mail [${name}] to [${to}]`, e);
// wait for a while before retrying
const elapsed = Date.now() - startTime;
const retryDelay = Math.min(30 * 1000, Math.round(elapsed / 2000) * 1000);
const retryDelay = this.calculateRetryDelay(startTime);
await sleep(retryDelay);
return JOB_SIGNAL.Retry;
}
return undefined;
}
private async fetchWorkspaceProps(workspaceId: string) {
@@ -151,4 +173,45 @@ export class MailJob {
return { email: user.email } satisfies UserProps;
}
@OnJob('notification.sendMail')
async sendMail(job: Jobs['notification.sendMail']) {
const cacheKey = sendMailCacheKey(job.name, job.to);
const retried = await this.cache.mapIncrease(sendMailKey, cacheKey, 1);
if (retried <= retryFirstTime) {
const ret = await this.sendMailInternal(job);
if (!ret) await this.cache.mapDelete(sendMailKey, cacheKey);
return ret;
}
await this.cache.mapSet(retryMailKey, cacheKey, JSON.stringify(job));
await this.cache.mapDelete(sendMailKey, cacheKey);
return undefined;
}
@Cron(CronExpression.EVERY_MINUTE)
async sendRetryMails() {
// pick random one from the retry map
let processed = 0;
let key = await this.cache.mapRandomKey(retryMailKey);
while (key && processed < retryMaxPerTick) {
try {
const job = await this.cache.mapGet<string>(retryMailKey, key);
if (job) {
const jobData = JSON.parse(job) as Jobs['notification.sendMail'];
await this.queue.add('notification.sendMail', jobData);
// wait for a while before retrying
const retryDelay = this.calculateRetryDelay(jobData.startTime);
await sleep(retryDelay);
}
await this.cache.mapDelete(retryMailKey, key);
} catch (e) {
this.logger.error(
`Failed to re-queue retry mail job for key [${key}]`,
e
);
}
key = await this.cache.mapRandomKey(retryMailKey);
processed++;
}
}
}

View File

@@ -194,14 +194,12 @@ export class SpaceSyncGateway
@ConnectedSocket() client: Socket,
@MessageBody()
{ spaceType, spaceId, clientVersion }: JoinSpaceMessage
): Promise<EventResponse<{ clientId: string; success: true }>> {
// TODO(@forehalo): remove this after 0.19 goes out of life
// simple match 0.19.x
if (/^0.19.[\d]$/.test(clientVersion)) {
const room = Room(spaceId, 'sync-019');
if (!client.rooms.has(room)) {
await client.join(room);
}
): Promise<EventResponse<{ clientId: string; success: boolean }>> {
if (
![SpaceType.Userspace, SpaceType.Workspace].includes(spaceType) ||
/^0.1/.test(clientVersion)
) {
return { data: { clientId: client.id, success: false } };
} else {
if (spaceType === SpaceType.Workspace) {
this.event.emit('workspace.embedding', { workspaceId: spaceId });

View File

@@ -112,9 +112,7 @@ export class GeminiGenerativeProvider extends GeminiProvider<GeminiGenerativeCon
`${baseUrl}/models?key=${this.config.apiKey}`
)
.then(r => r.json())
.then(
r => (console.log(JSON.stringify(r)), ModelListSchema.parse(r))
);
.then(r => ModelListSchema.parse(r));
this.onlineModelList = models.map(model =>
model.name.replace('models/', '')
);

View File

@@ -171,11 +171,11 @@ export class CopilotTranscriptionService {
if (payload.success) {
let { url, mimeType, infos } = payload.data;
infos = infos || [];
if (url && mimeType) {
if (url && mimeType && !infos.find(i => i.url === url)) {
infos.push({ url, mimeType });
}
ret.infos = this.mergeInfos(infos, url, mimeType);
ret.infos = infos;
if (job.status === AiJobStatus.claimed) {
ret.transcription = payload.data;
}
@@ -239,22 +239,6 @@ export class CopilotTranscriptionService {
}
}
// TODO(@darkskygit): remove after old server down
private mergeInfos(
infos?: AudioBlobInfos | null,
url?: string | null,
mimeType?: string | null
) {
if (url && mimeType) {
if (infos) {
infos.push({ url, mimeType });
} else {
infos = [{ url, mimeType }];
}
}
return infos || [];
}
private convertTime(time: number, offset = 0) {
time = time + offset;
const minutes = Math.floor(time / 60);
@@ -298,14 +282,10 @@ export class CopilotTranscriptionService {
jobId,
infos,
modelId,
// @deprecated
url,
mimeType,
}: Jobs['copilot.transcript.submit']) {
try {
const blobInfos = this.mergeInfos(infos, url, mimeType);
const transcriptions = await Promise.all(
Array.from(blobInfos.entries()).map(([idx, { url, mimeType }]) =>
Array.from(infos.entries()).map(([idx, { url, mimeType }]) =>
this.callTranscript(url, mimeType, idx * 10 * 60, modelId)
)
);

View File

@@ -55,12 +55,8 @@ declare global {
interface Jobs {
'copilot.transcript.submit': {
jobId: string;
infos?: AudioBlobInfos;
infos: AudioBlobInfos;
modelId?: string;
/// @deprecated use `infos` instead
url?: string;
/// @deprecated use `infos` instead
mimeType?: string;
};
'copilot.transcript.summary.submit': {
jobId: string;

View File

@@ -75,6 +75,7 @@ export const KNOWN_CONFIG_GROUPS = [
name: 'Notification',
module: 'mailer',
fields: [
'SMTP.name',
'SMTP.host',
'SMTP.port',
'SMTP.username',

151
yarn.lock
View File

@@ -921,12 +921,12 @@ __metadata:
"@affine/graphql": "workspace:*"
"@affine/reader": "workspace:*"
"@affine/server-native": "workspace:*"
"@ai-sdk/anthropic": "npm:^2.0.1"
"@ai-sdk/google": "npm:^2.0.4"
"@ai-sdk/google-vertex": "npm:^3.0.5"
"@ai-sdk/openai": "npm:^2.0.10"
"@ai-sdk/openai-compatible": "npm:^1.0.5"
"@ai-sdk/perplexity": "npm:^2.0.1"
"@ai-sdk/anthropic": "npm:^2.0.38"
"@ai-sdk/google": "npm:^2.0.24"
"@ai-sdk/google-vertex": "npm:^3.0.54"
"@ai-sdk/openai": "npm:^2.0.56"
"@ai-sdk/openai-compatible": "npm:^1.0.23"
"@ai-sdk/perplexity": "npm:^2.0.14"
"@apollo/server": "npm:^4.11.3"
"@aws-sdk/client-s3": "npm:^3.779.0"
"@aws-sdk/s3-request-presigner": "npm:^3.779.0"
@@ -988,7 +988,7 @@ __metadata:
"@types/semver": "npm:^7.5.8"
"@types/sinon": "npm:^17.0.3"
"@types/supertest": "npm:^6.0.2"
ai: "npm:^5.0.10"
ai: "npm:^5.0.81"
ava: "npm:^6.2.0"
bullmq: "npm:^5.40.2"
c8: "npm:^10.1.3"
@@ -1092,104 +1092,104 @@ __metadata:
languageName: unknown
linkType: soft
"@ai-sdk/anthropic@npm:2.0.1, @ai-sdk/anthropic@npm:^2.0.1":
version: 2.0.1
resolution: "@ai-sdk/anthropic@npm:2.0.1"
"@ai-sdk/anthropic@npm:2.0.38, @ai-sdk/anthropic@npm:^2.0.38":
version: 2.0.38
resolution: "@ai-sdk/anthropic@npm:2.0.38"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.1"
"@ai-sdk/provider-utils": "npm:3.0.13"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10/aa6fc0be49775e061412c4644b11b33ccf07a983a1353a1960ed3a8dc4236586003f46080b2890ed345c9bdc00f55628719ad8b0246528d8a40e066638840b53
zod: ^3.25.76 || ^4.1.8
checksum: 10/6e3e7084dbb8470b3e2dd56541e7ad71b5784f18a3afe3e8ec079d7c337a94072b8da939af6d6c2f7a5458f5ab670d34057079881e0f67f9d2b5b1525e043641
languageName: node
linkType: hard
"@ai-sdk/gateway@npm:1.0.4":
version: 1.0.4
resolution: "@ai-sdk/gateway@npm:1.0.4"
"@ai-sdk/gateway@npm:2.0.2":
version: 2.0.2
resolution: "@ai-sdk/gateway@npm:2.0.2"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.1"
"@ai-sdk/provider-utils": "npm:3.0.13"
"@vercel/oidc": "npm:3.0.3"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10/11b0322a619a922e74ae782ddff5577825f99b34f02889ffefc31652109a644110f6a4413caac047b8760635a880798e99d04a3bd042057013c772435dc4d172
zod: ^3.25.76 || ^4.1.8
checksum: 10/6f4058e26e275aaa59fc474f966604a1198863ad33a82570ac167dffd588dd21bb9ae476c2f9dc607374715a1d6840bcd9ff5e074961ee49cfef64eb44013bac
languageName: node
linkType: hard
"@ai-sdk/google-vertex@npm:^3.0.5":
version: 3.0.5
resolution: "@ai-sdk/google-vertex@npm:3.0.5"
"@ai-sdk/google-vertex@npm:^3.0.54":
version: 3.0.54
resolution: "@ai-sdk/google-vertex@npm:3.0.54"
dependencies:
"@ai-sdk/anthropic": "npm:2.0.1"
"@ai-sdk/google": "npm:2.0.4"
"@ai-sdk/anthropic": "npm:2.0.38"
"@ai-sdk/google": "npm:2.0.24"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.1"
"@ai-sdk/provider-utils": "npm:3.0.13"
google-auth-library: "npm:^9.15.0"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10/8f6cdba400e9443548e07940b6f753964d29981a43ab95d2d13e96883bf638c94e3a9a3aba2ce9a59ae4a771ab7ec6f2dac17c3610817f6b2819fbe7d2c296c5
zod: ^3.25.76 || ^4.1.8
checksum: 10/a2198cddb24b09cc548fc36028189008b11fac19fa439938856a94197359fe3bf737faf6b64d1426b6c4f004b2113d9fb7af5458c52e925c81fd729ee7943387
languageName: node
linkType: hard
"@ai-sdk/google@npm:2.0.4, @ai-sdk/google@npm:^2.0.4":
version: 2.0.4
resolution: "@ai-sdk/google@npm:2.0.4"
"@ai-sdk/google@npm:2.0.24, @ai-sdk/google@npm:^2.0.24":
version: 2.0.24
resolution: "@ai-sdk/google@npm:2.0.24"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.1"
"@ai-sdk/provider-utils": "npm:3.0.13"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10/f8d778804cc7e6674aa4ff3931e2cecbc95fdd6484669dcb25398f6461cb033372de1c8b0667fa96604fbe59a8cf37b9d1461b66e6a995f82a6ecf3917d589f1
zod: ^3.25.76 || ^4.1.8
checksum: 10/37fffa5272af9b1a3d415546b6c73c1f24ef87f2ba16ede96f3c26c33ac33a824b9c7bdb5e10edcb9d59e37aba84465d0635570261b719a932e921dcd84c5e45
languageName: node
linkType: hard
"@ai-sdk/openai-compatible@npm:^1.0.5":
version: 1.0.5
resolution: "@ai-sdk/openai-compatible@npm:1.0.5"
"@ai-sdk/openai-compatible@npm:^1.0.23":
version: 1.0.23
resolution: "@ai-sdk/openai-compatible@npm:1.0.23"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.1"
"@ai-sdk/provider-utils": "npm:3.0.13"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10/52437a335a64c3c9993aedad4e85cbfa7876fe073b3dfc543a7478d6d4f63ec5eba0b1c67de317732a70c682a1cbb903c36b2e623e25c15baf7450d677592fff
zod: ^3.25.76 || ^4.1.8
checksum: 10/f5a1fdc1fb55b166bad38bfa2fe746162aaff2a6dbcf9e958ff21615421cb1694c53e6a743db9184faf84eb870cf2785c04f7aa95e324b9a0e60fdf383563c1f
languageName: node
linkType: hard
"@ai-sdk/openai@npm:^2.0.10":
version: 2.0.10
resolution: "@ai-sdk/openai@npm:2.0.10"
"@ai-sdk/openai@npm:^2.0.56":
version: 2.0.56
resolution: "@ai-sdk/openai@npm:2.0.56"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.1"
"@ai-sdk/provider-utils": "npm:3.0.13"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10/5e07f9ed0f9a5459c6c6c7cc89e4efd6656b0db065b03f2e6ccacac567d84aa11ddd301ee076f4e438ee8819a0eeead45acc2a6ade82877e8445c862af464aa2
zod: ^3.25.76 || ^4.1.8
checksum: 10/bd075ba597243a4cb8dd1ae53e10dfdcb3cb66647228c264a96ce407f5a6a3cb931967bd7e6ab9d22ea299ab82c45633db3bec717e9eed947afaa2e8c6daf714
languageName: node
linkType: hard
"@ai-sdk/perplexity@npm:^2.0.1":
version: 2.0.1
resolution: "@ai-sdk/perplexity@npm:2.0.1"
"@ai-sdk/perplexity@npm:^2.0.14":
version: 2.0.14
resolution: "@ai-sdk/perplexity@npm:2.0.14"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.1"
"@ai-sdk/provider-utils": "npm:3.0.13"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10/7fe19ce52c8c7031d8f567d7c40c8b2c563838cd283baf4f2e278430d9dbddba0fa41184024d1818cb230143bdc1e5ec065d27dad240974bec16417d896482e2
zod: ^3.25.76 || ^4.1.8
checksum: 10/a3cddfa4fc6bda355e0783c8a735b0468fbc9ec54967d41a2d5c705ae295529d70b9c2143b882a4fc3986577a1404ae22c686522d4c002ff965180424bb08096
languageName: node
linkType: hard
"@ai-sdk/provider-utils@npm:3.0.1":
version: 3.0.1
resolution: "@ai-sdk/provider-utils@npm:3.0.1"
"@ai-sdk/provider-utils@npm:3.0.13":
version: 3.0.13
resolution: "@ai-sdk/provider-utils@npm:3.0.13"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@standard-schema/spec": "npm:^1.0.0"
eventsource-parser: "npm:^3.0.3"
zod-to-json-schema: "npm:^3.24.1"
eventsource-parser: "npm:^3.0.5"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10/23f841ff876dcdd3a507acad82e50501784eaa5635364ecc63790c518748309ab5a6b9a18f605ae471778335258917e372de4ad8f45ee94ffa690b7d2ae7ea99
zod: ^3.25.76 || ^4.1.8
checksum: 10/9dfc26323d31e4e06d4a854cdadd0e2f5952f117d0fc298ad100c48c8b77ece769c8e53338e570cadac814454550896991b9105168624de86e7d7662edea3bf7
languageName: node
linkType: hard
@@ -16371,6 +16371,13 @@ __metadata:
languageName: node
linkType: hard
"@vercel/oidc@npm:3.0.3":
version: 3.0.3
resolution: "@vercel/oidc@npm:3.0.3"
checksum: 10/2713aba666a8dd45c099f22936a25f1ab5cba357f41239fcec5ad1b65b57db2d47f322f136937c29428d8c0c9ec745aa25eafebed9fe45def94c5a4b75ca777f
languageName: node
linkType: hard
"@vitejs/plugin-react-swc@npm:^3.7.2":
version: 3.9.0
resolution: "@vitejs/plugin-react-swc@npm:3.9.0"
@@ -17032,17 +17039,17 @@ __metadata:
languageName: node
linkType: hard
"ai@npm:^5.0.10":
version: 5.0.10
resolution: "ai@npm:5.0.10"
"ai@npm:^5.0.81":
version: 5.0.81
resolution: "ai@npm:5.0.81"
dependencies:
"@ai-sdk/gateway": "npm:1.0.4"
"@ai-sdk/gateway": "npm:2.0.2"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.1"
"@ai-sdk/provider-utils": "npm:3.0.13"
"@opentelemetry/api": "npm:1.9.0"
peerDependencies:
zod: ^3.25.76 || ^4
checksum: 10/c424464f39cd9a875b7cbf1dac8046f9a8a164ac42f1cc25c0bb44597996656e9c2ab18bc518f8802ee3917624c666e26aa120ea8821282b7bd1cb8dc1eca518
zod: ^3.25.76 || ^4.1.8
checksum: 10/1b04184e6c35c0545ca988614cd6e67d748df86af474efc912348d89d7b79fcfad7bfbaec636fe5fafd0b0291b612175e18ba85e2800fd267b03ca3072c6702d
languageName: node
linkType: hard
@@ -18395,9 +18402,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001579, caniuse-lite@npm:^1.0.30001702, caniuse-lite@npm:^1.0.30001716":
version: 1.0.30001717
resolution: "caniuse-lite@npm:1.0.30001717"
checksum: 10/e47dfd8707ea305baa177f3d3d531df614f5a9ac6335363fc8f86f0be4caf79f5734f3f68b601fee4edd9d79f1e5ffc0931466bb894bf955ed6b1dd5a1c34b1d
version: 1.0.30001751
resolution: "caniuse-lite@npm:1.0.30001751"
checksum: 10/608f7e1248b7023020382c7dbb0ef389693b3fc98193c3ccea2d44126306d6ac905a5061cf9e62bf640535a86e7a98e563b34c02f909296cfe228f41627a4dc7
languageName: node
linkType: hard
@@ -22038,10 +22045,10 @@ __metadata:
languageName: node
linkType: hard
"eventsource-parser@npm:^3.0.0, eventsource-parser@npm:^3.0.1, eventsource-parser@npm:^3.0.3":
version: 3.0.3
resolution: "eventsource-parser@npm:3.0.3"
checksum: 10/b8f8e79333441ad0eb9299e3fa693ab506892ffc53f0cc1d23134090351cf2d71c8e405a2e879f6acfbd2e17f41d5a00dafba05ff25c82141fc07078ad992187
"eventsource-parser@npm:^3.0.0, eventsource-parser@npm:^3.0.1, eventsource-parser@npm:^3.0.5":
version: 3.0.6
resolution: "eventsource-parser@npm:3.0.6"
checksum: 10/febf7058b9c2168ecbb33e92711a1646e06bd1568f60b6eb6a01a8bf9f8fcd29cc8320d57247059cacf657a296280159f21306d2e3ff33309a9552b2ef889387
languageName: node
linkType: hard