263 lines
9.2 KiB
Markdown
263 lines
9.2 KiB
Markdown
## Context
|
|
|
|
> Superseded note: the later `update-image-api-compatibility-modes` change
|
|
> refines this design. In current implementation guidance, the legacy
|
|
> `tuzi-compatible` value is accepted only as an alias for `tuzi-gpt-image`,
|
|
> Tuzi GPT Image routes through the dedicated Tuzi GPT Image adapter, and
|
|
> `openai-compatible-basic` remains the generic fallback/default adapter path.
|
|
|
|
The project already has multi-provider concepts:
|
|
|
|
- `ProviderProfile` stores the API key, base URL, provider type, and auth mode.
|
|
- `ModelRef` stores `profileId + modelId`.
|
|
- `ProviderModelBinding` stores `profileId + modelId + operation + protocol + requestSchema`.
|
|
- The model adapter registry already prefers `requestSchema` matches over protocol and model matches.
|
|
|
|
This gives the system enough structure to solve the GPT Image compatibility issue without binding every `gpt-image-*` model directly to one adapter.
|
|
|
|
## Goals / Non-Goals
|
|
|
|
- Goals:
|
|
- Allow the same GPT Image model ID to route differently by provider profile/key.
|
|
- Preserve the current default adapter compatibility path.
|
|
- Add a dedicated official GPT Image generation path.
|
|
- Keep the user-facing configuration understandable.
|
|
- Keep this change smaller than the full image operation abstraction.
|
|
- Non-Goals:
|
|
- No new image edit task type.
|
|
- No new public `edit_image` tool.
|
|
- No automatic paid request fallback/retry across compatibility modes.
|
|
- No provider-wide rewrite of all image adapters.
|
|
|
|
## Decisions
|
|
|
|
- Decision: Store compatibility on `ProviderProfile`.
|
|
|
|
The compatibility difference is caused by the API key/profile, not by the model ID alone. The profile is therefore the right place for this setting.
|
|
|
|
- Decision: Name the field `imageApiCompatibility`.
|
|
|
|
The field describes the upstream image API contract. It does not expose internal adapter or schema names to users.
|
|
|
|
- Decision: Use user-facing values that map to internal schemas.
|
|
|
|
```ts
|
|
type ImageApiCompatibility =
|
|
| 'auto'
|
|
| 'openai-gpt-image'
|
|
| 'tuzi-compatible'
|
|
| 'openai-compatible-basic';
|
|
```
|
|
|
|
- Decision: Keep `auto` as a stored value.
|
|
|
|
`auto` should not be rewritten into a concrete mode during normalization. Runtime inference can evolve later while existing profiles continue to benefit from better inference rules.
|
|
|
|
- Decision: Resolve compatibility before binding inference.
|
|
|
|
The binding layer maps the resolved compatibility to a `requestSchema`. The adapter registry then selects the implementation by schema.
|
|
|
|
## Architecture Overview
|
|
|
|
This change adds a narrow compatibility-selection layer between provider profile resolution and image binding inference.
|
|
|
|
Runtime flow:
|
|
|
|
```text
|
|
Image request
|
|
-> ModelRef(profileId + modelId)
|
|
-> ProviderProfile
|
|
-> imageApiCompatibility
|
|
-> resolved image compatibility
|
|
-> ProviderModelBinding.requestSchema
|
|
-> model adapter registry
|
|
-> default image adapter OR GPT Image adapter
|
|
-> ProviderTransport
|
|
```
|
|
|
|
The important architectural boundary is that `modelId` identifies what the user selected, while `imageApiCompatibility` describes the request contract exposed by the chosen API key/profile.
|
|
|
|
### Data Model
|
|
|
|
Add a profile-level field:
|
|
|
|
```ts
|
|
type ImageApiCompatibility =
|
|
| 'auto'
|
|
| 'openai-gpt-image'
|
|
| 'tuzi-compatible'
|
|
| 'openai-compatible-basic';
|
|
|
|
interface ProviderProfile {
|
|
imageApiCompatibility?: ImageApiCompatibility;
|
|
}
|
|
```
|
|
|
|
Propagate the same field into `ProviderProfileSnapshot` because binding inference operates on snapshots rather than full settings objects.
|
|
|
|
### Request Schema Contract
|
|
|
|
The request schema is the stable handoff between routing and adapters:
|
|
|
|
```text
|
|
openai.image.basic-json
|
|
-> existing default image adapter
|
|
|
|
openai.image.gpt-generation-json
|
|
-> new GPT Image adapter
|
|
```
|
|
|
|
The adapter registry already scores `requestSchema` matches above protocol and model matches. This change relies on that behavior so official GPT Image requests are not selected by bare model ID alone.
|
|
|
|
### Module Responsibilities
|
|
|
|
- `settings-manager.ts`
|
|
- Owns the persisted profile field and normalization.
|
|
- Keeps missing values as `auto`.
|
|
- `settings-dialog.tsx`
|
|
- Lets users choose the profile's image interface format.
|
|
- Shows user-facing labels only.
|
|
- `provider-routing/types.ts`
|
|
- Adds the field to `ProviderProfileSnapshot`.
|
|
- `provider-routing/settings-repository.ts`
|
|
- Copies the field from settings profiles into snapshots.
|
|
- `provider-routing/binding-inference.ts`
|
|
- Resolves `auto` to a concrete compatibility mode.
|
|
- Maps the resolved mode to `requestSchema`.
|
|
- `model-adapters/gpt-image-adapter.ts`
|
|
- Implements official GPT Image generation request and response handling.
|
|
- `model-adapters/default-adapters.ts`
|
|
- Continues to serve Tuzi/basic and generic OpenAI-compatible image formats.
|
|
|
|
## Compatibility Resolution
|
|
|
|
Manual values win over `auto`.
|
|
|
|
Recommended `auto` inference:
|
|
|
|
- `api.openai.com` + `gpt-image*` -> `openai-gpt-image`
|
|
- `api.tu-zi.com` -> `tuzi-compatible`
|
|
- other `openai-compatible` or `custom` profiles -> `openai-compatible-basic`
|
|
- `gemini-compatible` profiles do not use GPT Image routing
|
|
|
|
## Request Schema Mapping
|
|
|
|
- `openai-gpt-image` + GPT Image model:
|
|
- `requestSchema: openai.image.gpt-generation-json`
|
|
- adapter: `gpt-image-adapter`
|
|
- `tuzi-gpt-image`:
|
|
- `requestSchema: tuzi.image.gpt-generation-json`
|
|
- adapter: `tuzi-gpt-image-adapter`
|
|
- `openai-compatible-basic`:
|
|
- `requestSchema: openai.image.basic-json`
|
|
- adapter: existing default image adapter
|
|
|
|
Official edit support uses:
|
|
|
|
- `openai.image.gpt-edit-form`
|
|
|
|
## Adapter Boundaries
|
|
|
|
The existing default adapter remains the compatibility path for generic OpenAI-compatible image providers. Tuzi GPT Image-specific behavior belongs to the dedicated Tuzi GPT Image adapter.
|
|
|
|
The new GPT Image adapter only handles official GPT Image request schemas. It should not match every `gpt-image-*` model by model ID alone.
|
|
|
|
Initial GPT Image adapter scope:
|
|
|
|
- POST `/images/generations`
|
|
- Do not default `response_format` to `url`
|
|
- Prefer `b64_json` parsing
|
|
- Accept URL responses for gateway compatibility
|
|
- Support official generation fields such as `size`, `quality`, `n`, `output_format`, `output_compression`, `background`, and `moderation` when present
|
|
|
|
### GPT Image Adapter Request Policy
|
|
|
|
The adapter should only include optional fields when the caller provided them or when the project already has a safe default for that official field.
|
|
|
|
It should not:
|
|
|
|
- inject `response_format: url` by default
|
|
- add provider-specific prompt prefixes
|
|
- use the basic compatibility `image` field for official edit semantics
|
|
|
|
It should:
|
|
|
|
- POST official text-to-image requests to `/images/generations`
|
|
- prefer `b64_json` response data
|
|
- tolerate URL responses from gateway variants
|
|
- normalize returned images to the existing `ImageGenerationResult`
|
|
|
|
### Default Adapter Policy
|
|
|
|
The default image adapter remains the compatibility adapter for:
|
|
|
|
- `tuzi-compatible`
|
|
- `openai-compatible-basic`
|
|
- unknown or broad OpenAI-compatible image gateways
|
|
|
|
Its existing GPT Image behavior is preserved to protect current users and API keys.
|
|
|
|
## Settings UI
|
|
|
|
Add a profile-level field near provider type / API URL / API key:
|
|
|
|
Label: `图片接口格式`
|
|
|
|
Options:
|
|
|
|
- `自动`
|
|
- `OpenAI GPT Image 格式`
|
|
- `兔子兼容格式`
|
|
- `通用 OpenAI 兼容格式`
|
|
|
|
Suggested helper text:
|
|
|
|
`同一个图片模型在不同 API Key 或网关下可能需要不同接口格式;不确定时使用自动。`
|
|
|
|
The UI should not mention adapters, request schemas, or routing internals.
|
|
|
|
## Observability
|
|
|
|
Generation diagnostics should expose enough non-secret context to debug misrouting:
|
|
|
|
- `profileId`
|
|
- `modelId`
|
|
- stored `imageApiCompatibility`
|
|
- resolved compatibility
|
|
- `requestSchema`
|
|
- adapter ID
|
|
- submit path
|
|
|
|
API keys and full authorization headers must never be logged.
|
|
|
|
## Migration
|
|
|
|
- Old profiles without `imageApiCompatibility` normalize to `auto`.
|
|
- Legacy default and managed Tuzi profiles should store `auto`.
|
|
- At runtime, `auto` for `api.tu-zi.com` resolves to `tuzi-compatible`, preserving current behavior.
|
|
- Copying a provider profile should copy the field.
|
|
- Deleting or disabling profiles requires no special handling.
|
|
|
|
## Risks
|
|
|
|
- `auto` inference may choose the wrong compatibility mode for a key.
|
|
- The adapter registry may accidentally select the default adapter if schema matching is incomplete.
|
|
- Default adapter changes may break existing Tuzi/basic image keys.
|
|
- Official GPT Image and gateway variants may return mixed response shapes.
|
|
- The UI setting may be unclear without concise helper text.
|
|
- Future `/images/edits` support may need a broader image operation abstraction.
|
|
|
|
## Rollout Plan
|
|
|
|
1. Add data model support and keep all existing profiles resolving to current behavior.
|
|
2. Add routing/schema split while defaulting Tuzi/basic profiles to the existing adapter.
|
|
3. Add GPT Image generation adapter and route only `openai-gpt-image` profiles to it.
|
|
4. Add UI controls and observability.
|
|
5. Add tests and manually verify two profiles with the same GPT model ID but different compatibility modes.
|
|
|
|
## Open Questions For Approval
|
|
|
|
- Should `api.tu-zi.com` always resolve to `tuzi-compatible` in `auto` mode?
|
|
- Should the first implementation include only `/images/generations`, or also `/images/edits`?
|
|
- Should `openai-compatible-basic` and `tuzi-compatible` remain separate user options even if they initially map to the same request schema?
|