This commit is contained in:
2026-02-24 18:57:20 -05:00
parent 45374a033b
commit 7727612ce9
36 changed files with 1331 additions and 70 deletions

View File

@@ -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(),
};
}