From ee84d69c29467b95511654df46f6b708e82cf52b Mon Sep 17 00:00:00 2001 From: coso Date: Wed, 29 Apr 2026 17:37:53 +0800 Subject: [PATCH] chore: release v1.23.0 --- .gitignore | 2 + RELEASE_NOTES.md | 28 +- docs/aiprompts/commands.md | 4 +- docs/aiprompts/components.md | 6 +- docs/aiprompts/credential-pool.md | 11 +- docs/aiprompts/database.md | 85 +- docs/aiprompts/hooks.md | 6 +- docs/aiprompts/overview.md | 7 +- docs/aiprompts/providers.md | 20 +- docs/aiprompts/quality-workflow.md | 4 +- docs/aiprompts/workspace.md | 2 +- docs/design/settings-redesign.md | 2 +- docs/develop/ai-agent-development-guide.md | 4 +- docs/exec-plans/README.md | 1 + docs/exec-plans/limenext-progress.md | 18 +- .../multimodal-runtime-contract-plan.md | 139 ++ .../old-session-open-performance-plan.md | 63 + .../upstream-runtime-alignment-progress.md | 4 +- .../implementation-progress-report.md | 6 +- .../p0-phase1-implementation-plan.md | 6 +- docs/ops.md | 5 +- docs/plugins/README.md | 11 + docs/plugins/antigravity-provider.md | 1243 ---------- docs/plugins/claude-provider.md | 1255 ---------- docs/plugins/codex-provider.md | 1806 --------------- docs/plugins/droid-provider.md | 2029 ----------------- docs/plugins/gemini-provider.md | 1535 ------------- docs/plugins/kiro-provider.md | 1255 ---------- docs/research/warp/README.md | 173 ++ ...agent-harness-and-multimodal-management.md | 256 +++ docs/research/warp/architecture-breakdown.md | 227 ++ docs/research/warp/architecture-diagrams.md | 264 +++ docs/research/warp/borrowable-patterns.md | 145 ++ .../research/warp/claudecode-compatibility.md | 143 ++ docs/research/warp/flowcharts.md | 175 ++ docs/research/warp/lime-gap-analysis.md | 228 ++ docs/research/warp/sequences.md | 195 ++ docs/roadmap/task/README.md | 2 +- docs/roadmap/task/architecture.md | 2 +- docs/roadmap/task/diagrams.md | 10 +- docs/roadmap/task/model-routing.md | 2 +- docs/roadmap/task/oem-and-local-policy.md | 6 +- docs/roadmap/task/rollout-plan.md | 2 +- docs/roadmap/task/runtime-integration.md | 2 +- docs/roadmap/warp/README.md | 204 ++ docs/roadmap/warp/acceptance.md | 191 ++ docs/roadmap/warp/capability-matrix.md | 147 ++ docs/roadmap/warp/contract-schema.md | 131 ++ docs/roadmap/warp/evolution-guide.md | 290 +++ docs/roadmap/warp/implementation-plan.md | 415 ++++ docs/roadmap/warp/runtime-fact-map.md | 136 ++ docs/test/README.md | 8 +- docs/test/integration-tests.md | 210 +- docs/test/test-cases/provider-tests.md | 322 +-- docs/test/unit-tests.md | 27 +- lime-e2e-layout-anchor-after-fix.png | Bin 0 -> 358504 bytes lime-e2e-layout-anchor-final.png | Bin 0 -> 358744 bytes lime-hello-layout-after-fix.png | Bin 0 -> 300126 bytes package-lock.json | 4 +- package.json | 5 +- packages/lime-cli-npm/README.md | 2 +- packages/lime-cli-npm/package.json | 2 +- public/oem-runtime-config.js | 2 +- scripts/check-modality-runtime-contracts.mjs | 555 +++++ scripts/playwright-login/README.md | 123 - scripts/playwright-login/browser-context.js | 292 --- scripts/playwright-login/index.js | 384 ---- scripts/playwright-login/oauth-handler.js | 249 -- scripts/playwright-login/package.json | 23 - .../test/browser-config.property.test.js | 256 --- .../test/oauth-url.property.test.js | 250 -- scripts/release-updater-manifest.test.mjs | 22 +- scripts/translate_all.py | 43 - src-tauri/Cargo.lock | 34 +- src-tauri/Cargo.toml | 4 +- .../crates/agent/src/aster_state_support.rs | 4 +- .../crates/agent/src/credential_bridge.rs | 54 +- src-tauri/crates/agent/src/lib.rs | 3 +- src-tauri/crates/agent/src/session_store.rs | 348 ++- .../crates/agent/src/subagent_scheduler.rs | 6 +- src-tauri/crates/config/src/lib.rs | 6 +- .../crates/config/src/observer/events.rs | 12 - src-tauri/crates/core/src/config/export.rs | 257 +-- src-tauri/crates/core/src/config/import.rs | 249 +- src-tauri/crates/core/src/config/tests.rs | 189 +- src-tauri/crates/core/src/config/types.rs | 62 +- src-tauri/crates/core/src/database/README.md | 2 +- .../src/database/agent_session_repository.rs | 35 +- .../crates/core/src/database/dao/agent.rs | 191 +- .../core/src/database/dao/agent_timeline.rs | 142 +- src-tauri/crates/core/src/database/schema.rs | 2 +- src-tauri/crates/core/src/models/mod.rs | 9 +- .../crates/core/src/models/model_registry.rs | 2 +- .../core/src/models/provider_pool_model.rs | 1172 ---------- .../core/src/models/runtime_provider_model.rs | 251 ++ .../src/plugin/examples/credential_monitor.rs | 320 --- .../crates/core/src/plugin/examples/mod.rs | 6 +- src-tauri/crates/core/src/processor/error.rs | 10 +- src-tauri/crates/core/src/tray_format.rs | 20 - src-tauri/crates/core/src/tray_menu_meta.rs | 33 +- src-tauri/crates/core/src/tray_state.rs | 225 +- src-tauri/crates/media-runtime/src/lib.rs | 26 + .../crates/providers/src/providers/error.rs | 66 +- .../providers/src/providers/openai_custom.rs | 180 +- src-tauri/crates/scheduler/src/executor.rs | 4 +- src-tauri/crates/server/src/handlers/api.rs | 79 +- .../src/handlers/api_key_provider_utils.rs | 144 -- .../server/src/handlers/credentials_api.rs | 272 --- src-tauri/crates/server/src/handlers/mod.rs | 3 - .../server/src/handlers/provider_calls.rs | 78 +- .../crates/server/src/handlers/websocket.rs | 22 +- src-tauri/crates/server/src/lib.rs | 28 +- .../crates/services/src/ai_summary_service.rs | 14 +- .../services/src/api_key_provider_service.rs | 207 +- .../services/src/provider_type_mapping.rs | 137 +- .../services/src/voice_config_service.rs | 17 +- .../crates/skills/src/lime_llm_provider.rs | 80 +- src-tauri/src/agent/aster_agent.rs | 32 + src-tauri/src/app/runner.rs | 2 - src-tauri/src/commands/asr_cmd.rs | 44 +- .../command_api/provider_api.rs | 2 +- .../command_api/runtime_api.rs | 155 +- src-tauri/src/commands/aster_agent_cmd/dto.rs | 13 + .../aster_agent_cmd/image_skill_launch.rs | 30 + src-tauri/src/commands/aster_agent_cmd/mod.rs | 2 +- .../src/commands/aster_agent_cmd/tests.rs | 42 + .../tool_runtime/creation_tools.rs | 148 ++ src-tauri/src/commands/media_task_cmd.rs | 451 ++++ src-tauri/src/commands/memory_search_cmd.rs | 53 +- src-tauri/src/commands/mod.rs | 1 + .../commands/modality_runtime_contracts.rs | 309 +++ src-tauri/src/commands/model_registry_cmd.rs | 2 +- src-tauri/src/config/tests.rs | 189 +- .../dev_bridge/dispatcher/agent_sessions.rs | 20 + src-tauri/src/plugin/README.md | 3 +- .../services/runtime_evidence_pack_service.rs | 585 ++++- .../services/runtime_replay_case_service.rs | 407 +++- src-tauri/src/tray/format.rs | 4 +- src-tauri/src/tray/manager.rs | 6 +- src-tauri/src/tray/menu.rs | 62 +- src-tauri/src/tray/menu_handler.rs | 46 +- src-tauri/src/tray/state.rs | 3 +- src-tauri/tauri.conf.headless.json | 3 +- src-tauri/tauri.conf.json | 3 +- src/components/AppSidebar.test.tsx | 38 + src/components/AppSidebar.tsx | 24 +- src/components/README.md | 2 +- .../agent/chat/AgentChatWorkspace.tsx | 121 +- .../components/AgentThreadTimeline.test.tsx | 87 + .../chat/components/AgentThreadTimeline.tsx | 123 +- .../InputbarVisionCapabilityNotice.test.tsx | 2 +- .../chat/components/MarkdownRenderer.test.tsx | 6 +- .../chat/components/MessageList.test.tsx | 119 +- .../agent/chat/components/MessageList.tsx | 135 +- .../components/TaskCenterTabStrip.test.tsx | 10 +- .../chat/components/TaskCenterTabStrip.tsx | 26 +- .../agent/chat/hooks/agentChatHistory.test.ts | 108 + .../agent/chat/hooks/agentChatHistory.ts | 61 +- .../agent/chat/hooks/agentChatStorage.test.ts | 35 + .../agent/chat/hooks/agentChatStorage.ts | 31 + .../chat/hooks/agentRuntimeAdapter.test.ts | 26 + .../agent/chat/hooks/agentRuntimeAdapter.ts | 31 + .../chat/hooks/agentSessionRefresh.test.ts | 12 +- .../agent/chat/hooks/agentSessionRefresh.ts | 4 +- .../hooks/agentSessionScopedStorage.test.ts | 28 + .../chat/hooks/agentSessionScopedStorage.ts | 160 +- .../chat/hooks/agentStreamRuntimeHandler.ts | 26 +- .../hooks/agentStreamTurnEventBinding.test.ts | 6 +- .../chat/hooks/agentStreamTurnEventBinding.ts | 2 +- .../agent/chat/hooks/skillCommand.ts | 40 +- .../hooks/useAgentRuntimeSyncEffects.test.tsx | 42 + .../chat/hooks/useAgentRuntimeSyncEffects.ts | 19 +- .../agent/chat/hooks/useAgentSession.ts | 640 +++++- .../chat/hooks/useAsterAgentChat.test.tsx | 301 ++- .../agent/chat/hooks/useAsterAgentChat.ts | 1 + .../agentRuntimeErrorPresentation.test.ts | 10 +- .../utils/agentRuntimeErrorPresentation.ts | 6 +- .../clawWorkspaceProviderSelection.test.ts | 2 +- .../agent/chat/utils/threadTimelineView.ts | 284 ++- .../agent/chat/workspace/imageSkillLaunch.ts | 13 + .../ApiKeyProviderSection.helpers.ts | 0 .../ApiKeyProviderSection.test.ts | 2 +- .../ApiKeyProviderSection.tsx | 2 +- .../ApiKeyProviderSection.ui.test.tsx | 0 .../ImportExportDialog.tsx | 2 +- .../ModelAddPanel.tsx | 2 +- .../ModelProviderList.test.ts | 0 .../ModelProviderList.tsx | 2 +- .../ModelProviderList.utils.ts | 0 .../ProviderSetting.test.ts | 2 +- .../ProviderSetting.tsx | 2 +- .../ProviderSetting.ui.test.tsx | 0 .../api-key => api-key-provider}/README.md | 2 +- .../connectionTestTypes.ts | 2 +- .../api-key => api-key-provider}/index.ts | 2 +- .../providerConfigUtils.test.ts | 2 +- .../providerConfigUtils.ts | 2 +- .../providerTypeMapping.test.ts | 2 +- .../providerTypeMapping.ts | 4 +- src/components/image-gen/useImageGen.ts | 10 +- .../input-kit/ModelSelector.test.tsx | 2 +- .../openclaw/OpenClawConfigurePage.tsx | 2 +- .../settings-v2/agent/image-gen/index.tsx | 2 +- .../agent/providers/index.test.tsx | 26 +- .../settings-v2/agent/providers/index.tsx | 25 +- .../settings-v2/agent/video-gen/index.tsx | 2 +- .../settings-v2/agent/voice/index.tsx | 4 +- src/hooks/useOemCloudAccess.test.tsx | 148 ++ src/hooks/useOemCloudAccess.ts | 6 +- src/hooks/useOemLimeHubProviderSync.test.tsx | 5 +- src/i18n/patches/en.json | 165 -- src/i18n/patches/zh.json | 165 -- .../agentRuntime/commandManifest.generated.ts | 2 +- src/lib/api/agentRuntime/sessionClient.ts | 28 +- src/lib/api/agentRuntime/types.ts | 23 +- src/lib/api/modelRegistry.ts | 4 +- .../governance/agentRuntimeCommandSchema.json | 2 +- src/lib/governance/legacySurfaceCatalog.json | 308 ++- .../governance/legacySurfaceCatalog.test.ts | 27 + .../governance/modalityCapabilityMatrix.json | 307 +++ .../governance/modalityRuntimeContracts.json | 415 ++++ src/lib/oemCloudLoginLauncher.test.ts | 216 ++ src/lib/oemCloudLoginLauncher.ts | 240 +- src/lib/oemCloudStartupLogin.test.ts | 16 + src/lib/oemCloudStartupLogin.ts | 6 +- src/lib/oemLimeHubProvider.test.ts | 12 +- src/lib/oemLimeHubProvider.ts | 28 +- src/lib/tauri-mock/core.ts | 28 +- vite.config.ts | 1 - 229 files changed, 12201 insertions(+), 15939 deletions(-) create mode 100644 docs/exec-plans/multimodal-runtime-contract-plan.md create mode 100644 docs/exec-plans/old-session-open-performance-plan.md create mode 100644 docs/plugins/README.md delete mode 100644 docs/plugins/antigravity-provider.md delete mode 100644 docs/plugins/claude-provider.md delete mode 100644 docs/plugins/codex-provider.md delete mode 100644 docs/plugins/droid-provider.md delete mode 100644 docs/plugins/gemini-provider.md delete mode 100644 docs/plugins/kiro-provider.md create mode 100644 docs/research/warp/README.md create mode 100644 docs/research/warp/agent-harness-and-multimodal-management.md create mode 100644 docs/research/warp/architecture-breakdown.md create mode 100644 docs/research/warp/architecture-diagrams.md create mode 100644 docs/research/warp/borrowable-patterns.md create mode 100644 docs/research/warp/claudecode-compatibility.md create mode 100644 docs/research/warp/flowcharts.md create mode 100644 docs/research/warp/lime-gap-analysis.md create mode 100644 docs/research/warp/sequences.md create mode 100644 docs/roadmap/warp/README.md create mode 100644 docs/roadmap/warp/acceptance.md create mode 100644 docs/roadmap/warp/capability-matrix.md create mode 100644 docs/roadmap/warp/contract-schema.md create mode 100644 docs/roadmap/warp/evolution-guide.md create mode 100644 docs/roadmap/warp/implementation-plan.md create mode 100644 docs/roadmap/warp/runtime-fact-map.md create mode 100644 lime-e2e-layout-anchor-after-fix.png create mode 100644 lime-e2e-layout-anchor-final.png create mode 100644 lime-hello-layout-after-fix.png create mode 100755 scripts/check-modality-runtime-contracts.mjs delete mode 100644 scripts/playwright-login/README.md delete mode 100644 scripts/playwright-login/browser-context.js delete mode 100644 scripts/playwright-login/index.js delete mode 100644 scripts/playwright-login/oauth-handler.js delete mode 100644 scripts/playwright-login/package.json delete mode 100644 scripts/playwright-login/test/browser-config.property.test.js delete mode 100644 scripts/playwright-login/test/oauth-url.property.test.js delete mode 100644 src-tauri/crates/core/src/models/provider_pool_model.rs create mode 100644 src-tauri/crates/core/src/models/runtime_provider_model.rs delete mode 100644 src-tauri/crates/core/src/plugin/examples/credential_monitor.rs delete mode 100644 src-tauri/crates/server/src/handlers/api_key_provider_utils.rs delete mode 100644 src-tauri/crates/server/src/handlers/credentials_api.rs create mode 100644 src-tauri/src/commands/modality_runtime_contracts.rs create mode 100644 src/components/agent/chat/hooks/agentChatStorage.test.ts rename src/components/{provider-pool/api-key => api-key-provider}/ApiKeyProviderSection.helpers.ts (100%) rename src/components/{provider-pool/api-key => api-key-provider}/ApiKeyProviderSection.test.ts (99%) rename src/components/{provider-pool/api-key => api-key-provider}/ApiKeyProviderSection.tsx (99%) rename src/components/{provider-pool/api-key => api-key-provider}/ApiKeyProviderSection.ui.test.tsx (100%) rename src/components/{provider-pool/api-key => api-key-provider}/ImportExportDialog.tsx (99%) rename src/components/{provider-pool/api-key => api-key-provider}/ModelAddPanel.tsx (99%) rename src/components/{provider-pool/api-key => api-key-provider}/ModelProviderList.test.ts (100%) rename src/components/{provider-pool/api-key => api-key-provider}/ModelProviderList.tsx (98%) rename src/components/{provider-pool/api-key => api-key-provider}/ModelProviderList.utils.ts (100%) rename src/components/{provider-pool/api-key => api-key-provider}/ProviderSetting.test.ts (99%) rename src/components/{provider-pool/api-key => api-key-provider}/ProviderSetting.tsx (99%) rename src/components/{provider-pool/api-key => api-key-provider}/ProviderSetting.ui.test.tsx (100%) rename src/components/{provider-pool/api-key => api-key-provider}/README.md (98%) rename src/components/{provider-pool/api-key => api-key-provider}/connectionTestTypes.ts (78%) rename src/components/{provider-pool/api-key => api-key-provider}/index.ts (95%) rename src/components/{provider-pool/api-key => api-key-provider}/providerConfigUtils.test.ts (99%) rename src/components/{provider-pool/api-key => api-key-provider}/providerConfigUtils.ts (99%) rename src/components/{provider-pool/api-key => api-key-provider}/providerTypeMapping.test.ts (98%) rename src/components/{provider-pool/api-key => api-key-provider}/providerTypeMapping.ts (98%) create mode 100644 src/lib/governance/modalityCapabilityMatrix.json create mode 100644 src/lib/governance/modalityRuntimeContracts.json create mode 100644 src/lib/oemCloudLoginLauncher.test.ts diff --git a/.gitignore b/.gitignore index 50707e269..bf41e7bd9 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,8 @@ docs/roadmap/* !docs/roadmap/harness-engine/ !docs/roadmap/harness-engine/README.md !docs/roadmap/harness-engine/diagrams.md +!docs/roadmap/warp/ +!docs/roadmap/warp/*.md docs/gongzonghao/ docs/bussniss/ docs/oem/ diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d985fee6f..7f2da598a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,27 +1,27 @@ -## Lime v1.22.0 +## Lime v1.23.0 发布日期:`2026-04-29` ### 发布概览 -- 本次发布目标 tag 为 `v1.22.0`。 +- 本次发布目标 tag 为 `v1.23.0`。 - 本次发布聚焦稳定版 GitHub Release / R2 分发链路收口、lime-cli 独立产物发布、Provider / Credential 旧路径清退、云端用户中心商业边界收口,以及 Agent 会话恢复与模型选择体验稳定性。 - 本轮待递交内容覆盖 Rust 后端、Tauri 配置、发布工作流、release asset 脚本、Provider / Model / Credential 治理、前端 Workspace / Settings / Provider API Key 主路径、测试覆盖、版本锁文件与执行计划文档。 ### 重点更新 -#### 1. 版本号同步到 v1.22.0 +#### 1. 版本号同步到 v1.23.0 -- 应用版本已同步为 `1.22.0`: +- 应用版本已同步为 `1.23.0`: - `package.json` - `package-lock.json` - `src-tauri/Cargo.toml` - `src-tauri/Cargo.lock` - `src-tauri/tauri.conf.json` - `src-tauri/tauri.conf.headless.json` -- `packages/lime-cli-npm/package.json` 与 `packages/lime-cli-npm/README.md` 已同步到 `1.22.0`,保持 CLI wrapper 与桌面 release 版本一致。 -- 浏览器模式默认 mock 的 update current version 已同步为 `1.22.0`。 -- GitHub release asset staging 测试中的当前发布样例已同步到 `v1.22.0`。 +- `packages/lime-cli-npm/package.json` 与 `packages/lime-cli-npm/README.md` 已同步到 `1.23.0`,保持 CLI wrapper 与桌面 release 版本一致。 +- 浏览器模式默认 mock 的 update current version 已同步为 `1.23.0`。 +- GitHub release asset staging 测试中的当前发布样例已同步到 `v1.23.0`。 #### 2. 稳定版发布与 R2 分发链路 @@ -57,7 +57,7 @@ #### 6. 文档、治理与回归 - `docs/aiprompts/` 下 Provider、Credential Pool、Services、Hooks、Components、Overview 等导航文档同步当前 provider / credential / model registry 事实源。 -- `docs/content/03.providers/1.overview.md` 与 `src/components/provider-pool/api-key/README.md` 更新当前 Provider 配置入口说明。 +- `docs/content/03.providers/1.overview.md` 与 `src/components/api-key-provider/README.md` 更新当前 Provider 配置入口说明。 - `scripts/release-updater-manifest.test.mjs` 增加 GitHub release asset staging 覆盖,保护 macOS 同名 updater bundle 重命名逻辑。 - `src-tauri/proptest-regressions/` 已纳入本轮待递交范围,保留 property test 回归种子。 @@ -74,23 +74,19 @@ - 已通过: - `npm run verify:app-version` - `cargo fmt --manifest-path "src-tauri/Cargo.toml" --all` - - `cargo test --manifest-path "src-tauri/Cargo.toml"` — 1070 passed / 0 failed / 2 ignored - - `cargo clippy --manifest-path "src-tauri/Cargo.toml" --all-targets --all-features` + - `cargo test --manifest-path "src-tauri/Cargo.toml" --target-dir "src-tauri/target/codex-release-v123"` — 1085 passed / 0 failed / 2 ignored + - `cargo clippy --manifest-path "src-tauri/Cargo.toml" --target-dir "src-tauri/target/codex-release-v123" --all-targets --all-features` - `npm run lint` - - `npx vitest run "src/components/agent/chat/hooks/useAsterAgentChat.test.tsx"` - `npm test` — 43 个 Vitest smart 批次通过 - `npm run test:contracts` - `git diff --check` -- `cargo test` 通过,当前存在 1 条预存 warning: - - `write_auxiliary_runtime_projection_fixture` 的 `dead_code` -- `cargo clippy` 通过,当前存在 6 条 warning: +- `cargo clippy` 通过,当前存在 5 条预存 warning: - `crates/services/src/aster_session_store.rs` 的 `manual_repeat_n` - `crates/skills/src/lime_llm_provider.rs` 的 2 处 `too_many_arguments` - `crates/agent/src/request_tool_policy.rs` 的 `too_many_arguments` - `crates/agent/src/session_execution_runtime.rs` 的 `needless_lifetimes` - - `src/services/runtime_evidence_pack_service.rs` 的 `dead_code` - GUI 主路径未额外执行 `npm run verify:gui-smoke`;本轮发布收口以版本、发布链路、Provider / Credential 治理和前端 / Rust 回归为主要风险覆盖。 --- -**完整变更**: `v1.21.0` -> `v1.22.0` +**完整变更**: `v1.22.0` -> `v1.23.0` diff --git a/docs/aiprompts/commands.md b/docs/aiprompts/commands.md index dc5dde429..0a4be097c 100644 --- a/docs/aiprompts/commands.md +++ b/docs/aiprompts/commands.md @@ -67,7 +67,7 @@ 它只允许读取 `src-tauri/resources/models/index.json` 的 `providers` 列表。无论是正式 Tauri 命令还是 DevBridge 开发链路,都不应再回退数据库或其它运行态缓存去“猜” provider 集合;资源异常时必须直接暴露错误,避免把索引损坏伪装成“只是没有模型”。 同理,聊天运行时初始化的 `aster_agent_init` 在浏览器 DevBridge 模式下也不能再被放进 `mockPriorityCommands`。只要桥接在线,它就必须优先读取后端真实 `provider_name / model_name`,让聊天入口拿到当前运行时模型。 -进一步地,围绕运行时模型解析的真相命令:`aster_agent_init`、`get_default_provider`、`get_provider_pool_overview`、`get_api_key_providers`、`get_model_registry`、`get_provider_alias_config`、`fetch_provider_models_auto`、`get_model_registry_provider_ids`,在浏览器 DevBridge 模式下如果桥接失败,必须直接抛错,不能再通过 `safeInvoke` 静默退回 mock;否则前端会把“后端未连上 / 命令失败”误显示成假的 Provider / 模型列表。 +进一步地,围绕运行时模型解析的真相命令:`aster_agent_init`、`get_default_provider`、`get_api_key_providers`、`get_model_registry`、`get_provider_alias_config`、`fetch_provider_models_auto`、`get_model_registry_provider_ids`,在浏览器 DevBridge 模式下如果桥接失败,必须直接抛错,不能再通过 `safeInvoke` 静默退回 mock;否则前端会把“后端未连上 / 命令失败”误显示成假的 Provider / 模型列表。旧 `get_provider_pool_overview` 属于凭证池命令面,已随凭证池退役,不得重新作为运行时模型解析事实源。 同时要明确,`aster_agent_init` 只负责初始化 Agent,并不保证已经完成 Provider 配置;当它未返回 `provider_name / model_name` 时,前端不得把本地硬编码默认值当作真实模型,而应继续回退到 `get_default_provider` + 已配置 Provider/模型注册表解析链,拿到当前工作区真正可用的 `provider/model`。 同一条约束也适用于 Prompt Cache 能力判断:运行时与前端都不得因为某个自定义 Provider “长得像 Anthropic 协议”就推断它支持官方 Anthropic Automatic Prompt Caching。当前事实源必须继续按 ProviderType 判断:`anthropic` 走自动缓存能力,`anthropic-compatible` 只保留显式 `cache_control` 语义;若上游没有实现 Automatic Prompt Cache,`cached_input_tokens` 为空不能直接归因到 Lime 没发字段。 @@ -324,7 +324,7 @@ Companion 桌宠链路同样遵循这条路径。当前主入口为 `src/lib/api Lime 主应用会在本地维护 `ws://127.0.0.1:45554/companion/pet` 的桌宠 companion 入口。前端如需感知桌宠连接状态,应继续通过 `companion-pet-status` 事件监听统一状态,不要在页面或 Hook 里自行直连本地 `WebSocket`。 -如果 companion 协议继续扩展,也应优先延续“Lime 做宿主、桌宠只收脱敏派生状态”的边界。例如 provider 概览相关能力,允许 Lime 通过 `companion_send_pet_command` 下发诸如 `pet.provider_overview` 这类脱敏摘要,并允许桌宠通过 `pet.open_provider_settings` 请求 Lime 聚焦主窗口并跳到 `设置 -> AI 服务商`,或通过 `pet.request_provider_overview_sync` 请求 Lime 立即重发最新的脱敏摘要;桌宠交互增强能力也应继续走这条主链,例如双击 / 三击桌宠后发出 `pet.request_pet_cheer`、`pet.request_pet_next_step`,或通过 `pet.request_chat_reply` 携带用户输入文本,请求 Lime 代为调用当前可聊天模型,再统一回写 `pet.show_bubble`;但不允许桌宠直接读取凭证文件、数据库或内部 `/v1/credentials/*` 完整凭证接口。 +如果 companion 协议继续扩展,也应优先延续“Lime 做宿主、桌宠只收脱敏派生状态”的边界。例如 provider 概览相关能力,允许 Lime 通过 `companion_send_pet_command` 下发诸如 `pet.provider_overview` 这类脱敏摘要,并允许桌宠通过 `pet.open_provider_settings` 请求 Lime 聚焦主窗口并跳到 `设置 -> AI 服务商`,或通过 `pet.request_provider_overview_sync` 请求 Lime 立即重发最新的脱敏摘要;桌宠交互增强能力也应继续走这条主链,例如双击 / 三击桌宠后发出 `pet.request_pet_cheer`、`pet.request_pet_next_step`,或通过 `pet.request_chat_reply` 携带用户输入文本,请求 Lime 代为调用当前可聊天模型,再统一回写 `pet.show_bubble`;但不允许桌宠直接读取凭证文件、数据库或已下线的完整凭证 HTTP 接口。 ## 命令契约的五个事实源 diff --git a/docs/aiprompts/components.md b/docs/aiprompts/components.md index 1bfffdd88..3ea3101bf 100644 --- a/docs/aiprompts/components.md +++ b/docs/aiprompts/components.md @@ -9,7 +9,7 @@ React 组件层,使用 TailwindCSS 和 shadcn/ui。 ``` src/components/ ├── ui/ # 基础 UI 组件 (shadcn/ui) -├── provider-pool/api-key/ # API Key Provider 设置(目录名保留历史路径) +├── api-key-provider/ # API Key Provider 设置 ├── flow-monitor/ # 流量监控 ├── general-chat/ # 兼容画布桥接(非对话主入口) ├── terminal/ # 已移除的历史终端页面路径(不要恢复) @@ -44,8 +44,8 @@ export function AppSidebar() { API Key Provider 设置组件,是当前设置页的 Provider 配置入口。 ```tsx -// src/components/provider-pool/api-key/ApiKeyProviderSection.tsx -import { ApiKeyProviderSection } from "@/components/provider-pool/api-key"; +// src/components/api-key-provider/ApiKeyProviderSection.tsx +import { ApiKeyProviderSection } from "@/components/api-key-provider"; ``` 旧 `ProviderPoolPage`、凭证卡片、OAuth 表单与 `useProviderPool` 已退役,不允许重新作为设置入口。 diff --git a/docs/aiprompts/credential-pool.md b/docs/aiprompts/credential-pool.md index 98440d874..e055f2f07 100644 --- a/docs/aiprompts/credential-pool.md +++ b/docs/aiprompts/credential-pool.md @@ -19,6 +19,11 @@ - Rust `ProviderPoolService`、`TokenCacheService`、Kiro 事件服务、OAuth 命令与旧 provider pool 命令 - Kiro / Qwen / Antigravity / Codex OAuth / Claude OAuth / Gemini OAuth 这类登录型或本地 CLI 凭证运行时 - `provider_pool_credentials` 的运行时读取、健康写回与 fallback 选择 +- 旧 `provider_pool_model` 运行时 / 展示 DTO 与凭证状态示例插件 +- server 内部 `/v1/credentials/*` 完整凭证 HTTP API;Provider 调用只允许通过 API Key Provider current 服务选择 `Runtime*` 凭证 +- 托盘里的旧 Token 刷新、凭证健康检查与凭证池计数入口 +- 旧 `scripts/playwright-login/` Provider OAuth 授权码自动化脚本及其 Tauri bundle include +- 旧 `src/components/provider-pool/api-key/` 目录名;当前 API Key Provider 组件已迁到 `src/components/api-key-provider/` ## 数据处理 @@ -36,11 +41,13 @@ - API Key Provider 中的 OpenAI、Anthropic、Gemini API Key、OpenRouter、GitHub、Azure 等配置 - 模型名或模型系列中出现的 `codex`、`gemini`、`qwen` 等字符串 - 协议转换器,尤其是 `src-tauri/crates/providers/src/converter/openai_to_antigravity.rs` -- server 内部短期用于桥接 API Key Provider 的兼容 DTO;它只能承载 current API Key Provider 数据,不代表凭证池恢复 +- `lime_core::models::runtime_provider_model` 暴露的 `Runtime*` DTO;它只能承载 current API Key Provider 数据,`RuntimeProviderType` 也不得别名回含旧凭证池 Provider 的历史 `ProviderType` +- API Key Provider 运行时凭证使用 `runtime-api-key-` 临时 UUID 前缀;不得恢复 `fallback-` 或 `[降级]` 这类旧凭证池选择语义 +- `retry.auto_switch_provider` 表示 current 的 Provider 自动切换,不代表旧凭证池 fallback;运行时文案不得再把它称为“自动降级” ## 守卫 -旧 UI / Hook / API 文件路径已登记到 `src/lib/governance/legacySurfaceCatalog.json`,不允许重新接回前端入口。 +旧 UI / Hook / API / 脚本文件路径已登记到 `src/lib/governance/legacySurfaceCatalog.json`,不允许重新接回前端入口、打包资源或运行时兜底。 涉及 Provider 或命令边界时,至少执行: diff --git a/docs/aiprompts/database.md b/docs/aiprompts/database.md index 73a3f2cce..94ab476a6 100644 --- a/docs/aiprompts/database.md +++ b/docs/aiprompts/database.md @@ -2,7 +2,7 @@ ## 概述 -使用 SQLite (rusqlite) 存储凭证元数据、流量记录等。 +使用 SQLite (rusqlite) 存储 API Key Provider、模型注册表、Agent 会话与流量记录等。旧凭证池表只保留为历史迁移和启动清理边界,运行时不得再读取它选择凭证。 ## 目录结构 @@ -12,37 +12,63 @@ src-tauri/src/database/ ├── schema.rs # 表结构定义 ├── migrations.rs # 数据库迁移 └── dao/ # 数据访问对象 - ├── credential_dao.rs + ├── api_key_provider.rs ├── flow_dao.rs └── config_dao.rs ``` ## 表结构 -### credentials +### api_key_providers ```sql -CREATE TABLE credentials ( +CREATE TABLE api_key_providers ( id TEXT PRIMARY KEY, - provider TEXT NOT NULL, name TEXT NOT NULL, - file_path TEXT NOT NULL, - status TEXT NOT NULL DEFAULT 'active', - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL + type TEXT NOT NULL, + api_host TEXT NOT NULL, + is_system INTEGER NOT NULL DEFAULT 0, + group_name TEXT NOT NULL, + enabled INTEGER NOT NULL DEFAULT 0, + sort_order INTEGER NOT NULL DEFAULT 0, + custom_models TEXT, + prompt_cache_mode TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL ); ``` -### token_cache +### api_keys ```sql -CREATE TABLE token_cache ( - credential_id TEXT PRIMARY KEY, - access_token TEXT NOT NULL, - refresh_token TEXT, - expires_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - FOREIGN KEY (credential_id) REFERENCES credentials(id) +CREATE TABLE api_keys ( + id TEXT PRIMARY KEY, + provider_id TEXT NOT NULL, + api_key_encrypted TEXT NOT NULL, + alias TEXT, + enabled INTEGER NOT NULL DEFAULT 1, + usage_count INTEGER NOT NULL DEFAULT 0, + error_count INTEGER NOT NULL DEFAULT 0, + last_used_at TEXT, + created_at TEXT NOT NULL, + FOREIGN KEY (provider_id) REFERENCES api_key_providers(id) ON DELETE CASCADE +); +``` + +### model_registry + +```sql +CREATE TABLE model_registry ( + id TEXT PRIMARY KEY, + display_name TEXT NOT NULL, + provider_id TEXT NOT NULL, + provider_name TEXT NOT NULL, + capabilities TEXT NOT NULL DEFAULT '{}', + limits TEXT NOT NULL DEFAULT '{}', + status TEXT NOT NULL DEFAULT 'active', + source TEXT NOT NULL DEFAULT 'local', + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL ); ``` @@ -69,19 +95,22 @@ CREATE INDEX idx_flow_timestamp ON flow_records(timestamp); ## DAO 模式 ```rust -pub struct CredentialDao { - conn: Arc>, -} +pub struct ApiKeyProviderDao; -impl CredentialDao { - pub fn insert(&self, credential: &Credential) -> Result<()>; - pub fn find_by_id(&self, id: &str) -> Result>; - pub fn find_all(&self) -> Result>; - pub fn update(&self, credential: &Credential) -> Result<()>; - pub fn delete(&self, id: &str) -> Result<()>; +impl ApiKeyProviderDao { + pub fn get_all_providers(conn: &Connection) -> Result>; + pub fn get_provider_by_id(conn: &Connection, id: &str) -> Result>; + pub fn insert_provider(conn: &Connection, provider: &ApiKeyProvider) -> Result<()>; + pub fn update_provider(conn: &Connection, provider: &ApiKeyProvider) -> Result<()>; + pub fn delete_provider(conn: &Connection, id: &str) -> Result; + pub fn get_enabled_api_keys_by_provider(conn: &Connection, provider_id: &str) -> Result>; } ``` +## 旧凭证池表边界 + +`provider_pool_credentials` 分类为 `deprecated` 存储边界:schema、历史迁移和启动期清理可以引用;运行时服务、Tauri 命令、前端 API 和旁路统计不得再读取它做凭证选择。 + ## 数据库迁移 ```rust @@ -91,9 +120,7 @@ pub fn run_migrations(conn: &Connection) -> Result<()> { if version < 1 { conn.execute_batch(include_str!("migrations/001_initial.sql"))?; } - if version < 2 { - conn.execute_batch(include_str!("migrations/002_add_flow.sql"))?; - } + run_startup_migrations(conn)?; set_schema_version(conn, CURRENT_VERSION)?; Ok(()) diff --git a/docs/aiprompts/hooks.md b/docs/aiprompts/hooks.md index 6c9090657..be2c089a4 100644 --- a/docs/aiprompts/hooks.md +++ b/docs/aiprompts/hooks.md @@ -72,8 +72,8 @@ export function useDeepLink() { const unlisten = listen("deep-link", async (event) => { const url = new URL(event.payload); - if (url.pathname === "/oauth/callback") { - await handleOAuthCallback(url.searchParams); + if (url.pathname === "/oem-cloud/auth/callback") { + await handleOemCloudAuthCallback(url.searchParams); } }); @@ -84,6 +84,8 @@ export function useDeepLink() { } ``` +`useDeepLink` 可以处理 OEM 云登录、外部连接器回调等独立流程,但不得恢复旧 Provider OAuth / 凭证池授权回调。 + ## Hook 规范 ### 命名约定 diff --git a/docs/aiprompts/overview.md b/docs/aiprompts/overview.md index 749fdbce2..650c05765 100644 --- a/docs/aiprompts/overview.md +++ b/docs/aiprompts/overview.md @@ -179,9 +179,10 @@ lime/ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Providers │ -│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ -│ │ Kiro │ │ Gemini │ │ Claude │ │ OpenAI │ ... │ -│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ +│ ┌────────────┐ ┌────────────┐ ┌──────────────────────────┐ │ +│ │ API Key │ │ Custom API │ │ Protocol Converters │ │ +│ │ Providers │ │ Providers │ │ Antigravity / Anthropic │ │ +│ └────────────┘ └────────────┘ └──────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ diff --git a/docs/aiprompts/providers.md b/docs/aiprompts/providers.md index 04a28a7aa..ba814d56d 100644 --- a/docs/aiprompts/providers.md +++ b/docs/aiprompts/providers.md @@ -43,18 +43,16 @@ pub enum ProviderType { pub trait Provider: Send + Sync { /// 获取 Provider 类型 fn provider_type(&self) -> ProviderType; - - /// 加载凭证 - async fn load_credential(&self, path: &Path) -> Result; - - /// 刷新 Token - async fn refresh_token(&self, credential: &mut CredentialData) -> Result<()>; - - /// 检查 Token 是否过期 - fn is_token_expired(&self, credential: &CredentialData) -> bool; - + + /// 返回运行时能力声明;凭证由 API Key Provider 服务解析后注入。 + fn capabilities(&self) -> ProviderCapabilities; + /// 发送 API 请求 - async fn send_request(&self, credential: &CredentialData, request: &Request) -> Result; + async fn send_request( + &self, + credential: &RuntimeProviderCredential, + request: &ProviderRequest, + ) -> Result; } ``` diff --git a/docs/aiprompts/quality-workflow.md b/docs/aiprompts/quality-workflow.md index 43fcf0a26..ac459330e 100644 --- a/docs/aiprompts/quality-workflow.md +++ b/docs/aiprompts/quality-workflow.md @@ -469,8 +469,8 @@ CI 里的 `.github/workflows/quality.yml` 结果摘要现在也会透出 `bridge 如果本轮修改了 Provider 类型与 Prompt Cache 能力边界,还应额外确认: - `anthropic-compatible` 不会再被 UI 或运行时误显示成“自动 Prompt Cache” -- Provider Pool 的列表、详情、创建和编辑入口中,受影响落点会继续提示“显式 cache_control” -- 聊天侧 `ModelSelector / Inputbar / MessageList / TokenUsageDisplay` 与 Provider Pool 的口径保持一致 +- `设置 -> AI 服务商` 的列表、详情、创建和编辑入口中,受影响落点会继续提示“显式 cache_control” +- 聊天侧 `ModelSelector / Inputbar / MessageList / TokenUsageDisplay` 与 API Key Provider / model registry 的口径保持一致 ### Layer 4:交互型 E2E diff --git a/docs/aiprompts/workspace.md b/docs/aiprompts/workspace.md index 821151ca6..ff11ec98a 100644 --- a/docs/aiprompts/workspace.md +++ b/docs/aiprompts/workspace.md @@ -240,7 +240,7 @@ pub async fn workspace_create_session(workspace_id: String, name: String) -> Res ~/Library/Application Support/lime/ # Lime 应用目录 ├── lime.db # 应用数据库(包含 workspaces 表) -├── credentials/ # 凭证文件 +├── credentials/ # 已退役托管凭证副本目录;启动期只做清理 └── workspaces/ # workspace 级配置 ├── default/ │ └── mcp.json # workspace 级 MCP 配置 diff --git a/docs/design/settings-redesign.md b/docs/design/settings-redesign.md index 774236c80..0e520c8e3 100644 --- a/docs/design/settings-redesign.md +++ b/docs/design/settings-redesign.md @@ -22,7 +22,7 @@ └── ⌨️ 快捷键 (Hotkeys) 📁 智能体 (Agent) -├── 🧠 AI 服务商 (Providers) → 现有 Provider Pool +├── 🧠 AI 服务商 (Providers) → API Key Provider / configured providers ├── 🤖 助理服务 (Assistant) → Agent 配置 ├── 🔧 技能管理 (Skills/MCP) → 现有 MCP 页面 ├── 🧩 记忆设置 (Memory) → 新增 diff --git a/docs/develop/ai-agent-development-guide.md b/docs/develop/ai-agent-development-guide.md index d20ae9210..1d8349c8e 100644 --- a/docs/develop/ai-agent-development-guide.md +++ b/docs/develop/ai-agent-development-guide.md @@ -29,8 +29,8 @@ Lime 的未来方向是从 **API 代理工具** 演进为 **AI Agent 创作平 │ ═══════════════════════════════════════════════════════════ │ │ ↓ ↓ ↓ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Kiro │ │ Gemini │ │ Qwen │ │ -│ │ Provider │ │ Provider │ │ Provider │ │ +│ │ API Key │ │ Model │ │ Protocol │ │ +│ │ Providers │ │ Registry │ │ Converters │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` diff --git a/docs/exec-plans/README.md b/docs/exec-plans/README.md index 5ec2059ca..ffc9f19e1 100644 --- a/docs/exec-plans/README.md +++ b/docs/exec-plans/README.md @@ -27,6 +27,7 @@ - 参考运行时主链总计划:`docs/exec-plans/upstream-runtime-alignment-plan.md` - 参考运行时主链进度日志:`docs/exec-plans/upstream-runtime-alignment-progress.md` - Provider 模型能力 taxonomy 进度日志:`docs/exec-plans/provider-model-taxonomy-progress.md` +- Lime 多模态运行合同实施计划:`docs/exec-plans/multimodal-runtime-contract-plan.md` - 云端套餐与支付边界收口计划:`docs/exec-plans/cloud-commerce-user-center-boundary.md` - `@` 命令本地执行纠偏计划:`docs/exec-plans/at-command-local-execution-alignment-plan.md` - LimeNext 总实施计划(`legacy current reference`,当前主规划已切到 `docs/roadmap/limenextv2/README.md`):`docs/exec-plans/limenext-plan.md` diff --git a/docs/exec-plans/limenext-progress.md b/docs/exec-plans/limenext-progress.md index d082d7ec7..d328675ad 100644 --- a/docs/exec-plans/limenext-progress.md +++ b/docs/exec-plans/limenext-progress.md @@ -439,7 +439,7 @@ - 定向 `eslint` 通过 - `verify:gui-smoke` 未通过,但当前失败点仍停留在既有 `smoke:browser-runtime` 链路:`launch_browser_session / close_chrome_profile_session` 持续 `fetch failed`;前置 `DevBridge / workspace-ready` 已正常,不按本次 `SceneApps` 首屏改动回归处理 - `verify:local` 未通过,当前阻塞仍是仓库既有无关 warning: - - `src/components/provider-pool/api-key/ProviderSetting.tsx:174` `react-hooks/exhaustive-deps` + - `src/components/api-key-provider/ProviderSetting.tsx:174` `react-hooks/exhaustive-deps` - 把 `人工复盘 -> 首页 / 我的方法 / slash 结果模板横幅` 从“首屏可解释”继续推进成“首屏可直接续接”,避免三处横幅虽然都已经能说明“这轮为什么更适合这样继续”,但用户仍然要自己再去点下一层目录才能真正开工: - 已更新: @@ -562,7 +562,7 @@ - 定向 `eslint` 通过 - `verify:gui-smoke` 通过:复用已运行的 headless 环境,完整跑通 `workspace-ready / browser-runtime / site-adapters / agent-service-skill-entry / agent-runtime-tool-surface / agent-runtime-tool-surface-page` - `verify:local` 未通过;当前阻塞来自仓库既有无关 lint warning: - - `src/components/provider-pool/api-key/ProviderSetting.tsx:174` 的 `react-hooks/exhaustive-deps` warning + - `src/components/api-key-provider/ProviderSetting.tsx:174` 的 `react-hooks/exhaustive-deps` warning - 因此本轮结论应更新为:`我的方法` 页这刀 current UI 收口已通过定向回归与 GUI smoke,仓库级统一入口仍受一条与本刀无关的存量 warning 阻塞 ## 2026-04-23 @@ -608,7 +608,7 @@ - `src/lib/api/agentRuntime/types.ts` - `src/lib/api/agentProtocol.test.ts` - `src/components/agent/chat/hooks/useAgentSession.ts` - - `src/components/provider-pool/api-key/ProviderModelList.tsx` + - `src/components/api-key-provider/ProviderModelList.tsx` - `src/components/settings-v2/system/automation/index.tsx` - `src/components/workspace/video/VideoCanvas.tsx` - `src/lib/mediaGeneration.ts` @@ -734,7 +734,7 @@ - `verify:gui-smoke` 通过 - 本轮未重复执行 `npm run verify:local`;仓库级统一入口当前仍有既有无关阻塞: - `src/components/agent/chat/hooks/useAgentSession.ts`:`normalizeLegacyThreadItems` 未定义 - - `src/components/provider-pool/api-key/ProviderModelList.tsx`:既有 hooks warning + - `src/components/api-key-provider/ProviderModelList.tsx`:既有 hooks warning - 把“当前续接成果”真正接进下一轮推荐,而不只是让它在成果分区里高亮可见: - 已更新: @@ -759,7 +759,7 @@ - `verify:gui-smoke` 通过 - `verify:local` 未通过;当前阻塞仍是仓库既有无关问题: - `src/components/agent/chat/hooks/useAgentSession.ts`:`normalizeLegacyThreadItems` 未定义 - - `src/components/provider-pool/api-key/ProviderModelList.tsx`:既有 hooks warning + - `src/components/api-key-provider/ProviderModelList.tsx`:既有 hooks warning - 把 `去灵感库继续` 从泛化首页跳转推进成“成果分区 + 当前续接对准”,避免用户刚从结果页返回灵感库时又得自己重找那条成果: - 已更新: @@ -790,7 +790,7 @@ - `verify:gui-smoke` 通过 - `verify:local` 未通过;当前阻塞仍是仓库既有无关问题: - `src/components/agent/chat/hooks/useAgentSession.ts`:`normalizeLegacyThreadItems` 未定义 - - `src/components/provider-pool/api-key/ProviderModelList.tsx`:既有 hooks warning + - `src/components/api-key-provider/ProviderModelList.tsx`:既有 hooks warning - 把 `保存到灵感库` 的前台反馈从 toast-only 收成“信号驱动的已收进灵感库”稳定状态,避免结果虽然已经沉淀成功,用户在结果面里却仍看到可重复点击的旧按钮: - 已更新: @@ -825,7 +825,7 @@ - `verify:gui-smoke` 通过 - `verify:local` 未通过;当前阻塞仍是仓库既有无关问题: - `src/components/agent/chat/hooks/useAgentSession.ts`:`normalizeLegacyThreadItems` 未定义 - - `src/components/provider-pool/api-key/ProviderModelList.tsx`:既有 hooks warning + - `src/components/api-key-provider/ProviderModelList.tsx`:既有 hooks warning - 把 `保存到灵感库` 从单一聊天结果卡扩到 `sceneapps` 深层结果面与自动化详情,避免高价值结果仍然只在一个结果入口里才能沉淀: - 已更新: @@ -856,7 +856,7 @@ - `verify:gui-smoke` 通过 - `verify:local` 未通过;当前阻塞来自仓库既有无关 lint 问题: - `src/components/agent/chat/hooks/useAgentSession.ts`:`normalizeLegacyThreadItems` 未定义 - - `src/components/provider-pool/api-key/ProviderModelList.tsx`:既有 hooks warning + - `src/components/api-key-provider/ProviderModelList.tsx`:既有 hooks warning - 把 `结果工作台 -> 灵感库` 这条主链补回 `SceneAppExecutionSummaryCard`,避免高价值结果仍然只停留在消息区或结果摘要里: - 已更新: @@ -5198,7 +5198,7 @@ - `rustfmt --check "src-tauri/src/sceneapp/dto.rs" "src-tauri/src/sceneapp/catalog.rs"` - 当前仓库级阻塞仍存在: - `npm run typecheck` 会被无关现有错误挡住: - - `src/components/provider-pool/api-key/ProviderConfigForm.ui.test.tsx` + - `src/components/api-key-provider/ProviderConfigForm.ui.test.tsx` - `src/components/settings-v2/general/memory/index.tsx` - `npm run verify:gui-smoke` 在 `smoke:browser-runtime` 阶段被现有环境问题挡住: - `launch_browser_session` 持续超时,`180000ms` 内未收到 DevBridge 响应 diff --git a/docs/exec-plans/multimodal-runtime-contract-plan.md b/docs/exec-plans/multimodal-runtime-contract-plan.md new file mode 100644 index 000000000..8f82eb7ed --- /dev/null +++ b/docs/exec-plans/multimodal-runtime-contract-plan.md @@ -0,0 +1,139 @@ +# Lime 多模态运行合同实施计划 + +## 摘要 + +本计划承接 `docs/roadmap/warp/`,用于把 Warp / ClaudeCode 参考落成 Lime 可检查的底层多模态运行合同。 + +本轮主目标不是先改 `@` 命令,而是先建立: + +1. 底层运行事实源地图 +2. `ModalityRuntimeContract` schema +3. 可机器检查的 contract registry +4. 最小治理守卫 + +## 主线声明 + +从现在开始,多模态能力默认向下面这条 current 主链收敛: + +```text +runtime identity + -> ModalityRuntimeContract + -> capability matrix + -> ModalityExecutionProfile + -> executor binding + -> domain artifact graph + -> evidence / replay / task index + -> viewer + -> entry binding +``` + +`@` 命令、按钮、Scene 只允许作为上层 entry binding,不能直接拥有 task、model、artifact、viewer 或 evidence 的事实写入权。 + +## 当前阶段 + +### Phase 0:底层运行事实源盘点 + +状态:进行中。 + +本阶段输出: + +1. `docs/roadmap/warp/runtime-fact-map.md` +2. 底层 fact source 的 `current / compat / deprecated / dead` 分类 +3. 后续 contract registry 的 owner / reader / writer 约束 + +### Phase 1:ModalityRuntimeContract schema + +状态:进行中。 + +本阶段输出: + +1. `docs/roadmap/warp/contract-schema.md` +2. `src/lib/governance/modalityRuntimeContracts.json` +3. `scripts/check-modality-runtime-contracts.mjs` +4. `npm run governance:modality-contracts` + +### Phase 2:多模态能力矩阵 + +状态:进行中。 + +本阶段输出: + +1. `docs/roadmap/warp/capability-matrix.md` +2. `src/lib/governance/modalityCapabilityMatrix.json` +3. contract 守卫检查 `required_capabilities` 与 `routing_slot` 是否引用已登记能力和模型角色 + +## 本轮最小闭环 + +第一轮已完成治理底座;当前纠偏后的最小闭环必须至少贯通一个真实运行时 vertical slice。 + +### 已完成治理底座 + +1. 新增 runtime fact map 文档。 +2. 新增 contract schema 文档。 +3. 新增首批底层 contract registry。 +4. 新增 contract 校验脚本。 +5. 接入 npm 脚本,作为后续 `verify:local` 集成前的显式检查入口。 + +### 当前实现闭环 + +1. 选择 `image_generation` 作为第一条 current vertical slice。 +2. Rust 运行时在 `harness.image_skill_launch` 与内部 `image_task` 上注入 `modality_contract_key=image_generation`、`modality=image`、`required_capabilities`、`routing_slot` 与 `runtime_contract` 快照。 +3. `lime_create_image_generation_task` 工具 schema、别名归一化与 request builder 接受并补齐 contract 字段。 +4. `image_generate` task artifact payload 固化同一组 contract 字段,artifact 成为图片生成 contract 的 truth source。 +5. DevBridge / browser mock 与前端 `CreateImageGenerationTaskArtifactRequest` 类型同步 contract 字段,避免浏览器链路变成假绿。 + +暂不做: + +1. 暂不把所有 `@`、按钮、Scene 批量登记到 `bound_entries`;仍等 Phase 7。 +2. 暂不改 viewer 或 workspace UI 视觉面。 +3. 暂不把 LimeCore 写成默认执行器;LimeCore 仍只作为 catalog / policy / offer / audit 控制面。 + +## 分类 + +### current + +- `ModalityRuntimeContract` +- `runtime fact map` +- `domain artifact graph` +- `execution profile` +- `capability matrix` +- `evidence pack` +- `LimeCore catalog / policy` 作为云控制面 + +### compat + +- 旧 `@` 命令 metadata,只能绑定到底层 contract +- 旧 `generic_file` 展示,只能作为兜底 artifact kind +- 旧本地 CLI adapter,只能作为 typed `local_cli` executor + +### deprecated + +- 入口直接创建多模态 task +- viewer 自己猜 artifact 类型 +- skill 内部自由选择 Bash / WebSearch / ToolSearch 作为首刀 + +### dead + +- 把 LimeCore 当成所有多模态入口的默认执行器 +- 用 `@` 命令名作为底层 contract 主键 +- 用通用文件卡承载所有多模态结果 + +## 进度日志 + +- 2026-04-29:创建本计划;固定本轮从 Phase 0/1 开始,先做底层 fact map、contract schema 和轻量守卫,不改上层 `@` 命令。 +- 2026-04-29:已新增 `runtime-fact-map.md`、`contract-schema.md`、`modalityRuntimeContracts.json` 与 `check-modality-runtime-contracts.mjs`;首批 5 个 current contract 为 `image_generation`、`browser_control`、`pdf_extract`、`voice_generation`、`web_research`,`bound_entries` 暂为空,等待 Phase 7 再接上层入口。 +- 2026-04-29:已接入 `npm run governance:modality-contracts` 并通过;同时通过 `npm run harness:doc-freshness`、脚本语法检查和本轮触达 Markdown 链接 / code fence 检查。 +- 2026-04-29:继续推进 Phase 2,新增 `capability-matrix.md` 与 `modalityCapabilityMatrix.json`;首批登记 13 个 capability 与 9 个 model role,并扩展 contract 守卫校验 `required_capabilities` / `routing_slot` 引用关系。 +- 2026-04-29:已把 `npm run governance:modality-contracts` 接入 `npm run test:contracts`;`test:contracts`、`harness:doc-freshness`、脚本语法检查与本轮触达 Markdown 链接 / code fence 检查通过。 +- 2026-04-29:开始真实运行时实现,完成 `image_generation` vertical slice 第一刀:`prepare_image_skill_launch_request_metadata` 注入底层 contract,`lime_create_image_generation_task` request builder / schema 补齐 contract 字段,`create_image_generation_task_artifact_inner` 将 `modality_contract_key`、`required_capabilities`、`routing_slot`、`runtime_contract` 写入 `image_generate` artifact payload,并补 Rust / TS 定向断言。 +- 2026-04-29:验证通过 `npm run test:contracts`、`npm test -- src/components/agent/chat/workspace/useWorkspaceImageWorkbenchActionRuntime.test.tsx src/lib/tauri-mock/core.test.ts`、`npm run typecheck`,并通过 `CARGO_TARGET_DIR=src-tauri/target/codex-modality-contract cargo test --manifest-path src-tauri/Cargo.toml test_prepare_image_skill_launch_request_metadata_materializes_input_refs`;继续补跑图片 artifact / tool request Rust 定向测试时,当前工作区已有 `src-tauri/crates/agent/src/session_store.rs` 编译错误阻断,错误点不属于本轮 `image_generation` contract 改动。 +- 2026-04-29:继续 `image_generation` vertical slice 第二刀:在图片任务 worker 进入 `lime_media_runtime::execute_image_generation_task_with_hook` 前读取 task artifact 的 contract / required capabilities / routing slot,并对明显文本模型候选(例如 `gpt-5.2`)输出 `image_generation_model_capability_gap`,阻止 contract 要求的图片生成任务误路由到普通文本模型。 +- 2026-04-29:第二刀验证通过 `npm run test:contracts` 与 `npm run typecheck`;Rust 定向测试 `CARGO_TARGET_DIR=src-tauri/target/codex-modality-contract cargo test --manifest-path src-tauri/Cargo.toml validate_image_generation_task_execution_contract_should_reject_text_model_candidate` 被当前工作区已有 `src-tauri/src/commands/aster_agent_cmd/command_api/runtime_api.rs` 缺少 `AgentRuntimeSessionHistoryCursor` import 的编译错误阻断,未继续扩大范围修 unrelated 编译债。 +- 2026-04-29:已用最小 import 修复 `runtime_api.rs` 的 `AgentRuntimeSessionHistoryCursor` 编译阻塞,并补通 `image_generation` preflight 的 Rust 定向验证:明显文本模型会被 `image_generation_model_capability_gap` 阻止,`gpt-image-1` 类图片模型候选可通过。 +- 2026-04-29:继续 `image_generation` vertical slice 第三刀:`runtime_evidence_pack_service` 现在会从 `.lime/tasks/image_generate/*.json` 提取 `ModalityRuntimeContract` 与 routing 决策快照,写入 `runtime.json` / `artifacts.json` 的 `modalityRuntimeContracts`,并把 `modalityRuntimeContract` 纳入 `observabilitySummary.signalCoverage`;当 preflight 失败时,evidence 中可直接看到 `routingEvent=routing_not_possible`、`routingOutcome=blocked`、`failureCode=image_generation_model_capability_gap`。 +- 2026-04-29:第三刀验证通过 `CARGO_TARGET_DIR=src-tauri/target/codex-modality-contract cargo test --manifest-path src-tauri/Cargo.toml should_export_modality_runtime_contract_snapshot_from_failed_image_task`、`validate_image_generation_task_execution_contract_should_accept_image_model_candidate`、`should_export_auxiliary_runtime_snapshots_from_image_task_artifact`、`npm run test:contracts`、`npm run typecheck` 与 `git diff --check`。 +- 2026-04-29:继续 `image_generation` vertical slice 第四刀:`runtime_replay_case_service` 现在从 evidence `runtime.json` 读取 `modalityRuntimeContracts`,写入 replay `input.json` / `expected.json` / `grader.md` / `evidence-links.json`;同时把 `modality-runtime-contract`、`modality-image_generation`、`routing-not-possible` 纳入 suite tags,把 `modality_contract_routing_blocked` 与 `image_generation_model_capability_gap` 纳入 failure modes,并在 grader 中增加“多模态运行合同检查”,使模型误路由样本可被回归评分而不是只停留在 evidence 里。 +- 2026-04-29:第四刀验证通过 `CARGO_TARGET_DIR=src-tauri/target/codex-modality-contract cargo test --manifest-path src-tauri/Cargo.toml should_carry_modality_runtime_contract_into_replay_case`、`should_export_runtime_replay_case_to_workspace`、`npm run test:contracts` 与 `git diff --check`。 +- 2026-04-29:继续 `image_generation` vertical slice 第五刀:图片任务 worker 的模型能力 preflight 从纯字符串启发式推进到 `ModelRegistryState` / `EnhancedModelMetadata`,优先按 `task_families` 与 `output_modalities` 判断 `ImageGeneration` / `ImageEdit` / `Image` 输出能力;只有 registry 找不到模型时才回退到文本模型启发式,避免 `gpt-5.2` 这类普通文本模型误进图片执行器,同时允许 registry 明确声明的图片模型通过。 +- 2026-04-29:第五刀进一步把 registry 判定结果持久化为 `image_generate` task payload 的 `model_capability_assessment`,并同步进入 evidence `modalityRuntimeContracts.snapshots[].modelCapabilityAssessment` 与 replay 的 `modalityContractChecks`,让后续回归能区分 `model_registry` 判定和 heuristic fallback,而不是只看到最终失败码。 +- 2026-04-29:第五刀验证通过 `CARGO_TARGET_DIR=src-tauri/target/codex-modality-contract cargo test --manifest-path src-tauri/Cargo.toml assess_image_generation_model_capability_should_use_output_modality`、`assess_image_generation_model_capability_should_reject_registered_text_model`、`assess_image_generation_model_capability_should_match_provider_model_id`、`validate_image_generation_task_execution_contract_should_reject_registry_text_model`、`validate_image_generation_task_execution_contract_should_trust_registry_image_model`、`patch_image_task_model_capability_assessment_should_persist_registry_snapshot`、`should_export_modality_runtime_contract_snapshot_from_failed_image_task`、`should_carry_modality_runtime_contract_into_replay_case`、`npm run test:contracts` 与本轮触达文件 `git diff --check`。 diff --git a/docs/exec-plans/old-session-open-performance-plan.md b/docs/exec-plans/old-session-open-performance-plan.md new file mode 100644 index 000000000..9fad14fce --- /dev/null +++ b/docs/exec-plans/old-session-open-performance-plan.md @@ -0,0 +1,63 @@ +# 旧会话打开性能优化计划 + +## 目标 + +降低打开旧对话的首帧等待和后续 invoke 争抢,优先保证旧会话能快速显示目标会话与最近消息,再逐步补齐历史与执行轨迹。 + +## 当前进度 + +- `P0` 已完成:所有非显式全量的 `getSession` 调用补齐 `historyLimit: 40`,避免静默恢复和 missing-from-topics 校验拉取全量历史。 +- `P1` 已完成:会话元数据 fallback 回填合并为一次 `updateSessionMetadata`;无本地快照切换时先进入目标会话 hydrating shell;侧边栏 focus refresh 降低为 idle 后台任务。 +- `P2` 已推进:加载更多历史已从递增 tail window 改为分页加载;首屏返回最近 `40` 条并带 `history_cursor.oldest_message_id`,加载更早历史时优先用 `historyBeforeMessageId` cursor,每次请求 `50` 条,`historyOffset` 保留为兼容 fallback;历史会话的 MessageList 首帧先渲染文本,timeline 在 idle 后补齐;timeline turn 绑定减少中间数组分配;折叠的 `AgentThreadTimeline` 明细改为展开时再物化;旧历史中已完成的单步 timeline 明细默认只渲染摘要,展开时再物化;已排序的 turn/thread item 不再重复复制排序。 +- `2026-04-29` 追加收口:过大的旧会话 transient / persisted snapshot 在恢复前直接丢弃;读取命中的会话快照不再同步刷新 `lastAccessedAt` 以避免点击时重写整张 snapshot map;历史消息与流式 text_delta 统一做 overlap 合并,修复累计快照式 delta 导致的重复吐字;完成态旧消息的执行过程与正文视觉分离,历史 timeline 默认折叠明细;浏览器 DevBridge 模式下跳过低优先级 metadata backfill,并且仅在活跃运行时订阅 team SSE,避免旧会话点击后抢占 bridge 连接。 + +## 剩余事项 + +- Cursor 分页已完成:`agent_runtime_get_session` 继续作为 current 主链,新增 `historyBeforeMessageId` 请求字段与 `history_cursor` 响应字段;无 cursor 或缓存恢复缺少 cursor 时继续 fallback 到 `historyOffset`。 +- 若用户仍反馈旧会话卡顿,再对 `AgentThreadTimeline` 内部渲染和 thread item 展开策略做专项测量。 +- 若后续仍有 CPU / 内存尖峰,下一刀优先检查真实旧会话中是否存在超长 artifact / markdown 表格渲染,以及 `StreamingRenderer` 首屏 markdown 解析成本。 +- `verify:local` 已完成通过;此前内嵌 smoke / 后续 smoke 重试中遇到的 DevBridge `fetch failed` 与临时 target 链接 `liblime_lib.dylib` 打开失败,已通过后续重跑消除,按历史环境/并发构建插曲保留记录。 + +## 验证记录 + +- `npm test -- src/components/agent/chat/utils/threadTimelineView.test.ts src/components/agent/chat/components/MessageList.test.tsx` 通过。 +- `npm test -- src/components/agent/chat/hooks/agentRuntimeAdapter.test.ts src/components/agent/chat/hooks/agentSessionRefresh.test.ts src/components/agent/chat/hooks/useAsterAgentChat.test.tsx src/components/AppSidebar.test.tsx src/components/agent/chat/components/MessageList.test.tsx src/components/agent/chat/utils/threadTimelineView.test.ts` 通过。 +- `npm run typecheck` 通过。 +- `npm run lint` 通过。 +- `cargo check --manifest-path src-tauri/Cargo.toml -p lime-services` 通过;期间修正 `api_key_provider_service.rs` 对运行时 Provider DTO 的显式导入,避免旧凭证池模型导出边界影响编译。 +- `npm run verify:gui-smoke` 通过。 +- `npm run generate:agent-runtime-clients` 通过,已更新 `agent_runtime_get_session` generated manifest。 +- `npm test -- src/components/agent/chat/hooks/agentChatHistory.test.ts src/components/agent/chat/hooks/useAsterAgentChat.test.tsx` 通过。 +- `cargo test --manifest-path src-tauri/Cargo.toml -p lime-core tail -- --nocapture` 通过。 +- `cargo test --manifest-path src-tauri/Cargo.toml -p lime-agent history_tail -- --nocapture` 通过。 +- `cargo test --manifest-path src-tauri/Cargo.toml -p lime-agent get_session_sync_with_history_limit_should_tail_persisted_history -- --nocapture` 通过。 +- `cargo test --manifest-path src-tauri/Cargo.toml normalize_runtime_session_history -- --nocapture` 通过。 +- `npx eslint "src/lib/api/agentRuntime.ts" "src/lib/api/agentRuntime/*.ts" "src/components/agent/chat/hooks/agentChatHistory.ts" "src/components/agent/chat/hooks/useAgentSession.ts" "src/components/agent/chat/hooks/useAsterAgentChat.test.tsx" --max-warnings 0` 通过。 +- `npm run test:contracts` 通过。 +- `npm run generate:agent-runtime-clients` 通过,已同步 `historyBeforeMessageId` generated manifest。 +- `npm test -- src/components/agent/chat/hooks/agentChatHistory.test.ts src/components/agent/chat/hooks/useAsterAgentChat.test.tsx` 通过(新增 cursor 起始索引与加载更早历史 cursor 请求断言)。 +- `npm test -- src/lib/api/agent.test.ts src/components/agent/chat/hooks/agentRuntimeAdapter.test.ts` 通过。 +- `cargo test --manifest-path src-tauri/Cargo.toml -p lime-core tail -- --nocapture` 通过。 +- `cargo test --manifest-path src-tauri/Cargo.toml -p lime-agent history_tail -- --nocapture` 通过。 +- `cargo test --manifest-path src-tauri/Cargo.toml -p lime-agent get_session_sync_with_history_limit_should_tail_persisted_history -- --nocapture` 通过。 +- `cargo test --manifest-path src-tauri/Cargo.toml normalize_runtime_session_history -- --nocapture` 通过。 +- `cargo test --manifest-path src-tauri/Cargo.toml should_skip_runtime_queue_snapshots_for_cursor_history_page -- --nocapture` 通过;首次重跑曾撞到并发修改/编译中的 `runtime_evidence_pack_service.rs`,随后重跑通过。 +- `npx eslint "src/lib/api/agentRuntime.ts" "src/lib/api/agentRuntime/*.ts" "src/components/agent/chat/hooks/useAgentSession.ts" "src/components/agent/chat/hooks/agentChatHistory.ts" --max-warnings 0` 通过。 +- `npm run typecheck` 通过。 +- `npm run test:contracts` 通过。 +- `npm run verify:local` 前置检查、前端测试、Rust 全量测试均通过;最后内嵌 `verify:gui-smoke` 因 DevBridge `fetch failed` 中断。 +- `npm run verify:gui-smoke` 后续重试在 headless Tauri 链接阶段失败,关键错误为临时 target 下 `liblime_lib.dylib` 打开失败;同命令此前已单独通过。 +- `npm test -- src/components/agent/chat/components/AgentThreadTimeline.test.tsx src/components/agent/chat/components/MessageList.test.tsx` 通过。 +- `npx eslint "src/components/agent/chat/components/AgentThreadTimeline.tsx" "src/components/agent/chat/components/AgentThreadTimeline.test.tsx" "src/components/agent/chat/components/MessageList.tsx" "src/components/agent/chat/components/MessageList.test.tsx" --max-warnings 0` 通过。 +- `npm test -- src/components/agent/chat/utils/threadTimelineView.test.ts src/components/agent/chat/components/MessageList.test.tsx` 通过。 +- `npx eslint "src/components/agent/chat/utils/threadTimelineView.ts" "src/components/agent/chat/utils/threadTimelineView.test.ts" "src/components/agent/chat/components/MessageList.tsx" "src/components/agent/chat/components/MessageList.test.tsx" --max-warnings 0` 通过。 +- `npm run typecheck` 通过。 +- `npm run verify:local` 通过;覆盖 `verify:app-version`、`lint`、`typecheck`、全量前端测试、`test:contracts`、全量 Rust 测试与内嵌 `verify:gui-smoke`。 +- `npm test -- src/components/agent/chat/components/AgentThreadTimeline.test.tsx src/components/agent/chat/components/MessageList.test.tsx` 通过(新增旧历史单步 timeline 展开后再物化明细断言)。 +- `npx eslint "src/components/agent/chat/components/AgentThreadTimeline.tsx" "src/components/agent/chat/components/AgentThreadTimeline.test.tsx" "src/components/agent/chat/components/MessageList.tsx" "src/components/agent/chat/components/MessageList.test.tsx" --max-warnings 0` 通过。 +- `npm run typecheck` 通过。 +- `npm run verify:gui-smoke` 通过。 +- `npm test -- src/components/agent/chat/components/AgentThreadTimeline.test.tsx src/components/agent/chat/components/MessageList.test.tsx src/components/agent/chat/hooks/agentChatHistory.test.ts src/components/agent/chat/hooks/agentChatStorage.test.ts src/components/agent/chat/hooks/agentSessionScopedStorage.test.ts src/components/agent/chat/hooks/useAgentRuntimeSyncEffects.test.tsx src/components/agent/chat/hooks/useAsterAgentChat.test.tsx` 通过(7 files / 293 tests)。 +- `npx eslint "src/components/agent/chat/hooks/useAgentSession.ts" "src/components/agent/chat/hooks/useAgentRuntimeSyncEffects.ts" "src/components/agent/chat/hooks/useAgentRuntimeSyncEffects.test.tsx" "src/components/agent/chat/hooks/useAsterAgentChat.test.tsx" "src/components/agent/chat/hooks/agentChatHistory.ts" "src/components/agent/chat/hooks/agentStreamRuntimeHandler.ts" "src/components/agent/chat/hooks/skillCommand.ts" "src/components/agent/chat/components/MessageList.tsx" "src/components/agent/chat/components/AgentThreadTimeline.tsx" "src/components/agent/chat/hooks/agentChatStorage.ts" "src/components/agent/chat/hooks/agentSessionScopedStorage.ts" "src/components/agent/chat/hooks/agentChatHistory.test.ts" "src/components/agent/chat/components/MessageList.test.tsx" "src/components/agent/chat/components/AgentThreadTimeline.test.tsx" "src/components/agent/chat/hooks/agentChatStorage.test.ts" "src/components/agent/chat/hooks/agentSessionScopedStorage.test.ts" --max-warnings 0` 通过。 +- `npm run bridge:health -- --timeout-ms 120000` 通过,DevBridge `/health` 约 `20ms` 就绪。 +- Playwright E2E(`http://127.0.0.1:1420/`):连续切换 `E2E layout anchor -> 你好!👋 很高兴见到你! -> 你好!有什么我可以帮你的吗?😊 -> E2E layout anchor`,每次 `restoring=0`,耗时约 `109-425ms`,`body/html cursor=auto`,最终 `heap≈121MB`;`assistant-primary-timeline-shell` 高度 `94px`,`openDetails=0`,最终正文不在执行过程 shell 内,未检测到 assistant greeting 重复;等待 `9s` 后 `eventRequests=0`,控制台 `0 error / 1 warning`(仅浏览器模式 i18n 默认语言提示)。 diff --git a/docs/exec-plans/upstream-runtime-alignment-progress.md b/docs/exec-plans/upstream-runtime-alignment-progress.md index 4de09d85b..a9bd8bf4a 100644 --- a/docs/exec-plans/upstream-runtime-alignment-progress.md +++ b/docs/exec-plans/upstream-runtime-alignment-progress.md @@ -385,7 +385,7 @@ - `npx vitest run "src/components/agent/chat/components/AgentThreadReliabilityPanel.test.tsx" "src/lib/dev-bridge/mockPriorityCommands.test.ts"` 通过(`17 passed`) - `npm run test:contracts` 通过 - `npm run typecheck` 通过 - - `npx vitest run "src/components/provider-pool/api-key/ProviderConfigForm.ui.test.tsx" "src/components/settings-v2/general/memory/index.test.tsx"` 通过(`19 passed`) + - `npx vitest run "src/components/api-key-provider/ProviderConfigForm.ui.test.tsx" "src/components/settings-v2/general/memory/index.test.tsx"` 通过(`19 passed`) - `env CARGO_TARGET_DIR="/Users/coso/Documents/dev/ai/aiclientproxy/lime/.codex-target" cargo check --manifest-path "src-tauri/Cargo.toml" --lib` 通过 - `env CARGO_TARGET_DIR="/Users/coso/Documents/dev/ai/aiclientproxy/lime/.codex-target" cargo test --manifest-path "src-tauri/Cargo.toml" runtime_file_checkpoint_service::tests:: --lib -- --nocapture` 通过(`3 passed`) - `env CARGO_TARGET_DIR="/Users/coso/Documents/dev/ai/aiclientproxy/lime/.codex-target" cargo test --manifest-path "src-tauri/Cargo.toml" should_export_runtime_ --lib -- --nocapture` 通过(`6 passed`) @@ -393,7 +393,7 @@ - `npm run verify:local` 通过 - 当前更高层验证状态: - 之前记录里的 `should_export_runtime_` / `SceneAppRunSummary` 阻塞已不再复现;当前仓库中的 `sceneapp` 初始化点已补齐新字段,本轮实际阻塞改为 [claude_custom.rs](../../src-tauri/crates/providers/src/providers/claude_custom.rs) 的 `Default` 实现缺口,现已修复并复测通过 - - 之前记录里的 `verify:local` TypeScript 阻塞也已修复;[ProviderConfigForm.ui.test.tsx](../../src/components/provider-pool/api-key/ProviderConfigForm.ui.test.tsx) 与 [index.tsx](../../src/components/settings-v2/general/memory/index.tsx) 的类型问题不再复现,本轮已重新从统一入口跑通 `npm run verify:local` + - 之前记录里的 `verify:local` TypeScript 阻塞也已修复;[ProviderConfigForm.ui.test.tsx](../../src/components/api-key-provider/ProviderConfigForm.ui.test.tsx) 与 [index.tsx](../../src/components/settings-v2/general/memory/index.tsx) 的类型问题不再复现,本轮已重新从统一入口跑通 `npm run verify:local` - 在 [auto_memory_service.rs](../../src-tauri/src/services/auto_memory_service.rs) 将自动记忆入口收口为 `memdir` 主链:新增 `user / feedback / project / reference` 四类目录脚手架、最小 provider seam、topic 文件递归索引与 `memory_type / provider / updated_at` 元数据;`feedback / project` 写入现在强制要求 `Why:` 与 `How to apply:` 结构,`project` 同时拒绝 `今天 / tomorrow / next week` 这类相对时间词,避免记忆过期后继续误导执行 - 在 [memory_source_resolver_service.rs](../../src-tauri/src/services/memory_source_resolver_service.rs) 为来源链读模型补齐 `source_bucket / provider / memory_type / updated_at`,并让 `auto_memory_item` 真正进入 runtime 解析与 prompt 来源链;同时把 `memory_type` 约束到 memdir 来源,不再误打到普通项目规则或其它非 memdir 文件 diff --git a/docs/iteration-notes/implementation-progress-report.md b/docs/iteration-notes/implementation-progress-report.md index 677a3586b..8563493c3 100644 --- a/docs/iteration-notes/implementation-progress-report.md +++ b/docs/iteration-notes/implementation-progress-report.md @@ -1,5 +1,7 @@ # Lime AI Agent 改进 - 实施进度报告 +> 历史记录:旧凭证池运行时已退役。本文中涉及真实模型调用的后续方案,以 current API Key Provider / configured providers / model registry 为准,不再接入 `ProviderPoolService`。 + ## 已完成工作 ### 1. 研究与分析(任务 #1-#4)✅ @@ -60,10 +62,10 @@ #### 2. 集成真实 LLM 调用 -**目标**: 替换 `call_llm_mock()` 为真实的 provider pool 调用 +**目标**: 替换 `call_summary_model_stub()` 为真实的 current model runtime 调用 **实现方式**: -- 注入 `ProviderPoolService` 到 `AISummaryService` +- 通过 API Key Provider / configured providers / model registry 解析可用模型 - 使用 Claude Haiku 模型(成本低、速度快) - 实现重试机制(最多 3 次) - 添加超时控制(5 秒) diff --git a/docs/iteration-notes/p0-phase1-implementation-plan.md b/docs/iteration-notes/p0-phase1-implementation-plan.md index bdd844f96..53da0c3f7 100644 --- a/docs/iteration-notes/p0-phase1-implementation-plan.md +++ b/docs/iteration-notes/p0-phase1-implementation-plan.md @@ -1,5 +1,7 @@ # P0 实施方案:分阶段升级上下文管理 +> 历史记录:旧凭证池运行时已退役。AI 摘要若接入真实模型调用,只能走 current API Key Provider / configured providers / model registry 主路径。 + ## 决策:采用混合策略 基于当前分析,我决定采用**分阶段混合策略**: @@ -36,8 +38,8 @@ pub struct AISummaryResponse { } pub struct AISummaryService { - // 使用 Lime 的 provider pool - provider_pool: Arc, + // 使用 Lime current model runtime,不接入旧凭证池 + runtime: Arc, } impl AISummaryService { diff --git a/docs/ops.md b/docs/ops.md index 50d5b0cdf..ba8a2b6f9 100644 --- a/docs/ops.md +++ b/docs/ops.md @@ -25,11 +25,10 @@ ## 数据与日志位置 - SQLite 数据库:`~/.lime/lime.db` -- 旧凭证池副本目录:macOS `~/Library/Application Support/lime/credentials/`,Linux `~/.local/share/lime/credentials/`,Windows `%APPDATA%\\lime\\credentials\\`;启动期会清理 Lime 管理的副本 -- OAuth/Token 目录(默认):`~/.lime/auth/` - 日志目录:`~/.lime/logs/` - 请求日志目录:`~/.lime/request_logs/` - 数据库备份目录:`~/.lime/backups/` +- 旧凭证池副本目录与旧 OAuth/Token 目录不属于 current 常规备份面;启动期会清理 Lime 管理的旧凭证池副本。如需法务或人工排障留存,先离线加密归档,再启动新版本。 ## 备份与恢复 @@ -41,9 +40,7 @@ - 复制以下路径: - 配置文件(macOS: `~/Library/Application Support/lime/config.yaml`,Linux: `~/.config/lime/config.yaml`,Windows: `%APPDATA%\\lime\\config.yaml`) - 配置备份文件:`config.yaml.backup` - - 旧凭证池副本目录(macOS: `~/Library/Application Support/lime/credentials/`,Linux: `~/.local/share/lime/credentials/`,Windows: `%APPDATA%\\lime\\credentials\\`) - `~/.lime/lime.db` - - `~/.lime/auth/`(如需要保留 OAuth/Token) - `~/.lime/logs/`、`~/.lime/request_logs/`(如需保留日志) 3. 将备份文件存入受控存储(加密磁盘或安全存储)。 diff --git a/docs/plugins/README.md b/docs/plugins/README.md new file mode 100644 index 000000000..1045c9dc3 --- /dev/null +++ b/docs/plugins/README.md @@ -0,0 +1,11 @@ +# Provider 插件文档退役说明 + +旧 OAuth Provider 插件文档已随凭证池退役下线,分类为 `dead`。 + +后续 Provider 文档只允许收敛到: + +- API Key Provider / configured providers +- 模型注册表 +- 协议转换器 + +Antigravity、Kiro、Codex、Claude、Gemini 等登录型或本地 CLI 凭证插件不得重新作为运行时凭证入口恢复。Antigravity 的 `openai_to_antigravity` 协议转换器不属于凭证池功能,仍可按 coding plan 需要继续使用。 diff --git a/docs/plugins/antigravity-provider.md b/docs/plugins/antigravity-provider.md deleted file mode 100644 index 00acf094f..000000000 --- a/docs/plugins/antigravity-provider.md +++ /dev/null @@ -1,1243 +0,0 @@ -# Antigravity Provider 插件文档 - -> 版本: 1.0.0 -> 仓库: `aiclientproxy/antigravity-provider` -> 类型: OAuth Provider Plugin - ---- - -## 一、概述 - -### 1.1 插件简介 - -Antigravity Provider 是 Lime 的 OAuth Provider 插件,用于对接 **Google 内部 Gemini CLI** 服务(Antigravity)。它支持 **动态协议选择**:根据模型类型自动选择输出协议(Claude 模型 → Anthropic,Gemini 模型 → Gemini)。 - -### 1.2 核心能力 - -| 能力 | 说明 | -|------|------| -| 动态协议 | claude-* → Anthropic SSE,gemini-* → Gemini 协议 | -| 多模型支持 | Gemini 3 Pro、Gemini 2.5 Flash、Claude Sonnet/Opus | -| Google OAuth | 使用 Google 账户认证 | -| 安全设置 | 自动附加 Safety Settings(关闭内容过滤)| -| 思维链 | 支持 reasoning_effort 配置 | -| 流式响应 | 真实端到端流式传输 | - -### 1.3 支持的模型 - -| 用户模型名 | 内部 API 名称 | 底层模型 | 输出协议 | -|-----------|-------------|---------|---------| -| `gemini-3-pro-preview` | `gemini-3-pro-high` | Gemini | Gemini | -| `gemini-3-pro-image-preview` | `gemini-3-pro-image` | Gemini | Gemini | -| `gemini-3-flash-preview` | `gemini-3-flash` | Gemini | Gemini | -| `gemini-2.5-flash` | `gemini-2.5-flash` | Gemini | Gemini | -| `gemini-2.5-computer-use-preview-10-2025` | `rev19-uic3-1p` | Gemini | Gemini | -| `gemini-claude-sonnet-4-5` | `claude-sonnet-4-5` | Claude | **Anthropic** | -| `gemini-claude-sonnet-4-5-thinking` | `claude-sonnet-4-5-thinking` | Claude | **Anthropic** | -| `gemini-claude-opus-4-5-thinking` | `claude-opus-4-5-thinking` | Claude | **Anthropic** | - ---- - -## 二、插件架构 - -### 2.1 项目结构 - -``` -antigravity-provider/ -├── plugin/ -│ ├── plugin.json # 插件元数据 -│ └── config.json # 默认配置 -│ -├── src-tauri/src/ # 后端 Rust 代码 -│ ├── lib.rs # 插件入口 -│ ├── commands.rs # Tauri 命令 -│ ├── provider.rs # AntigravityProvider 核心实现 -│ ├── credentials.rs # 凭证管理 -│ ├── oauth.rs # Google OAuth 流程 -│ ├── token_refresh.rs # Token 刷新 -│ ├── models.rs # 模型定义和别名映射 -│ ├── safety_settings.rs # 安全设置 -│ └── converter/ # 协议转换 -│ ├── mod.rs -│ ├── openai_to_antigravity.rs # OpenAI → Antigravity -│ ├── anthropic_to_antigravity.rs # Anthropic → Antigravity -│ ├── antigravity_to_anthropic.rs # Antigravity → Anthropic SSE -│ └── antigravity_to_gemini.rs # Antigravity → Gemini -│ -├── src/ # 前端 React UI -│ ├── index.tsx # 插件 UI 入口 -│ ├── components/ -│ │ ├── CredentialList.tsx # 凭证列表 -│ │ ├── CredentialCard.tsx # 凭证卡片 -│ │ ├── AntigravityForm.tsx # 凭证添加表单 -│ │ ├── OAuthLogin.tsx # Google OAuth 登录 -│ │ └── SettingsPanel.tsx # 插件设置 -│ └── types/ -│ └── index.ts # 类型定义 -│ -└── .github/ - └── workflows/ - └── release.yml # 自动构建发布 -``` - -### 2.2 plugin.json - -```json -{ - "name": "antigravity-provider", - "version": "1.0.0", - "description": "Antigravity (Google Gemini CLI) OAuth Provider - 支持 Gemini 和 Claude 模型", - "author": "Lime Team", - "homepage": "https://github.com/aiclientproxy/antigravity-provider", - "license": "MIT", - - "plugin_type": "oauth_provider", - "entry": "antigravity-provider-cli", - "min_lime_version": "1.0.0", - - "provider": { - "id": "antigravity", - "display_name": "Antigravity (Gemini CLI)", - "target_protocol": "dynamic", - "protocol_rules": { - "claude-*": "anthropic", - "gemini-*": "gemini" - }, - "supported_models": [ - "gemini-3-pro-*", - "gemini-2.5-*", - "gemini-claude-*" - ], - "auth_types": ["oauth"], - "credential_schema": { - "type": "object", - "properties": { - "access_token": { "type": "string", "title": "Access Token" }, - "refresh_token": { "type": "string", "title": "Refresh Token" }, - "expiry_date": { "type": "integer", "title": "过期时间戳" }, - "project_id": { "type": "string", "title": "Project ID" }, - "email": { "type": "string", "title": "Google 邮箱" } - }, - "required": ["access_token", "refresh_token"] - } - }, - - "binary": { - "binary_name": "antigravity-provider-cli", - "github_owner": "aiclientproxy", - "github_repo": "antigravity-provider", - "platform_binaries": { - "macos-arm64": "antigravity-provider-aarch64-apple-darwin", - "macos-x64": "antigravity-provider-x86_64-apple-darwin", - "linux-x64": "antigravity-provider-x86_64-unknown-linux-gnu", - "windows-x64": "antigravity-provider-x86_64-pc-windows-msvc.exe" - }, - "checksum_file": "checksums.txt" - }, - - "ui": { - "surfaces": ["oauth_providers"], - "icon": "Sparkles", - "title": "Antigravity Provider", - "entry": "dist/index.js", - "styles": "dist/styles.css", - "default_width": 900, - "default_height": 700, - "permissions": [ - "database:read", - "database:write", - "http:request", - "crypto:encrypt", - "shell:open" - ] - } -} -``` - -### 2.3 config.json - -```json -{ - "enabled": true, - "timeout_ms": 120000, - "settings": { - "api": { - "environment": "daily", - "base_url_daily": "https://daily-cloudcode-pa.sandbox.googleapis.com", - "base_url_autopush": "https://autopush-cloudcode-pa.sandbox.googleapis.com", - "api_version": "v1internal" - }, - "safety_settings": { - "harassment": "OFF", - "hate_speech": "OFF", - "sexually_explicit": "OFF", - "dangerous_content": "OFF", - "civic_integrity": "BLOCK_NONE" - }, - "token_refresh": { - "auto_refresh": true, - "refresh_skew_seconds": 3000, - "max_retry": 3 - }, - "reasoning": { - "default_effort": "medium", - "enable_thinking_models": true - } - } -} -``` - ---- - -## 三、后端实现 - -### 3.1 核心数据结构 - -#### 凭证结构 - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AntigravityCredentials { - pub access_token: Option, - pub refresh_token: Option, - pub token_type: Option, - pub expiry_date: Option, // 毫秒时间戳 - pub expire: Option, // RFC3339 格式 - pub scope: Option, - pub last_refresh: Option, // RFC3339 格式 - pub cred_type: String, // 默认: "antigravity" - pub expires_in: Option, // 有效期(秒) - pub timestamp: Option, // 获取时间(毫秒) - pub enable: Option, - pub project_id: Option, - pub email: Option, -} -``` - -#### 模型别名映射 - -```rust -/// 用户友好模型名 → 内部 API 模型名 -pub fn map_model_name(model: &str) -> String { - let mappings = [ - ("gemini-2.5-computer-use-preview-10-2025", "rev19-uic3-1p"), - ("gemini-3-pro-image-preview", "gemini-3-pro-image"), - ("gemini-3-pro-preview", "gemini-3-pro-high"), - ("gemini-3-flash-preview", "gemini-3-flash"), - ("gemini-2.5-flash", "gemini-2.5-flash"), - ("gemini-claude-sonnet-4-5", "claude-sonnet-4-5"), - ("gemini-claude-sonnet-4-5-thinking", "claude-sonnet-4-5-thinking"), - ("gemini-claude-opus-4-5-thinking", "claude-opus-4-5-thinking"), - ]; - - for (from, to) in mappings { - if model.contains(from) { - return to.to_string(); - } - } - - model.to_string() -} -``` - -### 3.2 动态协议选择 - -```rust -/// 根据模型确定输出协议 -pub fn determine_output_protocol(model: &str) -> OutputProtocol { - // Claude 模型 → Anthropic 协议 - if model.contains("claude") { - return OutputProtocol::Anthropic; - } - - // Gemini 模型 → Gemini 协议 - OutputProtocol::Gemini -} - -#[derive(Debug, Clone)] -pub enum OutputProtocol { - Anthropic, // Claude Code 使用 - Gemini, // Gemini 客户端使用 -} -``` - -### 3.3 OAuth 配置 - -```rust -/// OAuth 2.0 配置(Google) -/// 必须通过环境变量配置: -/// - ANTIGRAVITY_OAUTH_CLIENT_ID -/// - ANTIGRAVITY_OAUTH_CLIENT_SECRET -/// -/// 获取方式: -/// 1. 使用 Antigravity CLI 的默认凭据 -/// 2. 从 Google Cloud Console 创建 OAuth 2.0 客户端 -fn oauth_client_id() -> String { - std::env::var("ANTIGRAVITY_OAUTH_CLIENT_ID") - .expect("ANTIGRAVITY_OAUTH_CLIENT_ID environment variable must be set") -} - -fn oauth_client_secret() -> String { - std::env::var("ANTIGRAVITY_OAUTH_CLIENT_SECRET") - .expect("ANTIGRAVITY_OAUTH_CLIENT_SECRET environment variable must be set") -} - -pub const OAUTH_SCOPES: &[&str] = &[ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/userinfo.email", - "https://www.googleapis.com/auth/userinfo.profile", - "https://www.googleapis.com/auth/cclog", - "https://www.googleapis.com/auth/experimentsandconfigs", -]; - -pub const OAUTH_AUTH_URL: &str = "https://accounts.google.com/o/oauth2/auth"; -pub const OAUTH_TOKEN_URL: &str = "https://oauth2.googleapis.com/token"; -``` - -### 3.4 Token 刷新 - -```rust -impl AntigravityProvider { - /// Token 刷新时间偏移(提前 50 分钟刷新) - const REFRESH_SKEW: i64 = 3000; // 秒 - - /// 检查 Token 是否需要刷新 - pub fn needs_refresh(&self, credential: &AntigravityCredentials) -> bool { - let now = Utc::now().timestamp(); - - // 优先使用 expiry_date(毫秒时间戳) - if let Some(expiry_date) = credential.expiry_date { - let expiry_secs = expiry_date / 1000; - return now >= expiry_secs - Self::REFRESH_SKEW; - } - - // 使用 expire(RFC3339 字符串) - if let Some(expire) = &credential.expire { - if let Ok(expiry) = DateTime::parse_from_rfc3339(expire) { - return now >= expiry.timestamp() - Self::REFRESH_SKEW; - } - } - - // 使用 expires_in + timestamp - if let (Some(expires_in), Some(timestamp)) = (credential.expires_in, credential.timestamp) { - let expiry_secs = timestamp / 1000 + expires_in; - return now >= expiry_secs - Self::REFRESH_SKEW; - } - - // 默认需要刷新 - true - } - - /// 刷新 Token - pub async fn refresh_token(&self, credential: &mut AntigravityCredentials) -> Result<()> { - let refresh_token = credential.refresh_token.as_ref() - .ok_or(Error::MissingRefreshToken)?; - - let client_id = oauth_client_id(); - let client_secret = oauth_client_secret(); - let response = self.http_client - .post(OAUTH_TOKEN_URL) - .form(&[ - ("client_id", client_id.as_str()), - ("client_secret", client_secret.as_str()), - ("refresh_token", refresh_token.as_str()), - ("grant_type", "refresh_token"), - ]) - .send() - .await?; - - let token_response: TokenResponse = response.json().await?; - - // 更新凭证 - credential.access_token = Some(token_response.access_token); - credential.expiry_date = Some(Utc::now().timestamp_millis() + token_response.expires_in * 1000); - credential.last_refresh = Some(Utc::now().to_rfc3339()); - - Ok(()) - } -} -``` - -### 3.5 安全设置 - -```rust -/// 默认安全设置(关闭内容过滤) -pub fn default_safety_settings() -> Vec { - vec![ - SafetySetting { - category: "HARM_CATEGORY_HARASSMENT".to_string(), - threshold: "OFF".to_string(), - }, - SafetySetting { - category: "HARM_CATEGORY_HATE_SPEECH".to_string(), - threshold: "OFF".to_string(), - }, - SafetySetting { - category: "HARM_CATEGORY_SEXUALLY_EXPLICIT".to_string(), - threshold: "OFF".to_string(), - }, - SafetySetting { - category: "HARM_CATEGORY_DANGEROUS_CONTENT".to_string(), - threshold: "OFF".to_string(), - }, - SafetySetting { - category: "HARM_CATEGORY_CIVIC_INTEGRITY".to_string(), - threshold: "BLOCK_NONE".to_string(), - }, - ] -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SafetySetting { - pub category: String, - pub threshold: String, -} -``` - -### 3.6 协议转换 - -#### OpenAI → Antigravity - -```rust -/// 将 OpenAI ChatCompletion 请求转换为 Antigravity 格式 -pub fn convert_openai_to_antigravity( - request: &OpenAiRequest, - project_id: Option<&str>, -) -> AntigravityRequest { - // 1. 模型映射 - let model = map_model_name(&request.model); - - // 2. 消息转换 - let contents = convert_messages(&request.messages); - - // 3. 系统指令提取 - let system_instruction = extract_system_instruction(&request.messages); - - // 4. 工具转换 - let tools = request.tools.as_ref().map(|t| convert_tools(t)); - - // 5. 生成配置 - let generation_config = GenerationConfig { - max_output_tokens: request.max_tokens, - temperature: request.temperature, - top_p: request.top_p, - candidate_count: Some(1), - stop_sequences: request.stop.clone(), - // 思维链配置 - thinking_config: request.reasoning_effort.as_ref().map(|effort| { - ThinkingConfig { - thinking_budget: match effort.as_str() { - "low" => 1024, - "medium" => 4096, - "high" => 16384, - _ => 4096, - }, - } - }), - }; - - AntigravityRequest { - model, - contents, - system_instruction, - tools, - generation_config: Some(generation_config), - safety_settings: Some(default_safety_settings()), - } -} - -/// 消息格式转换 -fn convert_messages(messages: &[OpenAiMessage]) -> Vec { - messages - .iter() - .filter(|m| m.role != "system") // 系统消息单独处理 - .map(|msg| { - let role = match msg.role.as_str() { - "user" => "user", - "assistant" => "model", - "tool" => "function", - _ => "user", - }; - - Content { - role: role.to_string(), - parts: convert_content_parts(&msg.content, &msg.tool_calls), - } - }) - .collect() -} -``` - -#### Antigravity → Anthropic SSE(Claude 模型) - -```rust -/// 将 Antigravity 响应转换为 Anthropic SSE 格式 -pub struct AntigravityToAnthropicTranslator { - message_id: String, - model: String, - current_index: u32, - input_tokens: u32, - output_tokens: u32, -} - -impl AntigravityToAnthropicTranslator { - pub fn translate_chunk(&mut self, chunk: &GeminiStreamChunk) -> Vec { - let mut events = Vec::new(); - - // 候选内容处理 - if let Some(candidates) = &chunk.candidates { - for candidate in candidates { - if let Some(content) = &candidate.content { - for part in &content.parts { - // 文本内容 - if let Some(text) = &part.text { - events.push(AnthropicSseEvent::ContentBlockDelta { - index: self.current_index, - delta: Delta::TextDelta { text: text.clone() }, - }); - } - - // 思维内容(thinking models) - if let Some(thought) = &part.thought { - events.push(AnthropicSseEvent::ContentBlockDelta { - index: self.current_index, - delta: Delta::ThinkingDelta { thinking: thought.clone() }, - }); - } - - // 工具调用 - if let Some(function_call) = &part.function_call { - events.push(AnthropicSseEvent::ContentBlockStart { - index: self.current_index, - content_block: ContentBlock::ToolUse { - id: Uuid::new_v4().to_string(), - name: function_call.name.clone(), - }, - }); - } - } - } - - // 完成原因 - if let Some(finish_reason) = &candidate.finish_reason { - let stop_reason = match finish_reason.as_str() { - "STOP" => "end_turn", - "MAX_TOKENS" => "max_tokens", - "SAFETY" => "content_filter", - "TOOL_CALL" => "tool_use", - _ => "end_turn", - }; - - events.push(AnthropicSseEvent::MessageDelta { - delta: MessageDelta { - stop_reason: Some(stop_reason.to_string()), - }, - usage: Usage { - input_tokens: self.input_tokens, - output_tokens: self.output_tokens, - }, - }); - } - } - } - - // 使用统计 - if let Some(usage) = &chunk.usage_metadata { - self.input_tokens = usage.prompt_token_count.unwrap_or(0); - self.output_tokens = usage.candidates_token_count.unwrap_or(0); - } - - events - } -} -``` - -### 3.7 API 调用 - -```rust -impl AntigravityProvider { - /// API 基础 URL - fn get_base_url(&self) -> &str { - match self.config.environment.as_str() { - "autopush" => "https://autopush-cloudcode-pa.sandbox.googleapis.com", - _ => "https://daily-cloudcode-pa.sandbox.googleapis.com", - } - } - - /// 流式 API 调用 - pub async fn call_api_stream( - &self, - request: AntigravityRequest, - credential: &AntigravityCredentials, - ) -> Result>> { - let url = format!( - "{}/v1internal/models/{}:streamGenerateContent?alt=sse", - self.get_base_url(), - request.model - ); - - let response = self.http_client - .post(&url) - .header("Authorization", format!("Bearer {}", credential.access_token.as_ref().unwrap())) - .header("Content-Type", "application/json") - .header("X-Goog-Api-Client", "genai-js/0.21.0") - .json(&request) - .send() - .await?; - - // 解析 SSE 流 - Ok(parse_sse_stream(response.bytes_stream())) - } -} -``` - ---- - -## 四、前端 UI 实现 - -### 4.1 插件入口 - -```tsx -// src/index.tsx -import { LimePluginSDK } from '@lime/plugin-sdk'; -import { CredentialList } from './components/CredentialList'; -import { AntigravityForm } from './components/AntigravityForm'; -import { SettingsPanel } from './components/SettingsPanel'; - -interface PluginProps { - sdk: LimePluginSDK; - pluginId: string; -} - -export default function AntigravityProviderUI({ sdk, pluginId }: PluginProps) { - const [view, setView] = useState<'list' | 'add' | 'settings'>('list'); - const [credentials, setCredentials] = useState([]); - - useEffect(() => { - loadCredentials(); - }, []); - - const loadCredentials = async () => { - const result = await sdk.database.query( - 'SELECT * FROM plugin_credentials WHERE plugin_id = ? ORDER BY created_at DESC', - [pluginId] - ); - setCredentials(result); - }; - - return ( -
-
- Antigravity Provider - 支持 Gemini 3 Pro 和 Claude 模型 - - - - -
- - {/* 动态协议说明 */} - -

