Initial TrueGrowth source import

This commit is contained in:
2026-07-07 09:36:36 +08:00
commit 3b6781d695
2283 changed files with 691996 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
## Context
当前代码已经具备以下基础能力:
- `settings-manager` 可以管理 `ProviderProfile / ProviderCatalog / ModelRef / InvocationPreset`
- `runtime-model-discovery` 可以按 `profileId` 隔离发现和选择模型
- 图片和视频侧已经存在若干专用 adapter分别处理 Flux、MJ、Kling、Seedance 等协议变体
但当前结构仍存在三个核心缺口:
1. `resolveInvocationRoute` 只解析凭证和模型,不解析协议与请求体策略
2. `runtime-model-discovery` 仍以统一 `/models + Bearer` 的假设执行发现
3. `model-adapters` 仍然按模型 / 厂商 / 标签进行猜测式匹配,无法表达“同名模型在不同 profile 下走不同协议”的事实
这会导致下列问题:
- 同名模型在不同上游中发生协议冲突
- 同协议不同请求体的差异只能堆在 adapter 内的条件分支中,难以扩展
- 文本链路继续绕过统一路由层,无法和图片 / 视频共享同一套供应商协议规划
## Goals / Non-Goals
- Goals:
- 让运行时调用基于 `profileId + modelId + operation` 选择协议绑定
- 支持同一模型 ID 在不同供应商下走不同协议
- 支持同一协议下的不同请求体 schema
- 统一文本、图片、视频三类调用的运行时规划流程
- 尽量降低用户配置成本,优先自动推断协议绑定,仅在歧义时暴露高级覆盖项
- Non-Goals:
- 本次不实现跨供应商自动故障切换
- 本次不实现按价格 / 延迟 / 健康度自动择优调度
- 本次不为所有第三方平台实现完全自动化、零误差的协议识别
## Decisions
- Decision: 区分“模型标识”和“可执行绑定”
- `modelId` 表示用户看到的模型
- `ProviderModelBinding` 表示该模型在特定供应商下针对特定操作的调用方式
- 绑定的主键必须至少包含 `profileId + modelId + operation`
- Decision: 引入 `InvocationPlanner`
- 输入:`routeType + ModelRef + optional override`
- 输出:`InvocationPlan`
- Planner 负责在多个绑定中选择当前最合适的协议,而不是让 UI 或 service 直接拼 URL
- Decision: 将适配器从“按模型注册”迁移为“按协议注册”
- 现有 Flux / MJ / Kling / Seedance 等实现可以保留,但角色改为 `ProtocolAdapter`
- `ProtocolAdapter` 负责请求体构建、响应解析、可选轮询策略
- Decision: 将鉴权和基础 URL 处理下沉到 `ProviderTransport`
- 统一处理 `bearer / header / query / custom` 等认证方式
- 统一处理额外 Header、查询参数和供应商级发现接口
- 避免每个 adapter 再重复拼装认证信息
- Decision: discovery 必须保留原始元数据和绑定推断结果
- 不能只把远端模型压成扁平 `ModelConfig`
- 必须保留 `raw``capabilityHints``bindingCandidates`
- 否则后续运行时无法稳定判断同名模型的协议归属
- Decision: 同名模型不得按裸 `modelId` 全局去重
- 选择器和运行时缓存应使用 `selectionKey = profileId::modelId`
- 运行时绑定必须保留 `profileId`
## Proposed Data Model
```ts
interface ResolvedProviderContext {
profileId: string;
profileName: string;
providerType: string;
baseUrl: string;
apiKey: string;
authType: 'bearer' | 'header' | 'query' | 'custom';
extraHeaders?: Record<string, string>;
}
interface ProviderModelBinding {
id: string;
profileId: string;
modelId: string;
operation: 'text' | 'image' | 'video';
protocol:
| 'openai.chat.completions'
| 'openai.images.generations'
| 'openai.async.video'
| 'google.generateContent'
| 'mj.imagine'
| 'flux.task'
| 'kling.video'
| 'seedance.task';
requestSchema: string;
responseSchema: string;
submitPath: string;
pollPathTemplate?: string;
priority: number;
confidence: 'high' | 'medium' | 'low';
source: 'discovered' | 'template' | 'manual';
}
interface DiscoveredProviderModel {
profileId: string;
modelId: string;
selectionKey: string;
raw: unknown;
capabilityHints: {
supportsText: boolean;
supportsImage: boolean;
supportsVideo: boolean;
};
bindings: ProviderModelBinding[];
}
interface InvocationPlan {
provider: ResolvedProviderContext;
modelRef: { profileId: string; modelId: string };
binding: ProviderModelBinding;
}
```
## Execution Flow
1. 用户在设置中保存 `ProviderProfile`
2. `ProviderTransport` 根据 `providerType/baseUrl` 选择供应商模板或探测策略
3. discovery 同步远端模型,并推断出每个模型的 `bindings[]`
4. 用户在运行时选择 `ModelRef(profileId + modelId)`
5. `InvocationPlanner` 根据 `operation` 找到最高优先级的可执行 binding
6. `ProtocolAdapterRegistry``binding.protocol` 取得协议适配器
7. 适配器根据 `requestSchema` 构建请求,并通过 `ProviderTransport` 发送
8. 如为异步任务,则由对应 `PollingStrategy` 轮询并标准化结果
## Binding Inference Strategy
- OpenAI 兼容供应商:
- 优先使用 `/v1/models` 返回的字段、模型 ID 和供应商模板推断绑定
- 默认优先生成 `openai.chat.completions / openai.images.generations / openai.async.video`
- Gemini 官方供应商:
- 不再强依赖 `/models`
- 允许由 provider template 直接提供 `google.generateContent` 绑定
- 自定义供应商:
- 若 discovery 无法高置信度推断协议,则保留低置信度绑定
- 仅在需要时暴露高级覆盖入口
## Request Schema Strategy
同一协议下的请求体差异不再通过大量 `if modelId.includes(...)` 解决,而是显式建模:
- `openai.image.basic-json`
- `openai.video.form.input-reference`
- `google.gemini.generate-content.image`
- `kling.video.text2video`
- `kling.video.image2video`
- `seedance.video.first-last-frame`
这样新增模型时,优先复用已有协议,仅为其指派合适的 `requestSchema`
## Migration Plan
1. 新增 provider protocol routing 类型与 planner不改 UI
2. 将现有图片 / 视频 adapter 包装为 `ProtocolAdapter`
3. 将文本链路迁移到同一 planner 和 protocol registry
4. 扩展 discovery 存储,保留 `bindings[]`
5. 最后再清理旧的“按模型猜 adapter”逻辑
## Risks / Trade-offs
- 风险: 架构抽象增加初始复杂度
- Mitigation: 分阶段迁移,先保留现有 adapter 实现,只调整其注册方式
- 风险: 自动推断协议可能存在误判
- Mitigation: 为 binding 引入 `confidence``source`,仅在低置信度场景允许高级覆盖
- 风险: 文本 / 图片 / 视频同时迁移会影响面较大
- Mitigation: 先完成 planner 和 image/video 迁移,再迁移 text
## Open Questions
- 是否需要将用户手动覆盖的 binding 保存在 `ProviderCatalog`,还是单独存为 `ProviderBindingOverrides`
- `authType` 是否需要从当前的 `bearer/header` 扩展为更通用的 `query/custom`
- 官方 Gemini 等不提供 OpenAI 风格 `/models` 时,设置页是否需要显式展示“供应商模板已接管模型能力”

