Skip to Content
DesignPrompt SuggestionDesign de Sugestão de Prompt (NES)

Design de Sugestão de Prompt (NES)

Prevê o que o usuário digitaria naturalmente em seguida após a conclusão da resposta da IA, exibindo-o como ghost text no prompt de entrada.

Status da implementação: prompt-suggestion-implementation.md. Motor de especulação: speculation-design.md.

Visão Geral

Uma prompt suggestion (Next-step Suggestion / NES) é uma previsão curta (2 a 12 palavras) da próxima entrada do usuário, gerada por uma chamada de LLM após cada resposta da IA. Ela aparece como ghost text no prompt de entrada. O usuário pode aceitá-la com Tab/Enter/Seta para a Direita ou descartá-la ao começar a digitar.

Arquitetura

┌─────────────────────────────────────────────────────────────┐ │ AppContainer (CLI) │ │ │ │ Responding → Idle transition │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Guard Conditions (11 categories) │ │ │ │ settings, interactive, sdk, plan mode, dialogs, │ │ │ │ elicitation, API error │ │ │ └────────────────────┬────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ generatePromptSuggestion() │ │ │ │ │ │ │ │ ┌─── CacheSafeParams available? ───┐ │ │ │ │ │ │ │ │ │ │ ▼ YES NO ▼ │ │ │ │ runForkedQuery() BaseLlmClient.generateJson() │ │ │ │ (cache-aware) (standalone fallback) │ │ │ │ │ │ │ │ ──── SUGGESTION_PROMPT ──── │ │ │ │ ──── 12 filter rules ────── │ │ │ │ ──── getFilterReason() ──── │ │ │ └────────────────────┬────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ FollowupController (framework-agnostic) │ │ │ │ 300ms delay → show as ghost text │ │ │ │ │ │ │ │ Tab → accept (fill input) │ │ │ │ Enter → accept + submit │ │ │ │ Right → accept (fill input) │ │ │ │ Type → dismiss + abort speculation │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Telemetry (PromptSuggestionEvent) │ │ │ │ outcome, accept_method, timing, similarity, │ │ │ │ keystroke, focus, suppression reason, prompt_id │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘

Geração de Sugestão

Prompt da LLM

[SUGGESTION MODE: Suggest what the user might naturally type next.] FIRST: Read the LAST FEW LINES of the assistant's most recent message — that's where next-step hints, tips, and actionable suggestions usually appear. Then check the user's recent messages and original request. Your job is to predict what THEY would type - not what you think they should do. THE TEST: Would they think "I was just about to type that"? PRIORITY: If the assistant's last message contains a tip or hint like "Tip: type X to ..." or "type X to ...", extract X as the suggestion. These are explicit next-step hints. EXAMPLES: Assistant says "Tip: type post comments to publish findings" → "post comments" Assistant says "type /review to start" → "/review" User asked "fix the bug and run tests", bug is fixed → "run the tests" After code written → "try it out" Task complete, obvious follow-up → "commit this" or "push it" Format: 2-12 words, match the user's style. Or nothing. Reply with ONLY the suggestion, no quotes or explanation.

Regras de Filtro (12)

RegraExemplo bloqueado
done”done”
meta_text”nothing found”, “no suggestion”, “silence”
meta_wrapped”(silence)”, “[no suggestion]“
error_message”api error: 500”
prefixed_label”Suggestion: commit”
too_few_words”hmm” (mas permite “yes”, “commit”, “push” etc.)
too_many_words> 12 palavras
too_long>= 100 caracteres
multiple_sentences”Run tests. Then commit.”
has_formattingquebras de linha, negrito em markdown
evaluative”looks good”, “thanks” (com limites de palavra \b)
ai_voice”Let me…”, “I’ll…”, “Here’s…”

Condições de Guarda

AppContainer useEffect (13 verificações no código):

GuardaVerificação
Settings toggleenableFollowupSuggestions
Non-interactiveconfig.isInteractive()
SDK mode!config.getSdkMode()
Streaming transitionResponding → Idle (2 verificações)
API error (history)historyManager.history[last]?.type !== 'error'
API error (pending)!pendingGeminiHistoryItems.some(type === 'error')
Confirmation dialogsshell + general + loop detection (3 verificações)
Permission dialogisPermissionsDialogOpen
ElicitationsettingInputRequests.length === 0
Plan modeApprovalMode.PLAN

Dentro de generatePromptSuggestion():

GuardaVerificação
Early conversationmodelTurns < 2

Feature flags separados (fora do bloco de guarda):