此 Provider 支持动态协议选择:

-
    -
  • gemini-* 模型 → Gemini 协议输出
  • -
  • claude-* 模型 → Anthropic 协议输出
  • -
-
- - {view === 'list' && ( - - )} - - {view === 'add' && ( - { - loadCredentials(); - setView('list'); - }} - onCancel={() => setView('list')} - /> - )} - - {view === 'settings' && ( - setView('list')} - /> - )} -
- ); -} -``` - -### 4.2 凭证添加表单 - -```tsx -// src/components/AntigravityForm.tsx - -type AddMode = 'oauth' | 'file'; - -interface AntigravityFormProps { - sdk: LimePluginSDK; - onSuccess: () => void; - onCancel: () => void; -} - -export function AntigravityForm({ sdk, onSuccess, onCancel }: AntigravityFormProps) { - const [mode, setMode] = useState('oauth'); - const [loading, setLoading] = useState(false); - const [projectId, setProjectId] = useState(''); - const [name, setName] = useState(''); - - const handleOAuthLogin = async () => { - setLoading(true); - - try { - // 启动 OAuth 流程 - const result = await sdk.http.request('/api/antigravity/oauth/start', { - method: 'POST', - body: JSON.stringify({ - name: name || undefined, - skipProjectIdFetch: !projectId, - }), - }); - - // 打开授权 URL - await sdk.shell.open(result.authUrl); - - // 等待回调 - const credential = await sdk.http.request('/api/antigravity/oauth/callback/wait', { - method: 'POST', - timeout: 120000, // 2 分钟超时 - }); - - sdk.notification.success('OAuth 登录成功'); - onSuccess(); - } catch (error) { - sdk.notification.error(`登录失败: ${error.message}`); - } finally { - setLoading(false); - } - }; - - const handleFileImport = async (filePath: string) => { - setLoading(true); - - try { - await sdk.http.request('/api/antigravity/credentials/import', { - method: 'POST', - body: JSON.stringify({ - credsFilePath: filePath, - projectId: projectId || undefined, - name: name || undefined, - }), - }); - - sdk.notification.success('凭证导入成功'); - onSuccess(); - } catch (error) { - sdk.notification.error(`导入失败: ${error.message}`); - } finally { - setLoading(false); - } - }; - - return ( -
- {/* 模式选择 */} - - Google OAuth 登录 - 导入凭证文件 - - - {/* 通用配置 */} - - - setName(e.target.value)} - placeholder="我的 Antigravity 凭证" - /> - - - - - setProjectId(e.target.value)} - placeholder="自动获取或手动输入" - /> - 留空将自动从 Google 账户获取 - - - {mode === 'oauth' && ( - - )} - - {mode === 'file' && ( - - )} - - - - -
- ); -} -``` - -### 4.3 OAuth 登录模式 - -```tsx -// src/components/OAuthLogin.tsx - -interface OAuthModeProps { - loading: boolean; - onLogin: () => void; -} - -export function OAuthMode({ loading, onLogin }: OAuthModeProps) { - return ( -
- -

