responsesInputToMessages unmarshaled function_call.arguments and
function_call_output.output as bare strings, silently dropping the
value when it arrived as an object or content-parts array. This
caused the model to lose tool-call context in multi-turn
conversations, increasing the likelihood of malformed tool-call JSON.
Add rawJSONToString (re-encodes non-string values as JSON strings)
and outputToString (extracts text from content-parts arrays, re-encodes
other non-string values). Add 3 regression tests covering object
arguments, array output, and object output.
By-model panel gains a segmented 1天/7天/全部 control. Selecting a
range fetches /api/stats?since=<RFC3339> and re-renders only that table
client-side, replicating the human/rate template helpers in JS; the
existing /api/stats since param already drives the server-side filter.
- internal/server/templates/admin.html: filter UI + JS, empty/loading states
- internal/server/server_test.go: render test for the filter controls
- scripts/test-instance.sh: build→start→stop→clean test-instance helper
- Extract callUpstream helper to deduplicate ~30 lines between chatCompletions and responses
- Move HTML templates to internal/server/templates/ via go:embed (server.go 1749→1192 lines)
- Consolidate 4 copies of firstNonEmpty into util.FirstNonEmpty
- Extract chatStreamChunk named type shared by aggregateStream and aggregateResponsesStream
- Fix tool-call ordering: iterate sorted map keys instead of sequential 0..N
- Map upstream finish_reason to Responses API status (length/content_filter → incomplete)
- Surface /v1/models errors as 401/502 instead of silently returning empty 200
- Add Secure cookie flag via isTLSRequest helper
- forEachSSEChunk: use sseDataPayload parser, drop redundant json.Valid, distinguish bufio.ErrTooLong
- Fix StreamIdleTimout typo → StreamIdleTimeout
- sso.go: single Read → io.ReadAll(io.LimitReader), explicit unknown error fallback
Translate Responses API requests (input→messages, instructions→system,
max_output_tokens→max_tokens, text.format→response_format, flat tools→nested
{function:{…}}) to upstream chat/completions, then convert responses back to
Responses format (streaming SSE event lifecycle + non-streaming JSON).
Verified against OpenAI migration guide and Python SDK Response model:
- Echo back required fields parallel_tool_calls/tool_choice/tools
- Include content:[] in reasoning items, logprobs:[] in output_text parts
- Support function_call/function_call_output multi-turn input items
- Map usage fields prompt_tokens→input_tokens, completion_tokens→output_tokens
- Unify /admin/login and /admin/stats into a single /admin page with tabs
(Token stats default, credential login); old paths redirect to /admin.
- Restyle from dark glass-morphism to a clean light theme (single accent,
system font stack, 1px-bordered cards, generous whitespace).
- Humanize token counts with K/M/B suffixes via a humanNum template func.
- Paginate recent requests client-side (10 per page) with a compact pager.
- Drop the database path from the UI.
- Fix mobile horizontal overflow caused by grid min-width:auto.
- New internal/stats (types) and internal/store (SQLite owner: requests + credentials tables, WAL); store implements stats.Recorder.
- Stream (SSE tee) and non-stream chat paths parse upstream usage incl. cached_tokens and record per-request; add /api/stats, /api/stats/reset, /admin/stats HTML with cache hit rate.
- Drop credentials.json: remove auth file I/O and ZHANLU_CREDENTIALS_FILE; credential precedence is env vars > db row.
Redesign the login and login-result pages within the existing dark glass
palette: layered star-dot/grid/gradient background, gradient hairline panel
borders, gradient headline text, primary/ghost button split, and tri-state
status dots (ok/err/busy). Fix zero vertical spacing inside the login form
(the form element had no gap between fields and the submit button).
Model list is always fetched live from the gateway model-info endpoint, so the
static fallback list was dead config with stale IDs (GLM-4.7 vs zhanlu/glm-4.7).
Default to zhanlu/auto when the request omits model.
The 1.4.2 extension replaced the old signed/encrypted chat gateway with an
OpenAI-compatible aigateway. Align the proxy with the new flow:
- Use ecloud.10086.cn login/model base URLs, zhanlu_ide plugin headers and
v1.4.2 plugin version
- Provision the model API key via SM2-signed get-or-create after v1/login
profile fetch; store api_key/model_base_url/email in credentials
- Chat via Bearer apiKey against {modelBaseUrl}/chat/completions with plain
OpenAI SSE passthrough; fetch /v1/models from the gateway model-info endpoint
- Force HTTP/1.1 upstream (gateway drops HTTP/2 ALPN negotiation with EOF)
- Drop obsolete AES body encryption, model name mapping and vscode headers