forked from Alishahryar1/free-claude-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreasoning.py
More file actions
146 lines (117 loc) · 4.72 KB
/
Copy pathreasoning.py
File metadata and controls
146 lines (117 loc) · 4.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
"""Exclusive reasoning encoders for Google OpenAI-compatible endpoints."""
from dataclasses import dataclass
from typing import Any, cast
from free_claude_code.application.errors import InvalidRequestError
from free_claude_code.core.reasoning import (
DEFAULT_REASONING_POLICY,
ReasoningControl,
ReasoningEffort,
ReasoningPolicy,
)
from free_claude_code.providers.openai_chat import (
validate_extra_body_does_not_override_reasoning_fields,
)
_GEMINI_EFFORTS = {
ReasoningEffort.MINIMAL: "minimal",
ReasoningEffort.LOW: "low",
ReasoningEffort.MEDIUM: "medium",
ReasoningEffort.HIGH: "high",
ReasoningEffort.XHIGH: "high",
ReasoningEffort.MAX: "high",
}
_THINKING_CONFIG_CONFLICT = (
"Google extra_body.google.thinking_config cannot be combined with FCC "
"reasoning controls. Use either thinking/output_config or the provider-native "
"thinking_config."
)
@dataclass(frozen=True, slots=True)
class GeminiReasoningEncoder:
"""Choose one Gemini API reasoning channel for each resolved policy."""
def encode(self, body: dict[str, Any], policy: ReasoningPolicy) -> None:
if _preserve_caller_thinking_config(body, policy):
return
if policy.control is ReasoningControl.OFF:
body["reasoning_effort"] = "none"
return
if policy.budget_tokens is not None:
thinking = _thinking_config(body)
thinking["thinking_budget"] = policy.budget_tokens
thinking["include_thoughts"] = True
return
if effort := _GEMINI_EFFORTS.get(policy.effort):
body["reasoning_effort"] = effort
return
if policy.control is ReasoningControl.ON:
_thinking_config(body)["include_thoughts"] = True
@dataclass(frozen=True, slots=True)
class VertexReasoningEncoder:
"""Encode Vertex reasoning through one Google thinking-config object."""
def encode(self, body: dict[str, Any], policy: ReasoningPolicy) -> None:
if _preserve_caller_thinking_config(body, policy):
return
if policy.control is ReasoningControl.OFF:
thinking = _thinking_config(body)
thinking["thinking_budget"] = 0
thinking["include_thoughts"] = False
return
budget = policy.numeric_budget_tokens
if budget is not None:
thinking = _thinking_config(body)
thinking["thinking_budget"] = budget
thinking["include_thoughts"] = True
return
if policy.control is ReasoningControl.ON:
_thinking_config(body)["include_thoughts"] = True
def validate_google_extra_body(extra: dict[str, Any]) -> None:
"""Validate caller extensions before a Google encoder takes ownership."""
validate_extra_body_does_not_override_reasoning_fields(extra)
literal_extra = _optional_object(extra, "extra_body", "extra_body.extra_body")
if literal_extra is None:
return
google = _optional_object(literal_extra, "google", "extra_body.google")
if google is None:
return
_optional_object(
google,
"thinking_config",
"extra_body.google.thinking_config",
)
def _preserve_caller_thinking_config(
body: dict[str, Any], policy: ReasoningPolicy
) -> bool:
if _existing_thinking_config(body) is None:
return False
if policy != DEFAULT_REASONING_POLICY:
raise InvalidRequestError(_THINKING_CONFIG_CONFLICT)
return True
def _existing_thinking_config(body: dict[str, Any]) -> dict[str, Any] | None:
sdk_extra = body.get("extra_body")
if not isinstance(sdk_extra, dict):
return None
literal_extra = sdk_extra.get("extra_body")
if not isinstance(literal_extra, dict):
return None
google = literal_extra.get("google")
if not isinstance(google, dict):
return None
thinking = google.get("thinking_config")
return cast(dict[str, Any], thinking) if isinstance(thinking, dict) else None
def _thinking_config(body: dict[str, Any]) -> dict[str, Any]:
sdk_extra = _object(body, "extra_body")
literal_extra = _object(sdk_extra, "extra_body")
google = _object(literal_extra, "google")
return _object(google, "thinking_config")
def _object(container: dict[str, Any], key: str) -> dict[str, Any]:
value = container.setdefault(key, {})
if not isinstance(value, dict):
raise TypeError(f"{key} must be an object.")
return cast(dict[str, Any], value)
def _optional_object(
container: dict[str, Any], key: str, path: str
) -> dict[str, Any] | None:
if key not in container:
return None
value = container[key]
if not isinstance(value, dict):
raise ValueError(f"{path} must be an object.")
return cast(dict[str, Any], value)