Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/app/src/assets/acp-provider-icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const ACP_PROVIDER_ICON_SVGS = {
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">\n <path fill="currentColor" d="M8 1C4.134 1 1 4.134 1 8s3.134 7 7 7c1.5 0 2.9-.47 4.05-1.28l1.12 1.12a.75.75 0 1 0 1.06-1.06l-1.12-1.12A6.97 6.97 0 0 0 15 8c0-3.866-3.134-7-7-7Zm0 2c2.761 0 5 2.239 5 5s-2.239 5-5 5-5-2.239-5-5 2.239-5 5-5Z"/>\n</svg>\n',
"qwen-code":
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 141.38 140" width="16" height="16"><path fill="currentColor" d="m140.93 85-16.35-28.33-1.93-3.34 8.66-15a3.323 3.323 0 0 0 0-3.34l-9.62-16.67c-.3-.51-.72-.93-1.22-1.22s-1.07-.45-1.67-.45H82.23l-8.66-15a3.33 3.33 0 0 0-2.89-1.67H51.43c-.59 0-1.17.16-1.66.45-.5.29-.92.71-1.22 1.22L32.19 29.98l-1.92 3.33H12.96c-.59 0-1.17.16-1.66.45-.5.29-.93.71-1.22 1.22L.45 51.66a3.323 3.323 0 0 0 0 3.34l18.28 31.67-8.66 15a3.32 3.32 0 0 0 0 3.34l9.62 16.67c.3.51.72.93 1.22 1.22s1.07.45 1.67.45h36.56l8.66 15a3.35 3.35 0 0 0 2.89 1.67h19.25a3.34 3.34 0 0 0 2.89-1.67l18.28-31.67h17.32c.6 0 1.17-.16 1.67-.45s.92-.71 1.22-1.22l9.62-16.67a3.323 3.323 0 0 0 0-3.34ZM51.44 3.33 61.07 20l-9.63 16.66h76.98l-9.62 16.66H45.67l-11.54-20zM57.21 120H22.58l9.63-16.67h19.25l-38.5-66.67h19.25l9.62 16.67L68.78 100l-11.55 20Zm61.59-33.34-9.62-16.67-38.49 66.67-9.63-16.67 9.63-16.66 26.94-46.67h23.1l17.32 30z"/></svg>\n',
gjc: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">\n <text x="8" y="13" text-anchor="middle" font-size="13" font-family="Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, sans-serif" fill="currentColor">🦞</text>\n</svg>\n',
sigit:
'<?xml version="1.0" standalone="no" ?>\n<svg\n version="1.0"\n xmlns="http://www.w3.org/2000/svg"\n width="16"\n height="16"\n viewBox="0 0 1024 1024"\n preserveAspectRatio="xMidYMid meet"\n>\n\n<g\n transform="translate(0,1024) scale(0.1,-0.1)"\n fill="currentColor"\n stroke="none"\n >\n<path\n d="M6428 6358 c-104 -8 -227 -38 -314 -78 -73 -33 -196 -115 -239 -160\n-18 -19 -39 -30 -56 -30 -26 0 -27 1 -18 32 5 18 9 52 9 76 0 34 -6 49 -34 80\nl-34 37 -319 3 c-412 3 -383 18 -447 -236 -20 -78 -36 -154 -36 -168 0 -14 -5\n-35 -12 -47 -6 -12 -21 -60 -33 -107 l-22 -85 -22 43 c-47 91 -179 184 -310\n218 -146 37 -375 34 -536 -7 -203 -51 -365 -175 -443 -339 -68 -143 -109 -304\n-114 -445 l-2 -70 -127 5 c-70 3 -125 2 -123 -1 5 -8 -27 -21 -34 -14 -3 3 0\n5 7 5 7 0 9 5 6 10 -4 6 -11 8 -16 5 -5 -4 -15 -3 -21 2 -7 4 -17 8 -23 8 -5\n0 -10 7 -10 15 0 13 -142 15 -1205 18 -663 1 -1212 -1 -1220 -5 -8 -3 -19 -5\n-25 -4 -5 1 1 -8 15 -21 13 -13 29 -22 34 -20 5 2 3 -4 -4 -14 -13 -15 -15\n-14 -24 5 -5 13 -15 20 -24 16 -7 -3 -157 -5 -333 -5 l-319 0 0 -2540 0 -2540\n5120 0 5120 0 0 2540 0 2540 -685 1 c-377 1 -689 2 -694 3 -16 2 -25 16 -10\n16 11 0 11 2 -1 10 -8 5 -10 10 -4 10 5 0 16 -6 23 -12 11 -10 13 -10 7 0 -5\n8 1 12 18 12 14 0 27 -1 29 -2 1 -2 13 7 27 20 13 12 30 22 37 22 8 0 11 5 8\n10 -3 6 -2 10 2 10 5 0 9 8 9 18 1 9 18 82 38 161 20 79 36 158 36 176 0 42\n-38 97 -75 109 -17 6 -31 11 -32 11 -1 1 5 23 13 50 20 68 20 137 -2 169 -27\n43 -64 56 -155 56 -73 0 -80 2 -74 18 4 9 9 42 12 72 3 44 0 62 -17 88 -38 61\n-57 63 -396 60 l-304 -3 0 38 c0 29 -8 46 -34 75 l-34 37 -313 3 c-215 2 -321\n-1 -340 -8 -50 -21 -68 -52 -100 -177 l-31 -121 -28 16 c-16 9 -35 30 -43 47\n-23 48 -102 130 -168 174 -132 88 -315 126 -531 109z m296 -148 c43 -11 101\n-32 129 -47 67 -37 145 -115 170 -172 41 -92 33 -253 -25 -493 l-21 -88 -275\n0 -274 0 4 23 c3 12 22 91 42 175 25 102 36 170 34 205 -3 51 -4 52 -39 61\n-57 14 -98 -15 -128 -89 -36 -89 -321 -1238 -328 -1321 -5 -66 -4 -72 18 -88\n54 -38 118 -7 159 78 28 58 122 403 113 419 -2 4 -27 7 -54 7 -43 0 -50 3 -46\n18 3 9 20 79 38 154 l33 137 75 7 c79 7 558 1 566 -8 3 -3 -58 -260 -136 -572\nl-141 -566 -174 0 -174 0 0 29 c0 15 -3 49 -6 74 l-7 46 -61 -55 c-33 -30 -94\n-72 -135 -92 -74 -37 -76 -37 -201 -37 -126 0 -126 0 -202 38 -203 100 -254\n266 -179 592 66 287 235 944 271 1050 72 215 132 298 289 403 144 97 262 130\n462 131 95 1 145 -4 203 -19z m-1046 -37 c-3 -10 -18 -71 -33 -135 l-29 -118\n-273 0 c-213 0 -271 3 -268 13 2 6 13 50 25 97 11 46 25 101 31 122 l10 38\n271 0 c253 0 270 -1 266 -17z m2240 0 c-3 -10 -18 -71 -33 -135 l-29 -118\n-273 0 c-213 0 -271 3 -268 13 2 6 13 50 25 97 11 46 25 101 31 122 l10 38\n271 0 c253 0 270 -1 266 -17z m722 -139 c0 -3 -13 -59 -30 -124 -16 -65 -30\n-126 -30 -134 0 -13 13 -16 65 -16 45 0 65 -4 65 -12 0 -13 -31 -146 -51 -220\nl-10 -38 -72 0 -73 0 -132 -532 c-73 -293 -132 -552 -132 -574 0 -47 11 -54\n93 -54 26 0 47 -3 47 -7 0 -5 -15 -66 -33 -138 l-33 -130 -165 -3 c-326 -6\n-421 26 -459 155 -17 55 2 146 155 761 69 277 125 508 125 513 0 5 -25 9 -55\n9 l-55 0 7 28 c3 15 18 74 33 132 l26 105 59 5 59 5 34 138 34 137 264 0 c145\n0 264 -3 264 -6z m-4147 -223 c137 -36 218 -93 257 -183 27 -63 24 -128 -12\n-278 l-24 -95 -232 -3 c-143 -1 -232 1 -232 7 0 5 9 47 20 93 35 145 26 178\n-49 178 -65 0 -117 -79 -128 -195 -9 -104 10 -126 222 -252 211 -127 273 -191\n290 -305 14 -94 -44 -332 -112 -458 -105 -195 -319 -301 -607 -300 -253 1\n-438 89 -476 225 -17 61 -8 193 21 318 l22 97 234 0 233 0 -5 -22 c-3 -13 -14\n-61 -25 -108 -11 -47 -20 -106 -20 -132 0 -40 4 -50 25 -64 31 -20 85 -12 120\n19 32 28 64 120 72 203 8 82 7 82 -202 224 -178 120 -243 177 -277 242 -72\n138 33 532 175 650 70 59 97 75 175 105 101 38 184 51 328 52 99 1 149 -4 207\n-18z m1087 -20 c0 -7 -408 -1652 -426 -1718 l-6 -23 -269 0 c-173 0 -269 4\n-269 10 0 6 97 399 216 875 l216 865 269 0 c148 0 269 -4 269 -9z m2240 -1 c0\n-5 -97 -397 -215 -871 -118 -474 -215 -863 -215 -865 0 -2 -121 -4 -270 -4\n-174 0 -270 4 -270 10 0 6 97 399 216 875 l216 865 269 0 c166 0 269 -4 269\n-10z m1040 -742 c0 -5 -5 -6 -11 -2 -8 4 -9 -1 -4 -17 4 -13 4 -21 0 -17 -4 4\n-9 21 -11 39 -3 27 -2 30 11 19 8 -7 15 -17 15 -22z m-8192 4 c2 -7 -5 -12\n-15 -11 -12 0 -14 2 -4 6 11 5 11 7 0 15 -12 8 -12 9 0 6 8 -3 17 -10 19 -16z\nm1840 -1329 c-311 -2 -824 -2 -1140 0 -315 1 -60 2 567 2 627 0 885 -1 573 -2z\nm619 1 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z"\n />\n<path\n d="M900 6270 c0 -11 6 -20 14 -20 7 0 16 -9 19 -20 5 -20 12 -20 1803\n-18 1731 3 1799 4 1802 21 3 12 -4 20 -17 24 -12 3 -21 12 -21 19 0 12 -274\n14 -1800 14 -1793 0 -1800 0 -1800 -20z"\n />\n<path\n d="M900 6005 c0 -15 6 -25 14 -25 7 0 16 -9 19 -20 5 -20 12 -20 1334\n-18 l1328 3 3 23 c3 17 -2 22 -17 22 -14 0 -21 6 -21 20 0 20 -7 20 -1330 20\nl-1330 0 0 -25z"\n />\n<path\n d="M737 5713 c-12 -12 -7 -43 7 -43 7 0 16 -9 19 -20 5 -20 12 -20 1323\n-18 1248 3 1319 4 1322 21 2 9 -4 20 -14 24 -10 3 -26 15 -35 24 -17 17 -83\n18 -1317 19 -714 0 -1302 -3 -1305 -7z"\n />\n<path\n d="M1953 5412 c-1265 -2 -1273 -2 -1273 -22 0 -11 6 -20 14 -20 7 0 16\n-9 19 -20 5 -20 12 -20 1298 -18 1242 3 1294 4 1297 21 3 12 -4 20 -17 24 -12\n3 -21 11 -21 18 0 7 -10 14 -22 16 -13 2 -596 3 -1295 1z"\n />\n<path d="M9010 5130 c-12 -7 -10 -9 7 -7 12 0 19 5 17 9 -6 10 -6 10 -24 -2z" />\n<path\n d="M3180 5109 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10\n-5 -10 -11z"\n />\n</g>\n</svg>\n',
stakpak:
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/assets/acp-provider-icons/gjc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/app/src/components/provider-icon-name.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe("resolveProviderIconName", () => {
it("returns the catalog identifier for ACP catalog provider ids that ship an icon", () => {
expect(resolveProviderIconName("amp-acp")).toEqual({ kind: "catalog", id: "amp-acp" });
expect(resolveProviderIconName("gemini")).toEqual({ kind: "catalog", id: "gemini" });
expect(resolveProviderIconName("gjc")).toEqual({ kind: "catalog", id: "gjc" });
expect(resolveProviderIconName("traecli")).toEqual({ kind: "catalog", id: "traecli" });
});

Expand Down
10 changes: 10 additions & 0 deletions packages/app/src/data/acp-provider-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ const CATALOG_DATA = [
installLink: "https://geminicli.com",
command: ["npx", "-y", "@google/gemini-cli@0.52.0", "--acp"],
},
{
id: "gjc",
title: "Gajae Code",
description:
"External coding-agent harness with structured planning, persistent evidence, tmux-backed workers, and ACP support",
version: "manual",
iconId: "gjc",
installLink: "https://github.com/Yeachan-Heo/gajae-code",
command: ["gjc", "acp"],
},
{
id: "glm-acp-agent",
title: "GLM Agent",
Expand Down
7 changes: 7 additions & 0 deletions packages/app/src/hooks/use-acp-provider-catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ describe("ACP provider catalog", () => {
}
});

it("uses the lobster emoji for the Gajae Code catalog icon", () => {
const iconSvg = findProvider("gjc").iconSvg;
expect(iconSvg).toContain("🦞");
expect(iconSvg).not.toContain("&#x1F99E;");
});

it("does not offer Pi's unsupported ACP adapter", () => {
expect(ACP_PROVIDER_CATALOG.some((entry) => entry.id === "pi-acp")).toBe(false);
});
Expand All @@ -43,6 +49,7 @@ describe("ACP provider catalog", () => {
expect(findProvider("cursor").command).toEqual(["cursor-agent", "acp"]);
expect(findProvider("codewhale").command).toEqual(["codewhale", "serve", "--acp"]);
expect(findProvider("devin").command).toEqual(["devin", "acp"]);
expect(findProvider("gjc").command).toEqual(["gjc", "acp"]);
expect(findProvider("goose").command).toEqual(["goose", "acp"]);
expect(findProvider("junie").command).toEqual(["junie", "--acp", "true"]);
expect(findProvider("kiro").command).toEqual(["kiro-cli", "acp"]);
Expand Down
18 changes: 18 additions & 0 deletions packages/app/src/provider-selection/provider-selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,24 @@ describe("combined model selector data", () => {
expect(matchesModelSearch(row, "kimi gemini")).toBe(false);
});

it("matches model ids qualified by a thinking option suffix", () => {
const row = {
favoriteKey: "gjc:openai-codex/gpt-5.5",
provider: "gjc",
providerLabel: "Gajae Code",
modelId: "openai-codex/gpt-5.5",
modelLabel: "GPT-5.5",
description: "openai-codex/gpt-5.5",
thinkingOptions: [
{ id: "high", label: "High" },
{ id: "xhigh", label: "Extra high" },
],
};

expect(matchesModelSearch(row, "openai-codex/gpt-5.5:xhigh")).toBe(true);
expect(matchesModelSearch(row, "openai-codex/gpt-5.5:max")).toBe(false);
});

it("ranks model search results by fuzzy match quality", () => {
const rows = [
{
Expand Down
11 changes: 10 additions & 1 deletion packages/app/src/provider-selection/provider-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface ProviderSelectionModelRow {
modelLabel: string;
description?: string;
isDefault?: boolean;
thinkingOptions?: AgentModelDefinition["thinkingOptions"];
}

function buildModelRowKey(provider: string, modelId: string): string {
Expand Down Expand Up @@ -67,6 +68,7 @@ function buildModelRows(
modelLabel: model.label,
description: model.description ?? model.id,
isDefault: model.isDefault,
...(model.thinkingOptions?.length ? { thinkingOptions: model.thinkingOptions } : {}),
}));
}

Expand Down Expand Up @@ -230,7 +232,14 @@ export function matchesModelSearch(
}

function getModelRowSearchFields(row: ProviderSelectionModelRow): string[] {
return [row.modelLabel, row.modelId, row.providerLabel, row.description ?? ""];
const fields = [row.modelLabel, row.modelId, row.providerLabel, row.description ?? ""];
for (const option of row.thinkingOptions ?? []) {
fields.push(`${row.modelId}:${option.id}`);
fields.push(`${row.modelLabel}:${option.id}`);
fields.push(`${row.modelId}:${option.label}`);
fields.push(`${row.modelLabel}:${option.label}`);
}
return fields;
}

export function scoreModelRow(row: ProviderSelectionModelRow, normalizedQuery: string) {
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/src/provider-icon-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const ACP_PROVIDER_ICON_NAMES = [
"factory-droid",
"fast-agent",
"gemini",
"gjc",
"glm-acp-agent",
"goose",
"grok",
Expand Down
93 changes: 93 additions & 0 deletions packages/server/src/server/agent/provider-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ const mockState = vi.hoisted(() => {
env?: Record<string, string>;
providerParams?: unknown;
}>,
gjc: [] as Array<{
command: string[];
env?: Record<string, string>;
providerParams?: unknown;
}>,
trae: [] as Array<{
command: string[];
env?: Record<string, string>;
Expand Down Expand Up @@ -65,6 +70,7 @@ const mockState = vi.hoisted(() => {
this.constructorArgs.codex = [];
this.constructorArgs.copilot = [];
this.constructorArgs.cursor = [];
this.constructorArgs.gjc = [];
this.constructorArgs.trae = [];
this.constructorArgs.kimi = [];
this.constructorArgs.pi = [];
Expand Down Expand Up @@ -356,6 +362,59 @@ vi.mock("./providers/generic-acp-agent.js", () => ({
},
}));

vi.mock("./providers/gjc-acp-agent.js", () => ({
GjcACPAgentClient: class GjcACPAgentClient {
readonly capabilities = {
supportsStreaming: true,
supportsSessionPersistence: true,
supportsDynamicModes: true,
supportsMcpServers: true,
supportsReasoningStream: true,
supportsToolInvocations: true,
};
readonly provider = "acp";
readonly runtimeSettings?: unknown;

constructor(options: {
command: string[];
env?: Record<string, string>;
providerParams?: unknown;
}) {
this.runtimeSettings = {
command: {
mode: "replace",
argv: options.command,
},
env: options.env,
};
mockState.constructorArgs.gjc.push({
command: options.command,
env: options.env,
providerParams: options.providerParams,
});
}

async createSession(): Promise<never> {
throw new Error("not implemented");
}

async resumeSession(): Promise<never> {
throw new Error("not implemented");
}

async fetchCatalog(): Promise<ProviderCatalog> {
return {
models: mockState.runtimeModels.get(this.provider) ?? [],
modes: [],
};
}

async isAvailable(): Promise<boolean> {
return true;
}
},
}));

vi.mock("./providers/cursor-acp-agent.js", () => ({
CursorACPAgentClient: class CursorACPAgentClient {
readonly capabilities = {
Expand Down Expand Up @@ -825,6 +884,40 @@ test("ACP provider params can disable MCP support", () => {
]);
});

test("gjc provider extending acp uses GjcACPAgentClient", () => {
const registry = buildProviderRegistry(logger, {
providerOverrides: {
gjc: {
extends: "acp",
label: "Gajae Code",
command: ["gjc", "acp"],
env: {
GJC_LOG: "debug",
},
},
},
});

expect(registry.gjc.createClient(logger).provider).toBe("gjc");
expect(mockState.constructorArgs.gjc).toEqual([
{
command: ["gjc", "acp"],
env: {
GJC_LOG: "debug",
},
providerParams: undefined,
},
{
command: ["gjc", "acp"],
env: {
GJC_LOG: "debug",
},
providerParams: undefined,
},
]);
expect(mockState.constructorArgs.genericAcp).toEqual([]);
});

test("cursor provider extending acp uses CursorACPAgentClient", () => {
const registry = buildProviderRegistry(logger, {
providerOverrides: {
Expand Down
4 changes: 4 additions & 0 deletions packages/server/src/server/agent/provider-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { CodexAppServerAgentClient } from "./providers/codex-app-server-agent.js
import { CopilotACPAgentClient } from "./providers/copilot-acp-agent.js";
import { CursorACPAgentClient } from "./providers/cursor-acp-agent.js";
import { GenericACPAgentClient } from "./providers/generic-acp-agent.js";
import { GjcACPAgentClient } from "./providers/gjc-acp-agent.js";
import { KimiACPAgentClient } from "./providers/kimi-acp-agent.js";
import { KiroACPAgentClient } from "./providers/kiro-acp-agent.js";
import { OpenCodeAgentClient } from "./providers/opencode-agent.js";
Expand Down Expand Up @@ -793,6 +794,9 @@ function addDerivedProviders(
if (providerId === "cursor") {
return new CursorACPAgentClient(acpOptions);
}
if (providerId === "gjc") {
return new GjcACPAgentClient(acpOptions);
}
if (providerId === "kimi") {
return new KimiACPAgentClient(acpOptions);
}
Expand Down
Loading