Skip to content

feat(ai): streaming, reasoning y selección de modelos en la tab de IA#18

Merged
joajo13 merged 7 commits into
mainfrom
feat/ai-tab-enhancements
Jun 29, 2026
Merged

feat(ai): streaming, reasoning y selección de modelos en la tab de IA#18
joajo13 merged 7 commits into
mainfrom
feat/ai-tab-enhancements

Conversation

@joajo13

@joajo13 joajo13 commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Qué hace

Mejoras end-to-end de la tab de IA del chat: streaming SSE token a token con reasoning, header con selección de proveedor/modelo, fetch de modelos live + fallback curado, dialogs dedicados y un loader estilo Gemini. Todo aditivo: el agentDriver es compartido con el Ctrl+B text-to-SQL del query-tab y query()/run() quedan intactos — el streaming es un camino nuevo.

Alcance (decisiones cerradas)

  • Tool-calling = solo render (scaffold). La UI sabe dibujar chips de tool-calls si llegaran, pero no se definen tools reales ni se ejecuta nada.
  • Modelos = fetch live + fallback curado. Se le pega al /models de cada provider con la API key; si falla (CORS/permiso/offline) cae a una lista curada por provider con badge "lista offline".

Cambios

Drivers / streaming

  • base.ts: modelo de eventos AgentStreamEvent (reasoning / text / tool_call / done / error) + abstract chatStream.
  • sse.ts (nuevo): parser SSE compartido, resiliente a chunks partidos; cancela el reader en el path de error; readStreamError.
  • common.ts: refactor a prepareSession/persistAssistant (mismo historial multi-turno que antes); chatStream con hard fallback al query() no-streaming. query()/run() sin tocar.
  • chatgpt/anthropic/gemini: queryStream parseando SSE. Anthropic con thinking (reasoning best-effort); Gemini parsea thought parts sin forzar includeThoughts (no rompe el default 2.0-flash). OpenAI por chat-completions no expone reasoning (documentado, no se inventa).
  • list.tsx: chatStream que resuelve el driver y delega.

Modelos y storage

  • agent-models.ts (nuevo): fetchProviderModels (OpenAI/Anthropic/Gemini) + CURATED_MODELS + filtros puros.
  • ai-agent-storage.ts: mergeAgentConfig/patchAgentConfig (update parcial de config).

UI

  • chat-tab-header.tsx (nuevo): [sparkle] Provider · model (clickeables) + 🔑 API key + + new chat. Lee config vía el SWR /local-agent-setting.
  • model-picker-dialog.tsx / provider-picker-dialog.tsx (nuevos) + local-setting-dialog.tsx (queda como dialog de API key).
  • chat-shimmer.tsx (nuevo): sparkle que pulsa + texto con gradiente que barre (CSS puro).
  • chat-tab.tsx: consume el stream (reasoning colapsable, tool chips scaffold, shimmer, new chat).

Review adversarial

Pasé un workflow de review por dimensión (SSE, fallback/errores, regresión Ctrl+B, React/UI, dialogs/a11y) con verificación escéptica independiente de cada hallazgo: 3 confirmados, 7 refutados. Los confirmados ya están arreglados en este PR:

  1. El hard fallback gateaba por "se emitió algo" e incluía reasoning → si el stream emitía solo reasoning y fallaba antes del texto, el turno quedaba con "Error:". Ahora gatea por texto real emitido.
  2. Al cambiar de proveedor quedaba colgado el token del proveedor anterior (401 silencioso que rompía chat y Ctrl+B). Ahora se limpia la key y se abre el dialog para pegar la del proveedor nuevo.
  3. El reader SSE no se cancelaba en el path de error (leak de conexión). Cancelado.

Verificación local

typecheck ✓ · lint ✓ · jest 153/153 ✓ · audit:gate ✓ · build

Tests nuevos: parseo de /models por provider + fallback curado; queryStream SSE (text vs reasoning) por provider; hard fallback (incluido reasoning→error→fallback); helpers de storage.

joajo13 added 7 commits June 29, 2026 11:59
…ool-calls)

- base.ts: modelo de eventos AgentStreamEvent + abstract chatStream
- sse.ts: parser SSE compartido (resiliente a chunks partidos) + readStreamError
- common.ts: refactor a prepareSession/persistAssistant; chatStream con hard
  fallback al query() no-streaming; abstract queryStream. query()/run() intactos
- chatgpt/anthropic/gemini: queryStream parseando SSE (Anthropic con thinking,
  Gemini con thought parts best-effort)
- list.tsx: chatStream que resuelve el driver y delega
- common.ts: el hard fallback gatea por TEXTO emitido, no por emittedAny. Si el
  stream emite solo reasoning (Anthropic/Gemini) y después falla antes del texto,
  ahora cae al query() no-streaming en vez de dejar el turno con 'Error:'
- provider-picker: al cambiar de provider limpia el token (la key vieja es de otro
  provider → 401 silencioso que rompía chat Y Ctrl+B) y abre el dialog de key
- sse.ts: cancela el reader en el path de error para no colgar la conexión
- tests: reasoning→error→fallback, y merge con token vacío
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@joajo13 joajo13 merged commit 9bb60d6 into main Jun 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant