code-puppy 0.0.372__py3-none-any.whl → 0.0.373__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.
@@ -26,7 +26,7 @@ import httpx
26
26
 
27
27
  logger = logging.getLogger(__name__)
28
28
 
29
- # Refresh token if it's older than 1 hour (3600 seconds)
29
+ # Refresh token if it's older than the configured max age (seconds)
30
30
  TOKEN_MAX_AGE_SECONDS = 3600
31
31
 
32
32
  # Tool name prefix for Claude Code OAuth compatibility
@@ -94,13 +94,13 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
94
94
  return age
95
95
 
96
96
  # Fall back to calculating from 'exp' claim
97
- # Assume tokens are typically valid for 1 hour
97
+ # Assume tokens are typically valid for TOKEN_MAX_AGE_SECONDS
98
98
  if "exp" in payload:
99
99
  exp = float(payload["exp"])
100
100
  # If exp is in the future, calculate how long until expiry
101
- # and assume the token was issued 1 hour before expiry
101
+ # and assume the token was issued TOKEN_MAX_AGE_SECONDS before expiry
102
102
  time_until_exp = exp - now
103
- # If token has less than 1 hour left, it's "old"
103
+ # If token has less than TOKEN_MAX_AGE_SECONDS left, it's "old"
104
104
  age = TOKEN_MAX_AGE_SECONDS - time_until_exp
105
105
  return max(0, age)
106
106
 
@@ -119,13 +119,13 @@ 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 should be refreshed (within 1 hour of expiry).
122
+ """Check if the token should be refreshed (within the max-age window).
123
123
 
124
124
  Uses two strategies:
125
125
  1. Decode JWT to check token age (if possible)
126
126
  2. Fall back to stored expires_at from token file
127
127
 
128
- Returns True if token expires within TOKEN_MAX_AGE_SECONDS (1 hour).
128
+ Returns True if token expires within TOKEN_MAX_AGE_SECONDS.
129
129
  """
130
130
  token = self._extract_bearer_token(request)
131
131
  if not token:
@@ -169,7 +169,7 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
169
169
  if not tokens:
170
170
  return False
171
171
 
172
- # is_token_expired already uses TOKEN_REFRESH_BUFFER_SECONDS (1 hour)
172
+ # is_token_expired already uses the configured refresh buffer window
173
173
  return is_token_expired(tokens)
174
174
  except Exception as exc:
175
175
  logger.debug("Error checking stored token expiry: %s", exc)
@@ -366,10 +366,10 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
366
366
 
367
367
  # Handle auth errors with token refresh
368
368
  try:
