feat(server): expose secure harness API
This commit is contained in:
+134
-1
@@ -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 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user