diff --git a/.gitignore b/.gitignore index c6efbc0c..3c60ac8f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ dist/ # temp dir /tmp + +# kiro +.kiro/ \ No newline at end of file diff --git a/internal/providers/bedrock_nova_test.go b/internal/providers/bedrock_nova_test.go new file mode 100644 index 00000000..309cc9d7 --- /dev/null +++ b/internal/providers/bedrock_nova_test.go @@ -0,0 +1,91 @@ +package providers + +import ( + "regexp" + "testing" + + "charm.land/catwalk/pkg/catwalk" +) + +const nova2LiteModelID = "amazon.nova-2-lite-v1:0" + +func novaModels(provider catwalk.Provider) []catwalk.Model { + novaModelPattern := regexp.MustCompile(`^amazon\.nova-.+-v\d+:\d+$`) + var models []catwalk.Model + for _, model := range provider.Models { + if novaModelPattern.MatchString(model.ID) { + models = append(models, model) + } + } + return models +} + +func TestNova2LiteModelPresent(t *testing.T) { + provider := bedrockUnitedStatesProvider() + models := novaModels(provider) + + if len(models) != 1 { + t.Fatalf("expected exactly 1 Nova model, found %d", len(models)) + } + + model := models[0] + if model.ID != nova2LiteModelID { + t.Fatalf("expected %q, got %q", nova2LiteModelID, model.ID) + } + if model.Name != "Amazon Nova 2 Lite" { + t.Fatalf("unexpected model name: %q", model.Name) + } +} + +func TestNova2LiteModelConfiguration(t *testing.T) { + provider := bedrockUnitedStatesProvider() + + var model catwalk.Model + for _, m := range provider.Models { + if m.ID == nova2LiteModelID { + model = m + break + } + } + if model.ID == "" { + t.Fatal("Nova 2 Lite model not found") + } + + if model.CostPer1MIn != 0.06 { + t.Errorf("input cost should be 0.06, got %f", model.CostPer1MIn) + } + if model.CostPer1MOut != 0.24 { + t.Errorf("output cost should be 0.24, got %f", model.CostPer1MOut) + } + if model.ContextWindow != 1_000_000 { + t.Errorf("context window should be 1000000, got %d", model.ContextWindow) + } + if model.DefaultMaxTokens != 64_000 { + t.Errorf("default max tokens should be 64000, got %d", model.DefaultMaxTokens) + } + if !model.CanReason { + t.Error("Nova 2 Lite should support extended thinking") + } + if !model.SupportsImages { + t.Error("Nova 2 Lite should support attachments") + } +} + +func TestNovaGen1ModelsNotCataloged(t *testing.T) { + provider := bedrockUnitedStatesProvider() + + legacyIDs := []string{ + "amazon.nova-pro-v1:0", + "amazon.nova-lite-v1:0", + "amazon.nova-micro-v1:0", + "amazon.nova-premier-v1:0", + } + + for _, id := range legacyIDs { + for _, model := range provider.Models { + if model.ID == id { + t.Errorf("Gen 1 Nova model %q should not be in the Bedrock catalog", id) + } + } + } +} diff --git a/internal/providers/configs/bedrock-united-states.json b/internal/providers/configs/bedrock-united-states.json index 230f9cdf..d644d80d 100644 --- a/internal/providers/configs/bedrock-united-states.json +++ b/internal/providers/configs/bedrock-united-states.json @@ -138,6 +138,18 @@ "reasoning_levels": ["low", "medium", "high", "xhigh", "max"], "default_reasoning_effort": "high", "supports_attachments": true + }, + { + "id": "amazon.nova-2-lite-v1:0", + "name": "Amazon Nova 2 Lite", + "cost_per_1m_in": 0.06, + "cost_per_1m_out": 0.24, + "cost_per_1m_in_cached": 0, + "cost_per_1m_out_cached": 0, + "context_window": 1000000, + "default_max_tokens": 64000, + "can_reason": true, + "supports_attachments": true } ] }