mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
188 lines
5.8 KiB
TypeScript
188 lines
5.8 KiB
TypeScript
import type { AgentType } from '../../agent-status-types'
|
|
import type {
|
|
CatalogMidSessionApply,
|
|
AgentSessionOptionCatalog
|
|
} from '../../agent-session-option-catalog-types'
|
|
import type { SessionOptionValue, SessionOptionValueSource } from './native-chat-session-options'
|
|
|
|
export type TrackedNativeChatSessionOption = {
|
|
value: SessionOptionValue
|
|
source: Exclude<SessionOptionValueSource, 'unknown'>
|
|
}
|
|
|
|
export type NativeChatSessionOptionRecord = {
|
|
agent: AgentType
|
|
model?: TrackedNativeChatSessionOption
|
|
valuesByModel: Record<string, Record<string, TrackedNativeChatSessionOption>>
|
|
}
|
|
|
|
export function createNativeChatSessionOptionRecord(
|
|
agent: AgentType
|
|
): NativeChatSessionOptionRecord {
|
|
return { agent, valuesByModel: {} }
|
|
}
|
|
|
|
export function cloneNativeChatSessionOptionRecord(
|
|
record: NativeChatSessionOptionRecord
|
|
): NativeChatSessionOptionRecord {
|
|
return {
|
|
agent: record.agent,
|
|
...(record.model ? { model: { ...record.model } } : {}),
|
|
valuesByModel: Object.fromEntries(
|
|
Object.entries(record.valuesByModel).map(([modelId, values]) => [
|
|
modelId,
|
|
Object.fromEntries(Object.entries(values).map(([id, tracked]) => [id, { ...tracked }]))
|
|
])
|
|
)
|
|
}
|
|
}
|
|
|
|
export function isFlipOnlyMidSession(
|
|
midSession: CatalogMidSessionApply | undefined
|
|
): midSession is Extract<CatalogMidSessionApply, { kind: 'toggle-command' }> {
|
|
return midSession?.kind === 'toggle-command'
|
|
}
|
|
|
|
export function getTrackedSessionOption(
|
|
record: NativeChatSessionOptionRecord,
|
|
modelId: string | null,
|
|
optionId: string
|
|
): TrackedNativeChatSessionOption | undefined {
|
|
return modelId ? record.valuesByModel[modelId]?.[optionId] : undefined
|
|
}
|
|
|
|
export function clearTrackedSessionOption(
|
|
record: NativeChatSessionOptionRecord,
|
|
modelId: string | null,
|
|
optionId: string
|
|
): void {
|
|
if (!modelId) {
|
|
return
|
|
}
|
|
const current = record.valuesByModel[modelId]
|
|
if (!current || !(optionId in current)) {
|
|
return
|
|
}
|
|
const next = { ...current }
|
|
delete next[optionId]
|
|
if (Object.keys(next).length === 0) {
|
|
delete record.valuesByModel[modelId]
|
|
} else {
|
|
record.valuesByModel[modelId] = next
|
|
}
|
|
}
|
|
|
|
export function clearNativeChatSessionModel(record: NativeChatSessionOptionRecord): void {
|
|
const modelId = typeof record.model?.value === 'string' ? record.model.value : null
|
|
record.model = undefined
|
|
if (modelId) {
|
|
delete record.valuesByModel[modelId]
|
|
}
|
|
}
|
|
|
|
export function setTrackedSessionOption(
|
|
record: NativeChatSessionOptionRecord,
|
|
optionId: string,
|
|
value: SessionOptionValue,
|
|
source: TrackedNativeChatSessionOption['source'],
|
|
/** Picker model when none is tracked; retain options dispatched under the CLI default. */
|
|
fallbackModelId: string | null = null
|
|
): string | null {
|
|
if (optionId === 'model') {
|
|
record.model = { value, source }
|
|
return typeof value === 'string' ? value : null
|
|
}
|
|
const modelId =
|
|
(typeof record.model?.value === 'string' ? record.model.value : null) ?? fallbackModelId
|
|
if (!modelId) {
|
|
return null
|
|
}
|
|
record.valuesByModel[modelId] = {
|
|
...record.valuesByModel[modelId],
|
|
[optionId]: { value, source }
|
|
}
|
|
return modelId
|
|
}
|
|
|
|
export function flattenNativeChatSessionOptionRecord(
|
|
record: NativeChatSessionOptionRecord,
|
|
modelId: string
|
|
): Record<string, SessionOptionValue> {
|
|
return {
|
|
model: modelId,
|
|
...Object.fromEntries(
|
|
Object.entries(record.valuesByModel[modelId] ?? {}).map(([id, tracked]) => [
|
|
id,
|
|
tracked.value
|
|
])
|
|
)
|
|
}
|
|
}
|
|
|
|
export function applyNativeChatReportedSessionOptions(
|
|
record: NativeChatSessionOptionRecord,
|
|
values: Record<string, SessionOptionValue>,
|
|
/** Provider-confirmed ids; omitted means all values are read-back reports. */
|
|
confirmed?: readonly string[]
|
|
): boolean {
|
|
const sourceFor = (id: string): TrackedNativeChatSessionOption['source'] =>
|
|
confirmed === undefined || confirmed.includes(id) ? 'reported' : 'dispatched'
|
|
const modelId = typeof values.model === 'string' ? values.model : null
|
|
if (!modelId) {
|
|
return false
|
|
}
|
|
const modelChanged = record.model?.value !== modelId
|
|
let changed = modelChanged || record.model?.source !== sourceFor('model')
|
|
record.model = { value: modelId, source: sourceFor('model') }
|
|
const modelValues = modelChanged ? {} : { ...record.valuesByModel[modelId] }
|
|
for (const [id, value] of Object.entries(values)) {
|
|
if (id === 'model') {
|
|
continue
|
|
}
|
|
const current = modelValues[id]
|
|
if (current?.value !== value || current.source !== sourceFor(id)) {
|
|
changed = true
|
|
}
|
|
modelValues[id] = { value, source: sourceFor(id) }
|
|
}
|
|
record.valuesByModel[modelId] = modelValues
|
|
return changed
|
|
}
|
|
|
|
/** Match reported model by exact id, label, then longest contained id; retain raw reports for unseeded catalogs. Callers may keep unlisted selectors without fuzzy matching. */
|
|
export function matchNativeChatCatalogModelId(
|
|
catalog: AgentSessionOptionCatalog,
|
|
reported: string,
|
|
options?: { allowUnlisted?: boolean }
|
|
): string | null {
|
|
const normalized = reported.trim().toLowerCase()
|
|
if (!normalized) {
|
|
return null
|
|
}
|
|
// OMP seeds no models; its reported provider/id is already the exact reusable CLI selector.
|
|
if (catalog.models.length === 0) {
|
|
return reported.trim()
|
|
}
|
|
const exact = catalog.models.find((model) => model.id.toLowerCase() === normalized)
|
|
if (exact) {
|
|
return exact.id
|
|
}
|
|
const byLabel = catalog.models.find((model) => model.label.toLowerCase() === normalized)
|
|
if (byLabel) {
|
|
return byLabel.id
|
|
}
|
|
if (options?.allowUnlisted && !/\s/.test(normalized)) {
|
|
return reported.trim()
|
|
}
|
|
let containingId: string | null = null
|
|
for (const model of catalog.models) {
|
|
if (
|
|
(containingId === null || model.id.length > containingId.length) &&
|
|
normalized.includes(model.id.toLowerCase())
|
|
) {
|
|
containingId = model.id
|
|
}
|
|
}
|
|
return containingId
|
|
}
|