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
17 changes: 13 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -663,9 +663,13 @@ if(APPLE AND UAM_MACOS_BUNDLE)
"$<TARGET_BUNDLE_CONTENT_DIR:universal_agent_manager>/Resources/UI-V2/dist"
COMMAND ${CMAKE_COMMAND} -E make_directory
"$<TARGET_BUNDLE_CONTENT_DIR:universal_agent_manager>/Resources/markdown-store"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_SOURCE_DIR}/markdown-store/skill-builder.uam"
COMMAND ${CMAKE_COMMAND} -E rm -f
"$<TARGET_BUNDLE_CONTENT_DIR:universal_agent_manager>/Resources/markdown-store/skill-builder.uam"
COMMAND ${CMAKE_COMMAND} -E remove_directory
"$<TARGET_BUNDLE_CONTENT_DIR:universal_agent_manager>/Resources/markdown-store/bundled"
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/markdown-store/bundled"
"$<TARGET_BUNDLE_CONTENT_DIR:universal_agent_manager>/Resources/markdown-store/bundled"
COMMAND ${CMAKE_COMMAND} -E echo "Bundled UI-V2 build id: ${UAM_UI_BUILD_ID}"
COMMENT "Copying UI-V2/dist and bundled Markdown Store entries to Contents/Resources/"
)
Expand All @@ -678,9 +682,13 @@ else()
"$<TARGET_FILE_DIR:universal_agent_manager>/UI-V2/dist"
COMMAND ${CMAKE_COMMAND} -E make_directory
"$<TARGET_FILE_DIR:universal_agent_manager>/markdown-store"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_SOURCE_DIR}/markdown-store/skill-builder.uam"
COMMAND ${CMAKE_COMMAND} -E rm -f
"$<TARGET_FILE_DIR:universal_agent_manager>/markdown-store/skill-builder.uam"
COMMAND ${CMAKE_COMMAND} -E remove_directory
"$<TARGET_FILE_DIR:universal_agent_manager>/markdown-store/bundled"
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/markdown-store/bundled"
"$<TARGET_FILE_DIR:universal_agent_manager>/markdown-store/bundled"
COMMAND ${CMAKE_COMMAND} -E echo "Bundled UI-V2 build id: ${UAM_UI_BUILD_ID}"
COMMENT "Copying UI-V2/dist and bundled Markdown Store entries to build output"
)
Expand Down Expand Up @@ -892,6 +900,7 @@ if(UAM_BUILD_TESTS)
libcef_dll_wrapper
"${cef_binary_SOURCE_DIR}/Release/libcef.lib"
advapi32
shell32
windowsapp
)
endif()
Expand Down
24 changes: 17 additions & 7 deletions UI-V2/src/components/chat/modelOptions.test.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import { describe, expect, it } from 'vitest'
import type { AcpBinding } from '../../store/cpp/types'
import { buildModelOptions, reasoningEffortForModel } from './modelOptions'
import { buildCodexReasoningOptions, buildModelOptions, reasoningEffortForModel } from './modelOptions'

describe('reasoningEffortForModel', () => {
it('defaults invalid or empty effort to the runtime model default', () => {
const acp = {
availableModels: [{
id: 'gpt-5.4',
name: 'GPT-5.4',
id: 'gpt-5.6',
name: 'GPT-5.6',
defaultReasoningEffort: 'medium',
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh'],
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'ultra'],
}],
} as AcpBinding

expect(reasoningEffortForModel(acp, 'gpt-5.4')).toBe('medium')
expect(reasoningEffortForModel(acp, 'gpt-5.4', 'unknown')).toBe('medium')
expect(reasoningEffortForModel(acp, 'gpt-5.4', 'low')).toBe('low')
expect(reasoningEffortForModel(acp, 'gpt-5.6')).toBe('medium')
expect(reasoningEffortForModel(acp, 'gpt-5.6', 'unknown')).toBe('medium')
expect(reasoningEffortForModel(acp, 'gpt-5.6', 'low')).toBe('low')
expect(reasoningEffortForModel(acp, 'gpt-5.6', 'ultra')).toBe('ultra')
})

it('keeps provider-default model selection explicit', () => {
const options = buildModelOptions(undefined, '', undefined, 'gemini-cli', true)

expect(options[0]).toMatchObject({ id: '', label: 'Default' })
})

it('offers ultra reasoning when live model metadata is unavailable', () => {
expect(buildCodexReasoningOptions(undefined, 'gpt-5.6').at(-1)).toMatchObject({
id: 'ultra',
label: 'Ultra',
})
expect(buildCodexReasoningOptions(undefined, 'gpt-5.4').some((option) => option.id === 'ultra')).toBe(false)
expect(reasoningEffortForModel(undefined, 'gpt-5.4', 'ultra')).toBe('')
})
})
8 changes: 5 additions & 3 deletions UI-V2/src/components/chat/modelOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,17 @@ export function buildCodexReasoningOptions(acp: AcpBinding | undefined, modelId:
const runtimeModel = selectedRuntimeModel(acp, modelId)
const runtimeEfforts = runtimeModel?.supportedReasoningEfforts ?? []
if (runtimeModel && runtimeEfforts.length === 0) return []
const base = runtimeEfforts.length > 0 ? runtimeEfforts : ['none', 'minimal', 'low', 'medium', 'high', 'xhigh']
const fallbackEfforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh']
if (/^gpt-5\.6(?:$|-)/i.test(modelId.trim())) fallbackEfforts.push('ultra')
const base = runtimeEfforts.length > 0 ? runtimeEfforts : fallbackEfforts
const ids = runtimeModel ? [...base] : ['', ...base]
if (!runtimeModel && selectedReasoningEffort && !ids.includes(selectedReasoningEffort)) ids.push(selectedReasoningEffort)
if (!runtimeModel && selectedReasoningEffort && selectedReasoningEffort !== 'ultra' && !ids.includes(selectedReasoningEffort)) ids.push(selectedReasoningEffort)
return Array.from(new Set(ids)).map((id) => labeledOption(id, CODEX_REASONING_LABELS))
}

