Plan bundled OpenRouter configuration
This commit is contained in:
@@ -41,10 +41,10 @@ ConfigKit adapter. Place it in its own lowest-priority underlay, before every
|
||||
caller underlay and the main input. Preserve the existing order and grouping
|
||||
of caller inputs.
|
||||
|
||||
Enable the bundle by default, with a public engine option to disable it entirely.
|
||||
Enable the bundle by default, with `WithBundledOpenRouter(bool)` to disable or
|
||||
enable it explicitly.
|
||||
This follows the agreed convenience-oriented direction while preserving the
|
||||
ability to supply an application's complete catalog. Choose the exact option
|
||||
name during implementation using existing facade conventions.
|
||||
ability to supply an application's complete catalog.
|
||||
|
||||
Bundling a profile makes it available for explicit selection by a prompt or
|
||||
request. It does not establish an implicit execution profile, change profile
|
||||
@@ -133,9 +133,10 @@ The initial set contains seventeen root profiles:
|
||||
| `auto-max` | Inherits `auto`; sets cost tier `max`. |
|
||||
| `free` | OpenRouter free-model router (`openrouter/free`). |
|
||||
|
||||
These profile IDs are selected. Confirm the exact provider `model` slugs for
|
||||
all family aliases during implementation rather than deriving them mechanically
|
||||
from profile IDs. If a requested alias is unavailable, surface that mismatch
|
||||
These profile IDs are selected. The companion [implementation plan](implementation.md)
|
||||
records the exact provider `model` slugs checked against the public alias pages.
|
||||
Keep those mappings explicit rather than deriving them mechanically from IDs.
|
||||
If a requested alias becomes unavailable, surface that mismatch
|
||||
instead of silently substituting a pinned model or dropping the profile.
|
||||
The tilde and slash in a provider model slug remain part of that value; they
|
||||
do not change the root placement of the PromptKit profile.
|
||||
@@ -169,7 +170,6 @@ OpenRouter documents the
|
||||
[GPT Sol latest alias](https://openrouter.ai/~openai/gpt-sol-latest),
|
||||
[auto router](https://openrouter.ai/docs/guides/routing/routers/auto-router), and
|
||||
[free router](https://openrouter.ai/docs/guides/routing/routers/free-router).
|
||||
Verify the chosen slugs against current provider documentation when implementing.
|
||||
Stable selectors reduce maintenance but do not freeze model selection, price,
|
||||
availability, supported parameters, or output behavior. Describe the free router
|
||||
as the provider's offering rather than adding PromptKit guarantees about future
|
||||
@@ -212,35 +212,32 @@ automatic refresh mechanism is planned. Provider aliases and routers may change
|
||||
their targets independently of that release; the inspected configured slug
|
||||
does not identify or pin the concrete model selected upstream.
|
||||
|
||||
## Implementation Work
|
||||
## Acceptance And Documentation
|
||||
|
||||
1. Verify the provider endpoint and exact model values for the seventeen selected
|
||||
root profiles. Local HTTP checks establish payload construction, not provider
|
||||
acceptance. A live provider check may supplement that verification without
|
||||
becoming a recurring credential-dependent test requirement.
|
||||
2. Add the embedded YAML backend and profiles, and integrate their dedicated
|
||||
underlay with engine construction. Add the disable option using the existing
|
||||
facade option pattern. Apply the ordinary load/normalize/validate lifecycle.
|
||||
3. Verify default availability, disabling, caller precedence, whole-record
|
||||
replacement, and inherited variants through the real configuration loader.
|
||||
Include an application-provided OpenRouter collection and a replaced backend
|
||||
to exercise interaction with external catalogs. Verify all five cost-tier
|
||||
profiles inherit the base backend/model and send the expected top-level
|
||||
`plugins` array through the built-in HTTP client. Add representative explicit
|
||||
`append` and `replace` mode cases. Add a targeted case replacing root `auto`
|
||||
in a higher layer with a different backend/model, then verify its cost-tier
|
||||
children inherit those effective values and retain their plugin settings.
|
||||
4. Verify listing and inspection reflect effective definitions and meaningful
|
||||
source evidence. Exercise request preparation with a fake client or local
|
||||
server as appropriate; construction must not depend on provider reachability
|
||||
or a live API key. Reuse existing credential tests and add coverage only for
|
||||
new integration behavior. Cover default reachability, the authentication
|
||||
adapter, inference fallback to `gpt-luna-latest`, explicit prompt/profile
|
||||
selection, and application replacement of the fallback profile or metadata.
|
||||
5. Update the format reference, consumer guide, relevant GoDoc, and maintained
|
||||
examples when implementation lands. Explain how to disable, replace, or derive
|
||||
from bundled profiles, and distinguish routers/latest aliases from fixed
|
||||
models. Include the inventory/inspection change in release notes.
|
||||
The companion implementation plan owns the staged work. The integrated result
|
||||
should demonstrate:
|
||||
|
||||
- Default availability and full disabling through the ordinary configuration
|
||||
lifecycle, without construction-time provider or credential access.
|
||||
- Caller precedence, whole-record replacement, inherited variants, explicit
|
||||
append/replace modes, and coexistence with an external OpenRouter collection.
|
||||
- All five cost-tier profiles inheriting the effective `auto` backend/model,
|
||||
retaining their plugin settings even after parent replacement, and producing
|
||||
the expected top-level `plugins` array through the built-in HTTP client.
|
||||
- Listing and inspection of effective definitions with meaningful source evidence.
|
||||
- Explicit smoke diagnostics using backend endpoint fallbacks, authentication,
|
||||
inference defaults, explicit selection precedence, and effective application
|
||||
replacements of the default profile or backend metadata.
|
||||
|
||||
Offline HTTP tests establish payload construction, not provider acceptance.
|
||||
Credential-dependent live checks are not recurring test requirements. Existing
|
||||
credential behavior remains authoritative; coverage should focus on new
|
||||
integration risks rather than duplicate that suite.
|
||||
|
||||
The format reference, consumer guide, relevant GoDoc, and maintained examples
|
||||
should explain disabling, replacement, and inheritance. Release notes should
|
||||
identify inventory and inspection changes. Provider latest aliases and routers
|
||||
remain distinct from fixed models and carry no PromptKit upstream guarantees.
|
||||
|
||||
Keep current-state documentation unchanged until implementation lands. Retire
|
||||
this roadmap after the implemented behavior reaches its durable owners. No new
|
||||
|
||||
281
docs/roadmap/implementation.md
Normal file
281
docs/roadmap/implementation.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# Bundled OpenRouter Implementation Plan
|
||||
|
||||
Implement [bundled-openrouter.md](bundled-openrouter.md) in the sequential stages
|
||||
below. Each stage is one Luna-sized assignment. The feature roadmap owns scope
|
||||
and consumer policy; this document owns the implementation sequence and choices.
|
||||
|
||||
## Baseline And Working Rules
|
||||
|
||||
The baseline is `9e05c3a`: Go 1.27.0, ConfigKit v1.1.0, recorded field
|
||||
normalization, and no installed backend/profile catalog. The facade in
|
||||
`engine.go` applies options; `internal/engine.New` passes caller configuration
|
||||
to `internal/configuration.Load` before assembling execution collaborators.
|
||||
|
||||
Read `docs/development.md` and applicable agent, architecture, testing,
|
||||
documentation, OSS, and release policies. Follow
|
||||
[ADR 0001](../adr/0001-configuration-boundary.md). Reuse current source-loading,
|
||||
generation, and smoke-diagnostic contracts. Preserve unrelated changes.
|
||||
|
||||
This is one end-to-end sprint: intermediate stages need not compile or form a
|
||||
deployable state. Do not introduce compatibility scaffolding or intermediate
|
||||
human-review/publication gates. Record incomplete checks honestly and reuse
|
||||
successful checks for unchanged content. Do not publish a release or make paid
|
||||
provider requests. No dependency upgrade, configuration-version change, or
|
||||
provider-specific catalog module is required.
|
||||
|
||||
## Implementation Choices
|
||||
|
||||
- Add `internal/configuration/bundled/openrouter.yaml` and embed it from a small
|
||||
`bundled.go` in that package. Provide an internal exported
|
||||
`BundledOpenRouterUnderlay() configkit.Underlay` for engine assembly. It returns
|
||||
one input created through `configkit.NewFilesystem` and its `File` method,
|
||||
with a meaningful label such as `Promptkit bundled OpenRouter`.
|
||||
- Use the existing source loader once. Do not independently parse, validate,
|
||||
normalize, or cache a separate bundle registry before final composition.
|
||||
Tests inspect the assembled effective configuration, not raw-source validity.
|
||||
- Prepend the bundle as its own underlay in `internal/engine.New`. Allocate a
|
||||
new outer slice and copy caller groups so spare slice capacity cannot let
|
||||
assembly mutate caller-owned input. Preserve every caller group's contents,
|
||||
order, main input, imports, and source context.
|
||||
- Add `WithBundledOpenRouter(enabled bool) Option` in the facade. Add
|
||||
`disableBundledOpenRouter bool` to internal engine options and a setter
|
||||
`SetBundledOpenRouter(bool)` following existing setter style. The zero internal
|
||||
options value enables the bundle; the setter stores `!enabled`. Apply options
|
||||
in the existing order, so the last supplied bundle option wins. The facade
|
||||
only delegates; embedding and composition stay internal.
|
||||
- Disabled means no bundle source or definitions are installed. It does not
|
||||
reserve names, remove caller definitions, or disable the OpenAI-compatible
|
||||
client. Scoped views share the engine's captured configuration; they do not
|
||||
reload the bundle.
|
||||
- Keep the root backend and profiles described below. No built-in collection,
|
||||
prompts, implicit execution profile, tuning defaults, catalog download, or
|
||||
constructor-time provider/credential access is added.
|
||||
|
||||
## Exact Data To Install
|
||||
|
||||
The root backend is `openrouter`, with endpoint `https://openrouter.ai/api/v1`,
|
||||
`api_key_env: OPENROUTER_API_KEY`, and:
|
||||
|
||||
```yaml
|
||||
smoke_tests:
|
||||
default_profile: gpt-luna-latest
|
||||
authentication:
|
||||
kind: openrouter_key
|
||||
```
|
||||
|
||||
Leave concurrency, queue, provider parameters, and diagnostic endpoint overrides
|
||||
unset. Existing reachability and authentication endpoint fallback applies.
|
||||
|
||||
Use these exact root profile IDs and model values. Each non-cost-tier profile
|
||||
sets `backend: openrouter`. The ten family mappings were checked against the
|
||||
linked provider pages on 2026-11-03; this is authoring evidence, not a guarantee
|
||||
of future availability or a recurring release check.
|
||||
|
||||
| Profile ID | Model | Provider reference |
|
||||
| --- | --- | --- |
|
||||
| `gpt-astra-latest` | `~openai/gpt-astra-latest` | [Alias](https://openrouter.ai/~openai/gpt-astra-latest) |
|
||||
| `gpt-sol-latest` | `~openai/gpt-sol-latest` | [Alias](https://openrouter.ai/~openai/gpt-sol-latest) |
|
||||
| `gpt-terra-latest` | `~openai/gpt-terra-latest` | [Alias](https://openrouter.ai/~openai/gpt-terra-latest) |
|
||||
| `gpt-luna-latest` | `~openai/gpt-luna-latest` | [Alias](https://openrouter.ai/~openai/gpt-luna-latest) |
|
||||
| `claude-fable-latest` | `~anthropic/claude-fable-latest` | [Alias](https://openrouter.ai/~anthropic/claude-fable-latest) |
|
||||
| `claude-opus-latest` | `~anthropic/claude-opus-latest` | [Alias](https://openrouter.ai/~anthropic/claude-opus-latest) |
|
||||
| `claude-sonnet-latest` | `~anthropic/claude-sonnet-latest` | [Alias](https://openrouter.ai/~anthropic/claude-sonnet-latest) |
|
||||
| `claude-haiku-latest` | `~anthropic/claude-haiku-latest` | [Alias](https://openrouter.ai/~anthropic/claude-haiku-latest) |
|
||||
| `gemini-pro-latest` | `~google/gemini-pro-latest` | [Alias](https://openrouter.ai/~google/gemini-pro-latest) |
|
||||
| `gemini-flash-latest` | `~google/gemini-flash-latest` | [Alias](https://openrouter.ai/~google/gemini-flash-latest) |
|
||||
| `auto` | `openrouter/auto` | [Router](https://openrouter.ai/docs/guides/routing/routers/auto-router) |
|
||||
| `free` | `openrouter/free` | [Router](https://openrouter.ai/docs/guides/routing/routers/free-router) |
|
||||
|
||||
The five remaining profiles are `auto-low`, `auto-medium`, `auto-high`,
|
||||
`auto-xhigh`, and `auto-max`. Each uses `extends: {id: auto}` and only adds
|
||||
`extra_params.plugins: [{id: auto-router, cost_tier: TIER}]`, where TIER is its
|
||||
suffix. Do not duplicate backend/model in these children: they inherit the
|
||||
effective parent, including an application replacement. Base `auto` sets no
|
||||
cost tier. OpenRouter documents these five values and plugin spelling in its
|
||||
[cost-tier contract](https://openrouter.ai/docs/guides/routing/routers/auto-router#cost-tier).
|
||||
The [current-key endpoint](https://openrouter.ai/docs/api/api-reference/api-keys/get-current-api-key)
|
||||
documents the existing authentication adapter's destination.
|
||||
|
||||
Preserve `~` and `/` in model values; neither makes the profile namespaced.
|
||||
Do not add sampling, reasoning, timeout, token, or capability settings. If
|
||||
contradictory provider evidence appears, report it rather than silently selecting
|
||||
a fixed model or dropping a requested profile. A credentialed generation probe
|
||||
is not needed to implement this data-backed plan.
|
||||
|
||||
## Stage 1: Add The Embedded Configuration Source
|
||||
|
||||
Scope: new YAML and small helper in `internal/configuration`.
|
||||
|
||||
1. Define one root backend and seventeen root profiles exactly as above. Use
|
||||
ordinary identified lists and ConfigKit sibling inheritance for the five
|
||||
cost-tier children. No collection wrapper or prompt definitions are needed.
|
||||
2. Embed the file with `embed.FS`; expose only the internal underlay helper.
|
||||
Use a fresh public ConfigKit source/context for loading, without mutable
|
||||
package-level decoded data or filesystem discovery.
|
||||
3. Add a focused loader-level test composing that underlay through `Load`.
|
||||
Check the effective backend, the selected profile/model mapping, cost-tier
|
||||
inheritance, smoke metadata, and meaningful source evidence. These assertions
|
||||
protect the chosen catalog contents, not upstream model availability.
|
||||
4. Do not create raw-YAML validation or live-provider tests. No public catalog
|
||||
export, custom parser, catalog dependency, or parallel registry is needed.
|
||||
|
||||
Validation: `go test ./internal/configuration` after the helper is wired.
|
||||
|
||||
## Stage 2: Enable The Bundle Through Engine Construction
|
||||
|
||||
Scope: facade/internal options, assembly, and default/disabled contract tests.
|
||||
|
||||
1. Implement the option and default-on internal flag as specified. Prepend the
|
||||
underlay only when enabled, using a new outer slice. Keep schema/client/
|
||||
artifact options and main-source handling unchanged.
|
||||
2. Add external-package facade tests showing `NewEngine(Config{})` exposes the
|
||||
root backend and all seventeen profiles, but creates no prompts or synthetic
|
||||
`openrouter` collection. Exercise representative `InspectProfile` values and
|
||||
`InspectConfiguration` source explanations.
|
||||
3. Test false disables installation, true retains it, and option ordering follows
|
||||
ordinary last-option behavior. With false, caller-owned `openrouter` records
|
||||
remain available. Construction succeeds with the API-key variable unset and
|
||||
an HTTP transport that rejects unexpected calls.
|
||||
4. Verify assembly leaves caller underlay slice/backing-array contents unchanged.
|
||||
5. Adapt preexisting tests whose intended fixture is an application-only or empty
|
||||
catalog by explicitly disabling the bundle in their local helpers. Update
|
||||
default-inventory expectations where that is the behavior under test. Do not
|
||||
globally hide default-on behavior or weaken not-found/validation assertions.
|
||||
Internal direct configuration-loader tests do not need to install the bundle.
|
||||
|
||||
Validation: focused construction/listing/inspection tests, then
|
||||
`go test ./internal/engine ./...` when intermediate wiring is coherent.
|
||||
|
||||
## Stage 3: Protect Composition And Application Control
|
||||
|
||||
Scope: focused real-loader integration tests through the facade.
|
||||
|
||||
1. Verify a caller underlay and then the main input can replace a bundled ID in
|
||||
order, and caller strict groups remain strict. Demonstrate complete record
|
||||
replacement: a model-only replacement does not recover the old backend.
|
||||
Preserve partial-profile support rather than demanding construction readiness.
|
||||
2. Verify a distinct profile extending a bundled profile retains inherited
|
||||
fields and applies selected overrides. Add an application-provided OpenRouter
|
||||
collection with qualified references; root and collection profiles coexist.
|
||||
3. Replace root backend `openrouter` with a complete local endpoint definition.
|
||||
Confirm surviving bundled profiles use the replacement backend's settings.
|
||||
No reserved-ID checks or special merge semantics are added.
|
||||
4. Replace root `auto` with a profile selecting a different caller backend and
|
||||
model. All five cost-tier children must inherit that target and retain their
|
||||
plugin entries. Do not reject this customization or strip plugin settings.
|
||||
5. Exercise explicit sequence modes using ConfigKit's existing controls:
|
||||
`config.sequences: [{destination: [profiles], mode: append}]` with a duplicate
|
||||
bundled ID fails normally; `mode: replace` with an application profile list
|
||||
replaces all lower profiles. Include a nonconflicting append case. Keep
|
||||
backend/profile references valid where testing successful replacement.
|
||||
6. Show that clearing/replacing backend definitions without maintaining surviving
|
||||
profile references follows existing validation, not a hidden fallback.
|
||||
Disabling the bundle remains the simple application-only configuration path.
|
||||
|
||||
Validation: targeted new composition tests plus existing source, inheritance,
|
||||
namespace, and profile-completion tests. Avoid reproducing ConfigKit's generic
|
||||
sequence implementation suite.
|
||||
|
||||
## Stage 4: Verify Preparation And HTTP Request Construction
|
||||
|
||||
Scope: bundled-profile execution integration with existing runner/client paths.
|
||||
|
||||
1. With a minimal caller prompt and an injected deterministic client, prepare
|
||||
and run using an explicit bundled profile. Check selected profile/backend,
|
||||
model slug, and ordinary defaults/overrides. A prompt with no default and
|
||||
no request profile must still produce the existing profile-required error.
|
||||
2. For every cost tier, use the built-in HTTP client and an offline recording
|
||||
RoundTripper or local server to inspect the outbound JSON. Assert the model
|
||||
is `openrouter/auto` and `plugins` is a top-level array with the matching
|
||||
`auto-router`/`cost_tier` entry, not nested under an `extra_params` member.
|
||||
3. Verify base `auto` sends no explicit cost-tier plugin and `free` preserves
|
||||
`openrouter/free`. Check at least one `~`-prefixed model reaches the request
|
||||
unchanged. Return a minimal valid synthetic chat response.
|
||||
4. Reuse existing runtime credential tests and override tests. Add only missing
|
||||
bundle integration assertions, such as selecting `OPENROUTER_API_KEY` from
|
||||
the installed backend. No real credentials, calls, provider capability
|
||||
assumptions, hardcoded prices, or changes to optional-key behavior are needed.
|
||||
|
||||
Validation: new request cases and existing generation/profile/override tests.
|
||||
Offline HTTP assertions prove payload construction, not provider acceptance.
|
||||
|
||||
## Stage 5: Verify Bundled Smoke-Test Defaults
|
||||
|
||||
Scope: facade-level smoke integration with a synthetic HTTP boundary.
|
||||
|
||||
1. On a default engine, explicitly request reachability with
|
||||
`BackendID: openrouter`. Verify the existing backend-endpoint fallback.
|
||||
Authentication should select the `openrouter_key` adapter and
|
||||
`https://openrouter.ai/api/v1/key`, using an isolated synthetic key and a
|
||||
recording transport. Return the existing adapter's minimal success fixture.
|
||||
2. For text and structured inference, specify the backend without prompt/profile.
|
||||
Assert fallback to `gpt-luna-latest` and its exact model slug. Return suitable
|
||||
private-case responses using the existing smoke-test fixtures. Preserve
|
||||
factual reports and nil-error concluded-failure semantics.
|
||||
3. Show explicit profile selection wins over prompt default and backend metadata,
|
||||
and a prompt default wins over backend metadata when no profile is supplied.
|
||||
Smoke tests must still use their private cases, not execute caller prompt
|
||||
content or output contracts.
|
||||
4. Replace `gpt-luna-latest` through application configuration and verify fallback
|
||||
uses the effective replacement. Replace backend smoke metadata to select a
|
||||
different caller profile and confirm the ordinary selection path follows it.
|
||||
5. Verify listing/inspection are not modified by diagnostics. Keep the existing
|
||||
rule that diagnostics run only on explicit invocation; no new constructor,
|
||||
ordinary-run, CI, or release probe is introduced.
|
||||
|
||||
Validation: targeted bundled smoke tests and existing smoke contract/integration
|
||||
tests. Reuse existing endpoint/auth fixtures instead of adding an adapter or
|
||||
duplicating the full diagnostic suite.
|
||||
|
||||
## Stage 6: Update Documentation And Verify The Sprint
|
||||
|
||||
Scope: public GoDoc, current guidance/examples, and end-to-end verification.
|
||||
|
||||
1. Update `ConfigurationSources`, `Config`/`NewEngine`, and new-option GoDoc to
|
||||
describe the dedicated lower underlay, default-on behavior, disabling, and
|
||||
changed inventories. Keep exact option semantics in GoDoc.
|
||||
2. Update `docs/formats.md` and `docs/consumers/pkg-promptkit.md` with the bundle,
|
||||
root layout, explicit selection, optional credentials, smoke fallback, and
|
||||
disabling/whole-record replacement/inherited-variant examples. Make one
|
||||
canonical bundle inventory in the format reference; link rather than repeat
|
||||
it throughout guidance. Link provider docs for alias/router behavior.
|
||||
3. Remove stale claims of no installed catalogs from current migration guidance
|
||||
and internal source/assembly documentation. Explain that an external
|
||||
collection is still supported, and replacing `auto` changes inherited child
|
||||
targets while retaining plugins. Do not add a rule that external catalogs
|
||||
must be namespaced or upstream aliases must have fixed capabilities.
|
||||
4. Keep the three maintained examples offline. Where their caller-only inventories
|
||||
are intentional, pass `WithBundledOpenRouter(false)` and explain that choice;
|
||||
retain useful fixture assertions rather than expanding them with unrelated
|
||||
default inventory. Add a short consumer-guide bundled-profile example instead
|
||||
of another live example program. Recheck README quickstart claims.
|
||||
5. Add the feature and compatibility effects to the currently untagged
|
||||
`docs/releases/v0.18.0.md`, including how to retain application-only behavior.
|
||||
Explain that previously unresolved references matching bundled IDs can now
|
||||
resolve, alongside the changed default inventories and inspection sources.
|
||||
If that release has been published by implementation time, use the next
|
||||
unreleased minor note instead; preserve published notes/tags. Follow the
|
||||
release procedure for the note/footer. No release operation is part of this
|
||||
sprint.
|
||||
6. Format changes, prepare dependency caches, and run with the compatible local
|
||||
Go toolchain:
|
||||
|
||||
```sh
|
||||
go test -race -vet=off ./...
|
||||
go vet ./...
|
||||
go run ./examples/go-library/prepare
|
||||
go run ./examples/go-library/run
|
||||
go run ./examples/go-library/observe
|
||||
git diff --check
|
||||
```
|
||||
|
||||
7. Review the complete diff for unexpected API changes, caller mutation, new
|
||||
source/history validation, implicit selection, network access, credential
|
||||
values, and stale links. Confirm only intended documentation and inventories
|
||||
changed. Follow the focused commit policy and record completion in the
|
||||
feature roadmap; retire completed plans during release preparation.
|
||||
|
||||
Completion requires default availability, full disabling, ordinary caller
|
||||
composition and provenance, correct cost-tier payloads and explicit diagnostic
|
||||
fallbacks, and current documentation. There are no unresolved product choices,
|
||||
external publication prerequisites, or new recurring release gates.
|
||||
Reference in New Issue
Block a user