Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions docs/changes/unreleased/1737-aiand-native-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
kind: added
title: ai& is offered as a provider of its own, asked for by key and nothing else
pr: 1737
surface: [chat, engine]
invalidates:
- >-
ai& was reachable only by hand, as a **Custom OpenAI-compatible API** row with
`https://api.aiand.com/v1` typed into it, a name typed beside it and a key
pasted in. It now has its own row in `/connect`, its own name in
`codeaf connect aiand` and its own row in the Connections category of /settings.
- >-
A provider that served more than one lab had no way to say so. Every direct
connection was treated as one family, because one lab's own API takes one
family. ai& is reached through six catalog vendors from the one key, and the
crew can now put a planner and a checker on different labs through it.
- >-
A connection is seated on its vendor's flagship, on the assumption that the
best model is the one to land a person on. On ai& that model takes text
alone, so the first attachment a person tried was refused by the model
codeaf had chosen for them. The preference is now the best model that also
takes a file.
---

One prepaid credit spends any model in ai&'s list, and that list reaches GLM,
DeepSeek, Qwen and Kimi at once, so a single key covers models a person would
otherwise open four accounts for. ai& is one row in `modelsource.Vendored()`,
and every surface a person touches is derived from that catalog — the connect
panel, the providers group, the picker group, the Connections category in
/settings and `codeaf connect aiand`.

ai& lists its models — `GET /v1/models` answers — so the connect line carries
the count that came back, for example `aiand is connected · 13 models`, and
`/model` fills its group from that list rather than asking for a typed id.

The row's preference was CHANGED by what that list says. It was the flagship,
`zai-org/glm-5.3`, whose vendor manifest answers `attachment: false` — a person
who dropped a screenshot into their first ai& conversation was refused by the
model codeaf had just seated them on. It is now `zai-org/glm-5.3-flash`: the
same family, `input: [text, image, video]`, and $0.15/$0.50 against the
flagship's $1.00/$4.00. The rule the row follows is the one `Source` already
states — the vendor's best model, not simply its flagship.

What makes ai& a row naming six vendors rather than one is that it is not one
lab, so the crew treats it as one route that spans families:
`crewVendors["aiand"]` names `deepseek`, `z-ai`, `moonshotai`, `qwen`, `openai`
and `google`, and one key can carry a planner and a checker sitting on different
labs in the same task. The rename in `crewVendorWire` is not a spelling
preference: asked on 2026-10-02 for the bare names the table avoids sending —
`glm-5.3-flash`, `deepseek-v4-flash` — api.aiand.com answered 404
`model_not_found` for every one, while the two-segment forms answered 200. So a
send without it is a call that fails.

The implementation is a base-URL swap: `Config.Direct` already switches off the
router's lane sheet, receipt fetch, price ceiling and routing vocabulary for a
vendor reached directly, `clientdoor.go` keys its adapter pool on the resolved
connection, and `internal/config/crew.go` carries the two tables above as its
only production code. Effort clamping for direct providers and one
crew-unreachable vendor (`motif-technologies/motif-3`, pickable by hand) are
recorded as future work, not acted on here. Nothing on the wire separates a
subscription from metered credit, so codeaf makes no plan claim — a run with
nothing left is answered by ai& with a `402` and `insufficient_credits`, the
balance speaking, not a bad key.
74 changes: 72 additions & 2 deletions internal/config/crew.go
Original file line number Diff line number Diff line change
Expand Up @@ -690,11 +690,61 @@ type CrewProvider struct {
collides map[string]bool
}

// crewVendors maps a direct connection to the catalog vendor prefixes it
// crewVendors maps a direct connection to the CATALOG vendor prefixes it
// serves. A connection whose Written is the vendor's own prefix needs no row.
//
// THE PREFIXES ARE THE CATALOG'S, NOT THE CONNECTION'S OWN. The router weighs
// catalog ids ([CrewCatalog]), and [CrewProvider.route] reads the vendor off the
// front of one; the connection's spelling is what the SEND is built from, below.
// Moonshot's catalog says `moonshotai/kimi-k3` and Codex's says `openai/gpt-5.5`,
// which is why those two rows read the way they do. A TABLE OF FACTS: every
// entry is a vendor somebody watched that connection serve.
var crewVendors = map[string][]string{
"moonshot": {"moonshotai"},
"codex": {"openai"},
// OBSERVED 2026-10-02 on a live GET https://api.aiand.com/v1/models with a
// key, and read again off https://api.aiand.com/v1/api.json: thirteen models
// under seven prefixes, which are six catalog vendors. `google` is among them
// even though no OTHER connection carries it, because what this table needs is
// a CATALOG vendor and the catalog has a gemma family, so the crew may seat a
// Gemma through this one key.
//
// THE SEVENTH, motif-technologies, IS NOT IN THIS TABLE AND THAT IS CORRECT.
// No catalog row names the vendor, so the router has no figures to weigh and
// the model can never become a candidate — however good Motif 3's own numbers
// are, and it is a 314B MoE. It still reaches a person: config's listing parse
// keeps every id the endpoint publishes, so `motif-technologies/motif-3` sits
// in the picker and is sent whole. It is also the one row of the thirteen that
// answers `structured_output: false`, which matters to whoever picks it by
// hand.
//
// THE LIST IS ORG-SCOPED AND DYNAMIC: ai& adds and retires orgs, so a vendor
// missing from it is a model this connection no longer serves, not a claim
// that it never could.
"aiand": {"deepseek", "z-ai", "moonshotai", "qwen", "openai", "google"},
}

