claude-glm 1.2.4 → 1.3.1

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.
@@ -6,6 +6,7 @@ import { chatOpenAI } from "./providers/openai.js";
6
6
  import { chatOpenRouter } from "./providers/openrouter.js";
7
7
  import { chatGemini } from "./providers/gemini.js";
8
8
  import { passThrough } from "./providers/anthropic-pass.js";
9
+ import { preprocessImages } from "./vision-preprocess.js";
9
10
  import { config } from "dotenv";
10
11
  import { join } from "path";
11
12
  import { homedir } from "os";
@@ -28,7 +29,7 @@ fastify.get("/healthz", async () => ({
28
29
 
29
30
  // Status endpoint (shows current active provider/model)
30
31
  fastify.get("/_status", async () => {
31
- return active ?? { provider: "glm", model: "glm-4.6" };
32
+ return active ?? { provider: "glm", model: "glm-5" };
32
33
  });
33
34
 
34
35
  // Main messages endpoint - routes by model prefix
@@ -47,7 +48,10 @@ fastify.post("/v1/messages", async (req, res) => {
47
48
  // Warn if using tools with providers that may not support them
48
49
  warnIfTools(body, provider);
49
50
 
50
- active = { provider, model };
51
+ // Don't let internal Claude Code requests (haiku for titles, etc.) override the user's active model
52
+ if (provider !== "anthropic") {
53
+ active = { provider, model };
54
+ }
51
55
 
52
56
  // Validate API keys BEFORE setting headers
53
57
  if (provider === "openai") {
@@ -122,6 +126,8 @@ fastify.post("/v1/messages", async (req, res) => {
122
126
  "GLM_UPSTREAM_URL and ZAI_API_KEY not set in ~/.claude-proxy/.env. Run: ccx --setup"
123
127
  );
124
128
  }
129
+ // Convert images to text descriptions since GLM doesn't support vision
130
+ await preprocessImages(body, process.env.OPENROUTER_API_KEY);
125
131
  // Don't set headers here - passThrough will do it after validation
126
132
  return passThrough({
127
133
  res,
package/adapters/map.ts CHANGED
@@ -57,6 +57,11 @@ export function parseProviderModel(
57
57
  return { provider: "anthropic", model: expanded };
58
58
  }
59
59
 
60
+ // Auto-detect GLM models (start with "glm-") and route to glm
61
+ if (expanded.toLowerCase().startsWith("glm-")) {
62
+ return { provider: "glm", model: expanded };
63
+ }
64
+
60
65
  const sep = expanded.includes(":")
61
66
  ? ":"
62
67
  : expanded.includes("/")
@@ -0,0 +1,106 @@
1
+ // Vision preprocessing: converts image blocks to text descriptions for non-vision models
2
+ import type { AnthropicRequest } from "./types.js";
3
+
4
+ const DEFAULT_VISION_MODEL = "google/gemini-2.5-flash";
5
+ const DESCRIBE_PROMPT =
6
+ "Describe this image in granular detail — layout, text, colors, objects, spatial relationships, any code or data visible.";
7
+
8
+ interface ImageBlock {
9
+ type: "image";
10
+ source: { type: string; media_type: string; data: string; url?: string };
11
+ }
12
+
13
+ function isImageBlock(block: any): block is ImageBlock {
14
+ return block?.type === "image";
15
+ }
16
+
17
+ async function describeImage(
18
+ block: ImageBlock,
19
+ model: string,
20
+ apiKey: string
21
+ ): Promise<string> {
22
+ const content: any[] = [
23
+ { type: "text", text: DESCRIBE_PROMPT },
24
+ ];
25
+
26
+ if (block.source.type === "url" && block.source.url) {
27
+ content.push({
28
+ type: "image_url",
29
+ image_url: { url: block.source.url },
30
+ });
31
+ } else {
32
+ // base64
33
+ content.push({
34
+ type: "image_url",
35
+ image_url: {
36
+ url: `data:${block.source.media_type};base64,${block.source.data}`,
37
+ },
38
+ });
39
+ }
40
+
41
+ const resp = await fetch("https://openrouter.ai/api/v1/chat/completions", {
42
+ method: "POST",
43
+ headers: {
44
+ "Content-Type": "application/json",
45
+ Authorization: `Bearer ${apiKey}`,
46
+ },
47
+ body: JSON.stringify({
48
+ model,
49
+ messages: [{ role: "user", content }],
50
+ max_tokens: 1024,
51
+ }),
52
+ });
53
+
54
+ if (!resp.ok) {
55
+ const text = await resp.text();
56
+ console.error(`[ccx] Vision model error (${resp.status}): ${text}`);
57
+ return "[Image description unavailable]";
58
+ }
59
+
60
+ const json = (await resp.json()) as any;
61
+ return json.choices?.[0]?.message?.content?.trim() ?? "[Image description unavailable]";
62
+ }
63
+
64
+ /**
65
+ * Scans messages for image blocks and replaces them with text descriptions.
66
+ * Mutates body.messages in-place.
67
+ */
68
+ export async function preprocessImages(
69
+ body: AnthropicRequest,
70
+ apiKey?: string
71
+ ): Promise<void> {
72
+ if (!apiKey) {
73
+ console.warn("[ccx] OPENROUTER_API_KEY not set — skipping image preprocessing");
74
+ return;
75
+ }
76
+
77
+ const model = process.env.VISION_MODEL || DEFAULT_VISION_MODEL;
78
+
79
+ // Collect all image blocks with their location
80
+ const tasks: { msg: any; idx: number; block: ImageBlock }[] = [];
81
+ for (const msg of body.messages) {
82
+ if (!Array.isArray(msg.content)) continue;
83
+ for (let i = 0; i < msg.content.length; i++) {
84
+ if (isImageBlock(msg.content[i])) {
85
+ tasks.push({ msg, idx: i, block: msg.content[i] as ImageBlock });
86
+ }
87
+ }
88
+ }
89
+
90
+ if (tasks.length === 0) return;
91
+
92
+ console.log(`[ccx] Describing ${tasks.length} image(s) via ${model}...`);
93
+
94
+ const descriptions = await Promise.all(
95
+ tasks.map((t) => describeImage(t.block, model, apiKey))
96
+ );
97
+
98
+ // Replace image blocks with text descriptions (reverse order to preserve indices)
99
+ for (let i = tasks.length - 1; i >= 0; i--) {
100
+ const { msg, idx } = tasks[i];
101
+ msg.content[idx] = {
102
+ type: "text",
103
+ text: `[Image Description: ${descriptions[i]}]`,
104
+ };
105
+ }
106
+ }
package/bin/ccx CHANGED
@@ -64,6 +64,7 @@ fi
64
64
 
65
65
  export ANTHROPIC_BASE_URL="http://127.0.0.1:${PORT}"
66
66
  export ANTHROPIC_AUTH_TOKEN="${ANTHROPIC_AUTH_TOKEN:-local-proxy-token}"
67
+ export ANTHROPIC_MODEL="${ANTHROPIC_MODEL:-glm-5}"
67
68
 
68
69
  echo "[ccx] Starting Claude Code with multi-provider proxy..."
69
70
  echo "[ccx] Proxy will listen on: ${ANTHROPIC_BASE_URL}"
@@ -106,11 +107,16 @@ echo "🎯 Available model prefixes:"
106
107
  echo " openai:<model> - OpenAI models (gpt-4o, gpt-4o-mini, etc.)"
107
108
  echo " openrouter:<model> - OpenRouter models"
108
109
  echo " gemini:<model> - Google Gemini models"
109
- echo " glm:<model> - Z.AI GLM models (glm-4.6, glm-4.5, etc.)"
110
+ echo " glm:<model> - Z.AI GLM models (glm-5, glm-4.7, glm-4.5, etc.)"
110
111
  echo " anthropic:<model> - Anthropic Claude models"
111
112
  echo ""
112
113
  echo "💡 Switch models in-session with: /model <prefix>:<model-name>"
113
114
  echo ""
114
115
 
115
- # Hand off to Claude Code
116
- exec claude "$@"
116
+ # Hand off to Claude Code with glm-5 as default model
117
+ EXTRA_FLAGS=""
118
+ if [[ "$(basename "$0")" == "ccxd" ]]; then
119
+ EXTRA_FLAGS="--dangerously-skip-permissions"
120
+ echo "⚡ Running with --dangerously-skip-permissions"
121
+ fi
122
+ exec claude --model "${ANTHROPIC_MODEL:-glm-5}" $EXTRA_FLAGS "$@"
package/install.sh CHANGED
@@ -611,6 +611,7 @@ fi
611
611
 
612
612
  export ANTHROPIC_BASE_URL="http://127.0.0.1:${PORT}"
613
613
  export ANTHROPIC_AUTH_TOKEN="${ANTHROPIC_AUTH_TOKEN:-local-proxy-token}"
614
+ export ANTHROPIC_MODEL="${ANTHROPIC_MODEL:-glm-5}"
614
615
 
615
616
  echo "[ccx] Starting Claude Code with multi-provider proxy..."
616
617
  echo "[ccx] Proxy will listen on: ${ANTHROPIC_BASE_URL}"
@@ -653,19 +654,29 @@ echo "🎯 Available model prefixes:"
653
654
  echo " openai:<model> - OpenAI models (gpt-4o, gpt-4o-mini, etc.)"
654
655
  echo " openrouter:<model> - OpenRouter models"
655
656
  echo " gemini:<model> - Google Gemini models"
656
- echo " glm:<model> - Z.AI GLM models (glm-4.7, glm-4.5, etc.)"
657
+ echo " glm:<model> - Z.AI GLM models (glm-5, glm-4.7, glm-4.5, etc.)"
657
658
  echo " anthropic:<model> - Anthropic Claude models"
658
659
  echo ""
659
660
  echo "💡 Switch models in-session with: /model <prefix>:<model-name>"
660
661
  echo ""
661
662
 
662
- # Hand off to Claude Code
663
- exec claude "$@"
663
+ # Hand off to Claude Code with glm-5 as default model
664
+ EXTRA_FLAGS=""
665
+ if [[ "\$(basename "\$0")" == "ccxd" ]]; then
666
+ EXTRA_FLAGS="--dangerously-skip-permissions"
667
+ echo "⚡ Running with --dangerously-skip-permissions"
668
+ fi
669
+ exec claude --model "\${ANTHROPIC_MODEL:-glm-5}" \$EXTRA_FLAGS "\$@"
664
670
  CCXEOF
665
671
 
666
672
  chmod +x "$wrapper_path"
667
673
  echo "✅ Installed ccx at $wrapper_path"
668
674
 
675
+ # Create ccxd symlink (dangerous permissions mode)
676
+ local ccxd_path="${wrapper_path%ccx}ccxd"
677
+ ln -sf "$wrapper_path" "$ccxd_path"
678
+ echo "✅ Installed ccxd at $ccxd_path (--dangerously-skip-permissions)"
679
+
669
680
  # Add ccx alias to shell config
670
681
  add_ccx_alias
671
682
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-glm",
3
- "version": "1.2.4",
3
+ "version": "1.3.1",
4
4
  "description": "Cross-platform installer for Claude Code with Z.AI GLM models, multi-provider proxy, and dangerously-skip-permissions shortcuts. Run with: npx claude-glm",
5
5
  "keywords": [
6
6
  "claude",