1package provider
2
3import (
4 "math"
5 "slices"
6 "testing"
7)
8
9func TestAstraReleasedMetadata(t *testing.T) {
10 for _, name := range []string{"openai", "openai-codex"} {
11 t.Run(name, func(t *testing.T) {
12 m, err := FindModel(name, "gpt-6-astra")
13 if err != nil {
14 t.Fatal(err)
15 }
16 if m.ContextWindow != 500000 || m.MaxOutput != 128000 || m.Speculative {
17 t.Fatalf("released model must retain the intentional 500k budget: %+v", m)
18 }
19 if name == "openai" && m.API != APIResponses {
20 t.Fatalf("API = %q, want Responses", m.API)
21 }
22 if !slices.Contains(AvailableReasoningLevels(m), "max") || ClampReasoningForModel(m, "max") != "max" {
23 t.Fatalf("native max unavailable: %v", AvailableReasoningLevels(m))
24 }
25 if m.PriceTierInputTokens != 272000 || m.PriceInputAbove != 20 || m.PriceOutputAbove != 75 || m.PriceCacheReadAbove != 2 || m.PriceCacheWriteAbove != 25 {
26 t.Fatalf("long-context rates: %+v", m)
27 }
28 for _, tc := range []struct {
29 name string
30 usage Usage
31 want float64
32 }{
33 {"at_threshold", Usage{InputTokens: 100000, CacheReadTokens: 100000, CacheWriteTokens: 72000, OutputTokens: 1000}, 4.075},
34 {"above_threshold", Usage{InputTokens: 100000, CacheReadTokens: 100001, CacheWriteTokens: 72000, OutputTokens: 1000}, 4.075002},
35 } {
36 t.Run(tc.name, func(t *testing.T) {
37 if got := ComputeCost(m, tc.usage); math.Abs(got-tc.want) > 1e-9 {
38 t.Fatalf("cost = %.9f, want %.9f", got, tc.want)
39 }
40 })
41 }
42 })
43 }
44}
45
46func TestGPT61SolFamilyCapabilities(t *testing.T) {
47 for _, name := range []string{"openai", "openai-codex"} {
48 m, err := FindModel(name, "gpt-6.1-sol")
49 if err != nil {
50 t.Fatal(err)
51 }
52 if m.Speculative || !slices.Contains(AvailableReasoningLevels(m), "max") || ClampReasoningForModel(m, "max") != "max" {
53 t.Fatalf("%s: native max unavailable: %+v", name, m)
54 }
55 if m.PriceTierInputTokens != 272000 || m.PriceInputAbove != 4 || m.PriceOutputAbove != 15 || m.PriceCacheReadAbove != 0.2 || m.PriceCacheWriteAbove != 5 {
56 t.Fatalf("%s: long-context rates: %+v", name, m)
57 }
58 }
59 if OpenAICodexReasoningEffort("max", "gpt-6.1-sol") != "max" || !supportsOpenAIExplicitPromptCache("gpt-6.1-sol") {
60 t.Fatal("gpt-6.1-sol must share GPT-6 max reasoning and explicit prompt caching")
61 }
62}
63
64func TestAstraResponsesCapabilities(t *testing.T) {
65 public := NewOpenAIResponsesNamed("synthetic-token", "", "openai").(*renamedClient).inner.(*responsesWebSocketClient).http
66 custom := NewOpenAIResponsesNamed("synthetic-token", "https://example.com/v1/responses", "openai").(*renamedClient).inner.(*codexClient)
67 for _, tc := range []struct {
68 name string
69 client *codexClient
70 breakpoint bool
71 }{
72 {"public", public, true},
73 {"subscription", newOpenAICodexClient("synthetic-token", "synthetic-account", ""), false},
74 {"custom", custom, false},
75 } {
76 t.Run(tc.name, func(t *testing.T) {
77 wire, err := tc.client.buildRequest(Request{Model: "gpt-6-astra", Reasoning: "max", Messages: []Message{{Role: RoleUser, Content: []Content{TextBlock{Text: "hello"}}}}})
78 if err != nil {
79 t.Fatal(err)
80 }
81 if wire.Reasoning == nil || wire.Reasoning.Effort != "max" {
82 t.Fatalf("reasoning = %+v, want max", wire.Reasoning)
83 }
84 if !tc.breakpoint {
85 if wire.PromptCacheOptions != nil || len(wire.Input) != 1 {
86 t.Fatalf("undeclared endpoint received explicit caching: %+v", wire)
87 }
88 return
89 }
90 if wire.PromptCacheOptions == nil || wire.PromptCacheOptions.Mode != "implicit" || len(wire.Input) != 2 {
91 t.Fatalf("missing stable boundary with implicit history caching: %+v", wire)
92 }
93 boundary, ok := wire.Input[0].(codexInputMessage)
94 if !ok || boundary.Role != "developer" || len(boundary.Content) != 1 {
95 t.Fatalf("boundary = %#v", wire.Input[0])
96 }
97 text, ok := boundary.Content[0].(codexInputText)
98 if !ok || text.PromptCacheBreakpoint == nil || text.PromptCacheBreakpoint.Mode != "explicit" {
99 t.Fatalf("breakpoint = %#v", boundary.Content[0])
100 }
101 })
102 }
103}