opencodeがパラレル実行時にセッションIDを引き継げないことがある

This commit is contained in:
nrslib 2026-02-13 23:11:32 +09:00
parent f5d1c6fae2
commit 54e9f80a57
4 changed files with 44 additions and 60 deletions

View File

@ -14,26 +14,17 @@ export interface RunAgentOptions {
sessionId?: string; sessionId?: string;
model?: string; model?: string;
provider?: 'claude' | 'codex' | 'opencode' | 'mock'; provider?: 'claude' | 'codex' | 'opencode' | 'mock';
/** Resolved path to persona prompt file */
personaPath?: string; personaPath?: string;
/** Allowed tools for this agent run */
allowedTools?: string[]; allowedTools?: string[];
/** MCP servers for this agent run */
mcpServers?: Record<string, McpServerConfig>; mcpServers?: Record<string, McpServerConfig>;
/** Maximum number of agentic turns */
maxTurns?: number; maxTurns?: number;
/** Permission mode for tool execution (from piece step) */
permissionMode?: PermissionMode; permissionMode?: PermissionMode;
/** Provider-specific movement options */
providerOptions?: MovementProviderOptions; providerOptions?: MovementProviderOptions;
onStream?: StreamCallback; onStream?: StreamCallback;
onPermissionRequest?: PermissionHandler; onPermissionRequest?: PermissionHandler;
onAskUserQuestion?: AskUserQuestionHandler; onAskUserQuestion?: AskUserQuestionHandler;
/** Bypass all permission checks (sacrifice-my-pc mode) */
bypassPermissions?: boolean; bypassPermissions?: boolean;
/** Language for template resolution */
language?: Language; language?: Language;
/** Piece meta information for system prompt template */
pieceMeta?: { pieceMeta?: {
pieceName: string; pieceName: string;
pieceDescription?: string; pieceDescription?: string;
@ -41,6 +32,5 @@ export interface RunAgentOptions {
movementsList: ReadonlyArray<{ name: string; description?: string }>; movementsList: ReadonlyArray<{ name: string; description?: string }>;
currentPosition: string; currentPosition: string;
}; };
/** JSON Schema for structured output */
outputSchema?: Record<string, unknown>; outputSchema?: Record<string, unknown>;
} }

View File

