|
|
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
|
|
import { isDomainEventType, type DomainEventEmission } from "../agents/domain-events.js";
|
|
|
|
|
import type { ActorExecutionInput, ActorExecutionResult, ActorExecutor } from "../agents/pipeline.js";
|
|
|
|
|
import { isRecord, type JsonObject, type JsonValue } from "../agents/types.js";
|
|
|
|
|
import { createSessionContext, type SessionContext } from "../examples/session-context.js";
|
|
|
|
|
import { ClaudeObservabilityLogger } from "./claude-observability.js";
|
|
|
|
|
|
|
|
|
|
export type RunProvider = "codex" | "claude";
|
|
|
|
|
@@ -17,7 +16,7 @@ export type RunProvider = "codex" | "claude";
|
|
|
|
|
export type ProviderRunRuntime = {
|
|
|
|
|
provider: RunProvider;
|
|
|
|
|
config: Readonly<AppConfig>;
|
|
|
|
|
sessionContext: SessionContext;
|
|
|
|
|
sharedEnv: Record<string, string>;
|
|
|
|
|
claudeObservability: ClaudeObservabilityLogger;
|
|
|
|
|
close: () => Promise<void>;
|
|
|
|
|
};
|
|
|
|
|
@@ -30,6 +29,16 @@ type ProviderUsage = {
|
|
|
|
|
costUsd?: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function sanitizeEnv(input: Record<string, string | undefined>): Record<string, string> {
|
|
|
|
|
const output: Record<string, string> = {};
|
|
|
|
|
for (const [key, value] of Object.entries(input)) {
|
|
|
|
|
if (typeof value === "string") {
|
|
|
|
|
output[key] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ACTOR_RESPONSE_SCHEMA = {
|
|
|
|
|
type: "object",
|
|
|
|
|
additionalProperties: true,
|
|
|
|
|
@@ -74,8 +83,6 @@ const CLAUDE_OUTPUT_FORMAT = {
|
|
|
|
|
schema: ACTOR_RESPONSE_SCHEMA,
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
const CLAUDE_PROVIDER_MAX_TURNS = 2;
|
|
|
|
|
|
|
|
|
|
function toErrorMessage(error: unknown): string {
|
|
|
|
|
if (error instanceof Error) {
|
|
|
|
|
return error.message;
|
|
|
|
|
@@ -83,6 +90,23 @@ function toErrorMessage(error: unknown): string {
|
|
|
|
|
return String(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function resolveProviderWorkingDirectory(actorInput: ActorExecutionInput): string {
|
|
|
|
|
return actorInput.executionContext.security.worktreePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function buildProviderRuntimeEnv(input: {
|
|
|
|
|
runtime: ProviderRunRuntime;
|
|
|
|
|
actorInput: ActorExecutionInput;
|
|
|
|
|
includeClaudeAuth?: boolean;
|
|
|
|
|
}): Record<string, string> {
|
|
|
|
|
const workingDirectory = resolveProviderWorkingDirectory(input.actorInput);
|
|
|
|
|
return sanitizeEnv({
|
|
|
|
|
...input.runtime.sharedEnv,
|
|
|
|
|
...(input.includeClaudeAuth ? buildClaudeAuthEnv(input.runtime.config.provider) : {}),
|
|
|
|
|
AGENT_WORKTREE_PATH: workingDirectory,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toJsonValue(value: unknown): JsonValue {
|
|
|
|
|
return JSON.parse(JSON.stringify(value)) as JsonValue;
|
|
|
|
|
}
|
|
|
|
|
@@ -367,6 +391,7 @@ async function runCodexActor(input: {
|
|
|
|
|
const prompt = buildActorPrompt(actorInput);
|
|
|
|
|
const startedAt = Date.now();
|
|
|
|
|
const apiKey = resolveOpenAiApiKey(runtime.config.provider);
|
|
|
|
|
const workingDirectory = resolveProviderWorkingDirectory(actorInput);
|
|
|
|
|
|
|
|
|
|
const codex = new Codex({
|
|
|
|
|
...(apiKey ? { apiKey } : {}),
|
|
|
|
|
@@ -376,20 +401,21 @@ async function runCodexActor(input: {
|
|
|
|
|
...(actorInput.mcp.resolvedConfig.codexConfig
|
|
|
|
|
? { config: actorInput.mcp.resolvedConfig.codexConfig }
|
|
|
|
|
: {}),
|
|
|
|
|
env: runtime.sessionContext.runtimeInjection.env,
|
|
|
|
|
env: buildProviderRuntimeEnv({
|
|
|
|
|
runtime,
|
|
|
|
|
actorInput,
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const thread = codex.startThread({
|
|
|
|
|
workingDirectory: runtime.sessionContext.runtimeInjection.workingDirectory,
|
|
|
|
|
workingDirectory,
|
|
|
|
|
skipGitRepoCheck: runtime.config.provider.codexSkipGitCheck,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const turn = await runtime.sessionContext.runInSession(() =>
|
|
|
|
|
thread.run(prompt, {
|
|
|
|
|
signal: actorInput.signal,
|
|
|
|
|
outputSchema: ACTOR_RESPONSE_SCHEMA,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
const turn = await thread.run(prompt, {
|
|
|
|
|
signal: actorInput.signal,
|
|
|
|
|
outputSchema: ACTOR_RESPONSE_SCHEMA,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const usage: ProviderUsage = {
|
|
|
|
|
...(turn.usage
|
|
|
|
|
@@ -457,6 +483,7 @@ function buildClaudeOptions(input: {
|
|
|
|
|
actorInput: ActorExecutionInput;
|
|
|
|
|
}): Options {
|
|
|
|
|
const { runtime, actorInput } = input;
|
|
|
|
|
const workingDirectory = resolveProviderWorkingDirectory(actorInput);
|
|
|
|
|
|
|
|
|
|
const authOptionOverrides = runtime.config.provider.anthropicOauthToken
|
|
|
|
|
? { authToken: runtime.config.provider.anthropicOauthToken }
|
|
|
|
|
@@ -465,14 +492,15 @@ function buildClaudeOptions(input: {
|
|
|
|
|
return token ? { apiKey: token } : {};
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
const runtimeEnv = {
|
|
|
|
|
...runtime.sessionContext.runtimeInjection.env,
|
|
|
|
|
...buildClaudeAuthEnv(runtime.config.provider),
|
|
|
|
|
};
|
|
|
|
|
const runtimeEnv = buildProviderRuntimeEnv({
|
|
|
|
|
runtime,
|
|
|
|
|
actorInput,
|
|
|
|
|
includeClaudeAuth: true,
|
|
|
|
|
});
|
|
|
|
|
const traceContext = toClaudeTraceContext(actorInput);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
maxTurns: CLAUDE_PROVIDER_MAX_TURNS,
|
|
|
|
|
maxTurns: runtime.config.provider.claudeMaxTurns,
|
|
|
|
|
...(runtime.config.provider.claudeModel
|
|
|
|
|
? { model: runtime.config.provider.claudeModel }
|
|
|
|
|
: {}),
|
|
|
|
|
@@ -484,7 +512,7 @@ function buildClaudeOptions(input: {
|
|
|
|
|
? { mcpServers: actorInput.mcp.resolvedConfig.claudeMcpServers as Options["mcpServers"] }
|
|
|
|
|
: {}),
|
|
|
|
|
canUseTool: actorInput.mcp.createClaudeCanUseTool(),
|
|
|
|
|
cwd: runtime.sessionContext.runtimeInjection.workingDirectory,
|
|
|
|
|
cwd: workingDirectory,
|
|
|
|
|
env: runtimeEnv,
|
|
|
|
|
...runtime.claudeObservability.toOptionOverrides({
|
|
|
|
|
context: traceContext,
|
|
|
|
|
@@ -507,8 +535,8 @@ async function runClaudeTurn(input: {
|
|
|
|
|
context: traceContext,
|
|
|
|
|
data: {
|
|
|
|
|
...(options.model ? { model: options.model } : {}),
|
|
|
|
|
maxTurns: options.maxTurns ?? CLAUDE_PROVIDER_MAX_TURNS,
|
|
|
|
|
cwd: input.runtime.sessionContext.runtimeInjection.workingDirectory,
|
|
|
|
|
maxTurns: options.maxTurns ?? input.runtime.config.provider.claudeMaxTurns,
|
|
|
|
|
...(typeof options.cwd === "string" ? { cwd: options.cwd } : {}),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -605,13 +633,11 @@ async function runClaudeActor(input: {
|
|
|
|
|
actorInput: ActorExecutionInput;
|
|
|
|
|
}): Promise<ActorExecutionResult> {
|
|
|
|
|
const prompt = buildActorPrompt(input.actorInput);
|
|
|
|
|
const turn = await input.runtime.sessionContext.runInSession(() =>
|
|
|
|
|
runClaudeTurn({
|
|
|
|
|
runtime: input.runtime,
|
|
|
|
|
actorInput: input.actorInput,
|
|
|
|
|
prompt,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
const turn = await runClaudeTurn({
|
|
|
|
|
runtime: input.runtime,
|
|
|
|
|
actorInput: input.actorInput,
|
|
|
|
|
prompt,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const parsed = parseActorExecutionResultFromModelOutput({
|
|
|
|
|
rawText: turn.text,
|
|
|
|
|
@@ -626,33 +652,21 @@ async function runClaudeActor(input: {
|
|
|
|
|
|
|
|
|
|
export async function createProviderRunRuntime(input: {
|
|
|
|
|
provider: RunProvider;
|
|
|
|
|
initialPrompt: string;
|
|
|
|
|
config: Readonly<AppConfig>;
|
|
|
|
|
projectPath: string;
|
|
|
|
|
observabilityRootPath?: string;
|
|
|
|
|
baseEnv?: Record<string, string | undefined>;
|
|
|
|
|
}): Promise<ProviderRunRuntime> {
|
|
|
|
|
const sessionContext = await createSessionContext(input.provider, {
|
|
|
|
|
prompt: input.initialPrompt,
|
|
|
|
|
config: input.config,
|
|
|
|
|
workspaceRoot: input.projectPath,
|
|
|
|
|
});
|
|
|
|
|
const claudeObservability = new ClaudeObservabilityLogger({
|
|
|
|
|
workspaceRoot: input.observabilityRootPath ?? input.projectPath,
|
|
|
|
|
workspaceRoot: input.observabilityRootPath ?? process.cwd(),
|
|
|
|
|
config: input.config.provider.claudeObservability,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
provider: input.provider,
|
|
|
|
|
config: input.config,
|
|
|
|
|
sessionContext,
|
|
|
|
|
sharedEnv: sanitizeEnv(input.baseEnv ?? process.env),
|
|
|
|
|
claudeObservability,
|
|
|
|
|
close: async () => {
|
|
|
|
|
try {
|
|
|
|
|
await sessionContext.close();
|
|
|
|
|
} finally {
|
|
|
|
|
await claudeObservability.close();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
close: async () => claudeObservability.close(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|