Skip to content

feat(providers): provider OpenCode Go con cuotas 5 h/semana/ciclo (#187) - #201

Merged
egdev6 merged 12 commits into
egdev6:mainfrom
SrStamm:feat/opencode-go
Oct 29, 2026
Merged

egdev6 merged 12 commits into
egdev6:mainfrom
SrStamm:feat/opencode-go

Conversation

@SrStamm

@SrStamm SrStamm commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Related issue / Issue relacionada

Issue: #187
ODD: plan v2 aprobado como fuente única en #187 (comment); evidencia de implementación y smoke con API key real en #187 (comment)

Scope / Alcance

Provider nativo OpenCode Go (Kind::Subscription, grupo «sub»): cuotas de 5 h, semanal y mensual (ciclo de facturación) vía GET https://opencode.ai/zen/go/v1/usage con la API key de Go, sin x-opencode-org-id.

  • Backend (src-tauri/src/providers/opencode_go.rs): fetch, parseo tolerante por ventana con ids estables (five_hour/weekly/monthly), validación de percent (finito en [0,100]) y resetsAt independiente, degradación parcial explícita con un único Warning que nombra todas las ventanas afectadas, y precedencia de errores HTTP sobre el parseo (status_for_http puro, testeable sin red).
  • Config/secretos: OpenCodeGoConfig, OPENCODE_GO_API_KEY, ACCOUNT_PROVIDERS 11→12, tipos TS, toggle de visibilidad, cuentas adicionales con clave scoped.
  • UI: pestaña «OpenCode Go» en Suscripciones, sección en Ajustes con SecretInput/HealthStep y enlace a Console, monograma OC, filas en README.md/README.es.md.
  • Exclusiones (no-alcance del plan): desglose por modelo, saldo Extra Usage/USD, detección Go vs Go Plus, S1b («ventana no iniciada»: el reset recibido se muestra tal cual), OAuth de Console.

ODD feat/opencode-go-provider completo (T1–T9).

Coordination / Coordinación

Contributor / Contribuidor: @SrStamm
Claim or agreement evidence / Evidencia de asignación o acuerdo: asignación del mantenedor egdev6 en #187 (2026-10-27) y aprobación del plan v2 (2026-10-28, issuecomment-6472310756: «Plan aprobado: OK de arranque de T7»).

Acceptance and validation / Aceptación y validación

An open PR does not mean integration. Release, secrets and signing operations require separate authorization.
Una PR abierta no implica integración. Release, secretos y firma requieren autorización separada.

@egdev6 egdev6 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SrStamm gracias por la PR. Está muy alineada con el plan v2. Integraste todo lo previsto: secreto en ALL y for_provider, ACCOUNT_PROVIDERS, CONFIG_KEY/MainKey, dataKey, clave aislada en cuentas adicionales, OFFICIAL_USAGE_URL y monograma. El módulo está registrado desde T7, sin dead_code. La precedencia HTTP, el Warning único compuesto y S1b fuera respetan el contrato. También corregiste la etiqueta de evidencia de «ventana no iniciada» en el documento ODD. CI en verde en las cinco plataformas.

Un cambio necesario antes de integrar y algunos ajustes menores.

Bloqueante: validación de resetsAt (criterio 6)

parse_usage acepta cualquier string no vacío como resetsAt. Un valor como "garbage" pasa sin diagnóstico y llega al frontend, donde Date.parse devuelve NaN (src/lib/pace.ts). El contrato indica que un resetsAt inválido o ausente conserva el porcentaje, deja el reinicio como desconocido y lo advierte. chrono ya es dependencia del proyecto (zai.rs); con DateTime::parse_from_rfc3339 se resuelve. Añade un test con un string no interpretable que verifique: porcentaje conservado, resets_at: None y aviso emitido.

Cobertura de S4 (no bloqueante, pero estaba comprometida en el plan)

  • Faltan casos: el límite percent: 100, que S4 nombra explícitamente, y el JSON vacío o renombrado ({} o usage con otra clave).
  • Aserciones más estrictas:
    • rate_limited_window_warning_keeps_meters usa contains("rolling") || contains("tarifa"). Con ||, el test no garantiza que se nombre la ventana.
    • percent_validation_bounds debería verificar que weekly y monthly se conservan y que no se crea el medidor de five_hour.
    • unknown_status_or_missing_status debería comprobar que se diagnostican ambas ventanas, weekly y monthly.
  • Estado final del informe: ningún test verifica que el UsageReport termine en Status::Warning; solo se comprueba el vector de avisos. La nota 2 de la revisión del plan pedía que el test lo verificara. Extraer el tramo final de fetch a una función pura (por ejemplo build_report) permite probarlo sin red.
  • Procedencia de fixtures: el comentario de entitlement_body() describe la procedencia del 429 («no reproducido en vivo…»). El cuerpo del 403 es documental (openusage), no sintético.

Mensajes y copy (sugerencias)

  • Los avisos usan las claves internas del payload (rolling, monthly), pero el usuario ve «5 h» y «ciclo de facturación». Sugiero usar la etiqueta del medidor para que el mensaje sea accionable.
  • «Ventana limitada por tarifa» no transmite bien rate-limited. Una alternativa: «Límite de uso alcanzado en la ventana de 5 h».
  • La descripción de la pestaña dice «mensual», mientras la sección y el README hablan de «ciclo de facturación». Conviene unificarlo.
  • El guard de informe vacío (meters.is_empty() && warnings.is_empty()) no se cumple nunca, porque parse_usage siempre emite avisos cuando no hay medidores. El resultado actual es razonable (un Warning con diagnóstico), pero conviene eliminar esa rama o documentar que el diagnóstico la sustituye.

Tamaño

Teniendo en cuenta que el documento ODD y el inventario i18n son mecánicos, el código propio queda en un tamaño revisable. Mantenemos PR única, sin necesidad de partirla.

Con el ajuste de resetsAt y los tests reforzados, vuelvo a revisar. Esta revisión no autoriza merge ni release.

SrStamm added a commit to SrStamm/quorb that referenced this pull request Oct 28, 2026
- resetsAt no interpretable ya no llega tal cual al frontend (Date.parse daria
  NaN): reinicio desconocido + aviso, conservando un percent valido (criterio 6).
  Test dedicado con strings no interpretables (RED observado antes del fix).
- Avisos con la etiqueta visible del medidor (Cuota · 5 h / semana / ciclo de
  facturacion) en lugar de claves internas del payload (rolling/monthly); el
  limite de uso ahora se anuncia como tal.
- Extraer build_report del tramo final de fetch: tests verifican el Estado
  final del informe (Status::Warning) sin red.
- Cobertura S4 reforzada: percent 100 como limite valido, JSON vacio y usage
  renombrado, aserciones de ventanas diagnósticadas (percent_validation_bounds,
  unknown_status_or_missing_status), comentario del 403 con procedencia
  documental.

Refs: egdev6#187, feedback de review en PR egdev6#201
SrStamm added a commit to SrStamm/quorb that referenced this pull request Oct 28, 2026
Pestaña decia 'mensual' donde la sección y los READMEs dicen 'ciclo de
facturación' (sugerencia de review en PR egdev6#201).

Refs: egdev6#187, egdev6#201
SrStamm added a commit to SrStamm/quorb that referenced this pull request Oct 28, 2026
@SrStamm

SrStamm commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Gracias por la revisión, muy fina. Resuelto el bloqueante y aplicados los refuerzos, en 3 commits (cd70933, 82b8613, ef046c7).

Bloqueante: validación de resetsAt

parse_usage valida el string con chrono::DateTime::parse_from_rfc3339, la convención ya usada en el repo (cursor.rs, grok.rs). Un valor no interpretable conserva el porcentaje, deja el reinicio como desconocido y emite el aviso (criterio 6). Test nuevo con strings no interpretables, escrito primero y observado en rojo antes del fix.

Cobertura S4

  • percent: 100 como límite válido, sin diagnóstico y sin avisos.
  • JSON vacío ({}), usage renombrado y usage vacío: sin medidores y con diagnóstico; en el último caso cada ventana ausente se nombra y se componen en un único Warning.
  • Aserciones estrictas: el aviso de rate-limited nombra la ventana; percent_validation_bounds verifica que weekly y monthly se conservan y que five_hour no se crea; unknown_status_or_missing_status verifica que ambas ventanas quedan diagnosticadas.
  • build_report extraída del tramo final de fetch: dos tests verifican el estado final del informe (Warning con diagnósticos, Ok sin ellos) sin red.
  • Comentario del 403 corregido a procedencia documental.

Copy

  • Los avisos usan la etiqueta visible del medidor («Cuota · 5 h», «Cuota · semana», «Cuota · ciclo de facturación») en lugar de las claves internas rolling/monthly.
  • Rate-limited ahora: «Límite de uso alcanzado en Cuota · 5 h».
  • El guard de informe vacío quedó documentado como rama defensiva (inalcanzable hoy porque parse_usage siempre emite avisos sin medidores).
  • La pestaña dice «ciclo de facturación», unificada con la sección y los READMEs.

Verificación (post-cambios)

cargo test: 326 passed (19 del provider, +4). cargo fmt --check OK; clippy sin warnings en opencode_go.rs. npm test: 715 (vitest) + 240 (node scripts) passed. npm run typecheck limpio. node scripts/i18n-inventory.mjs --check OK. Verificación desde Linux, no se afirma Windows.

SrStamm added a commit to SrStamm/quorb that referenced this pull request Oct 28, 2026
…6#201)

Commits 82b8613 and cd70933 changed src-tauri texts ({label} renames,
new usage-limit and unknown-window messages, billing-cycle wording)
without regenerating docs/i18n-inventory.md, failing the Windows CI
i18n inventory check.
@egdev6

egdev6 commented Oct 29, 2026

Copy link
Copy Markdown
Owner

@SrStamm gracias por la respuesta tan completa. He revisado de nuevo el diff (7f91f67): todo lo de mi revisión anterior queda resuelto.

  • resetsAt se valida con DateTime::parse_from_rfc3339; un valor no interpretable conserva el porcentaje, deja el reinicio desconocido y avisa, con test para garbage, not-a-date y 2026-13-45T99:99:99Z.
  • Cobertura de S4 completa: percent: 100, JSON vacío, usage renombrado y usage vacío con un único Warning que nombra las tres ventanas; aserciones estrictas en rate-limited, percent_validation_bounds y unknown_status_or_missing_status.
  • build_report extraída y probada sin red para Warning y Ok; procedencia del 403 corregida; los avisos usan la etiqueta visible y la pestaña dice «ciclo de facturación».
  • Los 19 tests llevan #[test] y los 5 checks están en verde en el head.

Falta un paso: rebase sobre main

La PR está en conflicto porque #204 ya está en main, y cambia cómo se declaran las pestañas:

Coordinación con C1

@danielgap, para que lo tengas en cuenta: los textos del panel de OpenCode Go (kicker, nota, pasos, etiqueta y ayuda de la API key) quedan en español literal, lo que es correcto aquí porque migrar los paneles por proveedor es tu C1. Si #201 entra antes, C1 debería incluir este panel; si C1 entra antes, SrStamm lo adapta al rebasear.

Con el rebase y el CI en verde, la doy por aprobada.

…dev6#187)

Backend opencode_go.rs: GET https://opencode.ai/zen/go/v1/usage con API key
de Go (sin x-opencode-org-id), parseo tolerante por ventana (rolling/weekly/
monthly) con ids estables five_hour/weekly/monthly, validación de percent
(finito en [0,100]) y resetsAt independiente, degradación parcial explícita
con un único Warning que nombra todas las ventanas afectadas, y precedencia
de errores HTTP sobre el parseo (status_for_http puro, testeable sin red).

Estados: sin key y 401 → Unconfigured; 403 EntitlementError → Warning;
403 genérico → Unconfigured; 429 → Warning (fixture sintético marcado);
otro HTTP / no JSON → Error. S1b (heurística «ventana no iniciada») queda
fuera: el reset recibido se muestra tal cual.

Registrado en providers/mod.rs (fetch_all/fetch_one/fetch_account) en este
mismo commit para que quede verde. 15 tests propios; cargo test 322 OK,
fmt y clippy limpios.

Ref: egdev6#187
…gdev6#187)

S2: OpenCodeGoConfig { enabled } en AppConfig + ACCOUNT_PROVIDERS 11→12;
OPENCODE_GO_API_KEY en secrets.rs (ALL 13→14, for_provider); tipos TS
(opencodeGo, AccountProvider, SecretKey), MainKey + CONFIG_KEY en
settings-visibility, dataKey en widget-refresh, ACCOUNT_SECRETS +
accountReady en accounts.ts.

S3: pestaña «OpenCode Go» en PROVIDER_TABS (grupo sub), sección en
Settings con Toggle de visibilidad, SecretInput y HealthStep con enlace
a Console, rama scoped en AccountEditor, OFFICIAL_USAGE_URL a
opencode.ai/console, monograma OC en ProviderLogo, filas en
README.md/README.es.md. Los % se presentan como contabilidad de
OpenCode, sin desglose por modelo.

accountReady con rama opencode_go (exigida por el plan aprobado) y
monograma explícito 'OC' (Grok resuelve vía grok.svg en este repo).

Verificado: npm test 225 OK, typecheck limpio, inventario i18n OK,
cargo test 322 OK, cargo fmt --check y clippy limpios.

Ref: egdev6#187
…gdev6#187)

Los 6 textos nuevos de la UI (a40a158) no estaban en el inventario
commiteado; el contrato de CI de egdev6#186 (i18n-inventory --check) lo marca
como drift. React 770/915 → 776/930.

Ref: egdev6#187
- resetsAt no interpretable ya no llega tal cual al frontend (Date.parse daria
  NaN): reinicio desconocido + aviso, conservando un percent valido (criterio 6).
  Test dedicado con strings no interpretables (RED observado antes del fix).
- Avisos con la etiqueta visible del medidor (Cuota · 5 h / semana / ciclo de
  facturacion) en lugar de claves internas del payload (rolling/monthly); el
  limite de uso ahora se anuncia como tal.
- Extraer build_report del tramo final de fetch: tests verifican el Estado
  final del informe (Status::Warning) sin red.
- Cobertura S4 reforzada: percent 100 como limite valido, JSON vacio y usage
  renombrado, aserciones de ventanas diagnósticadas (percent_validation_bounds,
  unknown_status_or_missing_status), comentario del 403 con procedencia
  documental.

Refs: egdev6#187, feedback de review en PR egdev6#201
…6#201)

Commits 82b8613 and cd70933 changed src-tauri texts ({label} renames,
new usage-limit and unknown-window messages, billing-cycle wording)
without regenerating docs/i18n-inventory.md, failing the Windows CI
i18n inventory check.
@SrStamm

SrStamm commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Rebase hecho sobre 4e973d6 (#204). Head: 359f9c3. CI en verde en las cinco comprobaciones, incluida Windows.

Al resolver el conflicto adapté lo que pediste: PROVIDER_TABS usa descKey: 'settings.providers.desc.opencodeGo' con la clave en es.ts y en.ts, y los dos SecretInput nuevos reciben locale. Inventario i18n regenerado.

Un extra que apareció: el fixture de Settings-providers-i18n.test.tsx que viene de main no conocía opencodeGo y rompía 4 tests. Lo amplié y añadí la descripción nueva al array, así queda cubierta en ES y EN.

Nota sobre el historial: 82b8613 (unificar «ciclo de facturación») quedó vacío al rebasear, porque ese texto ya vive en la clave opencodeGo añadida en be7da93. Git descarta commits vacíos, por eso el conteo no sube aunque añadí el commit del test.

Con esto quedo pendiente de tu aprobación para que el review deje de bloquear el merge.

@egdev6 egdev6 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aprobado sobre 359f9c3. Los cambios pedidos en la revisión del 28 (validación de resetsAt con RFC 3339, cobertura de S4, build_report y textos de los avisos) quedan resueltos, y el rebase sobre #204 incorpora descKey, locale en los SecretInput e inventario regenerado. CI en verde en las cinco comprobaciones.

@egdev6

egdev6 commented Oct 29, 2026

Copy link
Copy Markdown
Owner

@SrStamm ¡muchas gracias por esta PR! He revisado el rebase (359f9c3) y está todo en orden: descKey con la descripción en es y en, locale en los dos SecretInput nuevos, inventario regenerado y el fixture del test de proveedores ampliado para cubrir OpenCode Go. El código del proveedor es el mismo que ya habíamos revisado.

Ha sido un trabajo de manual de principio a fin: evidencia real con tu API key, un plan ODD claro, tests escritos primero y respuestas a cada punto de la revisión sin dejar nada en el aire. Incluso el detalle del commit que quedó vacío al rebasear lo explicaste antes de que nadie preguntara. Así da gusto revisar.

Queda aprobada. Bienvenido OpenCode Go a la tira 🎉

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.

2 participants