点击下方按钮将打开浏览器进行 Google 账户授权。

-

授权完成后,凭证将自动添加。

-
- -
-

请求的权限:

-
    -
  • Cloud Platform 访问
  • -
  • 用户邮箱和个人资料
  • -
  • Cloud Code 日志
  • -
-
- - -
- ); -} -``` - -### 4.4 凭证卡片 - -```tsx -// src/components/CredentialCard.tsx - -interface CredentialCardProps { - credential: Credential; - onRefresh: () => void; - onDelete: () => void; -} - -export function CredentialCard({ credential, onRefresh, onDelete }: CredentialCardProps) { - const data = JSON.parse(credential.credential_data) as AntigravityCredentials; - const isHealthy = credential.status === 'active'; - - return ( - - -
- - {isHealthy ? '健康' : '异常'} -
- Google OAuth -
- - -
- - {credential.name || '未命名'} -
-
- - {data.email || '-'} -
-
- - {data.project_id || '自动'} -
-
- - - {data.expiry_date - ? formatDate(new Date(data.expiry_date)) - : '-' - } - -
-
- - {data.last_refresh || '-'} -
- - {/* 支持的模型 */} -
- -
- Gemini 3 Pro - Gemini 2.5 Flash - Claude Sonnet 4.5 - Claude Opus 4.5 -
-
-
- - - - - -
- ); -} -``` - -### 4.5 设置面板 - -```tsx -// src/components/SettingsPanel.tsx - -interface SettingsPanelProps { - sdk: LimePluginSDK; - pluginId: string; - onClose: () => void; -} - -export function SettingsPanel({ sdk, pluginId, onClose }: SettingsPanelProps) { - const [config, setConfig] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - loadConfig(); - }, []); - - const loadConfig = async () => { - const result = await sdk.database.query<{ config: string }>( - 'SELECT config FROM plugin_configs WHERE plugin_id = ?', - [pluginId] - ); - if (result.length > 0) { - setConfig(JSON.parse(result[0].config)); - } - setLoading(false); - }; - - const saveConfig = async () => { - await sdk.database.execute( - 'UPDATE plugin_configs SET config = ? WHERE plugin_id = ?', - [JSON.stringify(config), pluginId] - ); - sdk.notification.success('设置已保存'); - }; - - if (loading) return ; - - return ( -
-

