Files
AgentCompany/packages/adapter-gemini-local/src/server/execute-helpers.ts
T
2026-04-05 20:14:48 -04:00

128 lines
4.1 KiB
TypeScript

import fs from "node:fs/promises";
import type { Dirent } from "node:fs";
import os from "node:os";
import path from "node:path";
import type { AdapterExecutionContext, ManagedSkillRef } from "@agentcompany/adapter-utils";
import {
hasNonEmptyEnvValue,
buildSkillsCandidates,
resolveSkillsDir,
parseManagedSkills,
parseExtraArgs,
buildCommonAgentCompanyEnv,
resolveSessionResumeState,
} from "@agentcompany/adapter-utils";
import {
createManagedSkillSymlinks,
} from "@agentcompany/adapter-utils/server-utils";
export {
hasNonEmptyEnvValue,
parseManagedSkills,
parseExtraArgs,
buildCommonAgentCompanyEnv,
resolveSessionResumeState,
};
export const SKILLS_CANDIDATES = buildSkillsCandidates(import.meta.url);
export function resolveGeminiBillingType(env: Record<string, string>): "api" | "subscription" {
return hasNonEmptyEnvValue(env, "GEMINI_API_KEY") || hasNonEmptyEnvValue(env, "GOOGLE_API_KEY")
? "api"
: "subscription";
}
export function renderEnvNote(env: Record<string, string>): string {
const agentCompanyKeys = Object.keys(env)
.filter((key) => key.startsWith("AGENT_COMPANY_"))
.sort();
if (agentCompanyKeys.length === 0) return "";
return [
"Runtime note:",
`The following AGENT_COMPANY_* environment variables are available in this run: ${agentCompanyKeys.join(", ")}`,
"Do not assume these variables are missing without checking your shell environment.",
"",
"",
].join("\n");
}
export function renderApiAccessNote(env: Record<string, string>): string {
if (!hasNonEmptyEnvValue(env, "AGENT_COMPANY_API_URL") || !hasNonEmptyEnvValue(env, "AGENT_COMPANY_API_KEY")) return "";
return [
"API access note:",
"Use run_shell_command with curl to make API requests.",
"GET example:",
` run_shell_command({ command: "curl -s -H \\"Authorization: Bearer $AGENT_COMPANY_API_KEY\\" \\"$AGENT_COMPANY_API_URL/api/agents/me\\"" })`,
"POST/PATCH example:",
` run_shell_command({ command: "curl -s -X POST -H \\"Authorization: Bearer $AGENT_COMPANY_API_KEY\\" -H 'Content-Type: application/json' -H \\"X-Agent-Company-Run-Id: $AGENT_COMPANY_RUN_ID\\" -d '{...}' \\"$AGENT_COMPANY_API_URL/api/issues/{id}/checkout\\"" })`,
"",
"",
].join("\n");
}
export async function resolveAgentCompanySkillsDir(): Promise<string | null> {
return resolveSkillsDir(SKILLS_CANDIDATES);
}
export function geminiSkillsHome(): string {
return path.join(os.homedir(), ".gemini", "skills");
}
/**
* Inject AgentCompany skills directly into `~/.gemini/skills/` via symlinks.
* Uses managed skills when available; falls back to static repo scan.
*/
export async function ensureGeminiSkillsInjected(
managedSkills: ManagedSkillRef[],
onLog: AdapterExecutionContext["onLog"],
): Promise<void> {
const skillsHome = geminiSkillsHome();
if (managedSkills.length > 0) {
await createManagedSkillSymlinks(managedSkills, skillsHome, onLog);
return;
}
const skillsDir = await resolveSkillsDir(SKILLS_CANDIDATES);
if (!skillsDir) return;
try {
await fs.mkdir(skillsHome, { recursive: true });
} catch (err) {
await onLog(
"stderr",
`[agent-company] Failed to prepare Gemini skills directory ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
);
return;
}
let entries: Dirent[];
try {
entries = await fs.readdir(skillsDir, { withFileTypes: true });
} catch (err) {
await onLog(
"stderr",
`[agent-company] Failed to read AgentCompany skills from ${skillsDir}: ${err instanceof Error ? err.message : String(err)}\n`,
);
return;
}
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const source = path.join(skillsDir, entry.name);
const target = path.join(skillsHome, entry.name);
const existing = await fs.lstat(target).catch(() => null);
if (existing) continue;
try {
await fs.symlink(source, target);
await onLog("stderr", `[agent-company] Linked Gemini skill: ${entry.name}\n`);
} catch (err) {
await onLog(
"stderr",
`[agent-company] Failed to link Gemini skill "${entry.name}": ${err instanceof Error ? err.message : String(err)}\n`,
);
}
}
}