mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-10 08:32:09 +00:00
/model is the canonical command; /provider was a redundant alias that dispatched to the same ModelPicker overlay. Drop the alias, the regex branch in useCompletion, and the alias-coverage test.
112 lines
3 KiB
TypeScript
112 lines
3 KiB
TypeScript
import { useEffect, useRef, useState } from 'react'
|
|
|
|
import type { CompletionItem } from '../app/interfaces.js'
|
|
import { looksLikeSlashCommand } from '../domain/slash.js'
|
|
import type { GatewayClient } from '../gatewayClient.js'
|
|
import type { CompletionResponse } from '../gatewayTypes.js'
|
|
import { asRpcResult } from '../lib/rpc.js'
|
|
|
|
const TAB_PATH_RE = /((?:["']?(?:[A-Za-z]:[\\/]|\.{1,2}\/|~\/|\/|@|[^"'`\s]+\/))[^\s]*)$/
|
|
|
|
export function completionRequestForInput(
|
|
input: string
|
|
):
|
|
| { method: 'complete.path'; params: { word: string }; replaceFrom: number }
|
|
| { method: 'complete.slash'; params: { text: string }; replaceFrom: number }
|
|
| null {
|
|
const isSlashCommand = looksLikeSlashCommand(input)
|
|
const pathWord = isSlashCommand ? null : (input.match(TAB_PATH_RE)?.[1] ?? null)
|
|
|
|
if (!isSlashCommand && !pathWord) {
|
|
return null
|
|
}
|
|
|
|
// `/model` uses the two-step ModelPicker (real curated IDs).
|
|
// Slash completion here only showed short aliases + vendor/family meta.
|
|
if (isSlashCommand && /^\/model(?:\s|$)/.test(input)) {
|
|
return null
|
|
}
|
|
|
|
if (isSlashCommand) {
|
|
return { method: 'complete.slash', params: { text: input }, replaceFrom: 1 }
|
|
}
|
|
|
|
return {
|
|
method: 'complete.path',
|
|
params: { word: pathWord! },
|
|
replaceFrom: input.length - pathWord!.length
|
|
}
|
|
}
|
|
|
|
export function useCompletion(input: string, blocked: boolean, gw: GatewayClient) {
|
|
const [completions, setCompletions] = useState<CompletionItem[]>([])
|
|
const [compIdx, setCompIdx] = useState(0)
|
|
const [compReplace, setCompReplace] = useState(0)
|
|
const ref = useRef('')
|
|
|
|
useEffect(() => {
|
|
const clear = () => {
|
|
setCompletions(prev => (prev.length ? [] : prev))
|
|
setCompIdx(prev => (prev ? 0 : prev))
|
|
setCompReplace(prev => (prev ? 0 : prev))
|
|
}
|
|
|
|
if (blocked) {
|
|
ref.current = ''
|
|
clear()
|
|
|
|
return
|
|
}
|
|
|
|
if (input === ref.current) {
|
|
return
|
|
}
|
|
|
|
ref.current = input
|
|
|
|
const request = completionRequestForInput(input)
|
|
if (!request) {
|
|
clear()
|
|
|
|
return
|
|
}
|
|
|
|
const t = setTimeout(() => {
|
|
if (ref.current !== input) {
|
|
return
|
|
}
|
|
|
|
gw.request<CompletionResponse>(request.method, request.params)
|
|
.then(raw => {
|
|
if (ref.current !== input) {
|
|
return
|
|
}
|
|
|
|
const r = asRpcResult<CompletionResponse>(raw)
|
|
|
|
setCompletions(r?.items ?? [])
|
|
setCompIdx(0)
|
|
setCompReplace(request.method === 'complete.slash' ? (r?.replace_from ?? 1) : request.replaceFrom)
|
|
})
|
|
.catch((e: unknown) => {
|
|
if (ref.current !== input) {
|
|
return
|
|
}
|
|
|
|
setCompletions([
|
|
{
|
|
text: '',
|
|
display: 'completion unavailable',
|
|
meta: e instanceof Error && e.message ? e.message : 'unavailable'
|
|
}
|
|
])
|
|
setCompIdx(0)
|
|
setCompReplace(request.replaceFrom)
|
|
})
|
|
}, 60)
|
|
|
|
return () => clearTimeout(t)
|
|
}, [blocked, gw, input])
|
|
|
|
return { completions, compIdx, setCompIdx, compReplace }
|
|
}
|