Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions history.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ const (
MsgHistoryPage = "history_page"
)

type HistoryErrorCode string

const (
HistoryErrorSessionNotFound HistoryErrorCode = "session_not_found"
HistoryErrorUnavailable HistoryErrorCode = "history_unavailable"
HistoryErrorBusy HistoryErrorCode = "history_busy"
HistoryErrorInvalidRequest HistoryErrorCode = "invalid_request"
)

// SessionHeadPayload is carried in an AgentMessage with Type MsgSessionHead.
type SessionHeadPayload struct {
SessionID string `json:"session_id"`
Expand All @@ -43,6 +52,7 @@ type HistoryPagePayload struct {
HasMore bool `json:"has_more"`
RequestID string `json:"request_id"`
Error string `json:"error,omitempty"`
ErrorCode HistoryErrorCode `json:"error_code,omitempty"`
Trimmed bool `json:"trimmed,omitempty"`
RetainedOldestSeq uint64 `json:"retained_oldest_seq,omitempty"`
}
5 changes: 3 additions & 2 deletions history_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func TestHistoryPagePayloadJSONRoundtrip(t *testing.T) {
HasMore: true,
RequestID: "request-123",
Error: "history unavailable",
ErrorCode: HistoryErrorUnavailable,
Trimmed: true,
RetainedOldestSeq: 97,
}
Expand All @@ -96,7 +97,7 @@ func TestHistoryPagePayloadJSONRoundtrip(t *testing.T) {
if err != nil {
t.Fatalf("marshal HistoryPagePayload: %v", err)
}
const wantJSON = `{"session_id":"session-123","messages":[{"type":"output","session_id":"session-123","seq":97},{"type":"output","session_id":"session-123","seq":98}],"oldest_seq":97,"has_more":true,"request_id":"request-123","error":"history unavailable","trimmed":true,"retained_oldest_seq":97}`
const wantJSON = `{"session_id":"session-123","messages":[{"type":"output","session_id":"session-123","seq":97},{"type":"output","session_id":"session-123","seq":98}],"oldest_seq":97,"has_more":true,"request_id":"request-123","error":"history unavailable","error_code":"history_unavailable","trimmed":true,"retained_oldest_seq":97}`
if string(raw) != wantJSON {
t.Fatalf("HistoryPagePayload JSON = %s, want %s", raw, wantJSON)
}
Expand All @@ -105,7 +106,7 @@ func TestHistoryPagePayloadJSONRoundtrip(t *testing.T) {
if err := json.Unmarshal(raw, &out); err != nil {
t.Fatalf("unmarshal HistoryPagePayload: %v", err)
}
if out.SessionID != in.SessionID || out.OldestSeq != in.OldestSeq || out.HasMore != in.HasMore || out.RequestID != in.RequestID || out.Error != in.Error || out.Trimmed != in.Trimmed || out.RetainedOldestSeq != in.RetainedOldestSeq {
if out.SessionID != in.SessionID || out.OldestSeq != in.OldestSeq || out.HasMore != in.HasMore || out.RequestID != in.RequestID || out.Error != in.Error || out.ErrorCode != in.ErrorCode || out.Trimmed != in.Trimmed || out.RetainedOldestSeq != in.RetainedOldestSeq {
t.Fatalf("HistoryPagePayload scalar roundtrip = %+v, want %+v", out, in)
}
if len(out.Messages) != len(in.Messages) || string(out.Messages[0]) != string(in.Messages[0]) || string(out.Messages[1]) != string(in.Messages[1]) {
Expand Down
4 changes: 4 additions & 0 deletions provider_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type ProviderModelInfo struct {
ResolvedModel string `json:"resolved_model,omitempty"`
DisplayName string `json:"display_name"`
Description string `json:"description,omitempty"`
Disabled bool `json:"disabled,omitempty"`
SupportsEffort bool `json:"supports_effort,omitempty"`
SupportedEffortLevels []string `json:"supported_effort_levels,omitempty"`
DefaultEffort string `json:"default_effort,omitempty"`
Expand All @@ -61,6 +62,7 @@ type ProviderModelInfo struct {
type ProviderRuntimeCatalog struct {
Agent string `json:"agent"`
Provider string `json:"provider"`
ScopeID string `json:"scope_id,omitempty"`
State ProviderCatalogState `json:"state"`
Generation string `json:"generation,omitempty"`
Source ProviderCatalogSource `json:"source,omitempty"`
Expand All @@ -75,10 +77,12 @@ type ProviderRuntimeCatalog struct {
type ListProviderCatalogsRequest struct {
Type string `json:"type"`
RequestID string `json:"request_id"`
WorkDir string `json:"work_dir,omitempty"`
}

type ListProviderCatalogsResponse struct {
Type string `json:"type"`
RequestID string `json:"request_id,omitempty"`
ScopeID string `json:"scope_id,omitempty"`
Catalogs []ProviderRuntimeCatalog `json:"catalogs"`
}
68 changes: 67 additions & 1 deletion provider_catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ func TestProviderRuntimeCatalogRoundTrip(t *testing.T) {
want := ListProviderCatalogsResponse{
Type: MsgProviderCatalogList,
RequestID: "catalog-1",
ScopeID: "scope-abc123",
Catalogs: []ProviderRuntimeCatalog{
{
Agent: "claude-code",
Provider: "bedrock",
ScopeID: "scope-abc123",
State: ProviderCatalogReady,
Generation: "generation-1",
Source: ProviderCatalogSourceSDK,
Expand All @@ -26,6 +28,7 @@ func TestProviderRuntimeCatalogRoundTrip(t *testing.T) {
Value: "provider-model-a",
ResolvedModel: "provider-model-a-20260710",
DisplayName: "Provider Model A",
Disabled: true,
SupportsEffort: true,
SupportedEffortLevels: []string{"low", "high"},
SupportsAdaptiveThinking: true,
Expand All @@ -47,11 +50,74 @@ func TestProviderRuntimeCatalogRoundTrip(t *testing.T) {
if err := json.Unmarshal(raw, &got); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if len(got.Catalogs) != 1 || got.Catalogs[0].Provider != "bedrock" || got.Catalogs[0].Models[0].ResolvedModel != "provider-model-a-20260710" {
if got.ScopeID != "scope-abc123" ||
len(got.Catalogs) != 1 ||
got.Catalogs[0].Provider != "bedrock" ||
got.Catalogs[0].ScopeID != "scope-abc123" ||
got.Catalogs[0].Models[0].ResolvedModel != "provider-model-a-20260710" ||
!got.Catalogs[0].Models[0].Disabled {
t.Fatalf("round trip = %+v", got)
}
}

func TestListProviderCatalogsResponseScopeIDCompatibility(t *testing.T) {
t.Parallel()

tests := []struct {
name string
payload string
wantScope string
}{
{
name: "scoped response",
payload: `{"type":"provider_catalog_list","scope_id":"scope-abc123","catalogs":[]}`,
wantScope: "scope-abc123",
},
{
name: "legacy response",
payload: `{"type":"provider_catalog_list","catalogs":[]}`,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var got ListProviderCatalogsResponse
if err := json.Unmarshal([]byte(tt.payload), &got); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if got.ScopeID != tt.wantScope {
t.Fatalf("scope_id = %q, want %q", got.ScopeID, tt.wantScope)
}
})
}
}

func TestListProviderCatalogsRequestRoundTripScopedWorkDir(t *testing.T) {
t.Parallel()

in := ListProviderCatalogsRequest{
Type: MsgListProviderCatalogs,
RequestID: "catalog-request-1",
WorkDir: "/workspace/project",
}
raw, err := json.Marshal(in)
if err != nil {
t.Fatalf("Marshal: %v", err)
}
const wantJSON = `{"type":"list_provider_catalogs","request_id":"catalog-request-1","work_dir":"/workspace/project"}`
if string(raw) != wantJSON {
t.Fatalf("request JSON = %s, want %s", raw, wantJSON)
}
var got ListProviderCatalogsRequest
if err := json.Unmarshal(raw, &got); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if got != in {
t.Fatalf("round trip = %+v, want %+v", got, in)
}
}

func TestProviderCatalogUnavailableIsSanitized(t *testing.T) {
t.Parallel()

Expand Down