okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

Aly Raffauffeat(api)!: migrate daemon and clients to protocol 361c43ca

main
4.9 KiB41 linesraw

proxy

The proxy extension serves an OpenAI-compatible Chat Completions endpoint for every saved provider profile, so other tools can use albedo's providers (Codex, Antigravity, OpenAI-compatible endpoints) through one base url. It is installed and disabled; enable it globally in /extensions or with {"enabled": {"proxy": true}} in $ALBEDO_HOME/extensions.json. The change applies to the next request.

endpoint

The proxy is a service mounted at http://127.0.0.1:<port>/extensions/proxy/v1. Set ALBEDO_PORT in the daemon's environment to keep the port stable; otherwise it changes each time the daemon starts (see $ALBEDO_HOME/daemon.json).

  • GET /extensions/proxy/v1/models lists <profile>/<model> for every usable profile in config.json. A profile with its own baseUrl (an API-key profile) lists only its saved model, because models.dev can only guess what an arbitrary endpoint serves. Other profiles, such as codex and antigravity, list their provider's catalog, or their saved model while no catalog is cached. Profiles that cannot be used, such as a malformed entry, are named under a nonstandard errors field ([{"profile", "message"}]) instead of failing the list.
  • The list only helps clients choose. Any <profile>/<model> is requested as given, listed or not, and a broken or signed-out profile fails only its own requests.
  • POST /extensions/proxy/v1/chat/completions accepts model as <profile>/<model>, or a bare <profile> for its saved model. Streaming (stream, stream_options.include_usage) and non-streaming replies are supported.

Use the daemon bearer token as the client's API key. The operator can set ALBEDO_PROXY_ALLOW_ANONYMOUS=1 when starting the daemon to allow anonymous loopback requests to these two proxy routes. Core and other extension routes still require their own authentication. Browser requests follow the daemon's configured origin policy.

The request body limit is 32,000,000 bytes. Fixed-length and chunked uploads are supported; ambiguous framing returns 400, and oversized bodies return 413. A request without body framing has an empty body.

translation

Each request is translated into albedo's request types and projected onto the profile's upstream, as a session's history is:

  • Leading system and developer messages become the instructions. Later ones become user notes in place.
  • user text and base64 data: images become user input. Image urls are not fetched.
  • assistant messages with tool_calls become replayable tool calls. tool messages become their results.
  • tools, max_tokens/max_completion_tokens, temperature, top_p, stop, tool_choice, parallel_tool_calls, reasoning_effort (or reasoning.effort), and response_format (json_object or json_schema) become albedo's generation options. Each provider applies what its wire can express:
    • OpenAI Chat Completions and Responses take all of them, except that Responses has no stop sequences.
    • Codex keeps its fixed sampling. Effort, tool choice, parallel calls, and format override its defaults (medium effort, automatic parallel tools).
    • Antigravity maps effort onto each model's thinking budget or level, and sampling, stop sequences, and format into generationConfig. Claude routes always run tools VALIDATED. On Gemini routes a forced tool choice is also restated as a final instruction, because Cloud Code Assist drops the tool mode there.

Text and reasoning stream as delta.content and delta.reasoning_content. Each tool call is sent whole, with its id, name, and arguments, when the turn ends. Errors from the provider arrive as an error chunk while streaming, or as a JSON error otherwise.

state

The proxy stores nothing. Each request carries its conversation, and provider identity that should be stable across it, such as a Codex account or an Antigravity trajectory, is derived from the profile and the first user message.

Providers also return opaque state that they require back within a tool-calling turn: Codex's encrypted reasoning items, Gemini's thought signatures on function calls, and Claude's signed thinking. Clients echo only plain Chat Completions, but they always echo a tool call's id verbatim. So when a turn ends in tool calls, the proxy compresses that turn's native provider output, tags it with the profile that produced it, and appends it to the first call's id as <id>__albedo__<state>, using only [A-Za-z0-9_-]. When the history comes back, those turns become albedo transcript entries again and pass through the same projection a session uses. A request to the same profile replays them verbatim; a request to another profile gets the portable text and calls. Tool results are matched to the provider's own call ids. State larger than 256 KiB is not carried, and damaged state falls back to the portable history. Turns without tool calls carry no state, which the providers do not need after the next user message.