@ -273,6 +273,8 @@ export class OpenCodeClient {
let diagRef: StreamDiagnostics | undefined; let diagRef: StreamDiagnostics | undefined;
let serverClose: (() => void) | undefined; let serverClose: (() => void) | undefined;
let opencodeApiClient: Awaited<ReturnType<typeof createOpencode>>['client'] | undefined; let opencodeApiClient: Awaited<ReturnType<typeof createOpencode>>['client'] | undefined;
let sessionId: string | undefined = options.sessionId;
const interactionTimeoutMs = options.interactionTimeoutMs ?? OPENCODE_INTERACTION_TIMEOUT_MS;
const resetIdleTimeout = (): void => { const resetIdleTimeout = (): void => {
if (idleTimeoutId !== undefined) { if (idleTimeoutId !== undefined) {
@ -330,14 +332,14 @@ export class OpenCodeClient {
opencodeApiClient = client; opencodeApiClient = client;
serverClose = server.close; serverClose = server.close;
const sessionResult = options.sessionId const sessionResult = sessionId
? { data: { id: options.sessionId } } ? { data: { id: sessionId } }
: await client.session.create({ : await client.session.create({
directory: options.cwd, directory: options.cwd,
permission: buildOpenCodePermissionRuleset(options.permissionMode, options.networkAccess), permission: buildOpenCodePermissionRuleset(options.permissionMode, options.networkAccess),
}); });
const sessionId = sessionResult.data?.id; sessionId = sessionResult.data?.id;
if (!sessionId) { if (!sessionId) {
throw new Error('Failed to create OpenCode session'); throw new Error('Failed to create OpenCode session');
} }
@ -420,18 +422,24 @@ export class OpenCodeClient {
sessionID: string; sessionID: string;
}; };
if (permProps.sessionID === sessionId) { if (permProps.sessionID === sessionId) {
const reply = options.permissionMode try {
? mapToOpenCodePermissionReply(options.permissionMode) const reply = options.permissionMode
: 'once'; ? mapToOpenCodePermissionReply(options.permissionMode)
await withTimeout( : 'once';
(signal) => client.permission.reply({ await withTimeout(
requestID: permProps.id, (signal) => client.permission.reply({
directory: options.cwd, requestID: permProps.id,
reply, directory: options.cwd,
}, { signal }), reply,
OPENCODE_INTERACTION_TIMEOUT_MS, }, { signal }),
'OpenCode permission reply timed out', interactionTimeoutMs,
); 'OpenCode permission reply timed out',
);
} catch (e) {
success = false;
failureMessage = getErrorMessage(e);
break;
}
} }
continue; continue;
} }
@ -440,14 +448,20 @@ export class OpenCodeClient {
const questionProps = sseEvent.properties as OpenCodeQuestionAskedProperties; const questionProps = sseEvent.properties as OpenCodeQuestionAskedProperties;
if (questionProps.sessionID === sessionId) { if (questionProps.sessionID === sessionId) {
if (!options.onAskUserQuestion) { if (!options.onAskUserQuestion) {
await withTimeout( try {
(signal) => client.question.reject({ await withTimeout(
requestID: questionProps.id, (signal) => client.question.reject({
directory: options.cwd, requestID: questionProps.id,
}, { signal }), directory: options.cwd,
OPENCODE_INTERACTION_TIMEOUT_MS, }, { signal }),
'OpenCode question reject timed out', interactionTimeoutMs,
); 'OpenCode question reject timed out',
);
} catch (e) {
success = false;
failureMessage = getErrorMessage(e);
break;
}
continue; continue;
} }
@ -459,20 +473,12 @@ export class OpenCodeClient {
directory: options.cwd, directory: options.cwd,
answers: toQuestionAnswers(questionProps, answers), answers: toQuestionAnswers(questionProps, answers),
}, { signal }), }, { signal }),
OPENCODE_INTERACTION_TIMEOUT_MS, interactionTimeoutMs,
'OpenCode question reply timed out', 'OpenCode question reply timed out',
); );
} catch { } catch (e) {
await withTimeout(
(signal) => client.question.reject({
requestID: questionProps.id,
directory: options.cwd,
}, { signal }),
OPENCODE_INTERACTION_TIMEOUT_MS,
'OpenCode question reject timed out',
);
success = false; success = false;
failureMessage = 'OpenCode question handling failed'; failureMessage = getErrorMessage(e);
break; break;
} }
} }
@ -631,8 +637,8 @@ export class OpenCodeClient {
continue; continue;
} }
if (options.sessionId) { if (sessionId) {
emitResult(options.onStream, false, errorMessage, options.sessionId); emitResult(options.onStream, false, errorMessage, sessionId);
} }
return { return {
@ -640,7 +646,7 @@ export class OpenCodeClient {
status: 'error', status: 'error',
content: errorMessage, content: errorMessage,
timestamp: new Date(), timestamp: new Date(),
sessionId: options.sessionId, sessionId,
}; };
} finally { } finally {
if (idleTimeoutId !== undefined) { if (idleTimeoutId !== undefined) {

View File

@ -187,15 +187,11 @@ export interface OpenCodeCallOptions {
model: string; model: string;
systemPrompt?: string; systemPrompt?: string;
allowedTools?: string[]; allowedTools?: string[];
/** Permission mode for automatic permission handling */
permissionMode?: PermissionMode; permissionMode?: PermissionMode;
/** Override network access (webfetch/websearch) */
networkAccess?: boolean; networkAccess?: boolean;
/** Enable streaming mode with callback (best-effort) */
onStream?: StreamCallback; onStream?: StreamCallback;
onAskUserQuestion?: AskUserQuestionHandler; onAskUserQuestion?: AskUserQuestionHandler;
/** OpenCode API key */
opencodeApiKey?: string; opencodeApiKey?: string;
/** JSON Schema for structured output */
outputSchema?: Record<string, unknown>; outputSchema?: Record<string, unknown>;
interactionTimeoutMs?: number;
} }

View File

@ -24,25 +24,17 @@ export interface ProviderCallOptions {
sessionId?: string; sessionId?: string;
model?: string; model?: string;
allowedTools?: string[]; allowedTools?: string[];
/** MCP servers configuration */
mcpServers?: Record<string, McpServerConfig>; mcpServers?: Record<string, McpServerConfig>;
/** Maximum number of agentic turns */
maxTurns?: number; maxTurns?: number;
/** Permission mode for tool execution (from piece step) */
permissionMode?: PermissionMode; permissionMode?: PermissionMode;
/** Provider-specific movement options */
providerOptions?: MovementProviderOptions; providerOptions?: MovementProviderOptions;
onStream?: StreamCallback; onStream?: StreamCallback;
onPermissionRequest?: PermissionHandler; onPermissionRequest?: PermissionHandler;
onAskUserQuestion?: AskUserQuestionHandler; onAskUserQuestion?: AskUserQuestionHandler;
bypassPermissions?: boolean; bypassPermissions?: boolean;
/** Anthropic API key for Claude provider */
anthropicApiKey?: string; anthropicApiKey?: string;
/** OpenAI API key for Codex provider */
openaiApiKey?: string; openaiApiKey?: string;
/** OpenCode API key for OpenCode provider */
opencodeApiKey?: string; opencodeApiKey?: string;
/** JSON Schema for structured output */
outputSchema?: Record<string, unknown>; outputSchema?: Record<string, unknown>;
} }