// crewVendorWire spells the catalog's vendor segment the way a connection's OWN
// API spells it, where the two differ. A vendor absent from a connection's row
// keeps the catalog's own spelling — the ordinary case, and the reason moonshot
// and codex need no row here: the catalog already says `moonshotai/` and
// `openai/`. A connection named here is ITSELF A ROUTER: it addresses a model as
// <vendor>/<name> and never as a bare name, so its send keeps the segment a
// vendor's own API would have dropped.
//
// ai& IS THE FIRST SUCH CONNECTION, AND THE TABLE IS NOT OPTIONAL. Its listing
// names `deepseek-ai/deepseek-v4-flash` and `zai-org/glm-5.3-flash` where the
// catalog says `deepseek/deepseek-v4-flash` and `z-ai/glm-5.3-flash`.
//
// A BARE NAME IS REFUSED, which is what makes the send a correctness question
// rather than a tidiness one. Asked on 2026-10-02 for the bare names this table
// exists to avoid sending — `deepseek-v4-flash`, `glm-5.3-flash`, `kimi-k3` —
// api.aiand.com answered 404 `model_not_found` for every one, while
// `deepseek-ai/deepseek-v4-flash` and `zai-org/glm-5.3-flash` answered 200. So
// the send without this table is not a spelling somebody might prefer; it is a
// call that fails. A TABLE OF FACTS, one spelling each.
var crewVendorWire = map[string]map[string]string{
"aiand": {"deepseek": "deepseek-ai", "z-ai": "zai-org"},
}

// CrewProvidersAt is every provider the crew can route through: each
Expand Down Expand Up @@ -800,7 +850,27 @@ func (p CrewProvider) route(id string, model crewroute.Model, known bool) (crewr
return crewroute.Route{}, false
}
}
return crewroute.Route{Provider: p.ID, Send: p.Written + "/" + tail, Kind: p.Kind}, true
return crewroute.Route{Provider: p.ID, Send: p.send(vendor, tail), Kind: p.Kind}, true
}

// send is the id a call to this connection carries, which is not always its
// prefix over the model's name.
//
// THE ORDINARY SEND DROPS THE CATALOG'S VENDOR SEGMENT, because every vendor
// whose own API this table has reached so far takes a BARE model id: the
// catalog's `moonshotai/kimi-k3` goes out as `moonshot/kimi-k3`. A CONNECTION
// THAT IS ITSELF A ROUTER keeps the segment, spelled as its own API spells it
// ([crewVendorWire]) — `aiand/deepseek-ai/deepseek-v4-flash`, which is the id
// that listing named, rather than an `aiand/deepseek-v4-flash` no router of that
// shape has ever heard of.
func (p CrewProvider) send(vendor, tail string) string {
if wire, ok := crewVendorWire[p.ID]; ok {
if spelled, ok := wire[vendor]; ok {
return p.Written + "/" + spelled + "/" + tail
}
return p.Written + "/" + vendor + "/" + tail
}
return p.Written + "/" + tail
}

