Skip to content

model: speak the two dialects natively and stop hosting a Model component - #66

Closed
weilueluo wants to merge 3 commits into
mainfrom
agent-provider-catalog
Closed

model: speak the two dialects natively and stop hosting a Model component#66
weilueluo wants to merge 3 commits into
mainfrom
agent-provider-catalog

Conversation

@weilueluo

@weilueluo weilueluo commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Brain speaks exactly two API shapes — OpenAI and Anthropic — natively, and knows nothing else. Its
model configuration is the dialect, the endpoint, the credential and the model name; a Model is no
longer an extension. Which provider names a platform resolves is that platform's policy.

Design note: aex-research/docs/model-provider-and-catalog.md (aexhq/aex-research#16).
Companion PRs: aexhq/aex#134 (Aex owns the catalog), aexhq/extensions#48 (the Model packages go).

Public surface

const session = await brain.sessions.create({
  model: {
    dialect: "openai",                       // or "anthropic"
    baseUrl: "https://api.openai.com/v1",
    name: "gpt-4.1-nano",
    apiKey: process.env.OPENAI_API_KEY!,
  },
  agentloop: pi(),
});

There is no unsupported-provider case at this layer: any endpoint speaking either shape works by
pointing the URL at it. That is the extension point, and it needs no component, no registry and
no catalog.

What this restores rather than rebuilds

The native codecs were never deleted — the reset left them in crates/brain/src/provider/, still
built into the dialect factory both compositions compose (brain_providers::default_factory), and
severed only from the session path, with view.rs hard-coding Dialect::OpenAiChat "until the
legacy provider codecs are deleted". The sealed prefix carries the dialect again and
(brain.provider_factory)(dialect) resolves the turn's provider.

Deleted with the world they served: contracts/model/v1, the published model.wit, the model
bindgen and its runtime/worker arms, the guest fixture, brain-providers/src/component.rs, and the
"model" component kind in the manifest and the TypeScript client. provider leaves the create,
the sealed prefix, the session view and the model.usage event.

Regressions carried across, not left to die with the packages

Each of these was a live defect in the last two days, and each now has a test here:

  • an unset sampling field stays absent rather than serializing as null (both dialects)
  • the sealed system prompt reaches the request (leading system message / system field)
  • a non-2xx status keeps its status, body and retry hint
  • a stream that dies mid-frame leaves its bytes pending, so EOF reports rather than looks clean
  • reasoning_effort is refused at create by the Anthropic dialect that cannot send it

standalone_e2e now drives a scripted model endpoint over real HTTP in both shapes, asserting
per-session credentials, the system prompt and absent sampling fields on every round.

Endpoint convention

baseUrl is the endpoint's full API root, version segment included, and a dialect appends only its
operation path (/chat/completions, /messages). That is what models.dev publishes and what
providers that version differently — or not at all, like api.githubcopilot.com — actually need.

Cut clean

PrefixDoc loses provider and model_component with no tolerance window, so sessions sealed
before this do not hydrate: the development plane was reset for exactly this, and production has
never served a customer.

typed() moves here from @aexhq/model, beside the contracts whose extension-error it returns.

Gates

cargo fmt --all --check, cargo clippy --workspace --all-targets -D warnings,
cargo test --workspace (213 brain + protocol conformance + every brain-server gate including
standalone_e2e and component_tool_environment), cargo test -p brain-component-host (8/8 run
serially; the two frame-bound tests are timing-sensitive under local parallelism),
python tools/postprocess-generated.py --check, npm test (36), npm run package-smoke.

@aexhq/brain 0.6.0. 55 files, ~930 insertions against ~1260 deletions.

@weilueluo
weilueluo force-pushed the agent-provider-catalog branch 4 times, most recently from b25933f to bc6e30d Compare August 26, 2026 09:58
@weilueluo weilueluo changed the title model: name the provider and generate the models.dev catalog model: name the provider, generate the catalog, and bind an installed component Aug 26, 2026
@weilueluo
weilueluo force-pushed the agent-provider-catalog branch 2 times, most recently from a81af0c to 7d1e1a3 Compare August 26, 2026 10:15
@weilueluo
weilueluo marked this pull request as draft August 26, 2026 11:57
@weilueluo

Copy link
Copy Markdown
Contributor Author

Parked as a draft: the owner moved the boundary again — Brain will speak the two dialects natively and stop treating Model as a component kind, and the catalog moves to Aex. Re-planning before this branch is rewritten; nothing here should merge as it stands.

…eaks

Brain's model configuration is the endpoint, the shape it speaks, the credential and the model
name. The OpenAI and Anthropic request and response shapes are the de facto standard, so Brain
adopts them as its contract and keeps no notion of a provider anywhere: no Model world, no
component identity on the model object, and no provenance label on the session view or the usage
event. Which provider names resolve to an endpoint is a platform's policy, not the kernel's.
A session resolves through the dialect factory the compositions already build, so the OpenAI and
Anthropic codecs run in the kernel instead of behind a component. The Model registry, its host
bindings, its worker arms and its guest fixture are gone with the world they served.

The regressions the Model components learned the hard way are now Brain's own: an unset sampling
field stays absent rather than serializing as null, the sealed system prompt leads the request,
a non-2xx status keeps its status body and retry hint, a stream that dies mid-frame is reported
rather than read as a clean end, and reasoning effort is refused at create by the dialect that
cannot send it. The standalone gate now drives a scripted endpoint over real HTTP in both shapes.
The neutral client takes the dialect, the endpoint that speaks it, the credential and the model
name, and binds no Model component. typed() moves here from @aexhq/model: a component export must
return its declared extension-error rather than trap, which is a property of the contracts this
package already publishes, and it is now the only surviving export of that package.
@weilueluo
weilueluo force-pushed the agent-provider-catalog branch from 7d1e1a3 to 5261cbe Compare August 26, 2026 12:54
@weilueluo weilueluo changed the title model: name the provider, generate the catalog, and bind an installed component model: speak the two dialects natively and stop hosting a Model component Aug 26, 2026
@weilueluo
weilueluo marked this pull request as ready for review August 26, 2026 13:15
@weilueluo

Copy link
Copy Markdown
Contributor Author

Superseded by the merged provider normalization (#107) and the models.dev-driven registry (#114); conflicts with the post-reset main.

@weilueluo weilueluo closed this Aug 31, 2026
@weilueluo
weilueluo deleted the agent-provider-catalog branch August 31, 2026 09:08
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.

1 participant