Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
405 lines
14 KiB
JavaScript
405 lines
14 KiB
JavaScript
"use strict";
|
|
|
|
/**
|
|
* SDK driver registry. Mirrors craft backend/factory.ts DRIVER_REGISTRY.
|
|
* Each driver exposes a uniform runTurn(ctx) that builds its SDK options from
|
|
* the neutral context and streams events through ctx.emitter.
|
|
*
|
|
* ctx shape (built by sdkStreamHandlers.cjs):
|
|
* { prompt, attachments, cwd, model, env, binPath, injectedMcpServers, emitter,
|
|
* signal, resumeSessionId, apiKey, baseUrl }
|
|
*/
|
|
const claude = require("./claudeDriver.cjs");
|
|
const codex = require("./codexDriver.cjs");
|
|
const copilot = require("./copilotDriver.cjs");
|
|
const cursor = require("./cursorDriver.cjs");
|
|
const cursorCli = require("./cursorCliDriver.cjs");
|
|
const codebuddy = require("./codebuddyDriver.cjs");
|
|
const opencode = require("./opencodeDriver.cjs");
|
|
const grok = require("./grokDriver.cjs");
|
|
const grokAcp = require("./grokAcpDriver.cjs");
|
|
const { codebuddySessionManager } = require("./codebuddySessionManager.cjs");
|
|
|
|
function hasCodebuddyQueryOnlyOptions(options) {
|
|
return Boolean(
|
|
options.maxBudgetUsd ||
|
|
options.sandbox?.enabled === true ||
|
|
options.fallbackModel ||
|
|
options.enableFileCheckpointing === true ||
|
|
options.outputFormat,
|
|
);
|
|
}
|
|
|
|
const DRIVER_REGISTRY = {
|
|
claude: {
|
|
async runTurn(ctx) {
|
|
const options = claude.buildClaudeQueryOptions({
|
|
cwd: ctx.cwd,
|
|
model: ctx.model,
|
|
env: ctx.env,
|
|
pathToClaudeCodeExecutable: ctx.binPath,
|
|
abortController: ctx.abortController,
|
|
injectedMcpServers: ctx.injectedMcpServers,
|
|
settings: ctx.claudeSettings,
|
|
resume: ctx.resumeSessionId,
|
|
toolIntegrationMode: ctx.toolIntegrationMode,
|
|
});
|
|
return claude.runClaudeTurn({ prompt: ctx.prompt, attachments: ctx.attachments, options, emitter: ctx.emitter });
|
|
},
|
|
async listModels(ctx) {
|
|
return claude.listClaudeModels({
|
|
pathToClaudeCodeExecutable: ctx.binPath,
|
|
env: ctx.env,
|
|
abortController: ctx.abortController,
|
|
signal: ctx.signal,
|
|
});
|
|
},
|
|
},
|
|
codex: {
|
|
async runTurn(ctx) {
|
|
const constructorOptions = codex.buildCodexConstructorOptions({
|
|
codexPath: ctx.binPath,
|
|
env: ctx.env,
|
|
apiKey: ctx.apiKey,
|
|
baseUrl: ctx.baseUrl,
|
|
injectedMcpServers: ctx.injectedMcpServers,
|
|
});
|
|
const threadOptions = codex.buildCodexThreadOptions({ cwd: ctx.cwd, model: ctx.model });
|
|
return codex.runCodexTurn({
|
|
prompt: ctx.prompt,
|
|
attachments: ctx.attachments,
|
|
constructorOptions,
|
|
threadOptions,
|
|
resumeThreadId: ctx.resumeSessionId,
|
|
emitter: ctx.emitter,
|
|
signal: ctx.signal,
|
|
});
|
|
},
|
|
// codex-sdk exposes no model catalog; the UI falls back to curated presets.
|
|
async listModels() { return []; },
|
|
},
|
|
copilot: {
|
|
async runTurn(ctx) {
|
|
const clientOptions = copilot.buildCopilotClientOptions({ cliPath: ctx.binPath });
|
|
const sessionOptions = copilot.buildCopilotSessionOptions({
|
|
model: ctx.model,
|
|
injectedMcpServers: ctx.injectedMcpServers,
|
|
toolIntegrationMode: ctx.toolIntegrationMode,
|
|
});
|
|
return copilot.runCopilotTurn({
|
|
prompt: ctx.prompt,
|
|
attachments: ctx.attachments,
|
|
clientOptions,
|
|
sessionOptions,
|
|
resumeSessionId: ctx.resumeSessionId,
|
|
toolIntegrationMode: ctx.toolIntegrationMode,
|
|
runtimeEnv: ctx.env,
|
|
emitter: ctx.emitter,
|
|
signal: ctx.signal,
|
|
});
|
|
},
|
|
async listModels(ctx) {
|
|
return copilot.listCopilotModels({
|
|
cliPath: ctx.binPath,
|
|
abortController: ctx.abortController,
|
|
signal: ctx.signal,
|
|
});
|
|
},
|
|
},
|
|
cursor: {
|
|
async runTurn(ctx) {
|
|
const authMode = ctx.cursorAuthMode === "cli-login" ? "cli-login" : "api-key";
|
|
if (authMode === "cli-login") {
|
|
return cursorCli.runCursorCliTurn({
|
|
prompt: ctx.prompt,
|
|
binPath: ctx.cursorCliBinPath || ctx.binPath,
|
|
cwd: ctx.cwd,
|
|
chatSessionId: ctx.chatSessionId,
|
|
getTempDir: ctx.getTempDir,
|
|
model: ctx.model,
|
|
env: ctx.env,
|
|
permissionMode: ctx.permissionMode,
|
|
resumeSessionId: ctx.resumeSessionId,
|
|
injectedMcpServers: ctx.injectedMcpServers,
|
|
emitter: ctx.emitter,
|
|
signal: ctx.signal,
|
|
});
|
|
}
|
|
const agentOptions = cursor.buildCursorAgentOptions({
|
|
apiKey: ctx.apiKey,
|
|
env: ctx.env,
|
|
model: ctx.model,
|
|
cwd: ctx.cwd,
|
|
injectedMcpServers: ctx.injectedMcpServers,
|
|
});
|
|
return cursor.runCursorTurn({
|
|
prompt: ctx.prompt,
|
|
attachments: ctx.attachments,
|
|
agentOptions,
|
|
runtimeEnv: ctx.env,
|
|
resumeSessionId: ctx.resumeSessionId,
|
|
emitter: ctx.emitter,
|
|
signal: ctx.signal,
|
|
});
|
|
},
|
|
async listModels(ctx) {
|
|
if (ctx.cursorAuthMode === "cli-login") {
|
|
return cursorCli.listCursorCliModels({
|
|
binPath: ctx.cursorCliBinPath || ctx.binPath,
|
|
env: ctx.env,
|
|
abortController: ctx.abortController,
|
|
signal: ctx.signal,
|
|
});
|
|
}
|
|
return cursor.listCursorModels({
|
|
env: ctx.env,
|
|
abortController: ctx.abortController,
|
|
signal: ctx.signal,
|
|
});
|
|
},
|
|
},
|
|
codebuddy: {
|
|
async runTurn(ctx) {
|
|
// Build the permission handler: when the CLI hits a security restriction,
|
|
// auto-confirm (auto mode) or prompt the user (confirm mode) instead of
|
|
// throwing an error.
|
|
const canUseTool = codebuddy.buildCodebuddyCanUseTool({
|
|
permissionMode: ctx.permissionMode,
|
|
chatSessionId: ctx.chatSessionId,
|
|
requestApproval: ctx.requestApprovalFromRenderer,
|
|
});
|
|
// Build the elicitation handler: forwards create/complete events to the
|
|
// renderer and waits for the user's decision via the session manager's
|
|
// pending-response map (resolved by the elicitation-response IPC).
|
|
// chatSessionId lets closeForChat cancel pendings when the chat closes.
|
|
const elicitation = codebuddy.buildCodebuddyElicitation(
|
|
ctx.emitter,
|
|
codebuddySessionManager.elicitationPending,
|
|
{ chatSessionId: ctx.chatSessionId },
|
|
);
|
|
const options = codebuddy.buildCodebuddyQueryOptions({
|
|
cwd: ctx.cwd,
|
|
model: ctx.model,
|
|
env: ctx.env,
|
|
injectedMcpServers: ctx.injectedMcpServers,
|
|
abortController: ctx.abortController,
|
|
resume: ctx.resumeSessionId,
|
|
pathToCodebuddyCode: ctx.binPath,
|
|
toolIntegrationMode: ctx.toolIntegrationMode,
|
|
// SDK 0.3.230 options
|
|
systemPrompt: ctx.systemPrompt,
|
|
effort: ctx.effort,
|
|
maxTurns: ctx.maxTurns,
|
|
maxBudgetUsd: ctx.maxBudgetUsd,
|
|
fallbackModel: ctx.fallbackModel,
|
|
sandbox: ctx.sandbox,
|
|
agents: ctx.agents,
|
|
outputFormat: ctx.outputFormat,
|
|
enableFileCheckpointing: ctx.enableFileCheckpointing,
|
|
traceId: ctx.traceId,
|
|
parentSpanId: ctx.parentSpanId,
|
|
hooks: codebuddy.buildCodebuddyHooks(ctx.emitter, {
|
|
toolIntegrationMode: ctx.toolIntegrationMode,
|
|
additionalHooks: ctx.hooks,
|
|
allowedCliCommandPrefix: ctx.skillsCliCommandPrefix,
|
|
}),
|
|
elicitation,
|
|
canUseTool,
|
|
});
|
|
|
|
const sessionKey = [
|
|
String(ctx.chatSessionId || ""),
|
|
"codebuddy",
|
|
String(ctx.binPath || ""),
|
|
"sdk",
|
|
].join("\u0000");
|
|
|
|
// Try V2 Session API first (persistent multi-turn), falling back to
|
|
// query() only for fields that SessionOptions does not support.
|
|
const hasQueryOnlyOptions = hasCodebuddyQueryOnlyOptions(options);
|
|
|
|
if (!hasQueryOnlyOptions) {
|
|
const sessionOptions = {
|
|
cwd: options.cwd,
|
|
model: options.model,
|
|
env: options.env,
|
|
pathToCodebuddyCode: options.pathToCodebuddyCode,
|
|
mcpServers: options.mcpServers,
|
|
permissionMode: options.permissionMode,
|
|
extraArgs: options.extraArgs,
|
|
systemPrompt: options.systemPrompt,
|
|
hooks: options.hooks,
|
|
elicitation: options.elicitation,
|
|
canUseTool: options.canUseTool,
|
|
includePartialMessages: true,
|
|
tools: options.tools,
|
|
disallowedTools: options.disallowedTools,
|
|
settingSources: options.settingSources,
|
|
maxTurns: options.maxTurns,
|
|
agents: options.agents,
|
|
thinking: options.thinking,
|
|
effort: options.effort,
|
|
};
|
|
const v2Result = await codebuddySessionManager.runTurn({
|
|
sessionKey,
|
|
prompt: ctx.prompt,
|
|
attachments: ctx.attachments,
|
|
options,
|
|
emitter: ctx.emitter,
|
|
sessionOptions,
|
|
resumeSessionId: ctx.resumeSessionId,
|
|
});
|
|
if (v2Result) return v2Result;
|
|
} else {
|
|
// Do not leave a warm V2 process with stale context while query() is
|
|
// resuming and advancing the same persisted conversation.
|
|
codebuddySessionManager.closeSession(sessionKey);
|
|
}
|
|
|
|
// Fallback: legacy query() per-turn (supports all Options fields).
|
|
return codebuddy.runCodebuddyTurn({
|
|
prompt: ctx.prompt,
|
|
attachments: ctx.attachments,
|
|
options,
|
|
emitter: ctx.emitter,
|
|
});
|
|
},
|
|
async steerTurn(ctx) {
|
|
const sessionKey = [
|
|
String(ctx.chatSessionId || ""),
|
|
"codebuddy",
|
|
String(ctx.binPath || ""),
|
|
"sdk",
|
|
].join("\u0000");
|
|
return codebuddySessionManager.steer({
|
|
sessionKey,
|
|
prompt: ctx.prompt,
|
|
attachments: ctx.attachments,
|
|
});
|
|
},
|
|
async listModels(ctx) {
|
|
return codebuddy.listCodebuddyModels({
|
|
pathToCodebuddyCode: ctx.binPath,
|
|
env: ctx.env,
|
|
abortController: ctx.abortController,
|
|
signal: ctx.signal,
|
|
});
|
|
},
|
|
},
|
|
opencode: {
|
|
async runTurn(ctx) {
|
|
return opencode.runOpenCodeTurn({
|
|
prompt: ctx.prompt,
|
|
systemPrompt: ctx.systemPrompt,
|
|
attachments: ctx.attachments,
|
|
cwd: ctx.cwd,
|
|
model: ctx.model,
|
|
env: ctx.env,
|
|
binPath: ctx.binPath,
|
|
injectedMcpServers: ctx.injectedMcpServers,
|
|
toolIntegrationMode: ctx.toolIntegrationMode,
|
|
skillsPathAllowlist: ctx.skillsPathAllowlist,
|
|
resumeSessionId: ctx.resumeSessionId,
|
|
emitter: ctx.emitter,
|
|
abortController: ctx.abortController,
|
|
});
|
|
},
|
|
async listModels(ctx) {
|
|
return opencode.listOpenCodeModels({
|
|
env: ctx.env,
|
|
binPath: ctx.binPath,
|
|
abortController: ctx.abortController,
|
|
signal: ctx.abortController?.signal || ctx.signal,
|
|
});
|
|
},
|
|
},
|
|
grok: {
|
|
async runTurn(ctx) {
|
|
// Default: ACP (`grok agent stdio`) with session-level mcpServers.
|
|
// Explicit fallback: NETCATTY_GROK_RUNTIME=streaming-json or ctx.grokRuntime.
|
|
const runtime = String(
|
|
ctx.grokRuntime
|
|
|| ctx.env?.NETCATTY_GROK_RUNTIME
|
|
|| process.env.NETCATTY_GROK_RUNTIME
|
|
|| "acp",
|
|
).toLowerCase();
|
|
if (runtime === "streaming-json" || runtime === "cli" || runtime === "headless") {
|
|
// Headless cannot know if -r restored history before the prompt is sent.
|
|
// Prefer native -r without seed (common success path). Stale-id fallback
|
|
// is handled on ACP (default runtime) via historySeed + session/new.
|
|
return grok.runGrokTurn({
|
|
prompt: ctx.prompt,
|
|
binPath: ctx.binPath,
|
|
cwd: ctx.cwd,
|
|
model: ctx.model,
|
|
env: ctx.env,
|
|
permissionMode: ctx.permissionMode,
|
|
toolIntegrationMode: ctx.toolIntegrationMode,
|
|
resumeSessionId: ctx.resumeSessionId,
|
|
injectedMcpServers: ctx.injectedMcpServers,
|
|
emitter: ctx.emitter,
|
|
signal: ctx.signal || ctx.abortController?.signal,
|
|
});
|
|
}
|
|
return grokAcp.runGrokAcpTurn({
|
|
prompt: ctx.prompt,
|
|
systemPrompt: ctx.systemPrompt,
|
|
binPath: ctx.binPath,
|
|
cwd: ctx.cwd,
|
|
model: ctx.model,
|
|
env: ctx.env,
|
|
permissionMode: ctx.permissionMode,
|
|
toolIntegrationMode: ctx.toolIntegrationMode,
|
|
resumeSessionId: ctx.resumeSessionId,
|
|
historySeed: ctx.historySeed,
|
|
injectedMcpServers: ctx.injectedMcpServers,
|
|
emitter: ctx.emitter,
|
|
signal: ctx.signal || ctx.abortController?.signal,
|
|
});
|
|
},
|
|
async listModels(ctx) {
|
|
const acpCatalog = await grokAcp.listGrokAcpModels({
|
|
binPath: ctx.binPath,
|
|
env: ctx.env,
|
|
abortController: ctx.abortController,
|
|
signal: ctx.signal || ctx.abortController?.signal,
|
|
});
|
|
if (acpCatalog.models.length > 0) {
|
|
return acpCatalog;
|
|
}
|
|
const fallbackCatalog = await grok.listGrokModels({
|
|
binPath: ctx.binPath,
|
|
env: ctx.env,
|
|
abortController: ctx.abortController,
|
|
signal: ctx.signal || ctx.abortController?.signal,
|
|
});
|
|
const currentModelId = acpCatalog.currentModelId || fallbackCatalog.currentModelId;
|
|
const models = fallbackCatalog.models.length > 0
|
|
? fallbackCatalog.models
|
|
: (currentModelId
|
|
? [grok.applyGrokReasoningFallback({ id: currentModelId, name: currentModelId })]
|
|
: []);
|
|
return {
|
|
currentModelId: grok.resolveGrokCatalogCurrentModelId(models, currentModelId),
|
|
models,
|
|
};
|
|
},
|
|
},
|
|
};
|
|
function getDriver(backend) {
|
|
const driver = DRIVER_REGISTRY[backend];
|
|
if (!driver) throw new Error(`No SDK driver registered for backend: ${backend}`);
|
|
return driver;
|
|
}
|
|
|
|
function listBackends() {
|
|
return Object.keys(DRIVER_REGISTRY);
|
|
}
|
|
|
|
module.exports = {
|
|
DRIVER_REGISTRY,
|
|
getDriver,
|
|
listBackends,
|
|
hasCodebuddyQueryOnlyOptions,
|
|
};
|