View File

@@ -0,0 +1,45 @@
# Change: 增加供应商协议路由与请求体适配架构
## Why
当前多供应商架构已经能够保存多个 `baseUrl + apiKey`,并通过 `ModelRef(profileId + modelId)` 将模型归属到特定供应商。
但运行时请求仍然主要依赖“模型 ID / 厂商标签 -> adapter”的方式来决定执行链路这在以下场景下会失效
- 同一个模型 ID 在不同上游中需要走不同协议,例如:
- 上游 A 将 `gemini-3-pro-image-preview` 暴露为 `/v1/images/generations`
- 上游 B 将同名模型暴露为 Google 官方 `:generateContent`
- 同一个上游中的同一个模型可能支持多个调用协议,需要为不同操作或高级模式选择不同入口
- 即使协议相同,不同模型之间也可能要求不同的请求体字段、序列化格式、轮询方式和结果提取规则
- 文本链路目前仍然绕过现有图片/视频适配层,无法与多供应商路由形成统一模型
这意味着当前系统已经具备“凭证路由”,但还不具备“协议路由”和“请求体路由”。
## What Changes
- 引入 `ProviderModelBinding`,将“模型如何被调用”从 `modelId` 中解耦出来
- 引入 `InvocationPlanner`,统一根据 `profileId + modelId + operation` 选择协议绑定
- 引入 `ProviderTransport`,统一处理鉴权、基础 URL 归一化、额外 Header、查询参数和供应商级发现入口
- 将现有 `model-adapters` 重构为按 `protocol` 注册的协议适配器,而不是按模型猜测执行器
- 为协议适配器引入 `requestSchema / responseSchema / pollingStrategy` 概念,覆盖同协议不同请求体格式的场景
- 将文本、图片、视频三类调用统一接入同一套规划器与协议注册器
- 扩展运行时模型发现存储,保留每个供应商下模型的原始元数据、推断出的能力和绑定信息,避免仅依赖扁平化 `ModelConfig`
## Impact
- Affected specs:
- `provider-protocol-routing`
- Affected code:
- `packages/drawnix/src/utils/settings-manager.ts`
- `packages/drawnix/src/utils/runtime-model-discovery.ts`
- `packages/drawnix/src/services/model-adapters/*`
- `packages/drawnix/src/services/generation-api-service.ts`
- `packages/drawnix/src/utils/gemini-api/*`
- `packages/drawnix/src/services/video-api-service.ts`
- `packages/drawnix/src/services/async-image-api-service.ts`
- `packages/drawnix/src/services/media-executor/*`
## Relationship To Existing Changes
- 本变更建立在 `add-multi-provider-profiles``profiles + catalogs + presets` 结构之上
- 本变更进一步落实 `add-multi-provider-profiles/design.md` 中“将 `ProviderType/AuthType/capabilities` 下沉到适配层”的决策
- 本变更会扩展 `add-runtime-model-discovery` 的职责,使其不只负责模型列表同步,还要为运行时路由保留协议与绑定信息

