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 IA concluir uma resposta, exibindo como texto fantasma no campo de entrada.

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

Visão Geral

Uma sugestão de prompt (Sugestão de Próximo Passo / NES) é uma previsão curta (2-12 palavras) da próxima entrada do usuário, gerada por uma chamada de LLM após cada resposta da IA. Ela aparece como texto fantasma no campo de entrada. O usuário pode aceitá-la com Tab/Enter/Seta Direita ou descartá-la digitando.

Arquitetura

┌─────────────────────────────────────────────────────────────┐ │ AppContainer (CLI) │ │ │ │ Transição Respondendo → Ocioso │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Condições de Guarda (11 categorias) │ │ │ │ configurações, interativo, sdk, modo plano, │ │ │ │ diálogos, elicitação, erro de API │ │ │ └────────────────────┬────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ generatePromptSuggestion() │ │ │ │ │ │ │ │ ┌─── CacheSafeParams disponível? ───┐ │ │ │ │ │ │ │ │ │ │ ▼ SIM NÃO ▼ │ │ │ │ runForkedQuery() BaseLlmClient.generateJson() │ │ │ │ (ciente de cache) (fallback independente) │ │ │ │ │ │ │ │ ──── SUGGESTION_PROMPT ──── │ │ │ │ ──── 12 regras de filtro ────── │ │ │ │ ──── getFilterReason() ──── │ │ │ └────────────────────┬────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ FollowupController (independente de framework) │ │ │ │ Atraso de 300ms → exibir como texto fantasma │ │ │ │ │ │ │ │ Tab → aceitar (preencher entrada) │ │ │ │ Enter → aceitar + enviar │ │ │ │ Direita → aceitar (preencher entrada) │ │ │ │ Digitar → descartar + abortar especulação │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Telemetria (PromptSuggestionEvent) │ │ │ │ resultado, método_aceite, tempo, similaridade, │ │ │ │ tecla_pressionada, foco, motivo_supressão, id_prompt│ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘

Geração da Sugestão

Prompt do 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 markdown
evaluative”looks good”, “thanks” (com \b boundaries)
ai_voice”Let me…”, “I’ll…”, “Here’s…”

Condições de Guarda

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

GuardaVerificação
Alternância de config.enableFollowupSuggestions
Não interativoconfig.isInteractive()
Modo SDK!config.getSdkMode()
Transição de streamingRespondendo → Ocioso (2 verificações)
Erro de API (histórico)historyManager.history[last]?.type !== 'error'
Erro de API (pendente)!pendingGeminiHistoryItems.some(type === 'error')
Diálogos de confirmaçãoshell + geral + detecção de loop (3 verificações)
Diálogo de permissãoisPermissionsDialogOpen
ElicitaçãosettingInputRequests.length === 0
Modo planoApprovalMode.PLAN

Dentro de generatePromptSuggestion():

GuardaVerificação
Início da conversamodelTurns < 2

Flags de funcionalidade separadas (não no bloco de guarda):

FlagControla
enableCacheSharingSe usa consulta bifurcada ou fallback para generateJson
enableSpeculationSe inicia especulação ao exibir sugestão

Gerenciamento de Estado

FollowupState

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

FollowupController

Controlador independente de framework compartilhado entre CLI (Ink) e WebUI (React):

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

Interação por Teclado

TeclaCLIWebUI
TabPreencher entrada (sem envio)Preencher entrada (sem envio)
EnterPreencher + enviarPreencher + enviar (explicitText)
Seta DireitaPreencher entrada (sem envio)Preencher entrada (sem envio)
DigitaçãoDescartar + abortar especulaçãoDescartar
ColarDescartar + abortar especulaçãoDescartar

Nota sobre Atalhos de Teclado

O manipulador de Tab usa key.name === 'tab' explicitamente (não o matcher ACCEPT_SUGGESTION) porque ACCEPT_SUGGESTION também corresponde a Enter, que precisa ser tratado pelo manipulador 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 da exibição até aceitar
time_to_ignore_msnumberTempo da exibição até descartar
time_to_first_keystroke_msnumberTempo até primeira tecla enquanto exibido
suggestion_lengthnumberContagem de caracteres
similaritynumber1.0 para aceite, 0.0 para ignorar
was_focused_when_shownbooleanTerminal estava em foco
reasonstringPara suprimido: nome da regra de filtro

SpeculationEvent

CampoTipoDescrição
outcomeaccepted/aborted/failedResultado da especulação
turns_usednumberRound-trips de API
files_writtennumberArquivos no overlay
tool_use_countnumberFerramentas executadas
duration_msnumberTempo real decorrido
boundary_typestringO que parou a especulação
had_pipelined_suggestionbooleanPróxima sugestão gerada

Flags de Funcionalidade e Configurações

ConfiguraçãoTipoPadrãoDescrição
enableFollowupSuggestionsbooleantrueAlternância principal para sugestões de prompt
enableCacheSharingbooleantrueUsa consultas bifurcadas cientes de cache
enableSpeculationbooleanfalseMecanismo de execução preditiva
fastModel (top-level)string""Modelo para tarefas em segundo plano (vazio = modelo principal). Definido via /model --fast

Filtragem Interna de ID de Prompt

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

ID do PromptUsado por
prompt_suggestionGeração de sugestão
forked_queryConsultas bifurcadas cientes de cache
speculationMecanismo de especulação

Filtragem aplicada:

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

Modo de Raciocínio (Thinking)

O modo de raciocínio é explicitamente desabilitado (thinkingConfig: { includeThoughts: false }) para todos os caminhos de tarefas em segundo plano:

  • Caminho de consulta bifurcada (createForkedChat) — sobrescreve thinkingConfig no generationConfig clonado, cobrindo tanto geração de sugestão quanto especulação
  • Caminho de fallback BaseLlm (generateViaBaseLlm) — configuração por requisição sobrescreve as configurações de raciocínio do gerador de conteúdo base

Isso é seguro porque:

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