claude-glm 1.0.1 → 1.2.0
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/README.md +9 -4
- package/adapters/map.ts +41 -16
- package/adapters/providers/gemini.ts +1 -0
- package/adapters/providers/openai.ts +1 -0
- package/adapters/providers/openrouter.ts +269 -17
- package/install.sh +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ That's it!
|
|
|
58
58
|
## Features
|
|
59
59
|
|
|
60
60
|
- 🚀 **Easy switching** between GLM and Claude models
|
|
61
|
-
- ⚡ **Multiple GLM models**: GLM-
|
|
61
|
+
- ⚡ **Multiple GLM models**: GLM-5 (latest), GLM-4.7, GLM-4.5, and GLM-4.5-Air (fast)
|
|
62
62
|
- 🔒 **No sudo/admin required**: Installs to user's home directory
|
|
63
63
|
- 🖥️ **Cross-platform**: Works on Windows, macOS, and Linux
|
|
64
64
|
- 📁 **Isolated configs**: Each model uses its own config directory — no conflicts!
|
|
@@ -144,9 +144,9 @@ The installer creates these commands and aliases:
|
|
|
144
144
|
The `ccx` command starts a local proxy that lets you switch between multiple AI providers in a single session:
|
|
145
145
|
|
|
146
146
|
- **OpenAI**: GPT-4o, GPT-4o-mini, and more
|
|
147
|
-
- **OpenRouter**: Access to hundreds of models
|
|
147
|
+
- **OpenRouter**: Access to hundreds of models (including GLM-5)
|
|
148
148
|
- **Google Gemini**: Gemini 1.5 Pro and Flash
|
|
149
|
-
- **Z.AI GLM**: GLM-4.7, GLM-4.5, GLM-4.5-Air
|
|
149
|
+
- **Z.AI GLM**: GLM-5, GLM-4.7, GLM-4.5, GLM-4.5-Air
|
|
150
150
|
- **Anthropic**: Claude 3.5 Sonnet, etc.
|
|
151
151
|
|
|
152
152
|
Switch models mid-session using `/model <provider>:<model-name>`. Perfect for comparing responses or using the right model for each task!
|
|
@@ -375,6 +375,8 @@ Use Claude Code's built-in `/model` command with provider prefixes:
|
|
|
375
375
|
| `/model glm` | `glm:glm-4.7` | Friendly GLM shortcut |
|
|
376
376
|
| `/model glm47` | `glm:glm-4.7` | Explicit version |
|
|
377
377
|
| `/model glm45` | `glm:glm-4.5` | Previous version |
|
|
378
|
+
| `/model glm5` | `glm:glm-5` | Latest GLM-5 model |
|
|
379
|
+
| `/model glm5or` | `openrouter:z-ai/glm-5` | GLM-5 via OpenRouter |
|
|
378
380
|
| `/model flash` | `glm:glm-4-flash` | Fast model |
|
|
379
381
|
| `/model opus` | `anthropic:claude-opus-4-5-20251101` | Claude Opus (API key required) |
|
|
380
382
|
| `/model sonnet` | `anthropic:claude-sonnet-4-5-20250929` | Claude Sonnet (API key required) |
|
|
@@ -389,6 +391,8 @@ Use Claude Code's built-in `/model` command with provider prefixes:
|
|
|
389
391
|
```typescript
|
|
390
392
|
const MODEL_SHORTCUTS: Record<string, string> = {
|
|
391
393
|
g: "glm:glm-4.7",
|
|
394
|
+
glm5: "glm:glm-5",
|
|
395
|
+
glm5or: "openrouter:z-ai/glm-5",
|
|
392
396
|
o1: "openai:o1-preview", // Add your own!
|
|
393
397
|
fast: "glm:glm-4-flash",
|
|
394
398
|
// ... more shortcuts
|
|
@@ -745,7 +749,8 @@ Then reload: `. $PROFILE`
|
|
|
745
749
|
**A**:
|
|
746
750
|
|
|
747
751
|
- Use **`ccx`** for: Maximum flexibility, model comparison, leveraging different model strengths
|
|
748
|
-
- Use
|
|
752
|
+
- Use **`/model glm5`** for: Latest GLM-5 with advanced agentic capabilities and long-horizon workflows
|
|
753
|
+
- Use **`ccg` (GLM-4.7)** for: Complex coding, refactoring, detailed explanations
|
|
749
754
|
- Use **`ccg45` (GLM-4.5)** for: Previous version, if you need consistency with older projects
|
|
750
755
|
- Use **`ccf` (GLM-4.5-Air)** for: Quick questions, simple tasks, faster responses
|
|
751
756
|
- Use **`cc` (Claude)** for: Your regular Anthropic Claude setup
|
package/adapters/map.ts
CHANGED
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
// Provider parsing and message mapping utilities
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AnthropicMessage,
|
|
4
|
+
AnthropicRequest,
|
|
5
|
+
ProviderKey,
|
|
6
|
+
ProviderModel,
|
|
7
|
+
} from "./types.js";
|
|
3
8
|
|
|
4
|
-
const PROVIDER_PREFIXES: ProviderKey[] = [
|
|
9
|
+
const PROVIDER_PREFIXES: ProviderKey[] = [
|
|
10
|
+
"openai",
|
|
11
|
+
"openrouter",
|
|
12
|
+
"gemini",
|
|
13
|
+
"glm",
|
|
14
|
+
"anthropic",
|
|
15
|
+
];
|
|
5
16
|
|
|
6
17
|
// Model shortcuts - add your own aliases here
|
|
7
18
|
const MODEL_SHORTCUTS: Record<string, string> = {
|
|
8
19
|
// GLM shortcuts
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
g: "glm:glm-5",
|
|
21
|
+
glm: "glm:glm-5",
|
|
22
|
+
glm47: "glm:glm-4.7",
|
|
23
|
+
glm45: "glm:glm-4.5",
|
|
24
|
+
glm5: "glm:glm-5",
|
|
25
|
+
glm5or: "openrouter:z-ai/glm-5",
|
|
26
|
+
flash: "glm:glm-4-flash",
|
|
14
27
|
// Claude shortcuts (for API users)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
opus: "anthropic:claude-opus-4-5-20251101",
|
|
29
|
+
sonnet: "anthropic:claude-sonnet-4-5-20250929",
|
|
30
|
+
haiku: "anthropic:claude-haiku-4-5-20251001",
|
|
18
31
|
// Add more shortcuts as needed
|
|
19
32
|
};
|
|
20
33
|
|
|
@@ -23,7 +36,10 @@ const MODEL_SHORTCUTS: Record<string, string> = {
|
|
|
23
36
|
* Supports formats: "provider:model" or "provider/model"
|
|
24
37
|
* Falls back to defaults if no valid prefix found
|
|
25
38
|
*/
|
|
26
|
-
export function parseProviderModel(
|
|
39
|
+
export function parseProviderModel(
|
|
40
|
+
modelField: string,
|
|
41
|
+
defaults?: ProviderModel,
|
|
42
|
+
): ProviderModel {
|
|
27
43
|
if (!modelField) {
|
|
28
44
|
if (defaults) return defaults;
|
|
29
45
|
throw new Error("Missing 'model' in request");
|
|
@@ -37,7 +53,11 @@ export function parseProviderModel(modelField: string, defaults?: ProviderModel)
|
|
|
37
53
|
return { provider: "anthropic", model: expanded };
|
|
38
54
|
}
|
|
39
55
|
|
|
40
|
-
const sep = expanded.includes(":")
|
|
56
|
+
const sep = expanded.includes(":")
|
|
57
|
+
? ":"
|
|
58
|
+
: expanded.includes("/")
|
|
59
|
+
? "/"
|
|
60
|
+
: null;
|
|
41
61
|
if (!sep) {
|
|
42
62
|
// no prefix: fall back to defaults or assume glm as legacy
|
|
43
63
|
return defaults ?? { provider: "glm", model: expanded };
|
|
@@ -57,11 +77,16 @@ export function parseProviderModel(modelField: string, defaults?: ProviderModel)
|
|
|
57
77
|
/**
|
|
58
78
|
* Warn if tools are being used with providers that may not support them
|
|
59
79
|
*/
|
|
60
|
-
export function warnIfTools(
|
|
80
|
+
export function warnIfTools(
|
|
81
|
+
req: AnthropicRequest,
|
|
82
|
+
provider: ProviderKey,
|
|
83
|
+
): void {
|
|
61
84
|
if (req.tools && req.tools.length > 0) {
|
|
62
85
|
// Only GLM and Anthropic support tools natively
|
|
63
86
|
if (provider !== "glm" && provider !== "anthropic") {
|
|
64
|
-
console.warn(
|
|
87
|
+
console.warn(
|
|
88
|
+
`[proxy] Warning: ${provider} may not fully support Anthropic-style tools. Passing through anyway.`,
|
|
89
|
+
);
|
|
65
90
|
}
|
|
66
91
|
}
|
|
67
92
|
}
|
|
@@ -91,7 +116,7 @@ export function toPlainText(content: AnthropicMessage["content"]): string {
|
|
|
91
116
|
export function toOpenAIMessages(messages: AnthropicMessage[]) {
|
|
92
117
|
return messages.map((m) => ({
|
|
93
118
|
role: m.role,
|
|
94
|
-
content: toPlainText(m.content)
|
|
119
|
+
content: toPlainText(m.content),
|
|
95
120
|
}));
|
|
96
121
|
}
|
|
97
122
|
|
|
@@ -101,6 +126,6 @@ export function toOpenAIMessages(messages: AnthropicMessage[]) {
|
|
|
101
126
|
export function toGeminiContents(messages: AnthropicMessage[]) {
|
|
102
127
|
return messages.map((m) => ({
|
|
103
128
|
role: m.role === "assistant" ? "model" : "user",
|
|
104
|
-
parts: [{ text: toPlainText(m.content) }]
|
|
129
|
+
parts: [{ text: toPlainText(m.content) }],
|
|
105
130
|
}));
|
|
106
131
|
}
|
|
@@ -1,12 +1,86 @@
|
|
|
1
|
-
// OpenRouter adapter (OpenAI-compatible API)
|
|
1
|
+
// OpenRouter adapter (OpenAI-compatible API) with full tool calling support
|
|
2
2
|
import { FastifyReply } from "fastify";
|
|
3
3
|
import { createParser } from "eventsource-parser";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import type { AnthropicRequest } from "../types.js";
|
|
4
|
+
import { sendEvent } from "../sse.js";
|
|
5
|
+
import type { AnthropicRequest, AnthropicMessage, AnthropicTool, AnthropicContentBlock } from "../types.js";
|
|
7
6
|
|
|
8
7
|
const OR_BASE = process.env.OPENROUTER_BASE_URL || "https://openrouter.ai/api/v1";
|
|
9
8
|
|
|
9
|
+
// ── Format converters: Anthropic → OpenAI ──────────────────────────────
|
|
10
|
+
|
|
11
|
+
/** Convert Anthropic tools to OpenAI tools format */
|
|
12
|
+
function toOpenAITools(tools: AnthropicTool[]) {
|
|
13
|
+
return tools.map((t) => ({
|
|
14
|
+
type: "function" as const,
|
|
15
|
+
function: {
|
|
16
|
+
name: t.name,
|
|
17
|
+
description: t.description ?? "",
|
|
18
|
+
parameters: t.input_schema ?? { type: "object", properties: {} },
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Convert Anthropic messages (with tool_use/tool_result) to OpenAI messages */
|
|
24
|
+
function toOpenAIMessagesWithTools(messages: AnthropicMessage[]) {
|
|
25
|
+
const out: any[] = [];
|
|
26
|
+
|
|
27
|
+
for (const m of messages) {
|
|
28
|
+
if (typeof m.content === "string") {
|
|
29
|
+
out.push({ role: m.role, content: m.content });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Complex content blocks - need to split into separate messages
|
|
34
|
+
const textParts: string[] = [];
|
|
35
|
+
const toolCalls: any[] = [];
|
|
36
|
+
const toolResults: any[] = [];
|
|
37
|
+
|
|
38
|
+
for (const block of m.content as AnthropicContentBlock[]) {
|
|
39
|
+
if (block.type === "text") {
|
|
40
|
+
textParts.push(block.text);
|
|
41
|
+
} else if (block.type === "tool_use") {
|
|
42
|
+
toolCalls.push({
|
|
43
|
+
id: block.id,
|
|
44
|
+
type: "function",
|
|
45
|
+
function: {
|
|
46
|
+
name: block.name,
|
|
47
|
+
arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input),
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
} else if (block.type === "tool_result") {
|
|
51
|
+
const content = typeof block.content === "string"
|
|
52
|
+
? block.content
|
|
53
|
+
: JSON.stringify(block.content);
|
|
54
|
+
toolResults.push({
|
|
55
|
+
role: "tool",
|
|
56
|
+
tool_call_id: block.tool_use_id,
|
|
57
|
+
content,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Assistant message with tool calls
|
|
63
|
+
if (m.role === "assistant" && toolCalls.length > 0) {
|
|
64
|
+
out.push({
|
|
65
|
+
role: "assistant",
|
|
66
|
+
content: textParts.join("") || null,
|
|
67
|
+
tool_calls: toolCalls,
|
|
68
|
+
});
|
|
69
|
+
} else if (textParts.length > 0) {
|
|
70
|
+
out.push({ role: m.role, content: textParts.join("") });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Tool results become separate "tool" role messages
|
|
74
|
+
for (const tr of toolResults) {
|
|
75
|
+
out.push(tr);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Main adapter ────────────────────────────────────────────────────────
|
|
83
|
+
|
|
10
84
|
export async function chatOpenRouter(
|
|
11
85
|
res: FastifyReply,
|
|
12
86
|
body: AnthropicRequest,
|
|
@@ -20,10 +94,9 @@ export async function chatOpenRouter(
|
|
|
20
94
|
const url = `${OR_BASE}/chat/completions`;
|
|
21
95
|
const headers: Record<string, string> = {
|
|
22
96
|
Authorization: `Bearer ${apiKey}`,
|
|
23
|
-
"Content-Type": "application/json"
|
|
97
|
+
"Content-Type": "application/json",
|
|
24
98
|
};
|
|
25
99
|
|
|
26
|
-
// Add optional OpenRouter headers
|
|
27
100
|
if (process.env.OPENROUTER_REFERER) {
|
|
28
101
|
headers["HTTP-Referer"] = process.env.OPENROUTER_REFERER;
|
|
29
102
|
}
|
|
@@ -31,24 +104,34 @@ export async function chatOpenRouter(
|
|
|
31
104
|
headers["X-Title"] = process.env.OPENROUTER_TITLE;
|
|
32
105
|
}
|
|
33
106
|
|
|
107
|
+
// Build OpenAI-format request
|
|
108
|
+
const hasTools = body.tools && body.tools.length > 0;
|
|
109
|
+
const messages = hasTools
|
|
110
|
+
? toOpenAIMessagesWithTools(body.messages)
|
|
111
|
+
: toOpenAIMessagesWithTools(body.messages);
|
|
112
|
+
|
|
113
|
+
// Add system message if present
|
|
114
|
+
if (body.system) {
|
|
115
|
+
messages.unshift({ role: "system", content: body.system });
|
|
116
|
+
}
|
|
117
|
+
|
|
34
118
|
const reqBody: any = {
|
|
35
119
|
model,
|
|
36
|
-
messages
|
|
120
|
+
messages,
|
|
37
121
|
stream: true,
|
|
38
122
|
temperature: body.temperature ?? 0.7,
|
|
39
|
-
max_tokens: body.max_tokens
|
|
123
|
+
max_tokens: body.max_tokens,
|
|
40
124
|
};
|
|
41
125
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
console.
|
|
45
|
-
reqBody.tools = body.tools;
|
|
126
|
+
if (hasTools) {
|
|
127
|
+
reqBody.tools = toOpenAITools(body.tools!);
|
|
128
|
+
console.log(`[openrouter] Sending ${body.tools!.length} tools (converted to OpenAI format)`);
|
|
46
129
|
}
|
|
47
130
|
|
|
48
131
|
const resp = await fetch(url, {
|
|
49
132
|
method: "POST",
|
|
50
133
|
headers,
|
|
51
|
-
body: JSON.stringify(reqBody)
|
|
134
|
+
body: JSON.stringify(reqBody),
|
|
52
135
|
});
|
|
53
136
|
|
|
54
137
|
if (!resp.ok || !resp.body) {
|
|
@@ -56,7 +139,83 @@ export async function chatOpenRouter(
|
|
|
56
139
|
throw withStatus(resp.status || 500, `OpenRouter error: ${text}`);
|
|
57
140
|
}
|
|
58
141
|
|
|
59
|
-
|
|
142
|
+
// ── Stream response and convert back to Anthropic SSE format ──────
|
|
143
|
+
|
|
144
|
+
const msgId = `msg_${Date.now()}`;
|
|
145
|
+
let contentIndex = 0;
|
|
146
|
+
let hasStartedMessage = false;
|
|
147
|
+
let hasStartedThinking = false;
|
|
148
|
+
let hasStartedContent = false;
|
|
149
|
+
|
|
150
|
+
// Accumulate tool calls from streaming chunks
|
|
151
|
+
const pendingToolCalls: Record<number, { id: string; name: string; arguments: string }> = {};
|
|
152
|
+
|
|
153
|
+
function ensureMessageStarted() {
|
|
154
|
+
if (!hasStartedMessage) {
|
|
155
|
+
hasStartedMessage = true;
|
|
156
|
+
sendEvent(res, "message_start", {
|
|
157
|
+
type: "message_start",
|
|
158
|
+
message: {
|
|
159
|
+
id: msgId,
|
|
160
|
+
type: "message",
|
|
161
|
+
role: "assistant",
|
|
162
|
+
model,
|
|
163
|
+
content: [],
|
|
164
|
+
stop_reason: null,
|
|
165
|
+
stop_sequence: null,
|
|
166
|
+
usage: { input_tokens: 0, output_tokens: 0 },
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function ensureThinkingBlockStarted() {
|
|
173
|
+
if (!hasStartedThinking) {
|
|
174
|
+
hasStartedThinking = true;
|
|
175
|
+
ensureMessageStarted();
|
|
176
|
+
sendEvent(res, "content_block_start", {
|
|
177
|
+
type: "content_block_start",
|
|
178
|
+
index: contentIndex,
|
|
179
|
+
content_block: { type: "thinking", thinking: "" },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function closeThinkingBlock() {
|
|
185
|
+
if (hasStartedThinking) {
|
|
186
|
+
sendEvent(res, "content_block_stop", {
|
|
187
|
+
type: "content_block_stop",
|
|
188
|
+
index: contentIndex,
|
|
189
|
+
});
|
|
190
|
+
contentIndex++;
|
|
191
|
+
hasStartedThinking = false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function ensureContentBlockStarted() {
|
|
196
|
+
if (!hasStartedContent) {
|
|
197
|
+
// Close thinking block first if open
|
|
198
|
+
closeThinkingBlock();
|
|
199
|
+
hasStartedContent = true;
|
|
200
|
+
ensureMessageStarted();
|
|
201
|
+
sendEvent(res, "content_block_start", {
|
|
202
|
+
type: "content_block_start",
|
|
203
|
+
index: contentIndex,
|
|
204
|
+
content_block: { type: "text", text: "" },
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function closeContentBlock() {
|
|
210
|
+
if (hasStartedContent) {
|
|
211
|
+
sendEvent(res, "content_block_stop", {
|
|
212
|
+
type: "content_block_stop",
|
|
213
|
+
index: contentIndex,
|
|
214
|
+
});
|
|
215
|
+
contentIndex++;
|
|
216
|
+
hasStartedContent = false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
60
219
|
|
|
61
220
|
const reader = resp.body.getReader();
|
|
62
221
|
const decoder = new TextDecoder();
|
|
@@ -66,8 +225,46 @@ export async function chatOpenRouter(
|
|
|
66
225
|
if (!data || data === "[DONE]") return;
|
|
67
226
|
try {
|
|
68
227
|
const json = JSON.parse(data);
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
228
|
+
const choice = json.choices?.[0];
|
|
229
|
+
if (!choice) return;
|
|
230
|
+
|
|
231
|
+
const delta = choice.delta;
|
|
232
|
+
if (!delta) return;
|
|
233
|
+
|
|
234
|
+
// Handle reasoning/thinking tokens (GLM-5 and other reasoning models)
|
|
235
|
+
const reasoningChunk = delta.reasoning || "";
|
|
236
|
+
if (reasoningChunk) {
|
|
237
|
+
ensureThinkingBlockStarted();
|
|
238
|
+
sendEvent(res, "content_block_delta", {
|
|
239
|
+
type: "content_block_delta",
|
|
240
|
+
index: contentIndex,
|
|
241
|
+
delta: { type: "thinking_delta", thinking: reasoningChunk },
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Handle text content
|
|
246
|
+
const textChunk = delta.content || "";
|
|
247
|
+
if (textChunk) {
|
|
248
|
+
ensureContentBlockStarted();
|
|
249
|
+
sendEvent(res, "content_block_delta", {
|
|
250
|
+
type: "content_block_delta",
|
|
251
|
+
index: contentIndex,
|
|
252
|
+
delta: { type: "text_delta", text: textChunk },
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Handle streaming tool calls
|
|
257
|
+
if (delta.tool_calls) {
|
|
258
|
+
for (const tc of delta.tool_calls) {
|
|
259
|
+
const idx = tc.index ?? 0;
|
|
260
|
+
if (!pendingToolCalls[idx]) {
|
|
261
|
+
pendingToolCalls[idx] = { id: "", name: "", arguments: "" };
|
|
262
|
+
}
|
|
263
|
+
if (tc.id) pendingToolCalls[idx].id = tc.id;
|
|
264
|
+
if (tc.function?.name) pendingToolCalls[idx].name += tc.function.name;
|
|
265
|
+
if (tc.function?.arguments) pendingToolCalls[idx].arguments += tc.function.arguments;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
71
268
|
} catch {
|
|
72
269
|
// ignore parse errors
|
|
73
270
|
}
|
|
@@ -79,7 +276,62 @@ export async function chatOpenRouter(
|
|
|
79
276
|
parser.feed(decoder.decode(value));
|
|
80
277
|
}
|
|
81
278
|
|
|
82
|
-
|
|
279
|
+
// ── Finalize: emit tool_use blocks if any ─────────────────────────
|
|
280
|
+
|
|
281
|
+
ensureMessageStarted();
|
|
282
|
+
|
|
283
|
+
// Close any open blocks
|
|
284
|
+
closeThinkingBlock();
|
|
285
|
+
closeContentBlock();
|
|
286
|
+
|
|
287
|
+
// Emit tool_use content blocks
|
|
288
|
+
const toolCallEntries = Object.values(pendingToolCalls);
|
|
289
|
+
if (toolCallEntries.length > 0) {
|
|
290
|
+
for (const tc of toolCallEntries) {
|
|
291
|
+
// Start tool_use content block
|
|
292
|
+
sendEvent(res, "content_block_start", {
|
|
293
|
+
type: "content_block_start",
|
|
294
|
+
index: contentIndex,
|
|
295
|
+
content_block: {
|
|
296
|
+
type: "tool_use",
|
|
297
|
+
id: tc.id,
|
|
298
|
+
name: tc.name,
|
|
299
|
+
input: {},
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Send the input as a delta
|
|
304
|
+
sendEvent(res, "content_block_delta", {
|
|
305
|
+
type: "content_block_delta",
|
|
306
|
+
index: contentIndex,
|
|
307
|
+
delta: {
|
|
308
|
+
type: "input_json_delta",
|
|
309
|
+
partial_json: tc.arguments,
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Stop tool_use content block
|
|
314
|
+
sendEvent(res, "content_block_stop", {
|
|
315
|
+
type: "content_block_stop",
|
|
316
|
+
index: contentIndex,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
contentIndex++;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Determine stop reason
|
|
324
|
+
const stopReason = toolCallEntries.length > 0 ? "tool_use" : "end_turn";
|
|
325
|
+
|
|
326
|
+
// Send message_delta and message_stop
|
|
327
|
+
sendEvent(res, "message_delta", {
|
|
328
|
+
type: "message_delta",
|
|
329
|
+
delta: { stop_reason: stopReason, stop_sequence: null },
|
|
330
|
+
usage: { output_tokens: 0 },
|
|
331
|
+
});
|
|
332
|
+
sendEvent(res, "message_stop", { type: "message_stop" });
|
|
333
|
+
|
|
334
|
+
res.raw.end();
|
|
83
335
|
}
|
|
84
336
|
|
|
85
337
|
function withStatus(status: number, message: string) {
|
package/install.sh
CHANGED
|
@@ -338,12 +338,12 @@ create_claude_glm_wrapper() {
|
|
|
338
338
|
|
|
339
339
|
cat > "$wrapper_path" << EOF
|
|
340
340
|
#!/bin/bash
|
|
341
|
-
# Claude-GLM - Claude Code with Z.AI GLM-
|
|
341
|
+
# Claude-GLM - Claude Code with Z.AI GLM-5 (Standard Model)
|
|
342
342
|
|
|
343
343
|
# Set Z.AI environment variables
|
|
344
344
|
export ANTHROPIC_BASE_URL="https://api.z.ai/api/anthropic"
|
|
345
345
|
export ANTHROPIC_AUTH_TOKEN="$ZAI_API_KEY"
|
|
346
|
-
export ANTHROPIC_MODEL="glm-
|
|
346
|
+
export ANTHROPIC_MODEL="glm-5"
|
|
347
347
|
export ANTHROPIC_SMALL_FAST_MODEL="glm-4.5-air"
|
|
348
348
|
|
|
349
349
|
# Use custom config directory to avoid conflicts
|
|
@@ -358,14 +358,14 @@ cat > "\$CLAUDE_HOME/settings.json" << SETTINGS
|
|
|
358
358
|
"env": {
|
|
359
359
|
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
|
|
360
360
|
"ANTHROPIC_AUTH_TOKEN": "$ZAI_API_KEY",
|
|
361
|
-
"ANTHROPIC_MODEL": "glm-
|
|
361
|
+
"ANTHROPIC_MODEL": "glm-5",
|
|
362
362
|
"ANTHROPIC_SMALL_FAST_MODEL": "glm-4.5-air"
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
SETTINGS
|
|
366
366
|
|
|
367
367
|
# Launch Claude Code with custom config
|
|
368
|
-
echo "🚀 Starting Claude Code with GLM-
|
|
368
|
+
echo "🚀 Starting Claude Code with GLM-5 (Standard Model)..."
|
|
369
369
|
echo "📁 Config directory: \$CLAUDE_HOME"
|
|
370
370
|
echo ""
|
|
371
371
|
|
|
@@ -866,7 +866,7 @@ main() {
|
|
|
866
866
|
echo "📝 After sourcing, you can use:"
|
|
867
867
|
echo ""
|
|
868
868
|
echo "Commands:"
|
|
869
|
-
echo " claude-glm - GLM-
|
|
869
|
+
echo " claude-glm - GLM-5 (latest)"
|
|
870
870
|
echo " claude-glm-4.5 - GLM-4.5"
|
|
871
871
|
echo " claude-glm-fast - GLM-4.5-Air (fast)"
|
|
872
872
|
if [ "$install_ccx_choice" != "n" ] && [ "$install_ccx_choice" != "N" ]; then
|
|
@@ -875,7 +875,7 @@ main() {
|
|
|
875
875
|
echo ""
|
|
876
876
|
echo "Aliases:"
|
|
877
877
|
echo " cc - claude (regular Claude)"
|
|
878
|
-
echo " ccg - claude-glm (GLM-
|
|
878
|
+
echo " ccg - claude-glm (GLM-5)"
|
|
879
879
|
echo " ccg45 - claude-glm-4.5 (GLM-4.5)"
|
|
880
880
|
echo " ccf - claude-glm-fast"
|
|
881
881
|
echo " claude-d - claude --dangerously-skip-permissions"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-glm",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
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",
|