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.
- package/adapters/anthropic-gateway.ts +8 -2
- package/adapters/map.ts +5 -0
- package/adapters/vision-preprocess.ts +106 -0
- package/bin/ccx +9 -3
- package/install.sh +14 -3
- package/package.json +1 -1
|
@@ -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-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|