mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
Merge pull request #16600 from NousResearch/austin/fix/model-provider
fix(models): consolidate provider and model into /model command
This commit is contained in:
commit
60f2415a4a
7 changed files with 92 additions and 34 deletions
41
ui-tui/package-lock.json
generated
41
ui-tui/package-lock.json
generated
|
|
@ -124,6 +124,7 @@
|
||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
|
|
@ -501,31 +502,6 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emnapi/core": {
|
|
||||||
"version": "1.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
|
||||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/wasi-threads": "1.2.1",
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@emnapi/runtime": {
|
|
||||||
"version": "1.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
|
||||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@emnapi/wasi-threads": {
|
"node_modules/@emnapi/wasi-threads": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||||
|
|
@ -1700,6 +1676,7 @@
|
||||||
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.19.0"
|
"undici-types": "~7.19.0"
|
||||||
}
|
}
|
||||||
|
|
@ -1710,6 +1687,7 @@
|
||||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
|
|
@ -1720,6 +1698,7 @@
|
||||||
"integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==",
|
"integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.12.2",
|
"@eslint-community/regexpp": "^4.12.2",
|
||||||
"@typescript-eslint/scope-manager": "8.58.1",
|
"@typescript-eslint/scope-manager": "8.58.1",
|
||||||
|
|
@ -1749,6 +1728,7 @@
|
||||||
"integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
|
"integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.58.1",
|
"@typescript-eslint/scope-manager": "8.58.1",
|
||||||
"@typescript-eslint/types": "8.58.1",
|
"@typescript-eslint/types": "8.58.1",
|
||||||
|
|
@ -2066,6 +2046,7 @@
|
||||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
|
|
@ -2468,6 +2449,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.10.12",
|
"baseline-browser-mapping": "^2.10.12",
|
||||||
"caniuse-lite": "^1.0.30001782",
|
"caniuse-lite": "^1.0.30001782",
|
||||||
|
|
@ -3203,6 +3185,7 @@
|
||||||
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
|
|
@ -3334,6 +3317,7 @@
|
||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|
@ -4242,6 +4226,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/ink-text-input/-/ink-text-input-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ink-text-input/-/ink-text-input-6.0.0.tgz",
|
||||||
"integrity": "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==",
|
"integrity": "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"type-fest": "^4.18.2"
|
"type-fest": "^4.18.2"
|
||||||
|
|
@ -5678,6 +5663,7 @@
|
||||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|
@ -5787,6 +5773,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
|
||||||
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
|
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
|
@ -6611,6 +6598,7 @@
|
||||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "~0.27.0",
|
"esbuild": "~0.27.0",
|
||||||
"get-tsconfig": "^4.7.5"
|
"get-tsconfig": "^4.7.5"
|
||||||
|
|
@ -6737,6 +6725,7 @@
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|
@ -6846,6 +6835,7 @@
|
||||||
"integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
|
"integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lightningcss": "^1.32.0",
|
"lightningcss": "^1.32.0",
|
||||||
"picomatch": "^4.0.4",
|
"picomatch": "^4.0.4",
|
||||||
|
|
@ -7261,6 +7251,7 @@
|
||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { createSlashHandler } from '../app/createSlashHandler.js'
|
import { createSlashHandler } from '../app/createSlashHandler.js'
|
||||||
|
import { TUI_SESSION_MODEL_FLAG } from '../domain/slash.js'
|
||||||
import { getOverlayState, resetOverlayState } from '../app/overlayStore.js'
|
import { getOverlayState, resetOverlayState } from '../app/overlayStore.js'
|
||||||
import { getUiState, patchUiState, resetUiState } from '../app/uiStore.js'
|
import { getUiState, patchUiState, resetUiState } from '../app/uiStore.js'
|
||||||
|
|
||||||
|
|
@ -43,6 +44,28 @@ describe('createSlashHandler', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('honors TUI picker session scope without adding --global', async () => {
|
||||||
|
patchUiState({ sid: 'sid-abc' })
|
||||||
|
|
||||||
|
const ctx = buildCtx({
|
||||||
|
gateway: {
|
||||||
|
...buildGateway(),
|
||||||
|
rpc: vi.fn(() => Promise.resolve({ value: 'anthropic/claude-sonnet-4.6' }))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
createSlashHandler(ctx)(
|
||||||
|
`/model anthropic/claude-sonnet-4.6 --provider openrouter ${TUI_SESSION_MODEL_FLAG}`
|
||||||
|
)
|
||||||
|
).toBe(true)
|
||||||
|
expect(ctx.gateway.rpc).toHaveBeenCalledWith('config.set', {
|
||||||
|
key: 'model',
|
||||||
|
session_id: 'sid-abc',
|
||||||
|
value: 'anthropic/claude-sonnet-4.6 --provider openrouter'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('does not duplicate --global for explicit persistent model switches', () => {
|
it('does not duplicate --global for explicit persistent model switches', () => {
|
||||||
patchUiState({ sid: 'sid-abc' })
|
patchUiState({ sid: 'sid-abc' })
|
||||||
const ctx = buildCtx()
|
const ctx = buildCtx()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import type {
|
||||||
VoiceToggleResponse
|
VoiceToggleResponse
|
||||||
} from '../../../gatewayTypes.js'
|
} from '../../../gatewayTypes.js'
|
||||||
import { fmtK } from '../../../lib/text.js'
|
import { fmtK } from '../../../lib/text.js'
|
||||||
|
import { TUI_SESSION_MODEL_FLAG } from '../../../domain/slash.js'
|
||||||
import type { PanelSection } from '../../../types.js'
|
import type { PanelSection } from '../../../types.js'
|
||||||
import { patchOverlayState } from '../../overlayStore.js'
|
import { patchOverlayState } from '../../overlayStore.js'
|
||||||
import { patchUiState } from '../../uiStore.js'
|
import { patchUiState } from '../../uiStore.js'
|
||||||
|
|
@ -17,12 +18,32 @@ import type { SlashCommand } from '../types.js'
|
||||||
|
|
||||||
const GLOBAL_MODEL_FLAG_RE = /(?:^|\s)--global(?:\s|$)/
|
const GLOBAL_MODEL_FLAG_RE = /(?:^|\s)--global(?:\s|$)/
|
||||||
|
|
||||||
|
const TUI_SESSION_MODEL_RE = new RegExp(`(?:^|\\s)${TUI_SESSION_MODEL_FLAG}(?:\\s|$)`)
|
||||||
|
const TUI_SESSION_STRIP_RE = new RegExp(`\\s*${TUI_SESSION_MODEL_FLAG}\\b\\s*`, 'g')
|
||||||
|
|
||||||
const persistedModelArg = (arg: string) => {
|
const persistedModelArg = (arg: string) => {
|
||||||
const trimmed = arg.trim()
|
const trimmed = arg.trim()
|
||||||
|
|
||||||
return !trimmed || GLOBAL_MODEL_FLAG_RE.test(trimmed) ? trimmed : `${trimmed} --global`
|
return !trimmed || GLOBAL_MODEL_FLAG_RE.test(trimmed) ? trimmed : `${trimmed} --global`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stripTuiSessionFlag = (trimmed: string) =>
|
||||||
|
trimmed.replace(TUI_SESSION_STRIP_RE, ' ').replace(/\s+/g, ' ').trim()
|
||||||
|
|
||||||
|
const modelValueForConfigSet = (arg: string) => {
|
||||||
|
const trimmed = arg.trim()
|
||||||
|
|
||||||
|
if (!trimmed) {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TUI_SESSION_MODEL_RE.test(trimmed)) {
|
||||||
|
return stripTuiSessionFlag(trimmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return persistedModelArg(trimmed)
|
||||||
|
}
|
||||||
|
|
||||||
export const sessionCommands: SlashCommand[] = [
|
export const sessionCommands: SlashCommand[] = [
|
||||||
{
|
{
|
||||||
aliases: ['bg', 'btw'],
|
aliases: ['bg', 'btw'],
|
||||||
|
|
@ -60,7 +81,7 @@ export const sessionCommands: SlashCommand[] = [
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.gateway
|
ctx.gateway
|
||||||
.rpc<ConfigSetResponse>('config.set', { key: 'model', session_id: ctx.sid, value: persistedModelArg(arg) })
|
.rpc<ConfigSetResponse>('config.set', { key: 'model', session_id: ctx.sid, value: modelValueForConfigSet(arg) })
|
||||||
.then(
|
.then(
|
||||||
ctx.guarded<ConfigSetResponse>(r => {
|
ctx.guarded<ConfigSetResponse>(r => {
|
||||||
if (!r.value) {
|
if (!r.value) {
|
||||||
|
|
|
||||||
|
|
@ -655,7 +655,7 @@ export function useMainApp(gw: GatewayClient) {
|
||||||
|
|
||||||
const onModelSelect = useCallback((value: string) => {
|
const onModelSelect = useCallback((value: string) => {
|
||||||
patchOverlayState({ modelPicker: false })
|
patchOverlayState({ modelPicker: false })
|
||||||
slashRef.current(`/model ${value} --global`)
|
slashRef.current(`/model ${value}`)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const hasReasoning = useTurnSelector(state => Boolean(state.reasoning.trim()))
|
const hasReasoning = useTurnSelector(state => Boolean(state.reasoning.trim()))
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { Box, Text, useInput, useStdout } from '@hermes/ink'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import { providerDisplayNames } from '../domain/providers.js'
|
import { providerDisplayNames } from '../domain/providers.js'
|
||||||
|
import { TUI_SESSION_MODEL_FLAG } from '../domain/slash.js'
|
||||||
import type { GatewayClient } from '../gatewayClient.js'
|
import type { GatewayClient } from '../gatewayClient.js'
|
||||||
import type { ModelOptionProvider, ModelOptionsResponse } from '../gatewayTypes.js'
|
import type { ModelOptionProvider, ModelOptionsResponse } from '../gatewayTypes.js'
|
||||||
import { asRpcResult, rpcErrorMessage } from '../lib/rpc.js'
|
import { asRpcResult, rpcErrorMessage } from '../lib/rpc.js'
|
||||||
|
|
@ -52,6 +53,7 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
setModelIdx(0)
|
setModelIdx(0)
|
||||||
|
setStage('provider')
|
||||||
setErr('')
|
setErr('')
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
|
|
@ -110,7 +112,9 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
|
||||||
const model = models[modelIdx]
|
const model = models[modelIdx]
|
||||||
|
|
||||||
if (provider && model) {
|
if (provider && model) {
|
||||||
onSelect(`${model} --provider ${provider.slug}${persistGlobal ? ' --global' : ''}`)
|
onSelect(
|
||||||
|
`${model} --provider ${provider.slug}${persistGlobal ? ' --global' : ` ${TUI_SESSION_MODEL_FLAG}`}`
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
setStage('provider')
|
setStage('provider')
|
||||||
}
|
}
|
||||||
|
|
@ -136,7 +140,9 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
|
||||||
setProviderIdx(next)
|
setProviderIdx(next)
|
||||||
}
|
}
|
||||||
} else if (provider && models[offset + n - 1]) {
|
} else if (provider && models[offset + n - 1]) {
|
||||||
onSelect(`${models[offset + n - 1]} --provider ${provider.slug}${persistGlobal ? ' --global' : ''}`)
|
onSelect(
|
||||||
|
`${models[offset + n - 1]} --provider ${provider.slug}${persistGlobal ? ' --global' : ` ${TUI_SESSION_MODEL_FLAG}`}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -173,11 +179,15 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" width={width}>
|
<Box flexDirection="column" width={width}>
|
||||||
<Text bold color={t.color.amber} wrap="truncate-end">
|
<Text bold color={t.color.amber} wrap="truncate-end">
|
||||||
Select Provider
|
Select provider (step 1/2)
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text color={t.color.dim} wrap="truncate-end">
|
<Text color={t.color.dim} wrap="truncate-end">
|
||||||
Current model: {currentModel || '(unknown)'}
|
Full model IDs on the next step · Enter to continue
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text color={t.color.dim} wrap="truncate-end">
|
||||||
|
Current: {currentModel || '(unknown)'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={t.color.label} wrap="truncate-end">
|
<Text color={t.color.label} wrap="truncate-end">
|
||||||
{provider?.warning ? `warning: ${provider.warning}` : ' '}
|
{provider?.warning ? `warning: ${provider.warning}` : ' '}
|
||||||
|
|
@ -225,11 +235,11 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" width={width}>
|
<Box flexDirection="column" width={width}>
|
||||||
<Text bold color={t.color.amber} wrap="truncate-end">
|
<Text bold color={t.color.amber} wrap="truncate-end">
|
||||||
Select Model
|
Select model (step 2/2)
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text color={t.color.dim} wrap="truncate-end">
|
<Text color={t.color.dim} wrap="truncate-end">
|
||||||
{names[providerIdx] || '(unknown provider)'}
|
{names[providerIdx] || '(unknown provider)'} · Esc back
|
||||||
</Text>
|
</Text>
|
||||||
<Text color={t.color.label} wrap="truncate-end">
|
<Text color={t.color.label} wrap="truncate-end">
|
||||||
{provider?.warning ? `warning: ${provider.warning}` : ' '}
|
{provider?.warning ? `warning: ${provider.warning}` : ' '}
|
||||||
|
|
@ -254,6 +264,8 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const prefix = modelIdx === idx ? '▸ ' : row === currentModel ? '* ' : ' '
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
bold={modelIdx === idx}
|
bold={modelIdx === idx}
|
||||||
|
|
@ -262,7 +274,7 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke
|
||||||
key={`${provider?.slug ?? 'prov'}:${idx}:${row}`}
|
key={`${provider?.slug ?? 'prov'}:${idx}:${row}`}
|
||||||
wrap="truncate-end"
|
wrap="truncate-end"
|
||||||
>
|
>
|
||||||
{modelIdx === idx ? '▸ ' : ' '}
|
{prefix}
|
||||||
{i + 1}. {row}
|
{i + 1}. {row}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
/** Appended to `/model` args from the TUI picker for session scope; stripped in `session` slash before `config.set`. */
|
||||||
|
export const TUI_SESSION_MODEL_FLAG = '--tui-session'
|
||||||
|
|
||||||
export const looksLikeSlashCommand = (text: string) => /^\/[^\s/]*(?:\s|$)/.test(text)
|
export const looksLikeSlashCommand = (text: string) => /^\/[^\s/]*(?:\s|$)/.test(text)
|
||||||
|
|
||||||
export const parseSlashCommand = (cmd: string) => {
|
export const parseSlashCommand = (cmd: string) => {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,14 @@ export function useCompletion(input: string, blocked: boolean, gw: GatewayClient
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `/model` / `/provider` use the two-step ModelPicker (real curated IDs).
|
||||||
|
// Slash completion here only showed short aliases + vendor/family meta.
|
||||||
|
if (isSlash && /^\/(?:model|provider)(?:\s|$)/.test(input)) {
|
||||||
|
clear()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const pathReplace = input.length - (pathWord?.length ?? 0)
|
const pathReplace = input.length - (pathWord?.length ?? 0)
|
||||||
|
|
||||||
const t = setTimeout(() => {
|
const t = setTimeout(() => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue