-
Notifications
You must be signed in to change notification settings - Fork 11.2k
feat: support claude-opus-4-7 #4293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ var awsModelIDMap = map[string]string{ | |
| "claude-haiku-4-5-20251001": "anthropic.claude-haiku-4-5-20251001-v1:0", | ||
| "claude-opus-4-5-20251101": "anthropic.claude-opus-4-5-20251101-v1:0", | ||
| "claude-opus-4-6": "anthropic.claude-opus-4-6-v1", | ||
| "claude-opus-4-7": "anthropic.claude-opus-4-7", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: No Bedrock model ID "claude-opus-4-7" exists. Claude Opus 4.7 is listed as available in Amazon Bedrock (https://docs.aws.amazon.com/bedrock/latest/userguide/model-cards-anthropic.html), but its specific model ID is not explicitly stated in model ID lists or model cards. Similar models use IDs like anthropic.claude-opus-4-6-v1 (for Opus 4.6) or anthropic.claude-opus-4-5-20251101-v1:0 (for Opus 4.5). It may follow the pattern anthropic.claude-opus-4-7-v1, but this is not confirmed in official sources. Check the latest AWS Bedrock console or ListFoundationModels API for the exact ID. Citations:
🏁 Script executed: # First, let's check if the file exists and view the relevant sections
if [ -f relay/channel/aws/constants.go ]; then
echo "=== Lines 15-30 (around line 21) ==="
sed -n '15,30p' relay/channel/aws/constants.go
echo ""
echo "=== Lines 90-105 (around lines 95-99) ==="
sed -n '90,105p' relay/channel/aws/constants.go
echo ""
echo "=== Total line count ==="
wc -l relay/channel/aws/constants.go
else
echo "File not found"
fiRepository: QuantumNous/new-api Length of output: 1387 Replace model ID with documented versioned format or remove unpublished entry. The Bedrock model ID Affected locations: line 21 ( 🤖 Prompt for AI Agents |
||
| // Nova models | ||
| "nova-micro-v1:0": "amazon.nova-micro-v1:0", | ||
| "nova-lite-v1:0": "amazon.nova-lite-v1:0", | ||
|
|
@@ -91,6 +92,11 @@ var awsModelCanCrossRegionMap = map[string]map[string]bool{ | |
| "ap": true, | ||
| "eu": true, | ||
| }, | ||
| "anthropic.claude-opus-4-7": { | ||
| "us": true, | ||
| "ap": true, | ||
| "eu": true, | ||
| }, | ||
| "anthropic.claude-haiku-4-5-20251001-v1:0": { | ||
| "us": true, | ||
| "ap": true, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -154,33 +154,52 @@ func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRe | |
| } | ||
|
|
||
| if baseModel, effortLevel, ok := reasoning.TrimEffortSuffix(textRequest.Model); ok && effortLevel != "" && | ||
| strings.HasPrefix(textRequest.Model, "claude-opus-4-6") { | ||
| (strings.HasPrefix(textRequest.Model, "claude-opus-4-6") || strings.HasPrefix(textRequest.Model, "claude-opus-4-7")) { | ||
| claudeRequest.Model = baseModel | ||
| claudeRequest.Thinking = &dto.Thinking{ | ||
| Type: "adaptive", | ||
| } | ||
| claudeRequest.OutputConfig = json.RawMessage(fmt.Sprintf(`{"effort":"%s"}`, effortLevel)) | ||
| claudeRequest.TopP = nil | ||
| claudeRequest.Temperature = common.GetPointer[float64](1.0) | ||
| if strings.HasPrefix(baseModel, "claude-opus-4-7") { | ||
| // Opus 4.7 rejects non-default temperature/top_p/top_k with 400 | ||
| // and defaults display to "omitted"; restore the 4.6 visible summary. | ||
| claudeRequest.Thinking.Display = "summarized" | ||
| claudeRequest.Temperature = nil | ||
| claudeRequest.TopP = nil | ||
| claudeRequest.TopK = nil | ||
| } else { | ||
| claudeRequest.TopP = nil | ||
| claudeRequest.Temperature = common.GetPointer[float64](1.0) | ||
| } | ||
| } else if model_setting.GetClaudeSettings().ThinkingAdapterEnabled && | ||
| strings.HasSuffix(textRequest.Model, "-thinking") { | ||
|
|
||
| // 因为BudgetTokens 必须大于1024 | ||
| if claudeRequest.MaxTokens == nil || *claudeRequest.MaxTokens < 1280 { | ||
| claudeRequest.MaxTokens = common.GetPointer[uint](1280) | ||
| } | ||
| trimmedModel := strings.TrimSuffix(textRequest.Model, "-thinking") | ||
| if strings.HasPrefix(trimmedModel, "claude-opus-4-7") { | ||
| // Opus 4.7 rejects thinking.type="enabled"; use adaptive at high effort. | ||
| claudeRequest.Thinking = &dto.Thinking{Type: "adaptive", Display: "summarized"} | ||
| claudeRequest.OutputConfig = json.RawMessage(`{"effort":"high"}`) | ||
| claudeRequest.Temperature = nil | ||
| claudeRequest.TopP = nil | ||
| claudeRequest.TopK = nil | ||
|
Comment on lines
+163
to
+184
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep Opus 4.7 on adaptive thinking when reasoning overrides are present. Lines 163-184 switch Opus 4.7 to Possible direction+isOpus47 := strings.HasPrefix(strings.TrimSuffix(claudeRequest.Model, "-thinking"), "claude-opus-4-7")
+
if textRequest.ReasoningEffort != "" {
switch textRequest.ReasoningEffort {
case "low":
- claudeRequest.Thinking = &dto.Thinking{
- Type: "enabled",
- BudgetTokens: common.GetPointer[int](1280),
- }
+ if isOpus47 {
+ claudeRequest.Thinking = &dto.Thinking{Type: "adaptive", Display: "summarized"}
+ claudeRequest.OutputConfig = json.RawMessage(`{"effort":"low"}`)
+ claudeRequest.Temperature = nil
+ claudeRequest.TopP = nil
+ claudeRequest.TopK = nil
+ } else {
+ claudeRequest.Thinking = &dto.Thinking{
+ Type: "enabled",
+ BudgetTokens: common.GetPointer[int](1280),
+ }
+ }
}
}Apply the same branching to the Also applies to: 206-239 🤖 Prompt for AI Agents |
||
| } else { | ||
| // 因为BudgetTokens 必须大于1024 | ||
| if claudeRequest.MaxTokens == nil || *claudeRequest.MaxTokens < 1280 { | ||
| claudeRequest.MaxTokens = common.GetPointer[uint](1280) | ||
| } | ||
|
|
||
| // BudgetTokens 为 max_tokens 的 80% | ||
| claudeRequest.Thinking = &dto.Thinking{ | ||
| Type: "enabled", | ||
| BudgetTokens: common.GetPointer[int](int(float64(*claudeRequest.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage)), | ||
| // BudgetTokens 为 max_tokens 的 80% | ||
| claudeRequest.Thinking = &dto.Thinking{ | ||
| Type: "enabled", | ||
| BudgetTokens: common.GetPointer[int](int(float64(*claudeRequest.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage)), | ||
| } | ||
| // TODO: 临时处理 | ||
| // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking | ||
| claudeRequest.TopP = nil | ||
| claudeRequest.Temperature = common.GetPointer[float64](1.0) | ||
| } | ||
| // TODO: 临时处理 | ||
| // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking | ||
| claudeRequest.TopP = nil | ||
| claudeRequest.Temperature = common.GetPointer[float64](1.0) | ||
| if !model_setting.ShouldPreserveThinkingSuffix(textRequest.Model) { | ||
| claudeRequest.Model = strings.TrimSuffix(textRequest.Model, "-thinking") | ||
| claudeRequest.Model = trimmedModel | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -53,30 +53,49 @@ func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if baseModel, effortLevel, ok := reasoning.TrimEffortSuffix(request.Model); ok && effortLevel != "" && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| strings.HasPrefix(request.Model, "claude-opus-4-6") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (strings.HasPrefix(request.Model, "claude-opus-4-6") || strings.HasPrefix(request.Model, "claude-opus-4-7")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Model = baseModel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Thinking = &dto.Thinking{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Type: "adaptive", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.OutputConfig = json.RawMessage(fmt.Sprintf(`{"effort":"%s"}`, effortLevel)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Temperature = common.GetPointer[float64](1.0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if strings.HasPrefix(request.Model, "claude-opus-4-7") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Opus 4.7 rejects non-default temperature/top_p/top_k with 400 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // and defaults display to "omitted"; restore the 4.6 visible summary. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Thinking.Display = "summarized" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Temperature = nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.TopP = nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.TopK = nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Temperature = common.GetPointer[float64](1.0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| info.UpstreamModelName = request.Model | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if model_setting.GetClaudeSettings().ThinkingAdapterEnabled && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| strings.HasSuffix(request.Model, "-thinking") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if request.Thinking == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 因为BudgetTokens 必须大于1024 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if request.MaxTokens == nil || *request.MaxTokens < 1280 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.MaxTokens = common.GetPointer[uint](1280) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| baseModel := strings.TrimSuffix(request.Model, "-thinking") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if strings.HasPrefix(baseModel, "claude-opus-4-7") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Opus 4.7 rejects thinking.type="enabled"; use adaptive at high effort. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Thinking = &dto.Thinking{Type: "adaptive", Display: "summarized"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.OutputConfig = json.RawMessage(`{"effort":"high"}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Temperature = nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.TopP = nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.TopK = nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 因为BudgetTokens 必须大于1024 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if request.MaxTokens == nil || *request.MaxTokens < 1280 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.MaxTokens = common.GetPointer[uint](1280) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // BudgetTokens 为 max_tokens 的 80% | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Thinking = &dto.Thinking{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Type: "enabled", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BudgetTokens: common.GetPointer[int](int(float64(*request.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // BudgetTokens 为 max_tokens 的 80% | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Thinking = &dto.Thinking{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Type: "enabled", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BudgetTokens: common.GetPointer[int](int(float64(*request.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: 临时处理 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Temperature = common.GetPointer[float64](1.0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
75
to
98
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apply the Opus 4.7 normalization even when The 4.7-specific rewrite only runs inside 🐛 Minimal fix sketch- if request.Thinking == nil {
- baseModel := strings.TrimSuffix(request.Model, "-thinking")
- if strings.HasPrefix(baseModel, "claude-opus-4-7") {
- request.Thinking = &dto.Thinking{Type: "adaptive", Display: "summarized"}
- request.OutputConfig = json.RawMessage(`{"effort":"high"}`)
- request.Temperature = nil
- request.TopP = nil
- request.TopK = nil
- } else {
+ baseModel := strings.TrimSuffix(request.Model, "-thinking")
+ if strings.HasPrefix(baseModel, "claude-opus-4-7") {
+ if request.Thinking == nil {
+ request.Thinking = &dto.Thinking{}
+ }
+ request.Thinking.Type = "adaptive"
+ request.Thinking.Display = "summarized"
+ request.OutputConfig = json.RawMessage(`{"effort":"high"}`)
+ request.Temperature = nil
+ request.TopP = nil
+ request.TopK = nil
+ } else if request.Thinking == nil {
// 因为BudgetTokens 必须大于1024
if request.MaxTokens == nil || *request.MaxTokens < 1280 {
request.MaxTokens = common.GetPointer[uint](1280)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: 临时处理 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Temperature = common.GetPointer[float64](1.0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !model_setting.ShouldPreserveThinkingSuffix(info.OriginModelName) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.Model = strings.TrimSuffix(request.Model, "-thinking") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make
Thinking.Displaya pointer field.Displayis an optional scalar on a request DTO that gets parsed from client JSON and re-marshaled upstream. Withstring,omitempty, an explicit empty value is indistinguishable from unset, which breaks the DTO contract used elsewhere in this layer.♻️ Suggested shape
type Thinking struct { Type string `json:"type,omitempty"` BudgetTokens *int `json:"budget_tokens,omitempty"` - Display string `json:"display,omitempty"` + Display *string `json:"display,omitempty"` }As per coding guidelines,
dto/**/*.go: optional scalar fields MUST use pointer types withomitempty, not non-pointer scalars.🤖 Prompt for AI Agents