产出prd
This commit is contained in:
parent
834a240fe3
commit
b796a61469
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/plugin": "1.1.40"
|
"@opencode-ai/plugin": "1.4.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
762
.trellis/tasks/04-17-ops-mvp-design/prd.md
Normal file
762
.trellis/tasks/04-17-ops-mvp-design/prd.md
Normal file
@ -0,0 +1,762 @@
|
|||||||
|
# OPS MVP Design
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`opencode-provider-switch` (`ops`) is a local CLI + proxy for OpenCode.
|
||||||
|
|
||||||
|
Its job is narrow:
|
||||||
|
|
||||||
|
- Accept OpenCode traffic on `http://127.0.0.1:9982`
|
||||||
|
- Route requests by protocol, not by provider brand
|
||||||
|
- Retry/fail over across unreliable upstream relay providers
|
||||||
|
- Let multiple upstream providers share one logical model alias
|
||||||
|
- Manage state in SQLite, not in an `ops` config file
|
||||||
|
- Rewrite OpenCode global config to point at the local proxy
|
||||||
|
|
||||||
|
MVP intentionally does **not** try to become a general AI gateway, a dashboard product, or a provider-agnostic orchestration platform.
|
||||||
|
|
||||||
|
## Confirmed Decisions
|
||||||
|
|
||||||
|
- Language: Go
|
||||||
|
- Persistence: SQLite
|
||||||
|
- Default listen address: `127.0.0.1:9982`
|
||||||
|
- Delivery shape: CLI only
|
||||||
|
- MVP protocols:
|
||||||
|
- `openai-chat-completions`
|
||||||
|
- `openai-responses`
|
||||||
|
- Anthropic native protocol: out of MVP
|
||||||
|
- Logical model aliasing: required
|
||||||
|
- Billing/cost accounting: out of MVP
|
||||||
|
- Provider model discovery via `/models`: in MVP, best-effort
|
||||||
|
|
||||||
|
## Product Goals
|
||||||
|
|
||||||
|
1. Keep OpenCode usable when cheap relay providers fail intermittently.
|
||||||
|
2. Reduce OpenCode config complexity by centralizing provider management in `ops`.
|
||||||
|
3. Make failover behavior predictable, visible, and debuggable.
|
||||||
|
4. Preserve OpenCode-native workflow instead of asking users to switch tools.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
1. No GUI, web dashboard, or hosted control plane.
|
||||||
|
2. No Anthropic native API support in MVP.
|
||||||
|
3. No automatic price tracking or billing reconciliation.
|
||||||
|
4. No smart routing based on prompt content, latency scoring, or cost optimization.
|
||||||
|
5. No distributed/high-availability deployment story.
|
||||||
|
6. No attempt to normalize every provider quirk in MVP.
|
||||||
|
|
||||||
|
## Why Go
|
||||||
|
|
||||||
|
Go is the right fit for this product shape:
|
||||||
|
|
||||||
|
- `net/http` is strong enough for proxying and streaming without extra framework weight.
|
||||||
|
- Cross-platform static builds are straightforward.
|
||||||
|
- SQLite works well as a local state store.
|
||||||
|
- CLI ergonomics are mature with `cobra`.
|
||||||
|
- Concurrency model is a good fit for retries, request cancellation, and stream handling.
|
||||||
|
|
||||||
|
## Core Design Principle
|
||||||
|
|
||||||
|
`ops` should manage **protocol pools** and **logical aliases**, not raw provider selection inside OpenCode.
|
||||||
|
|
||||||
|
OpenCode should see a small number of local proxy providers. `ops` should own:
|
||||||
|
|
||||||
|
- upstream providers
|
||||||
|
- API keys and headers
|
||||||
|
- failover order
|
||||||
|
- alias to provider-model mapping
|
||||||
|
- model discovery cache
|
||||||
|
|
||||||
|
This keeps OpenCode config stable even when upstream providers are added, removed, or reprioritized.
|
||||||
|
|
||||||
|
## High-Level Architecture
|
||||||
|
|
||||||
|
```text
|
||||||
|
OpenCode
|
||||||
|
-> local OpenAI-compatible provider config
|
||||||
|
-> ops proxy (127.0.0.1:9982)
|
||||||
|
-> protocol router
|
||||||
|
-> alias resolver
|
||||||
|
-> failover engine
|
||||||
|
-> upstream relay provider A / B / C
|
||||||
|
```
|
||||||
|
|
||||||
|
### Major Components
|
||||||
|
|
||||||
|
1. `cli`
|
||||||
|
- all user-facing commands
|
||||||
|
- install, restore, provider management, alias management, doctor, serve
|
||||||
|
|
||||||
|
2. `sqlite store`
|
||||||
|
- source of truth for providers, aliases, protocol pools, model cache, install state
|
||||||
|
|
||||||
|
3. `proxy server`
|
||||||
|
- handles incoming HTTP requests from OpenCode
|
||||||
|
- supports streaming and non-streaming pass-through
|
||||||
|
|
||||||
|
4. `router`
|
||||||
|
- resolves protocol + alias -> ordered upstream targets
|
||||||
|
|
||||||
|
5. `failover engine`
|
||||||
|
- sequentially tries candidate targets based on priority and failure policy
|
||||||
|
|
||||||
|
6. `opencode integration`
|
||||||
|
- imports current OpenCode config
|
||||||
|
- writes generated proxy-backed OpenCode config
|
||||||
|
- restores backup when requested
|
||||||
|
|
||||||
|
7. `model discovery`
|
||||||
|
- fetches raw `/models` data from providers
|
||||||
|
- stores normalized cache for alias creation and diagnostics
|
||||||
|
|
||||||
|
## Supported Protocols in MVP
|
||||||
|
|
||||||
|
### 1. OpenAI Chat Completions
|
||||||
|
|
||||||
|
- Incoming route: `POST /v1/chat/completions`
|
||||||
|
- Discovery route: `GET /v1/models`
|
||||||
|
- OpenCode provider package: `@ai-sdk/openai-compatible`
|
||||||
|
|
||||||
|
### 2. OpenAI Responses
|
||||||
|
|
||||||
|
- Incoming route: `POST /v1/responses`
|
||||||
|
- Discovery route: `GET /v1/models`
|
||||||
|
- OpenCode provider package: `@ai-sdk/openai`
|
||||||
|
|
||||||
|
## Critical Scope Constraint
|
||||||
|
|
||||||
|
Even though both protocols are OpenAI-family APIs, they are **not** interchangeable at the OpenCode config level.
|
||||||
|
|
||||||
|
For MVP, `ops` should expose two local providers to OpenCode:
|
||||||
|
|
||||||
|
- one chat-completions provider
|
||||||
|
- one responses provider
|
||||||
|
|
||||||
|
This mirrors how OpenCode expects provider wiring today and avoids hidden protocol translation logic inside `ops`.
|
||||||
|
|
||||||
|
## OpenCode Integration Strategy
|
||||||
|
|
||||||
|
### Generated OpenCode Config
|
||||||
|
|
||||||
|
`ops install` should rewrite the user's global OpenCode config so that OpenCode points to local proxy providers instead of raw upstream relays.
|
||||||
|
|
||||||
|
Generated provider shape should be conceptually like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://opencode.ai/config.json",
|
||||||
|
"provider": {
|
||||||
|
"ops-chat": {
|
||||||
|
"npm": "@ai-sdk/openai-compatible",
|
||||||
|
"name": "OPS Chat",
|
||||||
|
"options": {
|
||||||
|
"baseURL": "http://127.0.0.1:9982/v1",
|
||||||
|
"apiKey": "ops-local"
|
||||||
|
},
|
||||||
|
"models": {
|
||||||
|
"gpt-5.4": {
|
||||||
|
"name": "gpt-5.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ops-responses": {
|
||||||
|
"npm": "@ai-sdk/openai",
|
||||||
|
"name": "OPS Responses",
|
||||||
|
"options": {
|
||||||
|
"baseURL": "http://127.0.0.1:9982/v1",
|
||||||
|
"apiKey": "ops-local"
|
||||||
|
},
|
||||||
|
"models": {
|
||||||
|
"gpt-5.4": {
|
||||||
|
"name": "gpt-5.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model": "ops-responses/gpt-5.4",
|
||||||
|
"small_model": "ops-chat/gpt-5.4-mini"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Important Rule
|
||||||
|
|
||||||
|
`ops` should preserve as much of the user's existing OpenCode config as possible.
|
||||||
|
|
||||||
|
`ops install` should:
|
||||||
|
|
||||||
|
1. Back up current global config file.
|
||||||
|
2. Import provider/model information relevant to migration.
|
||||||
|
3. Preserve unrelated keys like `plugin`, `agent`, `formatter`, `permission`, `instructions`, and other user settings.
|
||||||
|
4. Replace only the parts that need to point to local proxy providers:
|
||||||
|
- `provider`
|
||||||
|
- `model`
|
||||||
|
- `small_model`
|
||||||
|
|
||||||
|
### Project Config Limitation
|
||||||
|
|
||||||
|
OpenCode merges config from multiple sources, and project config can override global config.
|
||||||
|
|
||||||
|
That means `ops install` cannot guarantee full interception if a repository-local `opencode.json` or environment override replaces provider/model settings.
|
||||||
|
|
||||||
|
MVP response:
|
||||||
|
|
||||||
|
- document this clearly
|
||||||
|
- make `ops doctor` detect likely overrides
|
||||||
|
- support global install first
|
||||||
|
|
||||||
|
Do **not** promise perfect takeover across all OpenCode precedence layers in MVP.
|
||||||
|
|
||||||
|
## Migration Strategy
|
||||||
|
|
||||||
|
### Input Sources
|
||||||
|
|
||||||
|
`ops install` should inspect:
|
||||||
|
|
||||||
|
1. `~/.config/opencode/opencode.json`
|
||||||
|
2. `~/.config/opencode/opencode.jsonc`
|
||||||
|
3. `~/.local/share/opencode/auth.json` when present
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
|
||||||
|
- some users store `apiKey` inline in config
|
||||||
|
- OpenCode official flow often stores credentials in `auth.json`
|
||||||
|
|
||||||
|
### Migration Behavior
|
||||||
|
|
||||||
|
For each imported provider/model entry from OpenCode:
|
||||||
|
|
||||||
|
1. Determine whether it is chat-completions-oriented or responses-oriented.
|
||||||
|
2. Create an upstream provider record in SQLite.
|
||||||
|
3. Create logical aliases from existing model names.
|
||||||
|
4. Preserve model metadata when available from config:
|
||||||
|
- display name
|
||||||
|
- limit
|
||||||
|
- attachment
|
||||||
|
- reasoning
|
||||||
|
- tool_call
|
||||||
|
- options
|
||||||
|
- variants
|
||||||
|
5. Generate local `ops-*` providers for OpenCode.
|
||||||
|
|
||||||
|
### Protocol Classification
|
||||||
|
|
||||||
|
Import classification rules for MVP:
|
||||||
|
|
||||||
|
- `@ai-sdk/openai-compatible` -> `openai-chat-completions`
|
||||||
|
- `@ai-sdk/openai` -> `openai-responses`
|
||||||
|
- everything else -> unsupported for automatic migration in MVP
|
||||||
|
|
||||||
|
If unsupported providers exist, `ops install` should warn and skip them instead of guessing.
|
||||||
|
|
||||||
|
## SQLite-First State Model
|
||||||
|
|
||||||
|
`ops` should not keep its own user-editable config file.
|
||||||
|
|
||||||
|
Recommended database path:
|
||||||
|
|
||||||
|
- Linux/WSL: `~/.local/share/ops/ops.db`
|
||||||
|
- Windows native: use `os.UserConfigDir()` or `os.UserCacheDir()`-appropriate app path, finalized in implementation
|
||||||
|
|
||||||
|
The generated OpenCode config file is an output artifact, not `ops` source of truth.
|
||||||
|
|
||||||
|
### Proposed Tables
|
||||||
|
|
||||||
|
#### `providers`
|
||||||
|
|
||||||
|
Stores upstream provider connection info.
|
||||||
|
|
||||||
|
Suggested columns:
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `name`
|
||||||
|
- `protocol`
|
||||||
|
- `base_url`
|
||||||
|
- `api_key`
|
||||||
|
- `headers_json`
|
||||||
|
- `priority`
|
||||||
|
- `enabled`
|
||||||
|
- `created_at`
|
||||||
|
- `updated_at`
|
||||||
|
|
||||||
|
#### `provider_models`
|
||||||
|
|
||||||
|
Stores raw models discovered from upstream or imported from OpenCode config.
|
||||||
|
|
||||||
|
Suggested columns:
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `provider_id`
|
||||||
|
- `remote_model_id`
|
||||||
|
- `display_name`
|
||||||
|
- `source`
|
||||||
|
- `raw_json`
|
||||||
|
- `last_seen_at`
|
||||||
|
|
||||||
|
#### `aliases`
|
||||||
|
|
||||||
|
Stores user-facing logical model names per protocol.
|
||||||
|
|
||||||
|
Suggested columns:
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `protocol`
|
||||||
|
- `alias`
|
||||||
|
- `display_name`
|
||||||
|
- `capabilities_json`
|
||||||
|
- `limits_json`
|
||||||
|
- `options_json`
|
||||||
|
- `variants_json`
|
||||||
|
- `enabled`
|
||||||
|
- `created_at`
|
||||||
|
- `updated_at`
|
||||||
|
|
||||||
|
#### `alias_targets`
|
||||||
|
|
||||||
|
Maps one logical alias to one or more provider-specific targets.
|
||||||
|
|
||||||
|
Suggested columns:
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `alias_id`
|
||||||
|
- `provider_id`
|
||||||
|
- `remote_model_id`
|
||||||
|
- `priority`
|
||||||
|
- `enabled`
|
||||||
|
- `created_at`
|
||||||
|
- `updated_at`
|
||||||
|
|
||||||
|
#### `install_state`
|
||||||
|
|
||||||
|
Tracks OpenCode integration state.
|
||||||
|
|
||||||
|
Suggested columns:
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `backup_path`
|
||||||
|
- `managed_config_path`
|
||||||
|
- `installed_at`
|
||||||
|
- `last_generated_at`
|
||||||
|
- `original_config_hash`
|
||||||
|
- `generated_config_hash`
|
||||||
|
|
||||||
|
#### `request_log`
|
||||||
|
|
||||||
|
Optional but useful even in MVP for debugging. Keep retention small.
|
||||||
|
|
||||||
|
Suggested columns:
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `protocol`
|
||||||
|
- `alias`
|
||||||
|
- `provider_id`
|
||||||
|
- `remote_model_id`
|
||||||
|
- `attempt`
|
||||||
|
- `result`
|
||||||
|
- `status_code`
|
||||||
|
- `duration_ms`
|
||||||
|
- `error_text`
|
||||||
|
- `created_at`
|
||||||
|
|
||||||
|
## Model Alias Design
|
||||||
|
|
||||||
|
Alias support is a core MVP feature, not a nice-to-have.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- logical alias: `gpt-5.4`
|
||||||
|
- responses target priority 1: provider `su8`, remote model `gpt-5.4`
|
||||||
|
- responses target priority 2: provider `codex-for-me`, remote model `GPT-5.4`
|
||||||
|
- chat target priority 1: provider `relay-x`, remote model `gpt-5.4-chat`
|
||||||
|
|
||||||
|
This allows the user to keep using one stable model name inside OpenCode while `ops` handles provider-specific naming.
|
||||||
|
|
||||||
|
### Important Constraint
|
||||||
|
|
||||||
|
Aliases should be scoped by protocol.
|
||||||
|
|
||||||
|
`gpt-5.4` for chat completions and `gpt-5.4` for responses may exist simultaneously, but they should resolve through separate alias records or a protocol-aware unique key.
|
||||||
|
|
||||||
|
This avoids accidental cross-protocol reuse.
|
||||||
|
|
||||||
|
## `/models` Discovery Design
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
Reduce manual provider setup work.
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
|
||||||
|
`ops` should be able to call upstream `GET /v1/models` and cache results per provider.
|
||||||
|
|
||||||
|
Useful commands:
|
||||||
|
|
||||||
|
- `ops provider models sync <provider>`
|
||||||
|
- `ops provider models list <provider>`
|
||||||
|
- `ops alias suggest <provider>`
|
||||||
|
|
||||||
|
### Important Limitation
|
||||||
|
|
||||||
|
Provider `/models` data is often incomplete or inconsistent.
|
||||||
|
|
||||||
|
It usually does **not** fully describe:
|
||||||
|
|
||||||
|
- context length
|
||||||
|
- output limits
|
||||||
|
- attachment support
|
||||||
|
- reasoning support
|
||||||
|
- tool calling support
|
||||||
|
- custom request options
|
||||||
|
|
||||||
|
So for MVP, `/models` must be treated as:
|
||||||
|
|
||||||
|
- good for raw model ID discovery
|
||||||
|
- not authoritative for OpenCode capability metadata
|
||||||
|
|
||||||
|
This means alias metadata may still need manual editing or migration from prior OpenCode config.
|
||||||
|
|
||||||
|
## Proxy Request Flow
|
||||||
|
|
||||||
|
### Non-Streaming Request
|
||||||
|
|
||||||
|
1. Receive request on `/v1/chat/completions` or `/v1/responses`.
|
||||||
|
2. Parse request body enough to extract `model`.
|
||||||
|
3. Resolve `(protocol, alias)` to ordered alias targets.
|
||||||
|
4. Replace logical alias with provider-specific remote model ID.
|
||||||
|
5. Forward request to highest-priority enabled provider.
|
||||||
|
6. If response is retryable failure, try next target.
|
||||||
|
7. Return first successful response.
|
||||||
|
|
||||||
|
### Streaming Request
|
||||||
|
|
||||||
|
Same as above, with one critical rule:
|
||||||
|
|
||||||
|
- failover is only allowed **before first upstream response byte is sent to the client**
|
||||||
|
|
||||||
|
Once a stream begins successfully, `ops` must stay on that provider for that request.
|
||||||
|
|
||||||
|
## Failover Policy
|
||||||
|
|
||||||
|
MVP failover should stay simple and explicit.
|
||||||
|
|
||||||
|
### Retryable Conditions
|
||||||
|
|
||||||
|
- DNS/connect failure
|
||||||
|
- TCP reset / EOF before response
|
||||||
|
- request timeout
|
||||||
|
- chunk timeout before first byte
|
||||||
|
- HTTP `429`
|
||||||
|
- HTTP `500-599`
|
||||||
|
|
||||||
|
### Non-Retryable Conditions
|
||||||
|
|
||||||
|
- HTTP `400`
|
||||||
|
- HTTP `401`
|
||||||
|
- HTTP `403`
|
||||||
|
- HTTP `404`
|
||||||
|
- request validation errors from upstream after request is accepted
|
||||||
|
|
||||||
|
### Special Case: Model Not Found
|
||||||
|
|
||||||
|
Some providers return `404` or `400` for unknown model IDs.
|
||||||
|
|
||||||
|
MVP should **not** build broad heuristic parsing for this.
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
|
||||||
|
- error bodies are inconsistent
|
||||||
|
- aggressive parsing will create surprising behavior
|
||||||
|
|
||||||
|
Safer MVP rule:
|
||||||
|
|
||||||
|
- do not auto-fail over on ambiguous `400/404`
|
||||||
|
- surface error clearly
|
||||||
|
|
||||||
|
This is conservative, but predictable.
|
||||||
|
|
||||||
|
## Proxy Response Headers
|
||||||
|
|
||||||
|
For debugging, add response headers when possible:
|
||||||
|
|
||||||
|
- `X-OPS-Protocol`
|
||||||
|
- `X-OPS-Alias`
|
||||||
|
- `X-OPS-Provider`
|
||||||
|
- `X-OPS-Remote-Model`
|
||||||
|
- `X-OPS-Attempt`
|
||||||
|
- `X-OPS-Failover-Count`
|
||||||
|
|
||||||
|
These headers are low-cost and help explain behavior fast.
|
||||||
|
|
||||||
|
## Local Proxy Security Model
|
||||||
|
|
||||||
|
MVP security posture should be intentionally modest but clear.
|
||||||
|
|
||||||
|
### Default Behavior
|
||||||
|
|
||||||
|
- bind only to `127.0.0.1`
|
||||||
|
- generated OpenCode config uses local static API key like `ops-local`
|
||||||
|
- proxy accepts only loopback traffic by default
|
||||||
|
|
||||||
|
### Why This Is Acceptable For MVP
|
||||||
|
|
||||||
|
- tool is local-only by default
|
||||||
|
- threat model is mostly accidental exposure, not multi-tenant isolation
|
||||||
|
|
||||||
|
### Caveat
|
||||||
|
|
||||||
|
SQLite-stored API keys are sensitive.
|
||||||
|
|
||||||
|
MVP options:
|
||||||
|
|
||||||
|
1. Store plaintext in SQLite with strict file permissions.
|
||||||
|
2. Defer OS keychain integration to later.
|
||||||
|
|
||||||
|
Recommended MVP choice:
|
||||||
|
|
||||||
|
- plaintext in SQLite
|
||||||
|
- create DB with owner-only permissions where possible
|
||||||
|
- document this explicitly
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
|
||||||
|
- simplest implementation
|
||||||
|
- consistent cross-platform behavior
|
||||||
|
- avoids blocking MVP on secret-store complexity
|
||||||
|
|
||||||
|
## CLI Surface
|
||||||
|
|
||||||
|
Proposed command set:
|
||||||
|
|
||||||
|
### Lifecycle
|
||||||
|
|
||||||
|
- `ops init`
|
||||||
|
- `ops serve`
|
||||||
|
- `ops doctor`
|
||||||
|
- `ops install`
|
||||||
|
- `ops restore`
|
||||||
|
|
||||||
|
### Provider Management
|
||||||
|
|
||||||
|
- `ops provider add`
|
||||||
|
- `ops provider list`
|
||||||
|
- `ops provider edit`
|
||||||
|
- `ops provider remove`
|
||||||
|
- `ops provider enable`
|
||||||
|
- `ops provider disable`
|
||||||
|
|
||||||
|
### Model Discovery
|
||||||
|
|
||||||
|
- `ops provider models sync <provider>`
|
||||||
|
- `ops provider models list <provider>`
|
||||||
|
|
||||||
|
### Alias Management
|
||||||
|
|
||||||
|
- `ops alias add`
|
||||||
|
- `ops alias list`
|
||||||
|
- `ops alias bind`
|
||||||
|
- `ops alias unbind`
|
||||||
|
- `ops alias enable`
|
||||||
|
- `ops alias disable`
|
||||||
|
- `ops alias inspect <alias>`
|
||||||
|
|
||||||
|
### Diagnostics
|
||||||
|
|
||||||
|
- `ops logs tail`
|
||||||
|
- `ops route test --protocol <protocol> --model <alias>`
|
||||||
|
|
||||||
|
## Recommended Minimal UX
|
||||||
|
|
||||||
|
Prefer explicit CLI over magical automation.
|
||||||
|
|
||||||
|
Good path:
|
||||||
|
|
||||||
|
1. `ops init`
|
||||||
|
2. `ops provider add`
|
||||||
|
3. `ops provider models sync`
|
||||||
|
4. `ops alias add`
|
||||||
|
5. `ops alias bind`
|
||||||
|
6. `ops install`
|
||||||
|
7. `ops serve`
|
||||||
|
|
||||||
|
This is easy to explain and easy to debug.
|
||||||
|
|
||||||
|
## Suggested Go Package Layout
|
||||||
|
|
||||||
|
```text
|
||||||
|
cmd/ops/
|
||||||
|
internal/cli/
|
||||||
|
internal/db/
|
||||||
|
internal/models/
|
||||||
|
internal/providers/
|
||||||
|
internal/aliases/
|
||||||
|
internal/proxy/
|
||||||
|
internal/router/
|
||||||
|
internal/failover/
|
||||||
|
internal/opencode/
|
||||||
|
internal/doctor/
|
||||||
|
internal/logging/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Library Choices
|
||||||
|
|
||||||
|
- CLI: `cobra`
|
||||||
|
- SQLite driver: `modernc.org/sqlite`
|
||||||
|
- Logging: stdlib `log/slog`
|
||||||
|
- HTTP: stdlib `net/http`
|
||||||
|
- JSONC parsing for OpenCode config import/export: `tailscale/hujson` or equivalent
|
||||||
|
|
||||||
|
Avoid adding a heavy HTTP framework unless a real need appears.
|
||||||
|
|
||||||
|
## Generated OpenCode Provider Strategy
|
||||||
|
|
||||||
|
MVP should generate only providers that `ops` can actually back.
|
||||||
|
|
||||||
|
That means:
|
||||||
|
|
||||||
|
- `ops-chat`
|
||||||
|
- `ops-responses`
|
||||||
|
|
||||||
|
Do **not** generate fake Anthropic provider entries in MVP.
|
||||||
|
|
||||||
|
Do **not** attempt to preserve original provider IDs inside OpenCode after installation.
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
|
||||||
|
- `ops` becomes the stable local provider boundary
|
||||||
|
- upstream providers should move into SQLite management only
|
||||||
|
|
||||||
|
## WSL / Windows Strategy
|
||||||
|
|
||||||
|
This requirement is important, but it needs careful wording.
|
||||||
|
|
||||||
|
### What MVP Should Promise
|
||||||
|
|
||||||
|
1. Native Linux/WSL build works.
|
||||||
|
2. Native Windows build works.
|
||||||
|
3. Running OpenCode and `ops` in the **same environment** is supported.
|
||||||
|
4. `ops doctor` helps detect config-path and loopback issues.
|
||||||
|
|
||||||
|
### What MVP Should Not Promise Yet
|
||||||
|
|
||||||
|
1. Fully automatic cross-boundary migration between WSL OpenCode and Windows `ops`.
|
||||||
|
2. Transparent path translation for every user setup.
|
||||||
|
3. Zero-config interop when OpenCode runs on one side and proxy on the other.
|
||||||
|
|
||||||
|
### Practical Recommendation
|
||||||
|
|
||||||
|
For MVP, recommend users run OpenCode and `ops` in the same environment.
|
||||||
|
|
||||||
|
Cross-environment support can be added later via explicit install target flags.
|
||||||
|
|
||||||
|
## Biggest Risks
|
||||||
|
|
||||||
|
### 1. OpenCode Config Precedence
|
||||||
|
|
||||||
|
Global config rewrite alone may not capture project-level overrides.
|
||||||
|
|
||||||
|
Mitigation:
|
||||||
|
|
||||||
|
- `ops doctor`
|
||||||
|
- clear docs
|
||||||
|
- possible future `ops install --project`
|
||||||
|
|
||||||
|
### 2. Provider `/models` Quality
|
||||||
|
|
||||||
|
Discovery helps with model IDs, but not with full capability metadata.
|
||||||
|
|
||||||
|
Mitigation:
|
||||||
|
|
||||||
|
- preserve metadata during import
|
||||||
|
- allow manual alias metadata editing
|
||||||
|
|
||||||
|
### 3. Streaming Edge Cases
|
||||||
|
|
||||||
|
Once bytes are sent, failover is no longer safe.
|
||||||
|
|
||||||
|
Mitigation:
|
||||||
|
|
||||||
|
- fail over only before first byte
|
||||||
|
- good timeout defaults
|
||||||
|
- precise logging
|
||||||
|
|
||||||
|
### 4. Secret Storage
|
||||||
|
|
||||||
|
SQLite plaintext credentials are acceptable for MVP, but they are still sensitive.
|
||||||
|
|
||||||
|
Mitigation:
|
||||||
|
|
||||||
|
- strict file permissions
|
||||||
|
- local-only binding
|
||||||
|
- document tradeoff
|
||||||
|
|
||||||
|
### 5. Protocol Ambiguity During Migration
|
||||||
|
|
||||||
|
OpenCode custom providers can mix package usage in ways that are not cleanly inferable.
|
||||||
|
|
||||||
|
Mitigation:
|
||||||
|
|
||||||
|
- support only known package mappings in MVP
|
||||||
|
- warn instead of guessing
|
||||||
|
|
||||||
|
## MVP Success Criteria
|
||||||
|
|
||||||
|
MVP is successful if a user can:
|
||||||
|
|
||||||
|
1. Import existing OpenCode provider setup into `ops`.
|
||||||
|
2. Create or verify aliases for commonly used models.
|
||||||
|
3. Install proxy-backed OpenCode global config.
|
||||||
|
4. Run `ops serve`.
|
||||||
|
5. Use OpenCode normally against `127.0.0.1:9982`.
|
||||||
|
6. Survive common upstream failures by automatic provider failover.
|
||||||
|
|
||||||
|
## Recommended Implementation Order
|
||||||
|
|
||||||
|
### Phase 1
|
||||||
|
|
||||||
|
- bootstrap Go project
|
||||||
|
- SQLite store
|
||||||
|
- provider CRUD
|
||||||
|
- alias CRUD
|
||||||
|
|
||||||
|
### Phase 2
|
||||||
|
|
||||||
|
- `GET /v1/models` sync
|
||||||
|
- import from OpenCode config/auth
|
||||||
|
- generated OpenCode config writer
|
||||||
|
|
||||||
|
### Phase 3
|
||||||
|
|
||||||
|
- proxy server
|
||||||
|
- chat completions forwarding
|
||||||
|
- responses forwarding
|
||||||
|
- non-streaming failover
|
||||||
|
|
||||||
|
### Phase 4
|
||||||
|
|
||||||
|
- streaming support
|
||||||
|
- logging/diagnostics
|
||||||
|
- `ops doctor`
|
||||||
|
- restore flow
|
||||||
|
|
||||||
|
## Strong Recommendation
|
||||||
|
|
||||||
|
Do not start with background health checks, latency scoring, or dynamic routing.
|
||||||
|
|
||||||
|
Start with deterministic priority failover only.
|
||||||
|
|
||||||
|
That gives the product a sharp identity:
|
||||||
|
|
||||||
|
- local
|
||||||
|
- understandable
|
||||||
|
- reliable enough
|
||||||
|
- much smaller than LiteLLM
|
||||||
|
|
||||||
|
## Next Step
|
||||||
|
|
||||||
|
Next practical step is implementation bootstrap for:
|
||||||
|
|
||||||
|
1. Go module structure
|
||||||
|
2. SQLite schema migrations
|
||||||
|
3. Cobra command tree
|
||||||
|
4. OpenCode config importer/exporter
|
||||||
|
|
||||||
|
That is the smallest slice that validates the design without touching complex proxy streaming first.
|
||||||
45
.trellis/tasks/04-17-ops-mvp-design/task.json
Normal file
45
.trellis/tasks/04-17-ops-mvp-design/task.json
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"id": "04-17-ops-mvp-design",
|
||||||
|
"name": "OPS MVP Design",
|
||||||
|
"description": "Define MVP product and technical design for a Go-based local proxy that failsover OpenCode providers",
|
||||||
|
"status": "completed",
|
||||||
|
"dev_type": "docs",
|
||||||
|
"priority": "P1",
|
||||||
|
"creator": "Apale",
|
||||||
|
"assignee": "Apale",
|
||||||
|
"createdAt": "2026-04-17",
|
||||||
|
"completedAt": "2026-04-17",
|
||||||
|
"commit": null,
|
||||||
|
"subtasks": [
|
||||||
|
{
|
||||||
|
"name": "Define MVP scope and non-goals",
|
||||||
|
"status": "completed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Define Go architecture and SQLite schema",
|
||||||
|
"status": "completed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Document OpenCode migration and proxy behavior",
|
||||||
|
"status": "completed"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"relatedFiles": [
|
||||||
|
".trellis/tasks/04-17-ops-mvp-design/prd.md",
|
||||||
|
"examples/opencode.jsonc"
|
||||||
|
],
|
||||||
|
"notes": "Design-only task completed. Implementation should start from a follow-up task based on this PRD.",
|
||||||
|
"meta": {
|
||||||
|
"stack": [
|
||||||
|
"Go",
|
||||||
|
"SQLite"
|
||||||
|
],
|
||||||
|
"defaultPort": 9982,
|
||||||
|
"mvpProtocols": [
|
||||||
|
"openai-chat-completions",
|
||||||
|
"openai-responses"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user