Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

449 lines
16 KiB
Go

package agentruntime
import (
"testing"
activityshared "github.com/tutti-os/tutti/packages/agent/daemon/activity/events"
)
func TestClaudeCodeSDKAdapterWaitsForContextWindowBeforePublishingUsage(t *testing.T) {
adapter := NewClaudeCodeSDKAdapter(nil)
session := standardTestSession(ProviderClaudeCode)
adapterSession := &claudeSDKAdapterSession{liveState: newClaudeSDKLiveState()}
adapterSession.applyConfigOption("model", "opus")
adapter.storeSession(session.AgentSessionID, adapterSession)
events, terminal, err := adapter.sidecarTurnEvents(adapterSession, session, "turn-1", claudeSDKSidecarEvent{
Type: "usage_updated",
Payload: map[string]any{
"turnId": "turn-1",
"usage": map[string]any{
"input_tokens": 100,
"output_tokens": 20,
"cache_read_input_tokens": 7,
"cache_creation_input_tokens": 3,
},
},
})
if err != nil || terminal {
t.Fatalf("usage_updated terminal=%v err=%v", terminal, err)
}
if len(events) != 0 {
t.Fatalf("usage events = %#v, want none before context window is known", events)
}
state := adapter.SessionState(session)
if usage := state.RuntimeContext["usage"]; usage != nil {
t.Fatalf("runtime usage = %#v, want unavailable before context window is known", usage)
}
}
func TestClaudeSDKReportedUsageTokensKeepsAggregateSeparateFromLatestIteration(t *testing.T) {
usage := map[string]any{
"input_tokens": int64(22),
"output_tokens": int64(8_172),
"cache_read_input_tokens": int64(5_054_371),
"cache_creation_input_tokens": int64(511_644),
"iterations": []any{
map[string]any{
"input_tokens": int64(22),
"output_tokens": int64(8_172),
"cache_read_input_tokens": int64(504_411),
},
},
}
if got := claudeSDKUsageTokens(usage); got != 512_605 {
t.Fatalf("latest iteration tokens = %d, want 512605", got)
}
if got := claudeSDKReportedUsageTokens(usage); got != 5_574_209 {
t.Fatalf("reported aggregate tokens = %d, want 5574209", got)
}
}
func TestClaudeCodeSDKAdapterMapsModelUsageContextWindowMap(t *testing.T) {
adapter := NewClaudeCodeSDKAdapter(nil)
session := standardTestSession(ProviderClaudeCode)
adapterSession := &claudeSDKAdapterSession{liveState: newClaudeSDKLiveState()}
adapter.storeSession(session.AgentSessionID, adapterSession)
adapterSession.applyConfigOption("model", "sonnet")
events, terminal, err := adapter.sidecarTurnEvents(adapterSession, session, "turn-1", claudeSDKSidecarEvent{
Type: "usage_updated",
Payload: map[string]any{
"turnId": "turn-1",
"usage": map[string]any{
"input_tokens": 2,
"output_tokens": 13,
"cache_read_input_tokens": 18622,
"cache_creation_input_tokens": 17466,
},
"modelUsage": map[string]any{
"claude-haiku-4-5-20251001": map[string]any{
"contextWindow": 200_000,
},
"claude-sonnet-5": map[string]any{
"contextWindow": 1_000_000,
},
},
},
})
if err != nil || terminal {
t.Fatalf("usage_updated terminal=%v err=%v", terminal, err)
}
if len(events) != 1 || events[0].Type != activityshared.EventSessionUpdated {
t.Fatalf("usage events = %#v, want session.updated", events)
}
state := adapter.SessionState(session)
usage, _ := state.RuntimeContext["usage"].(map[string]any)
contextWindow, _ := usage["contextWindow"].(map[string]any)
if got, ok := int64Value(contextWindow["usedTokens"]); !ok || got != 36103 {
t.Fatalf("usedTokens = %#v, want 36103", contextWindow["usedTokens"])
}
if got, ok := int64Value(contextWindow["totalTokens"]); !ok || got != 1_000_000 {
t.Fatalf("totalTokens = %#v, want model usage context window", contextWindow["totalTokens"])
}
}
func TestClaudeCodeSDKContextWindowDoesNotBorrowAnotherModel(t *testing.T) {
total := claudeSDKContextWindowTokens(map[string]any{
"modelUsage": map[string]any{
"claude-haiku-4-5": map[string]any{"contextWindow": 200_000},
"claude-sonnet-4-6": map[string]any{"contextWindow": 1_000_000},
},
}, "opus")
if total != 0 {
t.Fatalf("context window=%d, want no value for unmatched active model", total)
}
}
func TestClaudeCodeSDKAdapterPublishesFirstTurnUsageAfterModelWindowArrives(t *testing.T) {
adapter := NewClaudeCodeSDKAdapter(nil)
session := standardTestSession(ProviderClaudeCode)
adapterSession := &claudeSDKAdapterSession{liveState: newClaudeSDKLiveState()}
adapter.storeSession(session.AgentSessionID, adapterSession)
adapterSession.applyConfigOption("model", "opus")
events, terminal, err := adapter.sidecarTurnEvents(adapterSession, session, "turn-1", claudeSDKSidecarEvent{
Type: "usage_updated",
Payload: map[string]any{
"turnId": "turn-1",
"usage": map[string]any{
"input_tokens": 30_000,
"output_tokens": 8_551,
},
},
})
if err != nil || terminal {
t.Fatalf("usage_updated terminal=%v err=%v", terminal, err)
}
if len(events) != 0 {
t.Fatalf("usage events = %#v, want none before modelUsage arrives", events)
}
state := adapter.SessionState(session)
if usage := state.RuntimeContext["usage"]; usage != nil {
t.Fatalf("runtime usage = %#v, want unavailable before modelUsage arrives", usage)
}
events, terminal, err = adapter.sidecarTurnEvents(adapterSession, session, "turn-1", claudeSDKSidecarEvent{
Type: "usage_updated",
Payload: map[string]any{
"turnId": "turn-1",
"usage": map[string]any{
"input_tokens": 32_000,
"output_tokens": 8_859,
},
"modelUsage": map[string]any{
"claude-opus-4-6": map[string]any{
"contextWindow": 1_000_000,
},
},
},
})
if err != nil || terminal {
t.Fatalf("usage_updated (final) terminal=%v err=%v", terminal, err)
}
if len(events) != 1 {
t.Fatalf("usage events (final) = %#v", events)
}
state = adapter.SessionState(session)
usage, _ := state.RuntimeContext["usage"].(map[string]any)
contextWindow, _ := usage["contextWindow"].(map[string]any)
if got, ok := int64Value(contextWindow["usedTokens"]); !ok || got != 40_859 {
t.Fatalf("usedTokens (final) = %#v, want 40,859", contextWindow["usedTokens"])
}
if got, ok := int64Value(contextWindow["totalTokens"]); !ok || got != 1_000_000 {
t.Fatalf("totalTokens (final) = %#v, want 1,000,000 from modelUsage", contextWindow["totalTokens"])
}
}
func TestClaudeCodeSDKAdapterDoesNotCarryContextWindowAcrossModelChange(t *testing.T) {
adapter := NewClaudeCodeSDKAdapter(nil)
session := standardTestSession(ProviderClaudeCode)
adapterSession := &claudeSDKAdapterSession{liveState: newClaudeSDKLiveState()}
adapter.storeSession(session.AgentSessionID, adapterSession)
adapterSession.applyConfigOption("model", "haiku")
events, terminal, err := adapter.sidecarTurnEvents(adapterSession, session, "turn-1", claudeSDKSidecarEvent{
Type: "usage_updated",
Payload: map[string]any{
"turnId": "turn-1",
"contextWindow": map[string]any{
"usedTokens": 20_000,
"totalTokens": 200_000,
},
},
})
if err != nil || terminal || len(events) != 1 {
t.Fatalf("haiku usage events=%#v terminal=%v err=%v, want session.updated", events, terminal, err)
}
adapterSession.applyConfigOption("model", "sonnet")
events, terminal, err = adapter.sidecarTurnEvents(adapterSession, session, "turn-2", claudeSDKSidecarEvent{
Type: "usage_updated",
Payload: map[string]any{
"turnId": "turn-2",
"usage": map[string]any{
"input_tokens": 2,
"output_tokens": 13,
"cache_read_input_tokens": 18_622,
"cache_creation_input_tokens": 17_466,
},
"modelUsage": map[string]any{
"claude-sonnet-5": map[string]any{
"contextWindow": 1_000_000,
},
},
},
})
if err != nil || terminal || len(events) != 1 {
t.Fatalf("sonnet usage events=%#v terminal=%v err=%v, want session.updated", events, terminal, err)
}
adapterSession.applyConfigOption("model", "haiku")
events, terminal, err = adapter.sidecarTurnEvents(adapterSession, session, "turn-3", claudeSDKSidecarEvent{
Type: "usage_updated",
Payload: map[string]any{
"turnId": "turn-3",
"contextWindow": map[string]any{
"usedTokens": 29_538,
},
},
})
if err != nil || terminal {
t.Fatalf("haiku context usage terminal=%v err=%v", terminal, err)
}
if len(events) != 0 {
t.Fatalf("haiku context usage events=%#v, want none before the new model window is known", events)
}
state := adapter.SessionState(session)
usage, _ := state.RuntimeContext["usage"].(map[string]any)
contextWindow, _ := usage["contextWindow"].(map[string]any)
if got, ok := int64Value(contextWindow["usedTokens"]); !ok || got != 36_103 {
t.Fatalf("usedTokens = %#v, want previous published usage until the new model window is known", contextWindow["usedTokens"])
}
if got, ok := int64Value(contextWindow["totalTokens"]); !ok || got != 1_000_000 {
t.Fatalf("totalTokens = %#v, want previous published context window until the new model window is known", contextWindow["totalTokens"])
}
}
func TestClaudeCodeSDKAdapterReusesKnownContextWindowForSameModel(t *testing.T) {
adapter := NewClaudeCodeSDKAdapter(nil)
session := standardTestSession(ProviderClaudeCode)
adapterSession := &claudeSDKAdapterSession{liveState: newClaudeSDKLiveState()}
adapterSession.applyConfigOption("model", "opus")
adapterSession.liveState.usage = claudeSDKUsageState{
contextUsedTokens: 20_000,
contextWindowTokens: 1_000_000,
contextKnown: true,
contextModel: "opus",
}
adapter.storeSession(session.AgentSessionID, adapterSession)
events, terminal, err := adapter.sidecarTurnEvents(adapterSession, session, "turn-2", claudeSDKSidecarEvent{
Type: "usage_updated",
Payload: map[string]any{
"turnId": "turn-2",
"usage": map[string]any{
"input_tokens": 30_000,
"output_tokens": 8_551,
},
},
})
if err != nil || terminal || len(events) != 1 {
t.Fatalf("usage events=%#v terminal=%v err=%v, want same-model session.updated", events, terminal, err)
}
state := adapter.SessionState(session)
usage, _ := state.RuntimeContext["usage"].(map[string]any)
contextWindow, _ := usage["contextWindow"].(map[string]any)
if got, ok := int64Value(contextWindow["usedTokens"]); !ok || got != 38_551 {
t.Fatalf("usedTokens = %#v, want 38,551", contextWindow["usedTokens"])
}
if got, ok := int64Value(contextWindow["totalTokens"]); !ok || got != 1_000_000 {
t.Fatalf("totalTokens = %#v, want retained same-model context window", contextWindow["totalTokens"])
}
}
func TestClaudeCodeSDKAdapterInvalidatesContextWindowWhenSettingsChangeModel(t *testing.T) {
adapterSession := &claudeSDKAdapterSession{liveState: newClaudeSDKLiveState()}
adapterSession.applyConfigOption("model", "haiku")
adapterSession.liveState.usage = claudeSDKUsageState{
contextUsedTokens: 20_000,
contextWindowTokens: 200_000,
contextKnown: true,
contextModel: "haiku",
quotas: []map[string]any{{
"quotaType": "weekly", "percentRemaining": 75.5,
}},
}
if !adapterSession.applySettingsPayload(map[string]any{"model": "opus"}) {
t.Fatal("applySettingsPayload() changed=false, want model change")
}
if adapterSession.liveState.usage.contextKnown {
t.Fatalf("context usage=%#v, want invalidated after model change", adapterSession.liveState.usage)
}
if len(adapterSession.liveState.usage.quotas) != 1 {
t.Fatalf("quotas=%#v, want preserved quotas", adapterSession.liveState.usage.quotas)
}
}
func TestClaudeCodeSDKAdapterMapsContextUsageUpdatedIntoRuntimeContext(t *testing.T) {
adapter := NewClaudeCodeSDKAdapter(nil)
session := standardTestSession(ProviderClaudeCode)
adapterSession := &claudeSDKAdapterSession{liveState: newClaudeSDKLiveState()}
adapter.storeSession(session.AgentSessionID, adapterSession)
events, terminal, err := adapter.sidecarTurnEvents(adapterSession, session, "turn-1", claudeSDKSidecarEvent{
Type: "usage_updated",
Payload: map[string]any{
"turnId": "turn-1",
"contextWindow": map[string]any{
"usedTokens": 50_062,
"totalTokens": 200_000,
},
},
})
if err != nil || terminal {
t.Fatalf("usage_updated terminal=%v err=%v", terminal, err)
}
if len(events) != 1 || events[0].Type != activityshared.EventSessionUpdated {
t.Fatalf("usage events = %#v, want session.updated", events)
}
state := adapter.SessionState(session)
usage, _ := state.RuntimeContext["usage"].(map[string]any)
contextWindow, _ := usage["contextWindow"].(map[string]any)
if got, ok := int64Value(contextWindow["usedTokens"]); !ok || got != 50_062 {
t.Fatalf("usedTokens = %#v, want getContextUsage snapshot", contextWindow["usedTokens"])
}
if got, ok := int64Value(contextWindow["totalTokens"]); !ok || got != 200_000 {
t.Fatalf("totalTokens = %#v, want model context window", contextWindow["totalTokens"])
}
patch, ok := statePatchFromSessionEvent(
reportTestSource(),
events[0],
session.AgentSessionID,
100,
)
if !ok {
t.Fatal("statePatchFromSessionEvent() did not accept usage update")
}
usagePatch, ok := patch.RuntimeContext["usage"].(map[string]any)
if !ok {
t.Fatalf("runtime context patch = %#v, want usage", patch.RuntimeContext)
}
patchedContextWindow, ok := usagePatch["contextWindow"].(map[string]any)
if !ok {
t.Fatalf("usage patch = %#v, want contextWindow", usagePatch)
}
if got, ok := int64Value(patchedContextWindow["totalTokens"]); !ok || got != 200_000 {
t.Fatalf("patched totalTokens = %#v, want 200000", patchedContextWindow["totalTokens"])
}
}
func TestClaudeCodeSDKAdapterStartAppliesRestoreUsageBeforeSessionStarted(t *testing.T) {
adapter := NewClaudeCodeSDKAdapter(nil)
session := standardTestSession(ProviderClaudeCode)
adapterSession := &claudeSDKAdapterSession{liveState: newClaudeSDKLiveState()}
events := adapter.applySidecarSessionEvent(adapterSession, session, claudeSDKSidecarEvent{
Type: "usage_updated",
Payload: map[string]any{
"contextWindow": map[string]any{
"usedTokens": 50_062,
"totalTokens": 200_000,
},
},
})
if len(events) != 0 {
t.Fatalf("restore usage events = %#v, want buffered state only", events)
}
events = adapter.applySidecarSessionEvent(adapterSession, session, claudeSDKSidecarEvent{
Type: "session_started",
Payload: map[string]any{
"providerSessionId": "provider-session-1",
},
})
if len(events) != 1 || events[0].Type != activityshared.EventSessionStarted {
t.Fatalf("session_started events = %#v, want started event", events)
}
usage, _ := events[0].Payload.Metadata["usage"].(map[string]any)
contextWindow, _ := usage["contextWindow"].(map[string]any)
if got, ok := int64Value(contextWindow["usedTokens"]); !ok || got != 50_062 {
t.Fatalf("started runtime usage = %#v, want restore snapshot", events[0].Payload.Metadata["usage"])
}
}
func TestClaudeCodeSDKAdapterSessionStartedUsesSidecarModelConfigOptions(t *testing.T) {
adapter := NewClaudeCodeSDKAdapter(nil)
session := standardTestSession(ProviderClaudeCode)
adapterSession := &claudeSDKAdapterSession{liveState: newClaudeSDKLiveState()}
events := adapter.applySidecarSessionEvent(adapterSession, session, claudeSDKSidecarEvent{
Type: "session_started",
Payload: map[string]any{
"providerSessionId": "provider-session-1",
"model": "mimo-v2.5-pro",
"configOptions": []any{
map[string]any{
"id": "model",
"currentValue": "mimo-v2.5-pro",
"options": []any{
map[string]any{
"value": "default",
"name": "Default",
"description": "Provider default",
},
map[string]any{
"value": "mimo-v2.5-pro",
"name": "Mimo v2.5 Pro",
"description": "Custom Mimo model",
},
},
},
},
},
})
if len(events) != 1 || events[0].Type != activityshared.EventSessionStarted {
t.Fatalf("session_started events = %#v, want started event", events)
}
configOptions, ok := events[0].Payload.Metadata["configOptions"].([]map[string]any)
if !ok {
t.Fatalf("configOptions = %#v, want descriptors", events[0].Payload.Metadata["configOptions"])
}
modelOption := configOptionByID(configOptions, "model")
if modelOption == nil {
t.Fatalf("configOptions = %#v, missing model option", configOptions)
}
if modelOption["currentValue"] != "mimo-v2.5-pro" {
t.Fatalf("model option currentValue = %#v, want mimo", modelOption["currentValue"])
}
modelOptions := configOptionEntries(modelOption["options"])
if len(modelOptions) != 2 || modelOptions[1]["value"] != "mimo-v2.5-pro" || modelOptions[1]["name"] != "Mimo v2.5 Pro" {
t.Fatalf("model options = %#v, want sidecar options", modelOptions)
}
if events[0].Payload.Metadata["model"] != "mimo-v2.5-pro" {
t.Fatalf("runtime model = %#v, want mimo", events[0].Payload.Metadata["model"])
}
}