🎯 Use case
This file lives under “hooks/”, which covers reusable UI or integration hooks. On the API surface it exposes usePromptSuggestion — mainly functions, hooks, or classes. Dependencies touch React UI. It composes internal code from ink, services, and state (relative imports).
Generated from folder role, exports, dependency roots, and inline comments — not hand-reviewed for every path.
🧠 Inline summary
import { useCallback, useRef } from 'react' import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js' import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent,
📤 Exports (heuristic)
usePromptSuggestion
📚 External import roots
Package roots from from "…" (relative paths omitted).
react
🖥️ Source preview
import { useCallback, useRef } from 'react'
import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js'
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from '../services/analytics/index.js'
import { abortSpeculation } from '../services/PromptSuggestion/speculation.js'
import { useAppState, useSetAppState } from '../state/AppState.js'
type Props = {
inputValue: string
isAssistantResponding: boolean
}
export function usePromptSuggestion({
inputValue,
isAssistantResponding,
}: Props): {
suggestion: string | null
markAccepted: () => void
markShown: () => void
logOutcomeAtSubmission: (
finalInput: string,
opts?: { skipReset: boolean },
) => void
} {
const promptSuggestion = useAppState(s => s.promptSuggestion)
const setAppState = useSetAppState()
const isTerminalFocused = useTerminalFocus()
const {
text: suggestionText,
promptId,
shownAt,
acceptedAt,
generationRequestId,
} = promptSuggestion
const suggestion =
isAssistantResponding || inputValue.length > 0 ? null : suggestionText
const isValidSuggestion = suggestionText && shownAt > 0
// Track engagement depth for telemetry
const firstKeystrokeAt = useRef<number>(0)
const wasFocusedWhenShown = useRef<boolean>(true)
const prevShownAt = useRef<number>(0)
// Capture focus state when a new suggestion appears (shownAt changes)
if (shownAt > 0 && shownAt !== prevShownAt.current) {
prevShownAt.current = shownAt
wasFocusedWhenShown.current = isTerminalFocused
firstKeystrokeAt.current = 0
} else if (shownAt === 0) {
prevShownAt.current = 0
}
// Record first keystroke while suggestion is visible
if (
inputValue.length > 0 &&
firstKeystrokeAt.current === 0 &&
isValidSuggestion
) {
firstKeystrokeAt.current = Date.now()
}
const resetSuggestion = useCallback(() => {
abortSpeculation(setAppState)
setAppState(prev => ({
...prev,
promptSuggestion: {
text: null,
promptId: null,
shownAt: 0,
acceptedAt: 0,
generationRequestId: null,
},
}))
}, [setAppState])
const markAccepted = useCallback(() => {
if (!isValidSuggestion) return
setAppState(prev => ({
...prev,
promptSuggestion: {
...prev.promptSuggestion,
acceptedAt: Date.now(),
},
}))
}, [isValidSuggestion, setAppState])
const markShown = useCallback(() => {
// Check shownAt inside setAppState callback to avoid depending on it
// (depending on shownAt causes infinite loop when this callback is called)
setAppState(prev => {
// Only mark shown if not already shown and suggestion exists
if (prev.promptSuggestion.shownAt !== 0 || !prev.promptSuggestion.text) {
return prev
}
return {
...prev,
promptSuggestion: {
...prev.promptSuggestion,
shownAt: Date.now(),
},
}
})
}, [setAppState])
const logOutcomeAtSubmission = useCallback(
(finalInput: string, opts?: { skipReset: boolean }) => {
if (!isValidSuggestion) return
// Determine if accepted: either Tab was pressed (acceptedAt set) OR
// final input matches suggestion (empty Enter case)
const tabWasPressed = acceptedAt > shownAt
const wasAccepted = tabWasPressed || finalInput === suggestionText
const timeMs = wasAccepted ? acceptedAt || Date.now() : Date.now()
logEvent('tengu_prompt_suggestion', {
source:
'cli' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
outcome: (wasAccepted
? 'accepted'
: 'ignored') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
prompt_id:
promptId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
...(generationRequestId && {
generationRequestId:
generationRequestId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
}),
...(wasAccepted && {
acceptMethod: (tabWasPressed
? 'tab'
: 'enter') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
}),
...(wasAccepted && {
timeToAcceptMs: timeMs - shownAt,
}),
...(!wasAccepted && {
timeToIgnoreMs: timeMs - shownAt,
}),
...(firstKeystrokeAt.current > 0 && {
timeToFirstKeystrokeMs: firstKeystrokeAt.current - shownAt,
}),
wasFocusedWhenShown: wasFocusedWhenShown.current,
similarity:
Math.round(
(finalInput.length / (suggestionText?.length || 1)) * 100,
) / 100,
...(process.env.USER_TYPE === 'ant' && {
suggestion:
suggestionText as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
userInput:
finalInput as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
}),
})
if (!opts?.skipReset) resetSuggestion()
},
[
isValidSuggestion,
acceptedAt,
shownAt,
suggestionText,
promptId,
generationRequestId,
resetSuggestion,
],
)
return {
suggestion,
markAccepted,
markShown,
logOutcomeAtSubmission,
}
}