Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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