Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
524 changes: 524 additions & 0 deletions docs/plans/2026-06-01-provider-neutral-naming-cleanup-plan.md

Large diffs are not rendered by default.

34 changes: 28 additions & 6 deletions src/main/services/claude-code-implementer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,32 +51,53 @@ const log = createLogger({ component: 'ClaudeCodeImplementer' })

const CLAUDE_EFFORT_VARIANTS = { low: {}, medium: {}, high: {} }
const CLAUDE_OPUS_EFFORT_VARIANTS = { low: {}, medium: {}, high: {}, xhigh: {}, max: {} }
const CLAUDE_DEFAULT_CONTEXT_LIMIT = 200_000
const CLAUDE_ONE_M_CONTEXT_LIMIT = 1_000_000

const CLAUDE_MODELS = [
{
id: 'opus',
name: 'Opus 4.8',
limit: { context: 200000, output: 32000 },
sdkModel: 'opus[1m]',
limit: { context: CLAUDE_ONE_M_CONTEXT_LIMIT, output: 32000 },
variants: CLAUDE_OPUS_EFFORT_VARIANTS,
defaultVariant: 'high',
supportsFastMode: true
},
{
id: 'sonnet',
name: 'Sonnet 4.6',
limit: { context: 200000, output: 16000 },
sdkModel: 'sonnet[1m]',
limit: { context: CLAUDE_ONE_M_CONTEXT_LIMIT, output: 16000 },
variants: CLAUDE_EFFORT_VARIANTS,
defaultVariant: 'high'
},
{
id: 'haiku',
name: 'Haiku 4.5',
limit: { context: 200000, output: 8192 },
sdkModel: 'haiku',
limit: { context: CLAUDE_DEFAULT_CONTEXT_LIMIT, output: 8192 },
variants: CLAUDE_EFFORT_VARIANTS,
defaultVariant: 'high'
}
]

function resolveClaudeModelDef(modelId: string | undefined): (typeof CLAUDE_MODELS)[number] | null {
if (!modelId) return null

const runtimeModelId = resolveRuntimeModelId(modelId, 'anthropic')
return (
CLAUDE_MODELS.find(
(model) => model.id === modelId || model.id === runtimeModelId || model.sdkModel === modelId
) ?? null
)
}

function resolveClaudeSdkModel(modelId: string | undefined): string | undefined {
const model = resolveClaudeModelDef(modelId)
return model?.sdkModel ?? modelId
}

function asNumberOrNull(value: unknown): number | null | undefined {
if (value === null) return null
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
Expand Down Expand Up @@ -853,7 +874,8 @@ export class ClaudeCodeImplementer implements AgentSdkImplementer, AgentRuntimeA

// Resolve effort level from variant selection (default per model)
const resolvedModel = modelOverride?.modelID ?? this.selectedModel
const modelDef = CLAUDE_MODELS.find((m) => m.id === resolvedModel)
const modelDef = resolveClaudeModelDef(resolvedModel)
const sdkModel = resolveClaudeSdkModel(resolvedModel)
const effortLevel = (modelOverride?.variant ??
this.selectedVariant ??
modelDef?.defaultVariant ??
Expand Down Expand Up @@ -886,7 +908,7 @@ export class ClaudeCodeImplementer implements AgentSdkImplementer, AgentRuntimeA
permissionMode: sdkPermissionMode,
abortController: session.abortController,
...(this.thinkingSupported ? { maxThinkingTokens: 31999 } : {}),
model: resolvedModel,
model: sdkModel,
includePartialMessages: true,
enableFileCheckpointing: true,
settingSources: ['user', 'project', 'local'],
Expand Down Expand Up @@ -2303,7 +2325,7 @@ export class ClaudeCodeImplementer implements AgentSdkImplementer, AgentRuntimeA
limit: { context: number; input?: number; output: number }
supportsFastMode?: boolean
} | null> {
const model = CLAUDE_MODELS.find((m) => m.id === modelId)
const model = resolveClaudeModelDef(modelId)
if (!model) return null
return {
id: model.id,
Expand Down
45 changes: 43 additions & 2 deletions src/main/services/codex-activity-mapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { SessionActivityCreate, SessionActivityKind, SessionActivityTone } from '../db'
import type { CodexManagerEvent } from './codex-app-server-manager'
import { asObject, asString } from './codex-utils'
import { asNumber, asObject, asString } from './codex-utils'
import {
buildCodexPlanUpdateSummary,
buildCodexUpdatePlanCallId,
Expand Down Expand Up @@ -70,6 +70,26 @@ export function mapCodexManagerEventToActivity(
): SessionActivityCreate | null {
const payload = asObject(event.payload)

if (event.kind === 'error') {
return buildActivity(
sessionId,
agentSessionId,
event,
'session.error',
'error',
event.message ??
asString(payload?.message) ??
asString(payload?.error) ??
asString(asObject(payload?.error)?.message) ??
'Runtime error',
{
...(payload ?? {}),
method: event.method,
message: event.message
}
)
}

switch (event.method) {
case 'item.started':
case 'item/started':
Expand Down Expand Up @@ -164,14 +184,35 @@ export function mapCodexManagerEventToActivity(

case 'runtime.error':
case 'runtime/error':
case 'error':
return buildActivity(
sessionId,
agentSessionId,
event,
'session.error',
'error',
asString(payload?.message) ?? asString(payload?.error) ?? 'Runtime error'
asString(payload?.message) ??
asString(payload?.error) ??
asString(asObject(payload?.error)?.message) ??
'Runtime error'
)

case 'session/exited': {
const exitCode = asNumber(payload?.exitCode)
const signal = asString(payload?.signal)
const errored =
payload?.errored === true ||
(exitCode !== undefined && exitCode !== 0) ||
(signal !== undefined && signal.length > 0)
return buildActivity(
sessionId,
agentSessionId,
event,
errored ? 'session.error' : 'session.info',
errored ? 'error' : 'info',
event.message ?? 'Codex app-server exited'
)
}

case 'thread/name/updated':
return buildActivity(
Expand Down
74 changes: 67 additions & 7 deletions src/main/services/codex-app-server-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import { CODEX_DEFAULT_MODEL } from './codex-models'
import { type CodexLaunchSpec } from './codex-binary-resolver'
import { spawnLaunchSpec } from './command-launch-utils'
import { getCodexRpcDumper } from './codex-rpc-dumper'
import {
buildCodexDynamicToolSpecs,
handleCodexDynamicToolCall,
type CodexDynamicToolConfig
} from './codex-dynamic-tools'

const log = createLogger({ component: 'CodexAppServerManager' })

Expand Down Expand Up @@ -89,6 +94,7 @@ export interface CodexSessionContext {
pendingUserInputs: Map<string, PendingUserInputRequest>
nextRequestId: number
stopping: boolean
dynamicTools?: CodexDynamicToolConfig
}

// ── Start session input ───────────────────────────────────────────
Expand All @@ -101,6 +107,7 @@ export interface CodexStartSessionOptions {
codexBinaryPath?: string
codexHomePath?: string
codexLaunchSpec?: CodexLaunchSpec
dynamicTools?: CodexDynamicToolConfig
}

// ── Turn input ────────────────────────────────────────────────────
Expand Down Expand Up @@ -389,7 +396,8 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
pendingApprovals: new Map(),
pendingUserInputs: new Map(),
nextRequestId: 1,
stopping: false
stopping: false,
...(options.dynamicTools ? { dynamicTools: options.dynamicTools } : {})
}

this.sessions.set(tempThreadId, context)
Expand Down Expand Up @@ -423,17 +431,24 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
}

// Open thread: resume or start fresh
const threadStartParams = {
const threadOpenBaseParams = {
model: options.model ?? null,
cwd: resolvedCwd,
...getDefaultCodexRuntimeConfig()
}
const dynamicTools = options.dynamicTools
? buildCodexDynamicToolSpecs(options.dynamicTools)
: []
const threadStartParams = {
...threadOpenBaseParams,
...(dynamicTools.length > 0 ? { dynamicTools } : {})
}

let threadOpenResponse: unknown
if (options.resumeThreadId) {
try {
threadOpenResponse = await this.sendRequest(context, 'thread/resume', {
...threadStartParams,
...threadOpenBaseParams,
threadId: options.resumeThreadId
})
} catch (error) {
Expand Down Expand Up @@ -910,7 +925,13 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
activeTurnId: null,
error: code === 0 ? context.session.error : message
})
this.emitLifecycleEvent(context, 'session/exited', message)
this.emitLifecycleEvent(context, 'session/exited', message, {
payload: {
exitCode: code,
signal,
errored: code !== 0 || signal !== null
}
})

// Remove from sessions map
for (const [key, ctx] of this.sessions.entries()) {
Expand Down Expand Up @@ -1055,7 +1076,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
return
}

if (statusType === 'error') {
if (statusType === 'error' || statusType === 'systemError') {
this.updateSession(context, {
status: 'error',
activeTurnId: null
Expand Down Expand Up @@ -1109,6 +1130,44 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
requestId,
payload: request.params
})

if (request.method === 'item/tool/call') {
this.respondToDynamicToolCall(context, request)
}
}

private respondToDynamicToolCall(context: CodexSessionContext, request: JsonRpcRequest): void {
handleCodexDynamicToolCall(context.dynamicTools, asObject(request.params) ?? {})
.then((result) => {
if (result) {
this.writeMessage(context, {
jsonrpc: '2.0',
id: request.id,
result
})
return
}

this.writeMessage(context, {
jsonrpc: '2.0',
id: request.id,
error: {
code: -32601,
message: 'Dynamic tool is not available in this Xuanpu Codex session.'
}
})
})
.catch((error) => {
const message = error instanceof Error ? error.message : String(error)
this.writeMessage(context, {
jsonrpc: '2.0',
id: request.id,
error: {
code: -32000,
message
}
})
})
}

private handleResponse(context: CodexSessionContext, response: JsonRpcResponse): void {
Expand Down Expand Up @@ -1180,7 +1239,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
context: CodexSessionContext,
method: string,
message: string,
extra?: { turnId?: string }
extra?: { turnId?: string; payload?: unknown }
): void {
this.emitEvent({
id: randomUUID(),
Expand All @@ -1190,7 +1249,8 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
createdAt: new Date().toISOString(),
method,
message,
...(extra?.turnId ? { turnId: extra.turnId } : {})
...(extra?.turnId ? { turnId: extra.turnId } : {}),
...(extra && 'payload' in extra ? { payload: extra.payload } : {})
})
}

Expand Down
Loading