View File

@@ -0,0 +1,81 @@
## ADDED Requirements
### Requirement: Route Provider-Backed Models Through Explicit Protocol Bindings
The system SHALL resolve provider-backed invocations through explicit protocol bindings instead of inferring the execution path from the bare model identifier alone.
#### Scenario: Same model ID uses different protocols in different provider profiles
- **GIVEN** two enabled provider profiles both expose a model with the ID `gemini-3-pro-image-preview`
- **AND** one profile binds image generation to an OpenAI-compatible `/images/generations` protocol while the other binds image generation to a Gemini official `:generateContent` protocol
- **WHEN** the user invokes image generation with a `ModelRef` that points to one of those provider profiles
- **THEN** the system SHALL choose the protocol binding that belongs to that provider profile
- **AND** SHALL not assume that the same model ID implies the same protocol across providers
#### Scenario: Same provider model has multiple protocol bindings
- **GIVEN** a provider-backed model has more than one protocol binding for the same operation
- **WHEN** the user invokes that operation without a manual binding override
- **THEN** the system SHALL choose the highest-priority valid binding for that invocation
- **AND** SHALL allow lower-priority bindings to remain available for advanced override flows
### Requirement: Adapt Request Bodies Through Request Schemas
The system SHALL separate protocol selection from request body construction so that models using the same protocol can still be invoked with different request schemas.
#### Scenario: Same protocol requires different fields for different models
- **GIVEN** two models use the same logical protocol family
- **AND** one requires a JSON body while the other requires multipart form fields such as `input_reference` or `first_frame_image`
- **WHEN** the system builds a request for either model
- **THEN** it SHALL use the request schema associated with that model binding
- **AND** SHALL not rely on a single shared payload shape for every model in that protocol family
#### Scenario: Same operation uses different request schemas under one provider
- **GIVEN** a provider exposes multiple video-capable models
- **AND** those models require different request field layouts for the same video generation operation
- **WHEN** the user invokes video generation
- **THEN** the system SHALL select the request schema attached to the chosen binding
- **AND** SHALL serialize the request body accordingly
### Requirement: Unify Text, Image, And Video Invocation Planning
The system SHALL use one invocation planner for text, image, and video requests so that every modality resolves through the same provider context and binding model.
#### Scenario: Text invocation follows the same planner as image and video
- **GIVEN** a provider profile exposes text and image models through different protocols
- **WHEN** the user invokes a text request and then an image request
- **THEN** both requests SHALL resolve through the same invocation planning pipeline
- **AND** each request SHALL select its own binding based on the requested operation
### Requirement: Apply Provider Transport Strategies At Runtime
The system SHALL execute each binding through a provider transport that applies the binding's provider-specific authentication and request transport rules.
#### Scenario: Different providers require different authentication styles
- **GIVEN** one provider binding requires Bearer authentication
- **AND** another provider binding requires a provider-specific header or query parameter
- **WHEN** the system executes invocations for those bindings
- **THEN** the provider transport SHALL apply the correct authentication strategy for each provider
- **AND** the protocol adapter SHALL not hardcode a single authentication style for all providers
### Requirement: Preserve Binding Identity Across Discovery And Selection
The system SHALL preserve provider and binding provenance across discovery, model selection, and runtime invocation.
#### Scenario: Discovery keeps same model ID separate by provider
- **GIVEN** two provider profiles discover models with the same `modelId`
- **WHEN** the discovery results are stored and exposed to selectors
- **THEN** the system SHALL preserve each model's `profileId` and selection identity
- **AND** SHALL not collapse those entries into one global runtime model solely by `modelId`
#### Scenario: Runtime invocation can resolve binding metadata from selection
- **GIVEN** the user selected a provider-backed model in a selector
- **WHEN** a request is submitted later from another entry point
- **THEN** the system SHALL be able to recover the provider context and binding metadata from that selection
- **AND** SHALL route the request through the same provider-specific protocol binding

