code-puppy 0.0.325__py3-none-any.whl → 0.0.341__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/agents/base_agent.py +110 -124
- code_puppy/claude_cache_client.py +208 -2
- code_puppy/cli_runner.py +152 -32
- code_puppy/command_line/add_model_menu.py +4 -0
- code_puppy/command_line/autosave_menu.py +23 -24
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +5 -0
- code_puppy/command_line/config_commands.py +24 -1
- code_puppy/command_line/core_commands.py +85 -0
- code_puppy/command_line/diff_menu.py +5 -0
- code_puppy/command_line/mcp/custom_server_form.py +4 -0
- code_puppy/command_line/mcp/install_menu.py +5 -1
- code_puppy/command_line/model_settings_menu.py +5 -0
- code_puppy/command_line/motd.py +13 -7
- code_puppy/command_line/onboarding_slides.py +180 -0
- code_puppy/command_line/onboarding_wizard.py +340 -0
- code_puppy/command_line/prompt_toolkit_completion.py +118 -0
- code_puppy/config.py +3 -2
- code_puppy/http_utils.py +201 -279
- code_puppy/keymap.py +10 -8
- code_puppy/mcp_/managed_server.py +7 -11
- code_puppy/messaging/messages.py +3 -0
- code_puppy/messaging/rich_renderer.py +114 -22
- code_puppy/model_factory.py +102 -15
- code_puppy/models.json +2 -2
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +668 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +136 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
- code_puppy/plugins/antigravity_oauth/storage.py +271 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +664 -0
- code_puppy/plugins/antigravity_oauth/utils.py +169 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +2 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +2 -0
- code_puppy/plugins/claude_code_oauth/utils.py +126 -7
- code_puppy/reopenable_async_client.py +8 -8
- code_puppy/terminal_utils.py +295 -3
- code_puppy/tools/command_runner.py +43 -54
- code_puppy/tools/common.py +3 -9
- code_puppy/uvx_detection.py +242 -0
- {code_puppy-0.0.325.data → code_puppy-0.0.341.data}/data/code_puppy/models.json +2 -2
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/METADATA +26 -49
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/RECORD +52 -36
- {code_puppy-0.0.325.data → code_puppy-0.0.341.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/licenses/LICENSE +0 -0
code_puppy/keymap.py
CHANGED
|
@@ -55,11 +55,19 @@ class KeymapError(Exception):
|
|
|
55
55
|
def get_cancel_agent_key() -> str:
|
|
56
56
|
"""Get the configured cancel agent key from config.
|
|
57
57
|
|
|
58
|
+
On Windows when launched via uvx, this automatically returns "ctrl+k"
|
|
59
|
+
to work around uvx capturing Ctrl+C before it reaches Python.
|
|
60
|
+
|
|
58
61
|
Returns:
|
|
59
62
|
The key name (e.g., "ctrl+c", "ctrl+k") from config,
|
|
60
63
|
or the default if not configured.
|
|
61
64
|
"""
|
|
62
65
|
from code_puppy.config import get_value
|
|
66
|
+
from code_puppy.uvx_detection import should_use_alternate_cancel_key
|
|
67
|
+
|
|
68
|
+
# On Windows + uvx, force ctrl+k to bypass uvx's SIGINT capture
|
|
69
|
+
if should_use_alternate_cancel_key():
|
|
70
|
+
return "ctrl+k"
|
|
63
71
|
|
|
64
72
|
key = get_value("cancel_agent_key")
|
|
65
73
|
if key is None or key.strip() == "":
|
|
@@ -86,15 +94,9 @@ def cancel_agent_uses_signal() -> bool:
|
|
|
86
94
|
"""Check if the cancel agent key uses SIGINT (Ctrl+C).
|
|
87
95
|
|
|
88
96
|
Returns:
|
|
89
|
-
True if the cancel key is ctrl+c
|
|
90
|
-
|
|
97
|
+
True if the cancel key is ctrl+c (uses SIGINT handler),
|
|
98
|
+
False if it uses keyboard listener approach.
|
|
91
99
|
"""
|
|
92
|
-
import sys
|
|
93
|
-
|
|
94
|
-
# On Windows, always use keyboard listener - SIGINT is unreliable
|
|
95
|
-
if sys.platform == "win32":
|
|
96
|
-
return False
|
|
97
|
-
|
|
98
100
|
return get_cancel_agent_key() == "ctrl+c"
|
|
99
101
|
|
|
100
102
|
|
|
@@ -222,18 +222,14 @@ class ManagedMCPServer:
|
|
|
222
222
|
http_kwargs["timeout"] = config["timeout"]
|
|
223
223
|
if "read_timeout" in config:
|
|
224
224
|
http_kwargs["read_timeout"] = config["read_timeout"]
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if isinstance(v, str):
|
|
232
|
-
resolved_headers[k] = os.path.expandvars(v)
|
|
233
|
-
else:
|
|
234
|
-
resolved_headers[k] = v
|
|
235
|
-
http_kwargs["headers"] = resolved_headers
|
|
225
|
+
|
|
226
|
+
# Handle http_client vs headers (mutually exclusive)
|
|
227
|
+
if "http_client" in config:
|
|
228
|
+
# Use provided http_client
|
|
229
|
+
http_kwargs["http_client"] = config["http_client"]
|
|
230
|
+
elif config.get("headers"):
|
|
236
231
|
# Create HTTP client if headers are provided but no client specified
|
|
232
|
+
http_kwargs["http_client"] = self._get_http_client()
|
|
237
233
|
|
|
238
234
|
self._pydantic_server = MCPServerStreamableHTTP(
|
|
239
235
|
**http_kwargs, process_tool_call=process_tool_call
|
code_puppy/messaging/messages.py
CHANGED
|
@@ -209,6 +209,9 @@ class ShellStartMessage(BaseMessage):
|
|
|
209
209
|
default=None, description="Working directory for the command"
|
|
210
210
|
)
|
|
211
211
|
timeout: int = Field(default=60, description="Timeout in seconds")
|
|
212
|
+
background: bool = Field(
|
|
213
|
+
default=False, description="Whether command runs in background mode"
|
|
214
|
+
)
|
|
212
215
|
|
|
213
216
|
|
|
214
217
|
class ShellLineMessage(BaseMessage):
|
|
@@ -348,7 +348,17 @@ class RichConsoleRenderer:
|
|
|
348
348
|
# =========================================================================
|
|
349
349
|
|
|
350
350
|
def _render_file_listing(self, msg: FileListingMessage) -> None:
|
|
351
|
-
"""Render a directory listing
|
|
351
|
+
"""Render a compact directory listing with directory summaries.
|
|
352
|
+
|
|
353
|
+
Instead of listing every file, we group by directory and show:
|
|
354
|
+
- Directory name
|
|
355
|
+
- Number of files
|
|
356
|
+
- Total size
|
|
357
|
+
- Number of subdirectories
|
|
358
|
+
"""
|
|
359
|
+
import os
|
|
360
|
+
from collections import defaultdict
|
|
361
|
+
|
|
352
362
|
# Header on single line
|
|
353
363
|
rec_flag = f"(recursive={msg.recursive})"
|
|
354
364
|
banner = self._format_banner("directory_listing", "DIRECTORY LISTING")
|
|
@@ -357,32 +367,104 @@ class RichConsoleRenderer:
|
|
|
357
367
|
f"📂 [bold cyan]{msg.directory}[/bold cyan] [dim]{rec_flag}[/dim]\n"
|
|
358
368
|
)
|
|
359
369
|
|
|
360
|
-
#
|
|
361
|
-
|
|
362
|
-
|
|
370
|
+
# Build a tree structure: {parent_path: {files: [], dirs: set(), size: int}}
|
|
371
|
+
# Each key is a directory path, value contains direct children stats
|
|
372
|
+
dir_stats: dict = defaultdict(
|
|
373
|
+
lambda: {"files": [], "subdirs": set(), "total_size": 0}
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Root directory is represented as ""
|
|
377
|
+
root_key = ""
|
|
363
378
|
|
|
364
|
-
# Build tree structure from flat list
|
|
365
379
|
for entry in msg.files:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
for d in range(entry.depth + 1):
|
|
369
|
-
if d == entry.depth:
|
|
370
|
-
prefix += "└── "
|
|
371
|
-
else:
|
|
372
|
-
prefix += " "
|
|
380
|
+
path = entry.path
|
|
381
|
+
parent = os.path.dirname(path) if os.path.dirname(path) else root_key
|
|
373
382
|
|
|
374
383
|
if entry.type == "dir":
|
|
375
|
-
|
|
384
|
+
# Register this dir as a subdir of its parent
|
|
385
|
+
dir_stats[parent]["subdirs"].add(path)
|
|
386
|
+
# Ensure the dir itself exists in stats (even if empty)
|
|
387
|
+
_ = dir_stats[path]
|
|
376
388
|
else:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
389
|
+
# It's a file - add to parent's stats
|
|
390
|
+
dir_stats[parent]["files"].append(entry)
|
|
391
|
+
dir_stats[parent]["total_size"] += entry.size
|
|
392
|
+
|
|
393
|
+
def render_dir_tree(dir_path: str, depth: int = 0) -> None:
|
|
394
|
+
"""Recursively render directory with compact summary."""
|
|
395
|
+
stats = dir_stats.get(
|
|
396
|
+
dir_path, {"files": [], "subdirs": set(), "total_size": 0}
|
|
397
|
+
)
|
|
398
|
+
files = stats["files"]
|
|
399
|
+
subdirs = sorted(stats["subdirs"])
|
|
400
|
+
|
|
401
|
+
# Calculate total size including subdirectories (recursive)
|
|
402
|
+
def get_recursive_size(d: str) -> int:
|
|
403
|
+
s = dir_stats.get(d, {"files": [], "subdirs": set(), "total_size": 0})
|
|
404
|
+
size = s["total_size"]
|
|
405
|
+
for sub in s["subdirs"]:
|
|
406
|
+
size += get_recursive_size(sub)
|
|
407
|
+
return size
|
|
408
|
+
|
|
409
|
+
def get_recursive_file_count(d: str) -> int:
|
|
410
|
+
s = dir_stats.get(d, {"files": [], "subdirs": set(), "total_size": 0})
|
|
411
|
+
count = len(s["files"])
|
|
412
|
+
for sub in s["subdirs"]:
|
|
413
|
+
count += get_recursive_file_count(sub)
|
|
414
|
+
return count
|
|
415
|
+
|
|
416
|
+
indent = " " * depth
|
|
417
|
+
|
|
418
|
+
# For root level, just show contents
|
|
419
|
+
if dir_path == root_key:
|
|
420
|
+
# Show files at root level (depth 0)
|
|
421
|
+
for f in sorted(files, key=lambda x: x.path):
|
|
422
|
+
icon = self._get_file_icon(f.path)
|
|
423
|
+
name = os.path.basename(f.path)
|
|
424
|
+
size_str = (
|
|
425
|
+
f" [dim]({self._format_size(f.size)})[/dim]"
|
|
426
|
+
if f.size > 0
|
|
427
|
+
else ""
|
|
428
|
+
)
|
|
429
|
+
self._console.print(
|
|
430
|
+
f"{indent}{icon} [green]{name}[/green]{size_str}"
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# Show subdirs at root level
|
|
434
|
+
for subdir in subdirs:
|
|
435
|
+
render_dir_tree(subdir, depth)
|
|
436
|
+
else:
|
|
437
|
+
# Show directory with summary
|
|
438
|
+
dir_name = os.path.basename(dir_path)
|
|
439
|
+
rec_size = get_recursive_size(dir_path)
|
|
440
|
+
rec_file_count = get_recursive_file_count(dir_path)
|
|
441
|
+
subdir_count = len(subdirs)
|
|
442
|
+
|
|
443
|
+
# Build summary parts
|
|
444
|
+
parts = []
|
|
445
|
+
if rec_file_count > 0:
|
|
446
|
+
parts.append(
|
|
447
|
+
f"{rec_file_count} file{'s' if rec_file_count != 1 else ''}"
|
|
448
|
+
)
|
|
449
|
+
if subdir_count > 0:
|
|
450
|
+
parts.append(
|
|
451
|
+
f"{subdir_count} subdir{'s' if subdir_count != 1 else ''}"
|
|
452
|
+
)
|
|
453
|
+
if rec_size > 0:
|
|
454
|
+
parts.append(self._format_size(rec_size))
|
|
455
|
+
|
|
456
|
+
summary = f" [dim]({', '.join(parts)})[/dim]" if parts else ""
|
|
382
457
|
self._console.print(
|
|
383
|
-
f"{
|
|
458
|
+
f"{indent}📁 [bold blue]{dir_name}/[/bold blue]{summary}"
|
|
384
459
|
)
|
|
385
460
|
|
|
461
|
+
# Recursively show subdirectories
|
|
462
|
+
for subdir in subdirs:
|
|
463
|
+
render_dir_tree(subdir, depth + 1)
|
|
464
|
+
|
|
465
|
+
# Render the tree starting from root
|
|
466
|
+
render_dir_tree(root_key, 0)
|
|
467
|
+
|
|
386
468
|
# Summary
|
|
387
469
|
self._console.print("\n[bold cyan]Summary:[/bold cyan]")
|
|
388
470
|
self._console.print(
|
|
@@ -538,15 +620,25 @@ class RichConsoleRenderer:
|
|
|
538
620
|
safe_command = escape_rich_markup(msg.command)
|
|
539
621
|
# Header showing command is starting
|
|
540
622
|
banner = self._format_banner("shell_command", "SHELL COMMAND")
|
|
541
|
-
|
|
623
|
+
|
|
624
|
+
# Add background indicator if running in background mode
|
|
625
|
+
if msg.background:
|
|
626
|
+
self._console.print(
|
|
627
|
+
f"\n{banner} 🚀 [dim]$ {safe_command}[/dim] [bold magenta][BACKGROUND 🌙][/bold magenta]"
|
|
628
|
+
)
|
|
629
|
+
else:
|
|
630
|
+
self._console.print(f"\n{banner} 🚀 [dim]$ {safe_command}[/dim]")
|
|
542
631
|
|
|
543
632
|
# Show working directory if specified
|
|
544
633
|
if msg.cwd:
|
|
545
634
|
safe_cwd = escape_rich_markup(msg.cwd)
|
|
546
635
|
self._console.print(f"[dim]📂 Working directory: {safe_cwd}[/dim]")
|
|
547
636
|
|
|
548
|
-
# Show timeout
|
|
549
|
-
|
|
637
|
+
# Show timeout or background status
|
|
638
|
+
if msg.background:
|
|
639
|
+
self._console.print("[dim]⏱ Runs detached (no timeout)[/dim]")
|
|
640
|
+
else:
|
|
641
|
+
self._console.print(f"[dim]⏱ Timeout: {msg.timeout}s[/dim]")
|
|
550
642
|
|
|
551
643
|
def _render_shell_line(self, msg: ShellLineMessage) -> None:
|
|
552
644
|
"""Render shell output line preserving ANSI codes."""
|
code_puppy/model_factory.py
CHANGED
|
@@ -4,7 +4,6 @@ import os
|
|
|
4
4
|
import pathlib
|
|
5
5
|
from typing import Any, Dict
|
|
6
6
|
|
|
7
|
-
import httpx
|
|
8
7
|
from anthropic import AsyncAnthropic
|
|
9
8
|
from openai import AsyncAzureOpenAI
|
|
10
9
|
from pydantic_ai.models.anthropic import AnthropicModel, AnthropicModelSettings
|
|
@@ -212,6 +211,7 @@ class ModelFactory:
|
|
|
212
211
|
|
|
213
212
|
# Import OAuth model file paths from main config
|
|
214
213
|
from code_puppy.config import (
|
|
214
|
+
ANTIGRAVITY_MODELS_FILE,
|
|
215
215
|
CHATGPT_MODELS_FILE,
|
|
216
216
|
CLAUDE_MODELS_FILE,
|
|
217
217
|
GEMINI_MODELS_FILE,
|
|
@@ -223,6 +223,7 @@ class ModelFactory:
|
|
|
223
223
|
(pathlib.Path(CHATGPT_MODELS_FILE), "ChatGPT OAuth models", False),
|
|
224
224
|
(pathlib.Path(CLAUDE_MODELS_FILE), "Claude Code OAuth models", True),
|
|
225
225
|
(pathlib.Path(GEMINI_MODELS_FILE), "Gemini OAuth models", False),
|
|
226
|
+
(pathlib.Path(ANTIGRAVITY_MODELS_FILE), "Antigravity OAuth models", False),
|
|
226
227
|
]
|
|
227
228
|
|
|
228
229
|
for source_path, label, use_filtered in extra_sources:
|
|
@@ -387,6 +388,20 @@ class ModelFactory:
|
|
|
387
388
|
return AnthropicModel(model_name=model_config["name"], provider=provider)
|
|
388
389
|
elif model_type == "claude_code":
|
|
389
390
|
url, headers, verify, api_key = get_custom_config(model_config)
|
|
391
|
+
if model_config.get("oauth_source") == "claude-code-plugin":
|
|
392
|
+
try:
|
|
393
|
+
from code_puppy.plugins.claude_code_oauth.utils import (
|
|
394
|
+
get_valid_access_token,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
refreshed_token = get_valid_access_token()
|
|
398
|
+
if refreshed_token:
|
|
399
|
+
api_key = refreshed_token
|
|
400
|
+
custom_endpoint = model_config.get("custom_endpoint")
|
|
401
|
+
if isinstance(custom_endpoint, dict):
|
|
402
|
+
custom_endpoint["api_key"] = refreshed_token
|
|
403
|
+
except ImportError:
|
|
404
|
+
pass
|
|
390
405
|
if not api_key:
|
|
391
406
|
emit_warning(
|
|
392
407
|
f"API key is not set for Claude Code endpoint; skipping model '{model_config.get('name')}'."
|
|
@@ -556,24 +571,94 @@ class ModelFactory:
|
|
|
556
571
|
f"API key is not set for custom Gemini endpoint; skipping model '{model_config.get('name')}'."
|
|
557
572
|
)
|
|
558
573
|
return None
|
|
559
|
-
os.environ["GEMINI_API_KEY"] = api_key
|
|
560
574
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
575
|
+
# Check if this is an Antigravity model
|
|
576
|
+
if model_config.get("antigravity"):
|
|
577
|
+
try:
|
|
578
|
+
from code_puppy.plugins.antigravity_oauth.token import (
|
|
579
|
+
is_token_expired,
|
|
580
|
+
refresh_access_token,
|
|
581
|
+
)
|
|
582
|
+
from code_puppy.plugins.antigravity_oauth.transport import (
|
|
583
|
+
create_antigravity_client,
|
|
584
|
+
)
|
|
585
|
+
from code_puppy.plugins.antigravity_oauth.utils import (
|
|
586
|
+
load_stored_tokens,
|
|
587
|
+
save_tokens,
|
|
588
|
+
)
|
|
564
589
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
590
|
+
# Try to import custom model for thinking signatures
|
|
591
|
+
try:
|
|
592
|
+
from code_puppy.plugins.antigravity_oauth.antigravity_model import (
|
|
593
|
+
AntigravityModel,
|
|
594
|
+
)
|
|
595
|
+
except ImportError:
|
|
596
|
+
AntigravityModel = None
|
|
568
597
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
598
|
+
# Get fresh access token (refresh if needed)
|
|
599
|
+
tokens = load_stored_tokens()
|
|
600
|
+
if not tokens:
|
|
601
|
+
emit_warning(
|
|
602
|
+
"Antigravity tokens not found; run /antigravity-auth first."
|
|
603
|
+
)
|
|
604
|
+
return None
|
|
574
605
|
|
|
575
|
-
|
|
576
|
-
|
|
606
|
+
access_token = tokens.get("access_token", "")
|
|
607
|
+
refresh_token = tokens.get("refresh_token", "")
|
|
608
|
+
expires_at = tokens.get("expires_at")
|
|
609
|
+
|
|
610
|
+
# Refresh if expired or about to expire
|
|
611
|
+
if is_token_expired(expires_at):
|
|
612
|
+
new_tokens = refresh_access_token(refresh_token)
|
|
613
|
+
if new_tokens:
|
|
614
|
+
access_token = new_tokens.access_token
|
|
615
|
+
tokens["access_token"] = new_tokens.access_token
|
|
616
|
+
tokens["refresh_token"] = new_tokens.refresh_token
|
|
617
|
+
tokens["expires_at"] = new_tokens.expires_at
|
|
618
|
+
save_tokens(tokens)
|
|
619
|
+
else:
|
|
620
|
+
emit_warning(
|
|
621
|
+
"Failed to refresh Antigravity token; run /antigravity-auth again."
|
|
622
|
+
)
|
|
623
|
+
return None
|
|
624
|
+
|
|
625
|
+
project_id = tokens.get(
|
|
626
|
+
"project_id", model_config.get("project_id", "")
|
|
627
|
+
)
|
|
628
|
+
client = create_antigravity_client(
|
|
629
|
+
access_token=access_token,
|
|
630
|
+
project_id=project_id,
|
|
631
|
+
model_name=model_config["name"],
|
|
632
|
+
base_url=url,
|
|
633
|
+
headers=headers,
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
provider = GoogleProvider(
|
|
637
|
+
api_key=api_key, base_url=url, http_client=client
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
# Use custom model if available to preserve thinking signatures
|
|
641
|
+
if AntigravityModel:
|
|
642
|
+
model = AntigravityModel(
|
|
643
|
+
model_name=model_config["name"], provider=provider
|
|
644
|
+
)
|
|
645
|
+
else:
|
|
646
|
+
model = GoogleModel(
|
|
647
|
+
model_name=model_config["name"], provider=provider
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
return model
|
|
651
|
+
|
|
652
|
+
except ImportError:
|
|
653
|
+
emit_warning(
|
|
654
|
+
f"Antigravity transport not available; skipping model '{model_config.get('name')}'."
|
|
655
|
+
)
|
|
656
|
+
return None
|
|
657
|
+
else:
|
|
658
|
+
client = create_async_client(headers=headers, verify=verify)
|
|
659
|
+
|
|
660
|
+
provider = GoogleProvider(api_key=api_key, base_url=url, http_client=client)
|
|
661
|
+
model = GoogleModel(model_name=model_config["name"], provider=provider)
|
|
577
662
|
return model
|
|
578
663
|
elif model_type == "cerebras":
|
|
579
664
|
|
|
@@ -592,6 +677,8 @@ class ModelFactory:
|
|
|
592
677
|
f"API key is not set for Cerebras endpoint; skipping model '{model_config.get('name')}'."
|
|
593
678
|
)
|
|
594
679
|
return None
|
|
680
|
+
# Add Cerebras 3rd party integration header
|
|
681
|
+
headers["X-Cerebras-3rd-Party-Integration"] = "code-puppy"
|
|
595
682
|
client = create_async_client(headers=headers, verify=verify)
|
|
596
683
|
provider_args = dict(
|
|
597
684
|
api_key=api_key,
|
code_puppy/models.json
CHANGED
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"supported_settings": ["reasoning_effort", "verbosity"],
|
|
56
56
|
"supports_xhigh_reasoning": true
|
|
57
57
|
},
|
|
58
|
-
"Cerebras-GLM-4.
|
|
58
|
+
"Cerebras-GLM-4.7": {
|
|
59
59
|
"type": "cerebras",
|
|
60
|
-
"name": "zai-glm-4.
|
|
60
|
+
"name": "zai-glm-4.7",
|
|
61
61
|
"custom_endpoint": {
|
|
62
62
|
"url": "https://api.cerebras.ai/v1",
|
|
63
63
|
"api_key": "$CEREBRAS_API_KEY"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Antigravity OAuth Plugin for Code Puppy.
|
|
2
|
+
|
|
3
|
+
Enables authentication with Google/Antigravity APIs to access Gemini and Claude models
|
|
4
|
+
via Google credentials. Supports multi-account load balancing and automatic failover.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .config import ANTIGRAVITY_OAUTH_CONFIG
|
|
8
|
+
from .register_callbacks import * # noqa: F401, F403
|
|
9
|
+
|
|
10
|
+
__all__ = ["ANTIGRAVITY_OAUTH_CONFIG"]
|