Files
magpie/internal/plugin/systemca_test.go
T
sundjly f55a13f23e gateway: a translated reply cut off mid-stream goes on where the upstream has a real prefill (#853)
* gateway: a Chat upstream gets a mended tool exchange — Kimi turns a broken one away 400 ("tool_call_id is not found"): a turn's tool results all go ahead of its own text, in the calls' order; a result whose call is gone gets a synthetic call (unknown_tool) so it survives; an unanswered call gets a synthetic error result; a thinking-only assistant turn, which an aborted stream leaves behind, is dropped

* gateway: a translated reply whose stream the upstream cuts off mid-way goes on instead of failing — the same conversation is asked again with what the client already has of the reply sent back as its last message, for the model to go on from (vk-relay dropping Kimi K3 and DeepSeek streams mid-reply: "API Error: Server error mid-response"). The reply so far goes back with its reasoning for models that read reasoning_content back (Kimi, DeepSeek), a full echo of it is dropped, and the continuation's fresh thinking isn't shown again; the client waits with keepalives between tries, up to two. A reply a tool call of has begun can't be prefilled, and a refusal isn't asked again: both end with the error in the stream, as they used to. A stream's own error event mid-reply ends its read at once rather than waiting on a vendor that keeps the connection open. TestStreamCutMidReplyContinues (reply goes on, one message), TestStreamCutContinuationEchoDropped, TestStreamCutAfterToolCallEndsAsBefore, TestStreamCutContinuesOnlySoLong, TestStreamCutMidThinkingContinues(WithoutReasoning), TestStreamErrorMidReplyContinuesAtOnce and TestContinuationUnecho cover it; TestStreamErrorKeptOpenFailsOverAtOnce and the severed-stream tests pass unchanged. go vet ./internal/gateway, go build (darwin, GOOS=linux, GOOS=windows), go test of gateway, provider, catalog and imagemcp pass.

* gateway: a Chat upstream's mended tool exchange — an orphaned or late result reaches the model as a user message, the Responses path's policy, so no call is made up and no id is used twice; an empty assistant is dropped only inside a pending exchange, where it would break adjacency, and passes through outside one; a second answer to a call is dropped, the first stands; the session replay harness gives way to fixed fixtures

* gateway: a cut reply is asked to go on only where the upstream goes on from a prefill — a trailing assistant message isn't one on most Chat APIs: the model answers it again from the start, reworded, and the client, an exact echo alone dropped, kept its part and the re-answer spliced as one reply with no error to tell (api.deepseek.com did so 5 times in 6). It goes on only where the prefill is real: an Anthropic Messages upstream natively (a model that turns one away answers 400 and the reply ends as it used to; the newest Claude among them), a Chat one in its vendor's own mode — DeepSeek's prefix, asked of its /beta endpoint with "prefix": true on the message, Kimi's "partial": true — a relay on this machine or the LAN serving one of their models taken to front the vendor, as geminiCompat takes one to front Gemini; everywhere else the reply ends with the error, as it used to, for the agent to try the turn again. reasoning_content goes back with the prefill by the same decision (reasoningBack named any kimi model on any host); the prefill's trailing whitespace, which Anthropic turns the request away for, is trimmed, the client having it already; and the tries' usages are summed into the reply's, the client's and the ledger's, where only the last try's was kept while the vendor billed each (a cut try's is only what it said before the cut). New tests: a re-answering upstream is never asked to go on (the client reads the error, not the garble), DeepSeek's /beta prefix mode and Kimi's partial on the wire, an Anthropic upstream's native prefill (text, thinking-only, whitespace trimmed, a refused prefill ending as it used to), the tries' usages summed, chatPrefill and prefillHow themselves. go vet, builds (darwin, nogui) and go test -count=1 -race ./internal/gateway pass (2146; gui's TestSetPortMovesTheGateway fails on main too).

* tests: three environment- and drift-broken suites pass again — port: a moved gateway finishes the connections it has (Relisten's handover), so answers() pooled a keep-alive one from its start-up polls and the old port answered from it forever; it now dials with keep-alive off, asking what the test means to: does the old port take NEW connections. plugin: TestBunIsGivenTheSystemsRoots read the shell's ambient NODE_EXTRA_CA_CERTS as the user's own (it wins by design); the test sets its own. accounts: forgetting the account in use puts the one left first since 30d952cf, which updated the provider and GUI tests but not accounts_plugin_cli_test.go; it now expects that. All three failed on upstream/main on a machine with a proxy CA configured; the full nogui suite passes.
2026-10-05 17:25:15 +08:00

49 lines
1.6 KiB
Go

package plugin
import (
"context"
"slices"
"strings"
"testing"
)
// Bun checks TLS against its own roots: behind a proxy that re-signs HTTPS
// with a root the user installed, `bun add` failed with
// UNABLE_TO_VERIFY_LEAF_SIGNATURE (chenyyi001 on X, installing
// @magpie-community/opencode-kiro-auth). Every bun magpie runs is given
// the system's roots, unless the user names a file of their own.
func TestBunIsGivenTheSystemsRoots(t *testing.T) {
old := systemCAFile
t.Cleanup(func() { systemCAFile = old })
systemCAFile = func() string { return "/cache/bun/system-ca-abc.pem" }
// one the environment sets already is the user's and wins (below);
// the ambient one a shell may have isn't this test's
t.Setenv("NODE_EXTRA_CA_CERTS", "")
cmd := bunCommand(context.Background(), "bun", t.TempDir(), "add", "x")
if !slices.Contains(cmd.Env, "NODE_EXTRA_CA_CERTS=/cache/bun/system-ca-abc.pem") {
t.Fatalf("bun add isn't given the system's roots: %v", caVars(cmd.Env))
}
t.Setenv("NODE_EXTRA_CA_CERTS", "/mine.pem")
cmd = bunCommand(context.Background(), "bun", t.TempDir(), "add", "x")
if got := caVars(cmd.Env); !slices.Equal(got, []string{"NODE_EXTRA_CA_CERTS=/mine.pem"}) {
t.Fatalf("the user's own NODE_EXTRA_CA_CERTS = %v, want it alone", got)
}
systemCAFile = func() string { return "" }
if got := caEnv(nil); got != nil {
t.Fatalf("with nothing to add: %v", got)
}
}
func caVars(env []string) []string {
var out []string
for _, kv := range env {
if strings.HasPrefix(strings.ToUpper(kv), "NODE_EXTRA_CA_CERTS=") {
out = append(out, kv)
}
}
return out
}