claude: a Claude account's usage and model test read Claude Code's answer with its verbose output on (KevinXC on Discord: 新版本 claude 额度查询失败了; the card said usage couldn't be read, its tooltip began [{"type":"system","subtype":"init") — with Claude Code's own "Verbose output" turned on (its /config, kept in .claude.json, which --setting-sources "" leaves in force), claude -p --output-format json prints every message as a JSON array, the system init first and the result last, and magpie read only the one-object form: /usage's output was taken for an error and shown as one, and a model test's is_error was missed. The result is now the array's last "result" message when it is an array; the one-object form reads as before. Arguments, prompts and headers are unchanged. TestClaudeVerboseOutput fails without the change; the real Claude Code 2.1.286 with verbose on, in a sandbox config directory, gives an array that now reads to its /usage text. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gateway ./internal/provider pass.

This commit is contained in:
yetone committed 2026-10-01 20:05:33 +08:00
1 parent 9c54a560cc
commit a02076dcb8
2 files changed
+56 -10

No files matched your search

+31 -10
View File
@@ -53,11 +53,7 @@ func askClaude(ctx context.Context, configDir, model string) error {
var out, stderr bytes.Buffer
cmd.Stdout, cmd.Stderr = &out, &stderr
runErr := cmd.Run()
var res struct {
IsError bool `json:"is_error"`
Result string `json:"result"`
}
if json.Unmarshal(out.Bytes(), &res) == nil && res.IsError {
if res, ok := claudeResult(out.Bytes()); ok && res.IsError {
return errors.New("Claude Code: " + clip(res.Result))
}
if runErr != nil {
@@ -98,11 +94,7 @@ func claudeUsage(ctx context.Context) (string, error) {
var out, stderr bytes.Buffer
cmd.Stdout, cmd.Stderr = &out, &stderr
runErr := cmd.Run()
var res struct {
IsError bool `json:"is_error"`
Result string `json:"result"`
}
if err := json.Unmarshal(out.Bytes(), &res); err == nil {
if res, ok := claudeResult(out.Bytes()); ok {
if res.IsError {
return "", errors.New("Claude Code: " + clip(res.Result))
}
@@ -118,6 +110,35 @@ func claudeUsage(ctx context.Context) (string, error) {
return "", errors.New("Claude Code: " + clip(msg))
}
// claudeRun is the result Claude Code prints for a -p run with
// --output-format json.
type claudeRun struct {
Type string `json:"type"`
IsError bool `json:"is_error"`
Result string `json:"result"`
}
// claudeResult reads the result from what claude -p --output-format json
// printed: one object, or, when the user turned on Claude Code's verbose
// output (its own config, which --setting-sources leaves on), an array of
// every message, the result last.
func claudeResult(b []byte) (claudeRun, bool) {
var res claudeRun
if json.Unmarshal(b, &res) == nil {
return res, true
}
var all []claudeRun
if json.Unmarshal(b, &all) != nil {
return claudeRun{}, false
}
for i := len(all) - 1; i >= 0; i-- {
if all[i].Type == "result" {
return all[i], true
}
}
return claudeRun{}, false
}
func claudeUsageArgs() []string {
return []string{"-p", "/usage", "--output-format", "json",
"--tools", "", "--strict-mcp-config", "--setting-sources", "", "--no-session-persistence"}
+25
View File
@@ -115,3 +115,28 @@ func TestClaudeUsageRunsClaudeCode(t *testing.T) {
}
}
}
// With Claude Code's verbose output on (its /config, kept in .claude.json),
// claude -p --output-format json prints every message as an array, the
// system init first and the result last (KevinXC on Discord: usage
// couldn't be read; the tooltip began [{"type":"system","subtype":"init")
func TestClaudeVerboseOutput(t *testing.T) {
text := "Current session: 13% used"
b, _ := json.Marshal([]map[string]any{
{"type": "system", "subtype": "init", "tools": []string{}},
{"type": "result", "subtype": "success", "is_error": false, "result": text},
})
fakeWarmClaude(t, string(b), 0)
got, err := claudeUsage(context.Background())
if err != nil || got != text {
t.Fatalf("usage %q %v", got, err)
}
b, _ = json.Marshal([]map[string]any{
{"type": "system", "subtype": "init"},
{"type": "result", "is_error": true, "result": "Invalid API key · Please run /login"},
})
fakeWarmClaude(t, string(b), 1)
if err := warmClaude(context.Background(), ""); err == nil || !strings.Contains(err.Error(), "Please run /login") || strings.Contains(err.Error(), "init") {
t.Fatalf("got %v", err)
}
}