Skip to content

Instantly share code, notes, and snippets.

@epequeno
Created February 27, 2026 21:18
Show Gist options
  • Select an option

  • Save epequeno/c1219fb15f18aa7c5c0f49b2f0fe8957 to your computer and use it in GitHub Desktop.

Select an option

Save epequeno/c1219fb15f18aa7c5c0f49b2f0fe8957 to your computer and use it in GitHub Desktop.
Patch for the pi coding harness to work with opencode's GLM-5 model
Patch file (pi-openai-completions-glm5-fix.patch):
- 82-line unified diff against @mariozechner/pi-ai@0.55.1
- Portable path headers (a/dist/... → b/dist/...)
- To apply on another machine:
cd <pi-ai-install-path> # e.g. ~/.local/share/fnm/.../node_modules/@mariozechner/pi-ai
patch -p1 < pi-openai-completions-glm5-fix.patch
--- a/dist/providers/openai-completions.js
+++ b/dist/providers/openai-completions.js
@@ -94,6 +94,12 @@
else if (block.type === "toolCall") {
block.arguments = parseStreamingJson(block.partialArgs);
delete block.partialArgs;
+ // Emit deferred start if it was never sent
+ if (!block._startEmitted) {
+ stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
+ block._startEmitted = true;
+ }
+ delete block._startEmitted;
stream.push({
type: "toolcall_end",
contentIndex: blockIndex(),
@@ -194,9 +200,14 @@
}
if (choice?.delta?.tool_calls) {
for (const toolCall of choice.delta.tool_calls) {
- if (!currentBlock ||
+ // Use function.name to detect new tool calls (not id alone),
+ // because some providers (e.g. OpenCode Zen/GLM-5) send a
+ // unique id on every streaming chunk. A new tool call always
+ // has a non-empty function.name; continuation chunks don't.
+ const isNewToolCall = !currentBlock ||
currentBlock.type !== "toolCall" ||
- (toolCall.id && currentBlock.id !== toolCall.id)) {
+ (toolCall.function?.name && toolCall.function.name.length > 0);
+ if (isNewToolCall) {
finishCurrentBlock(currentBlock);
currentBlock = {
type: "toolCall",
@@ -204,13 +215,15 @@
name: toolCall.function?.name || "",
arguments: {},
partialArgs: "",
+ _startEmitted: false,
};
output.content.push(currentBlock);
- stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
}
if (currentBlock.type === "toolCall") {
- if (toolCall.id)
- currentBlock.id = toolCall.id;
+ // Do NOT update id on continuation chunks.
+ // GLM-5 via OpenCode Zen sends a unique id per chunk,
+ // and the TUI uses id to track tool call components —
+ // changing it causes a new "$ ..." widget per chunk.
if (toolCall.function?.name)
currentBlock.name = toolCall.function.name;
let delta = "";
@@ -218,13 +231,24 @@
delta = toolCall.function.arguments;
currentBlock.partialArgs += toolCall.function.arguments;
currentBlock.arguments = parseStreamingJson(currentBlock.partialArgs);
+ // Defer both toolcall_start and toolcall_delta until
+ // the arguments JSON is complete, to avoid TUI flicker
+ // from per-token streaming (GLM-5 via OpenCode Zen).
+ let argsComplete = false;
+ try { JSON.parse(currentBlock.partialArgs); argsComplete = true; } catch {}
+ if (argsComplete) {
+ if (!currentBlock._startEmitted) {
+ stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
+ currentBlock._startEmitted = true;
+ }
+ stream.push({
+ type: "toolcall_delta",
+ contentIndex: blockIndex(),
+ delta,
+ partial: output,
+ });
+ }
}
- stream.push({
- type: "toolcall_delta",
- contentIndex: blockIndex(),
- delta,
- partial: output,
- });
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment