diff --git a/extensions/exacode/index.ts b/extensions/exacode/index.ts new file mode 100644 index 0000000..fca916b --- /dev/null +++ b/extensions/exacode/index.ts @@ -0,0 +1,69 @@ +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; +import { readFileSync, existsSync } from "node:fs"; +import { join } from "node:path"; + +const BASE_URL = "http://exacode-chat.lge.com/v1"; + +// The EXACODE gateway routes requests by the `X-Title` header. +// - "EXACODE Agent Roo" -> the official VS Code extension route; gated by a +// client-version check (blocks curl/SDKs/outdated builds). +// - "EXACODE SWE(API)" -> the API-client route; the intended tier for +// programmatic access. Requires an SWE(API)-scoped bearer token, which +// lives in `.env` as EXACODE_API_KEY (different from the Roo Code JWT). +// +// `X-Model` echoes the requested model id (redundant -- it also travels in the +// request body -- but the official example sets it as a default header). +const X_TITLE = "EXACODE SWE(API)"; + +// pi does not auto-load `.env`, so populate process.env ourselves at startup. +// Real environment variables take precedence over the file. +function loadDotEnv(file: string) { + if (!existsSync(file)) return; + const text = readFileSync(file, "utf8"); + for (const rawLine of text.split(/\r?\n/)) { + const line = rawLine.trim(); + if (!line || line.startsWith("#")) continue; + const eq = line.indexOf("="); + if (eq < 0) continue; + const key = line.slice(0, eq).trim(); + let value = line.slice(eq + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + if (key && process.env[key] === undefined) { + process.env[key] = value; + } + } +} + +loadDotEnv(join(process.cwd(), ".env")); + +const MODELS = [ + { + id: "Chat-EXACODE-A", + name: "Chat-EXACODE-A (EXACODE 3.5)", + reasoning: true, + input: ["text"] as const, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 131072, + maxTokens: 8192, + headers: { "X-Model": "Chat-EXACODE-A" }, + }, +]; + +export default function (pi: ExtensionAPI) { + pi.registerProvider("exacode", { + name: "exacode", + baseUrl: BASE_URL, + apiKey: "$EXACODE_API_KEY", + authHeader: true, + api: "openai-completions", + headers: { + "X-Title": X_TITLE, + }, + models: MODELS, + }); +} diff --git a/extensions/llama-cpp/index.ts b/extensions/llama-cpp/index.ts new file mode 100644 index 0000000..2f28e1f --- /dev/null +++ b/extensions/llama-cpp/index.ts @@ -0,0 +1,29 @@ +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +const BASE_URL = "http://172.26.76.49:8001/v1"; +//const BASE_URL = "http://127.0.0.1:4901/v1"; + +const MODELS = [ + { + id: "qwen-3.6", + name: "Qwen-3.6 35B A3B (llama.cpp)", + reasoning: true, + input: ["text", "image"] as const, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 131072, + maxTokens: 8192, + }, +]; + +export default function (pi: ExtensionAPI) { + pi.registerProvider("llama-cpp", { + name: "llama.cpp (local)", + baseUrl: BASE_URL, + // llama-server accepts any bearer token; send a placeholder so the + // Authorization header is present. + apiKey: "llama-cpp", + authHeader: true, + api: "openai-completions", + models: MODELS, + }); +}