Repository navigation
Conversation
… table, and the mmsp client that calls it (#247) ## Summary This adds an MMSP server, which serves the models of an explicit table over HTTP as MMSP streams, and the `mmsp` client type, which calls it. Python and TypeScript behave the same, and each client works against either server. - **Config:** one JSON file, passed as `--config` or `MMSP_SERVER_CONFIG`: - `models` is the table. Each row has `model_id`, `api_key` and `server_model_id` (the id clients name), which are required, and `client_type` and `base_url`, which are optional. An empty `client_type` means the official client the model id's family names; an empty `base_url` means that client's default endpoint. Adding a model means adding a row. - `api_keys` holds the bearer keys clients may send. Several are allowed; an empty list makes an open server, and the server says so at start. - A cell written as `$VAR` or `${VAR}` in `base_url`, `api_key`, `client_type` or `api_keys` is read from the environment when the file loads. An unset variable stops the start. - **Start:** `python -m mmsp.integration.server --config FILE [--metrics FILE]` or `npm run server -- --config FILE [--metrics FILE]`, defaulting to `127.0.0.1:25752`. It prints these lines, through `announce_server` / `announceServer`, the same as when the playground starts it: - `Starting MMSP server at http://127.0.0.1:25752/v1` - `Serving models: ...` - the open-server line, when there are no keys - **Code API:** `create_server_app(models, api_keys, metrics_path=None)` / `createServerApp({ models, apiKeys, metricsPath })`, `start_server` / `startServer` (which returns the `http.Server`; both take the metrics path), and `load_server_config` / `loadServerConfig`. - **Routes:** a client's base URL ends in `/v1`, as with OpenAI and vLLM. - `GET /v1/models` lists the table in OpenAI's list shape. - `POST /v1/stream` takes `{model, messages, config}` and answers with the public stream as SSE, then `data: [DONE]`. It sends a `: keep-alive` comment every 15 s while the model is silent. Bytes travel as base64. - **Metrics:** `ServerMetrics` keeps, since start, per model and in total: - requests, successes (ended with the stop event), failures (an error event) and client disconnects (counted apart) - refusals (401, malformed body, unknown model) - p50/p90 of first-event and total latency over the last 1000 successes - token totals, `tokens_out` (thinking + response), `generation_ms` (first event to end, summed) and `tps` - each model's last outcome and error, and the latest 100 failures (`errors`) Beside those, a history of the last 60 days: 10 s buckets for 2 h, 1 min buckets for 2 days, 1 h buckets for 60 days; a bucket that ages out of its tier is added into the next one's, its latency samples thinned to 64 evenly spaced order statistics. The snapshot gains `since`, the moment the history begins. `GET /v1/metrics` serves it under the key rule. `?window=N` (10 to 5184000) or `?from=F&to=T` (unix seconds, at most 60 days apart), with `?columns=C` (1 to 1440, 360 by default), adds `window`: columns of a span the server picks from 10 s … 12 h (`bucket_s`), arrays covering the whole range, sums and per-column values in total and per model, and `previous`, the range before. A bad query is a 400. `--metrics FILE` / `metrics_path` / `metricsPath` keep the history (buckets, errors, `since`) in one line of JSON, read at start and written atomically every 60 s when something changed and at stop. The playground's server page draws it, running or stopped (below); the server's root serves no page. - **Errors** have the shape `{"error": {type, message, ...}}`: - 401 when no valid key is sent - 400 for a malformed body - 404 `NotFoundError` for a model not in the table, and for any other route (including `/` and `/models` without the prefix) - 413 for a body over 50 MB - Inside a stream, one error event, then `[DONE]`. A disconnect aborts the upstream request. - **`mmsp` client** (`mmsp/`, `MmspClient`): a forwarding client. It yields the server's public stream as sent, bytes decoded, so what `curl -N` shows is what it yields; it saves no trace (the server does). Reads `MMSP_API_KEY` / `MMSP_BASE_URL` under the credential rule, default `http://127.0.0.1:25752/v1`. `list_models` returns the table's ids. - Every error the server reports, a refusal or an error event, is raised as the new `UpstreamError`: `client`, `status` (the refusal's HTTP status, else the upstream's own), `error_type` (the server-side class), `message`, and `error`, the server's error object with that error's fields. - **Playground:** - `mmsp` is in the client-type picker. Because the listing uses OpenAI's shape, the OpenAI client types can list the server too. - A **server page** at `/server/` opens from the top bar ("Open Server", after "Open Tracer"). On it you fill in a models table, the server keys, host and port. - The page has a header (state, base URL with a copy button, Apply / Save / Start-Stop) and three tabs: **Overview**, **Models**, **Settings**. - **Models:** each model row is one summary line (served id, upstream, state) that opens to its five fields, one row at a time. Typing a model id fills only Served as. Client type shows Auto and base URL shows Default. **Settings:** the server keys, host and port, and the **File** card: the saved file pretty-printed with token colours, plain-text keys shown as `••••••••` until the eye reveals them (`$VAR` as written), the path and a copy button. - Every model row, key and the listen pair reads **Live**, **Saved** or **Unsaved**: a word next to its dot, at every width. - **Save**, or **Ctrl/Cmd+S** anywhere on the page, writes the table to `MMSP_SERVER_CONFIG`, else `cache/server.json`, in the CLI's format; `$VAR` cells are written as typed. - **Start** runs the saved file and is enabled once one is saved. **Apply** shows while the file differs from what runs and replaces the running server with it; it builds the new app first, so a refused table keeps the old server running. **Stop** stops it. - **Overview, running:** a range of 15 min, 1 h, 6 h, 24 h, 7 d or 30 d, or a custom From/To of at most 60 days (a calendar segment opening a popover); six tiles (Requests, Success, Latency p50, Latency p90, Tokens out, TPS), each with its delta against the previous window and a sparkline; a card per model (state, requests, success, p50, p90, TPS, last outcome, trend) that opens its row on Models; the Requests chart (stacked ok / failed / dropped); the Latency chart (p50 and p90 lines); the Errors list. The page polls `GET /server/api/metrics?window=N&columns=C` (or `?from=F&to=T&columns=C`) every 3 s while visible, asking 30–90 columns from the chart's width; ticks fall on round local minutes, hours or days. **Stopped:** a checklist (add a model, save, start), with the dashboard of the history below it once there is one. - The playground keeps the history in `<config>-metrics.json` beside the config (`cache/server-metrics.json` by default), saved before Apply so the new server continues it, and closed with the server. - A refusal naming a model row shows under that row on Models and rings the cell it names; any other shows under the header. - The API: `GET`/`PUT /server/api/config` (with `text`, the file as written), `GET /server/api/status` (which also returns the running `config`), `GET /server/api/metrics` (the running server's `ServerMetrics`, with `?window=N`, `?from=F&to=T` and `?columns=C` as on `/v1/metrics`; stopped, `{"running": false}`, plus the history's `since`, `errors` and the window asked for when the file can be read), and `POST /server/api/start` / `restart` / `stop`. - **Chat page:** entries in the model picker are told apart by model id, client type, API key and base URL. An id listed from the server stays pickable next to the built-in with the same id, described as `mmsp · 127.0.0.1:25752`, and picking an entry fills all four fields. - **Docs:** a README section, a site Server page (en/zh), the models/errors pages, the skills references and the changelog entry. ## Design notes - **The table is explicit (owner's decision).** There is no scanning: the server serves exactly its rows. An empty `client_type` is `AutoLLMClient`'s choice (`CLIENT_TYPE`, else the official client the model id names); gateway ids such as `qwen/qwen3.8-27b` need one. A row with `client_type: "mmsp"` pointing at another server's `/v1` relays it. - **Upstream clients are built once per row, at start.** The whole table is validated first: an empty table, a missing or empty column, a duplicate `server_model_id`, or a client that refuses to build all stop the start, with the row named. Sharing a client across requests is safe: - Every client sets its fields only in its constructor, and `streaming_response` keeps its state in locals. - Python builds the SDK clients on the main thread and uses them on the one background loop, as the playground already does with its session clients. - A wrong key or an unreachable endpoint still shows only on the row's first request, as an in-stream `UpstreamError`. - **A forwarding client, one error class.** `POST /v1/stream` → SSE → `[DONE]` is already what `curl -N` does, and the server's upstream client has already closed the items, stamped the events and saved the trace. So `MmspClient` overrides `streaming_response` to yield each event as parsed, with only base64 decoded back to bytes; it no longer rebuilds items or saves a trace. Abort is one `run_with_abort` per event (TypeScript: the fetch's `signal`). `streaming_response_stateful` is unchanged: it reads the done items and the stop event the forwarded stream carries. Errors: re-raising the five MMSP errors as themselves meant rebuilding each from its wire fields (and patching the parse error's message); one `UpstreamError` carrying the server's object keeps every field without that, so `from_wire_error` / `fromWireError` are gone. The server's `to_wire_error` keeps the typed fields, and a `StreamProtocolError` message now travels whole. - **Tiers.** 10 s buckets for 2 h, 1 min for 2 days, 1 h for 60 days. A tier's floor is aligned to the next tier's bucket, so a whole minute (hour) moves at once and every stretch of time is stored in exactly one tier: at most 5106 buckets per series, each 9 counters and 2 × 64 samples. That is the memory bound, and the file holds the same buckets. Roll-up adds the counts and thins the latency samples to 64 evenly spaced order statistics; a column's percentiles are over the thinned samples of the buckets inside it, so coarse columns are as honest as fine ones, and a 15 min window at 10 s gives exactly the numbers of the last round. Compaction runs at most once a minute. A request's outcome, latency and tokens go into the bucket it began in, wherever that bucket has rolled to (none past 60 days; it still counts since start), so a bucket's requests equal its outcomes plus what is still streaming; a refusal goes into the bucket of its moment. `previous` is `null` unless the whole previous range lies inside the history (from `since`), so a delta never compares against time nobody measured. - **Column spans.** The server picks the span per request: the smallest of `COLUMN_SPANS` (10 s … 12 h) that fits `?columns=` and is no finer than the tier storing the range's start; the page asks 30–90 from its width. 12 h is the widest because day columns would be UTC days on a local axis: columns align to UTC multiples of the span, labels are local (lead's decision, accepted for now). `start` is no longer clamped to the server's first bucket; the arrays always cover the range, so the page dropped its padding. - **Persistence.** The CLI writes a history only with `--metrics FILE`; the playground always does, beside its config, keyed by the config path so two playgrounds on two configs never share one (lead's decision). Writes go through a temporary file and a rename, every 60 s when something changed and at close; a failed write prints once and is retried. Apply saves first, so the new store loads everything up to that moment; the old one's close may then write a few hundred milliseconds of outcomes the new one overwrites, the one place a request can go missing. The since-start counters stay this run's; `since` says where the history begins. A series is its `server_model_id`: a renamed model starts fresh, a removed one's history stays in the file and returns with its id. An unreadable file starts fresh and says so once; the stopped page reads the file silently through `ServerMetrics.from_history` / `fromHistory`. - **File card.** `GET /server/api/config` returns `text`, the file as written; the card prints the parsed JSON with 2 spaces and colours keys, strings, numbers and punctuation from the existing tokens. Plain-text keys are masked until the eye reveals them (lead's decision); Copy copies what is shown. - **TPS.** `tokens_out = thoughts + response`; `generation_ms = max(total_ms − first_event_ms, 1)` per success; `tps = Σ tokens_out / Σ generation_ms × 1000`, one decimal half up in both languages, `null` before a success. It is a ratio of sums, never a mean of per-request rates, so long generations weigh by their length; the 1 ms floor only touches a stream whose first event is its stop event. - **Dashboard colours.** Validated with the dataviz palette check against both surfaces: ok = accent, failed = red, dropped = grey. Green against red fails the colour-vision check in both themes, so success is never green in a chart; green stays the Live dot and the "improved" delta. p90 is the accent line, p50 muted; no band, no `p50–p90` text. - **Case-sensitive routing.** Express routes case-insensitively by default, so `POST /V1/stream` bypassed the `/v1/` key check. The TypeScript server now routes case-sensitively, as Flask does, and a test covers it in both languages. - **The playground's server page.** - One file per language: the page is one HTML document, `SERVER_TEMPLATE`, at the end of `server.py` / `server.ts` between two marker lines, identical in both, with the chat page's tokens, combobox and client-type lists and the tracer's top bar. The embed script rewrites only that region; the backends edit only above it. - The config's shape check and `$VAR` resolution moved into `resolve_server_config` / `resolveServerConfig`, which the file loader and the page share. - On a failed bind, werkzeug's `make_server` prints the error and calls `sys.exit(1)` instead of raising, and it treats a `unix://` host as a socket file to replace. So the playground binds the socket itself, as werkzeug would, and hands over the fd; a port in use comes back as a 400. - `server_base_url` / `serverBaseUrl` put an IPv6 host in brackets. - `GET /server/api/metrics` reads the running server's `ServerMetrics` in-process, so the page needs no server key. - **Keep-alive.** Without it, Node's fetch (undici's 300 s limit between body chunks) and common reverse proxies would cut a long thought. - **Shared e2e files (owner-approved).** These add one `ROUTING_CASES` row and an env-gated `AVAILABLE_MODELS` entry (`MMSP_API_KEY` + `MMSP_BASE_URL` + `MMSP_MODEL`). CI sets none of them. ## Tests On vps4: - **Unit:** Python 632 passed / 15 skipped; TypeScript 18 suites, 620 passed. Each was run twice. ruff, eslint, prettier, tsc and `npm run build` are clean, and the site builds. - **Live checks, both real playgrounds:** each history file was seeded with a deterministic 60-day synthetic history (`history.mjs`, same format as the server writes), with real `deepseek-flash` traffic on top. Python passed 89 checks and TypeScript 88, none failed. - The 15 min tiles moved within about 2.4 s and matched the traffic log. - `?window=86400&columns=72` returns `bucket_s` 1200 with 72 entries on `/v1/metrics` and `/server/api/metrics`. - 24 h, 7 d and 30 d draw the synthetic shape: day labels at midnights, weekly ticks on Mondays, and more than 10,000 requests over 30 d. - A custom range of yesterday 09:00–18:00 shows a busy day. - Apply, Stop and Start again never lowered the count over a fixed range, and Stop shows the past under the checklist. - The Settings File card masks `sk-bad` until the eye reveals it, and Copy copies what is shown. - **Stand-in layout checks:** 337 passed, none failed. They cover every preset and a custom range at 1280 and 390 px, light and dark: tick labels never overlap, the popover works from the keyboard, there are no console errors and no horizontal scroll. - **Real-model e2e through the server** (earlier rounds, `e2e_mmsp.sh`): five models through both servers, plus the cross-language pairs. - **Offline suites** (`test_mmsp_server.py`, `mmsp-server.test.ts`, `test_playground_server.py`, `playground-server.test.ts`) put a scripted upstream behind an in-process server and require the mmsp client's events to equal the upstream's public stream and the server's raw SSE. They also cover the `UpstreamError` round trips, auth, the 404s, the listing, startup validation, config loading, the start lines, abort, keep-alive, the metrics and their windows, the playground's server API, and the page's ids and functions. `site/src/docs/server.zh.md` has not been through `site/scripts/polish-zh.mjs`: there is no TokenDance key on the dev machine or on vps4. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…google-official (#248) ## Summary The official Gemini client is renamed `google-official`, after its vendor, like `openai-official` and `anthropic-official`. - `gemini_official/GeminiOfficialClient` → `google_official/GoogleOfficialClient` (Python and TypeScript, via `git mv`). - `OFFICIAL_CLIENT_TYPES` lists `google-official`, and the `gemini-` model family routes to it. - `gemini-official` still works as an unlisted alias, because 0.5.x shipped that name. This follows the precedent of `google-genai` keeping `gemini-generate-content`. A new `ROUTING_CASES` row in each language checks the alias. - Also updated: - The registry's Gemini entries. - The playground chat page and server page: the default base URL key, and the client-type descriptions in `server.html`, re-embedded into `server.py` / `server.ts`. - Tests, READMEs, site docs, skills, the dev skill and the copilot instructions. - `GEMINI_API_KEY` / `GEMINI_BASE_URL` are unchanged. - Changelog entry: `changelog/unreleased/2026-10-09-google-official.md` (+ `.zh.md`). ### Compatibility Not marked breaking: - `AutoLLMClient(client_type="gemini-official")` keeps working. - The client classes are not exported from the package root. Only a deep Python import of `mmsp.gemini_official` breaks, and the TS exports map already blocks deep imports. ## Test plan (vps4) - [x] Python unit: 633 passed, 15 skipped. `ruff check` and `ruff format --check` are clean. - [x] TS unit: 621 passed. eslint, `tsc --noEmit`, prettier and build are clean. - [x] Gemini e2e: 14/17 passed in Python and 14/15 in TS. The failures are connection errors (`APIConnectionError` / `fetch failed`) in the image tool-result and TTS tests. The same tests failed the same way on an unmodified dev tree on vps4, so they look like a vps4 network problem; CI runs them too. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary Support Claude Haiku 5.5 (`claude-haiku-5-5`, released 2026-10-07) and refresh the Claude 5.5 docs snapshot. Opus 5.5 and Sonnet 5.5 were already supported. Their official pages changed only in the lineup table and in Sonnet 5.5's cache-read price. - **`anthropic-official`** serves `claude-haiku-5-5`, matched by the explicit `haiku-5-5` version substring. That also covers Bedrock's `anthropic.claude-haiku-5-5`. - `thinking_level="none"` → adaptive thinking at `low` effort, as on Opus 5.5 / Fable 5.1. The other levels send the effort of the same name. - `tool_choice="required"` or a tool-name list → `UnsupportedParameterError`, as on Opus 5.5 / Sonnet 5.5. - `fast_mode` → `UnsupportedParameterError`. - The `_ALWAYS_THINKING` tuple is renamed `_LOW_EFFORT_FOR_NONE` (TS `LOW_EFFORT_FOR_NONE`), because Haiku 5.5 is not always-thinking. - **Registry** - New `claude-haiku-5-5` row: $0.10 / $0.50, cache reads $0.01, 1M context. This is the ≤100K-prompt tier; a comment notes that every rate is 5× above 100K, the same convention as MiniMax-M3 and gpt-6.1-sol. - New OpenRouter `anthropic/claude-haiku-5.5` row, with prices from the live `/models` API. - Sonnet 5.5 cache reads drop $0.20 → $0.10 in both the official and OpenRouter rows, per the official pricing page and OpenRouter's API. - **Tests:** a `claude-haiku-5-5` entry in `AVAILABLE_MODELS`, and rows in the existing thinking-level and summary mapping tables. - **Docs** - `llmsdk_docs/claude5_5/` adds the Haiku 5.5 overview, migration guide and what's-new pages, plus the pricing page. The models overview, the Opus and Sonnet 5.5 pages and their migration guides are refreshed. - The site and skills list the new model. - **Changelog:** `changelog/unreleased/2026-10-09-claude-haiku-5-5.md` (+ `.zh.md`). ## Live probes (claude-haiku-5-5, raw HTTP, 2026-10-09) | Request | Result | | --- | --- | | no thinking config | 200; adaptive thinking runs by default (it thinks at `max` even when no display is asked for) | | `thinking: disabled`, effort default / low / high | 200, no thinking | | `thinking: disabled`, effort xhigh / max | 400 "effort 'xhigh' is not supported when thinking is disabled on this model" | | `thinking: disabled` + `display` | 400 "Extra inputs are not permitted" | | `between_tools` | 400 "not supported for this model" | | `enabled` + `budget_tokens` | 400 | | adaptive, effort low … max | 200 | | `speed: "fast"` (+ beta) | 400 "does not support the `speed` parameter" | | `temperature: 1` / `0.5` | 200 / 400 "`temperature` is deprecated for this model" (the client already rejects ≠ 1 for all Claude) | | `tool_choice` any / named tool | 200, but the response has no thinking block | | top-level `cache_control` 5m / 1h | 200 | **Replay of a thinking-bearing assistant turn,** both before tool results and in a later follow-up: - Missing `signature` → 400 "Field required". - Tampered signature → 400 "Invalid `signature`". - Empty or edited thinking text → 200, with the input token count unchanged, so the signature carries the thinking. - Dropping the thinking block → 200, but the thinking is lost. - Dropping the tool_use `caller` field → 200. The existing replay (thinking text + signature) is therefore kept unchanged. Usage now also carries `output_tokens_details.thinking_tokens`; the client keeps reading `output_tokens` as before. ## Decisions (confirmed with the owner) - **NONE:** Haiku 5.5 does accept `disabled`, but NONE maps to adaptive + `low`, consistent with Opus 5.5 / Fable 5.1. The alternatives were `disabled` and omitting the config (which leaves adaptive thinking at the default effort). - **Forced tool_choice:** the API accepts it but silently skips thinking. It is rejected for consistency with the rest of the 5.5 family. - **Tiered pricing:** the registry has a single price per bucket, so it stores the ≤100K tier and notes the 5× factor in a comment. ## Test plan (vps4) - [x] Python unit: 635 passed, 15 skipped. `ruff check` and `ruff format --check` are clean. - [x] TS unit: 623 passed. eslint, `tsc --noEmit`, prettier and build are clean. - [x] Python e2e `-k claude-haiku-5-5`: 12 passed, 3 skipped (image generation / TTS / embedding are off by capability flags). - [x] TS e2e `-t claude-haiku-5-5`: 15 passed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…tion; the server page tests rows, warns while open and confirms a start (#250) ## Summary Four playground fixes the owner asked for before 0.5.2. 1. **Chat: sending to another model starts a new conversation.** - **Before:** the backend silently started an empty history whenever the client options changed, while the page kept showing the earlier messages as one continuous chat. - **Now:** sending to another entry (a different model id, client type or base URL) calls `/api/clear`, starts a new session, and draws a divider: "New conversation with `<model>` · the messages above are not sent". - **Composer hint:** it says this before Enter is pressed. Picking another model and back before sending changes nothing. - **Key or extra-header edits:** the conversation continues. `/api/chat` now carries a session's history over when it rebuilds the client, so no history resets silently. 2. **Server page: Test / Test all for model rows**, sent straight to the upstream through `POST /server/api/test`. - **Input:** `{"model": row}`, the row as typed. `$VAR` cells are read from the playground's environment. - **Output:** "Passed" with first token, total, tokens out (thinking + response) and TPS, or "Failed" with the upstream's message. - **Limits:** three rows at a time, 60 s each. - **First token:** the first event that carries an item. Anthropic reports usage in a stop event at `message_start`, before any token, so the first event alone would undercount the wait. 3. **Open-server banner:** while the running server has no keys, the top of the page says anyone who can reach its URL can use the models, with an **Add a key** button. 4. **Start / Apply confirmation:** - **Dialog contents:** a native `<dialog>` showing the listen URL, the model count and the keys the saved file holds, plus the open-server warning when there are none. - **Keyboard:** Enter confirms, Escape cancels, and Ctrl/Cmd+S does nothing while it is open. - **Why Apply confirms too:** it replaces the running server, so it can change the URL or drop the keys. - **Stop:** doesn't ask. The site docs (`server`, `tracing`) and an unreleased changelog entry are included. ## Decisions - **Owner:** "new conversation", not carrying history across models or keeping a thread per model. - **Owner:** the test goes straight to the upstream, not through the running server. - **Earlier messages stay visible above the divider:** comparing models on one prompt is what the playground is for. - **Key or extra-header edits continue the conversation:** fixing a wrong key mid-chat shouldn't wipe it. ## Test plan (vps4) - [x] Python: 649 passed, 15 skipped. `ruff check` and `ruff format --check` clean. - [x] TS: 637 passed. eslint, `tsc --noEmit`, prettier and build clean. - [x] Server page against the stub in headless Chrome: 420 checks, 0 FAIL. - Covers the dialog (contents, Escape, focus trap, Enter confirms once), the banner (shows while open, hides after a key is applied) and Test / Test all (disabled until model id + key, Passed / Failed text, a result is dropped when the row is edited). - The palette checker's chroma-floor lines on the unchanged neutral greys are not page checks. - [x] Live, both playgrounds (Python 25761, TS 25762), with real DeepSeek: - Server checks: 104 / 103 ok, 0 FAIL. - `/server/api/test`: `ok: true` with ~300 TPS. An unset `$NOPE` gives the unset-variable message, `sk-bad` gives the vendor's 401, and `client_type: nope` gives the unknown-client message. - The banner appears exactly when the console prints `Open server:`. - [x] Chat switch check on both playgrounds: - Switching the client type posts `/api/clear` with the old session, draws one divider, and the reply no longer knows the earlier word. - A key-only edit keeps the session, and the model still recalls the word from after the switch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Folds `changelog/unreleased/` into `changelog/0.5.2/` (#247 MMSP server and `mmsp` client, #248 `google-official`, #249 Claude Haiku 5.5, #250 playground fixes), adds the 0.5.2 line to `CHANGELOG.md` / `CHANGELOG.zh.md`, and versions `mmsp` and `@prismshadow/mmsp` 0.5.2. Verified on vps4: Python 649 passed, 15 skipped; TS 637 passed; build clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
dev → main for 0.5.2:
mmspclient (feat(integration,mmsp): an MMSP server that serves an explicit models table, and the mmsp client that calls it #247).google-officialas the official Gemini client's name (refactor(auto_client,google_official): the official Gemini client is google-official #248).The entries are in
changelog/0.5.2/.🤖 Generated with Claude Code