proxy-router: fix chat-history JSON round-trip + default forward-context off - #804
proxy-router: fix chat-history JSON round-trip + default forward-context off#804cowboycoderhq wants to merge 4 commits into
Conversation
…ext off
AppendChatHistory recovered stored turns with a plain type assertion
(chat.Prompt.(OpenAiCompletionRequest)). Because Prompt is interface{},
a history read back from disk is map[string]interface{}, so the assertion
always failed after a JSON round-trip — and the discarded `ok` made it fail
silently: every stored turn was dropped and the model got no history. Recover
the concrete type by re-marshalling the generic map. Also take the LAST stored
message (the turn the user actually took), not Messages[0], so a client that
sends the running transcript doesn't get its first message replayed each turn.
Adds a regression test that exercises the marshal/unmarshal path and asserts
the prior turn survives.
Default PROXY_FORWARD_CHAT_CONTEXT to false when unset (was true). The client
owns the transcript and sends the full messages[]; with forwarding on the
router ALSO prepends stored history, duplicating context for any
OpenAI-compatible client. Storage stays on for the history drawer; forwarding
is now an explicit opt-in for clients that send only the latest turn.
alex-sandrk
left a comment
There was a problem hiding this comment.
Issues
1. Multi-content prompts silently drop the whole turn. openai.ChatCompletionMessage marshals MultiContent as an array; unmarshalling that into the local ChatCompletionMessage.Content string fails, so asCompletionRequest returns false and the turn including its text response is skipped. Same effective behavior as before. Add test for that.
2. The default flip leaves the Desktop UI without chat memory. Bundled desktop client sends only the latest turn:
const payload = {
stream: true,
messages: [incommingMessage],
};Nothing in the launcher or shipped env files sets PROXY_FORWARD_CHAT_CONTEXT=true, so the desktop UI runs on the default. Update ui-desktop/orchestrator.config.ts with: PROXY_STORE_CHAT_CONTEXT: 'true',
3. Docs contradict the new default. docs/reference/env-proxy-router.mdx (line 158) documents the default as true. Update docs.
…d-context Address review on #804. Multi-content ("content" as an array of typed parts, as openai. ChatCompletionMessage marshals MultiContent) made the local ChatCompletionMessage unmarshal fail, so asCompletionRequest rejected the stored request and the whole turn — text response included — was silently dropped from replayed history. Accept string/null/array content (an array flattens to its text parts); genuinely malformed content still errors. Image-only turns flatten to empty text and are skipped rather than forwarded as empty-content user messages. Regression tests cover the round-trip and each content shape. Desktop UI: the bundled client sends only the latest turn, so set PROXY_FORWARD_CHAT_CONTEXT=true in the orchestrator env (every platform config inherits it) and append the key to a pre-existing .env on upgrade — writeEnvFile never rewrites an existing file, so a key introduced after first install would otherwise never reach upgraded installs. Only absent keys are appended; user-edited values are untouched. Docs: PROXY_FORWARD_CHAT_CONTEXT default documented as false; sample env flipped to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017M9gPEcjZajpp6EtHphaQB
|
Thanks for the catch on all three — addressed in 71445de. 1. Multi-content prompts. Fixed with a custom
One edge this surfaced: an image-only multi-content turn flattens to empty text, and forwarding an empty-content user message can be rejected by strict providers — 2. Desktop UI memory. That alone only covers fresh installs: (Related observation, no action taken: the bundled CLI also sends only the latest turn — its 3. Docs. Verification: |
morrpc frames from a contract provider (e.g. a custody contract with no private key) are signed by the contract's owner(); the consumer previously required the recovered signer to equal the provider address, rejecting all contract providers with ErrInvalidSig before any chain call. Add an owner-resolver (eth_getCode + owner(), cached) mirroring SessionRouter._isValidProviderReceipt's contract-owner branch, and route the ping-discovery validation through it. EOA providers and the nil-resolver (mobile) path are byte-identical to before. Opus-reviewed; decodes the first ABI word to match the on-chain owner() decode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011SHw2bF3J8JU825xhAqNaf
alex-sandrk
left a comment
There was a problem hiding this comment.
@cowboycoderhq, please create a separate MR for other bugs and avoid committing after approval.
mobile/sdk.go never wires the ProviderAuthResolver. The mobile path still rejects contract providers.
The desktop/server entrypoint wires the resolver in cmd/main.go, but mobile/sdk.go constructs its ProxySender (line 198) without a corresponding SetProviderAuthResolver call. With a nil resolver, authorizedSigner() falls back to the provider address itself, so mobile consumers keep failing contract providers with ErrInvalidSig, including EnsureProviderRegistered.
The commit message frames this as intentional ("nil-resolver (mobile) path are byte-identical to before"), which is fine as a compatibility statement, but it means the bug this PR fixes remains unfixed for mobile.
Suggest adding it in this PR.
cmd/main.go already sets the resolver; mobile/sdk.go constructed ProxySender without it, so authorizedSigner() fell back to the provider address and rejected contract providers with ErrInvalidSig (including EnsureProviderRegistered). Co-authored-by: Cursor <cursoragent@cursor.com>
|
Wired
|
…d-context Address review on MorpheusAIs#804. Multi-content ("content" as an array of typed parts, as openai. ChatCompletionMessage marshals MultiContent) made the local ChatCompletionMessage unmarshal fail, so asCompletionRequest rejected the stored request and the whole turn — text response included — was silently dropped from replayed history. Accept string/null/array content (an array flattens to its text parts); genuinely malformed content still errors. Image-only turns flatten to empty text and are skipped rather than forwarded as empty-content user messages. Regression tests cover the round-trip and each content shape. Desktop UI: the bundled client sends only the latest turn, so set PROXY_FORWARD_CHAT_CONTEXT=true in the orchestrator env (every platform config inherits it) and append the key to a pre-existing .env on upgrade — writeEnvFile never rewrites an existing file, so a key introduced after first install would otherwise never reach upgraded installs. Only absent keys are appended; user-edited values are untouched. Docs: PROXY_FORWARD_CHAT_CONTEXT default documented as false; sample env flipped to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Part 1 of 3, splitting #798 as requested (router first, then UI function, then optional visual). This PR is proxy-router only.
1. Chat-history JSON round-trip fix (+ regression test).
AppendChatHistoryrecovered stored turns with a plain type assertionchat.Prompt.(OpenAiCompletionRequest).Promptisinterface{}, so a history read back from disk is amap[string]interface{}and the assertion always fails after a JSON round-trip — and the discardedokmade it fail silently: every stored turn was dropped and the model got no history. Fixed by re-marshalling the generic map back through the concrete type. Also takes the last stored message (the turn the user took) instead ofMessages[0], so a client sending the running transcript doesn't get its first message replayed each turn.history_roundtrip_test.goexercises the marshal/unmarshal path and asserts the prior turn survives.2. Default⚠️ Default change — called out per review. The client owns the transcript and sends the full
PROXY_FORWARD_CHAT_CONTEXTtofalsewhen unset (wastrue).messages[]; with forwarding on, the router also prepends stored history, duplicating context for any OpenAI-compatible client.PROXY_STORE_CHAT_CONTEXTstays on for the history drawer //v1/chats/:id; forwarding is now an explicit opt-in for clients that send only the latest turn.Verified:
go build ./...clean;go test -run TestAppendChatHistorypasses.