Last active
September 1, 2025 19:39
-
-
Save vitobotta/2be3f33722e05e8d4f9d2b0138b8c863 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ChutesGLMTransformer { | |
constructor(options = {}) { | |
this.name = "chutes-glm"; | |
this.endPoint = "/v1/chat/completions"; | |
this.enable = options.enable ?? true; | |
} | |
async auth(request, provider) { | |
return { | |
body: request, | |
config: { | |
headers: { | |
...this.normalizeRequestHeaders(provider?.headers || {}), | |
Authorization: `Bearer ${provider.apiKey || "sk-no-key-required"}`, | |
"Content-Type": "application/json", | |
}, | |
}, | |
}; | |
} | |
normalizeRequestHeaders(headers) { | |
const out = {}; | |
for (const [key, value] of Object.entries(headers)) { | |
const lower = key.toLowerCase(); | |
if (lower === "connection" || lower === "content-length" || lower === "transfer-encoding") continue; | |
if (lower === "authorization") { out["Authorization"] = value; continue; } | |
out[key] = value; | |
} | |
out["Accept-Encoding"] = "identity"; | |
out["Host"] = "llm.chutes.ai"; | |
return out; | |
} | |
async transformRequestIn(request) { | |
request.stream = false; | |
request.max_tokens = 65536; | |
request.messages.forEach(message => { | |
if (typeof message.content === 'string') { | |
message.content += " /nothink"; | |
} | |
}); | |
return request; | |
} | |
async transformResponseOut(response) { | |
const ct = response.headers.get("Content-Type") || ""; | |
if (!ct.includes("application/json")) return response; | |
const json = await response.clone().json(); | |
const transformed = this.moveTagsIntoToolCalls(json); | |
const newHeaders = new Headers(response.headers); | |
newHeaders.delete("connection"); | |
newHeaders.delete("Connection"); | |
newHeaders.delete("transfer-encoding"); | |
newHeaders.delete("Transfer-Encoding"); | |
return new Response(JSON.stringify(transformed), { | |
status: response.status, | |
statusText: response.statusText, | |
headers: newHeaders, | |
}); | |
} | |
moveTagsIntoToolCalls(obj) { | |
if (!obj?.choices || !Array.isArray(obj.choices)) return obj; | |
const out = { ...obj, choices: [] }; | |
for (const choice of obj.choices) { | |
const c = JSON.parse(JSON.stringify(choice)); | |
const content = c?.message?.content; | |
const { toolCalls, modified } = this.extractToolCallsFromContent(content); | |
if (c?.message) { | |
c.message.content = modified; | |
c.message.tool_calls = (c.message.tool_calls || []).concat(toolCalls); | |
} | |
out.choices.push(c); | |
} | |
return out; | |
} | |
extractToolCallsFromContent(content) { | |
if (typeof content !== "string") return { toolCalls: [], modified: content }; | |
const toolCalls = []; | |
const toolCallPattern = /(<tool_call>\s*(\w+)\s*([\s\S]*?)\s*<\/tool_call>)/gm; | |
let modified = content.replace(toolCallPattern, ""); | |
const matches = [...content.matchAll(toolCallPattern)]; | |
for (const match of matches) { | |
const [fullMatch, toolName, argsContent] = match; | |
const id = String(Math.abs(this.hashString(fullMatch))); | |
const args = this.parseArgsSection(argsContent); | |
toolCalls.push({ | |
id, | |
type: "function", | |
function: { | |
name: toolName.trim(), | |
arguments: JSON.stringify(args), | |
}, | |
}); | |
} | |
return { toolCalls, modified }; | |
} | |
parseArgsSection(argsSection) { | |
const args = {}; | |
if (!argsSection) return args; | |
const argPattern = /<([^>]+)>(.*?)<\/\1>/gs; | |
const matches = [...argsSection.matchAll(argPattern)]; | |
for (const match of matches) { | |
const [fullMatch, key, value] = match; | |
let processedValue = value; | |
const isBytesSingle = processedValue.startsWith("b'") && processedValue.endsWith("'"); | |
const isBytesDouble = processedValue.startsWith('b"') && processedValue.endsWith('"'); | |
if (isBytesSingle || isBytesDouble) { | |
processedValue = processedValue.slice(2, -1); | |
} | |
processedValue = processedValue.replace(/\\"/g, '"'); | |
try { | |
args[key] = JSON.parse(processedValue); | |
} catch { | |
// If not valid JSON, return the raw (possibly bytes-decoded) string | |
args[key] = processedValue; | |
} | |
} | |
return args; | |
} | |
hashString(str) { | |
let h = 0; | |
for (let i = 0; i < str.length; i++) { | |
h = (h << 5) - h + str.charCodeAt(i); | |
h |= 0; // Convert to 32-bit integer | |
} | |
return h; | |
} | |
} | |
module.exports = ChutesGLMTransformer; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment