mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-07 21:38:21 +00:00
This change stops short of broader Claude Code runtime alignment and instead hardens two safe edges: builtin tool prefix handling and source-informed sentinel coverage for future drift checks. Constraint: Must preserve existing default behavior for current users Rejected: Implement control-plane/session alignment now | too much runtime risk for a first slice Confidence: high Scope-risk: narrow Reversibility: clean Directive: Treat the new fixtures as compatibility sentinels, not a full Claude Code schema contract Tested: go test ./test/...; go test ./sdk/translator/...; go test ./internal/runtime/executor -run 'Claude|Builtin|Tool'; go test ./... Not-tested: End-to-end Claude Code direct-connect/session runtime behavior
107 lines
3.4 KiB
Go
107 lines
3.4 KiB
Go
package test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
type jsonObject = map[string]any
|
|
|
|
func loadClaudeCodeSentinelFixture(t *testing.T, name string) jsonObject {
|
|
t.Helper()
|
|
path := filepath.Join("testdata", "claude_code_sentinels", name)
|
|
data := mustReadFile(t, path)
|
|
var payload jsonObject
|
|
if err := json.Unmarshal(data, &payload); err != nil {
|
|
t.Fatalf("unmarshal %s: %v", name, err)
|
|
}
|
|
return payload
|
|
}
|
|
|
|
func mustReadFile(t *testing.T, path string) []byte {
|
|
t.Helper()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read %s: %v", path, err)
|
|
}
|
|
return data
|
|
}
|
|
|
|
func requireStringField(t *testing.T, obj jsonObject, key string) string {
|
|
t.Helper()
|
|
value, ok := obj[key].(string)
|
|
if !ok || value == "" {
|
|
t.Fatalf("field %q missing or empty: %#v", key, obj[key])
|
|
}
|
|
return value
|
|
}
|
|
|
|
func TestClaudeCodeSentinel_ToolProgressShape(t *testing.T) {
|
|
payload := loadClaudeCodeSentinelFixture(t, "tool_progress.json")
|
|
if got := requireStringField(t, payload, "type"); got != "tool_progress" {
|
|
t.Fatalf("type = %q, want tool_progress", got)
|
|
}
|
|
requireStringField(t, payload, "tool_use_id")
|
|
requireStringField(t, payload, "tool_name")
|
|
requireStringField(t, payload, "session_id")
|
|
if _, ok := payload["elapsed_time_seconds"].(float64); !ok {
|
|
t.Fatalf("elapsed_time_seconds missing or non-number: %#v", payload["elapsed_time_seconds"])
|
|
}
|
|
}
|
|
|
|
func TestClaudeCodeSentinel_SessionStateShape(t *testing.T) {
|
|
payload := loadClaudeCodeSentinelFixture(t, "session_state_changed.json")
|
|
if got := requireStringField(t, payload, "type"); got != "system" {
|
|
t.Fatalf("type = %q, want system", got)
|
|
}
|
|
if got := requireStringField(t, payload, "subtype"); got != "session_state_changed" {
|
|
t.Fatalf("subtype = %q, want session_state_changed", got)
|
|
}
|
|
state := requireStringField(t, payload, "state")
|
|
switch state {
|
|
case "idle", "running", "requires_action":
|
|
default:
|
|
t.Fatalf("unexpected session state %q", state)
|
|
}
|
|
requireStringField(t, payload, "session_id")
|
|
}
|
|
|
|
func TestClaudeCodeSentinel_ToolUseSummaryShape(t *testing.T) {
|
|
payload := loadClaudeCodeSentinelFixture(t, "tool_use_summary.json")
|
|
if got := requireStringField(t, payload, "type"); got != "tool_use_summary" {
|
|
t.Fatalf("type = %q, want tool_use_summary", got)
|
|
}
|
|
requireStringField(t, payload, "summary")
|
|
rawIDs, ok := payload["preceding_tool_use_ids"].([]any)
|
|
if !ok || len(rawIDs) == 0 {
|
|
t.Fatalf("preceding_tool_use_ids missing or empty: %#v", payload["preceding_tool_use_ids"])
|
|
}
|
|
for i, raw := range rawIDs {
|
|
if id, ok := raw.(string); !ok || id == "" {
|
|
t.Fatalf("preceding_tool_use_ids[%d] invalid: %#v", i, raw)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestClaudeCodeSentinel_ControlRequestCanUseToolShape(t *testing.T) {
|
|
payload := loadClaudeCodeSentinelFixture(t, "control_request_can_use_tool.json")
|
|
if got := requireStringField(t, payload, "type"); got != "control_request" {
|
|
t.Fatalf("type = %q, want control_request", got)
|
|
}
|
|
requireStringField(t, payload, "request_id")
|
|
request, ok := payload["request"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("request missing or invalid: %#v", payload["request"])
|
|
}
|
|
if got := requireStringField(t, request, "subtype"); got != "can_use_tool" {
|
|
t.Fatalf("request.subtype = %q, want can_use_tool", got)
|
|
}
|
|
requireStringField(t, request, "tool_name")
|
|
requireStringField(t, request, "tool_use_id")
|
|
if input, ok := request["input"].(map[string]any); !ok || len(input) == 0 {
|
|
t.Fatalf("request.input missing or empty: %#v", request["input"])
|
|
}
|
|
}
|