FlagControla
enableCacheSharingSe deve usar forked query ou fallback para generateJson
enableSpeculationSe deve iniciar a especulação na exibição da sugestão

Gerenciamento de Estado

FollowupState

interface FollowupState { suggestion: string | null; isVisible: boolean; shownAt: number; // timestamp for telemetry }

FollowupController

Controlador agnóstico de framework compartilhado entre CLI (Ink) e WebUI (React):

  • setSuggestion(text) — exibição com atraso de 300ms, null limpa imediatamente
  • accept(method) — limpa o estado, dispara onAccept via microtask, lock de debounce de 100ms
  • dismiss() — limpa o estado, registra telemetria ignored
  • clear() — reset completo de todo o estado + timers
  • Object.freeze(INITIAL_FOLLOWUP_STATE) previne mutação acidental

Interação com o Teclado

TeclaCLIWebUI
TabPreenche o input (sem submit)Preenche o input (sem submit)
EnterPreenche + submitPreenche + submit (parâmetro explicitText)
Right ArrowPreenche o input (sem submit)Preenche o input (sem submit)
TypingDescarta + aborta especulaçãoDescarta
PasteDescarta + aborta especulaçãoDescarta

Nota sobre Key Binding

O handler do Tab usa key.name === 'tab' explicitamente (e não o matcher ACCEPT_SUGGESTION) porque ACCEPT_SUGGESTION também corresponde ao Enter, que precisa passar para o handler SUBMIT.

Telemetria

PromptSuggestionEvent

CampoTipoDescrição
outcomeaccepted/ignored/suppressedResultado final
prompt_idstringPadrão: ‘user_intent’
accept_methodtab/enter/rightComo o usuário aceitou
time_to_accept_msnumberTempo entre exibição e aceite
time_to_ignore_msnumberTempo entre exibição e descarte
time_to_first_keystroke_msnumberTempo até a primeira tecla pressionada enquanto visível
suggestion_lengthnumberContagem de caracteres
similaritynumber1.0 para aceite, 0.0 para ignorado
was_focused_when_shownbooleanSe o terminal estava em foco
reasonstringPara suprimido: nome da regra de filtro

SpeculationEvent

CampoTipoDescrição
outcomeaccepted/aborted/failedResultado da especulação
turns_usednumberRound-trips da API
files_writtennumberArquivos no overlay
tool_use_countnumberFerramentas executadas
duration_msnumberTempo de relógio (wall-clock)
boundary_typestringO que interrompeu a especulação
had_pipelined_suggestionbooleanPróxima sugestão gerada

Feature Flags e Configurações

ConfiguraçãoTipoPadrãoDescrição
enableFollowupSuggestionsbooleantrueToggle principal para prompt suggestions
enableCacheSharingbooleantrueUsa forked queries com awareness de cache
enableSpeculationbooleanfalseMotor de execução preditiva
fastModel (top-level)string""Modelo para todas as tarefas em background (vazio = usa o modelo principal). Definido via /model --fast

Filtragem de Internal Prompt ID

Operações em background usam prompt IDs dedicados (INTERNAL_PROMPT_IDS em utils/internalPromptIds.ts) para evitar que seu tráfego de API e chamadas de ferramentas apareçam na UI visível ao usuário:

Prompt IDUsado por
prompt_suggestionGeração de sugestão
forked_queryForked queries com awareness de cache
speculationMotor de especulação

Filtragem aplicada:

  • loggingContentGenerator — ignora logApiRequest e logging de interação OpenAI para IDs internos
  • logApiResponse / logApiError — ignora chatRecordingService.recordUiTelemetryEvent
  • logToolCall — ignora chatRecordingService.recordUiTelemetryEvent
  • uiTelemetryService.addEventnão filtrado (garante que o tracking de tokens do /stats funcione)

Thinking Mode

Thinking/reasoning é explicitamente desabilitado (thinkingConfig: { includeThoughts: false }) para todos os caminhos de tarefas em background:

  • Forked query path (createForkedChat) — sobrescreve thinkingConfig no generationConfig clonado, cobrindo tanto a geração de sugestão quanto a especulação
  • BaseLlm fallback path (generateViaBaseLlm) — config por requisição sobrescreve as configurações de thinking do content generator base

Isso é seguro porque:

  • O prefixo do cache é determinado por systemInstruction + tools + history, e não por thinkingConfig — cache hits não são afetados
  • Todos os backends (Gemini, OpenAI-compatible, Anthropic) tratam includeThoughts: false omitindo o campo thinking — sem erros de API em modelos sem suporte a thinking
  • A geração de sugestão e a especulação não se beneficiam de reasoning tokens
Last updated on