Files
NetMesh/electron/bridges/aiBridge/sdk/index.cjs
zhaolei 3c72efcb7f
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
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

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,
};