Changeset 0.10.0 (#56)

This commit is contained in:
2026-02-24 14:50:53 +00:00
parent cdfd69bad3
commit ea03f956ca
31 changed files with 3303 additions and 167 deletions

View File

@@ -2,6 +2,95 @@
All notable changes to Chat Switchboard.
## [0.10.0] — 2026-02-24
### Added
- **Model Roles** — Named model slots (`utility`, `embedding`, `generation`)
with primary + fallback bindings. Stored in `global_settings['model_roles']`.
Team-level overrides via `teams.settings` JSONB. New `server/roles/` package
with `Resolver.Complete()` and `Resolver.Embed()` for automatic failover.
Admin API: `GET/PUT /admin/roles/:role`, `POST /admin/roles/:role/test`.
Team API: `GET/PUT/DELETE /teams/:id/roles/:role`.
- **Provider Embed() interface** — All providers implement `Embed()`. OpenAI,
Venice, OpenRouter support `/v1/embeddings`; Anthropic returns `ErrNotSupported`.
New types: `EmbeddingRequest`, `EmbeddingResponse`.
- **Usage Tracking** — Every completion (streaming and non-streaming) logs
token counts and cost to `usage_log` table. Cost calculated at insert time
from `model_pricing` (no retroactive recalculation). Provider scope
denormalized for efficient admin filtering. Admin views exclude BYOK.
New stores: `UsageStore`, `PricingStore`. Admin API:
`GET /admin/usage`, `GET /admin/usage/users/:id`, `GET /admin/usage/teams/:id`.
User API: `GET /usage` (personal summary).
- **Model Pricing** — `model_pricing` table with catalog sync and manual admin
overrides. Sync from provider APIs (Venice, OpenRouter) auto-populates
pricing with `source='catalog'`; manual entries never overwritten. Admin API:
`GET/PUT/DELETE /admin/pricing`.
- **Streaming token capture** — OpenAI: `stream_options.include_usage` +
parse usage/cache from final chunk. Anthropic: parse `message_start` for
input/cache tokens, `message_delta` for output tokens. Cache token fields
(`CacheCreationTokens`, `CacheReadTokens`) on `CompletionResponse`,
`StreamEvent`, and `streamResult`.
- **Admin Roles tab** — Configure primary/fallback provider+model per role,
test-fire from UI.
- **Admin Usage dashboard** — Period selector (7d/30d/90d), group-by
(model/user/day/provider), totals cards, breakdown table, pricing table.
- **Team Usage dashboard** — Team admins can view usage against their
team-owned providers via `GET /teams/:teamId/usage`. Filters to
`provider_configs WHERE scope='team' AND owner_id=teamId`. Integrated
into team management panel with period/group-by selectors.
- **Admin Reset Password** — Button in user list with vault destruction
warning dialog (two-step confirmation).
- **User Usage tab** — Settings modal "Usage" tab shows personal token
consumption and estimated costs across all conversations, including BYOK.
Period selector (7d/30d/90d) and group-by (model/day).
- **Live Venice integration tests** — Gated behind `VENICE_API_KEY` env var.
Tests: non-streaming completion, streaming completion, usage logging for
both modes, pricing from catalog sync, and direct embeddings via BGE-M3.
Uses `qwen3-4b` (Venice Small) — cheapest at $0.05/$0.15 per 1M tokens.
- **Migration 004** — `usage_log` table, `model_pricing` table, `model_roles`
seed in `global_settings`.
### Fixed
- **UEK re-wrap on password change** — `ChangePassword` now decrypts UEK
with old password and re-encrypts with new password + fresh salt. Previously,
password changes silently broke all personal BYOK keys.
- **UEK destruction on admin password reset** — `ResetPassword` now nullifies
vault columns, evicts UEK from cache, deletes personal provider configs,
and logs an audit event. Previously, admin resets left orphaned encrypted
UEK that silently broke personal keys.
- **Streaming completions logged zero tokens** — `StreamEvent` lacked token
fields and providers discarded usage data from final chunks. Both OpenAI
and Anthropic streaming parsers now capture and propagate token counts.
- **OpenAI streaming usage race condition** — The OpenAI protocol sends
chunks in order: content → finish_reason → usage → [DONE]. The parser
sent `Done=true` on the finish_reason chunk (step 2) before the usage
chunk arrived (step 3, with `choices:[]`). The receiver returned
immediately on Done, so usage was captured but never delivered.
Fix: defer the Done event as `pendingFinish` until the usage chunk
arrives, then flush with tokens attached. Tool-call finishes flush
immediately (tool loop needs the event). Affects all OpenAI-compatible
providers (OpenAI, Venice, OpenRouter).
- **Token accumulation across tool iterations** — `streamResult` in
`stream_loop.go` now accumulates input/output/cache tokens across
multi-tool-call iterations instead of only capturing the final iteration.
- **Admin pricing leaked BYOK entries** — `PricingStore.List()` returned
all model_pricing rows including those from personal BYOK providers.
Admin panel showed pricing entries for user-private providers they
shouldn't see, with O(users × models) scale explosion. Now joins on
`provider_configs` and filters `scope != 'personal'`. Admin `UpsertPricing`
also validates provider scope, rejecting manual pricing on personal
providers.
- **Team role handlers used wrong param name** — `ListTeamRoles`,
`UpdateTeamRole`, `DeleteTeamRole` read `c.Param("id")` but routes use
`:teamId`. Every team role operation silently got an empty team ID.
- **Usage logging suppressed for zero-token streams** — `logUsage` had an
early exit when `inputTokens == 0 && outputTokens == 0`. Combined with the
OpenAI streaming parser bug (below), this silently dropped all streaming
usage rows. Removed the guard — requests are always recorded.
- **Live chat completion test used wrong field names** — `TestLive_VeniceChatCompletion`
sent `messages` and `config_id` but the handler expects `content` and
`provider_config_id`. Test silently skipped on the binding error.
## [0.9.4] — 2026-02-24
### Added