Skip to content
Merged
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
1 change: 1 addition & 0 deletions apps/desktop/src/main/ipc-methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const IPC_METHODS = [
'attachments.read',
'providers.detect',
'providers.models',
'providers.efforts',
'providers.plan',
'providers.install',
'providers.cancelInstall',
Expand Down
76 changes: 76 additions & 0 deletions apps/desktop/src/main/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,66 @@ async function probeAcpModels(
}
}

/**
* Thought/reasoning levels for one kind resolved against a specific model.
* Effort is per-model on some agents (OpenCode's `effort` option appears
* only for reasoning-capable models), so the picker asks with the selected
* model rather than trusting the default-model catalog probe. Throws on
* transport failure so the caller keeps its catalog fallback.
*/
async function probeEffortsForModel(
kind: DriverKind,
modelId: string | null | undefined,
): Promise<{ id: string; label: string; description?: string; current?: boolean }[]> {
const { detectDriver } = await import('@ari/providers/detector')
const environment = await resolveDetectionEnvironment()
const detection = await detectDriver(kind, environment)
if (!detection.binaryPath) return []
if (process.env['ARI_ACP'] === '0') return []
const launch = resolveAcpLaunch(kind, { cliBinaryPath: detection.binaryPath }, environment)
if (launch === null) return []
const { AcpConnection } = await import('@ari/providers/acp/connection')
const probeEnv = processEnvWithPath(environment.pathEnv)
const probe = probeLaunch(launch)
const connection = await AcpConnection.connect({
launch: probe,
cwd: homedir(),
initializeTimeoutMs: 20_000,
runtimeEnv: { ...probeEnv, ...probe.env },
})
try {
const created = await connection.newSession(homedir())
let configOptions = created.configOptions ?? []
if (typeof modelId === 'string' && modelId.length > 0) {
const modelOption = configOptions.find((o) => o.category === 'model' && o.type === 'select')
const offered = modelOption?.options?.some((v) => v.value === modelId) === true
if (modelOption?.id !== undefined && offered) {
const refreshed = await connection.setConfigOption(
created.sessionId as string,
modelOption.id,
modelId,
)
if (refreshed !== null && refreshed !== undefined && refreshed.length > 0) {
configOptions = refreshed
}
}
}
const catalog = mergeEffortCatalogs(
thoughtEffortsFromSession({ ...created, configOptions }),
thoughtEffortsFromMeta(connection.initialize._meta),
)
Comment thread
j35dev marked this conversation as resolved.
if (catalog.options.length > 0) setDynamicEfforts(kind, catalog)
return catalog.options.map((option) => ({
id: option.id,
label: option.label,
...(option.description !== undefined ? { description: option.description } : {}),
...(catalog.currentId === option.id ? { current: true as const } : {}),
}))
} finally {
await connection.shutdown()
}
}

/**
* Every CLI kind probed by `providers.detect`, regardless of hydration state,
* so the providers grid is complete even while drivers are still registering.
Expand Down Expand Up @@ -1073,6 +1133,22 @@ export function registerRpc(contents: WebContents, options: RegisterRpcOptions =
})
})

// Model-aware effort lookup for the picker: a throwaway ACP session has
// the selected model applied, then reports that model's own thought
// levels. Empty on any failure — the caller keeps the catalog fallback.
r.register('providers.efforts', async (params) => {
try {
const efforts = await probeEffortsForModel(params.kind, params.modelId ?? null)
return { efforts }
} catch (error) {
log.debug('model-specific effort probe failed; keeping catalog fallback', {
kind: params.kind,
error: String(error),
})
return { efforts: [] }
}
})

r.register('window.minimize', () => {
BrowserWindow.fromWebContents(contents)?.minimize()
return { done: true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -580,8 +580,8 @@ export function ModelSelector({
{!searching && fallbackLabel !== null && activeKind !== null ? (
<div className="flex items-start gap-2 border-t border-border px-2 py-1.5 text-2xs leading-relaxed text-fg-subtle">
<span className="min-w-0 flex-1">
{driverLabel(activeKind)} has not reported its own models — this is{' '}
{fallbackLabel}. It may not accept every entry.
{driverLabel(activeKind)} has not reported its own models — this is {fallbackLabel}.
It may not accept every entry.
</span>
<button
type="button"
Expand Down
Loading
Loading