polyclaude 0.4.2__tar.gz → 0.4.4__tar.gz
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.
- {polyclaude-0.4.2 → polyclaude-0.4.4}/PKG-INFO +4 -1
- {polyclaude-0.4.2 → polyclaude-0.4.4}/README.md +3 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/pyproject.toml +1 -1
- {polyclaude-0.4.2 → polyclaude-0.4.4}/src/polyclaude/__init__.py +1 -1
- {polyclaude-0.4.2 → polyclaude-0.4.4}/src/polyclaude/bridge.py +39 -9
- {polyclaude-0.4.2 → polyclaude-0.4.4}/src/polyclaude/cli.py +5 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/src/polyclaude/providers.py +13 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/.github/workflows/ci.yml +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/.github/workflows/release.yml +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/.gitignore +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/LICENSE +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/docs/prompts.md +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/docs/providers.md +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/polyclaude.png +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/src/polyclaude/__main__.py +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/src/polyclaude/identities/probo.json +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/src/polyclaude/profiles/concise.md +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/src/polyclaude/profiles/datascience.md +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/src/polyclaude/profiles/reviewer.md +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/src/polyclaude/replaces/example.txt +0 -0
- {polyclaude-0.4.2 → polyclaude-0.4.4}/src/polyclaude/reskin.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: polyclaude
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: Use OpenAI, Gemini, and any OpenAI-compatible model inside the Claude Code CLI.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ibedevesh/polyclaude
|
|
6
6
|
Project-URL: Repository, https://github.com/ibedevesh/polyclaude
|
|
@@ -86,6 +86,7 @@ polyclaude --gemini -- -p "explain this repo"
|
|
|
86
86
|
| OpenAI | `--openai` | `OPENAI_API_KEY` | `gpt-4.1` |
|
|
87
87
|
| Moonshot Kimi | `--kimi` (or `--moonshot`) | `MOONSHOT_API_KEY` | `kimi-k3.1-pro` |
|
|
88
88
|
| DeepSeek | `--deepseek` | `DEEPSEEK_API_KEY` | `deepseek-flash` |
|
|
89
|
+
| Z.ai GLM | `--glm` (or `--zai`) | `ZAI_API_KEY` | `glm-5.3` |
|
|
89
90
|
| Groq | `--groq` | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
|
90
91
|
| OpenRouter | `--openrouter` | `OPENROUTER_API_KEY` | `anthropic/claude-3.5-sonnet` |
|
|
91
92
|
| Ollama (local) | `--ollama` | — | `qwen2.5-coder` |
|
|
@@ -111,6 +112,8 @@ calling and reasoning keep working:
|
|
|
111
112
|
`kimi-k2.6`) work via `--model`.
|
|
112
113
|
- Reasoning models reject non-default sampling params — dropped automatically.
|
|
113
114
|
- Turns that hit the output-token cap are transparently continued.
|
|
115
|
+
- A deep-thinking turn on a long context can take minutes, so the bridge waits
|
|
116
|
+
up to 600s for the backend. Raise or lower it with `--timeout SECONDS`.
|
|
114
117
|
|
|
115
118
|
Run `polyclaude --list` to see everything.
|
|
116
119
|
|
|
@@ -65,6 +65,7 @@ polyclaude --gemini -- -p "explain this repo"
|
|
|
65
65
|
| OpenAI | `--openai` | `OPENAI_API_KEY` | `gpt-4.1` |
|
|
66
66
|
| Moonshot Kimi | `--kimi` (or `--moonshot`) | `MOONSHOT_API_KEY` | `kimi-k3.1-pro` |
|
|
67
67
|
| DeepSeek | `--deepseek` | `DEEPSEEK_API_KEY` | `deepseek-flash` |
|
|
68
|
+
| Z.ai GLM | `--glm` (or `--zai`) | `ZAI_API_KEY` | `glm-5.3` |
|
|
68
69
|
| Groq | `--groq` | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
|
69
70
|
| OpenRouter | `--openrouter` | `OPENROUTER_API_KEY` | `anthropic/claude-3.5-sonnet` |
|
|
70
71
|
| Ollama (local) | `--ollama` | — | `qwen2.5-coder` |
|
|
@@ -90,6 +91,8 @@ calling and reasoning keep working:
|
|
|
90
91
|
`kimi-k2.6`) work via `--model`.
|
|
91
92
|
- Reasoning models reject non-default sampling params — dropped automatically.
|
|
92
93
|
- Turns that hit the output-token cap are transparently continued.
|
|
94
|
+
- A deep-thinking turn on a long context can take minutes, so the bridge waits
|
|
95
|
+
up to 600s for the backend. Raise or lower it with `--timeout SECONDS`.
|
|
93
96
|
|
|
94
97
|
Run `polyclaude --list` to see everything.
|
|
95
98
|
|
|
@@ -17,6 +17,7 @@ Configured entirely via POLYCLAUDE_* environment variables (set by the CLI):
|
|
|
17
17
|
OpenAI uses it as-is, Kimi maps it to low|high|max
|
|
18
18
|
POLYCLAUDE_AUTOCONTINUE 1 = stitch a turn truncated at the token cap
|
|
19
19
|
POLYCLAUDE_MAXTOK output cap (default 32768)
|
|
20
|
+
POLYCLAUDE_TIMEOUT seconds to wait for a backend turn (default 600)
|
|
20
21
|
POLYCLAUDE_SYSTEM_APPEND_FILE append this file to the main system prompt
|
|
21
22
|
POLYCLAUDE_SYSTEM_REPLACE_FILE replace the main system prompt with this file
|
|
22
23
|
POLYCLAUDE_LOG optional path to a wire log
|
|
@@ -37,6 +38,10 @@ REASONING = os.environ.get("POLYCLAUDE_REASONING", "high")
|
|
|
37
38
|
AUTOCONT = os.environ.get("POLYCLAUDE_AUTOCONTINUE", "1") == "1"
|
|
38
39
|
MAX_TOK = int(os.environ.get("POLYCLAUDE_MAXTOK", "32768"))
|
|
39
40
|
MAXCONT = int(os.environ.get("POLYCLAUDE_MAXCONT", "4"))
|
|
41
|
+
# How long to wait for the backend to finish a turn. Thinking models (Kimi at
|
|
42
|
+
# reasoning_effort=max, GPT-5.x, Gemini 3.x) routinely need minutes on a long
|
|
43
|
+
# context, and the old hard-coded 120s cut them off mid-answer.
|
|
44
|
+
TIMEOUT = int(os.environ.get("POLYCLAUDE_TIMEOUT", "600"))
|
|
40
45
|
LOG = os.environ.get("POLYCLAUDE_LOG", "")
|
|
41
46
|
|
|
42
47
|
# Moonshot (Kimi) is OpenAI-compatible but has three quirks:
|
|
@@ -51,6 +56,15 @@ KIMI = "moonshot.ai" in BASE or "kimi.ai" in BASE
|
|
|
51
56
|
# polyclaude's low|medium|high -> Kimi's low|high|max (it has no "medium")
|
|
52
57
|
_KIMI_EFFORT = {"low": "low", "medium": "high", "high": "max"}
|
|
53
58
|
|
|
59
|
+
# Z.ai (GLM) is OpenAI-compatible but, like Kimi, reads `max_tokens` rather than
|
|
60
|
+
# `max_completion_tokens`. GLM-5.3 always thinks (thinking.type can only be
|
|
61
|
+
# "enabled") and exposes the same low|high|max effort ladder as Kimi — z.ai
|
|
62
|
+
# recommends "max" for coding. Sampling is left alone for the thinking models
|
|
63
|
+
# for the same reason as Kimi: the docs warn against pinning temperature and
|
|
64
|
+
# top_p together, and Claude Code sends both.
|
|
65
|
+
GLM = "api.z.ai" in BASE or "bigmodel.cn" in BASE
|
|
66
|
+
_GLM_EFFORT = _KIMI_EFFORT
|
|
67
|
+
|
|
54
68
|
# --- identity scrub / inspect -------------------------------------------------
|
|
55
69
|
# POLYCLAUDE_SCRUB=1 enable rewriting of the outgoing payload
|
|
56
70
|
# POLYCLAUDE_IDENTITY_MAP "old=new,old2=new2" literal replacements, applied
|
|
@@ -349,9 +363,9 @@ def _to_openai(body):
|
|
|
349
363
|
tgt = _pick_model(body.get("model", ""))
|
|
350
364
|
cap = min(int(body.get("max_tokens", MAX_TOK)), MAX_TOK)
|
|
351
365
|
out = {"model": tgt, "messages": msgs}
|
|
352
|
-
# Kimi reads max_tokens (and bills reasoning tokens against it)
|
|
353
|
-
# OpenAI family wants max_completion_tokens.
|
|
354
|
-
out["max_tokens" if KIMI else "max_completion_tokens"] = cap
|
|
366
|
+
# Kimi reads max_tokens (and bills reasoning tokens against it), and so does
|
|
367
|
+
# z.ai; the OpenAI family wants max_completion_tokens.
|
|
368
|
+
out["max_tokens" if (KIMI or GLM) else "max_completion_tokens"] = cap
|
|
355
369
|
# reasoning models (and Kimi, whose sampling is fixed server-side) reject
|
|
356
370
|
# explicit temperature / top_p
|
|
357
371
|
reasoning = KIMI or tgt.startswith(("gpt-5", "gpt-6", "o1", "o3", "o4"))
|
|
@@ -359,6 +373,11 @@ def _to_openai(body):
|
|
|
359
373
|
# K3.1 Pro always thinks; only the depth is tunable. Older Kimi models
|
|
360
374
|
# don't take the field at all.
|
|
361
375
|
out["reasoning_effort"] = _KIMI_EFFORT.get(REASONING, "max")
|
|
376
|
+
if GLM and tgt.startswith("glm-5"):
|
|
377
|
+
# GLM-5.x thinks by default; only the depth is tunable. The -flash tiers
|
|
378
|
+
# accept the field too, so key off the family rather than the exact id.
|
|
379
|
+
out["reasoning_effort"] = _GLM_EFFORT.get(REASONING, "max")
|
|
380
|
+
reasoning = True
|
|
362
381
|
if not reasoning:
|
|
363
382
|
if body.get("temperature") is not None:
|
|
364
383
|
out["temperature"] = body["temperature"]
|
|
@@ -466,7 +485,8 @@ def _use_responses(tgt):
|
|
|
466
485
|
# ---------------------------------------------------------------------------
|
|
467
486
|
# HTTP to the backend
|
|
468
487
|
# ---------------------------------------------------------------------------
|
|
469
|
-
def _post(path, payload, timeout):
|
|
488
|
+
def _post(path, payload, timeout=None):
|
|
489
|
+
timeout = TIMEOUT if timeout is None else timeout
|
|
470
490
|
req = urllib.request.Request(
|
|
471
491
|
f"{BASE}/{path}", data=json.dumps(payload).encode(),
|
|
472
492
|
headers={"Authorization": f"Bearer {API_KEY}",
|
|
@@ -479,7 +499,7 @@ def _post(path, payload, timeout):
|
|
|
479
499
|
|
|
480
500
|
|
|
481
501
|
def _chat(payload):
|
|
482
|
-
oai = _post("chat/completions", payload
|
|
502
|
+
oai = _post("chat/completions", payload)
|
|
483
503
|
if not AUTOCONT:
|
|
484
504
|
return oai
|
|
485
505
|
for _ in range(MAXCONT):
|
|
@@ -493,7 +513,7 @@ def _chat(payload):
|
|
|
493
513
|
half["reasoning_content"] = msg["reasoning_content"]
|
|
494
514
|
cont["messages"] = payload["messages"] + [
|
|
495
515
|
half, {"role": "user", "content": "continue"}]
|
|
496
|
-
nxt = _post("chat/completions", cont
|
|
516
|
+
nxt = _post("chat/completions", cont)
|
|
497
517
|
nmsg = (nxt.get("choices") or [{}])[0].get("message", {})
|
|
498
518
|
oai["choices"][0]["message"]["content"] = \
|
|
499
519
|
(msg.get("content") or "") + (nmsg.get("content") or "")
|
|
@@ -803,7 +823,7 @@ class Bridge:
|
|
|
803
823
|
f"msgs={len(body.get('messages', []))} ===")
|
|
804
824
|
try:
|
|
805
825
|
if _use_responses(tgt):
|
|
806
|
-
oai = _responses_to_oai(_post("responses", _to_responses(body)
|
|
826
|
+
oai = _responses_to_oai(_post("responses", _to_responses(body)))
|
|
807
827
|
_log(f">>> via /responses (reasoning={REASONING})")
|
|
808
828
|
else:
|
|
809
829
|
oai = _chat(_to_openai(body))
|
|
@@ -816,8 +836,18 @@ class Bridge:
|
|
|
816
836
|
_log(f"!!! backend {e.code}: {detail}")
|
|
817
837
|
sse = _error_sse(model, f"backend {e.code}: {detail}")
|
|
818
838
|
except Exception as e:
|
|
819
|
-
|
|
820
|
-
|
|
839
|
+
# a read timeout surfaces as TimeoutError; a connect timeout comes
|
|
840
|
+
# wrapped in URLError(reason=TimeoutError)
|
|
841
|
+
if isinstance(e, TimeoutError) or isinstance(
|
|
842
|
+
getattr(e, "reason", None), TimeoutError):
|
|
843
|
+
_log(f"!!! timeout after {TIMEOUT}s: {e!r}")
|
|
844
|
+
sse = _error_sse(model, (
|
|
845
|
+
f"the backend did not answer within {TIMEOUT}s. Thinking "
|
|
846
|
+
f"models can need longer on a big context — raise it with "
|
|
847
|
+
f"--timeout SECONDS (or POLYCLAUDE_TIMEOUT)."))
|
|
848
|
+
else:
|
|
849
|
+
_log(f"!!! {e!r}")
|
|
850
|
+
sse = _error_sse(model, repr(e))
|
|
821
851
|
flow.response = http.Response.make(
|
|
822
852
|
200, sse, {"content-type": "text/event-stream; charset=utf-8"})
|
|
823
853
|
|
|
@@ -199,6 +199,10 @@ def main(argv=None):
|
|
|
199
199
|
help="don't append the '⚠ model tried to STOP' banner")
|
|
200
200
|
ap.add_argument("--reasoning", choices=["low", "medium", "high"],
|
|
201
201
|
help="OpenAI reasoning depth (default high)")
|
|
202
|
+
ap.add_argument("--timeout", type=int, default=600, metavar="SECONDS",
|
|
203
|
+
help="how long to wait for the backend to finish a turn "
|
|
204
|
+
"(default 600; raise it for deep-thinking models on a "
|
|
205
|
+
"big context)")
|
|
202
206
|
ap.add_argument("--hue", type=float, default=110.0,
|
|
203
207
|
help="accent hue for the branding, degrees (default 110 = green)")
|
|
204
208
|
ap.add_argument("--resume", nargs="?", const="", default=None, metavar="SESSION",
|
|
@@ -276,6 +280,7 @@ def main(argv=None):
|
|
|
276
280
|
"POLYCLAUDE_SMALL": prov["small"],
|
|
277
281
|
"POLYCLAUDE_KEY": key or "none",
|
|
278
282
|
"POLYCLAUDE_REASONING": args.reasoning or "high",
|
|
283
|
+
"POLYCLAUDE_TIMEOUT": str(args.timeout),
|
|
279
284
|
"POLYCLAUDE_AUTOCONTINUE": "1",
|
|
280
285
|
"POLYCLAUDE_LOG": log,
|
|
281
286
|
})
|
|
@@ -64,6 +64,19 @@ PROVIDERS = {
|
|
|
64
64
|
"key_env": ["DEEPSEEK_API_KEY"],
|
|
65
65
|
"help": "Get a key at https://platform.deepseek.com/api_keys",
|
|
66
66
|
},
|
|
67
|
+
"glm": {
|
|
68
|
+
# Z.ai (Zhipu). glm-5.3 is the flagship (1M context, 128K output,
|
|
69
|
+
# always-thinking, text-only). glm-5.3-flash is the cheap tier and takes
|
|
70
|
+
# images, so it carries the side-call slot.
|
|
71
|
+
# GLM Coding Plan subscribers have a SEPARATE key and endpoint:
|
|
72
|
+
# POLYCLAUDE_BASE=https://api.z.ai/api/coding/paas/v4
|
|
73
|
+
"base": "https://api.z.ai/api/paas/v4",
|
|
74
|
+
"model": "glm-5.3",
|
|
75
|
+
"small": "glm-5.3-flash",
|
|
76
|
+
"key_env": ["ZAI_API_KEY", "GLM_API_KEY", "ZHIPUAI_API_KEY"],
|
|
77
|
+
"aliases": ["zai"],
|
|
78
|
+
"help": "Get a key at https://z.ai/manage-apikey/apikey-list",
|
|
79
|
+
},
|
|
67
80
|
"ollama": {
|
|
68
81
|
"base": "http://127.0.0.1:11434/v1",
|
|
69
82
|
"model": "qwen2.5-coder",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|