View File

@@ -0,0 +1,49 @@
## 1. Types And Storage
- [ ] 1.1 为运行时路由新增 `ResolvedProviderContext / ProviderModelBinding / InvocationPlan` 类型
- [ ] 1.2 扩展 provider catalog 存储结构,保存每个模型的原始元数据与绑定信息
- [ ] 1.3 停止按裸 `modelId` 作为跨 profile 的全局唯一键,统一使用 `selectionKey`
## 2. Provider Transport Layer
- [ ] 2.1 新增 `ProviderTransport` 抽象,统一处理 base URL、认证方式、额外 Header 和查询参数
- [ ] 2.2 为 `openai-compatible / gemini-compatible / custom` 提供 transport 模板或探测入口
- [ ] 2.3 将 discovery 从固定 `/models + Bearer` 改为通过 transport 执行
## 3. Binding Inference
- [ ] 3.1 为 discovery 增加 `bindingCandidates` 推断逻辑
- [ ] 3.2 支持同一模型在同一 profile 下生成多个协议绑定
- [ ] 3.3 为推断结果标记 `priority / confidence / source`
## 4. Invocation Planner
- [ ] 4.1 新增 `InvocationPlanner`,输入 `routeType + ModelRef`,输出 `InvocationPlan`
- [ ] 4.2 让 planner 支持显式模型、默认 preset 模型和 legacy 回退
- [ ] 4.3 当存在多个 binding 时,按优先级和能力筛选规则选择默认协议
## 5. Protocol Adapter Registry
- [ ] 5.1 将现有 `model-adapters` 重构为按 `protocol` 注册的适配器
- [ ] 5.2 为现有 Flux / MJ / Kling / Seedance / Seedream 适配器补齐 `requestSchema` 与标准化输出
- [ ] 5.3 为文本链路新增 `openai.chat.completions``google.generateContent` 适配器
- [ ] 5.4 为异步任务引入可复用的 `PollingStrategy`
## 6. Integration
- [ ] 6.1 将 `generation-api-service` 改为先走 planner再取 protocol adapter
- [ ] 6.2 将 `gemini-api` 文本 / 图片 / 视频调用改为通过 planner 解析协议
- [ ] 6.3 将 `media-executor` 与 fallback 执行链改为复用相同的 planner 和 adapter registry
## 7. UI And Advanced Overrides
- [ ] 7.1 设置页继续保持“最少配置”,默认自动推断 binding
- [ ] 7.2 在歧义场景下,为高级用户提供协议绑定覆盖入口
- [ ] 7.3 在模型管理界面中展示模型来源和可用协议摘要,而不是暴露底层细节给普通用户
## 8. Verification
- [ ] 8.1 补充“同名模型不同 profile 不同协议”的路由测试
- [ ] 8.2 补充“同协议不同 requestSchema”的请求体构建测试
- [ ] 8.3 补充 discovery、planner、adapter registry 的集成测试
- [ ] 8.4 手工验证 `gemini-3-pro-image-preview` 在 OpenAI 兼容上游与 Gemini 官方上游中的双协议路由行为