From fb7f84153760805b4d704b58f0242a6b834b5888 Mon Sep 17 00:00:00 2001 From: xiaomo Date: Sun, 7 Jun 2026 17:41:13 +0800 Subject: [PATCH 1/4] feat(sync): S3-compatible cloud backup sync (#1741) * fix(sync): skip FTS5 virtual/shadow tables on import FTS5 shadow tables carry a real CREATE TABLE sql in sqlite_master and cannot be written by a column-copy INSERT, raising "table X may not be modified". Exclude virtual tables and their _* shadow tables by name prefix; the external-content FTS index is rebuilt by triggers when the content table rows are imported. * feat(sync): add S3-compatible cloud backup sync Add an optional cloud layer on top of the existing local zip backup so data can move between machines. Uploads the latest local backup to an S3-compatible bucket (Cloudflare R2 / MinIO / AWS S3 / B2) and pulls the latest one back, reusing the existing import flow. - CloudStorageService wraps @aws-sdk/client-s3 (path-style, region auto) - Credentials stored via safeStorage; secret never returned to renderer - Cloud config is machine-local: stripped from backups, preserved on import, so a pull never clobbers local credentials - New manual buttons in Data settings: save / test / upload / pull * fix(sync): address cloud sync review --------- Co-authored-by: zhangmo8 Co-authored-by: zerob13 --- docs/features/cloud-sync-s3/plan.md | 47 ++++ docs/features/cloud-sync-s3/spec.md | 36 +++ docs/features/cloud-sync-s3/tasks.md | 26 +++ docs/issues/import-fts-shadow-table/spec.md | 33 +++ package.json | 1 + src/main/presenter/configPresenter/index.ts | 142 +++++++++++- .../presenter/sqlitePresenter/importData.ts | 22 +- .../syncPresenter/cloudStorageService.ts | 170 ++++++++++++++ src/main/presenter/syncPresenter/index.ts | 142 +++++++++++- src/main/routes/index.ts | 63 +++++ src/renderer/api/SyncClient.ts | 38 +++- .../settings/components/DataSettings.vue | 215 +++++++++++++++++- src/renderer/src/i18n/da-DK/settings.json | 22 ++ src/renderer/src/i18n/da-DK/sync.json | 13 +- src/renderer/src/i18n/de-DE/settings.json | 22 ++ src/renderer/src/i18n/de-DE/sync.json | 13 +- src/renderer/src/i18n/en-US/settings.json | 22 ++ src/renderer/src/i18n/en-US/sync.json | 13 +- src/renderer/src/i18n/es-ES/settings.json | 22 ++ src/renderer/src/i18n/es-ES/sync.json | 13 +- src/renderer/src/i18n/fa-IR/settings.json | 22 ++ src/renderer/src/i18n/fa-IR/sync.json | 13 +- src/renderer/src/i18n/fr-FR/settings.json | 22 ++ src/renderer/src/i18n/fr-FR/sync.json | 13 +- src/renderer/src/i18n/he-IL/settings.json | 22 ++ src/renderer/src/i18n/he-IL/sync.json | 13 +- src/renderer/src/i18n/id-ID/settings.json | 22 ++ src/renderer/src/i18n/id-ID/sync.json | 13 +- src/renderer/src/i18n/it-IT/settings.json | 22 ++ src/renderer/src/i18n/it-IT/sync.json | 13 +- src/renderer/src/i18n/ja-JP/settings.json | 22 ++ src/renderer/src/i18n/ja-JP/sync.json | 13 +- src/renderer/src/i18n/ko-KR/settings.json | 22 ++ src/renderer/src/i18n/ko-KR/sync.json | 13 +- src/renderer/src/i18n/ms-MY/settings.json | 22 ++ src/renderer/src/i18n/ms-MY/sync.json | 13 +- src/renderer/src/i18n/pl-PL/settings.json | 22 ++ src/renderer/src/i18n/pl-PL/sync.json | 13 +- src/renderer/src/i18n/pt-BR/settings.json | 22 ++ src/renderer/src/i18n/pt-BR/sync.json | 13 +- src/renderer/src/i18n/ru-RU/settings.json | 22 ++ src/renderer/src/i18n/ru-RU/sync.json | 13 +- src/renderer/src/i18n/tr-TR/settings.json | 22 ++ src/renderer/src/i18n/tr-TR/sync.json | 13 +- src/renderer/src/i18n/vi-VN/settings.json | 22 ++ src/renderer/src/i18n/vi-VN/sync.json | 13 +- src/renderer/src/i18n/zh-CN/settings.json | 22 ++ src/renderer/src/i18n/zh-CN/sync.json | 13 +- src/renderer/src/i18n/zh-HK/settings.json | 22 ++ src/renderer/src/i18n/zh-HK/sync.json | 13 +- src/renderer/src/i18n/zh-TW/settings.json | 22 ++ src/renderer/src/i18n/zh-TW/sync.json | 13 +- src/renderer/src/stores/sync.ts | 71 +++++- src/shared/contracts/routes.ts | 12 +- src/shared/contracts/routes/sync.routes.ts | 70 +++++- .../types/presenters/legacy.presenters.d.ts | 50 ++++ test/main/presenter/SyncPresenter.test.ts | 95 +++++++- 57 files changed, 1881 insertions(+), 52 deletions(-) create mode 100644 docs/features/cloud-sync-s3/plan.md create mode 100644 docs/features/cloud-sync-s3/spec.md create mode 100644 docs/features/cloud-sync-s3/tasks.md create mode 100644 docs/issues/import-fts-shadow-table/spec.md create mode 100644 src/main/presenter/syncPresenter/cloudStorageService.ts diff --git a/docs/features/cloud-sync-s3/plan.md b/docs/features/cloud-sync-s3/plan.md new file mode 100644 index 000000000..377dd2fb9 --- /dev/null +++ b/docs/features/cloud-sync-s3/plan.md @@ -0,0 +1,47 @@ +# Plan: 云同步(S3 兼容对象存储) + +## 架构总览 +云能力作为现有备份链路的**叠加层**,不重写本地备份/导入: + +```text +DataSettings.vue ─► sync store ─► SyncClient ─► [route] ─► SyncPresenter ─► CloudStorageService + 保存/测试/上传/拉取 │ │ + ConfigPresenter R2 / S3 桶 + (safeStorage 加密凭证) +``` + +- 上传 = 取本地最新 zip(`SyncPresenter.listBackups()`)→ `CloudStorageService.uploadBackup()`。 +- 拉取 = `CloudStorageService.downloadLatest()` 落地同步文件夹 → 复用 `SyncPresenter.importFromSync()`。 + +## 关键文件 +- `src/main/presenter/syncPresenter/cloudStorageService.ts`(新增):S3 客户端封装 + (`forcePathStyle: true`、`region` 默认 `auto`),方法 `testConnection / uploadBackup / + listRemoteBackups / downloadLatest`,沿用 `backup-\d+\.zip` 文件名约定。 +- `src/main/presenter/syncPresenter/index.ts`:新增 `testCloudConnection / uploadLatestBackupToCloud / + pullLatestBackupFromCloud`,从 ConfigPresenter 取解密后的 `ResolvedCloudSyncConfig` 构造服务。 +- `src/main/presenter/configPresenter/index.ts`:`getCloudSyncConfig / setCloudSyncConfig / + getResolvedCloudSyncConfig / isCloudSafeStorageAvailable`;secret 经 `safeStorage` 加密, + 视图脱敏(仅 `hasSecret`)。 +- `src/shared/contracts/routes/sync.routes.ts` + `routes.ts`:新增 5 路由并登记。 +- `src/main/routes/index.ts`:sync 段加 5 个 case 分发(上传/拉取复用 `recordSettingsActivity`)。 +- `src/shared/types/presenters/legacy.presenters.d.ts`:`ISyncPresenter` / `IConfigPresenter` 新方法 + + `CloudSyncConfigView / CloudSyncConfigInput / ResolvedCloudSyncConfig / CloudSyncResult` 类型。 +- `src/renderer/api/SyncClient.ts` + `src/renderer/src/stores/sync.ts`:5 个客户端方法 + store action。 +- `src/renderer/settings/components/DataSettings.vue`:同步卡片内新增云同步区块。 +- i18n:`sync.json`(success/error 云键)、`settings.json`(`data.cloudSync.*`),全语言补齐。 + +## 复用 +- 备份/导入:`performBackup / importFromSync / listBackups / getBackupsDirectory`。 +- 加密:`safeStorage`(参照 `databaseSecurityPresenter`)。 +- IPC / 渲染数据流:`defineRouteContract`、`useIpcQuery/useIpcMutation`、pinia store。 + +## 依赖 +- 新增 `@aws-sdk/client-s3`(与现有 `@aws-sdk/client-bedrock` 同源)。 + +## 边界与决策 +- R2 必须 path-style + `region: 'auto'`。 +- secret 留空 = 不修改既有值;非空才重新加密写入。 +- 配置保存先完成 secret 加密,再写入本地设置;如果第二步失败,回滚已写入的 secret,避免 + access key 与旧 secret 错配。 +- 云上传只接受结构可导入的 `backup-\d+\.zip`,避免用户放入任意 zip 后被同步到云端。 +- 活动记录复用既有 `backup_created` / `imported` action,不扩 schema(最小改动)。 diff --git a/docs/features/cloud-sync-s3/spec.md b/docs/features/cloud-sync-s3/spec.md new file mode 100644 index 000000000..a758feb0d --- /dev/null +++ b/docs/features/cloud-sync-s3/spec.md @@ -0,0 +1,36 @@ +# Spec: 云同步(S3 兼容对象存储) + +## 背景与问题 +DeepChat 现有的「同步」仅把数据库 + 配置打包为 `backup-<时间戳>.zip` 写入**本地**同步文件夹 +(默认 `~/DeepchatSync`),导入也只从该本地文件夹读取。多设备(家 / 公司)之间数据不会自动流转, +必须手动搬运 zip。 + +## 目标 +在**不改动**现有本地备份/导入逻辑的前提下,叠加一个最小的云能力: +1. **上传到云**:把本地最新备份 zip 推送到 S3 兼容对象存储。 +2. **从云拉取最新**:下载云端最新备份 zip 到本地同步文件夹,复用现有导入流程还原。 + +主用例为 Cloudflare R2,通过 **S3 兼容协议** 实现,使同一套配置也能连 MinIO / AWS S3 / B2。 + +## 非目标(明确不做,避免过度设计) +- 不做定时/自动上传,纯手动按钮触发。 +- 不做云端多版本管理、保留策略、冲突合并。 +- 不做 WebDAV / R2 专有 Token API。 +- 不引入新的导入合并语义,沿用现有 increment / overwrite。 + +## 用户故事 +- 作为多设备用户,我在 A 机点「上传到云」,在 B 机点「从云拉取最新」,即可把聊天记录与配置带过去。 + +## 验收标准 +- 设置 → 数据出现「云同步 (S3 兼容)」区块:endpoint / bucket / region / prefix / AK / SK + 保存 / 测试连接 / 上传 / 拉取。 +- 填入有效 R2 凭证后「测试连接」成功;「上传到云」后桶内出现 `deepchat-backups/backup-*.zip`。 +- 另一设备「从云拉取最新」后数据恢复。 +- `secretAccessKey` 在 `app-settings` 中以 safeStorage 密文存储,渲染层永不收到明文。 +- 同一套 UI 切换 endpoint/bucket 即可对接 MinIO(验证 S3 兼容)。 + +## 安全 +- 凭证 secret 用 Electron `safeStorage` 加密后落盘(与 `databaseSecurityPresenter` 一致)。 +- safeStorage 不可用时拒绝保存 secret 并提示(`sync.error.safeStorageUnavailable`)。 + +## 待澄清 +- 无(方案、凭证存储、触发方式均已与用户确认)。 diff --git a/docs/features/cloud-sync-s3/tasks.md b/docs/features/cloud-sync-s3/tasks.md new file mode 100644 index 000000000..d4f469d88 --- /dev/null +++ b/docs/features/cloud-sync-s3/tasks.md @@ -0,0 +1,26 @@ +# Tasks: 云同步(S3 兼容对象存储) + +- [x] 安装 `@aws-sdk/client-s3` 依赖 +- [x] 新增 `CloudStorageService`(testConnection / uploadBackup / listRemoteBackups / downloadLatest) +- [x] `ConfigPresenter` 加云凭证读写,secret 用 safeStorage 加密、视图脱敏 +- [x] `SyncPresenter` 加 testCloudConnection / uploadLatestBackupToCloud / pullLatestBackupFromCloud +- [x] 新增 5 个 IPC 路由契约并在 `routes.ts` 登记 +- [x] `legacy.presenters.d.ts` 加接口方法与 CloudSync* 类型 +- [x] `main/routes/index.ts` 注册 5 个 case +- [x] `SyncClient.ts` + `stores/sync.ts` 加云方法/状态 +- [x] `DataSettings.vue` 云同步 UI(表单 + 保存/测试/上传/拉取) +- [x] i18n:zh-CN / en-US 增云键,其余语言英文兜底,`pnpm run i18n` 校验通过 +- [x] PR review:云配置写入失败可感知并回滚 secret +- [x] PR review:云上传/下载改为流式 IO +- [x] PR review:上传前校验备份包结构,跳过伪造 zip +- [x] PR review:导入时本地 settings 读取失败则回滚 +- [x] PR review:云操作 busy guard 补齐 +- [x] PR review:he-IL / id-ID 云同步文案本地化 +- [x] 收尾:`pnpm run typecheck` / `format` / `lint` 全绿 + +## 待人工验证(需真实 R2 凭证) +- [ ] 填 R2 凭证 → 测试连接成功 +- [ ] 上传到云 → 桶内出现 `deepchat-backups/backup-*.zip` +- [ ] 另一设备拉取最新 → 数据恢复 +- [ ] 切换 MinIO endpoint 验证 S3 兼容 +- [ ] 确认 `app-settings` 中 secret 为密文 diff --git a/docs/issues/import-fts-shadow-table/spec.md b/docs/issues/import-fts-shadow-table/spec.md new file mode 100644 index 000000000..9eabea2e1 --- /dev/null +++ b/docs/issues/import-fts-shadow-table/spec.md @@ -0,0 +1,33 @@ +# Issue: 增量导入时 FTS5 影子表报错 + +## 现象 +增量(increment)导入备份时抛错并回滚: + +```text +Failed to import database: Failed to import table deepchat_search_documents_fts_config: +table deepchat_search_documents_fts_config may not be modified +``` + +经云同步「从云拉取最新」复用 `importFromSync` 时触发,本地「导入数据」走相同路径也会复现。 + +## 根因 +`DataImporter.getTablesInOrder()`(`src/main/presenter/sqlitePresenter/importData.ts`)用 +`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'` 取表, +未排除 FTS5 的虚拟表 `deepchat_search_documents_fts` 及其影子表 +`_data/_idx/_docsize/_config`。对影子表执行普通 `INSERT` 会被 SQLite 拒绝("may not be modified")。 + +关键坑:FTS5 影子表在 `sqlite_master` 中**带有真实的 `CREATE TABLE` sql(非 NULL)**,因此 +不能靠 `sql IS NULL` 识别,必须按「虚拟表名前缀」排除。 + +## 修复 +先取出所有虚拟表(`sql LIKE 'CREATE VIRTUAL TABLE%'`),再排除名字等于虚拟表名、或以 +`<虚拟表名>_` 开头的影子表。FTS5 采用外部内容表模式(`content='deepchat_search_documents'`) ++ 触发器,导入内容表 `deepchat_search_documents` 行时触发器会自动维护 FTS 索引,无需直接写 FTS 表。 + +## 影响范围 +- 仅增量导入路径(`DataImporter`);覆盖导入走整库文件拷贝,不受影响。 +- 修复后导入不再触碰 FTS 影子表,搜索索引由触发器重建。 + +## 验证 +- 含 `deepchat_search_documents_fts*` 表的备份增量导入成功,无报错。 +- 导入后对已导入文档执行搜索可命中(FTS 索引已由触发器填充)。 diff --git a/package.json b/package.json index 6739ee742..0db8c768c 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "@ai-sdk/openai-compatible": "^2.0.48", "@ai-sdk/provider": "^3.0.10", "@aws-sdk/client-bedrock": "^3.1057.0", + "@aws-sdk/client-s3": "^3.1062.0", "@duckdb/node-api": "1.5.3-r.1", "@e2b/code-interpreter": "^1.5.1", "@electron-toolkit/preload": "^3.0.2", diff --git a/src/main/presenter/configPresenter/index.ts b/src/main/presenter/configPresenter/index.ts index d54c3e21e..bfa38289d 100644 --- a/src/main/presenter/configPresenter/index.ts +++ b/src/main/presenter/configPresenter/index.ts @@ -19,6 +19,11 @@ import { AcpResolvedLaunchSpec, ProviderDbRefreshResult } from '@shared/presenter' +import type { + CloudSyncConfigView, + CloudSyncConfigInput, + ResolvedCloudSyncConfig +} from '@shared/presenter' import { ProviderBatchUpdate } from '@shared/provider-operations' import { SearchEngineTemplate } from '@shared/chat' import { @@ -38,7 +43,7 @@ import { import ElectronStore from 'electron-store' import { DEFAULT_PROVIDERS } from './providers' import path from 'path' -import { app, nativeTheme, shell } from 'electron' +import { app, nativeTheme, shell, safeStorage } from 'electron' import fs from 'fs' import { CONFIG_EVENTS, @@ -1875,6 +1880,141 @@ export class ConfigPresenter implements IConfigPresenter { this.setSetting('lastSyncTime', time) } + // === Cloud sync (S3-compatible) settings === + // Non-sensitive fields live in app-settings; the secret is encrypted via safeStorage. + private readonly CLOUD_SYNC_BASE_KEY = 'cloudSyncConfig' + private readonly CLOUD_SYNC_SECRET_KEY = 'cloudSyncSecret' + + isCloudSafeStorageAvailable(): boolean { + try { + return safeStorage.isEncryptionAvailable() + } catch { + return false + } + } + + private getCloudSyncBase(): { + enabled: boolean + endpoint: string + bucket: string + region: string + prefix: string + accessKeyId: string + } { + const stored = this.getSetting<{ + enabled?: boolean + endpoint?: string + bucket?: string + region?: string + prefix?: string + accessKeyId?: string + }>(this.CLOUD_SYNC_BASE_KEY) + return { + enabled: stored?.enabled ?? false, + endpoint: stored?.endpoint ?? '', + bucket: stored?.bucket ?? '', + region: stored?.region ?? 'auto', + prefix: stored?.prefix ?? 'deepchat-backups', + accessKeyId: stored?.accessKeyId ?? '' + } + } + + private getCloudSyncSecret(): string { + const wrapped = this.getSetting(this.CLOUD_SYNC_SECRET_KEY) + if (!wrapped) { + return '' + } + try { + return safeStorage.decryptString(Buffer.from(wrapped, 'base64')) + } catch (error) { + console.error('[Config] Failed to decrypt cloud sync secret:', error) + return '' + } + } + + getCloudSyncConfig(): CloudSyncConfigView { + const base = this.getCloudSyncBase() + return { + ...base, + hasSecret: Boolean(this.getCloudSyncSecret()), + safeStorageAvailable: this.isCloudSafeStorageAvailable() + } + } + + private setCloudSyncSetting(key: string, value: T): void { + this.getSettingsStoreForKey(key).set(key, value) + eventBus.sendToMain(CONFIG_EVENTS.SETTING_CHANGED, key, value) + } + + private deleteCloudSyncSetting(key: string): void { + this.getSettingsStoreForKey(key).delete(key) + eventBus.sendToMain(CONFIG_EVENTS.SETTING_CHANGED, key, undefined) + } + + setCloudSyncConfig(config: CloudSyncConfigInput): CloudSyncConfigView { + const current = this.getCloudSyncBase() + const next = { + enabled: config.enabled ?? current.enabled, + endpoint: config.endpoint ?? current.endpoint, + bucket: config.bucket ?? current.bucket, + region: config.region ?? current.region, + prefix: config.prefix ?? current.prefix, + accessKeyId: config.accessKeyId ?? current.accessKeyId + } + + // Only update the secret when a non-empty value is provided; empty/undefined keeps the existing one. + const currentWrappedSecret = this.getSetting(this.CLOUD_SYNC_SECRET_KEY) + let nextWrappedSecret: string | undefined + if (typeof config.secretAccessKey === 'string' && config.secretAccessKey.length > 0) { + if (!this.isCloudSafeStorageAvailable()) { + throw new Error('sync.error.safeStorageUnavailable') + } + nextWrappedSecret = Buffer.from(safeStorage.encryptString(config.secretAccessKey)).toString( + 'base64' + ) + } + + let secretWritten = false + try { + if (nextWrappedSecret !== undefined) { + this.setCloudSyncSetting(this.CLOUD_SYNC_SECRET_KEY, nextWrappedSecret) + secretWritten = true + } + this.setCloudSyncSetting(this.CLOUD_SYNC_BASE_KEY, next) + } catch (error) { + if (secretWritten) { + try { + if (currentWrappedSecret) { + this.setCloudSyncSetting(this.CLOUD_SYNC_SECRET_KEY, currentWrappedSecret) + } else { + this.deleteCloudSyncSetting(this.CLOUD_SYNC_SECRET_KEY) + } + } catch (rollbackError) { + console.error('[Config] Failed to rollback cloud sync secret:', rollbackError) + } + } + throw error + } + + return this.getCloudSyncConfig() + } + + getResolvedCloudSyncConfig(): ResolvedCloudSyncConfig | null { + const base = this.getCloudSyncBase() + const secretAccessKey = this.getCloudSyncSecret() + if (!base.endpoint || !base.bucket || !base.accessKeyId || !secretAccessKey) { + return null + } + return { + endpoint: base.endpoint, + bucket: base.bucket, + region: base.region, + prefix: base.prefix, + accessKeyId: base.accessKeyId, + secretAccessKey + } + } + // Skills settings getSkillsEnabled(): boolean { return this.getSetting('enableSkills') ?? true diff --git a/src/main/presenter/sqlitePresenter/importData.ts b/src/main/presenter/sqlitePresenter/importData.ts index 19198aa64..ffbfc618c 100644 --- a/src/main/presenter/sqlitePresenter/importData.ts +++ b/src/main/presenter/sqlitePresenter/importData.ts @@ -74,9 +74,25 @@ export class DataImporter { } private getTablesInOrder(): string[] { - const tables = this.sourceDb - .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'") - .all() as { name: string }[] + const allTables = this.sourceDb + .prepare( + "SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'" + ) + .all() as { name: string; sql: string | null }[] + + // Virtual tables (e.g. FTS5) and their shadow tables cannot be written by a plain column-copy + // INSERT — SQLite raises "table X may not be modified". Note FTS5 shadow tables + // (_data/_idx/_docsize/_config/_content) DO carry a real CREATE TABLE sql in + // sqlite_master, so they must be excluded by name prefix, not by inspecting their sql. + // For external-content FTS the index is rebuilt by triggers when the content table is imported. + const virtualTableNames = allTables + .filter((table) => typeof table.sql === 'string' && /^CREATE VIRTUAL TABLE/i.test(table.sql)) + .map((table) => table.name) + + const isVirtualOrShadow = (name: string): boolean => + virtualTableNames.some((vtab) => name === vtab || name.startsWith(`${vtab}_`)) + + const tables = allTables.filter((table) => !isVirtualOrShadow(table.name)) const preferredOrder = ['conversations', 'messages', 'attachments', 'message_attachments'] const preferredSet = new Set(preferredOrder) diff --git a/src/main/presenter/syncPresenter/cloudStorageService.ts b/src/main/presenter/syncPresenter/cloudStorageService.ts new file mode 100644 index 000000000..9a4e50dac --- /dev/null +++ b/src/main/presenter/syncPresenter/cloudStorageService.ts @@ -0,0 +1,170 @@ +import fs from 'fs' +import path from 'path' +import { Readable } from 'stream' +import { pipeline } from 'stream/promises' +import { + S3Client, + PutObjectCommand, + GetObjectCommand, + ListObjectsV2Command, + type _Object +} from '@aws-sdk/client-s3' +import type { SyncBackupInfo } from '@shared/presenter' + +/** + * Resolved cloud config carrying the real secret. Built in the main process from + * the (encrypted) values stored by ConfigPresenter — never sent to the renderer. + */ +export interface ResolvedCloudSyncConfig { + endpoint: string + bucket: string + region: string + prefix: string + accessKeyId: string + secretAccessKey: string +} + +const BACKUP_FILE_NAME_REGEX = /^backup-\d+\.zip$/ + +/** + * Thin wrapper around an S3-compatible object store (Cloudflare R2 / MinIO / AWS S3 / B2). + * Only the minimal operations needed for "upload the latest backup" and + * "pull the latest backup" are implemented — it does not manage local backups. + */ +export class CloudStorageService { + private readonly client: S3Client + private readonly bucket: string + private readonly prefix: string + + constructor(config: ResolvedCloudSyncConfig) { + this.bucket = config.bucket + // Normalize the prefix to a trailing-slash-free key segment (empty means bucket root). + this.prefix = config.prefix.replace(/^\/+|\/+$/g, '') + this.client = new S3Client({ + endpoint: config.endpoint, + // R2 expects 'auto'; AWS expects a real region. Default upstream is 'auto'. + region: config.region || 'auto', + credentials: { + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey + }, + // R2 / MinIO require path-style addressing. + forcePathStyle: true + }) + } + + private buildKey(fileName: string): string { + return this.prefix ? `${this.prefix}/${fileName}` : fileName + } + + /** ListObjects probe used by the settings "test connection" button. */ + public async testConnection(): Promise { + await this.client.send( + new ListObjectsV2Command({ + Bucket: this.bucket, + Prefix: this.prefix ? `${this.prefix}/` : undefined, + MaxKeys: 1 + }) + ) + } + + /** Upload a single local backup zip under the configured prefix. */ + public async uploadBackup(localZipPath: string, fileName: string): Promise { + const body = fs.createReadStream(localZipPath) + await this.client.send( + new PutObjectCommand({ + Bucket: this.bucket, + Key: this.buildKey(fileName), + Body: body, + ContentType: 'application/zip' + }) + ) + } + + private toReadableStream(body: unknown): NodeJS.ReadableStream { + if (body instanceof Readable) { + return body + } + + if (body && typeof (body as AsyncIterable)[Symbol.asyncIterator] === 'function') { + return Readable.from(body as AsyncIterable) + } + + const withWebStream = body as { transformToWebStream?: () => unknown } + if (typeof withWebStream?.transformToWebStream === 'function') { + return Readable.fromWeb( + withWebStream.transformToWebStream() as Parameters[0] + ) + } + + throw new Error('sync.error.cloudDownloadFailed') + } + + /** List remote `backup-*.zip` objects, newest first. */ + public async listRemoteBackups(): Promise { + const backups: SyncBackupInfo[] = [] + let continuationToken: string | undefined + + do { + const response = await this.client.send( + new ListObjectsV2Command({ + Bucket: this.bucket, + Prefix: this.prefix ? `${this.prefix}/` : undefined, + ContinuationToken: continuationToken + }) + ) + + for (const item of response.Contents ?? []) { + const info = this.toBackupInfo(item) + if (info) { + backups.push(info) + } + } + + continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined + } while (continuationToken) + + return backups.sort((a, b) => b.createdAt - a.createdAt) + } + + private toBackupInfo(item: _Object): SyncBackupInfo | null { + if (!item.Key) { + return null + } + const fileName = item.Key.split('/').pop() || '' + if (!BACKUP_FILE_NAME_REGEX.test(fileName)) { + return null + } + const match = fileName.match(/backup-(\d+)\.zip$/) + const createdAt = match ? Number(match[1]) : (item.LastModified?.getTime() ?? 0) + return { fileName, createdAt, size: item.Size ?? 0 } + } + + /** + * Download the newest remote backup into `targetDir` (the local sync folder). + * Returns the landed file name, or null when the bucket has no backup yet. + */ + public async downloadLatest(targetDir: string): Promise { + const remoteBackups = await this.listRemoteBackups() + if (remoteBackups.length === 0) { + return null + } + + const latest = remoteBackups[0] + const response = await this.client.send( + new GetObjectCommand({ + Bucket: this.bucket, + Key: this.buildKey(latest.fileName) + }) + ) + + if (!response.Body) { + throw new Error('sync.error.cloudDownloadFailed') + } + + fs.mkdirSync(targetDir, { recursive: true }) + const targetPath = path.join(targetDir, latest.fileName) + await pipeline(this.toReadableStream(response.Body), fs.createWriteStream(targetPath)) + return latest.fileName + } +} diff --git a/src/main/presenter/syncPresenter/index.ts b/src/main/presenter/syncPresenter/index.ts index 81abcdf02..bdb2ae4a5 100644 --- a/src/main/presenter/syncPresenter/index.ts +++ b/src/main/presenter/syncPresenter/index.ts @@ -7,8 +7,10 @@ import { ISyncPresenter, IConfigPresenter, ISQLitePresenter, - SyncBackupInfo + SyncBackupInfo, + CloudSyncResult } from '@shared/presenter' +import { CloudStorageService } from './cloudStorageService' import { eventBus, SendTarget } from '@/eventbus' import { SYNC_EVENTS } from '@/events' import { DataImporter } from '../sqlitePresenter/importData' @@ -43,6 +45,10 @@ const MIGRATED_APP_SETTINGS_KEYS = new Set([ 'customPrompts', 'systemPrompts' ]) +// Cloud sync credentials are machine-local (secret encrypted via safeStorage). They must never +// travel inside a backup: the secret can't be decrypted on another machine, and importing a +// foreign machine's cloud config would clobber the local one. Stripped on backup, preserved on import. +const CLOUD_SYNC_APP_SETTINGS_KEYS = ['cloudSyncConfig', 'cloudSyncSecret'] as const const KNOWN_IMPORT_ERRORS = new Set([ 'sync.error.noValidBackup', 'sync.error.unsupportedBackupVersion', @@ -114,6 +120,88 @@ export class SyncPresenter implements ISyncPresenter { return { isBackingUp: this.isBackingUp, lastBackupTime } } + // === Cloud sync (S3-compatible) === + + private buildCloudService(): CloudStorageService { + const resolved = this.configPresenter.getResolvedCloudSyncConfig() + if (!resolved) { + throw new Error('sync.error.cloudNotConfigured') + } + return new CloudStorageService(resolved) + } + + private normalizeCloudError(error: unknown): string { + const message = error instanceof Error ? error.message : String(error) + if (message.startsWith('sync.error.')) { + return message + } + return message || 'sync.error.cloudOperationFailed' + } + + public async testCloudConnection(): Promise { + try { + const service = this.buildCloudService() + await service.testConnection() + return { success: true, message: 'sync.success.cloudConnected' } + } catch (error) { + console.error('Cloud connection test failed:', error) + return { success: false, message: this.normalizeCloudError(error) } + } + } + + public async uploadLatestBackupToCloud(): Promise { + try { + const service = this.buildCloudService() + const backups = (await this.listBackups()).filter(({ fileName }) => + BACKUP_FILE_NAME_REGEX.test(fileName) + ) + if (backups.length === 0) { + return { success: false, message: 'sync.error.noLocalBackup' } + } + const { path: syncFolderPath } = await this.checkSyncFolder() + const backupsDir = this.getBackupsDirectory(syncFolderPath) + + for (const backup of backups) { + const localPath = path.join(backupsDir, backup.fileName) + if (!fs.existsSync(localPath)) { + continue + } + try { + this.validateBackupArchive(localPath) + } catch (error) { + console.warn('Skipping invalid local backup during cloud upload:', backup.fileName, error) + continue + } + await service.uploadBackup(localPath, backup.fileName) + return { success: true, message: 'sync.success.cloudUploaded', fileName: backup.fileName } + } + + return { success: false, message: 'sync.error.noLocalBackup' } + } catch (error) { + console.error('Cloud upload failed:', error) + return { success: false, message: this.normalizeCloudError(error) } + } + } + + public async pullLatestBackupFromCloud( + importMode: ImportMode = ImportMode.INCREMENT + ): Promise { + try { + const service = this.buildCloudService() + const { path: syncFolderPath } = await this.checkSyncFolder() + const backupsDir = this.getBackupsDirectory(syncFolderPath) + const fileName = await service.downloadLatest(backupsDir) + if (!fileName) { + return { success: false, message: 'sync.error.cloudNoBackup' } + } + const result = await this.importFromSync(fileName, importMode) + return { ...result, fileName } + } catch (error) { + console.error('Cloud pull failed:', error) + return { success: false, message: this.normalizeCloudError(error) } + } + } + public async listBackups(): Promise { const { path: syncFolderPath } = await this.checkSyncFolder() const backupsDir = this.getBackupsDirectory(syncFolderPath) @@ -661,6 +749,9 @@ export class SyncPresenter implements ISyncPresenter { if (MIGRATED_APP_SETTINGS_KEYS.has(key)) { return false } + if ((CLOUD_SYNC_APP_SETTINGS_KEYS as readonly string[]).includes(key)) { + return false + } if (key.startsWith('model_status_') || key.startsWith('custom_models_')) { return false } @@ -781,6 +872,46 @@ export class SyncPresenter implements ISyncPresenter { } } + private validateBackupArchive(backupZipPath: string): void { + const extractionDir = path.join(app.getPath('temp'), `deepchat-backup-validate-${Date.now()}`) + fs.mkdirSync(extractionDir, { recursive: true }) + + try { + this.extractBackupArchive(backupZipPath, extractionDir) + const configImportService = this.createConfigImportService() + const manifest = configImportService.readManifest(extractionDir) + const backupVersion = this.resolveBackupVersion(manifest) + const usesSqliteConfigStorage = backupVersion >= 2 && manifest?.configStorage === 'sqlite' + const backupDbSource = this.resolveBackupDbSource(extractionDir) + const backupAppSettingsPath = path.join(extractionDir, ZIP_PATHS.appSettings) + + if (!backupDbSource || !fs.existsSync(backupAppSettingsPath)) { + throw new Error('sync.error.noValidBackup') + } + if (usesSqliteConfigStorage && backupDbSource.type !== 'agent') { + throw new Error('sync.error.noValidBackup') + } + } finally { + this.removeDirectory(extractionDir) + } + } + + private readSettingsFile(filePath: string): Record | null { + if (!fs.existsSync(filePath)) { + return null + } + try { + const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8')) + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { + throw new Error('sync.error.importFailed') + } + return parsed as Record + } catch (error) { + console.error('Failed to read settings file for cloud config preservation:', error) + throw new Error('sync.error.importFailed') + } + } + private mergeAppSettingsPreservingSync(backupPath: string, targetPath: string): void { if (!fs.existsSync(backupPath)) { return @@ -811,6 +942,15 @@ export class SyncPresenter implements ISyncPresenter { preservedSettings.syncFolderPath = this.configPresenter.getSyncFolderPath() preservedSettings.lastSyncTime = this.configPresenter.getLastSyncTime() + // Keep the local machine's cloud credentials — a backup never carries them (see + // CLOUD_SYNC_APP_SETTINGS_KEYS), so read them back from the current target file before overwrite. + const localSettings = this.readSettingsFile(targetPath) + for (const key of CLOUD_SYNC_APP_SETTINGS_KEYS) { + if (localSettings && key in localSettings) { + preservedSettings[key] = localSettings[key] + } + } + const sanitizedBackupSettings = this.removeMigratedAppSettings(backupSettings) const mergedSettings = { ...sanitizedBackupSettings, diff --git a/src/main/routes/index.ts b/src/main/routes/index.ts index 77a1cca46..eea2ba791 100644 --- a/src/main/routes/index.ts +++ b/src/main/routes/index.ts @@ -191,6 +191,11 @@ import { syncListBackupsRoute, syncOpenFolderRoute, syncStartBackupRoute, + syncGetCloudConfigRoute, + syncSetCloudConfigRoute, + syncTestCloudRoute, + syncUploadToCloudRoute, + syncPullFromCloudRoute, systemOpenSettingsRoute, tabCaptureCurrentAreaRoute, tabNotifyRendererActivatedRoute, @@ -2535,6 +2540,64 @@ export async function dispatchDeepchatRoute( return syncOpenFolderRoute.output.parse({ opened: true }) } + case syncGetCloudConfigRoute.name: { + syncGetCloudConfigRoute.input.parse(rawInput) + const config = runtime.configPresenter.getCloudSyncConfig() + return syncGetCloudConfigRoute.output.parse({ config }) + } + + case syncSetCloudConfigRoute.name: { + const input = syncSetCloudConfigRoute.input.parse(rawInput) + const config = runtime.configPresenter.setCloudSyncConfig(input.config) + return syncSetCloudConfigRoute.output.parse({ config }) + } + + case syncTestCloudRoute.name: { + syncTestCloudRoute.input.parse(rawInput) + const result = await runtime.syncPresenter.testCloudConnection() + return syncTestCloudRoute.output.parse({ result }) + } + + case syncUploadToCloudRoute.name: { + syncUploadToCloudRoute.input.parse(rawInput) + const result = await runtime.syncPresenter.uploadLatestBackupToCloud() + if (result?.success) { + recordSettingsActivity(runtime, { + category: 'data', + action: 'backup_created', + targetType: 'backup', + targetId: result.fileName ?? 'cloud', + targetLabel: result.fileName ?? 'cloud', + routeName: 'settings-database', + summaryKey: 'settings.controlCenter.activity.backupCreated', + summaryParams: { + name: result.fileName ?? '' + } + }) + } + return syncUploadToCloudRoute.output.parse({ result }) + } + + case syncPullFromCloudRoute.name: { + const input = syncPullFromCloudRoute.input.parse(rawInput) + const result = await runtime.syncPresenter.pullLatestBackupFromCloud(input.mode) + if (result?.success) { + recordSettingsActivity(runtime, { + category: 'data', + action: 'imported', + targetType: 'backup', + targetId: result.fileName ?? 'cloud', + targetLabel: result.fileName ?? 'cloud', + routeName: 'settings-database', + summaryKey: 'settings.controlCenter.activity.backupImported', + summaryParams: { + name: result.fileName ?? '' + } + }) + } + return syncPullFromCloudRoute.output.parse({ result }) + } + case upgradeGetStatusRoute.name: { upgradeGetStatusRoute.input.parse(rawInput) const snapshot = runtime.upgradePresenter.getUpdateStatus() diff --git a/src/renderer/api/SyncClient.ts b/src/renderer/api/SyncClient.ts index e0fabddd0..137e8d374 100644 --- a/src/renderer/api/SyncClient.ts +++ b/src/renderer/api/SyncClient.ts @@ -13,8 +13,14 @@ import { syncImportRoute, syncListBackupsRoute, syncOpenFolderRoute, - syncStartBackupRoute + syncStartBackupRoute, + syncGetCloudConfigRoute, + syncSetCloudConfigRoute, + syncTestCloudRoute, + syncUploadToCloudRoute, + syncPullFromCloudRoute } from '@shared/contracts/routes' +import type { CloudSyncConfigInput } from '@shared/presenter' import { getDeepchatBridge } from './core' export function createSyncClient(bridge: DeepchatBridge = getDeepchatBridge()) { @@ -45,6 +51,31 @@ export function createSyncClient(bridge: DeepchatBridge = getDeepchatBridge()) { await bridge.invoke(syncOpenFolderRoute.name, {}) } + async function getCloudConfig() { + const result = await bridge.invoke(syncGetCloudConfigRoute.name, {}) + return result.config + } + + async function setCloudConfig(config: CloudSyncConfigInput) { + const result = await bridge.invoke(syncSetCloudConfigRoute.name, { config }) + return result.config + } + + async function testCloudConnection() { + const result = await bridge.invoke(syncTestCloudRoute.name, {}) + return result.result + } + + async function uploadToCloud() { + const result = await bridge.invoke(syncUploadToCloudRoute.name, {}) + return result.result + } + + async function pullFromCloud(mode?: 'increment' | 'overwrite') { + const result = await bridge.invoke(syncPullFromCloudRoute.name, { mode }) + return result.result + } + function onBackupStarted(listener: (payload: { version: number }) => void) { return bridge.on(syncBackupStartedEvent.name, listener) } @@ -88,6 +119,11 @@ export function createSyncClient(bridge: DeepchatBridge = getDeepchatBridge()) { startBackup, importFromSync, openSyncFolder, + getCloudConfig, + setCloudConfig, + testCloudConnection, + uploadToCloud, + pullFromCloud, onBackupStarted, onBackupCompleted, onBackupError, diff --git a/src/renderer/settings/components/DataSettings.vue b/src/renderer/settings/components/DataSettings.vue index 56c1ca23d..5ef140d50 100644 --- a/src/renderer/settings/components/DataSettings.vue +++ b/src/renderer/settings/components/DataSettings.vue @@ -164,6 +164,126 @@ + +
+
+ + + {{ t('settings.data.cloudSync.title') }} + +

+ {{ t('settings.data.cloudSync.description') }} +

+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +

+ {{ t('settings.data.cloudSync.safeStorageUnavailable') }} +

+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+
+
+
+
@@ -779,7 +899,9 @@ const databaseSecurityClient = createDatabaseSecurityClient() const { backups: backupsRef, isBackingUp: isBackingUpRef, - isImporting: isImportingRef + isImporting: isImportingRef, + cloudConfig, + isCloudBusy } = storeToRefs(syncStore) const { toast } = useToast() @@ -985,6 +1107,97 @@ const handleSyncEnabledChange = (value: boolean) => { syncEnabled.value = value } +// === Cloud sync (S3-compatible) === +const cloudPullMode = ref<'increment' | 'overwrite'>('increment') +const cloudForm = ref({ + endpoint: '', + bucket: '', + region: 'auto', + prefix: 'deepchat-backups', + accessKeyId: '', + secretAccessKey: '' +}) + +watch( + cloudConfig, + (config) => { + if (!config) { + return + } + cloudForm.value.endpoint = config.endpoint + cloudForm.value.bucket = config.bucket + cloudForm.value.region = config.region || 'auto' + cloudForm.value.prefix = config.prefix || 'deepchat-backups' + cloudForm.value.accessKeyId = config.accessKeyId + // never prefill the secret; empty means "keep existing" + cloudForm.value.secretAccessKey = '' + }, + { immediate: true } +) + +const handleSaveCloud = async () => { + await syncStore.saveCloudConfig({ + endpoint: cloudForm.value.endpoint.trim(), + bucket: cloudForm.value.bucket.trim(), + region: cloudForm.value.region.trim() || 'auto', + prefix: cloudForm.value.prefix.trim(), + accessKeyId: cloudForm.value.accessKeyId.trim(), + // omit when empty so the existing secret is preserved + secretAccessKey: cloudForm.value.secretAccessKey || undefined + }) + cloudForm.value.secretAccessKey = '' + toast({ + title: t('settings.data.cloudSync.savedTitle'), + duration: 3000 + }) +} + +const handleTestCloud = async () => { + const result = await syncStore.testCloud() + if (!result) { + return + } + toast({ + title: result.success + ? t('settings.data.cloudSync.testSuccessTitle') + : t('settings.data.cloudSync.testFailedTitle'), + description: result.success ? undefined : t(result.message), + variant: result.success ? 'default' : 'destructive', + duration: 4000 + }) +} + +const handleUploadToCloud = async () => { + const result = await syncStore.uploadToCloud() + if (!result) { + return + } + toast({ + title: result.success + ? t('settings.data.cloudSync.uploadSuccessTitle') + : t('settings.data.cloudSync.uploadFailedTitle'), + description: result.success ? undefined : t(result.message), + variant: result.success ? 'default' : 'destructive', + duration: 4000 + }) +} + +const handlePullFromCloud = async () => { + const result = await syncStore.pullFromCloud(cloudPullMode.value) + if (!result) { + return + } + if (result.success) { + toast({ + title: t('settings.data.cloudSync.pullSuccessTitle'), + description: t('settings.provider.toast.importSuccessMessage', { + count: result.count ?? 0 + }), + duration: 4000 + }) + } +} + const clearDatabasePasswordFields = () => { databaseCurrentPassword.value = '' databaseNewPassword.value = '' diff --git a/src/renderer/src/i18n/da-DK/settings.json b/src/renderer/src/i18n/da-DK/settings.json index 0e63eaea7..badef0419 100644 --- a/src/renderer/src/i18n/da-DK/settings.json +++ b/src/renderer/src/i18n/da-DK/settings.json @@ -318,6 +318,28 @@ "disableDialogDescription": "Migrér den aktuelle krypterede database tilbage til en lokal database i klartekst.", "cancelButton": "Annuller", "unknown": "Ukendt" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/da-DK/sync.json b/src/renderer/src/i18n/da-DK/sync.json index 1dc2cb946..ea07c801f 100644 --- a/src/renderer/src/i18n/da-DK/sync.json +++ b/src/renderer/src/i18n/da-DK/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "Import af {count} samtale(r) lykkedes." + "importComplete": "Import af {count} samtale(r) lykkedes.", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "Synkronisering er ikke aktiveret", @@ -15,6 +18,12 @@ "importProcess": "Der opstod en fejl under importen", "unknown": "Ukendt fejl", "encryptedBackupPasswordMissing": "Denne backup er krypteret, men den lokale databasenøgle er ikke tilgængelig. Lås først den krypterede database op, og importér derefter igen.", - "overwriteEncryptionMismatch": "Overskrivningsimport kræver, at backup og aktuel database har samme krypteringstilstand." + "overwriteEncryptionMismatch": "Overskrivningsimport kræver, at backup og aktuel database har samme krypteringstilstand.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/de-DE/settings.json b/src/renderer/src/i18n/de-DE/settings.json index 0f40594af..037e26bfe 100644 --- a/src/renderer/src/i18n/de-DE/settings.json +++ b/src/renderer/src/i18n/de-DE/settings.json @@ -448,6 +448,28 @@ "mistral": "Mistral AI" }, "applyFailed": "Import fehlgeschlagen: {message}" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/de-DE/sync.json b/src/renderer/src/i18n/de-DE/sync.json index d4a9f5649..53565cbc7 100644 --- a/src/renderer/src/i18n/de-DE/sync.json +++ b/src/renderer/src/i18n/de-DE/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "{count} Chats erfolgreich importiert" + "importComplete": "{count} Chats erfolgreich importiert", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "Synchronisierung ist nicht aktiviert", @@ -15,6 +18,12 @@ "importProcess": "Fehler während des Imports", "unknown": "Unbekannter Fehler", "encryptedBackupPasswordMissing": "Dieses Backup ist verschlüsselt, aber der lokale Datenbankschlüssel ist nicht verfügbar. Entsperren Sie zuerst die verschlüsselte Datenbank und importieren Sie dann erneut.", - "overwriteEncryptionMismatch": "Beim überschreibenden Import müssen Backup und aktuelle Datenbank denselben Verschlüsselungsstatus haben." + "overwriteEncryptionMismatch": "Beim überschreibenden Import müssen Backup und aktuelle Datenbank denselben Verschlüsselungsstatus haben.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/en-US/settings.json b/src/renderer/src/i18n/en-US/settings.json index a52f60c3e..2e72d17d9 100644 --- a/src/renderer/src/i18n/en-US/settings.json +++ b/src/renderer/src/i18n/en-US/settings.json @@ -227,6 +227,28 @@ "importData": "Import Data", "incrementImport": "Incremental Import", "overwriteImport": "Overwrite Import", + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" + }, "backupSelectLabel": "Select backup", "backupSelectDescription": "Choose the backup snapshot to import.", "selectBackupPlaceholder": "Select a backup", diff --git a/src/renderer/src/i18n/en-US/sync.json b/src/renderer/src/i18n/en-US/sync.json index 3d5694f6a..0fe723c05 100644 --- a/src/renderer/src/i18n/en-US/sync.json +++ b/src/renderer/src/i18n/en-US/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "Successfully imported {count} conversation(s)." + "importComplete": "Successfully imported {count} conversation(s).", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "Sync is not enabled", @@ -15,6 +18,12 @@ "importProcess": "An error occurred during the import process", "unknown": "Unknown error", "encryptedBackupPasswordMissing": "This backup is encrypted, but the local database key is not available. Unlock the encrypted database first, then import again.", - "overwriteEncryptionMismatch": "Overwrite import requires the backup and current database to use the same encryption state." + "overwriteEncryptionMismatch": "Overwrite import requires the backup and current database to use the same encryption state.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/es-ES/settings.json b/src/renderer/src/i18n/es-ES/settings.json index 1d802a1dc..02420e6fc 100644 --- a/src/renderer/src/i18n/es-ES/settings.json +++ b/src/renderer/src/i18n/es-ES/settings.json @@ -448,6 +448,28 @@ "mistral": "Mistral AI" }, "applyFailed": "Error de importación: {message}" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/es-ES/sync.json b/src/renderer/src/i18n/es-ES/sync.json index 9eaeb91f8..6bbea00f9 100644 --- a/src/renderer/src/i18n/es-ES/sync.json +++ b/src/renderer/src/i18n/es-ES/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "Se importaron correctamente {count} conversaciones." + "importComplete": "Se importaron correctamente {count} conversaciones.", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "La sincronización no está habilitada", @@ -15,6 +18,12 @@ "importProcess": "Se produjo un error durante el proceso de importación.", "unknown": "Error desconocido", "encryptedBackupPasswordMissing": "Esta copia de seguridad está cifrada, pero la clave de la base de datos local no está disponible. Primero desbloquee la base de datos cifrada y luego importe nuevamente.", - "overwriteEncryptionMismatch": "La importación sobrescrita requiere que la copia de seguridad y la base de datos actual utilicen el mismo estado de cifrado." + "overwriteEncryptionMismatch": "La importación sobrescrita requiere que la copia de seguridad y la base de datos actual utilicen el mismo estado de cifrado.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/fa-IR/settings.json b/src/renderer/src/i18n/fa-IR/settings.json index 7ff773591..e8a06a55a 100644 --- a/src/renderer/src/i18n/fa-IR/settings.json +++ b/src/renderer/src/i18n/fa-IR/settings.json @@ -385,6 +385,28 @@ "disableDialogDescription": "پایگاه داده رمزگذاری‌شده فعلی را به یک پایگاه داده محلی متن ساده برگردانید.", "cancelButton": "لغو", "unknown": "نامشخص" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/fa-IR/sync.json b/src/renderer/src/i18n/fa-IR/sync.json index daf0ec4a3..e1e31adbd 100644 --- a/src/renderer/src/i18n/fa-IR/sync.json +++ b/src/renderer/src/i18n/fa-IR/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "{count} مکالمه با موفقیت وارد شد." + "importComplete": "{count} مکالمه با موفقیت وارد شد.", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "ویژگی همگام‌سازی روشن نیست", @@ -15,6 +18,12 @@ "importProcess": "خطا در فرایند وارد کردن", "unknown": "خطای ناشناخته", "encryptedBackupPasswordMissing": "این نسخه پشتیبان رمزگذاری شده است، اما کلید پایگاه داده محلی در دسترس نیست. ابتدا پایگاه داده رمزگذاری‌شده را باز کنید و سپس دوباره وارد کنید.", - "overwriteEncryptionMismatch": "وارد کردن با بازنویسی نیاز دارد که نسخه پشتیبان و پایگاه داده فعلی وضعیت رمزگذاری یکسانی داشته باشند." + "overwriteEncryptionMismatch": "وارد کردن با بازنویسی نیاز دارد که نسخه پشتیبان و پایگاه داده فعلی وضعیت رمزگذاری یکسانی داشته باشند.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/fr-FR/settings.json b/src/renderer/src/i18n/fr-FR/settings.json index 3fdb48565..1cd3e6fed 100644 --- a/src/renderer/src/i18n/fr-FR/settings.json +++ b/src/renderer/src/i18n/fr-FR/settings.json @@ -385,6 +385,28 @@ "disableDialogDescription": "Migre la base chiffrée actuelle vers une base locale en clair.", "cancelButton": "Annuler", "unknown": "Inconnu" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/fr-FR/sync.json b/src/renderer/src/i18n/fr-FR/sync.json index f889c4fb8..0b7af962f 100644 --- a/src/renderer/src/i18n/fr-FR/sync.json +++ b/src/renderer/src/i18n/fr-FR/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "{count} conversation(s) importée(s) avec succès." + "importComplete": "{count} conversation(s) importée(s) avec succès.", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "La fonction de synchronisation n'est pas activée", @@ -15,6 +18,12 @@ "importProcess": "Erreur dans le processus d'importation", "unknown": "Erreur inconnue", "encryptedBackupPasswordMissing": "Cette sauvegarde est chiffrée, mais la clé de la base locale n’est pas disponible. Déverrouillez d’abord la base chiffrée, puis relancez l’importation.", - "overwriteEncryptionMismatch": "L’importation avec remplacement exige que la sauvegarde et la base actuelle aient le même état de chiffrement." + "overwriteEncryptionMismatch": "L’importation avec remplacement exige que la sauvegarde et la base actuelle aient le même état de chiffrement.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/he-IL/settings.json b/src/renderer/src/i18n/he-IL/settings.json index 9ea034f97..26cba9afe 100644 --- a/src/renderer/src/i18n/he-IL/settings.json +++ b/src/renderer/src/i18n/he-IL/settings.json @@ -385,6 +385,28 @@ "disableDialogDescription": "העברת מסד הנתונים המוצפן הנוכחי בחזרה למסד נתונים מקומי גלוי.", "cancelButton": "ביטול", "unknown": "לא ידוע" + }, + "cloudSync": { + "title": "סנכרון ענן (תואם S3)", + "description": "העלה גיבויים לאחסון תואם S3 כמו Cloudflare R2, ומשוך את הגיבוי האחרון במכשיר אחר.", + "endpoint": "נקודת קצה", + "bucket": "דלי", + "region": "אזור", + "prefix": "קידומת נתיב", + "accessKeyId": "מזהה מפתח גישה", + "secretAccessKey": "מפתח גישה סודי", + "secretConfigured": "מוגדר (השאר ריק כדי לשמור)", + "safeStorageUnavailable": "מחזיק המפתחות של המערכת אינו זמין; לא ניתן לאחסן את המפתח הסודי באופן מאובטח.", + "save": "שמור", + "test": "בדוק חיבור", + "upload": "העלה לענן", + "pull": "משוך את העדכני מהענן", + "savedTitle": "תצורת הענן נשמרה", + "testSuccessTitle": "החיבור הצליח", + "testFailedTitle": "החיבור נכשל", + "uploadSuccessTitle": "ההעלאה הצליחה", + "uploadFailedTitle": "ההעלאה נכשלה", + "pullSuccessTitle": "המשיכה הצליחה" } }, "dashboard": { diff --git a/src/renderer/src/i18n/he-IL/sync.json b/src/renderer/src/i18n/he-IL/sync.json index 83f335e6e..36ed4d8a8 100644 --- a/src/renderer/src/i18n/he-IL/sync.json +++ b/src/renderer/src/i18n/he-IL/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "יובאו בהצלחה {count} שיחות." + "importComplete": "יובאו בהצלחה {count} שיחות.", + "cloudConnected": "החיבור לענן הצליח", + "cloudUploaded": "הועלה לענן", + "cloudPulled": "הגיבוי האחרון נמשך מהענן" }, "error": { "notEnabled": "הסנכרון אינו מופעל", @@ -15,6 +18,12 @@ "importProcess": "אירעה שגיאה במהלך תהליך הייבוא", "unknown": "שגיאה לא ידועה", "encryptedBackupPasswordMissing": "הגיבוי הזה מוצפן, אך מפתח מסד הנתונים המקומי אינו זמין. פתח תחילה את מסד הנתונים המוצפן ולאחר מכן יבא שוב.", - "overwriteEncryptionMismatch": "ייבוא בדריסת נתונים דורש שלגיבוי ולמסד הנתונים הנוכחי יהיה אותו מצב הצפנה." + "overwriteEncryptionMismatch": "ייבוא בדריסת נתונים דורש שלגיבוי ולמסד הנתונים הנוכחי יהיה אותו מצב הצפנה.", + "cloudNotConfigured": "הענן אינו מוגדר. מלא ושמור תחילה את חיבור הענן.", + "cloudNoBackup": "אין גיבוי זמין בדלי הענן", + "cloudDownloadFailed": "נכשל בהורדת הגיבוי מהענן", + "cloudOperationFailed": "פעולת הענן נכשלה", + "noLocalBackup": "אין גיבוי מקומי להעלאה. הפעל גיבוי תחילה.", + "safeStorageUnavailable": "מחזיק המפתחות של המערכת אינו זמין; לא ניתן לאחסן את המפתח הסודי באופן מאובטח" } } diff --git a/src/renderer/src/i18n/id-ID/settings.json b/src/renderer/src/i18n/id-ID/settings.json index 9b6ceacfb..604651afa 100644 --- a/src/renderer/src/i18n/id-ID/settings.json +++ b/src/renderer/src/i18n/id-ID/settings.json @@ -448,6 +448,28 @@ "mistral": "Mistral AI" }, "applyFailed": "Impor gagal: {message}" + }, + "cloudSync": { + "title": "Sinkronisasi Cloud (kompatibel S3)", + "description": "Unggah cadangan ke penyimpanan yang kompatibel dengan S3 seperti Cloudflare R2, lalu tarik cadangan terbaru di perangkat lain.", + "endpoint": "Titik Akhir", + "bucket": "Bucket", + "region": "Wilayah", + "prefix": "Prefiks Jalur", + "accessKeyId": "ID Kunci Akses", + "secretAccessKey": "Kunci Akses Rahasia", + "secretConfigured": "Terkonfigurasi (biarkan kosong untuk mempertahankan)", + "safeStorageUnavailable": "Keychain sistem tidak tersedia; kunci rahasia tidak dapat disimpan dengan aman.", + "save": "Simpan", + "test": "Uji Koneksi", + "upload": "Unggah ke Cloud", + "pull": "Tarik Terbaru dari Cloud", + "savedTitle": "Konfigurasi cloud disimpan", + "testSuccessTitle": "Koneksi berhasil", + "testFailedTitle": "Koneksi gagal", + "uploadSuccessTitle": "Unggah berhasil", + "uploadFailedTitle": "Unggah gagal", + "pullSuccessTitle": "Tarik berhasil" } }, "dashboard": { diff --git a/src/renderer/src/i18n/id-ID/sync.json b/src/renderer/src/i18n/id-ID/sync.json index d05dfdb13..ef0a2b56d 100644 --- a/src/renderer/src/i18n/id-ID/sync.json +++ b/src/renderer/src/i18n/id-ID/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "Berhasil mengimpor percakapan {count}" + "importComplete": "Berhasil mengimpor percakapan {count}", + "cloudConnected": "Koneksi cloud berhasil", + "cloudUploaded": "Diunggah ke cloud", + "cloudPulled": "Cadangan terbaru ditarik dari cloud" }, "error": { "notEnabled": "Sinkronisasi tidak diaktifkan", @@ -15,6 +18,12 @@ "importProcess": "Kesalahan saat mengimpor", "unknown": "kesalahan yang tidak diketahui", "encryptedBackupPasswordMissing": "Cadangan dienkripsi, namun kunci database lokal tidak tersedia. Harap buka kunci basis data terenkripsi terlebih dahulu lalu impor lagi.", - "overwriteEncryptionMismatch": "Impor penimpaan mengharuskan database cadangan dan database saat ini berada dalam status enkripsi yang sama." + "overwriteEncryptionMismatch": "Impor penimpaan mengharuskan database cadangan dan database saat ini berada dalam status enkripsi yang sama.", + "cloudNotConfigured": "Cloud belum dikonfigurasi. Isi dan simpan koneksi cloud terlebih dahulu.", + "cloudNoBackup": "Tidak ada cadangan yang tersedia di bucket cloud", + "cloudDownloadFailed": "Gagal mengunduh cadangan dari cloud", + "cloudOperationFailed": "Operasi cloud gagal", + "noLocalBackup": "Tidak ada cadangan lokal untuk diunggah. Jalankan pencadangan terlebih dahulu.", + "safeStorageUnavailable": "Keychain sistem tidak tersedia; kunci rahasia tidak dapat disimpan dengan aman" } } diff --git a/src/renderer/src/i18n/it-IT/settings.json b/src/renderer/src/i18n/it-IT/settings.json index e7f2638a2..171b8810a 100644 --- a/src/renderer/src/i18n/it-IT/settings.json +++ b/src/renderer/src/i18n/it-IT/settings.json @@ -448,6 +448,28 @@ "mistral": "Mistral AI" }, "applyFailed": "Importazione non riuscita: {message}" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/it-IT/sync.json b/src/renderer/src/i18n/it-IT/sync.json index 68f65e070..137225d44 100644 --- a/src/renderer/src/i18n/it-IT/sync.json +++ b/src/renderer/src/i18n/it-IT/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "{count} conversazioni importate correttamente" + "importComplete": "{count} conversazioni importate correttamente", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "La sincronizzazione non è attiva", @@ -15,6 +18,12 @@ "importProcess": "Errore durante l'importazione", "unknown": "Errore sconosciuto", "encryptedBackupPasswordMissing": "Questo backup è cifrato, ma la chiave del database locale non è disponibile. Sblocca prima il database cifrato, poi importa di nuovo.", - "overwriteEncryptionMismatch": "L'importazione con sovrascrittura richiede che backup e database corrente abbiano lo stesso stato di cifratura." + "overwriteEncryptionMismatch": "L'importazione con sovrascrittura richiede che backup e database corrente abbiano lo stesso stato di cifratura.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/ja-JP/settings.json b/src/renderer/src/i18n/ja-JP/settings.json index 86bd64d6f..e33db0abf 100644 --- a/src/renderer/src/i18n/ja-JP/settings.json +++ b/src/renderer/src/i18n/ja-JP/settings.json @@ -385,6 +385,28 @@ "disableDialogDescription": "現在の暗号化データベースをローカルの平文データベースに戻します。", "cancelButton": "キャンセル", "unknown": "不明" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/ja-JP/sync.json b/src/renderer/src/i18n/ja-JP/sync.json index 84f95b204..fba481a96 100644 --- a/src/renderer/src/i18n/ja-JP/sync.json +++ b/src/renderer/src/i18n/ja-JP/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "{count}件の会話をインポートしました。" + "importComplete": "{count}件の会話をインポートしました。", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "同期機能が有効になっていません", @@ -15,6 +18,12 @@ "importProcess": "インポートプロセスでエラーが発生しました", "unknown": "不明なエラー", "encryptedBackupPasswordMissing": "このバックアップは暗号化されていますが、ローカルデータベースキーを利用できません。先に暗号化データベースのロックを解除してから、もう一度インポートしてください。", - "overwriteEncryptionMismatch": "上書きインポートでは、バックアップと現在のデータベースの暗号化状態が同じである必要があります。" + "overwriteEncryptionMismatch": "上書きインポートでは、バックアップと現在のデータベースの暗号化状態が同じである必要があります。", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/ko-KR/settings.json b/src/renderer/src/i18n/ko-KR/settings.json index 2873436cb..0894c9634 100644 --- a/src/renderer/src/i18n/ko-KR/settings.json +++ b/src/renderer/src/i18n/ko-KR/settings.json @@ -385,6 +385,28 @@ "disableDialogDescription": "현재 암호화된 데이터베이스를 로컬 평문 데이터베이스로 되돌립니다.", "cancelButton": "취소", "unknown": "알 수 없음" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/ko-KR/sync.json b/src/renderer/src/i18n/ko-KR/sync.json index a8d545801..150156b76 100644 --- a/src/renderer/src/i18n/ko-KR/sync.json +++ b/src/renderer/src/i18n/ko-KR/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "{count}개의 대화를 성공적으로 가져왔습니다." + "importComplete": "{count}개의 대화를 성공적으로 가져왔습니다.", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "동기화 기능이 활성화되지 않았습니다", @@ -15,6 +18,12 @@ "importProcess": "가져오기 과정에서 오류 발생", "unknown": "알 수 없는 오류", "encryptedBackupPasswordMissing": "이 백업은 암호화되어 있지만 로컬 데이터베이스 키를 사용할 수 없습니다. 먼저 암호화된 데이터베이스의 잠금을 해제한 다음 다시 가져오세요.", - "overwriteEncryptionMismatch": "덮어쓰기 가져오기는 백업과 현재 데이터베이스의 암호화 상태가 같아야 합니다." + "overwriteEncryptionMismatch": "덮어쓰기 가져오기는 백업과 현재 데이터베이스의 암호화 상태가 같아야 합니다.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/ms-MY/settings.json b/src/renderer/src/i18n/ms-MY/settings.json index 9b38ea043..d8728b472 100644 --- a/src/renderer/src/i18n/ms-MY/settings.json +++ b/src/renderer/src/i18n/ms-MY/settings.json @@ -448,6 +448,28 @@ "mistral": "Mistral AI" }, "applyFailed": "Import gagal: {message}" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/ms-MY/sync.json b/src/renderer/src/i18n/ms-MY/sync.json index 2f25ebe79..b78f3f16e 100644 --- a/src/renderer/src/i18n/ms-MY/sync.json +++ b/src/renderer/src/i18n/ms-MY/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "Berjaya mengimport perbualan {count}" + "importComplete": "Berjaya mengimport perbualan {count}", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "Penyegerakan tidak didayakan", @@ -15,6 +18,12 @@ "importProcess": "Ralat semasa import", "unknown": "ralat yang tidak diketahui", "encryptedBackupPasswordMissing": "Sandaran disulitkan, tetapi kunci pangkalan data tempatan tidak tersedia. Sila buka kunci pangkalan data yang disulitkan dahulu dan kemudian importnya semula.", - "overwriteEncryptionMismatch": "Import ganti ganti memerlukan pangkalan data sandaran dan semasa berada dalam keadaan penyulitan yang sama." + "overwriteEncryptionMismatch": "Import ganti ganti memerlukan pangkalan data sandaran dan semasa berada dalam keadaan penyulitan yang sama.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/pl-PL/settings.json b/src/renderer/src/i18n/pl-PL/settings.json index 4e93f30b1..b1eafe756 100644 --- a/src/renderer/src/i18n/pl-PL/settings.json +++ b/src/renderer/src/i18n/pl-PL/settings.json @@ -448,6 +448,28 @@ "mistral": "AI Mistrala" }, "applyFailed": "Import nie powiódł się: {message}" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/pl-PL/sync.json b/src/renderer/src/i18n/pl-PL/sync.json index 6f594e768..8d69daa63 100644 --- a/src/renderer/src/i18n/pl-PL/sync.json +++ b/src/renderer/src/i18n/pl-PL/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "Pomyślnie zaimportowano konwersacje {count}." + "importComplete": "Pomyślnie zaimportowano konwersacje {count}.", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "Synchronizacja nie jest włączona", @@ -15,6 +18,12 @@ "importProcess": "Wystąpił błąd podczas procesu importowania", "unknown": "Nieznany błąd", "encryptedBackupPasswordMissing": "Ta kopia zapasowa jest zaszyfrowana, ale klucz lokalnej bazy danych nie jest dostępny. Najpierw odblokuj zaszyfrowaną bazę danych, a następnie zaimportuj ją ponownie.", - "overwriteEncryptionMismatch": "Import nadpisujący wymaga, aby kopia zapasowa i bieżąca baza danych korzystały z tego samego stanu szyfrowania." + "overwriteEncryptionMismatch": "Import nadpisujący wymaga, aby kopia zapasowa i bieżąca baza danych korzystały z tego samego stanu szyfrowania.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/pt-BR/settings.json b/src/renderer/src/i18n/pt-BR/settings.json index 1487d8484..b4e3e2a71 100644 --- a/src/renderer/src/i18n/pt-BR/settings.json +++ b/src/renderer/src/i18n/pt-BR/settings.json @@ -385,6 +385,28 @@ "disableDialogDescription": "Migra o banco criptografado atual de volta para um banco local em texto puro.", "cancelButton": "Cancelar", "unknown": "Desconhecido" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/pt-BR/sync.json b/src/renderer/src/i18n/pt-BR/sync.json index 8d0e72ed3..752210089 100644 --- a/src/renderer/src/i18n/pt-BR/sync.json +++ b/src/renderer/src/i18n/pt-BR/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "{count} conversa(s) importada(s) com sucesso." + "importComplete": "{count} conversa(s) importada(s) com sucesso.", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "A sincronização não está ativada", @@ -15,6 +18,12 @@ "importProcess": "Ocorreu um erro durante o processo de importação", "unknown": "Erro desconhecido", "encryptedBackupPasswordMissing": "Este backup está criptografado, mas a chave do banco de dados local não está disponível. Desbloqueie o banco criptografado primeiro e importe novamente.", - "overwriteEncryptionMismatch": "A importação por substituição exige que o backup e o banco atual usem o mesmo estado de criptografia." + "overwriteEncryptionMismatch": "A importação por substituição exige que o backup e o banco atual usem o mesmo estado de criptografia.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/ru-RU/settings.json b/src/renderer/src/i18n/ru-RU/settings.json index 694072f2d..897b5fbe6 100644 --- a/src/renderer/src/i18n/ru-RU/settings.json +++ b/src/renderer/src/i18n/ru-RU/settings.json @@ -385,6 +385,28 @@ "disableDialogDescription": "Переносит текущую зашифрованную базу данных обратно в локальную базу в открытом виде.", "cancelButton": "Отмена", "unknown": "Неизвестно" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/ru-RU/sync.json b/src/renderer/src/i18n/ru-RU/sync.json index f11a797d0..59240cdb5 100644 --- a/src/renderer/src/i18n/ru-RU/sync.json +++ b/src/renderer/src/i18n/ru-RU/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "Успешно импортировано {count} диалогов." + "importComplete": "Успешно импортировано {count} диалогов.", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "Функция синхронизации не включена", @@ -15,6 +18,12 @@ "importProcess": "Ошибка в процессе импорта", "unknown": "Неизвестная ошибка", "encryptedBackupPasswordMissing": "Эта резервная копия зашифрована, но ключ локальной базы данных недоступен. Сначала разблокируйте зашифрованную базу, затем импортируйте снова.", - "overwriteEncryptionMismatch": "Для импорта с заменой резервная копия и текущая база должны иметь одинаковое состояние шифрования." + "overwriteEncryptionMismatch": "Для импорта с заменой резервная копия и текущая база должны иметь одинаковое состояние шифрования.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/tr-TR/settings.json b/src/renderer/src/i18n/tr-TR/settings.json index 8a4c05967..7d755587c 100644 --- a/src/renderer/src/i18n/tr-TR/settings.json +++ b/src/renderer/src/i18n/tr-TR/settings.json @@ -448,6 +448,28 @@ "mistral": "Mistral AI" }, "applyFailed": "İçe aktarma başarısız oldu: {message}" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/tr-TR/sync.json b/src/renderer/src/i18n/tr-TR/sync.json index 82ec1867e..a965e6568 100644 --- a/src/renderer/src/i18n/tr-TR/sync.json +++ b/src/renderer/src/i18n/tr-TR/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "{count} konuşma(lar)ı başarıyla içe aktarıldı." + "importComplete": "{count} konuşma(lar)ı başarıyla içe aktarıldı.", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "Senkronizasyon etkin değil", @@ -15,6 +18,12 @@ "importProcess": "İçe aktarma işlemi sırasında bir hata oluştu", "unknown": "Bilinmeyen hata", "encryptedBackupPasswordMissing": "Bu yedekleme şifrelenmiştir ancak yerel veritabanı anahtarı mevcut değildir. Önce şifrelenmiş veritabanının kilidini açın, ardından tekrar içe aktarın.", - "overwriteEncryptionMismatch": "Üzerine yazma içe aktarma, yedek ve geçerli veritabanının aynı şifreleme durumunu kullanmasını gerektirir." + "overwriteEncryptionMismatch": "Üzerine yazma içe aktarma, yedek ve geçerli veritabanının aynı şifreleme durumunu kullanmasını gerektirir.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/vi-VN/settings.json b/src/renderer/src/i18n/vi-VN/settings.json index 079f651bf..6f4d077dd 100644 --- a/src/renderer/src/i18n/vi-VN/settings.json +++ b/src/renderer/src/i18n/vi-VN/settings.json @@ -448,6 +448,28 @@ "mistral": "AI của Mistral" }, "applyFailed": "Nhập không thành công: {message}" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/vi-VN/sync.json b/src/renderer/src/i18n/vi-VN/sync.json index 63cef2369..db54ed72a 100644 --- a/src/renderer/src/i18n/vi-VN/sync.json +++ b/src/renderer/src/i18n/vi-VN/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "Đã nhập thành công (các) cuộc trò chuyện {count}." + "importComplete": "Đã nhập thành công (các) cuộc trò chuyện {count}.", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "Đồng bộ hóa chưa được bật", @@ -15,6 +18,12 @@ "importProcess": "Đã xảy ra lỗi trong quá trình nhập", "unknown": "Lỗi không xác định", "encryptedBackupPasswordMissing": "Bản sao lưu này được mã hóa nhưng không có khóa cơ sở dữ liệu cục bộ. Mở khóa cơ sở dữ liệu được mã hóa trước, sau đó nhập lại.", - "overwriteEncryptionMismatch": "Quá trình nhập ghi đè yêu cầu bản sao lưu và cơ sở dữ liệu hiện tại sử dụng cùng trạng thái mã hóa." + "overwriteEncryptionMismatch": "Quá trình nhập ghi đè yêu cầu bản sao lưu và cơ sở dữ liệu hiện tại sử dụng cùng trạng thái mã hóa.", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/zh-CN/settings.json b/src/renderer/src/i18n/zh-CN/settings.json index ef31d16c9..9aa957fe3 100644 --- a/src/renderer/src/i18n/zh-CN/settings.json +++ b/src/renderer/src/i18n/zh-CN/settings.json @@ -227,6 +227,28 @@ "importData": "导入数据", "incrementImport": "增量导入", "overwriteImport": "覆盖导入", + "cloudSync": { + "title": "云同步 (S3 兼容)", + "description": "将备份上传到 Cloudflare R2 等 S3 兼容存储,在另一台设备拉取最新备份。", + "endpoint": "Endpoint", + "bucket": "存储桶 (Bucket)", + "region": "区域 (Region)", + "prefix": "路径前缀 (Prefix)", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "已配置 (留空表示不修改)", + "safeStorageUnavailable": "系统密钥链不可用,无法安全保存密钥。", + "save": "保存配置", + "test": "测试连接", + "upload": "上传到云", + "pull": "从云拉取最新", + "savedTitle": "云端配置已保存", + "testSuccessTitle": "连接成功", + "testFailedTitle": "连接失败", + "uploadSuccessTitle": "上传成功", + "uploadFailedTitle": "上传失败", + "pullSuccessTitle": "拉取成功" + }, "backupSelectLabel": "选择备份时间点", "backupSelectDescription": "请选择需要导入的备份。", "selectBackupPlaceholder": "请选择备份", diff --git a/src/renderer/src/i18n/zh-CN/sync.json b/src/renderer/src/i18n/zh-CN/sync.json index afca01af5..7340f0fec 100644 --- a/src/renderer/src/i18n/zh-CN/sync.json +++ b/src/renderer/src/i18n/zh-CN/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "成功导入 {count} 个对话" + "importComplete": "成功导入 {count} 个对话", + "cloudConnected": "云端连接成功", + "cloudUploaded": "已上传到云端", + "cloudPulled": "已从云端拉取最新备份" }, "error": { "notEnabled": "同步功能未启用", @@ -15,6 +18,12 @@ "importProcess": "导入过程出错", "unknown": "未知错误", "encryptedBackupPasswordMissing": "该备份已加密,但本地数据库密钥不可用。请先解锁加密数据库,再重新导入。", - "overwriteEncryptionMismatch": "覆盖导入要求备份和当前数据库处于相同的加密状态。" + "overwriteEncryptionMismatch": "覆盖导入要求备份和当前数据库处于相同的加密状态。", + "cloudNotConfigured": "尚未配置云端,请先填写并保存云端连接信息", + "cloudNoBackup": "云端没有可用的备份文件", + "cloudDownloadFailed": "从云端下载备份失败", + "cloudOperationFailed": "云端操作失败", + "noLocalBackup": "本地没有可上传的备份,请先执行一次备份", + "safeStorageUnavailable": "系统密钥链不可用,无法安全保存密钥" } } diff --git a/src/renderer/src/i18n/zh-HK/settings.json b/src/renderer/src/i18n/zh-HK/settings.json index d385a6ebb..ead2d5cbb 100644 --- a/src/renderer/src/i18n/zh-HK/settings.json +++ b/src/renderer/src/i18n/zh-HK/settings.json @@ -385,6 +385,28 @@ "disableDialogDescription": "將目前加密資料庫遷移回本機明文資料庫。", "cancelButton": "取消", "unknown": "未知" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/zh-HK/sync.json b/src/renderer/src/i18n/zh-HK/sync.json index 5dfcf88af..01ea7551d 100644 --- a/src/renderer/src/i18n/zh-HK/sync.json +++ b/src/renderer/src/i18n/zh-HK/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "成功導入 {count} 個對話" + "importComplete": "成功導入 {count} 個對話", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "同步功能未啟用", @@ -15,6 +18,12 @@ "importProcess": "導入過程出錯", "unknown": "未知錯誤", "encryptedBackupPasswordMissing": "此備份已加密,但本機資料庫密鑰不可用。請先解鎖加密資料庫,然後重新匯入。", - "overwriteEncryptionMismatch": "覆蓋匯入要求備份和目前資料庫處於相同的加密狀態。" + "overwriteEncryptionMismatch": "覆蓋匯入要求備份和目前資料庫處於相同的加密狀態。", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/i18n/zh-TW/settings.json b/src/renderer/src/i18n/zh-TW/settings.json index 3cb79e03a..ade181c17 100644 --- a/src/renderer/src/i18n/zh-TW/settings.json +++ b/src/renderer/src/i18n/zh-TW/settings.json @@ -385,6 +385,28 @@ "disableDialogDescription": "將目前加密資料庫遷移回本機明文資料庫。", "cancelButton": "取消", "unknown": "未知" + }, + "cloudSync": { + "title": "Cloud Sync (S3-compatible)", + "description": "Upload backups to S3-compatible storage such as Cloudflare R2, and pull the latest backup on another device.", + "endpoint": "Endpoint", + "bucket": "Bucket", + "region": "Region", + "prefix": "Path Prefix", + "accessKeyId": "Access Key ID", + "secretAccessKey": "Secret Access Key", + "secretConfigured": "Configured (leave blank to keep)", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key.", + "save": "Save", + "test": "Test Connection", + "upload": "Upload to Cloud", + "pull": "Pull Latest from Cloud", + "savedTitle": "Cloud config saved", + "testSuccessTitle": "Connection succeeded", + "testFailedTitle": "Connection failed", + "uploadSuccessTitle": "Upload succeeded", + "uploadFailedTitle": "Upload failed", + "pullSuccessTitle": "Pull succeeded" } }, "dashboard": { diff --git a/src/renderer/src/i18n/zh-TW/sync.json b/src/renderer/src/i18n/zh-TW/sync.json index d697c67c7..512c83cda 100644 --- a/src/renderer/src/i18n/zh-TW/sync.json +++ b/src/renderer/src/i18n/zh-TW/sync.json @@ -1,6 +1,9 @@ { "success": { - "importComplete": "成功匯入 {count} 個對話" + "importComplete": "成功匯入 {count} 個對話", + "cloudConnected": "Cloud connection succeeded", + "cloudUploaded": "Uploaded to cloud", + "cloudPulled": "Pulled the latest backup from cloud" }, "error": { "notEnabled": "同步功能未啟用", @@ -15,6 +18,12 @@ "importProcess": "匯入過程發生錯誤", "unknown": "未知錯誤", "encryptedBackupPasswordMissing": "此備份已加密,但本機資料庫金鑰不可用。請先解鎖加密資料庫,然後重新匯入。", - "overwriteEncryptionMismatch": "覆蓋匯入要求備份和目前資料庫處於相同的加密狀態。" + "overwriteEncryptionMismatch": "覆蓋匯入要求備份和目前資料庫處於相同的加密狀態。", + "cloudNotConfigured": "Cloud is not configured. Fill in and save the cloud connection first.", + "cloudNoBackup": "No backup available in the cloud bucket", + "cloudDownloadFailed": "Failed to download backup from cloud", + "cloudOperationFailed": "Cloud operation failed", + "noLocalBackup": "No local backup to upload. Run a backup first.", + "safeStorageUnavailable": "System keychain unavailable; cannot securely store the secret key" } } diff --git a/src/renderer/src/stores/sync.ts b/src/renderer/src/stores/sync.ts index c2a5ddb68..968764605 100644 --- a/src/renderer/src/stores/sync.ts +++ b/src/renderer/src/stores/sync.ts @@ -6,7 +6,7 @@ import { createConfigClient } from '../../api/ConfigClient' import { useIpcQuery } from '@/composables/useIpcQuery' import { useIpcMutation } from '@/composables/useIpcMutation' import type { EntryKey, UseQueryReturn } from '@pinia/colada' -import type { SyncBackupInfo } from '@shared/presenter' +import type { SyncBackupInfo, CloudSyncConfigView, CloudSyncConfigInput } from '@shared/presenter' export const useSyncStore = defineStore('sync', () => { const syncEnabled = ref(false) @@ -22,6 +22,10 @@ export const useSyncStore = defineStore('sync', () => { importedSessions?: number } | null>(null) + // Cloud sync (S3-compatible) state + const cloudConfig = ref(null) + const isCloudBusy = ref(false) + const configClient = createConfigClient() const syncClient = createSyncClient() const deviceClient = createDeviceClient() @@ -115,6 +119,61 @@ export const useSyncStore = defineStore('sync', () => { } } + const loadCloudConfig = async () => { + try { + cloudConfig.value = await syncClient.getCloudConfig() + } catch (error) { + console.error('load cloud config failed:', error) + } + return cloudConfig.value + } + + const saveCloudConfig = async (config: CloudSyncConfigInput) => { + if (isCloudBusy.value) return cloudConfig.value + isCloudBusy.value = true + try { + cloudConfig.value = await syncClient.setCloudConfig(config) + return cloudConfig.value + } finally { + isCloudBusy.value = false + } + } + + const testCloud = async () => { + if (isCloudBusy.value) return null + isCloudBusy.value = true + try { + return await syncClient.testCloudConnection() + } finally { + isCloudBusy.value = false + } + } + + const uploadToCloud = async () => { + if (isCloudBusy.value) return null + isCloudBusy.value = true + try { + return await syncClient.uploadToCloud() + } finally { + isCloudBusy.value = false + } + } + + const pullFromCloud = async (mode: 'increment' | 'overwrite' = 'increment') => { + if (isCloudBusy.value) return null + isCloudBusy.value = true + try { + const result = await syncClient.pullFromCloud(mode) + if (result && !result.success) { + importResult.value = result + } + return result + } finally { + isCloudBusy.value = false + await refreshBackups() + } + } + const initialize = async () => { syncEnabled.value = await configClient.getSyncEnabled() syncFolderPath.value = await configClient.getSyncFolderPath() @@ -124,6 +183,7 @@ export const useSyncStore = defineStore('sync', () => { isBackingUp.value = status.isBackingUp await refreshBackups() + await loadCloudConfig() setupSyncEventListeners() setupSyncSettingsListener() } @@ -217,6 +277,8 @@ export const useSyncStore = defineStore('sync', () => { isImporting, importResult, backups, + cloudConfig, + isCloudBusy, initialize, setSyncEnabled, @@ -227,6 +289,11 @@ export const useSyncStore = defineStore('sync', () => { importData, restartApp, clearImportResult, - refreshBackups + refreshBackups, + loadCloudConfig, + saveCloudConfig, + testCloud, + uploadToCloud, + pullFromCloud } }) diff --git a/src/shared/contracts/routes.ts b/src/shared/contracts/routes.ts index effb7bc11..ce15444d9 100644 --- a/src/shared/contracts/routes.ts +++ b/src/shared/contracts/routes.ts @@ -264,7 +264,12 @@ import { syncImportRoute, syncListBackupsRoute, syncOpenFolderRoute, - syncStartBackupRoute + syncStartBackupRoute, + syncGetCloudConfigRoute, + syncSetCloudConfigRoute, + syncTestCloudRoute, + syncUploadToCloudRoute, + syncPullFromCloudRoute } from './routes/sync.routes' import { systemOpenSettingsRoute } from './routes/system.routes' import { toolsListDefinitionsRoute } from './routes/tools.routes' @@ -586,6 +591,11 @@ export const DEEPCHAT_ROUTE_CATALOG = { [syncStartBackupRoute.name]: syncStartBackupRoute, [syncImportRoute.name]: syncImportRoute, [syncOpenFolderRoute.name]: syncOpenFolderRoute, + [syncGetCloudConfigRoute.name]: syncGetCloudConfigRoute, + [syncSetCloudConfigRoute.name]: syncSetCloudConfigRoute, + [syncTestCloudRoute.name]: syncTestCloudRoute, + [syncUploadToCloudRoute.name]: syncUploadToCloudRoute, + [syncPullFromCloudRoute.name]: syncPullFromCloudRoute, [upgradeGetStatusRoute.name]: upgradeGetStatusRoute, [upgradeCheckRoute.name]: upgradeCheckRoute, [upgradeOpenDownloadRoute.name]: upgradeOpenDownloadRoute, diff --git a/src/shared/contracts/routes/sync.routes.ts b/src/shared/contracts/routes/sync.routes.ts index 7b9f8afef..52801e34b 100644 --- a/src/shared/contracts/routes/sync.routes.ts +++ b/src/shared/contracts/routes/sync.routes.ts @@ -1,8 +1,30 @@ import { z } from 'zod' -import type { SyncBackupInfo } from '@shared/presenter' +import type { SyncBackupInfo, CloudSyncResult } from '@shared/presenter' import { defineRouteContract } from '../common' const SyncBackupInfoSchema = z.custom() +const CloudSyncResultSchema = z.custom() + +const CloudSyncConfigViewSchema = z.object({ + enabled: z.boolean(), + endpoint: z.string(), + bucket: z.string(), + region: z.string(), + prefix: z.string(), + accessKeyId: z.string(), + hasSecret: z.boolean(), + safeStorageAvailable: z.boolean() +}) + +const CloudSyncConfigInputSchema = z.object({ + enabled: z.boolean().optional(), + endpoint: z.string().optional(), + bucket: z.string().optional(), + region: z.string().optional(), + prefix: z.string().optional(), + accessKeyId: z.string().optional(), + secretAccessKey: z.string().optional() +}) export const syncGetBackupStatusRoute = defineRouteContract({ name: 'sync.getBackupStatus', @@ -55,3 +77,49 @@ export const syncOpenFolderRoute = defineRouteContract({ opened: z.literal(true) }) }) + +// === Cloud sync (S3-compatible) === + +export const syncGetCloudConfigRoute = defineRouteContract({ + name: 'sync.getCloudConfig', + input: z.object({}).default({}), + output: z.object({ + config: CloudSyncConfigViewSchema + }) +}) + +export const syncSetCloudConfigRoute = defineRouteContract({ + name: 'sync.setCloudConfig', + input: z.object({ + config: CloudSyncConfigInputSchema + }), + output: z.object({ + config: CloudSyncConfigViewSchema + }) +}) + +export const syncTestCloudRoute = defineRouteContract({ + name: 'sync.testCloud', + input: z.object({}).default({}), + output: z.object({ + result: CloudSyncResultSchema + }) +}) + +export const syncUploadToCloudRoute = defineRouteContract({ + name: 'sync.uploadToCloud', + input: z.object({}).default({}), + output: z.object({ + result: CloudSyncResultSchema + }) +}) + +export const syncPullFromCloudRoute = defineRouteContract({ + name: 'sync.pullFromCloud', + input: z.object({ + mode: z.enum(['increment', 'overwrite']).optional() + }), + output: z.object({ + result: CloudSyncResultSchema + }) +}) diff --git a/src/shared/types/presenters/legacy.presenters.d.ts b/src/shared/types/presenters/legacy.presenters.d.ts index c323f0157..7d9f3dd38 100644 --- a/src/shared/types/presenters/legacy.presenters.d.ts +++ b/src/shared/types/presenters/legacy.presenters.d.ts @@ -652,6 +652,11 @@ export interface IConfigPresenter { setSyncFolderPath(folderPath: string): void getLastSyncTime(): number setLastSyncTime(time: number): void + // Cloud sync (S3-compatible) settings + getCloudSyncConfig(): CloudSyncConfigView + setCloudSyncConfig(config: CloudSyncConfigInput): CloudSyncConfigView + getResolvedCloudSyncConfig(): ResolvedCloudSyncConfig | null + isCloudSafeStorageAvailable(): boolean // Hooks & notifications settings getHooksNotificationsConfig(): HooksNotificationsSettings setHooksNotificationsConfig(config: HooksNotificationsSettings): HooksNotificationsSettings @@ -2027,11 +2032,56 @@ export interface ISyncPresenter { checkSyncFolder(): Promise<{ exists: boolean; path: string }> openSyncFolder(): Promise + // Cloud sync (S3-compatible) operations + testCloudConnection(config?: CloudSyncConfigInput): Promise + uploadLatestBackupToCloud(): Promise + pullLatestBackupFromCloud(importMode?: ImportMode): Promise + // Initialization and destruction init(): void destroy(): void } +/** Non-sensitive cloud sync config persisted in app settings (secret stored separately). */ +export interface CloudSyncConfigBase { + enabled: boolean + endpoint: string + bucket: string + region: string + prefix: string + accessKeyId: string +} + +/** Config view sent to the renderer — never includes the secret in plaintext. */ +export interface CloudSyncConfigView extends CloudSyncConfigBase { + hasSecret: boolean + safeStorageAvailable: boolean +} + +/** Config written from the renderer. `secretAccessKey` omitted = keep existing secret. */ +export interface CloudSyncConfigInput extends Partial { + secretAccessKey?: string +} + +/** Fully resolved runtime config; `enabled` is UI-only and omitted from S3 operations. */ +export interface ResolvedCloudSyncConfig { + endpoint: string + bucket: string + region: string + prefix: string + accessKeyId: string + secretAccessKey: string +} + +export interface CloudSyncResult { + success: boolean + message: string + fileName?: string + count?: number + sourceDbType?: 'agent' | 'chat' + importedSessions?: number +} + export interface SyncBackupInfo { fileName: string createdAt: number diff --git a/test/main/presenter/SyncPresenter.test.ts b/test/main/presenter/SyncPresenter.test.ts index fd3a24e04..2c8908a63 100644 --- a/test/main/presenter/SyncPresenter.test.ts +++ b/test/main/presenter/SyncPresenter.test.ts @@ -10,6 +10,17 @@ const configImportMocks = vi.hoisted(() => ({ readManifest: vi.fn() })) +const cloudStorageMocks = vi.hoisted(() => ({ + testConnection: vi.fn(), + uploadBackup: vi.fn(), + listRemoteBackups: vi.fn(), + downloadLatest: vi.fn() +})) + +const mainPresenterMocks = vi.hoisted(() => ({ + broadcastConversationThreadListUpdate: vi.fn() +})) + vi.mock('better-sqlite3-multiple-ciphers', async () => { const fs = await vi.importActual('fs') const path = await vi.importActual('path') @@ -274,6 +285,14 @@ vi.mock('../../../src/main/presenter/syncPresenter/configImportService', async ( } }) +vi.mock('../../../src/main/presenter/syncPresenter/cloudStorageService', () => ({ + CloudStorageService: vi.fn(() => cloudStorageMocks) +})) + +vi.mock('../../../src/main/presenter/index', () => ({ + presenter: mainPresenterMocks +})) + const realFs = await vi.importActual('fs') Object.assign(fsMock, realFs) ;(fsMock as any).promises = realFs.promises @@ -308,6 +327,11 @@ describe('SyncPresenter backup import', () => { configImportMocks.importLegacyConfig.mockClear() configImportMocks.ensureConfigMigrationMarker.mockClear() configImportMocks.readManifest.mockClear() + cloudStorageMocks.testConnection.mockReset() + cloudStorageMocks.uploadBackup.mockReset() + cloudStorageMocks.listRemoteBackups.mockReset() + cloudStorageMocks.downloadLatest.mockReset() + mainPresenterMocks.broadcastConversationThreadListUpdate.mockReset() userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepchat-user-')) tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepchat-temp-')) @@ -347,7 +371,15 @@ describe('SyncPresenter backup import', () => { getSyncFolderPath: vi.fn(() => syncDir), getSyncEnabled: vi.fn(() => true), getLastSyncTime: vi.fn(() => 0), - setLastSyncTime: vi.fn() + setLastSyncTime: vi.fn(), + getResolvedCloudSyncConfig: vi.fn(() => ({ + endpoint: 'https://r2.example.com', + bucket: 'deepchat', + region: 'auto', + prefix: 'deepchat-backups', + accessKeyId: 'access-key', + secretAccessKey: 'secret-key' + })) } presenter = new SyncPresenter(configPresenter, sqlitePresenter) @@ -531,6 +563,32 @@ describe('SyncPresenter backup import', () => { expect(sqlitePresenter.close).not.toHaveBeenCalled() }) + it('skips invalid backup-looking zip files during cloud upload', async () => { + const validTimestamp = 1000 + const invalidTimestamp = 2000 + const validBackupFile = createBackupArchive(syncDir, validTimestamp, { + conversations: [{ id: 'conv-1', title: 'Valid backup' }], + appSettings: { theme: 'dark' }, + customPrompts: { prompts: [] }, + systemPrompts: { prompts: [] }, + mcpSettings: {} + }) + fs.writeFileSync(path.join(syncDir, `backup-${invalidTimestamp}.zip`), 'not a zip') + cloudStorageMocks.uploadBackup.mockResolvedValue(undefined) + + const result = await presenter.uploadLatestBackupToCloud() + + expect(result).toEqual({ + success: true, + message: 'sync.success.cloudUploaded', + fileName: validBackupFile + }) + expect(cloudStorageMocks.uploadBackup).toHaveBeenCalledWith( + path.join(syncDir, validBackupFile), + validBackupFile + ) + }) + it('imports v2 sqlite config rows incrementally without overwriting local rows', async () => { createLocalState(userDataDir, { conversations: [{ id: 'conv-1', title: 'Local conversation' }], @@ -594,6 +652,41 @@ describe('SyncPresenter backup import', () => { ]) }) + it('rolls back import when local settings cannot be preserved', async () => { + createLocalState(userDataDir, { + conversations: [{ id: 'conv-1', title: 'Local conversation' }], + appSettings: { + theme: 'light', + cloudSyncConfig: { endpoint: 'https://r2.example.com' }, + cloudSyncSecret: 'wrapped-secret' + }, + customPrompts: { prompts: [] }, + systemPrompts: { prompts: [] }, + mcpSettings: {} + }) + const appSettingsPath = path.join(userDataDir, 'app-settings.json') + fs.writeFileSync(appSettingsPath, '{not-json', 'utf-8') + + const backupFile = createBackupArchive(syncDir, Date.now(), { + conversations: [{ id: 'conv-2', title: 'Imported conversation' }], + appSettings: { theme: 'dark' }, + customPrompts: { prompts: [] }, + systemPrompts: { prompts: [] }, + mcpSettings: {} + }) + + const result = await presenter.importFromSync(backupFile, ImportMode.INCREMENT) + + expect(result.success).toBe(false) + expect(result.message).toBe('sync.error.importFailed') + expect(fs.readFileSync(appSettingsPath, 'utf-8')).toBe('{not-json') + + const db = new Database(path.join(userDataDir, 'app_db', 'agent.db')) + const rows = db.prepare('SELECT id, title FROM conversations ORDER BY id').all() + db.close() + expect(rows).toEqual([{ id: 'conv-1', title: 'Local conversation' }]) + }) + it('rejects v2 sqlite backups without agent.db before touching local data', async () => { createLocalState(userDataDir, { conversations: [{ id: 'conv-1', title: 'Local conversation' }], From c96356891b3b9122399be1cabd8445c8d074edb9 Mon Sep 17 00:00:00 2001 From: xiaomo Date: Mon, 8 Jun 2026 16:09:15 +0800 Subject: [PATCH 2/4] fix(mcp): adapt dify new retrieval_model schema (#1745) Co-authored-by: zhangmo8 --- .../inMemoryServers/difyKnowledgeServer.ts | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts b/src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts index de9f3362c..14cbb9187 100644 --- a/src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts +++ b/src/main/presenter/mcpPresenter/inMemoryServers/difyKnowledgeServer.ts @@ -216,24 +216,31 @@ export class DifyKnowledgeServer { try { const url = `${config.endpoint.replace(/\/$/, '')}/datasets/${config.datasetId}/retrieve` + + // Dify 新版 API 把检索配置统一收进了 retrieval_model 对象,且要求: + // 1. search_method 为必填项(缺失会导致参数校验失败); + // 2. reranking_enable / score_threshold_enabled 必须是布尔值,旧版传 null 已不再被接受。 + // 这里默认走 semantic_search 且关闭 rerank —— 不依赖数据集是否配置了 Rerank 模型, + // 对任意数据集都可用;阈值过滤维持原有「不过滤」行为,保证检索结果集与改动前一致。 + const retrievalModel = { + search_method: 'semantic_search', + reranking_enable: false, + top_k: topK, + score_threshold_enabled: false, + score_threshold: null + } + console.log('performDifyKnowledgeSearch request', url, { query, - retrieval_model: { - top_k: topK, - score_threshold: scoreThreshold - } + score_threshold: scoreThreshold, + retrieval_model: retrievalModel }) const response = await axios.post( url, { query, - retrieval_model: { - top_k: topK, - score_threshold: scoreThreshold, - reranking_enable: null, // 下面这两个字段即使为空也必须要有,否则接口无法请求 - score_threshold_enabled: null - } + retrieval_model: retrievalModel }, { headers: { From 3b8f4227492cd26fb396e834e7fb604bc7bd479b Mon Sep 17 00:00:00 2001 From: zerob13 Date: Mon, 8 Jun 2026 18:25:00 +0800 Subject: [PATCH 3/4] chore: update provider json --- resources/acp-registry/icons/grok-build.svg | 4 +- resources/acp-registry/registry.json | 191 +- resources/model-db/providers.json | 3694 ++++++++++++------- 3 files changed, 2436 insertions(+), 1453 deletions(-) diff --git a/resources/acp-registry/icons/grok-build.svg b/resources/acp-registry/icons/grok-build.svg index 5cb93b2d8..69f573cba 100644 --- a/resources/acp-registry/icons/grok-build.svg +++ b/resources/acp-registry/icons/grok-build.svg @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/resources/acp-registry/registry.json b/resources/acp-registry/registry.json index 5a35277d9..f0d4dcae2 100644 --- a/resources/acp-registry/registry.json +++ b/resources/acp-registry/registry.json @@ -25,7 +25,7 @@ { "id": "amp-acp", "name": "Amp", - "version": "0.8.0", + "version": "0.8.1", "description": "ACP wrapper for Amp - the frontier coding agent", "repository": "https://github.com/tao12345666333/amp-acp", "authors": [ @@ -36,23 +36,23 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.8.0/amp-acp-darwin-aarch64.tar.gz", + "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.8.1/amp-acp-darwin-aarch64.tar.gz", "cmd": "./amp-acp" }, "darwin-x86_64": { - "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.8.0/amp-acp-darwin-x86_64.tar.gz", + "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.8.1/amp-acp-darwin-x86_64.tar.gz", "cmd": "./amp-acp" }, "linux-aarch64": { - "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.8.0/amp-acp-linux-aarch64.tar.gz", + "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.8.1/amp-acp-linux-aarch64.tar.gz", "cmd": "./amp-acp" }, "linux-x86_64": { - "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.8.0/amp-acp-linux-x86_64.tar.gz", + "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.8.1/amp-acp-linux-x86_64.tar.gz", "cmd": "./amp-acp" }, "windows-x86_64": { - "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.8.0/amp-acp-windows-x86_64.zip", + "archive": "https://github.com/tao12345666333/amp-acp/releases/download/v0.8.1/amp-acp-windows-x86_64.zip", "cmd": "amp-acp.exe" } } @@ -61,7 +61,7 @@ { "id": "auggie", "name": "Auggie CLI", - "version": "0.28.0", + "version": "0.29.0", "description": "Augment Code's powerful software agent, backed by industry-leading context engine", "repository": "https://github.com/augmentcode/auggie", "website": "https://www.augmentcode.com/", @@ -72,7 +72,7 @@ "icon": "https://cdn.agentclientprotocol.com/registry/v1/latest/auggie.svg", "distribution": { "npx": { - "package": "@augmentcode/auggie@0.28.0", + "package": "@augmentcode/auggie@0.29.0", "args": [ "--acp" ], @@ -103,7 +103,7 @@ { "id": "claude-acp", "name": "Claude Agent", - "version": "0.39.0", + "version": "0.42.0", "description": "ACP wrapper for Anthropic's Claude", "repository": "https://github.com/agentclientprotocol/claude-agent-acp", "authors": [ @@ -114,7 +114,7 @@ "license": "proprietary", "distribution": { "npx": { - "package": "@agentclientprotocol/claude-agent-acp@0.39.0" + "package": "@agentclientprotocol/claude-agent-acp@0.42.0" } }, "icon": "https://cdn.agentclientprotocol.com/registry/v1/latest/claude-acp.svg" @@ -122,7 +122,7 @@ { "id": "cline", "name": "Cline", - "version": "3.0.15", + "version": "3.0.20", "description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more", "repository": "https://github.com/cline/cline", "website": "https://cline.bot/cli", @@ -133,7 +133,7 @@ "icon": "https://cdn.agentclientprotocol.com/registry/v1/latest/cline.svg", "distribution": { "npx": { - "package": "cline@3.0.15", + "package": "cline@3.0.20", "args": [ "--acp" ] @@ -143,7 +143,7 @@ { "id": "codebuddy-code", "name": "Codebuddy Code", - "version": "2.100.1", + "version": "2.103.1", "description": "Tencent Cloud's official intelligent coding tool", "website": "https://www.codebuddy.cn/cli/", "authors": [ @@ -152,7 +152,7 @@ "license": "Proprietary", "distribution": { "npx": { - "package": "@tencent-ai/codebuddy-code@2.100.1", + "package": "@tencent-ai/codebuddy-code@2.103.1", "args": [ "--acp" ] @@ -356,7 +356,7 @@ { "id": "cursor", "name": "Cursor", - "version": "2026.05.20", + "version": "2026.05.28", "description": "Cursor's coding agent", "website": "https://cursor.com/docs/cli/acp", "authors": [ @@ -366,42 +366,42 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://downloads.cursor.com/lab/2026.05.20-2b5dd59/darwin/arm64/agent-cli-package.tar.gz", + "archive": "https://downloads.cursor.com/lab/2026.05.28-a70ca7c/darwin/arm64/agent-cli-package.tar.gz", "cmd": "./dist-package/cursor-agent", "args": [ "acp" ] }, "darwin-x86_64": { - "archive": "https://downloads.cursor.com/lab/2026.05.20-2b5dd59/darwin/x64/agent-cli-package.tar.gz", + "archive": "https://downloads.cursor.com/lab/2026.05.28-a70ca7c/darwin/x64/agent-cli-package.tar.gz", "cmd": "./dist-package/cursor-agent", "args": [ "acp" ] }, "linux-aarch64": { - "archive": "https://downloads.cursor.com/lab/2026.05.20-2b5dd59/linux/arm64/agent-cli-package.tar.gz", + "archive": "https://downloads.cursor.com/lab/2026.05.28-a70ca7c/linux/arm64/agent-cli-package.tar.gz", "cmd": "./dist-package/cursor-agent", "args": [ "acp" ] }, "linux-x86_64": { - "archive": "https://downloads.cursor.com/lab/2026.05.20-2b5dd59/linux/x64/agent-cli-package.tar.gz", + "archive": "https://downloads.cursor.com/lab/2026.05.28-a70ca7c/linux/x64/agent-cli-package.tar.gz", "cmd": "./dist-package/cursor-agent", "args": [ "acp" ] }, "windows-aarch64": { - "archive": "https://downloads.cursor.com/lab/2026.05.20-2b5dd59/windows/arm64/agent-cli-package.zip", + "archive": "https://downloads.cursor.com/lab/2026.05.28-a70ca7c/windows/arm64/agent-cli-package.zip", "cmd": "./dist-package\\cursor-agent.cmd", "args": [ "acp" ] }, "windows-x86_64": { - "archive": "https://downloads.cursor.com/lab/2026.05.20-2b5dd59/windows/x64/agent-cli-package.zip", + "archive": "https://downloads.cursor.com/lab/2026.05.28-a70ca7c/windows/x64/agent-cli-package.zip", "cmd": "./dist-package\\cursor-agent.cmd", "args": [ "acp" @@ -433,7 +433,7 @@ { "id": "dimcode", "name": "DimCode", - "version": "0.0.75", + "version": "0.0.78", "description": "A coding agent that puts leading models at your command.", "website": "https://dimcode.dev/docs/acp.html", "authors": [ @@ -442,7 +442,7 @@ "license": "proprietary", "distribution": { "npx": { - "package": "dimcode@0.0.75", + "package": "dimcode@0.0.78", "args": [ "acp" ] @@ -474,7 +474,7 @@ { "id": "factory-droid", "name": "Factory Droid", - "version": "0.137.1", + "version": "0.141.0", "description": "Factory Droid - AI coding agent powered by Factory AI", "website": "https://factory.ai/product/cli", "authors": [ @@ -483,7 +483,7 @@ "license": "proprietary", "distribution": { "npx": { - "package": "droid@0.137.1", + "package": "droid@0.141.0", "args": [ "exec", "--output-format", @@ -500,7 +500,7 @@ { "id": "fast-agent", "name": "fast-agent", - "version": "0.7.14", + "version": "0.7.15", "description": "Code and build agents with comprehensive multi-provider support", "repository": "https://github.com/evalstate/fast-agent", "website": "https://fast-agent.ai", @@ -510,7 +510,7 @@ "license": "Apache 2.0", "distribution": { "uvx": { - "package": "fast-agent-acp==0.7.14", + "package": "fast-agent-acp==0.7.15", "args": [ "-x" ] @@ -521,7 +521,7 @@ { "id": "gemini", "name": "Gemini CLI", - "version": "0.44.1", + "version": "0.45.1", "description": "Google's official CLI for Gemini", "repository": "https://github.com/google-gemini/gemini-cli", "website": "https://geminicli.com", @@ -531,7 +531,7 @@ "license": "Apache-2.0", "distribution": { "npx": { - "package": "@google/gemini-cli@0.44.1", + "package": "@google/gemini-cli@0.45.1", "args": [ "--acp" ] @@ -542,7 +542,7 @@ { "id": "github-copilot-cli", "name": "GitHub Copilot", - "version": "1.0.56", + "version": "1.0.59", "description": "GitHub's AI pair programmer", "repository": "https://github.com/github/copilot-cli", "website": "https://github.com/features/copilot/cli/", @@ -552,7 +552,7 @@ "license": "proprietary", "distribution": { "npx": { - "package": "@github/copilot@1.0.56", + "package": "@github/copilot@1.0.59", "args": [ "--acp" ] @@ -580,7 +580,7 @@ { "id": "goose", "name": "goose", - "version": "1.36.0", + "version": "1.37.0", "description": "A local, extensible, open source AI agent that automates engineering tasks", "repository": "https://github.com/block/goose", "website": "https://block.github.io/goose/", @@ -591,35 +591,35 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/block/goose/releases/download/v1.36.0/goose-aarch64-apple-darwin.tar.bz2", + "archive": "https://github.com/block/goose/releases/download/v1.37.0/goose-aarch64-apple-darwin.tar.bz2", "cmd": "./goose", "args": [ "acp" ] }, "darwin-x86_64": { - "archive": "https://github.com/block/goose/releases/download/v1.36.0/goose-x86_64-apple-darwin.tar.bz2", + "archive": "https://github.com/block/goose/releases/download/v1.37.0/goose-x86_64-apple-darwin.tar.bz2", "cmd": "./goose", "args": [ "acp" ] }, "linux-aarch64": { - "archive": "https://github.com/block/goose/releases/download/v1.36.0/goose-aarch64-unknown-linux-gnu.tar.bz2", + "archive": "https://github.com/block/goose/releases/download/v1.37.0/goose-aarch64-unknown-linux-gnu.tar.bz2", "cmd": "./goose", "args": [ "acp" ] }, "linux-x86_64": { - "archive": "https://github.com/block/goose/releases/download/v1.36.0/goose-x86_64-unknown-linux-gnu.tar.bz2", + "archive": "https://github.com/block/goose/releases/download/v1.37.0/goose-x86_64-unknown-linux-gnu.tar.bz2", "cmd": "./goose", "args": [ "acp" ] }, "windows-x86_64": { - "archive": "https://github.com/block/goose/releases/download/v1.36.0/goose-x86_64-pc-windows-msvc.zip", + "archive": "https://github.com/block/goose/releases/download/v1.37.0/goose-x86_64-pc-windows-msvc.zip", "cmd": "./goose-package\\goose.exe", "args": [ "acp" @@ -632,9 +632,9 @@ { "id": "grok-build", "name": "Grok Build", - "version": "0.2.11", + "version": "0.2.20", "description": "xAI's coding agent and CLI", - "website": "https://docs.x.ai/build/overview", + "website": "https://x.ai/cli", "authors": [ "xAI" ], @@ -642,48 +642,48 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-0.2.11-macos-aarch64", - "cmd": "./grok", + "archive": "https://x.ai/cli/grok-0.2.20-macos-aarch64", + "cmd": "./grok-0.2.20-macos-aarch64", "args": [ "agent", "stdio" ] }, "darwin-x86_64": { - "archive": "https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-0.2.11-macos-x86_64", - "cmd": "./grok", + "archive": "https://x.ai/cli/grok-0.2.20-macos-x86_64", + "cmd": "./grok-0.2.20-macos-x86_64", "args": [ "agent", "stdio" ] }, "linux-aarch64": { - "archive": "https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-0.2.11-linux-aarch64", - "cmd": "./grok", + "archive": "https://x.ai/cli/grok-0.2.20-linux-aarch64", + "cmd": "./grok-0.2.20-linux-aarch64", "args": [ "agent", "stdio" ] }, "linux-x86_64": { - "archive": "https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-0.2.11-linux-x86_64", - "cmd": "./grok", + "archive": "https://x.ai/cli/grok-0.2.20-linux-x86_64", + "cmd": "./grok-0.2.20-linux-x86_64", "args": [ "agent", "stdio" ] }, "windows-aarch64": { - "archive": "https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-0.2.11-windows-aarch64.exe", - "cmd": "./grok.exe", + "archive": "https://x.ai/cli/grok-0.2.20-windows-aarch64.exe", + "cmd": "./grok-0.2.20-windows-aarch64.exe", "args": [ "agent", "stdio" ] }, "windows-x86_64": { - "archive": "https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-0.2.11-windows-x86_64.exe", - "cmd": "./grok.exe", + "archive": "https://x.ai/cli/grok-0.2.20-windows-x86_64.exe", + "cmd": "./grok-0.2.20-windows-x86_64.exe", "args": [ "agent", "stdio" @@ -696,7 +696,7 @@ { "id": "junie", "name": "Junie", - "version": "1668.43.0", + "version": "1831.35.0", "description": "AI Coding Agent by JetBrains", "repository": "https://github.com/JetBrains/junie", "website": "https://junie.jetbrains.com", @@ -707,35 +707,35 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/JetBrains/junie/releases/download/1668.43/junie-release-1668.43-macos-aarch64.zip", + "archive": "https://github.com/JetBrains/junie/releases/download/1831.35/junie-release-1831.35-macos-aarch64.zip", "cmd": "./Applications/junie.app/Contents/MacOS/junie", "args": [ "--acp=true" ] }, "darwin-x86_64": { - "archive": "https://github.com/JetBrains/junie/releases/download/1668.43/junie-release-1668.43-macos-amd64.zip", + "archive": "https://github.com/JetBrains/junie/releases/download/1831.35/junie-release-1831.35-macos-amd64.zip", "cmd": "./Applications/junie.app/Contents/MacOS/junie", "args": [ "--acp=true" ] }, "linux-aarch64": { - "archive": "https://github.com/JetBrains/junie/releases/download/1668.43/junie-release-1668.43-linux-aarch64.zip", + "archive": "https://github.com/JetBrains/junie/releases/download/1831.35/junie-release-1831.35-linux-aarch64.zip", "cmd": "./junie-app/bin/junie", "args": [ "--acp=true" ] }, "linux-x86_64": { - "archive": "https://github.com/JetBrains/junie/releases/download/1668.43/junie-release-1668.43-linux-amd64.zip", + "archive": "https://github.com/JetBrains/junie/releases/download/1831.35/junie-release-1831.35-linux-amd64.zip", "cmd": "./junie-app/bin/junie", "args": [ "--acp=true" ] }, "windows-x86_64": { - "archive": "https://github.com/JetBrains/junie/releases/download/1668.43/junie-release-1668.43-windows-amd64.zip", + "archive": "https://github.com/JetBrains/junie/releases/download/1831.35/junie-release-1831.35-windows-amd64.zip", "cmd": "./junie/junie.exe", "args": [ "--acp=true" @@ -748,7 +748,7 @@ { "id": "kilo", "name": "Kilo", - "version": "7.3.16", + "version": "7.3.33", "description": "The open source coding agent", "repository": "https://github.com/Kilo-Org/kilocode", "website": "https://kilo.ai/", @@ -760,35 +760,35 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.16/kilo-darwin-arm64.zip", + "archive": "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.33/kilo-darwin-arm64.zip", "cmd": "./kilo", "args": [ "acp" ] }, "darwin-x86_64": { - "archive": "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.16/kilo-darwin-x64.zip", + "archive": "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.33/kilo-darwin-x64.zip", "cmd": "./kilo", "args": [ "acp" ] }, "linux-aarch64": { - "archive": "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.16/kilo-linux-arm64.tar.gz", + "archive": "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.33/kilo-linux-arm64.tar.gz", "cmd": "./kilo", "args": [ "acp" ] }, "linux-x86_64": { - "archive": "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.16/kilo-linux-x64.tar.gz", + "archive": "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.33/kilo-linux-x64.tar.gz", "cmd": "./kilo", "args": [ "acp" ] }, "windows-x86_64": { - "archive": "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.16/kilo-windows-x64.zip", + "archive": "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.33/kilo-windows-x64.zip", "cmd": "./kilo.exe", "args": [ "acp" @@ -796,7 +796,7 @@ } }, "npx": { - "package": "@kilocode/cli@7.3.16", + "package": "@kilocode/cli@7.3.33", "args": [ "acp" ] @@ -806,7 +806,7 @@ { "id": "kimi", "name": "Kimi CLI", - "version": "1.46.0", + "version": "1.47.0", "description": "Moonshot AI's coding assistant", "repository": "https://github.com/MoonshotAI/kimi-cli", "website": "https://moonshotai.github.io/kimi-cli/", @@ -817,28 +817,35 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/MoonshotAI/kimi-cli/releases/download/1.46.0/kimi-1.46.0-aarch64-apple-darwin.tar.gz", + "archive": "https://github.com/MoonshotAI/kimi-cli/releases/download/1.47.0/kimi-1.47.0-aarch64-apple-darwin.tar.gz", "cmd": "./kimi", "args": [ "acp" ] }, "linux-aarch64": { - "archive": "https://github.com/MoonshotAI/kimi-cli/releases/download/1.46.0/kimi-1.46.0-aarch64-unknown-linux-gnu.tar.gz", + "archive": "https://github.com/MoonshotAI/kimi-cli/releases/download/1.47.0/kimi-1.47.0-aarch64-unknown-linux-gnu.tar.gz", "cmd": "./kimi", "args": [ "acp" ] }, "linux-x86_64": { - "archive": "https://github.com/MoonshotAI/kimi-cli/releases/download/1.46.0/kimi-1.46.0-x86_64-unknown-linux-gnu.tar.gz", + "archive": "https://github.com/MoonshotAI/kimi-cli/releases/download/1.47.0/kimi-1.47.0-x86_64-unknown-linux-gnu.tar.gz", "cmd": "./kimi", "args": [ "acp" ] }, + "windows-aarch64": { + "archive": "https://github.com/MoonshotAI/kimi-cli/releases/download/1.47.0/kimi-1.47.0-aarch64-pc-windows-msvc.zip", + "cmd": "./kimi.exe", + "args": [ + "acp" + ] + }, "windows-x86_64": { - "archive": "https://github.com/MoonshotAI/kimi-cli/releases/download/1.46.0/kimi-1.46.0-x86_64-pc-windows-msvc.zip", + "archive": "https://github.com/MoonshotAI/kimi-cli/releases/download/1.47.0/kimi-1.47.0-x86_64-pc-windows-msvc.zip", "cmd": "./kimi.exe", "args": [ "acp" @@ -871,7 +878,7 @@ { "id": "mistral-vibe", "name": "Mistral Vibe", - "version": "2.13.0", + "version": "2.14.0", "description": "Mistral's open-source coding assistant", "repository": "https://github.com/mistralai/mistral-vibe", "website": "https://mistral.ai/products/vibe", @@ -883,23 +890,23 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/mistralai/mistral-vibe/releases/download/v2.13.0/vibe-acp-darwin-aarch64-2.13.0.zip", + "archive": "https://github.com/mistralai/mistral-vibe/releases/download/v2.14.0/vibe-acp-darwin-aarch64-2.14.0.zip", "cmd": "./vibe-acp" }, "darwin-x86_64": { - "archive": "https://github.com/mistralai/mistral-vibe/releases/download/v2.13.0/vibe-acp-darwin-x86_64-2.13.0.zip", + "archive": "https://github.com/mistralai/mistral-vibe/releases/download/v2.14.0/vibe-acp-darwin-x86_64-2.14.0.zip", "cmd": "./vibe-acp" }, "linux-aarch64": { - "archive": "https://github.com/mistralai/mistral-vibe/releases/download/v2.13.0/vibe-acp-linux-aarch64-2.13.0.zip", + "archive": "https://github.com/mistralai/mistral-vibe/releases/download/v2.14.0/vibe-acp-linux-aarch64-2.14.0.zip", "cmd": "./vibe-acp" }, "linux-x86_64": { - "archive": "https://github.com/mistralai/mistral-vibe/releases/download/v2.13.0/vibe-acp-linux-x86_64-2.13.0.zip", + "archive": "https://github.com/mistralai/mistral-vibe/releases/download/v2.14.0/vibe-acp-linux-x86_64-2.14.0.zip", "cmd": "./vibe-acp" }, "windows-x86_64": { - "archive": "https://github.com/mistralai/mistral-vibe/releases/download/v2.13.0/vibe-acp-windows-x86_64-2.13.0.zip", + "archive": "https://github.com/mistralai/mistral-vibe/releases/download/v2.14.0/vibe-acp-windows-x86_64-2.14.0.zip", "cmd": "./vibe-acp.exe" } } @@ -908,7 +915,7 @@ { "id": "nova", "name": "Nova", - "version": "1.1.13", + "version": "1.1.14", "description": "Nova by Compass AI - a fully-fledged software engineer at your command", "repository": "https://github.com/Compass-Agentic-Platform/nova", "website": "https://www.compassap.ai/portfolio/nova.html", @@ -919,7 +926,7 @@ "icon": "https://cdn.agentclientprotocol.com/registry/v1/latest/nova.svg", "distribution": { "npx": { - "package": "@compass-ai/nova@1.1.13", + "package": "@compass-ai/nova@1.1.14", "args": [ "acp" ] @@ -929,7 +936,7 @@ { "id": "opencode", "name": "OpenCode", - "version": "1.15.12", + "version": "1.16.2", "description": "The open source coding agent", "repository": "https://github.com/anomalyco/opencode", "website": "https://opencode.ai", @@ -941,42 +948,42 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/anomalyco/opencode/releases/download/v1.15.12/opencode-darwin-arm64.zip", + "archive": "https://github.com/anomalyco/opencode/releases/download/v1.16.2/opencode-darwin-arm64.zip", "cmd": "./opencode", "args": [ "acp" ] }, "darwin-x86_64": { - "archive": "https://github.com/anomalyco/opencode/releases/download/v1.15.12/opencode-darwin-x64.zip", + "archive": "https://github.com/anomalyco/opencode/releases/download/v1.16.2/opencode-darwin-x64.zip", "cmd": "./opencode", "args": [ "acp" ] }, "linux-aarch64": { - "archive": "https://github.com/anomalyco/opencode/releases/download/v1.15.12/opencode-linux-arm64.tar.gz", + "archive": "https://github.com/anomalyco/opencode/releases/download/v1.16.2/opencode-linux-arm64.tar.gz", "cmd": "./opencode", "args": [ "acp" ] }, "linux-x86_64": { - "archive": "https://github.com/anomalyco/opencode/releases/download/v1.15.12/opencode-linux-x64.tar.gz", + "archive": "https://github.com/anomalyco/opencode/releases/download/v1.16.2/opencode-linux-x64.tar.gz", "cmd": "./opencode", "args": [ "acp" ] }, "windows-aarch64": { - "archive": "https://github.com/anomalyco/opencode/releases/download/v1.15.12/opencode-windows-arm64.zip", + "archive": "https://github.com/anomalyco/opencode/releases/download/v1.16.2/opencode-windows-arm64.zip", "cmd": "./opencode", "args": [ "acp" ] }, "windows-x86_64": { - "archive": "https://github.com/anomalyco/opencode/releases/download/v1.15.12/opencode-windows-x64.zip", + "archive": "https://github.com/anomalyco/opencode/releases/download/v1.16.2/opencode-windows-x64.zip", "cmd": "./opencode.exe", "args": [ "acp" @@ -1084,7 +1091,7 @@ { "id": "qwen-code", "name": "Qwen Code", - "version": "0.17.0", + "version": "0.17.1", "description": "Alibaba's Qwen coding assistant", "repository": "https://github.com/QwenLM/qwen-code", "website": "https://qwenlm.github.io/qwen-code-docs/en/users/overview", @@ -1094,7 +1101,7 @@ "license": "Apache-2.0", "distribution": { "npx": { - "package": "@qwen-code/qwen-code@0.17.0", + "package": "@qwen-code/qwen-code@0.17.1", "args": [ "--acp", "--experimental-skills" @@ -1150,7 +1157,7 @@ { "id": "stakpak", "name": "Stakpak", - "version": "0.3.82", + "version": "0.3.86", "description": "Open-source DevOps agent in Rust with enterprise-grade security", "repository": "https://github.com/stakpak/agent", "website": "https://stakpak.dev", @@ -1162,35 +1169,35 @@ "distribution": { "binary": { "darwin-aarch64": { - "archive": "https://github.com/stakpak/agent/releases/download/v0.3.82/stakpak-darwin-aarch64.tar.gz", + "archive": "https://github.com/stakpak/agent/releases/download/v0.3.86/stakpak-darwin-aarch64.tar.gz", "cmd": "./stakpak", "args": [ "acp" ] }, "darwin-x86_64": { - "archive": "https://github.com/stakpak/agent/releases/download/v0.3.82/stakpak-darwin-x86_64.tar.gz", + "archive": "https://github.com/stakpak/agent/releases/download/v0.3.86/stakpak-darwin-x86_64.tar.gz", "cmd": "./stakpak", "args": [ "acp" ] }, "linux-aarch64": { - "archive": "https://github.com/stakpak/agent/releases/download/v0.3.82/stakpak-linux-aarch64.tar.gz", + "archive": "https://github.com/stakpak/agent/releases/download/v0.3.86/stakpak-linux-aarch64.tar.gz", "cmd": "./stakpak", "args": [ "acp" ] }, "linux-x86_64": { - "archive": "https://github.com/stakpak/agent/releases/download/v0.3.82/stakpak-linux-x86_64.tar.gz", + "archive": "https://github.com/stakpak/agent/releases/download/v0.3.86/stakpak-linux-x86_64.tar.gz", "cmd": "./stakpak", "args": [ "acp" ] }, "windows-x86_64": { - "archive": "https://github.com/stakpak/agent/releases/download/v0.3.82/stakpak-windows-x86_64.zip", + "archive": "https://github.com/stakpak/agent/releases/download/v0.3.86/stakpak-windows-x86_64.zip", "cmd": "./stakpak.exe", "args": [ "acp" diff --git a/resources/model-db/providers.json b/resources/model-db/providers.json index 9cf139910..cf9e5a921 100644 --- a/resources/model-db/providers.json +++ b/resources/model-db/providers.json @@ -1261,6 +1261,51 @@ }, "type": "chat" }, + { + "id": "accounts/fireworks/routers/kimi-k2p6-fast", + "name": "Kimi K2.6 Fast", + "display_name": "Kimi K2.6 Fast", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": true, + "release_date": "2026-04-17", + "last_updated": "2026-06-05", + "cost": { + "input": 2, + "output": 8, + "cache_read": 0.3 + }, + "type": "chat" + }, { "id": "accounts/fireworks/routers/kimi-k2p6-turbo", "name": "Kimi K2.6 Turbo", @@ -22580,6 +22625,7 @@ "context": 128000, "output": 16384 }, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -22611,6 +22657,7 @@ "context": 256000, "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -22642,6 +22689,7 @@ "context": 256000, "output": 262144 }, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -22672,6 +22720,7 @@ "context": 128000, "output": 16384 }, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -22702,6 +22751,7 @@ "context": 128000, "output": 16384 }, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -22732,6 +22782,7 @@ "context": 16384, "output": 8192 }, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -22762,6 +22813,7 @@ "context": 262144, "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -22793,6 +22845,7 @@ "context": 262144, "output": 16384 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -24323,7 +24376,12 @@ }, "tool_call": false, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": false, @@ -47921,6 +47979,11 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": false, "open_weights": false, "release_date": "2026-04-22", @@ -53259,6 +53322,38 @@ }, "type": "chat" }, + { + "id": "gemini-2.5-flash-tts", + "name": "Gemini 2.5 Flash TTS", + "display_name": "Gemini 2.5 Flash TTS", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-09-30", + "last_updated": "2025-12-10", + "cost": { + "input": 0.5, + "output": 10 + }, + "type": "chat" + }, { "id": "gemini-3.1-flash-lite-preview", "name": "Gemini 3.1 Flash Lite Preview", @@ -54107,6 +54202,58 @@ }, "type": "embedding" }, + { + "id": "gemini-2.5-pro-tts", + "name": "Gemini 2.5 Pro TTS", + "display_name": "Gemini 2.5 Pro TTS", + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + }, + "temperature": false, + "tool_call": false, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "budget", + "budget": { + "default": -1, + "min": 128, + "max": 32768, + "auto": -1, + "unit": "tokens" + }, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thought_signatures" + ] + } + }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2025-09-30", + "last_updated": "2025-12-10", + "cost": { + "input": 1, + "output": 20 + }, + "type": "chat" + }, { "id": "zai-org/glm-4.7-maas", "name": "GLM-4.7", @@ -85477,56 +85624,24 @@ "type": "chat" }, { - "id": "sonar-pro", - "name": "Sonar Pro", - "display_name": "Sonar Pro", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2025-09-01", - "release_date": "2024-01-01", - "last_updated": "2025-09-01", - "cost": { - "input": 3, - "output": 15 - }, - "type": "chat" - }, - { - "id": "gpt-5.4-pro", - "name": "GPT-5.4 Pro", - "display_name": "GPT-5.4 Pro", + "id": "minimax-m3", + "name": "MiniMax-M3", + "display_name": "MiniMax-M3", "modalities": { "input": [ "text", - "image" + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 1050000, + "context": 512000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -85534,39 +85649,24 @@ }, "extra_capabilities": { "reasoning": { - "supported": true, - "default_enabled": true, - "mode": "effort", - "effort": "high", - "effort_options": [ - "medium", - "high", - "xhigh" - ], - "verbosity": "medium", - "verbosity_options": [ - "low", - "medium", - "high" - ], - "visibility": "hidden" + "supported": true } }, "attachment": true, "open_weights": false, - "knowledge": "2025-08-31", - "release_date": "2026-03-05", - "last_updated": "2026-03-05", + "release_date": "2026-06-01", + "last_updated": "2026-06-01", "cost": { - "input": 30, - "output": 180 + "input": 0.6, + "output": 2.4, + "cache_read": 0.12 }, "type": "chat" }, { - "id": "qwen-max-latest", - "name": "Qwen Max Latest", - "display_name": "Qwen Max Latest", + "id": "sonar-pro", + "name": "Sonar Pro", + "display_name": "Sonar Pro", "modalities": { "input": [ "text", @@ -85577,186 +85677,29 @@ ] }, "limit": { - "context": 32768, + "context": 200000, "output": 8192 }, "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "release_date": "2025-01-25", - "last_updated": "2025-01-25", - "cost": { - "input": 0.345, - "output": 1.377 - }, - "type": "chat" - }, - { - "id": "gpt-4.1-mini", - "name": "GPT-4.1 mini", - "display_name": "GPT-4.1 mini", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": false - }, - "attachment": true, - "open_weights": false, - "knowledge": "2024-04", - "release_date": "2025-04-14", - "last_updated": "2025-04-14", - "cost": { - "input": 0.4, - "output": 1.6, - "cache_read": 0.1 - }, - "type": "chat" - }, - { - "id": "grok-4-20-beta-0309-non-reasoning", - "name": "Grok 4.20 (Non-Reasoning)", - "display_name": "Grok 4.20 (Non-Reasoning)", - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 30000 - }, - "temperature": true, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "attachment": true, "open_weights": false, - "release_date": "2026-03-09", - "last_updated": "2026-03-09", - "cost": { - "input": 1.25, - "output": 2.5, - "cache_read": 0.2, - "tiers": [ - { - "input": 2.5, - "output": 5, - "cache_read": 0.4, - "tier": { - "type": "context", - "size": 200000 - } - } - ], - "context_over_200k": { - "input": 2.5, - "output": 5, - "cache_read": 0.4 - } - }, - "type": "chat" - }, - { - "id": "deepseek-v3.2", - "name": "DeepSeek V3.2", - "display_name": "DeepSeek V3.2", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 16384 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-09-29", - "last_updated": "2025-09-29", - "cost": { - "input": 0.28, - "output": 0.42, - "cache_read": 0.056 - }, - "type": "chat" - }, - { - "id": "seed-1-8-251228", - "name": "Seed 1.8 (251228)", - "display_name": "Seed 1.8 (251228)", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8192 - }, - "temperature": true, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "attachment": true, - "open_weights": true, - "release_date": "2025-12-18", - "last_updated": "2025-12-18", + "knowledge": "2025-09-01", + "release_date": "2024-01-01", + "last_updated": "2025-09-01", "cost": { - "input": 0.25, - "output": 2, - "cache_read": 0.05 + "input": 3, + "output": 15 }, "type": "chat" }, { - "id": "gpt-5.2-pro", - "name": "GPT-5.2 Pro", - "display_name": "GPT-5.2 Pro", + "id": "gpt-5.4-pro", + "name": "GPT-5.4 Pro", + "display_name": "GPT-5.4 Pro", "modalities": { "input": [ "text", @@ -85767,7 +85710,251 @@ ] }, "limit": { - "context": 400000, + "context": 1050000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "high", + "effort_options": [ + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", + "cost": { + "input": 30, + "output": 180 + }, + "type": "chat" + }, + { + "id": "qwen-max-latest", + "name": "Qwen Max Latest", + "display_name": "Qwen Max Latest", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2025-01-25", + "last_updated": "2025-01-25", + "cost": { + "input": 0.345, + "output": 1.377 + }, + "type": "chat" + }, + { + "id": "gpt-4.1-mini", + "name": "GPT-4.1 mini", + "display_name": "GPT-4.1 mini", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1047576, + "output": 32768 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "knowledge": "2024-04", + "release_date": "2025-04-14", + "last_updated": "2025-04-14", + "cost": { + "input": 0.4, + "output": 1.6, + "cache_read": 0.1 + }, + "type": "chat" + }, + { + "id": "grok-4-20-beta-0309-non-reasoning", + "name": "Grok 4.20 (Non-Reasoning)", + "display_name": "Grok 4.20 (Non-Reasoning)", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-03-09", + "last_updated": "2026-03-09", + "cost": { + "input": 1.25, + "output": 2.5, + "cache_read": 0.2, + "tiers": [ + { + "input": 2.5, + "output": 5, + "cache_read": 0.4, + "tier": { + "type": "context", + "size": 200000 + } + } + ], + "context_over_200k": { + "input": 2.5, + "output": 5, + "cache_read": 0.4 + } + }, + "type": "chat" + }, + { + "id": "deepseek-v3.2", + "name": "DeepSeek V3.2", + "display_name": "DeepSeek V3.2", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-09-29", + "last_updated": "2025-09-29", + "cost": { + "input": 0.28, + "output": 0.42, + "cache_read": 0.056 + }, + "type": "chat" + }, + { + "id": "seed-1-8-251228", + "name": "Seed 1.8 (251228)", + "display_name": "Seed 1.8 (251228)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8192 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": true, + "release_date": "2025-12-18", + "last_updated": "2025-12-18", + "cost": { + "input": 0.25, + "output": 2, + "cache_read": 0.05 + }, + "type": "chat" + }, + { + "id": "gpt-5.2-pro", + "name": "GPT-5.2 Pro", + "display_name": "GPT-5.2 Pro", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, "output": 128000 }, "temperature": false, @@ -90253,6 +90440,46 @@ }, "type": "chat" }, + { + "id": "qwen3.7-plus", + "name": "Qwen3.7 Plus", + "display_name": "Qwen3.7 Plus", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-02", + "last_updated": "2026-06-02", + "cost": { + "input": 0.4, + "output": 1.6, + "cache_read": 0.08, + "cache_write": 0.5 + }, + "type": "chat" + }, { "id": "minimax-m2.5-highspeed", "name": "MiniMax-M2.5-highspeed", @@ -102755,7 +102982,7 @@ }, "limit": { "context": 40960, - "output": 40960 + "output": 4096 }, "tool_call": false, "reasoning": { @@ -112277,6 +112504,513 @@ } ] }, + "freemodel": { + "id": "freemodel", + "name": "FreeModel", + "display_name": "FreeModel", + "api": "https://cc.freemodel.dev/v1", + "doc": "https://freemodel.dev", + "models": [ + { + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "display_name": "Claude Sonnet 4.6", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-17", + "last_updated": "2026-03-13", + "cost": { + "input": 3, + "output": 15, + "cache_read": 0.3, + "cache_write": 3.75 + }, + "type": "chat" + }, + { + "id": "claude-haiku-4-5-20251001", + "name": "Claude Haiku 4.5", + "display_name": "Claude Haiku 4.5", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-02-28", + "release_date": "2025-10-15", + "last_updated": "2025-10-15", + "cost": { + "input": 1, + "output": 5, + "cache_read": 0.1, + "cache_write": 1.25 + }, + "type": "chat" + }, + { + "id": "gpt-5.4", + "name": "GPT-5.4", + "display_name": "GPT-5.4", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-05", + "last_updated": "2026-03-05", + "cost": { + "input": 2.5, + "output": 15, + "cache_read": 0.25, + "cache_write": 2.5 + }, + "type": "chat" + }, + { + "id": "gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-12-01", + "release_date": "2026-04-23", + "last_updated": "2026-04-23", + "cost": { + "input": 5, + "output": 30, + "cache_read": 0.5, + "cache_write": 5 + }, + "type": "chat" + }, + { + "id": "gpt-5.3-codex", + "name": "GPT-5.3 Codex", + "display_name": "GPT-5.3 Codex", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true, + "mode": "effort", + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-02-05", + "last_updated": "2026-02-05", + "cost": { + "input": 1.75, + "output": 14, + "cache_read": 0.175, + "cache_write": 1.75 + }, + "type": "chat" + }, + { + "id": "claude-opus-4-8", + "name": "Claude Opus 4.8", + "display_name": "Claude Opus 4.8", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-05-28", + "last_updated": "2026-05-28", + "cost": { + "input": 5, + "output": 25, + "cache_read": 0.5, + "cache_write": 6.25 + }, + "type": "chat" + }, + { + "id": "gpt-5.4-mini", + "name": "GPT-5.4 mini", + "display_name": "GPT-5.4 mini", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "none", + "effort_options": [ + "none", + "low", + "medium", + "high", + "xhigh" + ], + "verbosity": "medium", + "verbosity_options": [ + "low", + "medium", + "high" + ], + "visibility": "hidden" + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-08-31", + "release_date": "2026-03-17", + "last_updated": "2026-03-17", + "cost": { + "input": 0.75, + "output": 4.5, + "cache_read": 0.075, + "cache_write": 0.75 + }, + "type": "chat" + }, + { + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "display_name": "Claude Opus 4.6", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "mixed", + "budget": { + "min": 1024, + "unit": "tokens" + }, + "effort": "medium", + "effort_options": [ + "low", + "medium", + "high" + ], + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Anthropic recommends adaptive thinking with effort for Claude 4.6; budget_tokens remains a deprecated compatibility path." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-05-31", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", + "cost": { + "input": 5, + "output": 25, + "cache_read": 0.5, + "cache_write": 6.25 + }, + "type": "chat" + }, + { + "id": "claude-opus-4-7", + "name": "Claude Opus 4.7", + "display_name": "Claude Opus 4.7", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": false + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": false, + "mode": "effort", + "effort": "high", + "effort_options": [ + "low", + "medium", + "high", + "xhigh" + ], + "interleaved": true, + "summaries": true, + "continuation": [ + "thinking_blocks" + ], + "notes": [ + "Claude Opus 4.7 requires thinking.type = \"adaptive\" to enable thinking explicitly.", + "Manual budget_tokens requests return 400 on Claude Opus 4.7.", + "task_budget is separate from thinking control and should not be treated as a thinking budget." + ] + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2026-01-31", + "release_date": "2026-04-16", + "last_updated": "2026-04-16", + "cost": { + "input": 5, + "output": 25, + "cache_read": 0.5, + "cache_write": 6.25 + }, + "type": "chat" + } + ] + }, "lilac": { "id": "lilac", "name": "Lilac", @@ -113126,7 +113860,7 @@ ] }, "limit": { - "context": 262144, + "context": 1000000, "output": 65536 }, "temperature": true, @@ -113155,7 +113889,25 @@ "input": 0.5, "output": 3, "cache_read": 0.05, - "cache_write": 0.625 + "cache_write": 0.625, + "tiers": [ + { + "input": 2, + "output": 6, + "cache_read": 0.2, + "cache_write": 2.5, + "tier": { + "type": "context", + "size": 256000 + } + } + ], + "context_over_200k": { + "input": 2, + "output": 6, + "cache_read": 0.2, + "cache_write": 2.5 + } }, "type": "chat" }, @@ -113409,7 +114161,7 @@ ] }, "limit": { - "context": 262144, + "context": 1000000, "output": 65536 }, "temperature": true, @@ -113431,7 +114183,25 @@ "input": 0.4, "output": 1.6, "cache_read": 0.04, - "cache_write": 0.5 + "cache_write": 0.5, + "tiers": [ + { + "input": 1.2, + "output": 4.8, + "cache_read": 0.12, + "cache_write": 1.5, + "tier": { + "type": "context", + "size": 256000 + } + } + ], + "context_over_200k": { + "input": 1.2, + "output": 4.8, + "cache_read": 0.12, + "cache_write": 1.5 + } }, "type": "chat" }, @@ -144719,6 +145489,53 @@ "api": "https://www.xpersona.co/v1", "doc": "https://www.xpersona.co/docs", "models": [ + { + "id": "xpersona-gpt-5.5", + "name": "GPT-5.5", + "display_name": "GPT-5.5", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "temperature": false, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "interleaved": true, + "summaries": true, + "visibility": "summary", + "continuation": [ + "thinking_blocks" + ] + } + }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-12-30", + "release_date": "2026-05-29", + "last_updated": "2026-05-29", + "cost": { + "input": 3, + "output": 18, + "reasoning": 18, + "cache_read": 0.3 + }, + "type": "chat" + }, { "id": "xpersona-frieren-coder", "name": "Xpersona Frieren 1", @@ -152949,6 +153766,11 @@ "knowledge": "2024-10", "release_date": "2025-10-24", "last_updated": "2025-10-24", + "cost": { + "input": 0.03, + "output": 1.2, + "cache_read": 0.06 + }, "type": "chat" }, { @@ -153125,14 +153947,16 @@ "display_name": "DeepSeek V3.2", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 163842, + "context": 128000, "output": 8000 }, "temperature": true, @@ -153151,9 +153975,9 @@ "release_date": "2025-12-01", "last_updated": "2025-12-01", "cost": { - "input": 0.27, - "output": 0.4, - "cache_read": 0.22 + "input": 0.28, + "output": 0.42, + "cache_read": 0.028 }, "type": "chat" }, @@ -153163,7 +153987,9 @@ "display_name": "DeepSeek V3.2 Thinking", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -153171,7 +153997,7 @@ }, "limit": { "context": 128000, - "output": 64000 + "output": 8000 }, "temperature": true, "tool_call": true, @@ -153191,9 +154017,8 @@ "release_date": "2025-12-01", "last_updated": "2025-12-01", "cost": { - "input": 0.28, - "output": 0.42, - "cache_read": 0.03 + "input": 0.62, + "output": 1.85 }, "type": "chat" }, @@ -153226,7 +154051,8 @@ "last_updated": "2025-09-22", "cost": { "input": 0.27, - "output": 1 + "output": 1, + "cache_read": 0.135 }, "type": "chat" }, @@ -153236,7 +154062,9 @@ "display_name": "DeepSeek V4 Flash", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -153265,12 +154093,13 @@ }, "attachment": false, "open_weights": true, + "knowledge": "2025-05", "release_date": "2026-04-23", "last_updated": "2026-04-24", "cost": { "input": 0.14, "output": 0.28, - "cache_read": 0.028 + "cache_read": 0.0028 }, "type": "chat" }, @@ -153288,7 +154117,7 @@ }, "limit": { "context": 163840, - "output": 16384 + "output": 163840 }, "temperature": true, "tool_call": true, @@ -153301,8 +154130,9 @@ "release_date": "2024-12-26", "last_updated": "2024-12-26", "cost": { - "input": 0.77, - "output": 0.77 + "input": 0.27, + "output": 1.12, + "cache_read": 0.135 }, "type": "chat" }, @@ -153345,7 +154175,8 @@ "display_name": "DeepSeek V4 Pro", "modalities": { "input": [ - "text" + "text", + "pdf" ], "output": [ "text" @@ -153374,12 +154205,13 @@ }, "attachment": false, "open_weights": true, + "knowledge": "2025-05", "release_date": "2026-04-23", "last_updated": "2026-04-24", "cost": { - "input": 1.74, - "output": 3.48, - "cache_read": 0.145 + "input": 0.435, + "output": 0.87, + "cache_read": 0.0036 }, "type": "chat" }, @@ -153397,7 +154229,7 @@ }, "limit": { "context": 163840, - "output": 128000 + "output": 8192 }, "temperature": true, "tool_call": true, @@ -153411,8 +154243,9 @@ "release_date": "2025-08-21", "last_updated": "2025-08-21", "cost": { - "input": 0.3, - "output": 1 + "input": 0.56, + "output": 1.68, + "cache_read": 0.28 }, "type": "chat" }, @@ -153460,6 +154293,37 @@ }, "type": "chat" }, + { + "id": "inception/mercury-coder-small", + "name": "Mercury Coder Small Beta", + "display_name": "Mercury Coder Small Beta", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 16384 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": false + }, + "attachment": false, + "open_weights": false, + "release_date": "2025-02-26", + "last_updated": "2025-02-26", + "cost": { + "input": 0.25, + "output": 1 + }, + "type": "chat" + }, { "id": "inception/mercury-edit-2", "name": "Mercury Edit 2", @@ -153612,7 +154476,7 @@ "open_weights": false, "knowledge": "2025-08-31", "release_date": "2026-02-17", - "last_updated": "2026-02-17", + "last_updated": "2026-03-13", "cost": { "input": 3, "output": 15, @@ -153672,13 +154536,13 @@ "attachment": true, "open_weights": false, "knowledge": "2025-03-31", - "release_date": "2025-11-24", + "release_date": "2024-11-24", "last_updated": "2025-11-24", "cost": { "input": 5, "output": 25, "cache_read": 0.5, - "cache_write": 18.75 + "cache_write": 6.25 }, "type": "chat" }, @@ -153700,7 +154564,7 @@ "context": 1000000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -153732,6 +154596,7 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2026-01-31", "release_date": "2026-04-16", "last_updated": "2026-04-16", "cost": { @@ -153795,8 +154660,8 @@ "attachment": true, "open_weights": false, "knowledge": "2025-05-31", - "release_date": "2026-02", - "last_updated": "2026-02", + "release_date": "2026-02-05", + "last_updated": "2026-03-13", "cost": { "input": 5, "output": 25, @@ -154292,10 +155157,10 @@ "attachment": false, "open_weights": false, "knowledge": "2025-04", - "release_date": "2025-04", + "release_date": "2025-04-01", "last_updated": "2025-04", "cost": { - "input": 0.06, + "input": 0.12, "output": 0.24 }, "type": "chat" @@ -154313,7 +155178,7 @@ ] }, "limit": { - "context": 262144, + "context": 131072, "output": 32768 }, "temperature": true, @@ -154325,10 +155190,10 @@ "open_weights": true, "knowledge": "2025-04", "release_date": "2025-09-12", - "last_updated": "2025-09-12", + "last_updated": "2025-09", "cost": { - "input": 0.09, - "output": 1.1 + "input": 0.15, + "output": 1.2 }, "type": "chat" }, @@ -154383,7 +155248,8 @@ "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -154391,7 +155257,7 @@ }, "limit": { "context": 131072, - "output": 129024 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -154416,8 +155282,8 @@ "release_date": "2025-09-24", "last_updated": "2025-09-24", "cost": { - "input": 0.7, - "output": 8.4 + "input": 0.4, + "output": 4 }, "type": "chat" }, @@ -154428,7 +155294,8 @@ "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -154448,8 +155315,8 @@ "release_date": "2025-09-24", "last_updated": "2026-05-01", "cost": { - "input": 0.39999999999999997, - "output": 1.5999999999999999 + "input": 0.4, + "output": 1.6 }, "type": "chat" }, @@ -154491,10 +155358,10 @@ "attachment": true, "open_weights": false, "release_date": "2026-04-22", - "last_updated": "2026-05-01", + "last_updated": "2026-04-22", "cost": { "input": 0.6, - "output": 3.5999999999999996 + "output": 3.6 }, "type": "chat" }, @@ -154535,8 +155402,9 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-04", "release_date": "2026-02-16", - "last_updated": "2026-02-19", + "last_updated": "2026-02-16", "cost": { "input": 0.4, "output": 2.4, @@ -154584,7 +155452,8 @@ "last_updated": "2025-09-23", "cost": { "input": 1.2, - "output": 6 + "output": 6, + "cache_read": 0.24 }, "type": "chat" }, @@ -154602,7 +155471,7 @@ }, "limit": { "context": 1000000, - "output": 1000000 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -154616,7 +155485,8 @@ "last_updated": "2025-07-23", "cost": { "input": 1, - "output": 5 + "output": 5, + "cache_read": 0.2 }, "type": "chat" }, @@ -154706,7 +155576,6 @@ "modalities": { "input": [ "text", - "image", "pdf" ], "output": [ @@ -154733,10 +155602,10 @@ "release_date": "2026-05-21", "last_updated": "2026-05-21", "cost": { - "input": 2.5, - "output": 7.5, - "cache_read": 0.5, - "cache_write": 3.125 + "input": 1.25, + "output": 3.75, + "cache_read": 0.25, + "cache_write": 1.5625 }, "type": "chat" }, @@ -154777,12 +155646,13 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-04", "release_date": "2026-04-02", - "last_updated": "2026-04-03", + "last_updated": "2026-04-02", "cost": { "input": 0.5, "output": 3, - "cache_read": 0.09999999999999999, + "cache_read": 0.1, "cache_write": 0.625 }, "type": "chat" @@ -154801,7 +155671,7 @@ }, "limit": { "context": 131072, - "output": 65536 + "output": 32768 }, "temperature": true, "tool_call": true, @@ -154824,10 +155694,10 @@ "open_weights": true, "knowledge": "2025-09", "release_date": "2025-09-12", - "last_updated": "2025-09-12", + "last_updated": "2025-09", "cost": { "input": 0.15, - "output": 1.5 + "output": 1.2 }, "type": "chat" }, @@ -154847,7 +155717,7 @@ "context": 32768, "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -154856,10 +155726,6 @@ "open_weights": false, "release_date": "2025-06-05", "last_updated": "2025-06-05", - "cost": { - "input": 0.05, - "output": 0 - }, "type": "embedding" }, { @@ -154878,7 +155744,7 @@ "context": 32768, "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -154887,10 +155753,6 @@ "open_weights": false, "release_date": "2025-06-05", "last_updated": "2025-06-05", - "cost": { - "input": 0.02, - "output": 0 - }, "type": "embedding" }, { @@ -154909,7 +155771,7 @@ "context": 32768, "output": 32768 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -154918,10 +155780,6 @@ "open_weights": false, "release_date": "2025-11-14", "last_updated": "2025-11-14", - "cost": { - "input": 0.01, - "output": 0 - }, "type": "embedding" }, { @@ -154937,7 +155795,7 @@ ] }, "limit": { - "context": 40960, + "context": 262144, "output": 16384 }, "temperature": true, @@ -154948,11 +155806,11 @@ "attachment": false, "open_weights": false, "knowledge": "2025-04", - "release_date": "2025-04", + "release_date": "2025-04-01", "last_updated": "2025-04", "cost": { - "input": 0.13, - "output": 0.6 + "input": 0.22, + "output": 0.88 }, "type": "chat" }, @@ -154969,8 +155827,8 @@ ] }, "limit": { - "context": 160000, - "output": 32768 + "context": 262144, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -154981,11 +155839,11 @@ "attachment": false, "open_weights": false, "knowledge": "2025-04", - "release_date": "2025-04", + "release_date": "2025-04-01", "last_updated": "2025-04", "cost": { - "input": 0.07, - "output": 0.27 + "input": 0.15, + "output": 0.6 }, "type": "chat" }, @@ -155003,7 +155861,7 @@ }, "limit": { "context": 262144, - "output": 66536 + "output": 65536 }, "temperature": true, "tool_call": true, @@ -155013,11 +155871,54 @@ "attachment": false, "open_weights": false, "knowledge": "2025-04", - "release_date": "2025-04", + "release_date": "2025-04-01", "last_updated": "2025-04", "cost": { - "input": 0.38, - "output": 1.53 + "input": 1.5, + "output": 7.5, + "cache_read": 0.3 + }, + "type": "chat" + }, + { + "id": "alibaba/qwen3.7-plus", + "name": "Qwen 3.7 Plus", + "display_name": "Qwen 3.7 Plus", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "knowledge": "2025-04", + "release_date": "2026-06-01", + "last_updated": "2026-06-02", + "cost": { + "input": 0.4, + "output": 1.6, + "cache_read": 0.08, + "cache_write": 0.5 }, "type": "chat" }, @@ -155034,8 +155935,8 @@ ] }, "limit": { - "context": 40960, - "output": 16384 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -155046,11 +155947,11 @@ "attachment": false, "open_weights": false, "knowledge": "2025-04", - "release_date": "2025-04", + "release_date": "2025-04-01", "last_updated": "2025-04", "cost": { - "input": 0.1, - "output": 0.3 + "input": 0.16, + "output": 0.64 }, "type": "chat" }, @@ -155069,8 +155970,8 @@ ] }, "limit": { - "context": 262114, - "output": 262114 + "context": 131072, + "output": 32768 }, "temperature": true, "tool_call": true, @@ -155092,11 +155993,11 @@ "attachment": true, "open_weights": false, "knowledge": "2025-04", - "release_date": "2025-04", + "release_date": "2025-09-24", "last_updated": "2025-04", "cost": { - "input": 0.3, - "output": 2.9 + "input": 0.4, + "output": 4 }, "type": "chat" }, @@ -155152,7 +156053,6 @@ "modalities": { "input": [ "text", - "image", "pdf" ], "output": [ @@ -155206,7 +156106,7 @@ "attachment": false, "open_weights": false, "knowledge": "2025-04", - "release_date": "2025-04", + "release_date": "2025-04-01", "last_updated": "2025-04", "cost": { "input": 0.08, @@ -155221,7 +156121,8 @@ "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" @@ -155242,8 +156143,8 @@ "release_date": "2025-09-24", "last_updated": "2025-09-24", "cost": { - "input": 0.7, - "output": 2.8 + "input": 0.4, + "output": 1.6 }, "type": "chat" }, @@ -155271,12 +156172,13 @@ }, "attachment": false, "open_weights": false, + "knowledge": "2024-12", "release_date": "2026-03-18", - "last_updated": "2026-03-20", + "last_updated": "2026-03-18", "cost": { "input": 1, "output": 3, - "cache_read": 0.19999999999999998 + "cache_read": 0.2 }, "type": "chat" }, @@ -155288,8 +156190,7 @@ "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" @@ -155307,12 +156208,13 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2024-12", "release_date": "2026-04-22", - "last_updated": "2026-05-01", + "last_updated": "2026-04-22", "cost": { - "input": 0.39999999999999997, - "output": 2, - "cache_read": 0.08 + "input": 0.14, + "output": 0.28, + "cache_read": 0.0028 }, "type": "chat" }, @@ -155342,10 +156244,11 @@ "open_weights": false, "knowledge": "2024-10", "release_date": "2025-12-17", - "last_updated": "2025-12-17", + "last_updated": "2026-02-04", "cost": { "input": 0.1, - "output": 0.29 + "output": 0.3, + "cache_read": 0.01 }, "type": "chat" }, @@ -155355,7 +156258,9 @@ "display_name": "MiMo V2.5 Pro", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -155373,12 +156278,13 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2024-12", "release_date": "2026-04-22", - "last_updated": "2026-05-01", + "last_updated": "2026-04-22", "cost": { - "input": 1, - "output": 3, - "cache_read": 0.19999999999999998 + "input": 0.435, + "output": 0.87, + "cache_read": 0.0036 }, "type": "chat" }, @@ -155398,7 +156304,7 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -155407,10 +156313,6 @@ "open_weights": false, "release_date": "2025-05-28", "last_updated": "2025-05-28", - "cost": { - "input": 0.15, - "output": 0 - }, "type": "chat" }, { @@ -155472,6 +156374,10 @@ "knowledge": "2024-10", "release_date": "2025-05-07", "last_updated": "2025-05-07", + "cost": { + "input": 0.1, + "output": 0.3 + }, "type": "chat" }, { @@ -155490,7 +156396,7 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -155499,10 +156405,6 @@ "open_weights": false, "release_date": "2023-12-11", "last_updated": "2023-12-11", - "cost": { - "input": 0.1, - "output": 0 - }, "type": "chat" }, { @@ -155518,8 +156420,8 @@ ] }, "limit": { - "context": 60288, - "output": 16000 + "context": 131072, + "output": 131072 }, "temperature": true, "tool_call": true, @@ -155532,8 +156434,8 @@ "release_date": "2024-07-01", "last_updated": "2024-07-01", "cost": { - "input": 0.04, - "output": 0.17 + "input": 0.02, + "output": 0.04 }, "type": "chat" }, @@ -155694,6 +156596,10 @@ "knowledge": "2024-10", "release_date": "2025-12-09", "last_updated": "2025-12-09", + "cost": { + "input": 0.4, + "output": 2 + }, "type": "chat" }, { @@ -155996,7 +156902,9 @@ "display_name": "Grok 4.20 Multi-Agent", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -156017,9 +156925,9 @@ "release_date": "2026-03-09", "last_updated": "2026-03-23", "cost": { - "input": 2, - "output": 6, - "cache_read": 0.19999999999999998 + "input": 1.25, + "output": 2.5, + "cache_read": 0.2 }, "type": "chat" }, @@ -156052,9 +156960,9 @@ "release_date": "2026-03-09", "last_updated": "2026-03-23", "cost": { - "input": 2, - "output": 6, - "cache_read": 0.19999999999999998 + "input": 1.25, + "output": 2.5, + "cache_read": 0.2 }, "type": "chat" }, @@ -156064,15 +156972,17 @@ "display_name": "Grok 4.1 Fast Non-Reasoning", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -156122,11 +157032,11 @@ "attachment": true, "open_weights": false, "release_date": "2026-05-20", - "last_updated": "2026-05-21", + "last_updated": "2026-04-16", "cost": { "input": 1, "output": 2, - "cache_read": 0.19999999999999998 + "cache_read": 0.2 }, "type": "chat" }, @@ -156164,15 +157074,17 @@ "display_name": "Grok 4.1 Fast Reasoning", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 + "context": 1000000, + "output": 1000000 }, "temperature": true, "tool_call": true, @@ -156259,9 +157171,9 @@ "release_date": "2026-03-11", "last_updated": "2026-03-13", "cost": { - "input": 2, - "output": 6, - "cache_read": 0.19999999999999998 + "input": 1.25, + "output": 2.5, + "cache_read": 0.4 }, "type": "chat" }, @@ -156297,11 +157209,11 @@ "attachment": true, "open_weights": false, "release_date": "2026-04-30", - "last_updated": "2026-05-01", + "last_updated": "2026-04-17", "cost": { "input": 1.25, "output": 2.5, - "cache_read": 0.19999999999999998 + "cache_read": 0.2 }, "type": "chat" }, @@ -156334,9 +157246,9 @@ "release_date": "2026-03-11", "last_updated": "2026-03-13", "cost": { - "input": 2, - "output": 6, - "cache_read": 0.19999999999999998 + "input": 1.25, + "output": 2.5, + "cache_read": 0.2 }, "type": "chat" }, @@ -156374,7 +157286,9 @@ "display_name": "Grok 4.20 Multi Agent Beta", "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -156395,9 +157309,9 @@ "release_date": "2026-03-11", "last_updated": "2026-03-13", "cost": { - "input": 2, - "output": 6, - "cache_read": 0.19999999999999998 + "input": 1.25, + "output": 2.5, + "cache_read": 0.2 }, "type": "chat" }, @@ -156429,9 +157343,9 @@ "release_date": "2026-03-09", "last_updated": "2026-03-23", "cost": { - "input": 2, - "output": 6, - "cache_read": 0.19999999999999998 + "input": 1.25, + "output": 2.5, + "cache_read": 0.2 }, "type": "chat" }, @@ -156451,7 +157365,7 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -156460,10 +157374,6 @@ "open_weights": false, "release_date": "2025-04-15", "last_updated": "2025-04-15", - "cost": { - "input": 0.12, - "output": 0 - }, "type": "chat" }, { @@ -156551,8 +157461,8 @@ ] }, "limit": { - "context": 216144, - "output": 216144 + "context": 262114, + "output": 262114 }, "temperature": true, "tool_call": true, @@ -156577,9 +157487,9 @@ "release_date": "2025-11-06", "last_updated": "2025-11-06", "cost": { - "input": 0.47, - "output": 2, - "cache_read": 0.14 + "input": 0.6, + "output": 2.5, + "cache_read": 0.15 }, "type": "chat" }, @@ -156614,8 +157524,9 @@ }, "attachment": true, "open_weights": true, + "knowledge": "2025-01", "release_date": "2026-04-20", - "last_updated": "2026-04-24", + "last_updated": "2026-04-21", "cost": { "input": 0.95, "output": 4, @@ -156650,8 +157561,9 @@ "release_date": "2025-09-05", "last_updated": "2025-09-05", "cost": { - "input": 2.4, - "output": 10 + "input": 1.15, + "output": 8, + "cache_read": 0.15 }, "type": "chat" }, @@ -156694,16 +157606,15 @@ "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 262114, + "output": 262114 }, "temperature": true, "tool_call": true, @@ -156726,10 +157637,11 @@ "open_weights": true, "knowledge": "2025-01", "release_date": "2026-01-26", - "last_updated": "2026-01-26", + "last_updated": "2026-01", "cost": { "input": 0.6, - "output": 1.2 + "output": 3, + "cache_read": 0.1 }, "type": "chat" }, @@ -156792,10 +157704,6 @@ "knowledge": "2025-02", "release_date": "2025-02-19", "last_updated": "2025-02-19", - "cost": { - "input": 1, - "output": 1 - }, "type": "chat" }, { @@ -156825,10 +157733,6 @@ "knowledge": "2025-09", "release_date": "2025-02-19", "last_updated": "2025-02-19", - "cost": { - "input": 3, - "output": 15 - }, "type": "chat" }, { @@ -156891,10 +157795,6 @@ "knowledge": "2025-09", "release_date": "2025-02-19", "last_updated": "2025-02-19", - "cost": { - "input": 2, - "output": 8 - }, "type": "chat" }, { @@ -156922,10 +157822,10 @@ "attachment": false, "open_weights": false, "knowledge": "2024-10", - "release_date": "2024-12", - "last_updated": "2024-12", + "release_date": "2024-12-01", + "last_updated": "2025-12-15", "cost": { - "input": 0.06, + "input": 0.05, "output": 0.24 }, "type": "chat" @@ -156958,8 +157858,41 @@ "release_date": "2025-08-18", "last_updated": "2025-08-18", "cost": { - "input": 0.04, - "output": 0.16 + "input": 0.06, + "output": 0.23 + }, + "type": "chat" + }, + { + "id": "nvidia/nemotron-3-ultra-550b-a55b", + "name": "Nemotron 3 Ultra", + "display_name": "Nemotron 3 Ultra", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": false, + "release_date": "2026-06-04", + "last_updated": "2026-06-04", + "cost": { + "input": 0.6, + "output": 2.4, + "cache_read": 0.12 }, "type": "chat" }, @@ -156989,8 +157922,8 @@ "attachment": true, "open_weights": false, "knowledge": "2024-10", - "release_date": "2024-12", - "last_updated": "2024-12", + "release_date": "2024-12-01", + "last_updated": "2025-10-28", "cost": { "input": 0.2, "output": 0.6 @@ -157021,7 +157954,7 @@ "attachment": false, "open_weights": false, "release_date": "2026-03-18", - "last_updated": "2026-03-30", + "last_updated": "2026-03-11", "cost": { "input": 0.15, "output": 0.65 @@ -157052,7 +157985,7 @@ "attachment": false, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-01", + "release_date": "2025-01-01", "last_updated": "2025-01", "cost": { "input": 0.25, @@ -157084,10 +158017,10 @@ "attachment": false, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-12", + "release_date": "2025-12-01", "last_updated": "2025-12", "cost": { - "input": 0.05, + "input": 0.045, "output": 0.15 }, "type": "chat" @@ -157137,8 +158070,8 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -157151,8 +158084,8 @@ "release_date": "2024-07-23", "last_updated": "2024-07-23", "cost": { - "input": 0.03, - "output": 0.05 + "input": 0.22, + "output": 0.22 }, "type": "chat" }, @@ -157169,8 +158102,8 @@ ] }, "limit": { - "context": 131072, - "output": 16384 + "context": 128000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -157183,8 +158116,8 @@ "release_date": "2024-07-23", "last_updated": "2024-07-23", "cost": { - "input": 0.4, - "output": 0.4 + "input": 0.72, + "output": 0.72 }, "type": "chat" }, @@ -157416,6 +158349,74 @@ }, "type": "chat" }, + { + "id": "stepfun/step-3.7-flash", + "name": "Step 3.7 Flash", + "display_name": "Step 3.7 Flash", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-05-28", + "last_updated": "2026-05-28", + "cost": { + "input": 0.2, + "output": 1.15, + "cache_read": 0.04 + }, + "type": "chat" + }, + { + "id": "stepfun/step-3.5-flash", + "name": "StepFun 3.5 Flash", + "display_name": "StepFun 3.5 Flash", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262114, + "output": 262114 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": false, + "open_weights": false, + "knowledge": "2025-01", + "release_date": "2026-01-29", + "last_updated": "2026-02-13", + "cost": { + "input": 0.09, + "output": 0.3, + "cache_write": 0.02 + }, + "type": "chat" + }, { "id": "amazon/nova-2-lite", "name": "Nova 2 Lite", @@ -157446,7 +158447,8 @@ "last_updated": "2024-12-01", "cost": { "input": 0.3, - "output": 2.5 + "output": 2.5, + "cache_read": 0.075 }, "type": "chat" }, @@ -157466,19 +158468,15 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2024-04", + "release_date": "2024-04-01", "last_updated": "2024-04", - "cost": { - "input": 0.02, - "output": 0 - }, "type": "chat" }, { @@ -157615,8 +158613,8 @@ "attachment": false, "open_weights": true, "knowledge": "2025-01", - "release_date": "2025-01", - "last_updated": "2025-01", + "release_date": "2025-01-01", + "last_updated": "2026-01-19", "cost": { "input": 0.06, "output": 0.4, @@ -157689,8 +158687,9 @@ "release_date": "2025-09-30", "last_updated": "2025-09-30", "cost": { - "input": 0.45, - "output": 1.8 + "input": 0.6, + "output": 2.2, + "cache_read": 0.11 }, "type": "chat" }, @@ -157707,8 +158706,8 @@ ] }, "limit": { - "context": 202752, - "output": 120000 + "context": 131000, + "output": 40000 }, "temperature": true, "tool_call": true, @@ -157733,9 +158732,9 @@ "release_date": "2025-12-22", "last_updated": "2025-12-22", "cost": { - "input": 0.43, - "output": 1.75, - "cache_read": 0.08 + "input": 2.25, + "output": 2.75, + "cache_read": 2.25 }, "type": "chat" }, @@ -157754,7 +158753,7 @@ }, "limit": { "context": 66000, - "output": 66000 + "output": 16000 }, "temperature": true, "tool_call": true, @@ -157769,7 +158768,8 @@ "last_updated": "2025-08-11", "cost": { "input": 0.6, - "output": 1.8 + "output": 1.8, + "cache_read": 0.11 }, "type": "chat" }, @@ -157801,7 +158801,7 @@ "open_weights": false, "knowledge": "2024-10", "release_date": "2025-09-30", - "last_updated": "2025-09-30", + "last_updated": "2025-12-08", "cost": { "input": 0.3, "output": 0.9, @@ -157838,11 +158838,12 @@ }, "attachment": false, "open_weights": false, + "knowledge": "2025-04", "release_date": "2026-03-13", - "last_updated": "2026-03-13", + "last_updated": "2026-01-19", "cost": { "input": 0.07, - "output": 0.39999999999999997 + "output": 0.4 }, "type": "chat" }, @@ -157873,7 +158874,7 @@ "attachment": true, "open_weights": false, "release_date": "2026-04-01", - "last_updated": "2026-04-03", + "last_updated": "2026-04-01", "cost": { "input": 1.2, "output": 4, @@ -157895,7 +158896,7 @@ }, "limit": { "context": 202800, - "output": 131072 + "output": 131100 }, "temperature": true, "tool_call": true, @@ -157917,7 +158918,7 @@ "attachment": false, "open_weights": true, "release_date": "2026-02-12", - "last_updated": "2026-02-19", + "last_updated": "2026-02-11", "cost": { "input": 1, "output": 3.2, @@ -157955,7 +158956,7 @@ "attachment": false, "open_weights": false, "release_date": "2026-03-15", - "last_updated": "2026-03-17", + "last_updated": "2026-03-16", "cost": { "input": 1.2, "output": 4, @@ -157976,8 +158977,8 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 128000, + "output": 96000 }, "temperature": true, "tool_call": true, @@ -157998,7 +158999,8 @@ "last_updated": "2025-07-28", "cost": { "input": 0.6, - "output": 2.2 + "output": 2.2, + "cache_read": 0.11 }, "type": "chat" }, @@ -158031,7 +159033,8 @@ "last_updated": "2025-07-28", "cost": { "input": 0.2, - "output": 1.1 + "output": 1.1, + "cache_read": 0.03 }, "type": "chat" }, @@ -158050,8 +159053,8 @@ ] }, "limit": { - "context": 202752, - "output": 202752 + "context": 202800, + "output": 64000 }, "temperature": true, "tool_call": true, @@ -158067,7 +159070,7 @@ "attachment": true, "open_weights": false, "release_date": "2026-04-07", - "last_updated": "2026-04-16", + "last_updated": "2026-03-27", "cost": { "input": 1.4, "output": 4.4, @@ -158117,7 +159120,7 @@ }, "limit": { "context": 128000, - "output": 8192 + "output": 100000 }, "temperature": true, "tool_call": true, @@ -158180,19 +159183,15 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2024-01", + "release_date": "2024-01-01", "last_updated": "2024-01", - "cost": { - "input": 0.12, - "output": 0 - }, "type": "chat" }, { @@ -158211,19 +159210,15 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2024-09", + "release_date": "2024-09-01", "last_updated": "2024-09", - "cost": { - "input": 0.18, - "output": 0 - }, "type": "chat" }, { @@ -158242,19 +159237,15 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2024-03", + "release_date": "2024-03-01", "last_updated": "2024-03", - "cost": { - "input": 0.12, - "output": 0 - }, "type": "chat" }, { @@ -158273,7 +159264,7 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -158282,10 +159273,6 @@ "open_weights": false, "release_date": "2025-05-20", "last_updated": "2025-05-20", - "cost": { - "input": 0.06, - "output": 0 - }, "type": "chat" }, { @@ -158331,7 +159318,7 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -158340,10 +159327,6 @@ "open_weights": false, "release_date": "2025-05-20", "last_updated": "2025-05-20", - "cost": { - "input": 0.02, - "output": 0 - }, "type": "chat" }, { @@ -158389,19 +159372,15 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2024-09", + "release_date": "2024-09-01", "last_updated": "2024-09", - "cost": { - "input": 0.18, - "output": 0 - }, "type": "chat" }, { @@ -158420,19 +159399,15 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2024-03", + "release_date": "2024-03-01", "last_updated": "2024-03", - "cost": { - "input": 0.12, - "output": 0 - }, "type": "chat" }, { @@ -158638,7 +159613,7 @@ }, "limit": { "context": 32768, - "output": 32768 + "output": 65536 }, "temperature": true, "tool_call": false, @@ -158649,10 +159624,11 @@ "open_weights": false, "knowledge": "2025-01", "release_date": "2025-03-20", - "last_updated": "2025-03-20", + "last_updated": "2025-08-26", "cost": { "input": 0.3, - "output": 2.5 + "output": 2.5, + "cache_read": 0.03 }, "type": "imageGeneration" }, @@ -158683,6 +159659,41 @@ "last_updated": "2026-03-23", "type": "chat" }, + { + "id": "google/gemini-3.1-flash-image", + "name": "Gemini 3.1 Flash Image (Nano Banana 2)", + "display_name": "Gemini 3.1 Flash Image (Nano Banana 2)", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "temperature": true, + "tool_call": false, + "reasoning": { + "supported": true, + "default": true + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-05-28", + "last_updated": "2026-05-28", + "cost": { + "input": 0.5, + "output": 3, + "cache_read": 0.05 + }, + "type": "chat" + }, { "id": "google/gemini-3.1-flash-lite-preview", "name": "Gemini 3.1 Flash Lite Preview", @@ -158714,13 +159725,13 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-01", "release_date": "2026-03-03", - "last_updated": "2026-03-06", + "last_updated": "2026-03-03", "cost": { "input": 0.25, "output": 1.5, - "cache_read": 0.025, - "cache_write": 1 + "cache_read": 0.03 }, "type": "chat" }, @@ -158751,10 +159762,10 @@ "attachment": true, "open_weights": false, "release_date": "2026-04-02", - "last_updated": "2026-04-03", + "last_updated": "2026-04-02", "cost": { "input": 0.14, - "output": 0.39999999999999997 + "output": 0.4 }, "type": "chat" }, @@ -158801,19 +159812,15 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2024-08", + "release_date": "2024-08-01", "last_updated": "2024-08", - "cost": { - "input": 0.03, - "output": 0 - }, "type": "chat" }, { @@ -158832,7 +159839,7 @@ }, "limit": { "context": 1000000, - "output": 64000 + "output": 65000 }, "temperature": true, "tool_call": true, @@ -158914,8 +159921,9 @@ }, "attachment": true, "open_weights": false, - "release_date": "2026-02-19", - "last_updated": "2026-02-24", + "knowledge": "2025-01", + "release_date": "2025-11-18", + "last_updated": "2026-02-19", "cost": { "input": 2, "output": 12, @@ -158954,8 +159962,9 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-01", "release_date": "2026-05-07", - "last_updated": "2026-05-08", + "last_updated": "2026-05-07", "cost": { "input": 0.25, "output": 1.5, @@ -158971,8 +159980,6 @@ "input": [ "text", "image", - "audio", - "video", "pdf" ], "output": [ @@ -159106,10 +160113,11 @@ "attachment": true, "open_weights": false, "release_date": "2026-04-02", - "last_updated": "2026-04-03", + "last_updated": "2026-04-02", "cost": { - "input": 0.13, - "output": 0.39999999999999997 + "input": 0.15, + "output": 0.6, + "cache_read": 0.015 }, "type": "chat" }, @@ -159215,8 +160223,9 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-01", "release_date": "2026-05-19", - "last_updated": "2026-05-21", + "last_updated": "2026-05-19", "cost": { "input": 1.5, "output": 9, @@ -159259,8 +160268,6 @@ "input": [ "text", "image", - "video", - "audio", "pdf" ], "output": [ @@ -159338,19 +160345,16 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, + "knowledge": "2025-05", "release_date": "2025-05-20", "last_updated": "2025-05-20", - "cost": { - "input": 0.15, - "output": 0 - }, "type": "embedding" }, { @@ -159396,11 +160400,12 @@ "attachment": false, "open_weights": false, "knowledge": "2025-03", - "release_date": "2025-09", + "release_date": "2025-09-01", "last_updated": "2025-09", "cost": { "input": 2, - "output": 120 + "output": 12, + "cache_read": 0.2 }, "type": "chat" }, @@ -159447,19 +160452,15 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false }, "attachment": false, "open_weights": false, - "release_date": "2024-03", + "release_date": "2024-03-01", "last_updated": "2024-03", - "cost": { - "input": 0.03, - "output": 0 - }, "type": "chat" }, { @@ -159493,11 +160494,13 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-01", "release_date": "2026-02-26", - "last_updated": "2026-03-06", + "last_updated": "2026-02-26", "cost": { "input": 0.5, - "output": 3 + "output": 3, + "cache_read": 0.05 }, "type": "imageGeneration" }, @@ -159750,7 +160753,7 @@ "open_weights": false, "knowledge": "2024-10", "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "last_updated": "2025-10-06", "cost": { "input": 15, "output": 120 @@ -159790,7 +160793,7 @@ "cost": { "input": 1.25, "output": 10, - "cache_read": 0.13 + "cache_read": 0.125 }, "type": "chat" }, @@ -159843,12 +160846,12 @@ "attachment": true, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "cost": { "input": 1.75, "output": 14, - "cache_read": 0.18 + "cache_read": 0.175 }, "type": "chat" }, @@ -159868,7 +160871,7 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -159877,10 +160880,6 @@ "open_weights": false, "release_date": "2024-01-25", "last_updated": "2024-01-25", - "cost": { - "input": 0.02, - "output": 0 - }, "type": "embedding" }, { @@ -159929,8 +160928,9 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-08-31", "release_date": "2026-03-05", - "last_updated": "2026-03-06", + "last_updated": "2026-03-05", "cost": { "input": 30, "output": 180 @@ -159953,7 +160953,7 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -159962,10 +160962,6 @@ "open_weights": false, "release_date": "2024-01-25", "last_updated": "2024-01-25", - "cost": { - "input": 0.13, - "output": 0 - }, "type": "embedding" }, { @@ -160015,8 +161011,8 @@ "attachment": true, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2025-12-11", + "last_updated": "2025-12-11", "cost": { "input": 21, "output": 168 @@ -160041,7 +161037,7 @@ "context": 200000, "output": 100000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -160065,7 +161061,7 @@ "open_weights": false, "knowledge": "2024-10", "release_date": "2025-04-16", - "last_updated": "2025-04-16", + "last_updated": "2025-06-10", "cost": { "input": 20, "output": 80 @@ -160096,7 +161092,7 @@ "attachment": false, "open_weights": false, "knowledge": "2023-09", - "release_date": "2025-01", + "release_date": "2025-03-12", "last_updated": "2025-01", "cost": { "input": 0.15, @@ -160152,8 +161148,9 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-08-31", "release_date": "2026-03-05", - "last_updated": "2026-03-06", + "last_updated": "2026-03-05", "cost": { "input": 2.5, "output": 15, @@ -160208,8 +161205,9 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-12-01", "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "last_updated": "2026-04-23", "cost": { "input": 5, "output": 30, @@ -160231,7 +161229,7 @@ }, "limit": { "context": 131072, - "output": 131072 + "output": 131000 }, "temperature": true, "tool_call": true, @@ -160250,8 +161248,9 @@ "release_date": "2025-08-05", "last_updated": "2025-08-05", "cost": { - "input": 0.1, - "output": 0.5 + "input": 0.35, + "output": 0.75, + "cache_read": 0.25 }, "type": "chat" }, @@ -160303,12 +161302,12 @@ "attachment": true, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2025-11-19", + "last_updated": "2025-11-13", "cost": { "input": 1.25, "output": 10, - "cache_read": 0.13 + "cache_read": 0.125 }, "type": "chat" }, @@ -160360,12 +161359,12 @@ "attachment": true, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-05-16", - "last_updated": "2025-05-16", + "release_date": "2025-11-12", + "last_updated": "2025-11-13", "cost": { "input": 0.25, "output": 2, - "cache_read": 0.03 + "cache_read": 0.025 }, "type": "chat" }, @@ -160385,7 +161384,7 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -160394,10 +161393,6 @@ "open_weights": false, "release_date": "2022-12-15", "last_updated": "2022-12-15", - "cost": { - "input": 0.1, - "output": 0 - }, "type": "embedding" }, { @@ -160414,7 +161409,7 @@ }, "limit": { "context": 131072, - "output": 32768 + "output": 8192 }, "temperature": true, "tool_call": true, @@ -160433,8 +161428,8 @@ "release_date": "2025-08-05", "last_updated": "2025-08-05", "cost": { - "input": 0.07, - "output": 0.3 + "input": 0.05, + "output": 0.2 }, "type": "chat" }, @@ -160456,7 +161451,7 @@ "context": 200000, "output": 100000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -160536,10 +161531,11 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-08-31", "release_date": "2026-03-17", "last_updated": "2026-03-17", "cost": { - "input": 0.19999999999999998, + "input": 0.2, "output": 1.25, "cache_read": 0.02 }, @@ -160592,8 +161588,9 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-08-31", "release_date": "2026-02-24", - "last_updated": "2026-02-24", + "last_updated": "2026-02-05", "cost": { "input": 1.75, "output": 14, @@ -160632,8 +161629,9 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-12-01", "release_date": "2026-04-24", - "last_updated": "2026-04-24", + "last_updated": "2026-04-23", "cost": { "input": 30, "output": 180 @@ -160664,8 +161662,8 @@ "attachment": false, "open_weights": false, "knowledge": "2021-09", - "release_date": "2023-03-01", - "last_updated": "2023-03-01", + "release_date": "2023-05-28", + "last_updated": "2023-11-06", "cost": { "input": 0.5, "output": 1.5 @@ -160699,12 +161697,12 @@ "attachment": true, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-08-07", + "release_date": "2025-12-11", "last_updated": "2025-08-07", "cost": { "input": 1.75, "output": 14, - "cache_read": 0.18 + "cache_read": 0.175 }, "type": "chat" }, @@ -160732,7 +161730,7 @@ "attachment": false, "open_weights": false, "knowledge": "2021-09", - "release_date": "2023-03-01", + "release_date": "2023-09-28", "last_updated": "2023-03-01", "cost": { "input": 1.5, @@ -160751,8 +161749,7 @@ "pdf" ], "output": [ - "text", - "image" + "text" ] }, "limit": { @@ -160789,12 +161786,12 @@ "attachment": true, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-08-07", + "release_date": "2025-11-12", "last_updated": "2025-08-07", "cost": { "input": 1.25, "output": 10, - "cache_read": 0.13 + "cache_read": 0.125 }, "type": "chat" }, @@ -160846,8 +161843,8 @@ "attachment": true, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-12", - "last_updated": "2025-12", + "release_date": "2025-12-18", + "last_updated": "2025-12-11", "cost": { "input": 1.75, "output": 14, @@ -160903,12 +161900,12 @@ "attachment": true, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-08-07", - "last_updated": "2025-08-07", + "release_date": "2025-11-12", + "last_updated": "2025-11-13", "cost": { "input": 1.25, "output": 10, - "cache_read": 0.13 + "cache_read": 0.125 }, "type": "chat" }, @@ -160940,9 +161937,9 @@ "release_date": "2024-12-01", "last_updated": "2024-12-01", "cost": { - "input": 0.08, + "input": 0.075, "output": 0.3, - "cache_read": 0.04 + "cache_read": 0.037 }, "type": "chat" }, @@ -160994,6 +161991,7 @@ }, "attachment": true, "open_weights": false, + "knowledge": "2025-08-31", "release_date": "2026-03-17", "last_updated": "2026-03-17", "cost": { @@ -161022,7 +162020,7 @@ "context": 400000, "output": 128000 }, - "temperature": false, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -161052,12 +162050,12 @@ "attachment": true, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-08-07", + "release_date": "2025-11-12", "last_updated": "2025-08-07", "cost": { "input": 1.25, "output": 10, - "cache_read": 0.13 + "cache_read": 0.125 }, "type": "chat" }, @@ -161785,7 +162783,7 @@ "attachment": false, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-09", + "release_date": "2025-09-01", "last_updated": "2025-09", "cost": { "input": 0.25, @@ -161820,7 +162818,7 @@ "attachment": false, "open_weights": false, "knowledge": "2024-10", - "release_date": "2025-10", + "release_date": "2025-09-01", "last_updated": "2025-10", "cost": { "input": 0.25, @@ -161883,14 +162881,53 @@ "last_updated": "2024-10", "type": "chat" }, + { + "id": "minimax/minimax-m3", + "name": "MiniMax M3", + "display_name": "MiniMax M3", + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 1000000 + }, + "temperature": true, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "attachment": true, + "open_weights": false, + "release_date": "2026-05-31", + "last_updated": "2026-06-01", + "cost": { + "input": 0.3, + "output": 1.2, + "cache_read": 0.06 + }, + "type": "chat" + }, { "id": "minimax/minimax-m2.7-highspeed", "name": "MiniMax M2.7 High Speed", "display_name": "MiniMax M2.7 High Speed", "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" @@ -161961,12 +162998,12 @@ "open_weights": false, "knowledge": "2024-10", "release_date": "2025-10-27", - "last_updated": "2025-10-27", + "last_updated": "2025-12-23", "cost": { "input": 0.3, "output": 1.2, "cache_read": 0.03, - "cache_write": 0.38 + "cache_write": 0.375 }, "type": "chat" }, @@ -161976,9 +163013,7 @@ "display_name": "Minimax M2.7", "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" @@ -162030,8 +163065,8 @@ ] }, "limit": { - "context": 262114, - "output": 262114 + "context": 205000, + "output": 205000 }, "temperature": true, "tool_call": true, @@ -162051,10 +163086,10 @@ "release_date": "2025-10-27", "last_updated": "2025-10-27", "cost": { - "input": 0.27, - "output": 1.15, + "input": 0.3, + "output": 1.2, "cache_read": 0.03, - "cache_write": 0.38 + "cache_write": 0.375 }, "type": "chat" }, @@ -162088,7 +163123,7 @@ "attachment": false, "open_weights": false, "release_date": "2026-02-12", - "last_updated": "2026-02-19", + "last_updated": "2026-02-12", "cost": { "input": 0.3, "output": 1.2, @@ -162110,8 +163145,8 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 204800, + "output": 131000 }, "temperature": true, "tool_call": true, @@ -162127,7 +163162,7 @@ "attachment": false, "open_weights": false, "release_date": "2026-02-12", - "last_updated": "2026-03-13", + "last_updated": "2026-02-13", "cost": { "input": 0.6, "output": 2.4, @@ -162167,7 +163202,7 @@ "input": 0.3, "output": 2.4, "cache_read": 0.03, - "cache_write": 0.38 + "cache_write": 0.375 }, "type": "chat" } @@ -165081,38 +166116,6 @@ }, "type": "chat" }, - { - "id": "gemini-2.5-flash-tts", - "name": "Gemini 2.5 Flash TTS", - "display_name": "Gemini 2.5 Flash TTS", - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 32768, - "output": 16384 - }, - "temperature": true, - "tool_call": false, - "reasoning": { - "supported": false - }, - "attachment": false, - "open_weights": false, - "knowledge": "2025-01", - "release_date": "2025-09-30", - "last_updated": "2025-12-10", - "cost": { - "input": 0.5, - "output": 10 - }, - "type": "chat" - }, { "id": "gemini-2.5-flash-image", "name": "Gemini 2.5 Flash Image", @@ -166082,7 +167085,7 @@ ] }, "limit": { - "context": 65536, + "context": 131072, "output": 32768 }, "temperature": true, @@ -168071,6 +169074,11 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": false, "open_weights": true, "knowledge": "2025-06", @@ -175690,6 +176698,11 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "attachment": false, "open_weights": true, "release_date": "2026-04-20", @@ -181280,6 +182293,28 @@ }, "type": "chat" }, + { + "id": "gemma4:12b", + "name": "gemma4 12b", + "display_name": "gemma4 12b", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 8192 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "type": "chat" + }, { "id": "gemma4:26b", "name": "gemma4 26b", @@ -182690,6 +183725,28 @@ }, "type": "chat" }, + { + "id": "lfm2.5", + "name": "lfm2.5", + "display_name": "lfm2.5", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 2048 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "type": "chat" + }, { "id": "lfm2.5-thinking", "name": "lfm2.5-thinking", @@ -182712,6 +183769,28 @@ }, "type": "chat" }, + { + "id": "lfm2.5:8b", + "name": "lfm2.5 8b", + "display_name": "lfm2.5 8b", + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 8192 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "type": "chat" + }, { "id": "llama-guard3", "name": "llama-guard3", @@ -184888,6 +185967,7 @@ "context": 1048576, "output": 131072 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -184937,6 +186017,7 @@ "context": 262144, "output": 65536 }, + "temperature": true, "tool_call": true, "reasoning": { "supported": true, @@ -195852,6 +196933,20 @@ }, "type": "chat" }, + { + "id": "deepseek/deepseek-ocr", + "name": "DeepSeek-OCR", + "display_name": "DeepSeek-OCR", + "limit": { + "context": 8192, + "output": 8192 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "type": "chat" + }, { "id": "deepseek/deepseek-v3.2-exp", "name": "Deepseek V3.2 Exp", @@ -201889,6 +202984,61 @@ }, "type": "chat" }, + { + "id": "step-3.7-flash-free", + "name": "step-3.7-flash-free", + "display_name": "step-3.7-flash-free", + "modalities": { + "input": [ + "text", + "image" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "cost": { + "input": 0, + "output": 0, + "cache_read": 0 + }, + "type": "chat" + }, + { + "id": "hy3-preview", + "name": "hy3-preview", + "display_name": "hy3-preview", + "modalities": { + "input": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + }, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "cost": { + "input": 0.17, + "output": 0.566661, + "cache_read": 0.051 + }, + "type": "chat" + }, { "id": "minimax-m3", "name": "minimax-m3", @@ -201973,6 +203123,38 @@ }, "type": "chat" }, + { + "id": "claude-opus-4-8-think", + "name": "claude-opus-4-8-think", + "display_name": "claude-opus-4-8-think", + "modalities": { + "input": [ + "text", + "image" + ] + }, + "limit": { + "context": 200000, + "output": 200000 + }, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true, + "default_enabled": true + } + }, + "cost": { + "input": 5, + "output": 25, + "cache_read": 0.5 + }, + "type": "chat" + }, { "id": "qwen3.7-max", "name": "qwen3.7-max", @@ -202390,9 +203572,9 @@ } }, "cost": { - "input": 0.478, - "output": 0.956, - "cache_read": 0.004302 + "input": 0.464, + "output": 0.928, + "cache_read": 0.003851 }, "type": "chat" }, @@ -208410,29 +209592,6 @@ }, "type": "chat" }, - { - "id": "wan2.2-t2v-plus", - "name": "wan2.2-t2v-plus", - "display_name": "wan2.2-t2v-plus", - "modalities": { - "input": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "cost": { - "input": 2, - "output": 0 - }, - "type": "chat" - }, { "id": "wan2.2-i2v-plus", "name": "wan2.2-i2v-plus", @@ -209624,49 +210783,6 @@ }, "type": "chat" }, - { - "id": "veo3.1", - "name": "veo3.1", - "display_name": "veo3.1", - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "cost": { - "input": 200, - "output": 200, - "cache_read": 200 - }, - "type": "chat" - }, - { - "id": "veo-2.0-generate-001", - "name": "veo-2.0-generate-001", - "display_name": "veo-2.0-generate-001", - "modalities": { - "input": [ - "video" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "cost": { - "input": 2, - "output": 2, - "cache_read": 0 - }, - "type": "chat" - }, { "id": "imagen-4.0", "name": "imagen-4.0", @@ -210133,32 +211249,6 @@ }, "type": "chat" }, - { - "id": "veo-3.0-generate-preview", - "name": "veo-3.0-generate-preview", - "display_name": "veo-3.0-generate-preview", - "modalities": { - "input": [ - "text", - "image", - "video" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "cost": { - "input": 2, - "output": 2, - "cache_read": 0 - }, - "type": "chat" - }, { "id": "deepseek-ocr", "name": "deepseek-ocr", @@ -210394,33 +211484,42 @@ "type": "chat" }, { - "id": "DeepSeek-OCR", - "name": "DeepSeek-OCR", - "display_name": "DeepSeek-OCR", + "id": "veo-3.1-generate-preview", + "name": "veo-3.1-generate-preview", + "display_name": "veo-3.1-generate-preview", "modalities": { "input": [ "text", - "image" + "image", + "video" ] }, "limit": { - "context": 8000, - "output": 8000 + "context": 8192, + "output": 8192 }, "tool_call": false, "reasoning": { "supported": false }, "cost": { - "input": 0.02, - "output": 0.02 + "input": 2, + "output": 2, + "cache_read": 0 }, "type": "chat" }, { - "id": "alicloud-kimi-k2-instruct", - "name": "alicloud-kimi-k2-instruct", - "display_name": "alicloud-kimi-k2-instruct", + "id": "veo-3.1-fast-generate-preview", + "name": "veo-3.1-fast-generate-preview", + "display_name": "veo-3.1-fast-generate-preview", + "modalities": { + "input": [ + "text", + "image", + "video" + ] + }, "limit": { "context": 8192, "output": 8192 @@ -210430,15 +211529,15 @@ "supported": false }, "cost": { - "input": 0.548, - "output": 2.192 + "input": 2, + "output": 0 }, "type": "chat" }, { - "id": "veo-3.1-generate-preview", - "name": "veo-3.1-generate-preview", - "display_name": "veo-3.1-generate-preview", + "id": "veo-3.0-generate-preview", + "name": "veo-3.0-generate-preview", + "display_name": "veo-3.0-generate-preview", "modalities": { "input": [ "text", @@ -210462,16 +211561,33 @@ "type": "chat" }, { - "id": "veo-3.1-fast-generate-preview", - "name": "veo-3.1-fast-generate-preview", - "display_name": "veo-3.1-fast-generate-preview", + "id": "DeepSeek-OCR", + "name": "DeepSeek-OCR", + "display_name": "DeepSeek-OCR", "modalities": { "input": [ "text", - "image", - "video" + "image" ] }, + "limit": { + "context": 8000, + "output": 8000 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "cost": { + "input": 0.02, + "output": 0.02 + }, + "type": "chat" + }, + { + "id": "alicloud-kimi-k2-instruct", + "name": "alicloud-kimi-k2-instruct", + "display_name": "alicloud-kimi-k2-instruct", "limit": { "context": 8192, "output": 8192 @@ -210481,15 +211597,15 @@ "supported": false }, "cost": { - "input": 2, - "output": 0 + "input": 0.548, + "output": 2.192 }, "type": "chat" }, { - "id": "gpt-4.1-mini", - "name": "gpt-4.1-mini", - "display_name": "gpt-4.1-mini", + "id": "aihubmix-router", + "name": "aihubmix-router", + "display_name": "aihubmix-router", "modalities": { "input": [ "text", @@ -210497,10 +211613,10 @@ ] }, "limit": { - "context": 1047576, - "output": 1047576 + "context": 8192, + "output": 8192 }, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, @@ -210512,9 +211628,9 @@ "type": "chat" }, { - "id": "aihubmix-router", - "name": "aihubmix-router", - "display_name": "aihubmix-router", + "id": "gpt-4.1-mini", + "name": "gpt-4.1-mini", + "display_name": "gpt-4.1-mini", "modalities": { "input": [ "text", @@ -210522,10 +211638,10 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1047576, + "output": 1047576 }, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, @@ -211280,6 +212396,36 @@ }, "type": "chat" }, + { + "id": "ernie-5.0-thinking-preview", + "name": "ernie-5.0-thinking-preview", + "display_name": "ernie-5.0-thinking-preview", + "modalities": { + "input": [ + "text" + ] + }, + "limit": { + "context": 183000, + "output": 183000 + }, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "cost": { + "input": 0.822, + "output": 3.288, + "cache_read": 0.822 + }, + "type": "chat" + }, { "id": "inclusionAI/Ling-1T", "name": "inclusionAI/Ling-1T", @@ -211333,17 +212479,63 @@ "type": "chat" }, { - "id": "ernie-5.0-thinking-preview", - "name": "ernie-5.0-thinking-preview", - "display_name": "ernie-5.0-thinking-preview", + "id": "inclusionAI/Ling-flash-2.0", + "name": "inclusionAI/Ling-flash-2.0", + "display_name": "inclusionAI/Ling-flash-2.0", "modalities": { "input": [ "text" ] }, "limit": { - "context": 183000, - "output": 183000 + "context": 8192, + "output": 8192 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "cost": { + "input": 0.136, + "output": 0.544 + }, + "type": "chat" + }, + { + "id": "inclusionAI/Ling-mini-2.0", + "name": "inclusionAI/Ling-mini-2.0", + "display_name": "inclusionAI/Ling-mini-2.0", + "modalities": { + "input": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "cost": { + "input": 0.068, + "output": 0.272 + }, + "type": "chat" + }, + { + "id": "inclusionAI/Ring-flash-2.0", + "name": "inclusionAI/Ring-flash-2.0", + "display_name": "inclusionAI/Ring-flash-2.0", + "modalities": { + "input": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 }, "tool_call": true, "reasoning": { @@ -211356,16 +212548,15 @@ } }, "cost": { - "input": 0.822, - "output": 3.288, - "cache_read": 0.822 + "input": 0.136, + "output": 0.544 }, "type": "chat" }, { - "id": "doubao-seedream-4-0", - "name": "doubao-seedream-4-0", - "display_name": "doubao-seedream-4-0", + "id": "jina-deepsearch-v1", + "name": "jina-deepsearch-v1", + "display_name": "jina-deepsearch-v1", "modalities": { "input": [ "text", @@ -211373,27 +212564,33 @@ ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1000000, + "output": 1000000 }, "tool_call": false, "reasoning": { - "supported": false + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "cost": { - "input": 2, - "output": 0, - "cache_read": 0 + "input": 0.05, + "output": 0.05 }, - "type": "imageGeneration" + "type": "chat" }, { - "id": "embedding-v1", - "name": "embedding-v1", - "display_name": "embedding-v1", + "id": "jina-embeddings-v4", + "name": "jina-embeddings-v4", + "display_name": "jina-embeddings-v4", "modalities": { "input": [ - "text" + "text", + "image" ] }, "limit": { @@ -211405,15 +212602,15 @@ "supported": false }, "cost": { - "input": 0.068, - "output": 0 + "input": 0.05, + "output": 0.05 }, "type": "embedding" }, { - "id": "ernie-4.5-turbo-latest", - "name": "ernie-4.5-turbo-latest", - "display_name": "ernie-4.5-turbo-latest", + "id": "jina-reranker-v3", + "name": "jina-reranker-v3", + "display_name": "jina-reranker-v3", "modalities": { "input": [ "text", @@ -211421,72 +212618,71 @@ ] }, "limit": { - "context": 135000, - "output": 135000 + "context": 131000, + "output": 131000 }, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "cost": { - "input": 0.11, - "output": 0.44 + "input": 0.05, + "output": 0.05 }, - "type": "chat" + "type": "rerank" }, { - "id": "glm-4.5-x", - "name": "glm-4.5-x", - "display_name": "glm-4.5-x", + "id": "llama-4-maverick", + "name": "llama-4-maverick", + "display_name": "llama-4-maverick", "modalities": { "input": [ - "text" + "text", + "image" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 1048576, + "output": 1048576 }, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "cost": { - "input": 2.2, - "output": 8.91, - "cache_read": 0.44 + "input": 0.2, + "output": 0.2 }, "type": "chat" }, { - "id": "gme-qwen2-vl-2b-instruct", - "name": "gme-qwen2-vl-2b-instruct", - "display_name": "gme-qwen2-vl-2b-instruct", + "id": "llama-4-scout", + "name": "llama-4-scout", + "display_name": "llama-4-scout", "modalities": { "input": [ "text", - "image", - "video" + "image" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 131000, + "output": 131000 }, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "cost": { - "input": 0.138, - "output": 0.138 + "input": 0.2, + "output": 0.2 }, - "type": "embedding" + "type": "chat" }, { - "id": "gte-rerank-v2", - "name": "gte-rerank-v2", - "display_name": "gte-rerank-v2", + "id": "qwen-image", + "name": "qwen-image", + "display_name": "qwen-image", "modalities": { "input": [ "text", @@ -211502,15 +212698,16 @@ "supported": false }, "cost": { - "input": 0.11, - "output": 0.11 + "input": 2, + "output": 0, + "cache_read": 0 }, - "type": "rerank" + "type": "imageGeneration" }, { - "id": "bce-reranker-base", - "name": "bce-reranker-base", - "display_name": "bce-reranker-base", + "id": "qwen-image-edit", + "name": "qwen-image-edit", + "display_name": "qwen-image-edit", "modalities": { "input": [ "text", @@ -211526,15 +212723,16 @@ "supported": false }, "cost": { - "input": 0.068, - "output": 0 + "input": 2, + "output": 0, + "cache_read": 0 }, - "type": "rerank" + "type": "imageGeneration" }, { - "id": "codex-mini-latest", - "name": "codex-mini-latest", - "display_name": "codex-mini-latest", + "id": "qwen-image-max", + "name": "qwen-image-max", + "display_name": "qwen-image-max", "modalities": { "input": [ "text", @@ -211550,62 +212748,62 @@ "supported": false }, "cost": { - "input": 1.5, - "output": 6, - "cache_read": 0.375 + "input": 2, + "output": 0, + "cache_read": 0 }, - "type": "chat" + "type": "imageGeneration" }, { - "id": "inclusionAI/Ling-flash-2.0", - "name": "inclusionAI/Ling-flash-2.0", - "display_name": "inclusionAI/Ling-flash-2.0", + "id": "qwen-mt-plus", + "name": "qwen-mt-plus", + "display_name": "qwen-mt-plus", "modalities": { "input": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 16000, + "output": 16000 }, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "cost": { - "input": 0.136, - "output": 0.544 + "input": 0.492, + "output": 1.476 }, "type": "chat" }, { - "id": "inclusionAI/Ling-mini-2.0", - "name": "inclusionAI/Ling-mini-2.0", - "display_name": "inclusionAI/Ling-mini-2.0", + "id": "qwen-mt-turbo", + "name": "qwen-mt-turbo", + "display_name": "qwen-mt-turbo", "modalities": { "input": [ "text" ] }, "limit": { - "context": 8192, - "output": 8192 + "context": 16000, + "output": 16000 }, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "cost": { - "input": 0.068, - "output": 0.272 + "input": 0.192, + "output": 0.534912 }, "type": "chat" }, { - "id": "inclusionAI/Ring-flash-2.0", - "name": "inclusionAI/Ring-flash-2.0", - "display_name": "inclusionAI/Ring-flash-2.0", + "id": "qwen3-embedding-0.6b", + "name": "qwen3-embedding-0.6b", + "display_name": "qwen3-embedding-0.6b", "modalities": { "input": [ "text" @@ -211615,60 +212813,46 @@ "context": 8192, "output": 8192 }, - "tool_call": true, + "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "cost": { - "input": 0.136, - "output": 0.544 + "input": 0.068, + "output": 0 }, - "type": "chat" + "type": "embedding" }, { - "id": "jina-deepsearch-v1", - "name": "jina-deepsearch-v1", - "display_name": "jina-deepsearch-v1", + "id": "qwen3-embedding-4b", + "name": "qwen3-embedding-4b", + "display_name": "qwen3-embedding-4b", "modalities": { "input": [ - "text", - "image" + "text" ] }, "limit": { - "context": 1000000, - "output": 1000000 + "context": 8192, + "output": 8192 }, "tool_call": false, "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } + "supported": false }, "cost": { - "input": 0.05, - "output": 0.05 + "input": 0.068, + "output": 0.068 }, - "type": "chat" + "type": "embedding" }, { - "id": "jina-embeddings-v4", - "name": "jina-embeddings-v4", - "display_name": "jina-embeddings-v4", + "id": "qwen3-embedding-8b", + "name": "qwen3-embedding-8b", + "display_name": "qwen3-embedding-8b", "modalities": { "input": [ - "text", - "image" + "text" ] }, "limit": { @@ -211680,15 +212864,15 @@ "supported": false }, "cost": { - "input": 0.05, - "output": 0.05 + "input": 0.068, + "output": 0 }, "type": "embedding" }, { - "id": "jina-reranker-v3", - "name": "jina-reranker-v3", - "display_name": "jina-reranker-v3", + "id": "qwen3-reranker-0.6b", + "name": "qwen3-reranker-0.6b", + "display_name": "qwen3-reranker-0.6b", "modalities": { "input": [ "text", @@ -211696,23 +212880,23 @@ ] }, "limit": { - "context": 131000, - "output": 131000 + "context": 16000, + "output": 16000 }, "tool_call": false, "reasoning": { "supported": false }, "cost": { - "input": 0.05, - "output": 0.05 + "input": 0.11, + "output": 0.11 }, "type": "rerank" }, { - "id": "llama-4-maverick", - "name": "llama-4-maverick", - "display_name": "llama-4-maverick", + "id": "qwen3-reranker-4b", + "name": "qwen3-reranker-4b", + "display_name": "qwen3-reranker-4b", "modalities": { "input": [ "text", @@ -211720,23 +212904,23 @@ ] }, "limit": { - "context": 1048576, - "output": 1048576 + "context": 8192, + "output": 8192 }, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "cost": { - "input": 0.2, - "output": 0.2 + "input": 0.11, + "output": 0.11 }, - "type": "chat" + "type": "rerank" }, { - "id": "llama-4-scout", - "name": "llama-4-scout", - "display_name": "llama-4-scout", + "id": "qwen3-reranker-8b", + "name": "qwen3-reranker-8b", + "display_name": "qwen3-reranker-8b", "modalities": { "input": [ "text", @@ -211744,29 +212928,23 @@ ] }, "limit": { - "context": 131000, - "output": 131000 + "context": 8192, + "output": 8192 }, - "tool_call": true, + "tool_call": false, "reasoning": { "supported": false }, "cost": { - "input": 0.2, - "output": 0.2 + "input": 0.11, + "output": 0.11 }, - "type": "chat" + "type": "rerank" }, { - "id": "qwen-image", - "name": "qwen-image", - "display_name": "qwen-image", - "modalities": { - "input": [ - "text", - "image" - ] - }, + "id": "tao-8k", + "name": "tao-8k", + "display_name": "tao-8k", "limit": { "context": 8192, "output": 8192 @@ -211776,16 +212954,15 @@ "supported": false }, "cost": { - "input": 2, - "output": 0, - "cache_read": 0 + "input": 0.068, + "output": 0.068 }, - "type": "imageGeneration" + "type": "embedding" }, { - "id": "qwen-image-edit", - "name": "qwen-image-edit", - "display_name": "qwen-image-edit", + "id": "doubao-seedream-4-0", + "name": "doubao-seedream-4-0", + "display_name": "doubao-seedream-4-0", "modalities": { "input": [ "text", @@ -211808,13 +212985,12 @@ "type": "imageGeneration" }, { - "id": "qwen-image-max", - "name": "qwen-image-max", - "display_name": "qwen-image-max", + "id": "embedding-v1", + "name": "embedding-v1", + "display_name": "embedding-v1", "modalities": { "input": [ - "text", - "image" + "text" ] }, "limit": { @@ -211826,85 +213002,39 @@ "supported": false }, "cost": { - "input": 2, - "output": 0, - "cache_read": 0 - }, - "type": "imageGeneration" - }, - { - "id": "qwen-mt-plus", - "name": "qwen-mt-plus", - "display_name": "qwen-mt-plus", - "modalities": { - "input": [ - "text" - ] - }, - "limit": { - "context": 16000, - "output": 16000 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "cost": { - "input": 0.492, - "output": 1.476 + "input": 0.068, + "output": 0 }, - "type": "chat" + "type": "embedding" }, { - "id": "qwen-mt-turbo", - "name": "qwen-mt-turbo", - "display_name": "qwen-mt-turbo", + "id": "ernie-4.5-turbo-latest", + "name": "ernie-4.5-turbo-latest", + "display_name": "ernie-4.5-turbo-latest", "modalities": { "input": [ - "text" + "text", + "image" ] }, "limit": { - "context": 16000, - "output": 16000 + "context": 135000, + "output": 135000 }, - "tool_call": false, + "tool_call": true, "reasoning": { "supported": false }, "cost": { - "input": 0.192, - "output": 0.534912 + "input": 0.11, + "output": 0.44 }, "type": "chat" }, { - "id": "qwen3-embedding-0.6b", - "name": "qwen3-embedding-0.6b", - "display_name": "qwen3-embedding-0.6b", - "modalities": { - "input": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "cost": { - "input": 0.068, - "output": 0 - }, - "type": "embedding" - }, - { - "id": "qwen3-embedding-4b", - "name": "qwen3-embedding-4b", - "display_name": "qwen3-embedding-4b", + "id": "glm-4.5-x", + "name": "glm-4.5-x", + "display_name": "glm-4.5-x", "modalities": { "input": [ "text" @@ -211919,18 +213049,21 @@ "supported": false }, "cost": { - "input": 0.068, - "output": 0.068 + "input": 2.2, + "output": 8.91, + "cache_read": 0.44 }, - "type": "embedding" + "type": "chat" }, { - "id": "qwen3-embedding-8b", - "name": "qwen3-embedding-8b", - "display_name": "qwen3-embedding-8b", + "id": "gme-qwen2-vl-2b-instruct", + "name": "gme-qwen2-vl-2b-instruct", + "display_name": "gme-qwen2-vl-2b-instruct", "modalities": { "input": [ - "text" + "text", + "image", + "video" ] }, "limit": { @@ -211942,15 +213075,15 @@ "supported": false }, "cost": { - "input": 0.068, - "output": 0 + "input": 0.138, + "output": 0.138 }, "type": "embedding" }, { - "id": "qwen3-reranker-0.6b", - "name": "qwen3-reranker-0.6b", - "display_name": "qwen3-reranker-0.6b", + "id": "gte-rerank-v2", + "name": "gte-rerank-v2", + "display_name": "gte-rerank-v2", "modalities": { "input": [ "text", @@ -211958,8 +213091,8 @@ ] }, "limit": { - "context": 16000, - "output": 16000 + "context": 8192, + "output": 8192 }, "tool_call": false, "reasoning": { @@ -211972,9 +213105,9 @@ "type": "rerank" }, { - "id": "qwen3-reranker-4b", - "name": "qwen3-reranker-4b", - "display_name": "qwen3-reranker-4b", + "id": "bce-reranker-base", + "name": "bce-reranker-base", + "display_name": "bce-reranker-base", "modalities": { "input": [ "text", @@ -211990,15 +213123,15 @@ "supported": false }, "cost": { - "input": 0.11, - "output": 0.11 + "input": 0.068, + "output": 0 }, "type": "rerank" }, { - "id": "qwen3-reranker-8b", - "name": "qwen3-reranker-8b", - "display_name": "qwen3-reranker-8b", + "id": "codex-mini-latest", + "name": "codex-mini-latest", + "display_name": "codex-mini-latest", "modalities": { "input": [ "text", @@ -212014,28 +213147,11 @@ "supported": false }, "cost": { - "input": 0.11, - "output": 0.11 - }, - "type": "rerank" - }, - { - "id": "tao-8k", - "name": "tao-8k", - "display_name": "tao-8k", - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "cost": { - "input": 0.068, - "output": 0.068 + "input": 1.5, + "output": 6, + "cache_read": 0.375 }, - "type": "embedding" + "type": "chat" }, { "id": "jina-clip-v2", @@ -212108,31 +213224,6 @@ }, "type": "embedding" }, - { - "id": "gpt-4o-search-preview", - "name": "gpt-4o-search-preview", - "display_name": "gpt-4o-search-preview", - "modalities": { - "input": [ - "text", - "image" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "cost": { - "input": 2.5, - "output": 10, - "cache_read": 1.25 - }, - "type": "chat" - }, { "id": "DeepSeek-R1", "name": "DeepSeek-R1", @@ -212167,6 +213258,31 @@ }, "type": "chat" }, + { + "id": "gpt-4o-search-preview", + "name": "gpt-4o-search-preview", + "display_name": "gpt-4o-search-preview", + "modalities": { + "input": [ + "text", + "image" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + }, + "tool_call": true, + "reasoning": { + "supported": false + }, + "cost": { + "input": 2.5, + "output": 10, + "cache_read": 1.25 + }, + "type": "chat" + }, { "id": "gpt-4o-mini-search-preview", "name": "gpt-4o-mini-search-preview", @@ -212591,6 +213707,58 @@ }, "type": "chat" }, + { + "id": "gemini-embedding-001", + "name": "gemini-embedding-001", + "display_name": "gemini-embedding-001", + "modalities": { + "input": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "cost": { + "input": 0.15, + "output": 0.15 + }, + "type": "embedding" + }, + { + "id": "gpt-oss-120b", + "name": "gpt-oss-120b", + "display_name": "gpt-oss-120b", + "modalities": { + "input": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "tool_call": true, + "reasoning": { + "supported": true, + "default": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, + "cost": { + "input": 0.18, + "output": 0.9 + }, + "type": "chat" + }, { "id": "Qwen2-VL-72B-Instruct", "name": "Qwen2-VL-72B-Instruct", @@ -212660,58 +213828,6 @@ }, "type": "chat" }, - { - "id": "gemini-embedding-001", - "name": "gemini-embedding-001", - "display_name": "gemini-embedding-001", - "modalities": { - "input": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "cost": { - "input": 0.15, - "output": 0.15 - }, - "type": "embedding" - }, - { - "id": "gpt-oss-120b", - "name": "gpt-oss-120b", - "display_name": "gpt-oss-120b", - "modalities": { - "input": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "extra_capabilities": { - "reasoning": { - "supported": true - } - }, - "cost": { - "input": 0.18, - "output": 0.9 - }, - "type": "chat" - }, { "id": "Qwen/Qwen3-30B-A3B", "name": "Qwen/Qwen3-30B-A3B", @@ -212794,8 +213910,8 @@ } }, "cost": { - "input": 0.28, - "output": 2.8 + "input": 0.16, + "output": 0.64 }, "type": "chat" }, @@ -213475,6 +214591,30 @@ }, "type": "chat" }, + { + "id": "veo-2.0-generate-001", + "name": "veo-2.0-generate-001", + "display_name": "veo-2.0-generate-001", + "modalities": { + "input": [ + "video" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "tool_call": false, + "reasoning": { + "supported": false + }, + "cost": { + "input": 2, + "output": 2, + "cache_read": 0 + }, + "type": "chat" + }, { "id": "o1-preview", "name": "o1-preview", @@ -218457,24 +219597,6 @@ }, "type": "chat" }, - { - "id": "deepseek-ai/DeepSeek-V2.5", - "name": "deepseek-ai/DeepSeek-V2.5", - "display_name": "deepseek-ai/DeepSeek-V2.5", - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "cost": { - "input": 0.16, - "output": 0.32 - }, - "type": "chat" - }, { "id": "imagen-4.0-generate-preview-05-20", "name": "imagen-4.0-generate-preview-05-20", @@ -219987,17 +221109,9 @@ } }, { - "id": "veo-3", - "name": "veo-3", - "display_name": "veo-3", - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ] - }, + "id": "deepseek-ai/DeepSeek-V2.5", + "name": "deepseek-ai/DeepSeek-V2.5", + "display_name": "deepseek-ai/DeepSeek-V2.5", "limit": { "context": 8192, "output": 8192 @@ -220007,9 +221121,8 @@ "supported": false }, "cost": { - "input": 2, - "output": 2, - "cache_read": 0 + "input": 0.16, + "output": 0.32 }, "type": "chat" }, @@ -221117,33 +222230,6 @@ }, "type": "chat" }, - { - "id": "veo3", - "name": "veo3", - "display_name": "veo3", - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "cost": { - "input": 2, - "output": 2, - "cache_read": 0 - }, - "type": "chat" - }, { "id": "yi-large", "name": "yi-large", @@ -222361,29 +223447,6 @@ }, "type": "chat" }, - { - "id": "arcee-ai/spotlight", - "name": "Arcee AI: Spotlight", - "display_name": "Arcee AI: Spotlight", - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65537 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "imageGeneration" - }, { "id": "arcee-ai/trinity-large-thinking", "name": "Arcee AI: Trinity Large Thinking", @@ -222454,30 +223517,6 @@ }, "type": "chat" }, - { - "id": "baidu/ernie-4.5-vl-28b-a3b", - "name": "Baidu: ERNIE 4.5 VL 28B A3B", - "display_name": "Baidu: ERNIE 4.5 VL 28B A3B", - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 30000, - "output": 8000 - }, - "tool_call": true, - "reasoning": { - "supported": true, - "default": true - }, - "type": "imageGeneration" - }, { "id": "baidu/ernie-4.5-vl-424b-a47b", "name": "Baidu: ERNIE 4.5 VL 424B A47B", @@ -223907,8 +224946,8 @@ ] }, "limit": { - "context": 262144, - "output": 16384 + "context": 256000, + "output": 8192 }, "temperature": true, "tool_call": true, @@ -224853,7 +225892,7 @@ }, "limit": { "context": 196608, - "output": 131072 + "output": 196608 }, "temperature": true, "tool_call": true, @@ -225490,8 +226529,8 @@ ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 262142, + "output": 262142 }, "tool_call": true, "reasoning": { @@ -225595,28 +226634,6 @@ }, "type": "chat" }, - { - "id": "nousresearch/hermes-2-pro-llama-3-8b", - "name": "NousResearch: Hermes 2 Pro - Llama-3 8B", - "display_name": "NousResearch: Hermes 2 Pro - Llama-3 8B", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": false, - "reasoning": { - "supported": false - }, - "type": "chat" - }, { "id": "nousresearch/hermes-3-llama-3.1-405b", "name": "Nous: Hermes 3 405B Instruct", @@ -226127,28 +227144,6 @@ }, "type": "chat" }, - { - "id": "openai/gpt-4-1106-preview", - "name": "OpenAI: GPT-4 Turbo (older v1106)", - "display_name": "OpenAI: GPT-4 Turbo (older v1106)", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, { "id": "openai/gpt-4-turbo", "name": "OpenAI: GPT-4 Turbo", @@ -228804,7 +229799,7 @@ }, "limit": { "context": 40960, - "output": 20000 + "output": 16384 }, "tool_call": true, "reasoning": { @@ -229596,7 +230591,7 @@ }, "limit": { "context": 262144, - "output": 81920 + "output": 262144 }, "tool_call": true, "reasoning": { @@ -229739,8 +230734,8 @@ ] }, "limit": { - "context": 262140, - "output": 262140 + "context": 131072, + "output": 131072 }, "tool_call": true, "reasoning": { @@ -230051,28 +231046,6 @@ }, "type": "chat" }, - { - "id": "sao10k/l3-euryale-70b", - "name": "Sao10k: Llama 3 Euryale 70B v2.1", - "display_name": "Sao10k: Llama 3 Euryale 70B v2.1", - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - }, - "tool_call": true, - "reasoning": { - "supported": false - }, - "type": "chat" - }, { "id": "sao10k/l3-lunaris-8b", "name": "Sao10K: Llama 3 8B Lunaris", @@ -230277,6 +231250,11 @@ "supported": true, "default": true }, + "extra_capabilities": { + "reasoning": { + "supported": true + } + }, "type": "chat" }, { @@ -239164,7 +240142,7 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -239173,10 +240151,6 @@ "open_weights": false, "release_date": "2024-01-25", "last_updated": "2024-01-25", - "cost": { - "input": 0.13, - "output": 0 - }, "type": "embedding" }, { @@ -239195,7 +240169,7 @@ "context": 8192, "output": 1536 }, - "temperature": false, + "temperature": true, "tool_call": false, "reasoning": { "supported": false @@ -239204,10 +240178,6 @@ "open_weights": false, "release_date": "2024-01-25", "last_updated": "2024-01-25", - "cost": { - "input": 0.02, - "output": 0 - }, "type": "embedding" }, { @@ -239838,7 +240808,7 @@ }, "limit": { "context": 256000, - "output": 16384 + "output": 256000 }, "temperature": true, "tool_call": true, @@ -239847,12 +240817,13 @@ "default": true }, "attachment": true, - "open_weights": true, + "open_weights": false, "release_date": "2026-05-28", "last_updated": "2026-05-28", "cost": { - "input": 0, - "output": 0 + "input": 0.2, + "output": 1.15, + "cache_read": 0.04 }, "type": "chat" }, @@ -239953,7 +240924,12 @@ }, "tool_call": false, "reasoning": { - "supported": false + "supported": true + }, + "extra_capabilities": { + "reasoning": { + "supported": true + } }, "attachment": false, "open_weights": false, From 52a92e32cd3e03692a91641ade58126c1f6f9c2d Mon Sep 17 00:00:00 2001 From: zerob13 Date: Mon, 8 Jun 2026 19:30:57 +0800 Subject: [PATCH 4/4] release: prepare v1.0.6-beta.1 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5632bdc59..031d21c51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v1.0.6-beta.1 (2026-06-08) +- Added S3-compatible cloud backup sync for more flexible cross-device data backup +- Improved Dify knowledge import compatibility with the latest `retrieval_model` schema +- Refreshed bundled provider and ACP registry data for current model and agent availability +- 新增 S3 兼容云备份同步,让跨设备数据备份更灵活 +- 优化 Dify 知识库导入兼容性,适配最新 `retrieval_model` schema +- 刷新内置 Provider 与 ACP registry 数据,更新模型和 Agent 可用性 + ## v1.0.5 (2026-06-05) - Added scheduled tasks, agent progress todos, session transfer, session tape memory, and remote `/agent` commands for more persistent agent workflows - Added OpenAI-compatible video generation, tool result image previews, remote image delivery, and richer TTS model routing controls diff --git a/package.json b/package.json index 0db8c768c..0a5bb949d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "DeepChat", - "version": "1.0.5", + "version": "1.0.6-beta.1", "description": "DeepChat,一个简单易用的 Agent 客户端", "main": "./out/main/index.js", "author": "ThinkInAIXYZ",