369
- if response.status_code in (400, 401) and not request.extensions.get(
369
+ if response.status_code in (400, 401, 403) and not request.extensions.get(
370
370
  "claude_oauth_refresh_attempted"
371
371
  ):
372
- is_auth_error = response.status_code == 401
372
+ is_auth_error = response.status_code in (401, 403)
373
373
 
374
374
  if response.status_code == 400:
375
375
  is_auth_error = self._is_cloudflare_html_error(response)
@@ -52,7 +52,7 @@ The plugin ships with sensible defaults in `config.py`:
52
52
  ```python
53
53
  CLAUDE_CODE_OAUTH_CONFIG = {
54
54
  "auth_url": "https://claude.ai/oauth/authorize",
55
- "token_url": "https://claude.ai/api/oauth/token",
55
+ "token_url": "https://console.anthropic.com/v1/oauth/token",
56
56
  "api_base_url": "https://api.anthropic.com",
57
57
  "client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
58
58
  "scope": "org:create_api_key user:profile user:inference",
@@ -37,7 +37,7 @@ Anthropic exposes a shared **public client** (`claude-cli`) for command-line too
37
37
  ```python
38
38
  CLAUDE_CODE_OAUTH_CONFIG = {
39
39
  "auth_url": "https://claude.ai/oauth/authorize",
40
- "token_url": "https://claude.ai/api/oauth/token",
40
+ "token_url": "https://console.anthropic.com/v1/oauth/token",
41
41
  "api_base_url": "https://api.anthropic.com",
42
42
  "client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
43
43
  "scope": "org:create_api_key user:profile user:inference",
@@ -21,10 +21,10 @@ from .config import (
21
21
  get_token_storage_path,
22
22
  )
23
23
 
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
24
+ # Proactive refresh buffer default (seconds). Actual buffer is dynamic
25
+ # based on expires_in to avoid overly aggressive refreshes.
26
+ TOKEN_REFRESH_BUFFER_SECONDS = 300
27
+ MIN_REFRESH_BUFFER_SECONDS = 30
28
28
 
29
29
  logger = logging.getLogger(__name__)
30
30
 
@@ -146,15 +146,40 @@ def _calculate_expires_at(expires_in: Optional[float]) -> Optional[float]:
146
146
  return None
147
147
 
148
148
 
149
- def is_token_expired(tokens: Dict[str, Any]) -> bool:
149
+ def _calculate_refresh_buffer(expires_in: Optional[float]) -> float:
150
+ default_buffer = float(TOKEN_REFRESH_BUFFER_SECONDS)
151
+ if expires_in is None:
152
+ return default_buffer
153
+ try:
154
+ expires_value = float(expires_in)
155
+ except (TypeError, ValueError):
156
+ return default_buffer
157
+ return min(default_buffer, max(MIN_REFRESH_BUFFER_SECONDS, expires_value * 0.1))
158
+
159
+
160
+ def _get_expires_at_value(tokens: Dict[str, Any]) -> Optional[float]:
150
161
  expires_at = tokens.get("expires_at")
151
162
  if expires_at is None:
152
- return False
163
+ return None
153
164
  try:
154
- expires_at_value = float(expires_at)
165
+ return float(expires_at)
155
166
  except (TypeError, ValueError):
167
+ return None
168
+
169
+
170
+ def _is_token_actually_expired(tokens: Dict[str, Any]) -> bool:
171
+ expires_at_value = _get_expires_at_value(tokens)
172
+ if expires_at_value is None:
173
+ return False
174
+ return time.time() >= expires_at_value
175
+
176
+
177
+ def is_token_expired(tokens: Dict[str, Any]) -> bool:
178
+ expires_at_value = _get_expires_at_value(tokens)
179
+ if expires_at_value is None:
156
180
  return False
157
- return time.time() >= expires_at_value - TOKEN_REFRESH_BUFFER_SECONDS
181
+ buffer_seconds = _calculate_refresh_buffer(tokens.get("expires_in"))
182
+ return time.time() >= expires_at_value - buffer_seconds
158
183
 
159
184
 
160
185
  def update_claude_code_model_tokens(access_token: str) -> bool:
@@ -216,11 +241,12 @@ def refresh_access_token(force: bool = False) -> Optional[str]:
216
241
  new_tokens = response.json()
217
242
  tokens["access_token"] = new_tokens.get("access_token")
218
243
  tokens["refresh_token"] = new_tokens.get("refresh_token", refresh_token)
219
- if "expires_in" in new_tokens:
220
- tokens["expires_in"] = new_tokens["expires_in"]
221
- tokens["expires_at"] = _calculate_expires_at(
222
- new_tokens.get("expires_in")
223
- )
244
+ expires_in_value = new_tokens.get("expires_in")
245
+ if expires_in_value is None:
246
+ expires_in_value = tokens.get("expires_in")
247
+ if expires_in_value is not None:
248
+ tokens["expires_in"] = expires_in_value
249
+ tokens["expires_at"] = _calculate_expires_at(expires_in_value)
224
250
  if save_tokens(tokens):
225
251
  update_claude_code_model_tokens(tokens["access_token"])
226
252
  return tokens["access_token"]
@@ -249,6 +275,11 @@ def get_valid_access_token() -> Optional[str]:
249
275
  refreshed = refresh_access_token()
250
276
  if refreshed:
251
277
  return refreshed
278
+ if not _is_token_actually_expired(tokens):
279
+ logger.warning(
280
+ "Claude Code token refresh failed; using existing access token until expiry"
281
+ )
282
+ return access_token
252
283
  logger.warning("Claude Code token refresh failed")
253
284
  return None
254
285
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.372
3
+ Version: 0.0.373
4
4
  Summary: Code generation agent
5
5
  Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
6
6
  Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
@@ -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=upMuAfOhMB7SEpVw4CU4GjgaeZ8X65ri3yNM-dnlmYA,12308
5
- code_puppy/claude_cache_client.py,sha256=1-rIDtZBJ_aiAZSprdsq0ty2urftu6F0uzJDn_OE41Q,23966
5
+ code_puppy/claude_cache_client.py,sha256=GtwYrxcTe0pE-JGtl1ysR2qskfeE73_x4w7q_u-kR1k,24026
6
6
  code_puppy/cli_runner.py,sha256=w5CLKgQYYaT7My3Cga2StXYol-u6DBxNzzUuhhsfhsA,34952
7
7
  code_puppy/config.py,sha256=aKWADF6PdHnr9_0ZVZHwBh5NH9uSAx1lmIiycfaYEF8,54737
8
8
  code_puppy/error_logging.py,sha256=a80OILCUtJhexI6a9GM-r5LqIdjvSRzggfgPp2jv1X0,3297
@@ -165,13 +165,13 @@ code_puppy/plugins/chatgpt_oauth/oauth_flow.py,sha256=i-CP2gpzEBT3ogUt-oTMexiP2o
165
165
  code_puppy/plugins/chatgpt_oauth/register_callbacks.py,sha256=KNi5-R0EXtkBm3p55ttAxuA_ApaOs_tsGDnPt-5vgGA,2998
166
166
  code_puppy/plugins/chatgpt_oauth/test_plugin.py,sha256=oHX7Eb_Hb4rgRpOWdhtFp8Jj6_FDuvXQITRPiNy4tRo,9622
167
167
  code_puppy/plugins/chatgpt_oauth/utils.py,sha256=fzpsCQOv0kqPWmG5vNEV_GLSUrMQh8cF7tdIjSOt1Dc,16504
168
- code_puppy/plugins/claude_code_oauth/README.md,sha256=76nHhMlhk61DZa5g0Q2fc0AtpplLmpbwuWFZt7PHH5g,5458
169
- code_puppy/plugins/claude_code_oauth/SETUP.md,sha256=lnGzofPLogBy3oPPFLv5_cZ7vjg_GYrIyYnF-EoTJKg,3278
168
+ code_puppy/plugins/claude_code_oauth/README.md,sha256=fOSDDzCdm2JCKjU5J82IRHIAhxYxl8_UmHo7uH4AbFo,5469
169
+ code_puppy/plugins/claude_code_oauth/SETUP.md,sha256=DCNLkSU9nf86S1rsrIg8HBe87NZrF8YND8P4ettWeEM,3289
170
170
  code_puppy/plugins/claude_code_oauth/__init__.py,sha256=mCcOU-wM7LNCDjr-w-WLPzom8nTF1UNt4nqxGE6Rt0k,187
171
171
  code_puppy/plugins/claude_code_oauth/config.py,sha256=DjGySCkvjSGZds6DYErLMAi3TItt8iSLGvyJN98nSEM,2013
172
172
  code_puppy/plugins/claude_code_oauth/register_callbacks.py,sha256=ZnLQfwssbQXdCpnMGHjzEIzXTLc_OZ1UGS4npU5k5m8,9168
173
173
  code_puppy/plugins/claude_code_oauth/test_plugin.py,sha256=yQy4EeZl4bjrcog1d8BjknoDTRK75mRXXvkSQJYSSEM,9286
174
- code_puppy/plugins/claude_code_oauth/utils.py,sha256=Ltk_m2efGNQSSvmb1lxgcPp_vcEig8VMydTZffQP-2c,17433
174
+ code_puppy/plugins/claude_code_oauth/utils.py,sha256=2ioGG-4FCh4WdHrN2MJvWKbPWA-YVg_WTEeddc1xv4U,18557
175
175
  code_puppy/plugins/customizable_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
176
176
  code_puppy/plugins/customizable_commands/register_callbacks.py,sha256=zVMfIzr--hVn0IOXxIicbmgj2s-HZUgtrOc0NCDOnDw,5183
177
177
  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.372.data/data/code_puppy/models.json,sha256=jAHRsCl3trysP4vU_k_ltA8GcFU2APd4lxFl8-4Jnvc,3243
212
- code_puppy-0.0.372.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
213
- code_puppy-0.0.372.dist-info/METADATA,sha256=fqWkbQK2MIKm-wet6ooDhPvSdw8mFpzEbvnV2-2vIxg,27604
214
- code_puppy-0.0.372.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
215
- code_puppy-0.0.372.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
216
- code_puppy-0.0.372.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
217
- code_puppy-0.0.372.dist-info/RECORD,,
211
+ code_puppy-0.0.373.data/data/code_puppy/models.json,sha256=jAHRsCl3trysP4vU_k_ltA8GcFU2APd4lxFl8-4Jnvc,3243
212
+ code_puppy-0.0.373.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
213
+ code_puppy-0.0.373.dist-info/METADATA,sha256=bdAEhxmZ0q5kT15tSnZ2yuLdwhZ-a2-UiiQY0NZXN9M,27604
214
+ code_puppy-0.0.373.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
215
+ code_puppy-0.0.373.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
216
+ code_puppy-0.0.373.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
217
+ code_puppy-0.0.373.dist-info/RECORD,,