// CrewCandidatesAt is what the router may pick from on this profile: every
Expand Down
164 changes: 163 additions & 1 deletion internal/config/crew_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ func rawCrewRow(t *testing.T, dir, key string) string {
func crewProfile(t *testing.T) string {
t.Helper()
for _, name := range []string{APIKeyEnv, "OPENAI_API_KEY", ModelEnv, PlanModelEnv, CheckModelEnv, "CODEAF_BASE_URL",
"DEEPSEEK_API_KEY", "ZHIPU_API_KEY", "MOONSHOT_API_KEY", "MINIMAX_API_KEY", "DASHSCOPE_API_KEY"} {
"DEEPSEEK_API_KEY", "ZHIPU_API_KEY", "MOONSHOT_API_KEY", "MINIMAX_API_KEY", "DASHSCOPE_API_KEY",
"AIAND_API_KEY"} {
t.Setenv(name, "")
}
dir := t.TempDir()
Expand Down Expand Up @@ -399,6 +400,167 @@ func TestAPlanRouteIsFreeAndCollidingIdsKeepTheirRouterSpelling(t *testing.T) {
}
}

// A DIRECT CONNECTION REACHES THE CATALOG VENDORS IT ACTUALLY CARRIES, which is
// what [crewVendors] is for: Moonshot's Written is `moonshot` where the catalog
// writes `moonshotai/`, and Codex's is `codex` where the catalog writes `openai/`,
// so neither would ever be offered a Kimi or a GPT without a row naming the
// vendor the catalog actually uses.
//
// THE SEND IS THE ID THAT CONNECTION'S OWN API KNOWS, and that is not always the
// catalog's id with the segment dropped. ai& is the first connection whose own
// API is ITSELF a router — its 2026-10-02 listing named
// `deepseek-ai/deepseek-v4-flash` and `zai-org/glm-5.3-flash` — so its send
// keeps the segment, spelled as ai& spells it rather than as the catalog does.
//
// AND IT IS A MULTI-FAMILY PROVIDER, which is the whole reason a row naming six
// vendors exists at all. ai& is not one lab with one model line: its listing
// reaches GLM, DeepSeek, Qwen and Kimi at once, so ONE key buys the same
// families a person would otherwise open four accounts for. The two facts are
// kept apart on purpose — a vendor that served only one family would need no row
// naming vendors at all, because its own Written would be that family.
func TestADirectRouterConnectionServesItsCatalogVendorsUnderItsOwnIds(t *testing.T) {
dir := crewProfile(t)
if err := writeProfileValue(dir, keyModelSources, []PersistedSource{
{ID: "aiand", Written: "aiand", Key: "sk-aiand-crewtest-0123456789ab", Order: 1},
}); err != nil {
t.Fatal(err)
}
// THE FIXTURE CATALOG IS DELIBERATELY WIDENED HERE AND ONLY HERE. The shared
// rows carry one model per family for three families, which is enough to keep
// the routing tests cheap but cannot show a multi-family provider reaching
// more than one — and a claim that one key spans four labs deserves the four
// labs in the fixture rather than an inference from three.
widenCrewCatalog(t,
catalog.Model{ID: "qwen/qwen3.8-flash", OpenWeights: true, PromptPrice: 2e-7, CompletionPrice: 6e-7,
IntelligenceIndex: 40.1, CodingIndex: 70.2, AgenticIndex: 49, ArenaElo: 1330,
ContextLength: 1310720, Parameters: []string{"tools"}},
catalog.Model{ID: "openai/gpt-oss-120b", OpenWeights: true, PromptPrice: 1e-7, CompletionPrice: 4e-7,
IntelligenceIndex: 33, CodingIndex: 55, AgenticIndex: 30, ArenaElo: 1200,
ContextLength: 131072, Parameters: []string{"tools"}},
catalog.Model{ID: "google/gemma-4-31b-it", OpenWeights: true, PromptPrice: 2e-7, CompletionPrice: 5e-7,
IntelligenceIndex: 36, CodingIndex: 58, AgenticIndex: 31, ArenaElo: 1240,
ContextLength: 262144, Parameters: []string{"tools"}},
// AND THE ONE THAT IS NOT. motif-technologies is on ai&'s listing and in
// no catalog row, so it can never become a candidate the crew weighs —
// which is the whole difference between a vendor ai& happens to serve and
// one the router knows how to price.
catalog.Model{ID: "motif-technologies/motif-3", OpenWeights: true, PromptPrice: 5e-7, CompletionPrice: 2e-6,
IntelligenceIndex: 45, CodingIndex: 68, AgenticIndex: 44, ArenaElo: 1300,
ContextLength: 262144, Parameters: []string{"tools"}},
)
routes := map[string]string{}
families := map[string]bool{}
for _, c := range CrewCandidatesAt(dir) {
for _, r := range c.Routes {
if r.Provider == "aiand" {
routes[c.Model.ID] = r.Send
if vendor, _, ok := strings.Cut(c.Model.ID, "/"); ok {
families[vendor] = true
}
}
}
}
want := map[string]string{
// Both renames come from the listing the row's comment cites.
"deepseek/deepseek-v4-flash": "aiand/deepseek-ai/deepseek-v4-flash",
"z-ai/glm-5.3-flash": "aiand/zai-org/glm-5.3-flash",
// And a vendor the catalog and ai& already spell the same way keeps the
// catalog's segment rather than losing it.
"moonshotai/kimi-k3": "aiand/moonshotai/kimi-k3",
"qwen/qwen3.8-flash": "aiand/qwen/qwen3.8-flash",
"openai/gpt-oss-120b": "aiand/openai/gpt-oss-120b",
// A vendor no OTHER connected provider carries is still reached, because
// what the crew needs is a CATALOG vendor and google is one. This is the
// half of the listing that is easy to get backwards.
"google/gemma-4-31b-it": "aiand/google/gemma-4-31b-it",
}
for model, send := range want {
if routes[model] != send {
t.Errorf("%s through ai& sends %q, want %q", model, routes[model], send)
}
}
// THE MULTI-FAMILY CLAIM, counted rather than read off the sends above: one
// connection, four labs. A row that quietly collapsed to a single family would
// still pass every send assertion while failing this.
for _, family := range []string{"deepseek", "z-ai", "moonshotai", "qwen"} {
if !families[family] {
t.Errorf("ai& reaches no %s model, want a family alongside the other three", family)
}
}
// A vendor it does not carry is still not offered through it, however good
// the catalog's figures for it are.
if _, offered := routes["anthropic/claude-opus-5"]; offered {
t.Error("ai& offers a model from a vendor it does not serve")
}
// AND a model the CATALOG does not carry is not offered through it either,
// however good ai&'s own figures are. Motif 3 is on the listing and in the
// crew row's absence is the whole story: no catalog row, no candidate.
if _, offered := routes["motif-technologies/motif-3"]; offered {
t.Error("ai& offers a model no catalog row names, so the crew cannot weigh it")
}
}

// ONE KEY, TWO FAMILIES, TWO SEATS. This is the claim the row above cannot make
// on its own: that a multi-family provider is not merely reachable but USABLE as
// one route, so a single connection can carry a planner and a checker that sit on
// different labs in the same task. A crew that treated ai& as one family would
// have to put both seats on the same model, and the point of the provider is that
// it does not have to.
func TestOneMultiFamilyKeyCarriesSeatsOnDifferentLabs(t *testing.T) {
dir := crewProfile(t)
if err := writeProfileValue(dir, keyModelSources, []PersistedSource{
{ID: "aiand", Written: "aiand", Key: "sk-aiand-crewtest-0123456789ab", Order: 1},
}); err != nil {
t.Fatal(err)
}
widenCrewCatalog(t, catalog.Model{ID: "qwen/qwen3.8-flash", OpenWeights: true, PromptPrice: 2e-7, CompletionPrice: 6e-7,
IntelligenceIndex: 40.1, CodingIndex: 70.2, AgenticIndex: 49, ArenaElo: 1330,
ContextLength: 1310720, Parameters: []string{"tools"}})
decision, err := RouteCrew(dir, CrewAsk{Task: crewroute.Task{Text: openTask}})
if err != nil {
t.Fatal(err)
}
seats := map[crewroute.Seat]crewroute.Pick{}
for _, seat := range crewroute.Seats {
seats[seat] = decision.Seat(seat)
}
// Every seat is answered, and every answer is answered by the one key.
labelled := map[string][]string{}
for _, seat := range crewroute.Seats {
pick := seats[seat]
if pick.Model == "" {
t.Fatalf("the %s seat got no model", seat)
}
if pick.Provider != "aiand" {
t.Errorf("the %s seat ran on %q, want the one connected provider", seat, pick.Provider)
}
vendor, _, _ := strings.Cut(pick.Model, "/")
labelled[vendor] = append(labelled[vendor], string(seat))
}
// The checker buys the stronger model when the work is open-ended, so the
// seats genuinely straddle two labs here — which is the behaviour under test,
// and the reason this is asserted on the SEATS rather than on the catalog.
if len(labelled) < 2 {
t.Fatalf("every seat landed on one family (%v), want the crew spanning labs through one key", labelled)
}
for family, seats := range labelled {
t.Logf("ai& served %s to the %s", family, strings.Join(seats, " and "))
}
}

// widenCrewCatalog adds rows to the catalog for THIS test only and puts the
// previous one back afterwards. It reads the live catalog rather than a literal
// so a row added to the shared fixture is carried along instead of dropped.
func widenCrewCatalog(t *testing.T, extra ...catalog.Model) {
t.Helper()
previous := CrewCatalog
base := previous()
t.Cleanup(func() { CrewCatalog = previous })
CrewCatalog = func() []catalog.Model {
return append(append([]catalog.Model(nil), base...), extra...)
}
}

func TestMigratingARetiredCrew(t *testing.T) {
dir := crewProfile(t)
// A balanced preset applied in the `open` family: all five rows written,
Expand Down
Loading
Loading