JSONL headless: reconstrua a resposta com honestidade
Siga eventos thought, text, end e error sem inventar um evento de resultado final.
Commit upstream fixado: b189869b7755d2b482969acf6c92da3ecfeffd36
- Reconstruir uma resposta a partir de deltas de texto ordenados.
- Ler `end` como metadata, não texto da resposta.
- Explicar por que eventos desconhecidos são ignorados e falha de processo ainda falha.
A grande ideia
No modo streaming o Grok imprime um objeto JSON por linha. O texto chega em partes, então o Alembic concatena cada campo `data` na ordem. A linha final `end` fecha o stream e carrega metadados de sessão e uso; ela não repete a resposta. Linhas desconhecidas são ignoradas para que um evento futuro não corrompa o resultado atual.
Pense como… É um trem: cada linha `text` é um vagão com parte da resposta, enquanto `end` é a lanterna do guarda confirmando que o trem terminou. A lanterna traz documentação, não outro vagão. A analogia quebra porque o JSONL pode incluir tipos de evento ignoráveis entre chunks de texto.
`parseStreamLine` é stateless: `text` vira evento de texto; `end` pode virar eventos de sessão e uso; `error` vira detalhe de resultado/erro; thought, desconhecido, malformado e não JSON não produzem eventos. O estado vive no acumulador ordenado do Orchestrator.
O fallback do resultado é `resultText || accumulatedOutput || raw stdout`. A ordem importa: o stream normal do Grok tem deltas mais end somente com metadata, então o texto acumulado precisa vencer o JSONL bruto. Exit não zero ainda falha a execução e prefere stderr no diagnóstico.
Evidência: Guia fixado do modo headless
Compare as fronteiras
Seguro: concatenar apenas `text.data`, então retornar o acumulador.
Arriscado: serializar `end` ou JSONL bruto como resposta do assistente.
| Superfície | Upstream | Alembic | Veredito |
|---|---|---|---|
| text | Delta de resposta em `data` | Concatenar ao acumulador | KEEP |
| thought | Stream de raciocínio | Ignorar no output do provider | IGNORE |
| end | Metadata de sessão + uso | Emitir metadata; sem texto final | CLOSE |
O fallback do resultado é `resultText || accumulatedOutput || raw stdout`. A ordem importa: o stream normal do Grok tem deltas mais end somente com metadata, então o texto acumulado precisa vencer o JSONL bruto. Exit não zero ainda falha a execução e prefere stderr no diagnóstico.
Código e comandos reais
packages/factory/src/GrokAgentProvider.ts
if (event.type === "text" && typeof event.data === "string") {
return [{ type: "text", text: event.data }];
}
if (event.type !== "end") return [];
if (typeof event.sessionId === "string") {
parsed.push({ type: "session_id", sessionId: event.sessionId });
}Como chegar lá
sed -n '35,120p' packages/factory/src/GrokAgentProvider.ts sed -n '145,205p' packages/factory/src/Orchestrator.ts
Evidência: RESOURCES.md
Atlas visual
Prática e feedback
O stream emite dois deltas de texto e então `end`. Onde está a resposta final?
Cartões de recuperação
Percorra o stream
Selecione um evento e veja a ação exata do provider.
Resumo em quatro slides
Teste seu modelo
Qual evento fornece o texto normal da resposta final?