mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 16:02:45 +00:00
Floor the structured option catalog on the seed the client already holds
`structuredAgentSessionOptionCatalog` runs on the client, over a result a host of any version published — both `use-structured-agent-session.ts` and mobile's `use-mobile-structured-agent-options.ts` hand it straight off the `agentSession.options` wire. It mapped `result.models` through unchanged, so an empty list reached the snapshot, which correctly returns `[]` for an empty catalog, and the model pill and effort pill both vanished. On `origin/main` that was unreachable: this function unconditionally pushed `current.model`, so the list was never empty. Withholding unofficial ids removed that push, so the exposure is this PR's. Flooring only the host-side readers closes it for new hosts; a new client paired with an older host still hits it, which is the divergence `docs/reference/remote-wire-compatibility.md` covers when a host changes what it publishes. The floor uses `seed.models`, the catalog the client already passes in, so it holds for Claude and any future structured agent rather than one provider. The reader-side floors stay as defence in depth.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { CODEX_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-claude-codex'
|
||||
import {
|
||||
CLAUDE_SESSION_OPTION_CATALOG,
|
||||
CODEX_SESSION_OPTION_CATALOG
|
||||
} from './agent-session-option-catalog-claude-codex'
|
||||
import { buildNativeChatSessionOptionSnapshot } from './native-chat-session-option-snapshot'
|
||||
import { createNativeChatSessionOptionRecord } from './native-chat-session-option-state'
|
||||
import {
|
||||
@@ -93,6 +96,33 @@ describe('structured agent session options', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ agent: 'codex' as const, seed: CODEX_SESSION_OPTION_CATALOG },
|
||||
{ agent: 'claude' as const, seed: CLAUDE_SESSION_OPTION_CATALOG }
|
||||
])(
|
||||
'keeps the options row when an older host publishes an empty list: $agent',
|
||||
({ agent, seed }) => {
|
||||
// Wire case, not a unit case: `structuredAgentSessionOptionCatalog` runs on the client over
|
||||
// whatever a host published. A host that predates the readers' own seed floor still sends
|
||||
// `models: []` beside a current model for a restored thread, and the row must survive it.
|
||||
const state = applyStructuredAgentSessionOptions(
|
||||
createStructuredAgentSessionOptionState(agent),
|
||||
seed,
|
||||
{ models: [], current: { model: 'unlisted-from-an-old-host' } }
|
||||
)
|
||||
|
||||
const snapshot = structuredAgentSessionOptionSnapshot(state)
|
||||
expect(snapshot.map((descriptor) => descriptor.id)).toEqual(['model', 'effort'])
|
||||
const model = snapshot[0]!
|
||||
// Only official names reach the pill; the raw id is neither offered nor shown.
|
||||
expect(model).toMatchObject({ valueSource: 'unknown' })
|
||||
expect(model.kind.type === 'select' ? model.kind.choices.map((c) => c.value) : []).toEqual(
|
||||
seed.models.map((seeded) => seeded.id)
|
||||
)
|
||||
expect(snapshot[1]).toMatchObject({ id: 'effort', settable: true, kind: { type: 'select' } })
|
||||
}
|
||||
)
|
||||
|
||||
it('projects live options as directly settable descriptors', () => {
|
||||
const state = applyStructuredAgentSessionOptions(
|
||||
createStructuredAgentSessionOptionState('codex'),
|
||||
|
||||
@@ -52,7 +52,12 @@ export function structuredAgentSessionOptionCatalog(
|
||||
// Why: only ids the provider lists may be offered. An unlisted `current.model` still
|
||||
// reaches the snapshot through the record, which draws it as tracked-but-unmatched —
|
||||
// a neutral pill plus effort from `unknownModelOptions` — instead of a raw-id row.
|
||||
const models: CatalogModel[] = result.models.map(discoveredModel)
|
||||
const discovered: CatalogModel[] = result.models.map(discoveredModel)
|
||||
// Why here and not only in each host-side reader: this runs on the client, on a result a host
|
||||
// of any version published. An empty list offers nothing to pick and carries no options, so the
|
||||
// snapshot drops the whole row — and a host that predates the readers' own seed floor publishes
|
||||
// exactly that for a restored thread whose provider listed nothing. The seed is agent-agnostic.
|
||||
const models = discovered.length > 0 ? discovered : [...seed.models]
|
||||
return { ...seed, models, defaultModelIsCliDefault: true }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user