Skip to content

connect: ai& is a provider of its own, and one key reaches four labs - #1737

Open
fenilmodi00 wants to merge 3 commits into
Agent-Field:devfrom
fenilmodi00:aiand-provider
Open

fenilmodi00 wants to merge 3 commits into
Agent-Field:devfrom
fenilmodi00:aiand-provider

Conversation

@fenilmodi00

@fenilmodi00 fenilmodi00 commented Oct 2, 2026 •

Copy link
Copy Markdown

What changed

ai& (api.aiand.com) is now a provider of its own. One key reaches models from four labs, so it covers ground that would otherwise take four accounts.

The provider is one row in modelsource.Vendored(). The connect panel, the providers group, the picker group, the Providers tab and codeaf connect aiand all derive from that catalog, so nothing else needed wiring.

internal/config/crew.go grew two tables, because ai& is the first connection here that is itself a router:

 var crewVendors = map[string][]string{
 	"moonshot": {"moonshotai"},
 	"codex":    {"openai"},
+	"aiand": {"deepseek", "z-ai", "moonshotai", "qwen", "openai", "google"},
 }
+
+var crewVendorWire = map[string]map[string]string{
+	"aiand": {"deepseek": "deepseek-ai", "z-ai": "zai-org"},
+}

The first row is what makes one key serve several labs. The second is a spelling, and it is load-bearing: ai& publishes deepseek-ai/deepseek-v4-flash where the catalog says deepseek/deepseek-v4-flash, and asked on 2026-10-02 for the bare names it answered 404 model_not_found for every one. moonshotai is deliberately left alone, because ai& serves moonshotai/kimi-k3 and renaming it would break a send that works.

The row's preferred model moved. https://api.aiand.com/v1/api.json answers 200 without a key and publishes per-model cost, limits and modalities; it says the flagship zai-org/glm-5.3 is attachment: false, so the model codeaf seated a new connection on refused their first screenshot. The preference is now zai-org/glm-5.3-flash: same family, same million tokens, takes image and video, and $0.15/$0.50 against $1.00/$4.00.

The manual gained ai& in the two places that named the provider group and counted it, where leaving the old list would have made the page false. The crew page needed no edit, since it already says every model a connected provider serves is a candidate, and that sentence is now true of ai&.

Merge danger. Two-way door, and the revert is clean. The one thing worth a reviewer's attention is crewVendorWire: it reads close to crewroute.vendorOrgs, which holds the inverse mappings, and the two disagree on moonshot. Folding them together would rename aiand/moonshotai/kimi-k3 into a send that 404s. The commit message says so at the point of the table.

How it was checked

Live against the API on 2026-10-02: the listing, a streamed reply with its usage frame, tool calling, max_tokens, stream_options, the refusal of a bare model name, and the manifest every cost figure above is read from.

The multi-family claim is proved on the seats rather than the catalog, because a row that collapsed to one family would still pass every send assertion:

TestOneMultiFamilyKeyCarriesSeatsOnDifferentLabs
    ai& served z-ai to the worker and planner
    ai& served moonshotai to the checker
ok  	github.com/Agent-Field/codeaf/internal/config	14.797s
ok  	github.com/Agent-Field/codeaf/internal/modelsource	1.318s
ok  	github.com/Agent-Field/codeaf/internal/manual	7.043s
ok  	github.com/Agent-Field/codeaf/internal/namelaw	7.431s

$ make test-laws
laws: 138 files in 30 packages

go build ./..., go vet and gofmt -l are clean. One earlier run showed TestADecisionTakesUnderTwoMilliseconds at 2.11ms against a 2ms budget while five packages ran at once; internal/crewroute is not in this diff and the test passes 3/3 focused and green on its own.

Not in this change

