feat: Address review comments, add stage/unstage functionality, conflict resolution improvements, support for Sonnet 4.6

This commit is contained in:
gsxdsm
2026-02-18 18:58:33 -08:00
parent df9a6314da
commit 983eb21faa
66 changed files with 2317 additions and 823 deletions

View File

@@ -343,6 +343,18 @@ export class ClaudeProvider extends BaseProvider {
tier: 'premium' as const,
default: true,
},
{
id: 'claude-sonnet-4-6',
name: 'Claude Sonnet 4.6',
modelString: 'claude-sonnet-4-6',
provider: 'anthropic',
description: 'Balanced performance and cost with enhanced reasoning',
contextWindow: 200000,
maxOutputTokens: 128000,
supportsVision: true,
supportsTools: true,
tier: 'standard' as const,
},
{
id: 'claude-sonnet-4-20250514',
name: 'Claude Sonnet 4',

View File

@@ -245,15 +245,9 @@ async function resolveCodexExecutionPlan(options: ExecuteOptions): Promise<Codex
throw new Error(ERROR_CODEX_CLI_REQUIRED);
}
if (!cliAuthenticated) {
throw new Error(ERROR_CODEX_AUTH_REQUIRED);
}
return {
mode: CODEX_EXECUTION_MODE_CLI,
cliPath,
openAiApiKey,
};
// At this point, neither hasCliNativeAuth nor hasApiKey is true,
// so authentication is required regardless.
throw new Error(ERROR_CODEX_AUTH_REQUIRED);
}
function getEventType(event: Record<string, unknown>): string | null {

View File

@@ -15,6 +15,9 @@ const SDK_HISTORY_HEADER = 'Current request:\n';
const DEFAULT_RESPONSE_TEXT = '';
const SDK_ERROR_DETAILS_LABEL = 'Details:';
type SdkReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
const SDK_REASONING_EFFORTS = new Set<string>(['minimal', 'low', 'medium', 'high', 'xhigh']);
type PromptBlock = {
type: string;
text?: string;
@@ -103,9 +106,6 @@ export async function* executeCodexSdkQuery(
// The model must be passed to startThread/resumeThread so the SDK
// knows which model to use for the conversation. Without this,
// the SDK may use a default model that the user doesn't have access to.
type SdkReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
const SDK_REASONING_EFFORTS = new Set<string>(['minimal', 'low', 'medium', 'high', 'xhigh']);
const threadOptions: {
model?: string;
modelReasoningEffort?: SdkReasoningEffort;
@@ -118,6 +118,7 @@ export async function* executeCodexSdkQuery(
// Add reasoning effort to thread options if model supports it
if (
options.reasoningEffort &&
options.model &&
supportsReasoningEffort(options.model) &&
options.reasoningEffort !== 'none' &&
SDK_REASONING_EFFORTS.has(options.reasoningEffort)

View File

@@ -42,7 +42,7 @@ import {
const logger = createLogger('CopilotProvider');
// Default bare model (without copilot- prefix) for SDK calls
const DEFAULT_BARE_MODEL = 'claude-sonnet-4.5';
const DEFAULT_BARE_MODEL = 'claude-sonnet-4.6';
// =============================================================================
// SDK Event Types (from @github/copilot-sdk)

View File

@@ -549,8 +549,15 @@ export class OpencodeProvider extends CliProvider {
// sdkSessionId IS set — the CLI will receive `--session <id>`.
// If that session no longer exists, intercept the error and retry fresh.
//
// To avoid buffering the entire stream in memory for long-lived sessions,
// we only buffer an initial window of messages until we observe a healthy
// (non-error) message. Once a healthy message is seen, we flush the buffer
// and switch to direct passthrough, while still watching for session errors
// via isSessionNotFoundError on any subsequent error messages.
const buffered: ProviderMessage[] = [];
let sessionError = false;
let seenHealthyMessage = false;
try {
for await (const msg of super.executeQuery(options)) {
@@ -565,13 +572,30 @@ export class OpencodeProvider extends CliProvider {
break; // stop consuming the failed stream
}
// Non-session error — clean and buffer
// Non-session error — clean it
if (msg.error && typeof msg.error === 'string') {
msg.error = OpencodeProvider.cleanErrorMessage(msg.error);
}
} else {
// A non-error message is a healthy signal — stop buffering after this
seenHealthyMessage = true;
}
buffered.push(msg);
if (seenHealthyMessage && buffered.length > 0) {
// Flush the pre-healthy buffer first, then switch to passthrough
for (const bufferedMsg of buffered) {
yield bufferedMsg;
}
buffered.length = 0;
}
if (seenHealthyMessage) {
// Passthrough mode — yield directly without buffering
yield msg;
} else {
// Still in initial window — buffer until we see a healthy message
buffered.push(msg);
}
}
} catch (error) {
// Also handle thrown exceptions (e.g. from mapError in cli-provider)
@@ -602,12 +626,15 @@ export class OpencodeProvider extends CliProvider {
}
yield retryMsg;
}
} else {
// No session error — flush buffered messages to the consumer
} else if (buffered.length > 0) {
// No session error and still have buffered messages (stream ended before
// any healthy message was observed) — flush them to the consumer
for (const msg of buffered) {
yield msg;
}
}
// If seenHealthyMessage is true, all messages have already been yielded
// directly in passthrough mode — nothing left to flush.
}
/**
@@ -673,7 +700,7 @@ export class OpencodeProvider extends CliProvider {
return {
type: 'error',
session_id: finishEvent.sessionID,
error: finishEvent.part.error,
error: OpencodeProvider.cleanErrorMessage(finishEvent.part.error),
};
}
@@ -682,7 +709,7 @@ export class OpencodeProvider extends CliProvider {
return {
type: 'error',
session_id: finishEvent.sessionID,
error: 'Step execution failed',
error: OpencodeProvider.cleanErrorMessage('Step execution failed'),
};
}
@@ -705,8 +732,10 @@ export class OpencodeProvider extends CliProvider {
case 'tool_error': {
const toolErrorEvent = openCodeEvent as OpenCodeBaseEvent;
// Extract error message from part.error
const errorMessage = toolErrorEvent.part?.error || 'Tool execution failed';
// Extract error message from part.error and clean ANSI codes
const errorMessage = OpencodeProvider.cleanErrorMessage(
toolErrorEvent.part?.error || 'Tool execution failed'
);
return {
type: 'error',
@@ -719,16 +748,8 @@ export class OpencodeProvider extends CliProvider {
// The event format includes the tool name, call ID, and state with input/output.
// Handle both 'tool_use' (actual CLI format) and 'tool_call' (legacy/alternative) for robustness.
case 'tool_use': {
const toolUseEvent = openCodeEvent as OpenCodeBaseEvent;
const part = toolUseEvent.part as OpenCodePart & {
callID?: string;
tool?: string;
state?: {
status?: string;
input?: unknown;
output?: string;
};
};
const toolUseEvent = openCodeEvent as OpenCodeToolUseEvent;
const part = toolUseEvent.part;
// Generate a tool use ID if not provided
const toolUseId = part?.callID || part?.call_id || generateToolUseId();
@@ -898,9 +919,9 @@ export class OpencodeProvider extends CliProvider {
default: true,
},
{
id: 'opencode/glm-4.7-free',
name: 'GLM 4.7 Free',
modelString: 'opencode/glm-4.7-free',
id: 'opencode/glm-5-free',
name: 'GLM 5 Free',
modelString: 'opencode/glm-5-free',
provider: 'opencode',
description: 'OpenCode free tier GLM model',
supportsTools: true,
@@ -918,19 +939,19 @@ export class OpencodeProvider extends CliProvider {
tier: 'basic',
},
{
id: 'opencode/grok-code',
name: 'Grok Code (Free)',
modelString: 'opencode/grok-code',
id: 'opencode/kimi-k2.5-free',
name: 'Kimi K2.5 Free',
modelString: 'opencode/kimi-k2.5-free',
provider: 'opencode',
description: 'OpenCode free tier Grok model for coding',
description: 'OpenCode free tier Kimi model for coding',
supportsTools: true,
supportsVision: false,
tier: 'basic',
},
{
id: 'opencode/minimax-m2.1-free',
name: 'MiniMax M2.1 Free',
modelString: 'opencode/minimax-m2.1-free',
id: 'opencode/minimax-m2.5-free',
name: 'MiniMax M2.5 Free',
modelString: 'opencode/minimax-m2.5-free',
provider: 'opencode',
description: 'OpenCode free tier MiniMax model',
supportsTools: true,
@@ -1052,7 +1073,7 @@ export class OpencodeProvider extends CliProvider {
*
* OpenCode CLI output format (one model per line):
* opencode/big-pickle
* opencode/glm-4.7-free
* opencode/glm-5-free
* anthropic/claude-3-5-haiku-20241022
* github-copilot/claude-3.5-sonnet
* ...

View File

@@ -94,7 +94,7 @@ export interface StreamingQueryOptions extends SimpleQueryOptions {
/**
* Default model to use when none specified
*/
const DEFAULT_MODEL = 'claude-sonnet-4-20250514';
const DEFAULT_MODEL = 'claude-sonnet-4-6';
/**
* Execute a simple query and return the text result