mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-11 18:00:54 -10:00
The hooks specialist declares, supplied from the two surfaces that own a provider and a sandbox. DISCOVERY FOLLOWS THE LIVE PROVIDER. It was bound once at startup, so a session that switched provider with /model kept probing the one it began on: a run on xai/grok-4.5 had its plan assigned from a nineteen-model Ollama list and the provider refused every task. It now re-resolves through the same path a freshly spawned child takes, config plus ZERO_PROVIDER, so discovery and child execution read one source of truth rather than two kept in sync. The captured profile still wins when the provider is unchanged, because --provider, --base-url and an inline --api-key are applied on top of Resolve and re-resolving would discard them. A CHILD STANDS ON THE SAME GROUND AS ITS PARENT. A run granted a directory outside its workspace could create it, populate it, and then watch every plan task be refused at that same directory — a child rebuilds its sandbox from --cwd alone and the grant lived only in the parent's engine. The run's roots now reach the child as --add-dir, read at launch so a mid-session approval lands, and on the resume path too: this file has already seen the resume path forget what the fresh path carried. PROVING, wired to the provider stack and exposed as `providers models --verify` — which model this provider will actually run, as opposed to which it lists. That is the difference the exclude list was being maintained by hand to express. Origin-Session: local-de382f | Claude Code | 3 prompts Origin-Snapshot: c28c274718fa
89 lines
3.0 KiB
Go
89 lines
3.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Gitlawb/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)
|
|
}
|
|
}
|
|
}
|