mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 09:01:14 +00:00
Fix tool call ID normalization for cross-provider switches to Anthropic/GitHub Copilot
This commit is contained in:
parent
3690137ecc
commit
3c60ffa677
6 changed files with 131 additions and 7 deletions
21
Dockerfile.gh-build
Normal file
21
Dockerfile.gh-build
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y curl unzip git xz-utils ca-certificates \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
||||||
|
&& apt-get install -y nodejs \
|
||||||
|
&& BUN_VERSION=1.2.20 \
|
||||||
|
&& ARCH=$(dpkg --print-architecture) \
|
||||||
|
&& if [ "$ARCH" = "amd64" ]; then BUN_ARCH="linux-x64"; else BUN_ARCH="linux-aarch64"; fi \
|
||||||
|
&& curl -fsSL "https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/bun-${BUN_ARCH}.zip" -o /tmp/bun.zip \
|
||||||
|
&& unzip /tmp/bun.zip -d /tmp \
|
||||||
|
&& mv /tmp/bun-${BUN_ARCH}/bun /usr/local/bin/bun \
|
||||||
|
&& chmod +x /usr/local/bin/bun \
|
||||||
|
&& rm -rf /tmp/bun.zip /tmp/bun-linux-x64 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /repo
|
||||||
|
|
||||||
|
ENTRYPOINT ["/bin/bash", "-lc"]
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import type { Api, AssistantMessage, Message, Model, ToolCall, ToolResultMessage } from "../types.js";
|
import type { Api, AssistantMessage, Message, Model, ToolCall, ToolResultMessage } from "../types.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize tool call ID for GitHub Copilot cross-API compatibility.
|
* Normalize tool call ID for cross-provider compatibility.
|
||||||
* OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
|
* OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
|
||||||
* Other APIs (Claude, etc.) require max 40 chars and only alphanumeric + underscore + hyphen.
|
* Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
|
||||||
*/
|
*/
|
||||||
function normalizeCopilotToolCallId(id: string): string {
|
function normalizeToolCallId(id: string): string {
|
||||||
return id.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 40);
|
return id.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 40);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,11 +38,17 @@ export function transformMessages<TApi extends Api>(messages: Message[], model:
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we need to normalize tool call IDs (github-copilot cross-API)
|
// Check if we need to normalize tool call IDs
|
||||||
const needsToolCallIdNormalization =
|
// Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars)
|
||||||
|
// OpenAI Responses API generates IDs with `|` and 450+ chars
|
||||||
|
// GitHub Copilot routes to Anthropic for Claude models
|
||||||
|
const targetRequiresStrictIds = model.api === "anthropic-messages" || model.provider === "github-copilot";
|
||||||
|
const crossProviderSwitch = assistantMsg.provider !== model.provider;
|
||||||
|
const copilotCrossApiSwitch =
|
||||||
assistantMsg.provider === "github-copilot" &&
|
assistantMsg.provider === "github-copilot" &&
|
||||||
model.provider === "github-copilot" &&
|
model.provider === "github-copilot" &&
|
||||||
assistantMsg.api !== model.api;
|
assistantMsg.api !== model.api;
|
||||||
|
const needsToolCallIdNormalization = targetRequiresStrictIds && (crossProviderSwitch || copilotCrossApiSwitch);
|
||||||
|
|
||||||
// Transform message from different provider/model
|
// Transform message from different provider/model
|
||||||
const transformedContent = assistantMsg.content.flatMap((block) => {
|
const transformedContent = assistantMsg.content.flatMap((block) => {
|
||||||
|
|
@ -54,10 +60,10 @@ export function transformMessages<TApi extends Api>(messages: Message[], model:
|
||||||
text: block.thinking,
|
text: block.thinking,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Normalize tool call IDs for github-copilot cross-API switches
|
// Normalize tool call IDs when target API requires strict format
|
||||||
if (block.type === "toolCall" && needsToolCallIdNormalization) {
|
if (block.type === "toolCall" && needsToolCallIdNormalization) {
|
||||||
const toolCall = block as ToolCall;
|
const toolCall = block as ToolCall;
|
||||||
const normalizedId = normalizeCopilotToolCallId(toolCall.id);
|
const normalizedId = normalizeToolCallId(toolCall.id);
|
||||||
if (normalizedId !== toolCall.id) {
|
if (normalizedId !== toolCall.id) {
|
||||||
toolCallIdMap.set(toolCall.id, normalizedId);
|
toolCallIdMap.set(toolCall.id, normalizedId);
|
||||||
return { ...toolCall, id: normalizedId };
|
return { ...toolCall, id: normalizedId };
|
||||||
|
|
|
||||||
1
packages/coding-agent/.gitignore
vendored
Normal file
1
packages/coding-agent/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
*.bun-build
|
||||||
|
|
@ -33,6 +33,12 @@ function getAliases(): Record<string, string> {
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const packageIndex = path.resolve(__dirname, "../..", "index.js");
|
const packageIndex = path.resolve(__dirname, "../..", "index.js");
|
||||||
|
|
||||||
|
// Debug: log what we're resolving
|
||||||
|
if (process.env.DEBUG_EXTENSIONS) {
|
||||||
|
console.error("[DEBUG] import.meta.url:", import.meta.url);
|
||||||
|
console.error("[DEBUG] __dirname:", __dirname);
|
||||||
|
}
|
||||||
|
|
||||||
const typeboxEntry = require.resolve("@sinclair/typebox");
|
const typeboxEntry = require.resolve("@sinclair/typebox");
|
||||||
const typeboxRoot = typeboxEntry.replace(/\/build\/cjs\/index\.js$/, "");
|
const typeboxRoot = typeboxEntry.replace(/\/build\/cjs\/index\.js$/, "");
|
||||||
|
|
||||||
|
|
@ -43,6 +49,11 @@ function getAliases(): Record<string, string> {
|
||||||
"@mariozechner/pi-ai": require.resolve("@mariozechner/pi-ai"),
|
"@mariozechner/pi-ai": require.resolve("@mariozechner/pi-ai"),
|
||||||
"@sinclair/typebox": typeboxRoot,
|
"@sinclair/typebox": typeboxRoot,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (process.env.DEBUG_EXTENSIONS) {
|
||||||
|
console.error("[DEBUG] aliases:", JSON.stringify(_aliases, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
return _aliases;
|
return _aliases;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
40
scripts/gh-build-docker.sh
Executable file
40
scripts/gh-build-docker.sh
Executable file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "=== Versions ==="
|
||||||
|
node --version
|
||||||
|
bun --version
|
||||||
|
npm --version
|
||||||
|
|
||||||
|
echo "=== Install dependencies ==="
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
echo "=== Install cross-platform bindings ==="
|
||||||
|
npm install --no-save --force \
|
||||||
|
@mariozechner/clipboard-darwin-arm64@0.3.0 \
|
||||||
|
@mariozechner/clipboard-darwin-x64@0.3.0 \
|
||||||
|
@mariozechner/clipboard-linux-x64-gnu@0.3.0 \
|
||||||
|
@mariozechner/clipboard-linux-arm64-gnu@0.3.0 \
|
||||||
|
@mariozechner/clipboard-win32-x64-msvc@0.3.0
|
||||||
|
|
||||||
|
npm install --no-save --force \
|
||||||
|
@img/sharp-darwin-arm64@0.34.5 \
|
||||||
|
@img/sharp-darwin-x64@0.34.5 \
|
||||||
|
@img/sharp-linux-x64@0.34.5 \
|
||||||
|
@img/sharp-linux-arm64@0.34.5 \
|
||||||
|
@img/sharp-win32-x64@0.34.5 \
|
||||||
|
@img/sharp-libvips-darwin-arm64@1.2.4 \
|
||||||
|
@img/sharp-libvips-darwin-x64@1.2.4 \
|
||||||
|
@img/sharp-libvips-linux-x64@1.2.4 \
|
||||||
|
@img/sharp-libvips-linux-arm64@1.2.4
|
||||||
|
|
||||||
|
echo "=== Build all packages ==="
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
echo "=== Build darwin-arm64 binary ==="
|
||||||
|
mkdir -p /repo/.tmp
|
||||||
|
cd packages/coding-agent
|
||||||
|
bun build --compile --target=bun-darwin-arm64 ./dist/cli.js --outfile /repo/.tmp/pi-darwin-arm64
|
||||||
|
|
||||||
|
echo "=== Done ==="
|
||||||
|
ls -la /repo/.tmp/pi-darwin-arm64
|
||||||
45
scripts/test-gh-build.sh
Normal file
45
scripts/test-gh-build.sh
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Simulate GH Actions build locally
|
||||||
|
|
||||||
|
cd /Users/badlogic/workspaces/pi-mono
|
||||||
|
|
||||||
|
echo "=== Cleaning node_modules ==="
|
||||||
|
rm -rf node_modules packages/*/node_modules
|
||||||
|
|
||||||
|
echo "=== npm ci ==="
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
echo "=== Install cross-platform bindings (like GH Actions) ==="
|
||||||
|
npm install --no-save --force \
|
||||||
|
@mariozechner/clipboard-darwin-arm64@0.3.0 \
|
||||||
|
@mariozechner/clipboard-darwin-x64@0.3.0 \
|
||||||
|
@mariozechner/clipboard-linux-x64-gnu@0.3.0 \
|
||||||
|
@mariozechner/clipboard-linux-arm64-gnu@0.3.0 \
|
||||||
|
@mariozechner/clipboard-win32-x64-msvc@0.3.0
|
||||||
|
|
||||||
|
npm install --no-save --force \
|
||||||
|
@img/sharp-darwin-arm64@0.34.5 \
|
||||||
|
@img/sharp-darwin-x64@0.34.5 \
|
||||||
|
@img/sharp-linux-x64@0.34.5 \
|
||||||
|
@img/sharp-linux-arm64@0.34.5 \
|
||||||
|
@img/sharp-win32-x64@0.34.5 \
|
||||||
|
@img/sharp-libvips-darwin-arm64@1.2.4 \
|
||||||
|
@img/sharp-libvips-darwin-x64@1.2.4 \
|
||||||
|
@img/sharp-libvips-linux-x64@1.2.4 \
|
||||||
|
@img/sharp-libvips-linux-arm64@1.2.4
|
||||||
|
|
||||||
|
echo "=== Build all packages ==="
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
echo "=== Build binary with cross-compile flag ==="
|
||||||
|
cd packages/coding-agent
|
||||||
|
bun build --compile --target=bun-darwin-arm64 ./dist/cli.js --outfile /tmp/pi-gh-sim/pi
|
||||||
|
cp package.json /tmp/pi-gh-sim/
|
||||||
|
|
||||||
|
echo "=== Test the binary ==="
|
||||||
|
/tmp/pi-gh-sim/pi -e /Users/badlogic/workspaces/pi-doom --help 2>&1 | head -5
|
||||||
|
|
||||||
|
echo "=== Binary size ==="
|
||||||
|
ls -la /tmp/pi-gh-sim/pi
|
||||||
Loading…
Add table
Add a link
Reference in a new issue