The effort words are per-model and do not line up with codeaf's ladder. Only high is universal across the thirteen: kimi-k2.7-code takes high alone, qwen3.8-27b takes none,low,medium,xhigh and so not high. ai& publishes reasoning_efforts per model, but config.listedOutcome keeps only the id and the adapter's ReasoningProfile seam is fed by the router's catalog, so a direct provider's profile reads unknown and the requested word goes out unclamped. Fixing that means changing internal/catalog, internal/config and internal/provider together and would move effort handling for every direct connection, so it wants its own change and a live key. The change entry records the measurements.

motif-technologies/motif-3 reaches the picker and sends correctly, and the crew cannot weigh it because no catalog row names that vendor. TestAModelTheCatalogCannotWeighStillReachesThePicker holds that split so it is not later "fixed" by dropping the row from the listing.

Checklist

  • A change entry — docs/changes/unreleased/1737-aiand-native-provider.md, named for this pull request and carrying the four beliefs it contradicts.
  • The manual knows about it — commands.md and services.md corrected where they named the group and counted it. No slash command, key, tool, default, limit or refusal moved, and no gate asks for more.
  • No new line in .github/known-red.txt — the file does not exist.
  • Only my own paths are staged.

ai& (api.aiand.com) is a Japan-hosted, OpenAI- and Anthropic-compatible
inference service selling one prepaid credit per token. Its list reaches models
from several labs at once, so one key covers what would otherwise take four
accounts.

The PROVIDER is one row in modelsource.Vendored(). Everything else the person
touches is already derived from that catalog: the connect panel, the providers
group, the picker group, the Providers tab and `codeaf connect aiand`.
Config.Direct already switches off the router's lane sheet, generation-receipt
fetch, price ceiling and routing vocabulary for a vendor reached directly, so a
direct vendor stays a base-URL swap. internal/session needed no change either —
clientdoor.go keys its adapter pool on the resolved connection and strips the
service segment from the id it sends, which is why ai&'s own two-segment ids
already reach it whole.

WHAT MAKES THIS ROW NAME SIX VENDORS RATHER THAN ONE IS THAT AI& IS NOT ONE LAB.
Every other direct connection here is one vendor with one model family, which is
why the existing rows need no vendor list: their own Written prefix already names
their only family. ai& reaches GLM, DeepSeek, Qwen and Kimi at once, so the crew
is given the CATALOG vendor words it actually serves —
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. That is the shape the provider exists for, and it is proved on the
SEATS rather than on the catalog, because a row that quietly collapsed to one
family would still pass every send assertion. `google` is in that list even
though no other connection carries it, because what the crew needs is a CATALOG
vendor and the catalog has a gemma family; the seventh organisation on the
listing, motif-technologies, is in no catalog row, so the crew cannot weigh Motif
3 however good its own numbers are.

TWO FACTS THE CATALOG ALONE DOES NOT GIVE, and both are about that send.

ai& is the first connection here that is ITSELF A ROUTER: its ids already name
the lab that built the model (`zai-org/glm-5.3`, `deepseek-ai/deepseek-v4.1-
flash`), so a send is the provider segment over the vendor's OWN id rather than a
bare name. crewVendorWire spells the catalog's vendor the way ai& spells it.

This is not a tidiness fix. Asked on 2026-10-02 for the bare names that 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 a
send without the rename is a call that fails, not a spelling somebody might
prefer. A vendor the two spell the same way keeps the catalog's own segment, so
`moonshotai/kimi-k3` is sent as `aiand/moonshotai/kimi-k3` rather than being
renamed to some other organisation's name. Note this is NOT crewroute's
vendorOrgs table read backwards: that one maps `moonshot` to the catalog's
`moonshotai`, and folding the two together would rename a send that works into
one that 404s.

THE PREFERENCE MOVED, and the vendor's own manifest is why.
https://api.aiand.com/v1/api.json answers 200 without a key and publishes cost,
limits, modalities and capabilities per model. It says the flagship,
`zai-org/glm-5.3`, is `attachment: false` with `input: [text]` — so a person who
dropped a screenshot into their first ai& conversation was refused by the model
codeaf had just seated them on. The preference is now `zai-org/glm-5.3-flash`:
same family, same million tokens (1048550 against 1048576), `input: [text, image,
video]`, and $0.15/$0.50 against $1.00/$4.00. It is the cheapest of the four rows
that take attachments and hold 1M, which is the rule Source already states — the
vendor's best model, not simply its flagship.

