@earendil-works/pi-coding-agent
Version:
Coding agent CLI with read, bash, edit, write tools and session management
107 lines • 5.18 kB
JavaScript
import { stream, streamSimple } from "@earendil-works/pi-ai/compat";
import { LlamaClient, llamaInferenceUrl, normalizeLlamaServerUrl } from "./client.js";
export const LLAMA_PROVIDER_ID = "llama.cpp";
export const DEFAULT_LLAMA_SERVER_URL = "http://127.0.0.1:8080";
function credentialServerUrl(credential) {
const value = credential?.env?.LLAMA_BASE_URL;
return typeof value === "string" && value.trim() ? normalizeLlamaServerUrl(value) : undefined;
}
async function resolveServerUrl(ctx, credential) {
const configured = credentialServerUrl(credential) ?? (await ctx.env("LLAMA_BASE_URL"))?.trim();
return configured ? normalizeLlamaServerUrl(configured) : undefined;
}
function toPiModel(model, serverUrl) {
const reportedContextWindow = model.meta?.n_ctx ?? model.meta?.n_ctx_train;
const contextWindow = reportedContextWindow && reportedContextWindow > 0 ? reportedContextWindow : 128000;
return {
id: model.id,
name: model.id,
api: "openai-completions",
provider: LLAMA_PROVIDER_ID,
baseUrl: llamaInferenceUrl(serverUrl),
reasoning: false,
input: model.architecture?.input_modalities?.includes("image") ? ["text", "image"] : ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow,
maxTokens: contextWindow,
compat: {
supportsStore: false,
supportsDeveloperRole: false,
supportsReasoningEffort: false,
supportsUsageInStreaming: true,
supportsStrictMode: false,
maxTokensField: "max_tokens",
},
};
}
export function createLlamaProvider() {
let models = [];
const setCatalog = (catalog, serverUrl) => {
models = catalog.filter((model) => model.status.value === "loaded").map((model) => toPiModel(model, serverUrl));
};
const provider = {
id: LLAMA_PROVIDER_ID,
name: "llama.cpp",
baseUrl: llamaInferenceUrl(DEFAULT_LLAMA_SERVER_URL),
auth: {
apiKey: {
name: "llama.cpp server",
login: async (interaction) => {
const enteredUrl = await interaction.prompt({
type: "text",
message: "llama.cpp server URL",
placeholder: process.env.LLAMA_BASE_URL ?? DEFAULT_LLAMA_SERVER_URL,
});
const serverUrl = normalizeLlamaServerUrl(enteredUrl.trim() || process.env.LLAMA_BASE_URL || DEFAULT_LLAMA_SERVER_URL);
const apiKey = (await interaction.prompt({
type: "secret",
message: "API key (optional)",
})).trim();
await new LlamaClient(serverUrl, apiKey || undefined).list({ signal: interaction.signal });
return {
type: "api_key",
key: apiKey || undefined,
env: { LLAMA_BASE_URL: serverUrl },
};
},
check: async ({ ctx, credential }) => {
const serverUrl = await resolveServerUrl(ctx, credential);
return serverUrl
? { type: "api_key", source: credential ? "stored credential" : "LLAMA_BASE_URL" }
: undefined;
},
resolve: async ({ ctx, credential }) => {
const serverUrl = await resolveServerUrl(ctx, credential);
if (!serverUrl)
return undefined;
const apiKey = credential?.key ?? (await ctx.env("LLAMA_API_KEY")) ?? "local";
return {
auth: { apiKey, baseUrl: llamaInferenceUrl(serverUrl) },
env: { ...credential?.env, LLAMA_BASE_URL: serverUrl },
source: credential ? "stored credential" : "LLAMA_BASE_URL",
};
},
},
},
getModels: () => models,
refreshModels: async (context) => {
const stored = await context.store.read();
if (stored) {
models = stored.models.filter((model) => model.provider === LLAMA_PROVIDER_ID && model.api === "openai-completions");
}
if (!context.allowNetwork || context.signal?.aborted || context.credential?.type !== "api_key")
return;
const serverUrl = credentialServerUrl(context.credential);
if (!serverUrl)
return;
const catalog = await new LlamaClient(serverUrl, context.credential.key).list({ signal: context.signal });
setCatalog(catalog, serverUrl);
if (!context.signal?.aborted)
await context.store.write({ models, checkedAt: Date.now() });
},
stream: (model, context, options) => stream(model, context, options),
streamSimple: (model, context, options) => streamSimple(model, context, options),
};
return { provider, setCatalog };
}
//# sourceMappingURL=provider.js.map