1# proxy
2
3The `proxy` extension serves an OpenAI-compatible Chat Completions endpoint for every saved provider profile, so other tools can use albedo's providers (Codex, Antigravity, OpenAI-compatible endpoints) through one base url. It is installed and disabled; enable it globally in `/extensions` or with `{"enabled": {"proxy": true}}` in `$ALBEDO_HOME/extensions.json`. The change applies to the next request.
4
5## endpoint
6
7The proxy is a [service](extensions.md) mounted at `http://127.0.0.1:<port>/extensions/proxy/v1`. Set `ALBEDO_PORT` in the daemon's environment to keep the port stable; otherwise it changes each time the daemon starts (see `$ALBEDO_HOME/daemon.json`).
8
9- `GET /extensions/proxy/v1/models` lists `<profile>/<model>` for every usable profile in `config.json`. A profile with its own `baseUrl` (an API-key profile) lists only its saved model, because models.dev can only guess what an arbitrary endpoint serves. Other profiles, such as `codex` and `antigravity`, list their provider's catalog, or their saved model while no catalog is cached. Profiles that cannot be used, such as a malformed entry, are named under a nonstandard `errors` field (`[{"profile", "message"}]`) instead of failing the list.
10- The list only helps clients choose. Any `<profile>/<model>` is requested as given, listed or not, and a broken or signed-out profile fails only its own requests.
11- `POST /extensions/proxy/v1/chat/completions` accepts `model` as `<profile>/<model>`, or a bare `<profile>` for its saved model. Streaming (`stream`, `stream_options.include_usage`) and non-streaming replies are supported.
12
13Use the daemon bearer token as the client's API key. The operator can set
14`ALBEDO_PROXY_ALLOW_ANONYMOUS=1` when starting the daemon to allow anonymous
15loopback requests to these two proxy routes. Core and other extension routes
16still require their own authentication. Browser requests follow the daemon's
17configured origin policy.
18
19The request body limit is 32,000,000 bytes. Fixed-length and chunked uploads
20are supported; ambiguous framing returns `400`, and oversized bodies return
21`413`. A request without body framing has an empty body.
22
23## translation
24
25Each request is translated into albedo's request types and projected onto the profile's upstream, as a session's history is:
26
27- Leading `system` and `developer` messages become the instructions. Later ones become user notes in place.
28- `user` text and base64 `data:` images become user input. Image urls are not fetched.
29- `assistant` messages with `tool_calls` become replayable tool calls. `tool` messages become their results.
30- `tools`, `max_tokens`/`max_completion_tokens`, `temperature`, `top_p`, `stop`, `tool_choice`, `parallel_tool_calls`, `reasoning_effort` (or `reasoning.effort`), and `response_format` (`json_object` or `json_schema`) become albedo's generation options. Each provider applies what its wire can express:
31  - OpenAI Chat Completions and Responses take all of them, except that Responses has no stop sequences.
32  - Codex keeps its fixed sampling. Effort, tool choice, parallel calls, and format override its defaults (medium effort, automatic parallel tools).
33  - Antigravity maps effort onto each model's thinking budget or level, and sampling, stop sequences, and format into `generationConfig`. Claude routes always run tools `VALIDATED`. On Gemini routes a forced tool choice is also restated as a final instruction, because Cloud Code Assist drops the tool mode there.
34
35Text and reasoning stream as `delta.content` and `delta.reasoning_content`. Each tool call is sent whole, with its id, name, and arguments, when the turn ends. Errors from the provider arrive as an `error` chunk while streaming, or as a JSON error otherwise.
36
37## state
38
39The proxy stores nothing. Each request carries its conversation, and provider identity that should be stable across it, such as a Codex account or an Antigravity trajectory, is derived from the profile and the first user message.
40
41Providers also return opaque state that they require back within a tool-calling turn: Codex's encrypted reasoning items, Gemini's thought signatures on function calls, and Claude's signed thinking. Clients echo only plain Chat Completions, but they always echo a tool call's id verbatim. So when a turn ends in tool calls, the proxy compresses that turn's native provider output, tags it with the profile that produced it, and appends it to the first call's id as `<id>__albedo__<state>`, using only `[A-Za-z0-9_-]`. When the history comes back, those turns become albedo transcript entries again and pass through the same projection a session uses. A request to the same profile replays them verbatim; a request to another profile gets the portable text and calls. Tool results are matched to the provider's own call ids. State larger than 256 KiB is not carried, and damaged state falls back to the portable history. Turns without tool calls carry no state, which the providers do not need after the next user message.