export function reasoningEffortForModel(acp: AcpBinding | undefined, modelId: string, currentEffort = '') {
const model = selectedRuntimeModel(acp, modelId)
if (!model) return currentEffort
if (!model) return currentEffort === 'ultra' && !/^gpt-5\.6(?:$|-)/i.test(modelId.trim()) ? '' : currentEffort
const supported = model.supportedReasoningEfforts ?? []
if (supported.length === 0) return ''
if (supported.includes(currentEffort)) return currentEffort
Expand Down
12 changes: 10 additions & 2 deletions UI-V2/src/components/settings/MarkdownStoreModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('MarkdownStoreModal', () => {
markdownStoreLoading: false,
markdownStoreError: '',
markdownStoreEntries: [
{ id: 'review', title: 'Review code', maker: 'David', review: '', dateCreated: '', dateUpdated: '', preview: 'Find regressions', body: '# Review\n\nFind regressions', favorite: false, sourceProvider: 'codex', sourcePath: '/tmp/codex/review.md', commandName: 'review-code', filePath: '/tmp/store/review.uam' },
{ id: 'review', title: 'Review code', maker: 'David', review: '', dateCreated: '', dateUpdated: '', preview: 'Find regressions', body: '# Review\n\nFind regressions', favorite: false, sourceProvider: 'codex', sourcePath: '/tmp/codex/review.md', commandName: 'review-code', group: 'Coding', filePath: '/tmp/store/review.uam' },
{ id: 'notes', title: 'Release notes', maker: 'Sam', review: '', dateCreated: '', dateUpdated: '', preview: 'Summarize changes', body: '# Notes', favorite: true, sourceProvider: 'gemini-cli', sourcePath: '/tmp/gemini/notes.md', commandName: 'release-notes', filePath: '/tmp/store/notes.uam' },
],
closeMarkdownStore: vi.fn(),
Expand Down Expand Up @@ -113,6 +113,9 @@ describe('MarkdownStoreModal', () => {
act(() => { filter.value = 'source:codex'; filter.dispatchEvent(new Event('change', { bubbles: true })) })
expect(host.textContent).toContain('Review code')
expect(host.textContent).not.toContain('Release notes')
act(() => { filter.value = 'group:Coding'; filter.dispatchEvent(new Event('change', { bubbles: true })) })
expect(host.textContent).toContain('Review code')
expect(host.textContent).not.toContain('Release notes')

const favorite = host.querySelector('[aria-label="Add Review code to favorites"]') as HTMLElement
await act(async () => { favorite.dispatchEvent(new MouseEvent('click', { bubbles: true })); await Promise.resolve() })
Expand All @@ -136,11 +139,16 @@ describe('MarkdownStoreModal', () => {
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set?.call(title, 'Review carefully')
title.dispatchEvent(new Event('input', { bubbles: true }))
})
const group = host.querySelector('input[aria-label="Entry group"]') as HTMLInputElement
act(() => {
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set?.call(group, 'Coding / Review')
group.dispatchEvent(new Event('input', { bubbles: true }))
})
act(() => Array.from(host.querySelectorAll('button')).find((button) => button.textContent === 'Preview Markdown')?.click())
expect(host.textContent).toContain('Find regressions')

await act(async () => { Array.from(host.querySelectorAll('button')).find((button) => button.textContent === 'Save')?.click(); await Promise.resolve() })
expect(update).toHaveBeenCalledWith(expect.objectContaining({ id: 'review' }), expect.objectContaining({ title: 'Review carefully' }))
expect(update).toHaveBeenCalledWith(expect.objectContaining({ id: 'review' }), expect.objectContaining({ title: 'Review carefully', group: 'Coding / Review' }))
expect(host.querySelector('input[aria-label="Entry title"]')).toBeTruthy()
await act(async () => { Array.from(host.querySelectorAll('button')).find((button) => button.textContent === 'Save')?.click(); await Promise.resolve() })
expect(host.querySelector('input[aria-label="Entry title"]')).toBeFalsy()
Expand Down
Loading
Loading