feat(server): expose secure harness API

This commit is contained in:
Wenxin Duan
2026-09-07 16:37:51 +08:00
parent 64ca126047
commit 5e28e74ca1
16 changed files with 654 additions and 1 deletions
+134 -1
View File
@@ -1 +1,134 @@
export {};
import { mkdir } from "node:fs/promises";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import {
FileAttachmentStore,
HarnessSession,
InMemoryApprovalBroker,
InMemoryEventStore,
InMemoryModelConnectionStore,
InMemorySessionRegistry,
SafeWorkspace,
SkillCatalog,
SkillEventInference,
createMockAdsMetricsTool,
createPiAgentSessionPortFactory,
createSaveReportDraftTool,
validateBaseUrl,
} from "@agent-studio/harness";
import type { ModelConnectionInput } from "@agent-studio/shared";
import { buildApp } from "./app.js";
import { loadServerConfig } from "./config.js";
export async function buildDefaultApp(environment: NodeJS.ProcessEnv = process.env) {
const config = loadServerConfig(environment);
const projectRoot = process.cwd();
const sessionsRoot = resolve(projectRoot, config.sessionsRoot);
const agentDir = resolve(projectRoot, config.agentDir);
await mkdir(sessionsRoot, { recursive: true, mode: 0o700 });
await mkdir(agentDir, { recursive: true, mode: 0o700 });
const eventStore = new InMemoryEventStore();
const skillCatalog = new SkillCatalog({
cwd: projectRoot,
agentDir,
...(environment.AGENT_SKILLS_DIRS === undefined
? {}
: { extraDirsValue: environment.AGENT_SKILLS_DIRS }),
});
await skillCatalog.reload();
const environmentConnection = await readEnvironmentConnection(environment, config.allowPrivateLlmBaseUrls);
const modelStore = new InMemoryModelConnectionStore({
...(environmentConnection === undefined ? {} : { environment: environmentConnection }),
validateUrl: (url) => validateBaseUrl(url, {
allowPrivateNetwork: config.allowPrivateLlmBaseUrls,
allowLocalhostHttp: environment.NODE_ENV !== "production",
}),
});
const approvalBroker = new InMemoryApprovalBroker({
eventStore,
timeoutMs: config.approvalTimeoutMs,
});
const attachmentStore = new FileAttachmentStore({
sessionsRoot,
eventStore,
maxBytes: config.maxImageBytes,
maxAttachmentsPerMessage: config.maxImagesPerMessage,
});
const registry = new InMemorySessionRegistry<HarnessSession>({
sessionsRoot,
createSession: ({ sessionId, workspaceRoot }) => {
eventStore.append(sessionId, { type: "session.started", payload: { cwd: "." } });
return new HarnessSession({
sessionId,
workspaceRoot,
eventStore,
modelStore,
attachmentStore,
approvalBroker,
runTimeoutMs: config.runTimeoutMs,
createSkillInference: (runId) => new SkillEventInference({
sessionId,
runId,
eventStore,
skillIndex: skillCatalog.index(),
}),
createAgentPort: async (input) => {
const workspace = await SafeWorkspace.create(workspaceRoot);
return createPiAgentSessionPortFactory({
agentDir,
approvalBroker,
customTools: [
createMockAdsMetricsTool(join(projectRoot, "fixtures", "ads-account.json")),
createSaveReportDraftTool(workspace),
],
})(input);
},
});
},
});
return buildApp({
sessionRegistry: registry,
skillCatalog,
modelStore,
attachmentStore,
approvalBroker,
});
}
async function readEnvironmentConnection(
environment: NodeJS.ProcessEnv,
allowPrivateNetwork: boolean,
): Promise<ModelConnectionInput | undefined> {
const providerId = environment.AGENT_PROVIDER?.trim();
const modelId = environment.AGENT_MODEL?.trim();
if (providerId === undefined || providerId === "" || modelId === undefined || modelId === "") return undefined;
const api = environment.LLM_API ?? "openai-responses";
if (api !== "openai-responses" && api !== "openai-completions" && api !== "anthropic-messages") return undefined;
const rawBaseUrl = environment.LLM_BASE_URL?.trim();
const baseUrl = rawBaseUrl === undefined || rawBaseUrl === ""
? undefined
: (await validateBaseUrl(rawBaseUrl, {
allowPrivateNetwork,
allowLocalhostHttp: environment.NODE_ENV !== "production",
})).normalizedUrl;
const apiKey = providerId === "anthropic"
? environment.ANTHROPIC_API_KEY?.trim()
: environment.OPENAI_API_KEY?.trim();
return {
providerId,
modelId,
api,
supportsImages: environment.AGENT_MODEL_SUPPORTS_IMAGES !== "false",
...(baseUrl === undefined ? {} : { baseUrl }),
...(apiKey === undefined || apiKey === "" ? {} : { apiKey }),
};
}
const entryPoint = process.argv[1];
if (entryPoint !== undefined && import.meta.url === pathToFileURL(entryPoint).href) {
const config = loadServerConfig(process.env);
const app = await buildDefaultApp();
await app.listen({ host: config.host, port: config.port });
}