Repository navigation
feat(providers): anillo de uso y saldo PAYG de MiniMax (#165) - #172
Conversation
Ref egdev6#165 - `GET {base}/v1/token_plan/remains` con la **Subscription Key** del plan M/Token. - `GET {base}/account/query_balance` con la **API key de pago por uso**, en lectura best-effort: su fallo deja intacto el informe del plan. Son dos credenciales distintas en la consola de MiniMax y cada medidor usa la suya: `secrets::MINIMAX_SUBSCRIPTION_KEY` y `secrets::MINIMAX_API_KEY`. **MiniMax responde HTTP 200 con el fallo dentro de `base_resp`.** Sin chequear el sobre, una clave caducada produce un informe vacío que parece correcto, que es peor que un fallo visible. `envelope()` es estricto: sobre ausente o vacío es error, `status_code` distinto de 0 propaga `status_msg`, y un sobre sin código no se interpreta como éxito ni como fallo. El porcentaje sale de `current_interval_usage_count / current_interval_total_count`. Según MiniMax-AI/cli#104, `usage_count` es el **consumo** y `current_interval_remains_count` lo que queda: usarlo invertiría el anillo. Un test lo fija con un payload donde los dos valores son distintos. El saldo PAYG se emite como medidor con `percent: None`, porque un saldo no es un porcentaje consumido y no hay total contra el que medirlo. La UI ya lo soporta: `ProviderGauge`, `ProviderDetails`, `ProviderCard`, `alerts` y `format` filtran los medidores sin porcentaje, así que no añade un anillo ni dispara avisos. `MiniMaxConfig.base_url` cubre las dos regiones (global `api.minimax.io`, China `api.minimaxi.com`), saneado igual que el de NaN. Tests deterministas con fixtures, sin red: porcentaje, `base_resp` fallido con HTTP 200, saldo sin degradar el plan, importes como string o número, total cero, y ventana con su reinicio.
El header graba `git rev-parse HEAD:src`, así que el inventario solo puede generarse después de commitear el código. +15 strings de React (sección de Ajustes) y +11 de Rust (minimax.rs).
egdev6
left a comment
There was a problem hiding this comment.
Revisión hecha — la arquitectura está bien resuelta: el chequeo estricto de base_resp (HTTP 200 no significa éxito), el porcentaje desde usage / total con fixture que cazaría un parser que cogiera remains, el saldo PAYG como medidor sin anillo que no degrada el informe del plan, y el cableado completo del patrón de proveedores (secretos, dataKey, alias minimax-cn, Ajustes). Nada de esto cambia.
Para aprobar necesito estos tres ajustes — los dos primeros son de dos líneas, el tercero es solo formato:
-
Indicar el saldo no disponible en el mensaje. Hoy, si el usuario configura
MINIMAX_API_KEYperoquery_balancefalla (clave inválida, host de región equivocado), no hay ningún indicio: ni medidor ni nota ni error. Es best-effort y el plan no se degrada — correcto — pero el usuario no puede distinguir "no configuro el saldo" de "el saldo falla". Propongo un apéndice en elmessagecuando la API key existe y elbalancevinoNone, por ejemplo"· saldo no disponible". Un test que lo fije y listo. -
Clamp del porcentaje a 100. Si un payload real reporta
current_interval_usage_count > current_interval_total_count(dato sucio, ciclo mal cerrado), el anillo va por encima de 100 %. Si la UI ya satura el anillo, este punto se puede cerrar solo con un test que lo fije; si no, un.min(100.0)enparselo resuelve gratis. Dime cuál de las dos y lo ajustamos. -
Formato: línea
minimax:mal indentada en elDefaultdeconfig.rs(4 espacios en vez de 8) y falta el newline final enminimax.rsyminimax.svg. Uncargo fmt+ newline.
Con eso, mi aprobación. El riesgo real que señalás en la descripción — nombres de campo no validados contra un payload vivo — me parece bien mitigado con la tolerancia del parser; si algún día llega una clave o un ejemplo real, los fixtures se ajustan encima sin cambios de diseño.
Verificado contra la documentación oficial tras revisar la spec. **Host.** El curl de la FAQ oficial usa `https://www.minimax.io/v1/token_plan/remains`, con `www.`, no `api.minimax.io`. El default pasa a `www.minimax.io` y el comentario de las regiones China (`www.minimaxi.com`) queda alineado. **Segunda ventana.** El Token Plan tiene dos: una de 5 horas (`current_interval_*`) y otra semanal (`current_weekly_*`), ambas citadas en el FAQ. El parser solo leía la de 5 h, así que el anillo se mantenía tranquilo mientras la semana iba al límite. Ahora emite las dos ventanas con reinicio y duración propias, y el mensaje del popup resume la peor. **Semántica de los contadores.** Estos campos cambiaron de significado al migrar de endpoint: en `/coding_plan/remains`, `current_interval_usage_count` era lo que quedaba (MiniMax-AI/MiniMax-M2#99); en `/v1/token_plan/remains` es el consumo (MiniMax-AI/cli#104, «`current_interval_usage_count` is used count, not remaining»). Aquí se usa la lectura del endpoint actual. El comentario en el parser deja constancia de las dos, porque volver a confundirlas invierte el anillo en silencio. **Nombre de modelo.** No aplica: el plan es una barra de uso compartida y no expone contadores por modelo, así que el proveedor no depende de si la línea es M3 o M3.1 Flash.
|
@Rafaeldelinares revisé los dos commits nuevos ( Las dos ventanas del plan: bien resueltoVentana de 5 h + semanal como medidores independientes con la tabla Bloqueante nuevo: el cambio de
|
Responde a la review del PR egdev6#172. **Host (bloqueante).** Verificado empíricamente contra la API: `api.` y `www.` de ambas regiones responden el mismo JSON con el sobre `base_resp`, así que los dos sirven y ninguno devuelve HTML. Se vuelve al default `api.minimax.io` por semántica —es el host de la API, no el sitio web— y el comentario en `config.rs` deja constancia de la verificación y de que el `curl` oficial usa `www.`, que también responde. El mismo sondeo confirma que `query_balance` es real: responde `{"base_resp":{"status_code":1004,"status_msg":"not authorized"}}` en vez de un 404, y `token_plan/remains` devuelve **HTTP 200 con `status_code` 1004**, que es justo el caso que justifica el chequeo estricto del sobre. **Saldo no disponible.** `parse` recibe si hay API key configurada, para que el usuario distinga «no configuré el saldo» de «lo configuré y falló». Sin la clave no se dice nada; con la clave y el saldo caído, el mensaje acaba en «· saldo no disponible». Test que fija los tres casos. **Saturación a 100.** El anillo ya saturaba en `ProviderGauge` y `RingGauge`, pero el valor crudo escapaba por `formatPercent` (que no satura), por `history.rs` —que lo persiste— y por el payload de alertas. El clamp va en `parse`: `used` y `limit` conservan el dato crudo y solo el porcentaje se satura. Test que fija un payload con `usage > total`. **Formato.** Corregida la indentación de la línea `minimax:` en el `Default` de `config.rs` (las vecinas van a 12 espacios) y añadidos los newlines finales que faltaban. No se corre `cargo fmt` porque el repo no lo cumple de base: son más de 400 archivos con el mismo patrón de líneas largas, incluido el resto de proveedores, y formatear solo este lo haría inconsistente con los demás.
|
@D6 Resueltos los cuatro puntos en Host: sondeado, no estimadoTu objeción era correcta en el riesgo: un default mal puesto rompe a todo el mundo y ningún test lo detecta. Así que lo comprobé contra la API en vez de discutirlo.
Conclusión: aun así vuelvo a Bonus del mismo sondeo: confirma que Clamp: la UI saturaba, pero no bastabaA tu pregunta de cuál de las dos: el anillo sí saturaba (
Así que el clamp va en Saldo no disponible
Formato: lo que nombraste sí,
|
|
Rectificación de dos erratas en mi comentario anterior: donde dice «Aun así», es «Aun así»; y donde dice «los dos funcionan». El resto de los datos del sondeo y los enlaces son correctos. Lo dejo anotado por si se copió antes de leerlo. |
|
@Rafaeldelinares he revisado el PR completo y reproducido la verificación en local. El backend es sólido y los tests son buenos, pero hay 3 fallos de integración que impiden el merge tal cual — los tres son cambios pequeños. ✅ Verificación reproducida
🔴 Bloqueante 1:
|
El parser anterior leía `current_interval_usage_count`, `current_interval_total_count` y las marcas de tiempo **en la raíz** de la respuesta. Con un payload real esos campos no existen ahí: el parser habría devuelto **cero medidores en todas las llamadas**, y todos los tests pasaban igual porque los fixtures eran inventados. Evidencia — `GET https://api.minimax.io/v1/token_plan/remains` con una Subscription Key válida (`sk-cp-…`, región global): ``` raíz: model_remains, base_resp raíz tiene current_interval_usage_count? False raíz tiene current_interval_total_count? False raíz tiene start_time? False raíz tiene base_resp? True modelos: ['general', 'video'] ``` Tres diferencias que el parser anterior no contemplaba: **1. Los datos viven en `model_remains[]`, y hay uno por categoría de modelo.** `general` y `video` consumen cuotas distintas: no es una barra compartida como supuse en la descripción del PR. El id del medidor lleva ahora el modelo (`token_plan_general_5h`) porque el popup y el historial los tienen que distinguir. **2. El campo fiable es `current_*_remaining_percent`, un porcentaje restante.** Los contadores `_usage_count` / `_total_count` existen pero no sirven: en `general` vienen a `usage=0, total=0` mientras el porcentaje restante dice 78. Dividir uno por otro daría 0 % con la cuota ya consumida. El porcentaje ahora sale de `100 - remaining_percent`, saturado. **3. Los tiempos van en milisegundos.** `end_time = 1792699200000` es `2026-10-22T20:00:00Z`. Interpretado como segundos caería en el año 58.000. Los tests se reescriben contra este payload real (recortado a los campos que usa el parser) e incluyen los que rompen cada una de las tres suposiciones: si el parser volviera a usar los contadores a cero, a leer la raíz o a tratar los tiempos como segundos, fallan.
**1. `CONFIG_KEY` sin `minimax`.** En `setAccountVisibility`, para un proveedor principal sin entrada en `CONFIG_KEY`: ```ts if (!Object.prototype.hasOwnProperty.call(CONFIG_KEY, id)) return cfg; ``` El toggle «Mostrar en el widget» **descartaba el cambio en silencio**: la UI lo reflejaba y al reiniciar volvía al valor anterior. Añadido a `CONFIG_KEY` y al tipo `MainKey`. **2. `minimax.baseUrl` no tenía campo en ninguna parte de la UI**, pero el hint de la Subscription Key prometía «cambia también el host en Ajustes avanzados». Era una promesa falsa: el usuario de la región de China tenía que editar el JSON a mano. Añadido el campo en un bloque «Avanzado», con el aviso de que las claves son por región —lo verificado en el sondeo de hosts, donde una clave global da `2049 invalid api key` en `api.minimaxi.com`— y los errores concretos a los que apuntar si el host no es el de la región. **3. Guard de medidores vacíos.** Si MiniMax renombra los campos, `parse` devolvía cero medidores y sin mensaje: un orbe en cero que el usuario lee como «no he consumido», que es justo lo que la descripción dice querer evitar. Ahora es un `Status::Warning` explícito. Mismo patrón que `claude.rs`, `codex.rs`, `cursor.rs`, `gemini.rs` y `nan.rs`.
- `needless_borrow` en la llamada a `balance(http, &base, …)`: `base` ya es `&str`. - `timestamp()` queda sin uso tras pasar los tiempos a milisegundos, y se elimina. - Typos en comentarios: «hides» → «esconde», «ycreer», y «no publishes saldo» → «no publica el saldo». `cargo clippy --lib --tests` queda en **0 avisos** atribuibles a `minimax.rs`.
|
@D6 Tres commits sobre tu review, en el orden que pediste. El primero cambia el diseño del parser, así que empieza por ese. El parser anterior no habría funcionado nuncaTenía una Subscription Key válida a mano, así que consulté la API antes de seguir. El parser leía Habría devuelto cero medidores en todas las llamadas, con los tests en verde porque los fixtures eran inventados. Es exactamente el riesgo que marqué en la descripción del PR, y este commit lo cierra con la evidencia que pediste. Tres suposiciones que resultaron falsas:
Los tests reescritos contra ese payload real, y cada suposición tiene el suyo: si el parser vuelve a la raíz, a los contadores o a los segundos, fallan. Lo que sí acertó tu review: Los tres bloqueantes, resueltos
Campo de host — añadido el input de Guard de medidores vacíos — MenoresClippy a 0 avisos en Una duda de criterio antes de que revises: con |
|
@Rafaeldelinares he revisado los tres commits ( ✅ Verificación reproducida
🔴→✅ Los tres bloqueantes, bien resueltos
🔴→✅ Rediseño del parser: verificado y convincenteEl payload real (
🟡 Menores (no bloquean)
📌 Sobre tu pregunta (4 medidores vs solo
|
|
@Rafaeldelinares un añadido menor del que no hace falta hacer PR aparte — cabe en este: Edge del guard de medidores vacíos: si No bloquea el merge; solo para que no quede en el ruido. |
Ref #165
Implementa el proveedor MiniMax con las dos credenciales separadas que fijó la review.
Los dos medidores
GET {base}/v1/token_plan/remainsGET {base}/account/query_balanceSon claves distintas en la consola de MiniMax y cada una va a su constante:
secrets::MINIMAX_SUBSCRIPTION_KEYysecrets::MINIMAX_API_KEY. Sin la segunda, el proveedor funciona igual y solo desaparece el saldo.base_resp: HTTP 200 no significa éxitoEs el punto que pediste validar. MiniMax devuelve HTTP 200 con el fallo dentro del sobre
base_resp. Sin chequearlo, una Subscription Key caducada produce un informe vacío que parece correcto — peor que un fallo visible, porque el usuario ve un orbe en cero y asume que consumió nada.envelope()es estricto en las tres direcciones:status_code == 0→ éxito.status_code != 0→ error, propagandostatus_msgcuando viene.nullo sin código → no se afirma ni éxito ni fallo: el caso con código ausente se acepta, y la ausencia total se reporta como error porque untoken_plan/remainssin sobre no es un payload que sabemos leer.Cubierto por
rejects_a_200_carrying_a_failed_base_resp, que ejercita justo elstatus_code: 1004con HTTP 200.El porcentaje va de
usage / total, nunca deremainsSegún MiniMax-AI/cli#104,
current_interval_usage_countes el consumo ycurrent_interval_remains_countes lo que queda. Usar el segundo invierte el anillo. El fixture usausage: 2500 / total: 10000conremains: 7500presentes, así que un parser que cogiera el campo equivocado daría 75 % en vez de 25 % y el test lo detecta.El saldo PAYG no es un anillo
No hay «total» contra el que medir un saldo, así que va como medidor con
percent: None. No añade un anillo ni puede disparar un aviso: verifiqué que la UI ya soporta ese caso —ProviderGauge.tsx:57,ProviderDetails.tsx:111,ProviderCard.tsx:80,alerts.ts:58yformat.ts:82filtran los medidores sin porcentaje en todos los caminos. Por eso no hace falta tocar esa lógica.Y su fallo no degrada el informe del plan:
a_missing_or_unusable_balance_leaves_the_plan_report_intactlo comprueba con saldo ausente, mal formado y conavailable_amountnulo, y en los tres casos el anillo del plan sigue intacto.Regiones
MiniMaxConfig.base_urlcon defaultapi.minimax.io, saneado igual que el de NaN (vacío → default, y se quita la barra final). Ajustes menciona la región de China enminimaxi.comal explicar de dónde sacar cada clave.Alcance
Kind::Subscription, logo SVG ensrc/assets/logos/, sección de Ajustes con los dos campos y botón de comprobar,widget-refresh.ts, alias de logo para el nodominimax-cnde 9Router, y sin cuentas adicionales: MiniMax no tiene un concepto de sesión por cuenta que valga el modelo deaccounts, así quefetch_accountqueda fuera a propósito.Verificación
cargo test --lib: 252 pasan, incluidos los 6 nuevos, todos con fixtures deterministas y sin red.npm test: 51 suites / 657 tests, exit 0.npm run typechecklimpio.docs/i18n-inventory.mdregenerado y--checken verde (+15 strings de React, +11 de Rust).Dos cosas que quiero que revises
totalcero o ausente no produce anillo, marcas de tiempo opcionales), pero un payload real podría usar nombres distintos. Si me pasás una clave o un ejemplo de la respuesta, ajusto los fixtures a la realidad.currentColorpara que rinda bien en el orbe. Si preferís el asset oficial de marca, lo cambio en un commit encima.Y el punto que dejé abierto en #168 sigue igual: esto no toca el cuadro negro de Wayland, que sigue siendo el problema de renderizado WebKitGTK que estamos midiendo por separado.