mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
fix(native-chat): a remembered effort a saved Pi listing lacks no longer refuses new chats (carries #31576) (#33637)
New native-chat Pi sessions are no longer refused before Pi starts when a remembered effort (e.g. xhigh) isn't listed in Orca's saved `pi --list-models` row: a remembered effort the row doesn't list moves to the nearest listed level (explicit --model/--effort stay strict), and the remembered preference itself is left intact. Pi clamps unsupported levels itself. Fixes #31493 Supersedes #31576 Co-authored-by: 王浩生 <22971846+hualong1009@users.noreply.github.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import type {
|
||||
AgentSessionRecord
|
||||
} from '../../shared/agent-session-record'
|
||||
import type { AgentSessionModelOption } from '../../shared/agent-session-wire'
|
||||
import { nearestAgentEffort } from './session-tabs/structured-launch-seed-listing'
|
||||
|
||||
export function replaceAgentSessionRecordOptions(
|
||||
record: AgentSessionRecord,
|
||||
@@ -47,6 +48,35 @@ export function agentSessionOptionAdmitted(
|
||||
)
|
||||
}
|
||||
|
||||
/** Fit a remembered seed to its model's saved row: an effort the row lacks moves to the nearest listed
|
||||
* level, other unlisted values drop; keys the caller named this launch keep strict admission. */
|
||||
export function seedOptionsAdmittedOnSavedListing(
|
||||
provider: string,
|
||||
seed: Readonly<Record<string, string>>,
|
||||
models: AgentSessionAdmissionModels,
|
||||
explicit: ReadonlySet<string>
|
||||
): Record<string, string> | undefined {
|
||||
const admitted: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(seed)) {
|
||||
if (explicit.has(key) || agentSessionOptionAdmitted(provider, models, seed.model, key, value)) {
|
||||
admitted[key] = value
|
||||
continue
|
||||
}
|
||||
const row = models?.find((entry) => entry.id === seed.model)
|
||||
const nearest =
|
||||
key === 'effort' && row
|
||||
? nearestAgentEffort(
|
||||
value,
|
||||
row.efforts.map((choice) => choice.value)
|
||||
)
|
||||
: undefined
|
||||
if (nearest) {
|
||||
admitted[key] = nearest
|
||||
}
|
||||
}
|
||||
return Object.keys(admitted).length > 0 ? admitted : undefined
|
||||
}
|
||||
|
||||
export function assertAgentSessionOptionsAdmitted(
|
||||
provider: string,
|
||||
models: AgentSessionAdmissionModels,
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import type { AgentSessionAttachParams } from '../../../native-chat/agent-session-wire/structured-agent-session-attach'
|
||||
import type { StructuredAgentSessionHost } from '../../../native-chat/agent-session-wire/structured-agent-session-host'
|
||||
import type { OrcaRuntimeService } from '../../orca-runtime'
|
||||
import { prepareStructuredAgentSessionCreateForWorktree } from './structured-agent-session-create'
|
||||
|
||||
const listing = (models: unknown[]) => ({ origin: 'probe', models })
|
||||
|
||||
async function prepareWithSeed(
|
||||
seed: Readonly<Record<string, string>> | undefined,
|
||||
options: Readonly<Record<string, string>> | undefined,
|
||||
listingModels: unknown[] | null
|
||||
): Promise<AgentSessionAttachParams['options']> {
|
||||
const attachParams = {
|
||||
envelope: {
|
||||
sessionId: 'pi_session_1',
|
||||
clientOperationId: 'operation-1',
|
||||
expectedRuntimeFence: null,
|
||||
payloadFingerprint: ''
|
||||
},
|
||||
location: {
|
||||
executionHostId: 'local',
|
||||
wslDistro: null,
|
||||
workspaceId: 'workspace-1',
|
||||
workspaceKind: 'git-worktree'
|
||||
},
|
||||
provider: 'pi',
|
||||
agent: 'pi',
|
||||
accountHome: { variable: 'HOME', path: '/home/user' },
|
||||
runtimeKind: 'native' as const,
|
||||
...(seed ? { options: seed } : {})
|
||||
}
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the stub implements only the members prepare reaches.
|
||||
const host = {
|
||||
deps: {
|
||||
modelCatalog: listingModels ? { read: vi.fn(async () => listing(listingModels)) } : undefined
|
||||
}
|
||||
} as unknown as StructuredAgentSessionHost
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the stub implements only the members prepare reaches.
|
||||
const runtime = {
|
||||
resolveStructuredAgentSessionCreateIntent: vi.fn(async () => attachParams),
|
||||
resolveStructuredAgentSessionLocalWorkspacePath: vi.fn(async () => '/work/repo')
|
||||
} as unknown as OrcaRuntimeService
|
||||
const prepared = await prepareStructuredAgentSessionCreateForWorktree({
|
||||
runtime,
|
||||
ensureHost: async () => host,
|
||||
envelope: attachParams.envelope,
|
||||
worktree: 'id:workspace-1',
|
||||
agent: 'pi',
|
||||
caller: { callerKey: 'caller-1' },
|
||||
...(options ? { options } : {})
|
||||
})
|
||||
return prepared.attachParams.options
|
||||
}
|
||||
|
||||
const piRow = (efforts: readonly string[]) => ({
|
||||
id: 'some/model',
|
||||
label: 'Some Model',
|
||||
isDefault: false,
|
||||
efforts: efforts.map((value) => ({ value, label: value }))
|
||||
})
|
||||
|
||||
it('moves a remembered effort the saved probe row lacks to its nearest listed level', async () => {
|
||||
// Pi's live menu offers xhigh where its --list-models probe stops at high.
|
||||
const options = await prepareWithSeed({ model: 'some/model', effort: 'xhigh' }, undefined, [
|
||||
piRow(['off', 'minimal', 'low', 'medium', 'high'])
|
||||
])
|
||||
expect(options).toEqual({ model: 'some/model', effort: 'high' })
|
||||
})
|
||||
|
||||
it('drops a remembered effort its saved row no longer admits', async () => {
|
||||
const options = await prepareWithSeed({ model: 'some/model', effort: 'off' }, undefined, [
|
||||
piRow([])
|
||||
])
|
||||
expect(options).toEqual({ model: 'some/model' })
|
||||
})
|
||||
|
||||
it('keeps an effort this launch named itself, even one its row does not admit', async () => {
|
||||
// Explicit options replace the saved selection; only their own values stay the caller's picks.
|
||||
const options = await prepareWithSeed({ model: 'some/model' }, { effort: 'off' }, [piRow([])])
|
||||
expect(options).toEqual({ effort: 'off' })
|
||||
})
|
||||
|
||||
it('keeps a remembered effort when no saved rows name its model', async () => {
|
||||
const options = await prepareWithSeed({ model: 'some/model', effort: 'off' }, undefined, [
|
||||
{ id: 'other/model', label: 'Other Model', isDefault: false, efforts: [] }
|
||||
])
|
||||
expect(options).toEqual({ model: 'some/model', effort: 'off' })
|
||||
})
|
||||
@@ -35,6 +35,7 @@ import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../shared/layout-owner-switc
|
||||
import { structuredAgentSessionClosedBeforeCreate } from './structured-agent-session-close-before-create'
|
||||
import { structuredLaunchSeedOnSavedListing } from '../../session-tabs/structured-launch-seed-listing'
|
||||
import type { ChatTabPlacement } from '../../agent-session/agent-session-tab-layout'
|
||||
import { seedOptionsAdmittedOnSavedListing } from '../../agent-session-record-options'
|
||||
|
||||
export type PreparedStructuredAgentSessionCreate = {
|
||||
host: StructuredAgentSessionHost
|
||||
@@ -165,13 +166,23 @@ export async function prepareStructuredAgentSessionCreateForWorktree(args: {
|
||||
await catalogFolder()
|
||||
)
|
||||
: undefined
|
||||
// Fit remembered values to the saved row instead of refusing the create (#31493).
|
||||
const admittedSeed =
|
||||
options && catalogModels && !args.options?.model
|
||||
? seedOptionsAdmittedOnSavedListing(
|
||||
resolved.provider,
|
||||
options,
|
||||
catalogModels,
|
||||
new Set(Object.keys(args.options ?? {}))
|
||||
)
|
||||
: options
|
||||
return {
|
||||
host,
|
||||
...(catalogModels ? { catalogModels } : {}),
|
||||
...(hostLaunchDirectory ? { hostLaunchDirectory } : {}),
|
||||
attachParams: {
|
||||
...resolvedAttach,
|
||||
...(options ? { options } : {}),
|
||||
...(admittedSeed ? { options: admittedSeed } : {}),
|
||||
...(args.tabId ? { surfaceTabId: args.tabId } : {}),
|
||||
provider: resolved.provider,
|
||||
agent: resolved.agent,
|
||||
|
||||
Reference in New Issue
Block a user