DELIBERATELY NOT COPIED FROM ALIBABA'S ROW. DashScope declares two regions and two
billing doors because it genuinely runs an international and a China host and
sells a coding plan beside metered traffic. ai& is Japan-only on one endpoint and
sells one prepaid credit, so there is no region to choose and no second billing
product to tell apart — and a test already holds that law: a second door is legal
only with wire evidence distinguishing the billing products, which is why MiniMax
declares none either. Offering a choice that does not exist is worse than offering
none. The one-door claim says why in the row for the same reason: nothing on the
wire separates a plan from metered credit. A 402 insufficient_credits is already
read correctly by paymentrefusal's generic 402 rule. ai& collides with no model
author, so it keeps the name `aiand` and never becomes `aiand-direct`.

DELIBERATELY NOT FIXED HERE, AND IT IS NOT A BUG IN THIS ROW. ai& publishes the
effort words per model (`reasoning_efforts` on every row of /v1/models) and only
`high` is universal across the thirteen: kimi-k2.7-code takes `high` alone,
qwen3.8-27b takes none/low/medium/xhigh and so NOT `high`, and glm-5.3 takes
low/high/max and so neither `none` nor `medium`. The listing parse keeps only
the `id`, and the adapter's ReasoningProfile seam is fed by the ROUTER's catalog,
so a direct provider's profile reads unknown and the requested word goes out
unclamped. Teaching a direct provider to publish its own reasoning profile means
changing internal/catalog, internal/config and internal/provider together, and it
would move effort handling for every direct connection rather than this one. It
wants its own change and a live key; the change entry records the measurements so
they are not rediscovered.

The rest of the diff is what the row forces. The vendored-row test pins the
table's order and its Ollama-is-the-only-optional-key law, so both move and the
Ollama lookup stops being an index — inserting a row above it must not have to be
re-counted. The three test helpers that clear provider key variables gain
AIAND_API_KEY, because connect/key.go resolves a connection straight from the
environment and a stray variable would otherwise leak a CONNECTED ai& into those
panels. A listing test holds that a model the catalog cannot weigh still reaches
the picker with its vendor segment whole, which is the other half of the motif
story and the wrong fix twice over. The manual gains the provider in the two
places that named the group and counted it, where leaving the old list would have
made the page false; the crew page needed no edit, because it already says every
model a connected provider serves is a candidate, and that sentence is now true
of ai& rather than needing one written for it.

Verified live against the API on 2026-10-02: the listing, a streamed reply with its
usage frame, tool calling, max_tokens, stream_options, the refusal of a bare
model name, and the manifest every cost and modality figure above is read from.
Also observed: X-Cost is advertised in access-control-expose-headers and never
sent, so a direct provider's cost cannot be read off a response.
docs/rules/changelog.md puts the order plainly: push the branch, open the pull
request, let `check` go red once, then add the entry with the number that
failure names. The entry was written against a guessed 1736, and the pull
request is 1.

Renaming the file is the whole change. `make changelog-check` reads 9 entries,
all well formed.

One sentence inside it was also untrue once the crew tables landed: it still
said the change was one row and nothing else, which stopped being the case
when internal/config/crew.go grew two. It now says which two files carry
production code.
The entry was first written against a guessed 1736, then renamed to 1 when the
pull request was opened on the fork. It is now opened upstream, where the
number is 1737, and that is the number the entry has to carry: the file name is
what the roll-up reads, and a merge takes the upstream number.

`make changelog-check` reads 9 entries, all well formed.
@CLAassistant

CLAassistant commented Oct 2, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@santoshkumarradha

Copy link
Copy Markdown
Member

Hey, thanks for the PR. Could you sign the CLA when you get a chance so we can review it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants