## Problem We're getting 402s when an AI **agent** (chat) run hits the free allowance / spending cap, but the frontend handles them poorly and never pops the usage-limit modal. The agent runs its tool calls **server-side** (loopback HTTP via `PolicyExecutor`), so the 402 never reaches the `apiClient` interceptor that pops the modal for direct calls. It was caught by the generic tool-failure handler and flattened into a `CANNOT_CONTINUE` reason string (`"The /api/v1/… tool failed: 402…"`), streamed as a `result` event, and rendered as a scary chat bubble. This is the same gap the policy auto-run path bridges (#6626) — one layer up. ## Fix **Backend** (`proprietary`) - `AiWorkflowResponse` gains `errorCode` + `errorSubscribed`. - `AiWorkflowService` detects a downstream 401/402 entitlement sentinel in its three tool-exec catch sites (`onToolCall`, `runPlan`, `onConvertMarkdown`) and surfaces the structured code (+ `subscribed`) on the terminal response instead of the raw failure text. - Factored the 401/402 body extraction `PolicyEngine` already had into a shared `DownstreamEntitlementError` util so the two server-side paths can't drift. **Frontend** - New `usageLimitBridge` (`PAYG_LIMIT_REACHED_EVENT` + `dispatchPaygLimitReached`) generalises the previously policy-only bridge. Proprietary can't import the saas modal API (layering), so server-side limit hits broadcast a window event the saas `UsageLimitModalHost` opens the modal from. Migrated the policy path onto it. - `ChatContext` fires the matching modal (free → subscribe, subscribed → raise cap) on the limit result **and** on a direct 402, replacing the raw reason with a brief friendly line (`chat.responses.usage_limit_reached`). No Python engine changes — the charge/402 happens on the Java tool endpoint that Java itself calls. ## Test plan - [x] `:proprietary:compileJava` + `spotlessCheck` clean - [x] `AiWorkflowServiceTest` + `PolicyEngineTest` green - [x] eslint, proprietary + saas typechecks clean - [ ] Manual: drive an agent run over the limit → brief line in chat + the right modal (free vs cap) > Note: proprietary test compilation is currently blocked on the pre-existing `InitialSecuritySetupTest` 6-arg ctor break (unrelated, tracked separately); verified locally by temporarily patching it.
Stirling PDF - The Open-Source PDF Platform
Stirling PDF is a powerful, open-source PDF editing platform. Run it as a personal desktop app, in the browser, or deploy it on your own servers with a private API. Edit, sign, redact, convert, and automate PDFs without sending documents to external services.
Key Capabilities
- Everywhere you work - Desktop client, browser UI, and self-hosted server with a private API.
- 50+ PDF tools - Edit, merge, split, sign, redact, convert, OCR, compress, and more.
- Automation & workflows - No-code pipelines direct in UI with APIs to process millions of PDFs.
- Enterprise‑grade - SSO, auditing, and flexible on‑prem deployments.
- Developer platform - REST APIs available for nearly all tools to integrate into your existing systems.
- Global UI - Interface available in 40+ languages.
For a full feature list, see the docs: https://docs.stirlingpdf.com
Quick Start
docker run -p 8080:8080 docker.stirlingpdf.com/stirlingtools/stirling-pdf
Then open: http://localhost:8080
For full installation options (including desktop and Kubernetes), see our Documentation Guide.
Resources
Support
- Community Discord
- Bug Reports: Github issues
Contributing
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
This project uses Task as a unified command runner for all build, dev, and test commands. Run task install to get started, or see the Developer Guide for full details.
For adding translations, see the Translation Guide.
License
Stirling PDF is open-core. See LICENSE for details.

