mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-26 11:12:53 +00:00
### Linked issue Closes #5247 ### Type of change - [x] Bug fix - [ ] New feature - [ ] Enhancement - [ ] Refactor - [ ] Docs ### Reasoning A user whose MiniMax account has no active token plan opened the Usage page and saw a red **Error** badge on the MiniMax card with a block of raw JSON as its body: `[{"expected":"array","code":"invalid_type","path":["model_remains"],...}]`. MiniMax reports application-level failures inside an HTTP 200 body — `base_resp.status_code` is non-zero and `model_remains` comes back as `null`. Two things went wrong with that response: - `MiniMaxQuotaResponseSchema` declared `model_remains` as `z.array(...).optional()`, which admits a *missing* key but not `null`, so `.parse()` threw on a perfectly ordinary no-subscription response. - `fetchUsage()` only checked `res.ok`, so an API-level error returned with HTTP 200 was never inspected in the first place. The rejection propagated to `ProviderUsageService`, which stores `reason.message` as the card's error text. For a `ZodError`, that message *is* the serialized issue list — which is how the JSON blob reached the card. This accepts `null` for `model_remains`, parses `base_resp`, and returns the plain unavailable result when the status code is non-zero, matching how a failed HTTP response is already handled in the same method. The API's own `status_msg` goes to the debug log rather than to the card: an account without a token plan is a normal state, not an error, so it should read as "Unavailable" like an unconfigured provider does — not as a red Error. ### Goals - A MiniMax account with no active token plan renders as **Unavailable** with no error text. - `model_remains: null` is accepted wherever it appears, with or without a `base_resp`. - Application-level errors carried in an HTTP 200 body are detected rather than treated as success. - Accounts that do have a plan keep rendering their interval and weekly windows unchanged. ### Non-goals - **Generic error rendering in the Usage card.** The issue also asks that structured errors (ZodError and friends) be mapped to a concise message in the UI. That changes what every provider's card shows on schema drift, so it is a design decision for the maintainer rather than part of this fix. With the producer corrected, no ZodError arises on this path; if another provider's response shape drifts, raw issue JSON can still reach the card. Left open for the maintainer. - Surfacing MiniMax's `status_msg` in the UI. It is logged at debug instead, so the card does not turn red for a normal no-subscription account. ### QA Reproduced and verified through the real `ProviderUsageService` with the exact payload from the report, in `packages/server/src/services/quota-fetcher/service.test.ts`. **Before** — `reports MiniMax accounts with no active token plan as unavailable` fails, and the failure output is the reported symptom verbatim: ``` - Expected + Received - "status": "unavailable", + "status": "error", - "error": null, + "error": "[ + { + \"expected\": \"array\", + \"code\": \"invalid_type\", + \"path\": [ \"model_remains\" ], + \"message\": \"Invalid input: expected array, received null\" + } + ]", ``` **After** — the full quota-fetcher suite passes: `Tests 71 passed (71)`. Three tests were added, two of which are red without the provider change (confirmed by stashing only `minimax.ts`): - `reports MiniMax accounts with no active token plan as unavailable` — the reported response (`model_remains: null` + `base_resp` 2062) → `status: "unavailable"`, `error: null`. - `reports a null MiniMax token plan as unavailable even without base_resp` — `model_remains: null` alone. - `still reads MiniMax windows when base_resp reports success` — guards against the new status check rejecting healthy accounts. Also run locally: `npm run typecheck --workspace=@getpaseo/server`, `npx oxlint packages/server/src/services/quota-fetcher/`, `npx oxfmt --check packages/server/src/services/quota-fetcher/` — all clean. No UI change, so no screenshots: with the provider fixed, the card takes its existing "Unavailable" rendering, the same one an unconfigured provider already gets. ### Checklist - [x] Plugin changes follow the SDK import boundaries (if applicable) - [x] One focused change - [x] `npm run typecheck` passes - [x] `npm run lint` passes - [x] `npm run format` passes - [x] QA evidence - [x] Tests added or updated where it made sense ## Supersedes - [PR #5252 — fix(quota): handle MiniMax accounts with no active token plan (#5247)](https://github.com/getpaseo/paseo/pull/5252) — Same diagnosis and same reported workflow. That PR returns `status: "error"` carrying MiniMax's `status_msg`, which keeps the red Error badge on the card for an account that is simply unsubscribed; this branch returns the plain unavailable result the issue asks for. It also adds cross-provider `ZodError` summarizing in `ProviderUsageService`, which is the design decision left as a non-goal above. Co-authored-by: Matteo Pietro Dazzi <ilteoood@gmail.com>
Voice Assistant
A voice-controlled terminal assistant that runs as a single local service.
Quick Start
# Install dependencies
npm install
# Copy environment variables
cp .env.example .env
# Edit .env and add your API keys (OpenAI, Deepgram)
# Run development servers
npm run dev
# Open browser to http://localhost:5173
Architecture
- Express Server (port 3000) - Serves API and built UI in production
- Vite Dev Server (port 5173) - Hot-reload React UI in development
- WebSocket (
/ws) - Real-time bidirectional communication - Agent - STT → LLM → TTS pipeline with terminal control
- Daemon - tmux-based terminal management (in-process)
Development
# Run both servers (recommended)
npm run dev
# Or run separately:
npm run dev:server # Express on port 3000
npm run dev:ui # Vite on port 5173
# Type checking
npm run typecheck
# Build for production
npm run build
# Start production server
npm start
Project Status
✅ Completed (Phases 1-2):
- Package setup and configuration
- Express server with WebSocket
- React UI with Vite
- WebSocket client with ping/pong testing
⏳ In Progress (Phase 3):
- Terminal control (tmux integration)
📋 Planned (Phases 4-9):
- LLM integration (OpenAI GPT-4)
- Agent orchestrator
- Speech-to-Text (Deepgram)
- Text-to-Speech (OpenAI)
- Audio streaming
- UI polish
See IMPLEMENTATION_PLAN.md for complete details.
Environment Variables
OPENAI_API_KEY=your-openai-key-here # GPT-4 and TTS
DEEPGRAM_API_KEY=your-deepgram-key-here # Streaming STT
STT_MODEL=whisper-1 # Optional: override to gpt-4o-transcribe, etc.
STT_CONFIDENCE_THRESHOLD=-3.0 # Optional: reject low-confidence clips
STT_DEBUG_AUDIO_DIR=.stt-debug # Optional: persist raw dictation audio for debugging
PASEO_HOME=~/.paseo # Runtime state directory (agents/, etc.)
PASEO_LISTEN=127.0.0.1:6767 # Listen address (host:port or /path/to/socket)
PASEO_HOME defaults to ~/.paseo and isolates runtime artifacts like agents/. PASEO_LISTEN controls the daemon listen address. For blue/green testing you can run a parallel server without touching production state:
PASEO_HOME=~/.paseo-blue PASEO_LISTEN=127.0.0.1:7777 npm run dev
Tech Stack
- Server: Express, TypeScript, ws (WebSocket)
- Client: React 18, Vite, TypeScript
- Terminal: tmux (via child_process)
- AI: OpenAI (LLM + TTS), Deepgram (STT)
Testing
Currently manual testing via:
- Start servers:
npm run dev - Open http://localhost:5173
- Test WebSocket connection (green status indicator)
- Click "Send Ping" button to test communication
More testing guidance as features are implemented.
License
MIT