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

View File

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

View File

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

View File

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