插件设置

- - {/* API 环境 */} - - - - - - {/* 思维链配置 */} - - - - 影响 thinking 模型的思考预算 - - - {/* 安全设置 */} - - - { - const value = checked ? 'OFF' : 'BLOCK_MEDIUM_AND_ABOVE'; - setConfig({ - ...config, - settings: { - ...config?.settings, - safety_settings: { - harassment: value, - hate_speech: value, - sexually_explicit: value, - dangerous_content: value, - civic_integrity: 'BLOCK_NONE', - }, - }, - }); - }} - > - 关闭所有内容过滤 - - - - - - - -
- ); -} -``` - ---- - -## 五、凭证文件格式 - -### 5.1 单凭证格式 - -**路径**: `~/.antigravity/oauth_creds.json` - -```json -{ - "access_token": "ya29.a0AfH6SMC...", - "refresh_token": "1//0gXXXXXXXXXXXX...", - "token_type": "Bearer", - "expiry_date": 1704369600000, - "scope": "https://www.googleapis.com/auth/cloud-platform ...", - "cred_type": "antigravity", - "project_id": "my-project-123", - "email": "user@example.com" -} -``` - -### 5.2 多凭证数组格式 - -**路径**: `accounts.json`(兼容 antigravity2api-nodejs) - -```json -[ - { - "access_token": "ya29.a0AfH6SMC...", - "refresh_token": "1//0gXXXXXXXXXXXX...", - "expiry_date": 1704369600000, - "email": "user1@example.com", - "enable": true - }, - { - "access_token": "ya29.b1BgH7TNE...", - "refresh_token": "1//0hYYYYYYYYYYYY...", - "expiry_date": 1704456000000, - "email": "user2@example.com", - "enable": true - } -] -``` - ---- - -## 六、API 调用流程 - -### 6.1 完整调用链路 - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ Antigravity Provider 调用流程 │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. 客户端请求 (Anthropic/OpenAI 格式) │ -│ │ │ -│ ▼ │ -│ 2. 模型识别 & 协议选择 │ -│ ├── gemini-* → 输出 Gemini 协议 │ -│ └── claude-* → 输出 Anthropic 协议 │ -│ │ │ -│ ▼ │ -│ 3. 凭证获取 │ -│ ├── 从凭证池选择健康凭证 │ -│ ├── 检查 Token 是否过期 │ -│ └── 必要时刷新 Token │ -│ │ │ -│ ▼ │ -│ 4. 请求转换 │ -│ ├── 模型名映射(用户名 → 内部 API 名) │ -│ ├── 消息格式转换 │ -│ ├── 附加 Safety Settings │ -│ └── 配置思维链(reasoning_effort) │ -│ │ │ -│ ▼ │ -│ 5. Antigravity API 调用 │ -│ └── POST {base_url}/v1internal/models/{model}:streamGenerateContent │ -│ │ │ -│ ▼ │ -│ 6. 响应转换(根据模型类型) │ -│ ├── Gemini 模型 → Gemini SSE 格式 │ -│ └── Claude 模型 → Anthropic SSE 格式 │ -│ │ │ -│ ▼ │ -│ 7. 返回客户端 │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### 6.2 Antigravity API - -**端点**: `POST {base_url}/v1internal/models/{model}:streamGenerateContent?alt=sse` - -**请求格式**: - -```json -{ - "model": "gemini-3-pro-high", - "contents": [ - { - "role": "user", - "parts": [ - { "text": "Hello, how are you?" } - ] - } - ], - "systemInstruction": { - "parts": [ - { "text": "You are a helpful assistant." } - ] - }, - "generationConfig": { - "maxOutputTokens": 4096, - "temperature": 0.7, - "topP": 0.9, - "candidateCount": 1, - "thinkingConfig": { - "thinkingBudget": 4096 - } - }, - "safetySettings": [ - { "category": "HARM_CATEGORY_HARASSMENT", "threshold": "OFF" }, - { "category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "OFF" }, - { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "OFF" }, - { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "OFF" }, - { "category": "HARM_CATEGORY_CIVIC_INTEGRITY", "threshold": "BLOCK_NONE" } - ] -} -``` - -**响应格式**: Gemini SSE Stream - ---- - -## 七、错误处理 - -### 7.1 错误类型 - -| 错误码 | 说明 | 处理方式 | -|--------|------|---------| -| 401 | Token 过期 | 自动刷新 Token 重试 | -| 403 | 权限不足 / 账户无访问权限 | 标记凭证异常 | -| 429 | 请求限流 | 冷却期后重试 | -| 500 | 服务器错误 | 切换凭证重试 | - -### 7.2 Token 刷新错误 - -```rust -pub enum TokenRefreshError { - MissingRefreshToken, - InvalidRefreshToken, - NetworkError(String), - AuthorizationRevoked, -} -``` - ---- - -## 八、开发指南 - -### 8.1 本地开发 - -```bash -# 克隆仓库 -git clone https://github.com/aiclientproxy/antigravity-provider.git -cd antigravity-provider - -# 安装依赖 -pnpm install -cd src-tauri && cargo build - -# 前端开发 -pnpm dev - -# 后端开发 -cargo watch -x run -``` - -### 8.2 测试 - -```bash -# 单元测试 -cargo test - -# 协议转换测试 -cargo test --test converter - -# 前端测试 -pnpm test -``` - ---- - -## 附录 - -### A. 环境变量 - -| 变量 | 说明 | 默认值 | -|------|------|--------| -| `ANTIGRAVITY_ENVIRONMENT` | API 环境 | `daily` | -| `ANTIGRAVITY_DEBUG` | 调试模式 | `false` | -| `ANTIGRAVITY_TIMEOUT_MS` | 请求超时 | `120000` | -| `ANTIGRAVITY_OAUTH_CLIENT_ID` | OAuth Client ID(必需) | 无,必须设置 | -| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | OAuth Client Secret(必需) | 无,必须设置 | - -**注意**:OAuth 凭据可以从 Antigravity CLI 获取或从 Google Cloud Console 创建。 - -### B. 参考链接 - -- [Google Cloud AI Platform](https://cloud.google.com/ai-platform) -- [Gemini API 文档](https://ai.google.dev/docs) -- [Lime 插件开发指南](../prd/credential-provider-plugin-architecture.md) diff --git a/docs/plugins/claude-provider.md b/docs/plugins/claude-provider.md deleted file mode 100644 index 7a7500325..000000000 --- a/docs/plugins/claude-provider.md +++ /dev/null @@ -1,1255 +0,0 @@ -# Claude Provider 插件文档 - -> 版本: 1.0.0 -> 仓库: `aiclientproxy/claude-provider` -> 类型: OAuth Provider Plugin - ---- - -## 一、概述 - -### 1.1 插件简介 - -Claude Provider 是 Lime 的综合性 Anthropic/Claude 插件,支持 **多种认证方式** 访问 Claude 模型。无论是官方 OAuth、Claude Code、Console、AWS Bedrock 还是第三方中转服务,都可以通过此插件统一管理。 - -### 1.2 支持的认证方式 - -| 认证方式 | 说明 | 适用场景 | -|---------|------|---------| -| **OAuth** | 标准 OAuth 2.0 + PKCE | Claude.ai 个人账户 | -| **Claude Code** | Claude Code CLI 认证 | 开发者工具 | -| **Console** | Anthropic Console OAuth | 企业/团队账户 | -| **Setup Token** | 只读推理 Token | 最小权限场景 | -| **Bedrock** | AWS Bedrock Claude | AWS 云服务 | -| **CCR** | 第三方中转服务 | 自定义 API 端点 | - -### 1.3 核心能力 - -| 能力 | 说明 | -|------|------| -| 多认证统一管理 | 一个插件管理所有 Claude 访问方式 | -| 自动 Token 刷新 | OAuth 类型自动刷新,提前 5 分钟 | -| PKCE 安全 | OAuth 使用 PKCE 流程确保安全 | -| Cookie 快速授权 | 使用 sessionKey 自动完成 OAuth | -| 凭证加密存储 | AES-256 加密敏感信息 | -| 健康检查 | 凭证池级别健康监控 | - -### 1.4 支持的模型 - -| 模型 | 说明 | -|------|------| -| `claude-opus-4-20250514` | Claude Opus 4 最新版 | -| `claude-opus-4-5-20251101` | Claude Opus 4.5 | -| `claude-sonnet-4-5-20250929` | Claude Sonnet 4.5 | -| `claude-sonnet-4-20250514` | Claude Sonnet 4 | -| `claude-haiku-3-5-20241022` | Claude Haiku 3.5 | - ---- - -## 二、插件架构 - -### 2.1 项目结构 - -``` -claude-provider/ -├── plugin/ -│ ├── plugin.json # 插件元数据 -│ └── config.json # 默认配置 -│ -├── src-tauri/src/ # 后端 Rust 代码 -│ ├── lib.rs # 插件入口 -│ ├── commands.rs # Tauri 命令 -│ ├── provider.rs # ClaudeProvider 核心实现 -│ ├── auth/ # 认证模块 -│ │ ├── mod.rs -│ │ ├── oauth.rs # OAuth 2.0 + PKCE -│ │ ├── claude_code.rs # Claude Code 认证 -│ │ ├── console.rs # Console OAuth -│ │ ├── setup_token.rs # Setup Token -│ │ ├── bedrock.rs # AWS Bedrock -│ │ └── ccr.rs # 第三方中转 -│ ├── credentials.rs # 凭证管理 -│ ├── token_refresh.rs # Token 刷新 -│ └── api/ # API 调用 -│ ├── mod.rs -│ ├── anthropic.rs # Anthropic API -│ └── bedrock.rs # Bedrock API -│ -├── src/ # 前端 React UI -│ ├── index.tsx # 插件 UI 入口 -│ ├── components/ -│ │ ├── CredentialList.tsx # 凭证列表 -│ │ ├── CredentialCard.tsx # 凭证卡片 -│ │ ├── AuthMethodTabs.tsx # 认证方式选择 -│ │ ├── OAuthForm.tsx # OAuth 表单 -│ │ ├── ClaudeCodeForm.tsx # Claude Code 表单 -│ │ ├── ConsoleForm.tsx # Console 表单 -│ │ ├── BedrockForm.tsx # Bedrock 表单 -│ │ ├── CCRForm.tsx # CCR 表单 -│ │ └── SettingsPanel.tsx # 插件设置 -│ └── types/ -│ └── index.ts # 类型定义 -│ -└── .github/ - └── workflows/ - └── release.yml # 自动构建发布 -``` - -### 2.2 plugin.json - -```json -{ - "name": "claude-provider", - "version": "1.0.0", - "description": "Claude Provider - 支持 OAuth、Claude Code、Console、Bedrock、CCR 多种认证方式", - "author": "Lime Team", - "homepage": "https://github.com/aiclientproxy/claude-provider", - "license": "MIT", - - "plugin_type": "oauth_provider", - "entry": "claude-provider-cli", - "min_lime_version": "1.0.0", - - "provider": { - "id": "claude", - "display_name": "Claude (Anthropic)", - "target_protocol": "anthropic", - "supported_models": ["claude-*"], - "auth_types": ["oauth", "claude_code", "console", "setup_token", "bedrock", "ccr"], - "credential_schemas": { - "oauth": { - "type": "object", - "properties": { - "access_token": { "type": "string" }, - "refresh_token": { "type": "string" }, - "email": { "type": "string" }, - "expire": { "type": "string" } - }, - "required": ["access_token", "refresh_token"] - }, - "claude_code": { - "type": "object", - "properties": { - "access_token": { "type": "string" }, - "refresh_token": { "type": "string" }, - "session_key": { "type": "string" } - } - }, - "console": { - "type": "object", - "properties": { - "access_token": { "type": "string" }, - "refresh_token": { "type": "string" }, - "organization_id": { "type": "string" } - } - }, - "setup_token": { - "type": "object", - "properties": { - "access_token": { "type": "string" } - }, - "required": ["access_token"] - }, - "bedrock": { - "type": "object", - "properties": { - "access_key_id": { "type": "string" }, - "secret_access_key": { "type": "string" }, - "session_token": { "type": "string" }, - "region": { "type": "string", "default": "us-east-1" } - }, - "required": ["access_key_id", "secret_access_key", "region"] - }, - "ccr": { - "type": "object", - "properties": { - "api_key": { "type": "string" }, - "base_url": { "type": "string" } - }, - "required": ["api_key", "base_url"] - } - } - }, - - "binary": { - "binary_name": "claude-provider-cli", - "github_owner": "aiclientproxy", - "github_repo": "claude-provider", - "platform_binaries": { - "macos-arm64": "claude-provider-aarch64-apple-darwin", - "macos-x64": "claude-provider-x86_64-apple-darwin", - "linux-x64": "claude-provider-x86_64-unknown-linux-gnu", - "windows-x64": "claude-provider-x86_64-pc-windows-msvc.exe" - }, - "checksum_file": "checksums.txt" - }, - - "ui": { - "surfaces": ["oauth_providers"], - "icon": "MessageSquare", - "title": "Claude Provider", - "entry": "dist/index.js", - "styles": "dist/styles.css", - "default_width": 950, - "default_height": 750, - "permissions": [ - "database:read", - "database:write", - "http:request", - "crypto:encrypt", - "shell:open" - ] - } -} -``` - -### 2.3 config.json - -```json -{ - "enabled": true, - "timeout_ms": 60000, - "settings": { - "oauth": { - "client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e", - "auth_url": "https://claude.ai/oauth/authorize", - "token_url": "https://console.anthropic.com/v1/oauth/token", - "redirect_uri": "https://console.anthropic.com/oauth/code/callback", - "scopes": "org:create_api_key user:profile user:inference", - "scopes_setup": "user:inference" - }, - "api": { - "base_url": "https://api.anthropic.com", - "version": "2023-06-01" - }, - "bedrock": { - "default_region": "us-east-1", - "model_prefix": "us.anthropic." - }, - "token_refresh": { - "auto_refresh": true, - "refresh_threshold_minutes": 5, - "max_retry": 3, - "retry_delay_ms": 1000 - }, - "encryption": { - "algorithm": "aes-256-cbc", - "key_derivation": "pbkdf2" - } - } -} -``` - ---- - -## 三、认证方式详解 - -### 3.1 OAuth 认证(标准 Claude.ai) - -#### OAuth 配置 - -```rust -const CLAUDE_AUTH_URL: &str = "https://claude.ai/oauth/authorize"; -const CLAUDE_TOKEN_URL: &str = "https://console.anthropic.com/v1/oauth/token"; -const CLAUDE_CLIENT_ID: &str = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"; -const CLAUDE_REDIRECT_URI: &str = "https://console.anthropic.com/oauth/code/callback"; -const CLAUDE_SCOPES: &str = "org:create_api_key user:profile user:inference"; -``` - -#### PKCE 流程实现 - -```rust -use sha2::{Sha256, Digest}; -use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; -use rand::Rng; - -/// 生成 OAuth 参数(PKCE) -pub fn generate_oauth_params() -> OAuthParams { - // 1. 生成随机 state - let state: [u8; 32] = rand::thread_rng().gen(); - let state = URL_SAFE_NO_PAD.encode(&state); - - // 2. 生成 code_verifier - let code_verifier: [u8; 32] = rand::thread_rng().gen(); - let code_verifier = URL_SAFE_NO_PAD.encode(&code_verifier); - - // 3. 计算 code_challenge = SHA256(code_verifier) - let mut hasher = Sha256::new(); - hasher.update(code_verifier.as_bytes()); - let code_challenge = URL_SAFE_NO_PAD.encode(hasher.finalize()); - - // 4. 构建授权 URL - let auth_url = format!( - "{}?response_type=code&client_id={}&redirect_uri={}&scope={}&state={}&code_challenge={}&code_challenge_method=S256", - CLAUDE_AUTH_URL, - CLAUDE_CLIENT_ID, - urlencoding::encode(CLAUDE_REDIRECT_URI), - urlencoding::encode(CLAUDE_SCOPES), - state, - code_challenge - ); - - OAuthParams { - auth_url, - code_verifier, - state, - code_challenge, - } -} - -/// 交换授权码获取 Token -pub async fn exchange_authorization_code( - authorization_code: &str, - code_verifier: &str, - state: &str, -) -> Result { - let response = reqwest::Client::new() - .post(CLAUDE_TOKEN_URL) - .json(&json!({ - "client_id": CLAUDE_CLIENT_ID, - "grant_type": "authorization_code", - "code": authorization_code, - "redirect_uri": CLAUDE_REDIRECT_URI, - "code_verifier": code_verifier, - "state": state - })) - .send() - .await?; - - let token_response: TokenResponse = response.json().await?; - - Ok(OAuthTokens { - access_token: token_response.access_token, - refresh_token: token_response.refresh_token, - expires_at: Utc::now() + Duration::seconds(token_response.expires_in), - email: token_response.account.map(|a| a.email_address), - }) -} -``` - -#### Cookie 快速授权 - -```rust -/// 使用 sessionKey 自动完成 OAuth 流程 -pub async fn oauth_with_cookie( - session_key: &str, - is_setup_token: bool, -) -> Result { - let client = reqwest::Client::new(); - - // 1. 获取组织信息 - let orgs_response = client - .get("https://claude.ai/api/organizations") - .header("Cookie", format!("sessionKey={}", session_key)) - .header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)") - .header("Origin", "https://claude.ai") - .header("Referer", "https://claude.ai/new") - .send() - .await?; - - let organizations: Vec = orgs_response.json().await?; - - // 2. 选择具有 chat 能力的组织 - let org = organizations - .iter() - .find(|o| o.capabilities.contains(&"chat".to_string())) - .ok_or(Error::NoValidOrganization)?; - - // 3. 生成 OAuth 参数 - let params = generate_oauth_params(); - let scopes = if is_setup_token { CLAUDE_SCOPES_SETUP } else { CLAUDE_SCOPES }; - - // 4. 使用 Cookie 请求授权码 - let auth_url = format!( - "{}?response_type=code&client_id={}&redirect_uri={}&scope={}&state={}&code_challenge={}&code_challenge_method=S256", - CLAUDE_AUTH_URL, CLAUDE_CLIENT_ID, - urlencoding::encode(CLAUDE_REDIRECT_URI), - urlencoding::encode(scopes), - params.state, params.code_challenge - ); - - let auth_response = client - .get(&auth_url) - .header("Cookie", format!("sessionKey={}", session_key)) - .send() - .await?; - - // 5. 解析回调中的授权码 - let callback_url = auth_response.url().to_string(); - let code = extract_code_from_url(&callback_url)?; - - // 6. 交换 Token - exchange_authorization_code(&code, ¶ms.code_verifier, ¶ms.state).await -} -``` - -### 3.2 Claude Code 认证 - -```rust -/// Claude Code 凭证结构 -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ClaudeCodeCredentials { - pub access_token: String, - pub refresh_token: Option, - pub session_key: Option, - pub expires_at: Option>, -} - -/// 默认凭证路径 -const CLAUDE_CODE_CREDS_PATH: &str = "~/.claude/oauth_creds.json"; - -/// 从 Claude Code 配置加载凭证 -pub fn load_claude_code_credentials() -> Result { - let path = expand_tilde(CLAUDE_CODE_CREDS_PATH); - let content = fs::read_to_string(&path)?; - let creds: ClaudeCodeCredentials = serde_json::from_str(&content)?; - Ok(creds) -} -``` - -### 3.3 Console OAuth(企业/团队) - -```rust -/// Console OAuth 配置 -const CONSOLE_AUTH_URL: &str = "https://console.anthropic.com/oauth/authorize"; -const CONSOLE_TOKEN_URL: &str = "https://console.anthropic.com/v1/oauth/token"; - -/// Console 凭证结构 -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ConsoleCredentials { - pub access_token: String, - pub refresh_token: String, - pub organization_id: Option, - pub organization_name: Option, - pub expires_at: DateTime, -} - -/// Console OAuth 流程(与标准 OAuth 类似,但针对企业账户) -pub async fn console_oauth_flow( - authorization_code: &str, - code_verifier: &str, -) -> Result { - // 与标准 OAuth 类似,但返回组织信息 - let response = exchange_authorization_code(authorization_code, code_verifier).await?; - - Ok(ConsoleCredentials { - access_token: response.access_token, - refresh_token: response.refresh_token, - organization_id: response.organization.map(|o| o.id), - organization_name: response.organization.map(|o| o.name), - expires_at: response.expires_at, - }) -} -``` - -### 3.4 Setup Token(最小权限) - -```rust -/// Setup Token - 只有推理权限,无 refresh_token -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SetupTokenCredentials { - pub access_token: String, - pub creds_type: String, // "claude_setup_token" -} - -/// 使用 Cookie 获取 Setup Token -pub async fn get_setup_token(session_key: &str) -> Result { - let tokens = oauth_with_cookie(session_key, true /* is_setup_token */).await?; - - Ok(SetupTokenCredentials { - access_token: tokens.access_token, - creds_type: "claude_setup_token".to_string(), - }) -} -``` - -### 3.5 AWS Bedrock - -```rust -/// AWS Bedrock 凭证结构 -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BedrockCredentials { - pub access_key_id: String, - pub secret_access_key: String, - pub session_token: Option, - pub region: String, - pub default_model: Option, -} - -/// Bedrock 模型映射 -const BEDROCK_MODEL_MAP: &[(&str, &str)] = &[ - ("claude-opus-4-20250514", "us.anthropic.claude-opus-4-20250514-v1:0"), - ("claude-sonnet-4-20250514", "us.anthropic.claude-sonnet-4-20250514-v1:0"), - ("claude-sonnet-4-5-20250929", "us.anthropic.claude-sonnet-4-5-20250929-v1:0"), - ("claude-haiku-3-5-20241022", "us.anthropic.claude-haiku-3-5-20241022-v1:0"), -]; - -/// Bedrock API 调用 -pub async fn call_bedrock_api( - credentials: &BedrockCredentials, - request: &AnthropicRequest, -) -> Result>> { - // 1. 模型名映射 - let model_id = map_to_bedrock_model(&request.model); - - // 2. 构建 AWS 签名 - let aws_credentials = AwsCredentials::new( - &credentials.access_key_id, - &credentials.secret_access_key, - credentials.session_token.as_deref(), - ); - - // 3. 调用 Bedrock API - let url = format!( - "https://bedrock-runtime.{}.amazonaws.com/model/{}/invoke-with-response-stream", - credentials.region, - model_id - ); - - let signed_request = sign_aws_request( - "POST", - &url, - &aws_credentials, - &credentials.region, - "bedrock", - &serde_json::to_vec(request)?, - )?; - - // 4. 发送请求并返回流 - let response = reqwest::Client::new() - .post(&url) - .headers(signed_request.headers) - .body(signed_request.body) - .send() - .await?; - - Ok(parse_bedrock_stream(response.bytes_stream())) -} -``` - -### 3.6 CCR(第三方中转) - -```rust -/// CCR(Custom Claude Relay)凭证结构 -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CCRCredentials { - pub api_key: String, - pub base_url: String, - pub name: Option, -} - -/// CCR API 调用(直接转发) -pub async fn call_ccr_api( - credentials: &CCRCredentials, - request: &AnthropicRequest, -) -> Result>> { - let url = format!("{}/v1/messages", credentials.base_url); - - let response = reqwest::Client::new() - .post(&url) - .header("x-api-key", &credentials.api_key) - .header("anthropic-version", "2023-06-01") - .header("Content-Type", "application/json") - .json(request) - .send() - .await?; - - Ok(parse_anthropic_sse_stream(response.bytes_stream())) -} -``` - ---- - -## 四、Token 刷新机制 - -### 4.1 自动刷新逻辑 - -```rust -impl ClaudeProvider { - /// Token 刷新阈值(提前 5 分钟) - const REFRESH_THRESHOLD: Duration = Duration::minutes(5); - - /// 检查 Token 是否需要刷新 - pub fn needs_refresh(&self, credentials: &OAuthCredentials) -> bool { - if let Some(expires_at) = credentials.expires_at { - let now = Utc::now(); - return now >= expires_at - Self::REFRESH_THRESHOLD; - } - true // 无过期时间则默认需要刷新 - } - - /// 刷新 Token(带重试) - pub async fn refresh_token_with_retry( - &self, - credentials: &mut OAuthCredentials, - max_retries: u32, - ) -> Result<()> { - let mut last_error = None; - - for attempt in 0..max_retries { - match self.refresh_token(credentials).await { - Ok(_) => return Ok(()), - Err(e) => { - last_error = Some(e); - // 指数退避 - let delay = Duration::milliseconds(1000 * 2_i64.pow(attempt)); - tokio::time::sleep(delay.to_std().unwrap()).await; - } - } - } - - Err(last_error.unwrap()) - } - - /// 刷新 Token - async fn refresh_token(&self, credentials: &mut OAuthCredentials) -> Result<()> { - let refresh_token = credentials.refresh_token.as_ref() - .ok_or(Error::MissingRefreshToken)?; - - let response = self.http_client - .post(CLAUDE_TOKEN_URL) - .json(&json!({ - "client_id": CLAUDE_CLIENT_ID, - "grant_type": "refresh_token", - "refresh_token": refresh_token - })) - .send() - .await?; - - if !response.status().is_success() { - let error_text = response.text().await?; - return Err(Error::TokenRefreshFailed(error_text)); - } - - let token_response: TokenResponse = response.json().await?; - - // 更新凭证 - credentials.access_token = token_response.access_token; - if let Some(new_refresh) = token_response.refresh_token { - credentials.refresh_token = Some(new_refresh); - } - credentials.expires_at = Some(Utc::now() + Duration::seconds(token_response.expires_in)); - credentials.last_refresh = Some(Utc::now()); - - // 更新邮箱(如果有) - if let Some(account) = token_response.account { - credentials.email = Some(account.email_address); - } - - Ok(()) - } -} -``` - ---- - -## 五、前端 UI 实现 - -### 5.1 插件入口 - -```tsx -// src/index.tsx -import { LimePluginSDK } from '@lime/plugin-sdk'; -import { CredentialList } from './components/CredentialList'; -import { AuthMethodTabs } from './components/AuthMethodTabs'; -import { SettingsPanel } from './components/SettingsPanel'; - -interface PluginProps { - sdk: LimePluginSDK; - pluginId: string; -} - -export default function ClaudeProviderUI({ sdk, pluginId }: PluginProps) { - const [view, setView] = useState<'list' | 'add' | 'settings'>('list'); - const [credentials, setCredentials] = useState([]); - - useEffect(() => { - loadCredentials(); - }, []); - - const loadCredentials = async () => { - const result = await sdk.database.query( - 'SELECT * FROM plugin_credentials WHERE plugin_id = ? ORDER BY created_at DESC', - [pluginId] - ); - setCredentials(result); - }; - - return ( -
-
- Claude Provider - 支持 OAuth、Claude Code、Console、Bedrock、CCR - - - - -
- - {view === 'list' && ( - - )} - - {view === 'add' && ( - { - loadCredentials(); - setView('list'); - }} - onCancel={() => setView('list')} - /> - )} - - {view === 'settings' && ( - setView('list')} - /> - )} -
- ); -} -``` - -### 5.2 认证方式选择 - -```tsx -// src/components/AuthMethodTabs.tsx - -type AuthMethod = 'oauth' | 'claude_code' | 'console' | 'setup_token' | 'bedrock' | 'ccr'; - -interface AuthMethodTabsProps { - sdk: LimePluginSDK; - onSuccess: () => void; - onCancel: () => void; -} - -export function AuthMethodTabs({ sdk, onSuccess, onCancel }: AuthMethodTabsProps) { - const [method, setMethod] = useState('oauth'); - - return ( -
- - - - OAuth - - - - Claude Code - - - - Console - - - - Setup Token - - - - Bedrock - - - - CCR - - - -
- {method === 'oauth' && } - {method === 'claude_code' && } - {method === 'console' && } - {method === 'setup_token' && } - {method === 'bedrock' && } - {method === 'ccr' && } -
- - - - -
- ); -} -``` - -### 5.3 OAuth 表单 - -```tsx -// src/components/OAuthForm.tsx - -type OAuthMode = 'browser' | 'cookie' | 'file'; - -export function OAuthForm({ sdk, onSuccess }: FormProps) { - const [mode, setMode] = useState('cookie'); - const [sessionKey, setSessionKey] = useState(''); - const [authUrl, setAuthUrl] = useState(''); - const [loading, setLoading] = useState(false); - - const handleCookieAuth = async () => { - setLoading(true); - try { - await sdk.http.request('/api/claude/oauth/cookie', { - method: 'POST', - body: JSON.stringify({ sessionKey, isSetupToken: false }), - }); - sdk.notification.success('OAuth 认证成功'); - onSuccess(); - } catch (error) { - sdk.notification.error(`认证失败: ${error.message}`); - } finally { - setLoading(false); - } - }; - - const handleBrowserAuth = async () => { - setLoading(true); - try { - const result = await sdk.http.request('/api/claude/oauth/start'); - setAuthUrl(result.authUrl); - await sdk.shell.open(result.authUrl); - - // 等待回调 - const credential = await sdk.http.request('/api/claude/oauth/callback/wait', { - timeout: 120000, - }); - sdk.notification.success('OAuth 认证成功'); - onSuccess(); - } catch (error) { - sdk.notification.error(`认证失败: ${error.message}`); - } finally { - setLoading(false); - } - }; - - return ( -
- - Cookie 快速授权 - 浏览器授权 - 导入文件 - - - {mode === 'cookie' && ( -
- - -