yetone
2a57b7bb60
tui: magpie tui serves the gateway while it is open when no other magpie does, so agents it connected reach it in WSL (gnayiab on X)
...
In WSL, with only magpie tui and Cursor's subscription, Claude Code said ECONNREFUSED. The terminal set agents to the gateway but never served it, and nothing listened at 127.0.0.1:3425 unless the app, magpie web or magpie serve ran.
magpie tui now serves the gateway in its own process while it is open, as the app does, whenever no magpie answers at its address. One that does (the app, magpie web, magpie serve) keeps it. The terminal looks every 15s and takes the gateway up once that one is gone.
The status line says "● gateway <url> · served while magpie tui is open" when it has nothing else to say, or "✗ gateway not running: <why>" when the port couldn't be had. On quitting after serving, it says the gateway stopped with it and what serves it otherwise. The usage text and docs/reference.md say so too.
Tested in a sandbox HOME on port 3486 with a real DeepSeek key: magpie tui alone answered /v1/models; Claude Code (sandbox HOME) set to deepseek/deepseek-flash ran a Bash tool turn through it; the status line showed the gateway; q printed the note and freed the port.
TestTUIServesTheGatewayWhenNoneDoes (serves, status, lets go at quit) and TestTUILeavesTheGatewayToAnotherAndTakesItUp (leaves another magpie's gateway alone, takes it up once it is gone) are new and pass 3x under -race; they don't build without the change.
go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 21:29:01 +08:00
yetone
528d6c12dc
agent, gui: Codex left on model_provider = "magpie" with no [model_providers.magpie] loads its config again, and connecting with nothing in magpie says what to do, in the reader's language (Tystem on Discord) — the Codex app said "无法加载 config.toml … Model provider magpie not found", magpie showed Codex as not connected on "deepseek-flash", and its switch answered only "Codex can't be connected to magpie", in English. Codex looks model_provider up in [model_providers.*] as it loads config.toml (codex-rs: "Model provider {id} not found"), so the key without the table keeps it from loading any config, a new thread too, not just an old one. magpie never writes one without the other: it takes model_provider out whenever it steps out as the provider (disconnect, Unwire, a model of Codex's own, Join) and keeps the table so the threads started on magpie still open. That is unchanged, and TestCodexSteppingOutLeavesNoHalfState now covers it. Another tool or a hand edit had taken the table away. Sync, at magpie's start and whenever the catalog changes, now writes the table back whenever the config names magpie as its provider, at the top or in a profile, and has no table, whether Codex is connected or not. Check reports that state too. A config magpie can't parse is still only reported, never written to. A Connect that finds none of magpie's models because no provider or subscription has been added answers agent.NoModelsError, "Add a provider or subscription in magpie first, then connect Codex". /api answers it with code no_models and the agent's name, which app.js says through t() (en/zh/ja/de). docs/reference.md no longer says the table goes with a native model. TestCodexProviderWithoutTable (top and profile: Check says so, Sync writes the table) and TestConnectWithNoModels in internal/agent, TestAgentConnectNoModels in internal/gui, and agent-connect-no-models.test.cjs (WebKit, en/zh/ja/de: the toast in the reader's language, the switch back off, no scroll, no native dialog) are new and fail without the change; the latter's en case passes either way, as the server's message is English. Chromium wouldn't launch on this machine for this test or the existing agent-connect.test.cjs. TestCodexSyncAddsBaseURLToProvider now expects the table added to a hand-made provider that had none. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. In the first full run, internal/gui's TestProvidersAnswerWhileListsComeIn and internal/proc's TestUserPath failed under load; both passed alone and in the second full run.
2026-10-05 21:09:13 +08:00
yetone
2f135ed4d5
plugin, gui, docs: Plugins says which plugins are providers and which are middleware, lists the community's ready-made middleware, and edits a middleware's options in its row (the owner) — the Plugins page still read as if every plugin were a subscription, and nothing showed what a middleware does or let you set its options without editing plugins.json. Every Discover card and Installed row now carries a Provider or Middleware chip (both for a plugin that is both), also for one that is off (isMiddleware/middlewareOnly from the server, read from its files); Discover splits into Subscriptions and Gateway middleware ("runs in magpie's gateway on what every agent sends and gets back, whichever provider serves it"), the latter from market entries with "kind": "middleware" and no providers or icon (the built-in market.json lists the five new @magpie-community/middleware-* packages: param-override, model-map, system-prompt, word-guard, think-tags, mostly New API's channel settings with the same JSON). A middleware row has Options: a JSON editor in the row, starting from the package's magpie.options example when none are set, which validates it is an object, saves (POST /api/plugins/options) for the next request, clears, and cancels with Esc. magpie plugin options takes a short name (model-map for @magpie-community/middleware-model-map; plugin.ShortName, also in SetOptions). en/zh/ja/de. Docs: usemagpie.ai/docs/plugins (en/zh/ja) gains Ready-made middleware and Options and the registry's kind; reference.md, both READMEs and docs/subsystems/gateway-middleware.md likewise. Tested with a real DeepSeek account and real Claude Code through magpie web --gateway in a sandbox HOME, with all five installed and options set by short name: a mapped model named back, max_tokens capped, a prompt appended for claude only, word-guard's 400 and param-override's 403 in the agent's shape, <think> stripped from Chat and Responses streams and a whole Anthropic reply, reasoning_content moved into the text; /api/plugins showed the kinds and the example. TestSetOptionsShortName is new; TestBuiltinMarket checks a middleware entry has no providers or icon and no other kind. plugin-middleware.test.cjs (Chromium and WebKit, en and zh: the chips, Discover's two sections, the options editor with its errors, no select, no scroll, no left border) fails without the change; the other plugin tests and click-scroll pass in WebKit (plugin-market's section name updated; plugin-move fails only under parallel load and passes alone). go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 20:50:09 +08:00
yetone
eebd0b4222
plugin, gateway, gui: a plugin can be gateway middleware — onRequest, onEvent and onResponse run in magpie's gateway on what agents send and get back (moejs) — a plugin could only be a subscription provider, run in the Bun host, a process away from the gateway. A plugin may now declare middleware (package.json magpie.middleware, or a *.middleware.js / .mjs file), run in-process by moejs (pure-Go JS engine): compiled once, one runtime per request from a pool, so every hook of a request shares ctx.state. onRequest(body, ctx) sees the agent's body in its own API before routing; what it returns is the body the next middleware and the gateway get, undefined leaves the bytes as they were, ctx.reject(status, msg) answers with an error in the agent's API's shape. onEvent(ev, ctx) sees each SSE event's JSON (export const events = [...] filters by name; an object changes it, null drops it, undefined passes the bytes through untouched; data: [DONE] is no JSON and passes by). onResponse(body, ctx) sees a whole JSON reply, with ctx.status. ctx has protocol, model, stream, path, agent, the plugin's options and state. A hook that throws, returns a non-object or runs past its limit (250ms request/response, 50ms an event, 1s load; interrupted) leaves the request or reply as it was and is counted and logged (60 lines a minute); a compressed reply passes as-is. Files are reloaded within a second of a change. Only agents' requests run through it (serveAgent at the gateway's agent entry points and the Codex backend), never magpie's own titles, classifier or stand-ins; it sees bodies before redaction. A middleware-only package is skipped by the Bun host. The Plugins page marks a middleware with a chip, its hooks, calls and µs per call (JSON in and out counted), failures in red with the last error, and a load error. en/zh/ja/de; docs/reference.md and usemagpie.ai/docs/plugins#middleware (en/zh/ja) with an alias example. Tested with a real DeepSeek account through magpie web --gateway in a sandbox HOME: an alias middleware served "fast" and named it in message_start, a reject came back as 403 permission_error, a Chat stream uppercased; real Claude Code (sandbox CLAUDE_CONFIG_DIR) did Read, Bash and Glob tool turns through three middlewares with 0 failures: ≈1–1.6µs an event, 0.2–0.7ms an onRequest that parses and rewrites Claude Code's ≈80KB body. Real use found two bugs fixed here: [DONE] parsed as JSON (SyntaxError on every Chat stream) and stats leaving out the JSON time. internal/middleware tests (chain, undefined keeps bytes, reject, fail-open on throw/spin/string with the runtime reused, async hook, load errors, off and Bun skip, reload, split/CRLF/filtered/dropped stream events, [DONE] untouched, whole reply and gzip, 64 in parallel; BenchmarkEvent ≈0.9µs, 11 allocs) and TestMiddlewareThroughGateway (alias routed, events by protocol, redaction still upstream, reject in Anthropic's shape; fails without the change) are new; plugin-middleware.test.cjs (Chromium and WebKit, en and zh: chip, hooks and stats, failures with last error, broken and off, no left border) fails without it. The other plugin GUI tests and click-scroll pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (internal/provider failed once under load and passes alone).
2026-10-05 19:14:31 +08:00
yetone
b9d20856ef
gateway, gui: a gateway key can be held to routing groups (Magic_zero on Discord) — a key's models menu on the Gateway page listed providers' models only, so a key couldn't be limited to the routing groups an agent was meant to use. The menu now lists the routing groups first, with Every routing group (group/*), and a key may keep group/<id>. A key that names a group may use it with every member in it, the next member too when one fails, but not those members asked for by name, a group it doesn't name, or a fallback outside the group; a group named inside one it doesn't name counts for its own members. A group the key doesn't name still passes only when it may use every member, as before. /v1/models and the Anthropic and Gemini lists show the key its groups. en/zh/ja/de; docs/reference.md. TestGatewayKeyModelsNameAGroup (group/both lists and serves only the group, falls to the next member, members by name and other groups refused; group/* every group; a nested named group; a member's own fallback outside the group refused) fails without the change; TestCallerKeyModelsRoutes lists the groups. gateway-key-groups.test.cjs (WebKit, en and zh: groups first after All models, Every routing group, a group picked sends group/coding, a held key's group ticked, no select, no scroll, no left border) fails without the change; gateway-key-models, gateway-key-limit and click-scroll pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 18:44:27 +08:00
yetone
1534382ff9
gui, web: gateway mode — magpie web on a server that is only a gateway shows no Agents, Sessions or Library (Player on Discord) — a magpie web run on a server or NAS for other machines' agents showed the Agents, Sessions and Library pages and the settings written into this machine's agents, none of which do anything there. In gateway mode the page has Providers, Gateway, Routing, Usage, Plugins and Settings; an address kept for a page left out opens Providers. Settings leaves out provider in model names, Codex subagents, long conversations, Codex thread titles, the usage/balance alerts and reset reminders, on top of the tray, open at login, Dock, text size and lightweight rows magpie web already hid; Language, theme, currency, proxy, redaction, vision/image models, sync, updates and the rest stay. magpie web is in it by itself when it finds no agents on its machine (the Docker image, a NAS) and with the new magpie web --gateway; Settings › General › Gateway mode (Automatic / On / Off, saying why it is on) wins over both and Off brings every page back at once. The choice is this machine's own (KeepOwn: never synced or restored from another computer), set only by POST /api/settings/gateway-mode, never by the Settings page's save. The app's own windows are never in gateway mode. boot.js tells the page before it paints. en/zh/ja/de; docs/reference.md (Docker › NAS, CLI list). TestGatewayMode (temp HOME: no agents → on, agents → off, --gateway → on, Off wins over --gateway, the Settings save leaves it, On with agents, Automatic, an unknown mode refused, the app window never told, KeepOwn) is new; TestSettingsSaveKeepsWhatItDoesNotSend sets it. gateway-mode.test.cjs (WebKit, en and zh: the tabs, ?view=agents/sessions/library open Providers, the switch and why, the seven rows hidden, Off posts "off" and brings tabs and rows back with Settings kept open and Agents opening, Automatic hides them again, the Settings save never sends it, no scroll, no select, no left border; web with agents and the app window keep every tab, the switch is magpie web's alone) fails without the change. mobile-web's desktop comparison differs from origin/main on Settings only, by the new row. settings-sections, settings-groups, click-scroll, cli-path, plain-names, codex-titles and agent-grip pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 18:20:28 +08:00
maY and yetone
6fe8169502
gateway, agent: Codex draws with its native image tool through magpie ( #870 )
...
* fix(agent): give magpie's provider table the actor-authorization header
Codex offers its built-in image tool (image_gen.imagegen) — and the other
OpenAI-provider tools — only to a provider it reads as the OpenAI actor:
ModelProviderInfo::uses_openai_actor_authorization(), i.e. a provider that
does not require OpenAI auth but carries x-openai-actor-authorization.
magpie writes [model_providers.magpie] but carried neither signal, so a
Codex on magpie was never given the tool and could not draw through a
Codex backend such as sub2api's.
Write http_headers = { "x-openai-actor-authorization" = "magpie" } into
that table. magpie does not forward the header upstream: codexHeader
rejects anything naming "authorization".
Test: TestCodexMagpieProviderCarriesActorAuthorization.
* gateway/provider: let a Responses-only relay (Codex backend, sub2api) draw
A provider added as 'OpenAI Responses' only (what a Codex backend such as
sub2api speaks) has no chat base, so Drawers() returned nil before ever
looking at the vendor's list, draw() refused with 'can't draw', and
drawImages() posted to an empty base. The vendor's own /v1/models does list
its image models (sub2api serves gpt-image-2.5), and they are meant to be
picked in Settings → Images.
- Drawers: a Responses-only provider draws when it has any OpenAI-shaped
base, not only a chat one.
- draw: refuse only when neither Chat nor Responses is set.
- drawImages: fall back to the Responses base when there is no chat base.
- List: the add form's 'Fetch models' keeps the vendor's image models so
one can be picked while the provider is added.
Tests: TestResponsesOnlyProviderDraws, TestListOffersImageModels.
* fix: route Codex native images through its turn provider
---------
Co-authored-by: yetone <yetoneful@gmail.com >
2026-10-05 17:19:42 +08:00
yetone
46f03154ff
gateway: a gateway key can be held to some models ( #882 , LCorleone) — a key could reach every model magpie serves. A key now keeps an optional list of models (caller-keys.json "models", none = every model, so older files read as before): <provider>/<model> or <provider>/*, matched against the provider that serves the call, so a bare model name is resolved first and OpenRouter's "anthropic/x" is not Anthropic's. Such a key sees only its models in /v1/models, /v1/models/{id} and the Anthropic (Claude Desktop) and Gemini lists, a routing group only when every member is its; asking for another, by id, bare name or through a group with a member it may not use, is refused before any provider is asked with a 403 in the API's own error shape (permission_error) that names the key and its models, logged as a rejected call like an over-limit key's 429; a fallback it may not use is skipped; embeddings, images and videos it names are held too. magpie's own calls for it (a web search, a picture described, Codex titles with the model Settings name) are marked in their context, not by a User-Agent a client could send, and go on. Set it with the new badge on the key's row in the Gateway page (the app's menu: All models, every model of a provider, each model; sent when the menu closes) or magpie gateway-key models <id> openai/gpt-5 anthropic/* (all takes it off); gateway-key list has a MODELS column. The app's menu, fitting neither below nor above its button, now goes on the roomier side and scrolls instead of running off the window. en/zh/ja/de. TestGatewayKeyModelsHoldAKey (lists, 403s by id, bare name and group, Anthropic shape, fallback skipped, magpie's own call, a free key) fails without the change; TestModelPatterns, TestKeyModelsKept, TestGatewayKeyModelsCLI, TestCallerKeyModelsRoutes pass; gateway-key-models.test.cjs (Chromium and WebKit, en and zh: no native select, menu inside a 420px window, picks sent on close, All models, no scroll, no left border, 560px fits) passes with gateway-connect-keys, gateway-key-limit, gateway-caller-keys and click-scroll. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 16:52:23 +08:00
yetone
9fb51c7742
docs: a new README, in English and Chinese, that shows what magpie does
...
The old README was the whole manual in one file. Someone who opened the
repo saw a wall of config paths before seeing the app. The new one leads
with the app: a hero screenshot, six highlights, then a section for each
part (picking models, providers, routing groups and intent routing,
subscriptions and plugins, usage, sharing, the library), with the site's
light and dark screenshots in <picture> tags. A supported-agents list, a
three-step quick start and a CLI sample follow. README.zh-CN.md is the
same page in Chinese, with the Chinese screenshots where the site has
them.
The full manual moves, word for word, to docs/reference.md, with its two
relative links fixed. Both READMEs link to it, so no detail is lost.
2026-10-05 15:02:09 +08:00