code-puppy 0.0.367__py3-none-any.whl → 0.0.368__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_puppy/claude_cache_client.py +45 -7
- code_puppy/plugins/claude_code_oauth/utils.py +4 -1
- code_puppy/pydantic_patches.py +62 -0
- {code_puppy-0.0.367.dist-info → code_puppy-0.0.368.dist-info}/METADATA +1 -1
- {code_puppy-0.0.367.dist-info → code_puppy-0.0.368.dist-info}/RECORD +10 -10
- {code_puppy-0.0.367.data → code_puppy-0.0.368.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.367.data → code_puppy-0.0.368.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.367.dist-info → code_puppy-0.0.368.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.367.dist-info → code_puppy-0.0.368.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.367.dist-info → code_puppy-0.0.368.dist-info}/licenses/LICENSE +0 -0
|
@@ -119,24 +119,62 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
|
|
|
119
119
|
return None
|
|
120
120
|
|
|
121
121
|
def _should_refresh_token(self, request: httpx.Request) -> bool:
|
|
122
|
-
"""Check if the token
|
|
122
|
+
"""Check if the token should be refreshed (within 1 hour of expiry).
|
|
123
|
+
|
|
124
|
+
Uses two strategies:
|
|
125
|
+
1. Decode JWT to check token age (if possible)
|
|
126
|
+
2. Fall back to stored expires_at from token file
|
|
127
|
+
|
|
128
|
+
Returns True if token expires within TOKEN_MAX_AGE_SECONDS (1 hour).
|
|
129
|
+
"""
|
|
123
130
|
token = self._extract_bearer_token(request)
|
|
124
131
|
if not token:
|
|
125
132
|
return False
|
|
126
133
|
|
|
134
|
+
# Strategy 1: Try to decode JWT age
|
|
127
135
|
age = self._get_jwt_age_seconds(token)
|
|
128
|
-
if age is None:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
if age is not None:
|
|
137
|
+
should_refresh = age >= TOKEN_MAX_AGE_SECONDS
|
|
138
|
+
if should_refresh:
|
|
139
|
+
logger.info(
|
|
140
|
+
"JWT token is %.1f seconds old (>= %d), will refresh proactively",
|
|
141
|
+
age,
|
|
142
|
+
TOKEN_MAX_AGE_SECONDS,
|
|
143
|
+
)
|
|
144
|
+
return should_refresh
|
|
145
|
+
|
|
146
|
+
# Strategy 2: Fall back to stored expires_at from token file
|
|
147
|
+
should_refresh = self._check_stored_token_expiry()
|
|
132
148
|
if should_refresh:
|
|
133
149
|
logger.info(
|
|
134
|
-
"
|
|
135
|
-
age,
|
|
150
|
+
"Stored token expires within %d seconds, will refresh proactively",
|
|
136
151
|
TOKEN_MAX_AGE_SECONDS,
|
|
137
152
|
)
|
|
138
153
|
return should_refresh
|
|
139
154
|
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _check_stored_token_expiry() -> bool:
|
|
157
|
+
"""Check if the stored token expires within TOKEN_MAX_AGE_SECONDS.
|
|
158
|
+
|
|
159
|
+
This is a fallback for when JWT decoding fails or isn't available.
|
|
160
|
+
Uses the expires_at timestamp from the stored token file.
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
from code_puppy.plugins.claude_code_oauth.utils import (
|
|
164
|
+
is_token_expired,
|
|
165
|
+
load_stored_tokens,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
tokens = load_stored_tokens()
|
|
169
|
+
if not tokens:
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
# is_token_expired already uses TOKEN_REFRESH_BUFFER_SECONDS (1 hour)
|
|
173
|
+
return is_token_expired(tokens)
|
|
174
|
+
except Exception as exc:
|
|
175
|
+
logger.debug("Error checking stored token expiry: %s", exc)
|
|
176
|
+
return False
|
|
177
|
+
|
|
140
178
|
@staticmethod
|
|
141
179
|
def _prefix_tool_names(body: bytes) -> bytes | None:
|
|
142
180
|
"""Prefix all tool names in the request body with TOOL_PREFIX.
|
|
@@ -21,7 +21,10 @@ from .config import (
|
|
|
21
21
|
get_token_storage_path,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
# Proactive refresh buffer: refresh tokens 1 hour before expiration
|
|
25
|
+
# This ensures smooth operation by refreshing during model requests,
|
|
26
|
+
# well before the token actually expires
|
|
27
|
+
TOKEN_REFRESH_BUFFER_SECONDS = 3600 # 1 hour
|
|
25
28
|
|
|
26
29
|
logger = logging.getLogger(__name__)
|
|
27
30
|
|
code_puppy/pydantic_patches.py
CHANGED
|
@@ -121,6 +121,67 @@ def patch_process_message_history() -> None:
|
|
|
121
121
|
pass
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
def patch_tool_call_json_repair() -> None:
|
|
125
|
+
"""Patch pydantic-ai's _call_tool to auto-repair malformed JSON arguments.
|
|
126
|
+
|
|
127
|
+
LLMs sometimes produce slightly broken JSON in tool calls (trailing commas,
|
|
128
|
+
missing quotes, etc.). This patch intercepts tool calls and runs json_repair
|
|
129
|
+
on the arguments before validation, preventing unnecessary retries.
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
import json_repair
|
|
133
|
+
from pydantic_ai._tool_manager import ToolManager
|
|
134
|
+
|
|
135
|
+
# Store the original method
|
|
136
|
+
_original_call_tool = ToolManager._call_tool
|
|
137
|
+
|
|
138
|
+
async def _patched_call_tool(
|
|
139
|
+
self,
|
|
140
|
+
call,
|
|
141
|
+
*,
|
|
142
|
+
allow_partial: bool,
|
|
143
|
+
wrap_validation_errors: bool,
|
|
144
|
+
approved: bool,
|
|
145
|
+
):
|
|
146
|
+
"""Patched _call_tool that repairs malformed JSON before validation."""
|
|
147
|
+
# Only attempt repair if args is a string (JSON)
|
|
148
|
+
if isinstance(call.args, str) and call.args:
|
|
149
|
+
try:
|
|
150
|
+
repaired = json_repair.repair_json(call.args)
|
|
151
|
+
if repaired != call.args:
|
|
152
|
+
# JSON was repaired! Log and update
|
|
153
|
+
try:
|
|
154
|
+
from rich.console import Console
|
|
155
|
+
console = Console(stderr=True)
|
|
156
|
+
console.print(
|
|
157
|
+
"[bold yellow]🐕 WOOF! Repaired malformed tool call JSON! AWOOOOOOOOOO![/]"
|
|
158
|
+
)
|
|
159
|
+
except ImportError:
|
|
160
|
+
pass # No rich console available
|
|
161
|
+
|
|
162
|
+
# Update the call args with repaired JSON
|
|
163
|
+
call.args = repaired
|
|
164
|
+
except Exception:
|
|
165
|
+
pass # If repair fails, let original validation handle it
|
|
166
|
+
|
|
167
|
+
# Call the original method
|
|
168
|
+
return await _original_call_tool(
|
|
169
|
+
self,
|
|
170
|
+
call,
|
|
171
|
+
allow_partial=allow_partial,
|
|
172
|
+
wrap_validation_errors=wrap_validation_errors,
|
|
173
|
+
approved=approved,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Apply the patch
|
|
177
|
+
ToolManager._call_tool = _patched_call_tool
|
|
178
|
+
|
|
179
|
+
except ImportError:
|
|
180
|
+
pass # json_repair or pydantic_ai not available
|
|
181
|
+
except Exception:
|
|
182
|
+
pass # Don't crash on patch failure
|
|
183
|
+
|
|
184
|
+
|
|
124
185
|
def apply_all_patches() -> None:
|
|
125
186
|
"""Apply all pydantic-ai monkey patches.
|
|
126
187
|
|
|
@@ -129,3 +190,4 @@ def apply_all_patches() -> None:
|
|
|
129
190
|
patch_user_agent()
|
|
130
191
|
patch_message_history_cleaning()
|
|
131
192
|
patch_process_message_history()
|
|
193
|
+
patch_tool_call_json_repair()
|
|
@@ -2,7 +2,7 @@ code_puppy/__init__.py,sha256=xMPewo9RNHb3yfFNIk5WCbv2cvSPtJOCgK2-GqLbNnU,373
|
|
|
2
2
|
code_puppy/__main__.py,sha256=pDVssJOWP8A83iFkxMLY9YteHYat0EyWDQqMkKHpWp4,203
|
|
3
3
|
code_puppy/callbacks.py,sha256=Pp0VyeXJBEtk-N_RSWr5pbveelovsdLUiJ4f11dzwGw,10775
|
|
4
4
|
code_puppy/chatgpt_codex_client.py,sha256=Om0ANB_kpHubhCwNzF9ENf8RvKBqs0IYzBLl_SNw0Vk,9833
|
|
5
|
-
code_puppy/claude_cache_client.py,sha256=
|
|
5
|
+
code_puppy/claude_cache_client.py,sha256=1-rIDtZBJ_aiAZSprdsq0ty2urftu6F0uzJDn_OE41Q,23966
|
|
6
6
|
code_puppy/cli_runner.py,sha256=w5CLKgQYYaT7My3Cga2StXYol-u6DBxNzzUuhhsfhsA,34952
|
|
7
7
|
code_puppy/config.py,sha256=blowBU3bBOdQSuLYKBUrb7f7CxHH_e25a_A4lQGsjgk,53494
|
|
8
8
|
code_puppy/error_logging.py,sha256=a80OILCUtJhexI6a9GM-r5LqIdjvSRzggfgPp2jv1X0,3297
|
|
@@ -16,7 +16,7 @@ code_puppy/model_utils.py,sha256=55TKNnGTXQlHJNqejL2PfQqQmChXfzOjJg-hlarfR7w,555
|
|
|
16
16
|
code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
|
|
17
17
|
code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
|
|
18
18
|
code_puppy/models_dev_parser.py,sha256=8ndmWrsSyKbXXpRZPXc0w6TfWMuCcgaHiMifmlaBaPc,20611
|
|
19
|
-
code_puppy/pydantic_patches.py,sha256=
|
|
19
|
+
code_puppy/pydantic_patches.py,sha256=Y8v2VIlhOArODIaQJYJWNR3rVtte0qox3Am0X_d70N8,6943
|
|
20
20
|
code_puppy/reopenable_async_client.py,sha256=pD34chyBFcC7_OVPJ8fp6aRI5jYdN-7VDycObMZPwG8,8292
|
|
21
21
|
code_puppy/round_robin_model.py,sha256=kSawwPUiPgg0yg8r4AAVgvjzsWkptxpSORd75-HP7W4,5335
|
|
22
22
|
code_puppy/session_storage.py,sha256=T4hOsAl9z0yz2JZCptjJBOnN8fCmkLZx5eLy1hTdv6Q,9631
|
|
@@ -170,7 +170,7 @@ code_puppy/plugins/claude_code_oauth/__init__.py,sha256=mCcOU-wM7LNCDjr-w-WLPzom
|
|
|
170
170
|
code_puppy/plugins/claude_code_oauth/config.py,sha256=DjGySCkvjSGZds6DYErLMAi3TItt8iSLGvyJN98nSEM,2013
|
|
171
171
|
code_puppy/plugins/claude_code_oauth/register_callbacks.py,sha256=g8sl-i7jIOF6OFALeaLqTF3mS4tD8GR_FCzvPjVw2js,10165
|
|
172
172
|
code_puppy/plugins/claude_code_oauth/test_plugin.py,sha256=yQy4EeZl4bjrcog1d8BjknoDTRK75mRXXvkSQJYSSEM,9286
|
|
173
|
-
code_puppy/plugins/claude_code_oauth/utils.py,sha256=
|
|
173
|
+
code_puppy/plugins/claude_code_oauth/utils.py,sha256=Ltk_m2efGNQSSvmb1lxgcPp_vcEig8VMydTZffQP-2c,17433
|
|
174
174
|
code_puppy/plugins/customizable_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
175
175
|
code_puppy/plugins/customizable_commands/register_callbacks.py,sha256=zVMfIzr--hVn0IOXxIicbmgj2s-HZUgtrOc0NCDOnDw,5183
|
|
176
176
|
code_puppy/plugins/example_custom_command/README.md,sha256=5c5Zkm7CW6BDSfe3WoLU7GW6t5mjjYAbu9-_pu-b3p4,8244
|
|
@@ -208,10 +208,10 @@ code_puppy/tools/browser/chromium_terminal_manager.py,sha256=w1thQ_ACb6oV45L93TS
|
|
|
208
208
|
code_puppy/tools/browser/terminal_command_tools.py,sha256=9byOZku-dwvTtCl532xt7Lumed_jTn0sLvUe_X75XCQ,19068
|
|
209
209
|
code_puppy/tools/browser/terminal_screenshot_tools.py,sha256=J_21YO_495NvYgNFu9KQP6VYg2K_f8CtSdZuF94Yhnw,18448
|
|
210
210
|
code_puppy/tools/browser/terminal_tools.py,sha256=F5LjVH3udSCFHmqC3O1UJLoLozZFZsEdX42jOmkqkW0,17853
|
|
211
|
-
code_puppy-0.0.
|
|
212
|
-
code_puppy-0.0.
|
|
213
|
-
code_puppy-0.0.
|
|
214
|
-
code_puppy-0.0.
|
|
215
|
-
code_puppy-0.0.
|
|
216
|
-
code_puppy-0.0.
|
|
217
|
-
code_puppy-0.0.
|
|
211
|
+
code_puppy-0.0.368.data/data/code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
|
|
212
|
+
code_puppy-0.0.368.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
|
|
213
|
+
code_puppy-0.0.368.dist-info/METADATA,sha256=kymVWcqiOTKCZz-LgbRVLSz49pa4S42pdp3ZAi5kNhI,27600
|
|
214
|
+
code_puppy-0.0.368.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
215
|
+
code_puppy-0.0.368.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
|
|
216
|
+
code_puppy-0.0.368.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
217
|
+
code_puppy-0.0.368.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|