skyl ships no model-name constants and never validates a model against a list.
Request.Model is an opaque string passed straight to the provider. This page
is about why, and about the trade you are accepting.
You will learn
- Why a curated model list is guaranteed to be wrong
- What you give up by not having one
- How to discover what a provider currently offers
- What model metadata skyl does and does not give you
The decision#
Model: "claude-opus-5" // works the day it launches
Model: "gpt-5.6"
Model: "gemini-3.6-flash"
Model: "llama3.3" // your own Ollama tagModel: "claude-opus-5" // works the day it launches
Model: "gpt-5.6"
Model: "gemini-3.6-flash"
Model: "llama3.3" // your own Ollama tagNo enum, no validation table, no release required.
Deep diveWhy a curated list is guaranteed to be wrong
Consider a four-week window in mid-2026:
| Model | Released | |---|---| | GPT-5.6 Sol | 9 Jul 2026 | | Gemini 3.6 Flash | 21 Jul 2026 | | Claude Opus 5 | 24 Jul 2026 | | Qwen3.7 Flash | 27 Jul 2026 |
Any hardcoded table is wrong within weeks. And the failure mode is the worst possible one for a library: skyl would be rejecting a model you are entitled to use and are already paying for, because skyl has not cut a release.
Pass-through is correct forever. Recorded as ADR-0004.
The price#
Handle it explicitly, because the message is otherwise easy to misread as a credential problem:
resp, err := client.Complete(ctx, req)
if errors.Is(err, skyl.ErrNotFound) {
return fmt.Errorf("no model %q on %s — check the spelling, or your account's access", req.Model, resp.Provider)
}resp, err := client.Complete(ctx, req)
if errors.Is(err, skyl.ErrNotFound) {
return fmt.Errorf("no model %q on %s — check the spelling, or your account's access", req.Model, resp.Provider)
}Discovering what is available#
Ask the provider, live:
models, err := client.Models(ctx)
if err != nil {
return err
}
for _, m := range models {
fmt.Printf("%s (%s) context=%d\n", m.ID, m.DisplayName, m.ContextWindow)
}models, err := client.Models(ctx)
if err != nil {
return err
}
for _, m := range models {
fmt.Printf("%s (%s) context=%d\n", m.ID, m.DisplayName, m.ContextWindow)
}That hits the provider's real models endpoint, so the answer is never stale — it is not skyl's opinion, it is the provider's answer.
All four adapters support it; none return ErrUnsupported. What they report
varies:
| Field | anthropic | openai | openaicompat | gemini |
|---|---|---|---|---|
| ID, Provider, Raw | yes | yes | yes | yes |
| DisplayName | yes | empty | host-dependent | yes |
| ContextWindow | yes | empty | host-dependent | yes |
| MaxOutputTokens | yes | never set | never set | yes |
OpenAI's models endpoint simply has no such fields — the gap is upstream, not in
the adapter. ModelInfo.Raw carries the provider's untouched entry, so anything
they do report is reachable.
Model metadata#
Context window, pricing and modality are genuinely useful, and skyl does not
have them for every provider today. The plan is a generated registry — a
scheduled CI job that queries live provider endpoints and regenerates a
committed file — rather than a hand-typed table that silently rots. Until it
lands, ModelInfo carries what the provider returns.
Practical advice#
Put model IDs in configuration, not in code. Since skyl will not validate them, your config layer is the right place to fail fast:
type Config struct {
FastModel string `env:"FAST_MODEL,required"`
SmartModel string `env:"SMART_MODEL,required"`
}type Config struct {
FastModel string `env:"FAST_MODEL,required"`
SmartModel string `env:"SMART_MODEL,required"`
}That way changing model is a deploy, not a release — which is the whole benefit pass-through was protecting.
Recap
- Model IDs are opaque strings; skyl ships no constants and validates nothing.
- A curated list would eventually reject a model you are entitled to use.
- The price is that a typo costs a round trip and arrives as
ErrNotFound. Client.Models(ctx)asks the provider live, so the answer is never stale.- What model listing reports varies sharply; OpenAI supplies ID and little else.
- Keep model IDs in configuration so changing one is a deploy, not a release.
Try out some challenges
Each of these is solvable with what this page covered. Run them against the sandbox — no API key needed.
Validate model IDs at startup
You cannot get compile-time checking, but you can fail at boot rather than on the first user request.
Show hint
Client.Models gives you the live list. Check your configured IDs against it
once, during startup.
Show solution
func checkModels(ctx context.Context, c *skyl.Client, want ...string) error {
models, err := c.Models(ctx)
if err != nil {
// A provider that cannot list models is not a reason to refuse to
// start — pass-through still works. Warn and continue.
log.Printf("could not verify model IDs: %v", err)
return nil
}
have := make(map[string]bool, len(models))
for _, m := range models {
have[m.ID] = true
}
for _, w := range want {
if !have[w] {
return fmt.Errorf("configured model %q is not offered by this provider", w)
}
}
return nil
}func checkModels(ctx context.Context, c *skyl.Client, want ...string) error {
models, err := c.Models(ctx)
if err != nil {
// A provider that cannot list models is not a reason to refuse to
// start — pass-through still works. Warn and continue.
log.Printf("could not verify model IDs: %v", err)
return nil
}
have := make(map[string]bool, len(models))
for _, m := range models {
have[m.ID] = true
}
for _, w := range want {
if !have[w] {
return fmt.Errorf("configured model %q is not offered by this provider", w)
}
}
return nil
}Note the deliberate non-failure when listing itself fails. Refusing to start because a metadata endpoint was down would be worse than the typo you are guarding against.