Files
zero/internal/cli/exec_usage_cache_test.go
T
jatmnandClaude Opus 5.5 833219aeb4 Merge #829 (feat/zeromaxing) onto current main
Brings gnanam1990's 195-commit zeromaxing/orchestrate branch (head
6916f853) onto main b32f7ce4, with every original commit unchanged.
All conflict resolution lives in this merge commit.

Resolutions that combine both sides:
- module path: Gitlawb/zero imports rewritten to Twigpine/zero
- config: project maxTurns stays tighten-only and records maxTurnsSet
  only when it applies; the partial (no-provider) config keeps Profiles
- config writer: SetKeepFinishedAgents takes main's config file lock
- exec: execTurnBudget applies before the zeromaxing refusal/delta notice
- exec_command: tty-gated interactive guard (main) plus the
  sandbox-selected shell preflight (PR); the Windows description
  guidance is the <environment> pointer, kept paired with
  hostExecCommandShellGuidance for the token ratchet
- specialist: LifecycleHooks (main) alongside the PR's executor fields;
  main and the PR both fixed resume --model, so it is emitted once
- cli: registerSpecialistTools keeps the PR's signature and wires main's
  lifecycle hooks bridge into the executor
- tui: main's file-view refresh, run-details clicks, notify fields and
  locked model selection, combined with the PR's plan messages, chip,
  background-plan /new guard and spinner-tick ordering
- resume model tests: both add/add files kept (resume_parent_model_test.go)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

Rebased integration onto b32f7ce4; original #829 commits and integration resolutions retained.
2026-11-09 08:26:38 -08:00

89 lines
3.0 KiB
Go

package cli
import (
"bytes"
"encoding/json"
"strings"
"testing"
"github.com/Twigpine/zero/internal/agent"
)
// THE CHILD MUST PUBLISH WHAT MAKES ITS TURN PRICEABLE.
//
// This writer is the only channel a parent has. Its session record already keeps
// cachedInputTokens / cacheWriteTokens / reasoningTokens so a turn can be costed
// exactly; the stream carried prompt/completion/total alone, so every sub-agent
// turn rolled up to its parent was priced as if nothing had been cached.
//
// A measured plan task had 49,280 of 49,894 prompt tokens served from cache —
// 98.8%. Plan tasks are the ideal cache case: one large stable prompt, re-sent
// every turn.
func TestTheChildPublishesCacheAndReasoningTokensOnTheUsageStream(t *testing.T) {
var stdout, stderr bytes.Buffer
writer := execEventWriter{
stdout: &stdout,
stderr: &stderr,
format: execOutputStreamJSON,
runID: "run_usage",
streamedText: &strings.Builder{},
}
writer.usage(agent.Usage{
PromptTokens: 30000,
CompletionTokens: 500,
CachedInputTokens: 29000,
CacheWriteTokens: 1000,
ReasoningTokens: 120,
})
if writer.err != nil {
t.Fatalf("usage: %v", writer.err)
}
var payload map[string]any
if err := json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &payload); err != nil {
t.Fatalf("decode stream event %q: %v", stdout.String(), err)
}
for key, want := range map[string]float64{
"cachedInputTokens": 29000, "cacheWriteTokens": 1000, "reasoningTokens": 120,
} {
got, ok := payload[key]
if !ok {
t.Errorf("the stream omits %q, so a parent prices this turn as uncached: %v", key, payload)
continue
}
if number, ok := got.(float64); !ok || number != want {
t.Errorf("%s: want %v, got %v", key, want, got)
}
}
// The three original fields must be exactly as before.
for key, want := range map[string]float64{
"promptTokens": 30000, "completionTokens": 500, "totalTokens": 30500,
} {
if got, ok := payload[key].(float64); !ok || got != want {
t.Errorf("%s changed: want %v, got %v", key, want, payload[key])
}
}
}
// NON-ZERO ONLY, exactly like usage.EventUsagePayload. A provider reporting no
// cache must produce the same three-field event it always did, or every existing
// reader of this stream sees a shape it has never seen.
func TestAProviderWithNoCacheStillEmitsTheOriginalUsageShape(t *testing.T) {
var stdout, stderr bytes.Buffer
writer := execEventWriter{
stdout: &stdout, stderr: &stderr, format: execOutputStreamJSON,
runID: "run_usage", streamedText: &strings.Builder{},
}
writer.usage(agent.Usage{PromptTokens: 100, CompletionTokens: 10})
var payload map[string]any
if err := json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &payload); err != nil {
t.Fatalf("decode: %v", err)
}
for _, key := range []string{"cachedInputTokens", "cacheWriteTokens", "reasoningTokens"} {
if _, present := payload[key]; present {
t.Errorf("%q was emitted as a zero, widening the event for every provider that reports no cache", key)
}
}
}