llm-cli-gateway 2.12.1 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +177 -19
- package/dist/acp/provider-registry.js +13 -0
- package/dist/api-provider.d.ts +1 -0
- package/dist/api-provider.js +13 -0
- package/dist/approval-manager.d.ts +1 -1
- package/dist/async-job-manager.d.ts +35 -3
- package/dist/async-job-manager.js +344 -46
- package/dist/cli-updater.d.ts +1 -1
- package/dist/cli-updater.js +17 -9
- package/dist/config.d.ts +34 -0
- package/dist/config.js +93 -8
- package/dist/doctor.d.ts +32 -4
- package/dist/doctor.js +80 -13
- package/dist/endpoint-exposure.js +15 -3
- package/dist/executor.js +2 -0
- package/dist/http-transport.d.ts +3 -0
- package/dist/http-transport.js +138 -8
- package/dist/index.d.ts +62 -5
- package/dist/index.js +774 -83
- package/dist/model-registry.d.ts +1 -1
- package/dist/model-registry.js +12 -16
- package/dist/provider-login-guidance.d.ts +12 -0
- package/dist/provider-login-guidance.js +46 -0
- package/dist/provider-status.d.ts +13 -1
- package/dist/provider-status.js +22 -13
- package/dist/provider-tool-capabilities.d.ts +4 -1
- package/dist/provider-tool-capabilities.js +310 -11
- package/dist/provider-types.d.ts +4 -0
- package/dist/provider-types.js +10 -0
- package/dist/resources.d.ts +6 -2
- package/dist/resources.js +72 -8
- package/dist/session-manager.d.ts +3 -5
- package/dist/session-manager.js +4 -2
- package/dist/upstream-contracts.js +169 -0
- package/dist/validation-normalizer.js +5 -4
- package/dist/validation-orchestrator.js +4 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/setup/status.schema.json +68 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the llm-cli-gateway project.
|
|
4
4
|
|
|
5
|
+
## [2.13.0] - 2026-07-01: Cursor Agent CLI provider
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Cursor Agent CLI (7th gateway provider).** `cursor_request` / `cursor_request_async`
|
|
10
|
+
drive `cursor-agent --print` headless mode with model selection, `plan`/`ask`
|
|
11
|
+
mode, output format, force/auto-review/sandbox/trust controls, workspace and
|
|
12
|
+
extra `--add-dir` roots, and session resume (`--resume`/`--continue`). Cursor
|
|
13
|
+
has no `promptParts` surface (plain `prompt` only, like Devin) and is not
|
|
14
|
+
wired for request-time MCP server injection because Cursor owns MCP config via
|
|
15
|
+
`cursor-agent mcp`. Covered by doctor, provider capability inventory,
|
|
16
|
+
provider-login guidance, upstream-contract drift detection, and CLI upgrade
|
|
17
|
+
support (`cursor-agent update`).
|
|
18
|
+
- **Cursor native ACP discovery and gated transport.** The installed Cursor Agent
|
|
19
|
+
CLI exposes a native `cursor-agent acp` stdio entrypoint; initialize plus
|
|
20
|
+
`session/new` smoke passed locally. Gateway request tools still default to the
|
|
21
|
+
CLI headless path. Explicit `transport:"acp"` stays config-gated and rejects
|
|
22
|
+
unsupported Cursor CLI-only controls instead of silently dropping them.
|
|
23
|
+
- **Cursor review hardening.** Cursor validation reviewers now run through
|
|
24
|
+
`cursor-agent --print --mode ask --sandbox enabled`; high-impact Cursor flags
|
|
25
|
+
(`force`, `trust`, `sandbox:"disabled"`) are gated under
|
|
26
|
+
`approvalStrategy:"mcp_managed"`; remote HTTP/OAuth workspace inputs must use
|
|
27
|
+
registered workspace aliases/roots; and docs/schema warn that gateway-created
|
|
28
|
+
`gw-*` ids are not resumable Cursor chat ids.
|
|
29
|
+
|
|
30
|
+
## [2.12.2] - 2026-07-01: backpressure hardening and release-gate stability
|
|
31
|
+
|
|
32
|
+
### Security
|
|
33
|
+
|
|
34
|
+
- **HTTP/session and async-job backpressure hardening for issue #130.** HTTP MCP session initialization is now bounded before gateway session creation, including concurrent initialize bursts and `createGatewayServer()` / `server.connect()` failure cleanup. Async process/API jobs now share bounded global, per-provider, and queue caps; queued jobs are treated as in-progress by sync/deferred paths; direct sync and inline API execution are covered by the same limiter; and `/healthz` / `llm_process_health` expose prompt-free session, queue, limiter, and memory pressure metrics. The release includes deterministic regression coverage for HTTP initialize bursts, pending-initialize accounting, queued deferral behavior, and async job caps.
|
|
35
|
+
- **`llm_process_health` now redacts `base_url` userinfo.** The outbound-providers health block surfaces each API provider's `base_url`, which is config-supplied and may legally carry URL userinfo (`https://user:pass@host/v1`). Both the dedicated `xai` block and the generic `apiProviders` array now run `base_url` through `redactDiagnosticUrl` (stripping `username`/`password` and sensitive query params) before emitting it, matching the redaction already applied on the `doctor` and login-guidance surfaces. The live request path and circuit breakers are unaffected.
|
|
36
|
+
- **`redactDiagnosticUrl` now leaves clean URLs byte-identical.** When a URL has nothing to redact (no userinfo, no sensitive query/hash params), the helper returns the caller's exact bytes instead of the URL parser's canonicalized form (lowercased host, dropped default port, normalized path/encoding). This keeps non-secret `base_url` / public-URL values unchanged on every diagnostic surface (`doctor`, login-guidance, `llm_process_health`).
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **Doctor, status, login-guidance, tool-capabilities, and resources now surface enabled API providers (Slice 6).** The five peripheral discovery surfaces were CLI-only; they now report enabled `[providers.<name>]` (kind:`api`) providers too, staying byte-identical when none are enabled:
|
|
41
|
+
- `doctor --json` gains an `api_providers` health block (per provider: kind, base_url, default_model, models, the key env var name, whether the key is present, and login guidance). The block is omitted entirely when no API providers are enabled. An opt-in `--probe-api-providers` flag adds a reachability result per provider; a normal `doctor` run spends no network or tokens (`reachable` stays null). The key value is never reported, only its presence. `setup/status.schema.json` is updated to match.
|
|
42
|
+
- `provider-status` gains `getApiProviderStatus` (enabled + key-present projection; no spawnable binary) and `provider-login-guidance` gains `getApiProviderLoginGuidance` (where to get the key, which env var, the base_url; keyless-local guidance for loopback providers).
|
|
43
|
+
- `provider_tool_capabilities` now builds real per-`api`-kind metadata on demand instead of the former unreachable defensive throw: API providers support model + sampling + (xai-responses) reasoning + continuity, and never expose allow/deny lists, MCP servers, local skills, or workspace/worktree controls. The capability filter enum, the `provider-tools://<id>` resource allowlist, and the capability-id set widen together to include enabled API provider names.
|
|
44
|
+
- `models://<api-provider>` resources are exposed for every enabled API provider, and `sessions://<api-provider>` for continuity-tracked kinds. The `CLI_TYPES` guard on the `provider-subcommands://` resources is unchanged, so API providers stay correctly excluded there.
|
|
45
|
+
- **`list_models` now surfaces enabled API providers (Slice 5).** When one or more `[providers.<name>]` API providers are enabled, `list_models` returns them under an `apiProviders` array (each tagged `providerKind:"api"` with `defaultModel` and the optional model allowlist), mirroring `list_available_models` and the `llm_process_health` outbound block. The `cli` filter accepts an enabled API provider name in addition to the six CLI providers. The field is omitted entirely when no API providers are enabled, so dormant output is byte-identical to before.
|
|
46
|
+
|
|
47
|
+
### CI
|
|
48
|
+
|
|
49
|
+
- **Lychee no longer fails release/security CI on GitHub's unauthenticated latest-release API.** The link-rot gate now excludes `https://api.github.com/repos/*/*/releases/latest`, matching the upstream-scan model where these advisory release metadata URLs are verified separately and GitHub Actions 403/rate-limit responses should not block an otherwise clean release.
|
|
50
|
+
|
|
5
51
|
## [2.12.1] - 2026-06-30: fast-uri transitive vulnerability remediation
|
|
6
52
|
|
|
7
53
|
### Security
|
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
> _"Without consultation, plans are frustrated, but with many counselors they succeed."_
|
|
10
10
|
> — Proverbs 15:22 (LSB)
|
|
11
11
|
|
|
12
|
-
A Model Context Protocol (MCP) gateway for running Claude Code, Codex, Gemini, Grok, Mistral (Vibe), and
|
|
12
|
+
A Model Context Protocol (MCP) gateway for running Claude Code, Codex, Gemini, Grok, Mistral (Vibe), Devin, and Cursor Agent CLIs from one MCP endpoint, with durable async jobs, session continuity, cache-aware prompting, observability, and personal-appliance setup tooling.
|
|
13
13
|
|
|
14
|
-
**Why developers try it:** one local MCP endpoint for cross-LLM validation, multi-agent coding workflows, and repeatable assistant-led setup across
|
|
14
|
+
**Why developers try it:** one local MCP endpoint for cross-LLM validation, multi-agent coding workflows, and repeatable assistant-led setup across registered provider CLIs.
|
|
15
15
|
|
|
16
16
|
**Current signals:** CI and security workflows pass on `main`, OpenSSF Scorecard is published, OpenSSF Best Practices is passing, releases use Sigstore signing, and the package is MIT licensed.
|
|
17
17
|
|
|
@@ -38,7 +38,7 @@ Or use directly with `npx` from an MCP client:
|
|
|
38
38
|
|
|
39
39
|
`llm-cli-gateway` is a single-user MCP gateway for cross-LLM validation and multi-agent coding workflows. It is more than a thin CLI wrapper:
|
|
40
40
|
|
|
41
|
-
- Runs
|
|
41
|
+
- Runs registered provider CLIs through consistent sync and async MCP tools.
|
|
42
42
|
- Persists long-running jobs, supports restart-safe result collection, deduplication, cancellation, and sync-to-async deferral.
|
|
43
43
|
- Tracks sessions, real CLI resume paths, structured response metadata, and cache telemetry.
|
|
44
44
|
- Supports cache-aware `promptParts`, including explicit Claude `cache_control` when opted in.
|
|
@@ -157,7 +157,7 @@ docker compose -f docker/personal.compose.yml run --rm doctor
|
|
|
157
157
|
|
|
158
158
|
### Core Capabilities
|
|
159
159
|
|
|
160
|
-
- **Multi-LLM Orchestration**: Unified interface for Claude Code, Codex, Gemini, Grok, Mistral (Vibe), and
|
|
160
|
+
- **Multi-LLM Orchestration**: Unified interface for Claude Code, Codex, Gemini, Grok, Mistral (Vibe), Devin, and Cursor Agent CLIs
|
|
161
161
|
- **Session Management**: Track and resume conversations across all CLIs with persistent storage
|
|
162
162
|
- **Gateway-owned worktrees**: Run any sync or async provider request inside a managed git worktree, with per-session reuse and cleanup
|
|
163
163
|
- **Token Optimization**: Automatic 44% reduction on prompts, 37% on responses (opt-in)
|
|
@@ -169,11 +169,11 @@ docker compose -f docker/personal.compose.yml run --rm doctor
|
|
|
169
169
|
- **SQLite Flight Recorder**: Every request/response logged to `~/.llm-cli-gateway/logs.db` with correlation IDs, token usage, duration, retry counts, and circuit breaker state. Browse with [Datasette](https://datasette.io/): `datasette ~/.llm-cli-gateway/logs.db`
|
|
170
170
|
- **Structured Metadata**: Tool responses include machine-readable `structuredContent` (model, cli, correlationId, sessionId, durationMs, token counts)
|
|
171
171
|
- **Cache observability resources**: `cache-state://global`, `cache-state://session/{id}`, and `cache-state://prefix/{hash}` MCP resources return aggregate cache hit/miss/savings — tokens and hashes only, no prompt text. `session_get` includes a `cacheState` block when the session has prior requests.
|
|
172
|
-
- **Provider capability inventory**: `provider_tool_capabilities` and `provider-tools://catalog` expose the gateway request fields, supported/degraded provider controls, local skill/tool discovery, and safe config-surface hints for Claude Code, Codex CLI, Gemini/Antigravity, Grok CLI/API, Mistral Vibe, and
|
|
172
|
+
- **Provider capability inventory**: `provider_tool_capabilities` and `provider-tools://catalog` expose the gateway request fields, supported/degraded provider controls, local skill/tool discovery, and safe config-surface hints for Claude Code, Codex CLI, Gemini/Antigravity, Grok CLI/API, Mistral Vibe, Cognition Devin, and Cursor Agent. `doctor --json` includes a compact `provider_capabilities` summary for setup assistants.
|
|
173
173
|
|
|
174
174
|
### Cache-aware operation
|
|
175
175
|
|
|
176
|
-
Every `*_request` and `*_request_async` tool except `devin_request` / `devin_request_async` accepts an optional `promptParts` field that structures the prompt for better cache hit rates (the Devin headless
|
|
176
|
+
Every `*_request` and `*_request_async` tool except `devin_request` / `devin_request_async` and `cursor_request` / `cursor_request_async` accepts an optional `promptParts` field that structures the prompt for better cache hit rates (the Devin and Cursor headless paths take a plain `prompt` only). The gateway concatenates the parts in canonical order (`system → tools → context → task`) so that the stable prefix bytes precede the volatile task tail unchanged across calls, letting each provider's automatic prompt-caching land on the same content hash each time.
|
|
177
177
|
|
|
178
178
|
```json
|
|
179
179
|
{
|
|
@@ -188,7 +188,7 @@ Every `*_request` and `*_request_async` tool except `devin_request` / `devin_req
|
|
|
188
188
|
|
|
189
189
|
`prompt` and `promptParts` are mutually exclusive — pass exactly one.
|
|
190
190
|
|
|
191
|
-
Per-CLI capability matrix (prefix discipline is automatic via `promptParts` for all providers except Devin, which
|
|
191
|
+
Per-CLI capability matrix (prefix discipline is automatic via `promptParts` for all providers except Devin and Cursor, which have no `promptParts` surface; explicit levers are provider-specific):
|
|
192
192
|
|
|
193
193
|
| CLI | Prefix discipline | Explicit lever(s) |
|
|
194
194
|
| ------- | ----------------- | ----------------- |
|
|
@@ -197,6 +197,8 @@ Per-CLI capability matrix (prefix discipline is automatic via `promptParts` for
|
|
|
197
197
|
| gemini | yes | none (implicit server-side) |
|
|
198
198
|
| grok | yes | `compactionMode` / `compactionDetail` (context compaction: `summary|transcript|segments`; `segments` writes per-segment markdown) |
|
|
199
199
|
| mistral | yes | none (implicit) |
|
|
200
|
+
| devin | no | plain `prompt` only |
|
|
201
|
+
| cursor | no | plain `prompt` only |
|
|
200
202
|
|
|
201
203
|
**Claude example (explicit cacheControl)**
|
|
202
204
|
|
|
@@ -234,7 +236,7 @@ Opt-in flags (all default off) live under `[cache_awareness]` in `~/.llm-cli-gat
|
|
|
234
236
|
|
|
235
237
|
- **Retry Logic**: Exponential backoff with circuit breaker for transient failures
|
|
236
238
|
- **Atomic File Writes**: Process-specific temp files with fsync for data integrity
|
|
237
|
-
- **
|
|
239
|
+
- **Host-protection backpressure**: bounded HTTP session lifecycle (max sessions + idle reaper), global and per-provider job-execution limits with a bounded FIFO queue, and a configurable per-job output cap (default 50MB). See [Host-protection limits](#host-protection-limits-http-and-limits).
|
|
238
240
|
- **NVM Path Caching**: Eliminates I/O overhead on every request
|
|
239
241
|
- **Long-Running Jobs**: Non-time-bound async execution via `*_request_async` + polling tools
|
|
240
242
|
|
|
@@ -665,6 +667,61 @@ Legacy environment variables (deprecated; emit a warning at startup):
|
|
|
665
667
|
- `LLM_GATEWAY_DEDUP_WINDOW_MS` — overrides `dedupWindowMs`.
|
|
666
668
|
- `LLM_GATEWAY_ACKNOWLEDGE_EPHEMERAL` — `1`/`true`/`yes` sets `acknowledgeEphemeral = true`.
|
|
667
669
|
|
|
670
|
+
##### Host-protection limits (`[http]` and `[limits]`)
|
|
671
|
+
|
|
672
|
+
The gateway bounds HTTP session growth and async/sync job execution so a burst of
|
|
673
|
+
clients or requests cannot drive unbounded memory, process, CPU, or provider-request
|
|
674
|
+
growth. All keys live in the same `~/.llm-cli-gateway/config.toml`; defaults are
|
|
675
|
+
conservative but chosen not to surprise local stdio development.
|
|
676
|
+
|
|
677
|
+
```toml
|
|
678
|
+
[http] # HTTP MCP transport session lifecycle
|
|
679
|
+
max_sessions = 100 # max concurrent live sessions; excess initialize returns HTTP 429
|
|
680
|
+
session_idle_ttl_ms = 1800000 # 30 min: reap a session idle longer than this (no client DELETE needed)
|
|
681
|
+
session_reaper_interval_ms = 60000 # 1 min: how often the idle reaper sweeps
|
|
682
|
+
|
|
683
|
+
[limits] # async + sync job-execution backpressure (per gateway process)
|
|
684
|
+
max_running_jobs = 32 # global concurrent running jobs (process CLI + HTTP API)
|
|
685
|
+
max_running_jobs_per_provider = 16 # per-provider concurrent running jobs
|
|
686
|
+
max_queued_jobs = 128 # bounded wait queue; a full queue rejects new work
|
|
687
|
+
queue_timeout_ms = 120000 # 2 min: max time a job waits in the queue before failing
|
|
688
|
+
completed_job_memory_ttl_ms = 3600000 # 1 h: in-memory retention for finished jobs (durable rows kept separately)
|
|
689
|
+
max_job_output_bytes = 52428800 # 50 MB: per-job stdout+stderr cap
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
Failure modes (all deterministic and safe to retry):
|
|
693
|
+
|
|
694
|
+
- **HTTP session cap reached**: the initialize request returns `429` with `Retry-After: 5` and a structured `{ error, code: "session_capacity", retryable: true }` body. No new session is created.
|
|
695
|
+
- **Idle HTTP session**: the reaper closes it (transport + gateway server) once idle past `session_idle_ttl_ms`, independent of the client sending `DELETE`. A session with an in-flight request is never reaped mid-request.
|
|
696
|
+
- **Job limiter saturated**: when the running limit is reached and the queue is full, `*_request` / `*_request_async` and the direct-sync fallback return a retryable `saturated` error (`structuredContent.errorCategory = "saturated"`, `retryable: true`). Nothing is spawned. When the queue has room the job waits (FIFO, per-provider fair) up to `queue_timeout_ms`, then fails with the same category.
|
|
697
|
+
- **Sync direct execution**: the `SYNC_DEADLINE_MS=0` and storeless/`backend="none"` paths acquire the same process permit before spawning, so no execution bypasses the limiter.
|
|
698
|
+
- **Output overflow**: a job whose combined stdout+stderr exceeds `max_job_output_bytes` is failed (exit code 126), its process terminated, its completion persisted, and its run slot released.
|
|
699
|
+
- **In-memory vs durable retention**: `completed_job_memory_ttl_ms` only ages finished jobs out of the in-memory map; the durable job store keeps its own (longer) `[persistence].retentionDays` retention, so results stay readable via `llm_job_result` / `llm_request_result` after in-memory eviction.
|
|
700
|
+
|
|
701
|
+
Live counters are exposed on `GET /healthz` (unauthenticated, HTTP transport) and via the `llm_process_health` tool `backpressure` block: session current/max/oldest-age/idle-TTL/saturation, running and queued job counts globally and per provider, limiter saturation counters, configured TTL/output caps, and parent-process RSS/heap. These surfaces report **counts, ages, and bytes only**, never prompt text, response content, tokens, session IDs, bearer/OAuth tokens, API keys, or machine secrets.
|
|
702
|
+
|
|
703
|
+
For production user services, pair the in-process limits above with systemd's
|
|
704
|
+
outer guardrails so an unexpected bug, provider CLI leak, or evaluation burst
|
|
705
|
+
cannot consume the host:
|
|
706
|
+
|
|
707
|
+
```bash
|
|
708
|
+
systemctl --user edit llm-cli-gateway.service
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
```ini
|
|
712
|
+
[Service]
|
|
713
|
+
MemoryMax=2G
|
|
714
|
+
TasksMax=512
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
Choose values for your workload: `MemoryMax` should cover the gateway process,
|
|
718
|
+
the configured `max_running_jobs` provider children, and normal output buffering;
|
|
719
|
+
`TasksMax` should exceed the process/thread count implied by `max_running_jobs`
|
|
720
|
+
plus the HTTP server and SQLite work, but still be far below host exhaustion. If
|
|
721
|
+
systemd terminates the service at those limits, durable jobs can be inspected
|
|
722
|
+
after restart and `llm_process_health.backpressure` should be used to tune
|
|
723
|
+
`[http]`, `[limits]`, `MemoryMax`, and `TasksMax` together.
|
|
724
|
+
|
|
668
725
|
##### Per-project isolation
|
|
669
726
|
|
|
670
727
|
By default, **all gateway data is global per user**, not per project. With no overrides, every Claude Code window — across every repo — spawns its own gateway subprocess but they all read and write the same files:
|
|
@@ -905,9 +962,36 @@ Run a Cognition Devin CLI request synchronously (headless print mode, `devin -p`
|
|
|
905
962
|
- `idleTimeoutMs` (integer, optional): Kill a stuck process after output inactivity; 30,000 to 3,600,000 ms
|
|
906
963
|
- `forceRefresh` (boolean, optional): Bypass dedup and force a fresh CLI run, default: false
|
|
907
964
|
|
|
908
|
-
##### `
|
|
965
|
+
##### `cursor_request`
|
|
966
|
+
|
|
967
|
+
Run a Cursor Agent CLI request synchronously. Defaults to headless print mode (`cursor-agent --print`) and auto-defers to a pollable job past the sync deadline when async jobs are enabled. Set `transport: "acp"` to use Cursor's native `cursor-agent acp` transport when `[acp]` and `[acp.providers.cursor].runtime_enabled` are enabled; current ACP routing accepts only prompt/model/session inputs and rejects CLI-only options such as `mode`, `workspace`, `sandbox`, `force`, and `trust`.
|
|
968
|
+
|
|
969
|
+
**Parameters:**
|
|
970
|
+
|
|
971
|
+
- `prompt` (string, required): Prompt text for Cursor Agent CLI (1-100,000 chars)
|
|
972
|
+
- `model` (string, optional): Model name or alias (for example `gpt-5`, `sonnet-4-thinking`, or `latest`)
|
|
973
|
+
- `mode` (string, optional): Cursor mode, `"plan"` or `"ask"` (`--mode`)
|
|
974
|
+
- `outputFormat` (string, optional): `"text"` (default), `"json"`, or `"stream-json"`
|
|
975
|
+
- `transport` (string, optional): `"cli"` (default) or `"acp"`; ACP fails closed unless enabled in gateway config and rejects unsupported Cursor CLI-only controls instead of dropping them
|
|
976
|
+
- `force` (boolean, optional): Emit `--force` for non-interactive operation
|
|
977
|
+
- `autoReview` (boolean, optional): Emit `--auto-review`
|
|
978
|
+
- `sandbox` (string, optional): `"enabled"` or `"disabled"` (`--sandbox`)
|
|
979
|
+
- `trust` (boolean, optional): Emit `--trust` for this invocation
|
|
980
|
+
- `workspace` (string, optional): Cursor workspace path or name (`--workspace`); remote HTTP/OAuth callers must pass a registered workspace alias, while local stdio callers may pass paths
|
|
981
|
+
- `addDir` (string[], optional): Additional workspace roots (one `--add-dir` per entry); remote HTTP/OAuth callers must use registered workspace roots
|
|
982
|
+
- `sessionId` (string, optional): Cursor chat/session ID to resume (`--resume <id>`). The `gw-*` id minted for a brand-new gateway session is not resumable through `sessionId`; continue with `resumeLatest: true`
|
|
983
|
+
- `resumeLatest` (boolean, optional): Resume the most recent Cursor chat (`--continue`)
|
|
984
|
+
- `createNewSession` (boolean, optional): Force a new session
|
|
985
|
+
- `approvalStrategy` (string, optional): `"legacy"` (default) or `"mcp_managed"`; under MCP-managed approval, high-impact Cursor flags (`force`, `trust`, or `sandbox: "disabled"`) are denied unless bypass approval is explicitly allowed
|
|
986
|
+
- `approvalPolicy` (string, optional): `"strict"`, `"balanced"`, or `"permissive"` override for MCP-managed approval
|
|
987
|
+
- `optimizePrompt` / `optimizeResponse` (boolean, optional): Token-efficiency optimisation, default: false
|
|
988
|
+
- `correlationId` (string, optional): Request trace ID (auto-generated if omitted)
|
|
989
|
+
- `idleTimeoutMs` (integer, optional): Kill a stuck process after output inactivity; 30,000 to 3,600,000 ms
|
|
990
|
+
- `forceRefresh` (boolean, optional): Bypass dedup and force a fresh CLI run, default: false
|
|
909
991
|
|
|
910
|
-
|
|
992
|
+
##### `claude_request_async` / `codex_request_async` / `gemini_request_async` / `grok_request_async` / `mistral_request_async` / `devin_request_async` / `cursor_request_async`
|
|
993
|
+
|
|
994
|
+
Start a long-running Claude, Codex, Gemini, Grok, Mistral, Devin, or Cursor request without waiting for completion in the same MCP call.
|
|
911
995
|
|
|
912
996
|
Use this flow when analysis/runtime can exceed client tool-call limits:
|
|
913
997
|
|
|
@@ -925,7 +1009,7 @@ Async request tools accept the same approval strategy fields as their sync varia
|
|
|
925
1009
|
|
|
926
1010
|
##### `llm_job_status`
|
|
927
1011
|
|
|
928
|
-
Return lifecycle status (`running`, `completed`, `failed`, `canceled`) and metadata for an async job.
|
|
1012
|
+
Return lifecycle status (`queued`, `running`, `completed`, `failed`, `canceled`, `orphaned`) and metadata for an async job.
|
|
929
1013
|
|
|
930
1014
|
##### `llm_job_result`
|
|
931
1015
|
|
|
@@ -966,7 +1050,7 @@ Return the gateway's declared provider CLI contracts, optionally probing the ins
|
|
|
966
1050
|
|
|
967
1051
|
**Parameters:**
|
|
968
1052
|
|
|
969
|
-
- `cli` (string, optional): Filter (`claude|codex|gemini|grok|mistral|devin`)
|
|
1053
|
+
- `cli` (string, optional): Filter (`claude|codex|gemini|grok|mistral|devin|cursor`)
|
|
970
1054
|
- `probeInstalled` (boolean, optional, default `false`): Run local `--help` probes and compare advertised flags against the declared contract — strongly recommended after any provider CLI upgrade. The probe reports `missingFlags`, `extraFlags`, `acknowledgedExtraFlags` (known upstream-only flags filtered from `extraFlags`), `discoveredFlags`, and stale-marker `warnings`.
|
|
971
1055
|
|
|
972
1056
|
#### Session Management Tools
|
|
@@ -977,7 +1061,7 @@ Create a new session for a specific CLI.
|
|
|
977
1061
|
|
|
978
1062
|
**Parameters:**
|
|
979
1063
|
|
|
980
|
-
- `cli` (string, required): CLI to create session for ("claude", "codex", "gemini", "grok", "mistral", "devin")
|
|
1064
|
+
- `cli` (string, required): CLI to create session for ("claude", "codex", "gemini", "grok", "mistral", "devin", "cursor")
|
|
981
1065
|
- `description` (string, optional): Description for the session
|
|
982
1066
|
- `setAsActive` (boolean, optional): Set as active session, default: true
|
|
983
1067
|
|
|
@@ -997,7 +1081,7 @@ List all sessions, optionally filtered by CLI.
|
|
|
997
1081
|
|
|
998
1082
|
**Parameters:**
|
|
999
1083
|
|
|
1000
|
-
- `cli` (string, optional): Filter by CLI ("claude", "codex", "gemini", "grok", "mistral", "devin")
|
|
1084
|
+
- `cli` (string, optional): Filter by CLI ("claude", "codex", "gemini", "grok", "mistral", "devin", "cursor")
|
|
1001
1085
|
|
|
1002
1086
|
**Response includes:**
|
|
1003
1087
|
|
|
@@ -1046,7 +1130,7 @@ List available models for each CLI.
|
|
|
1046
1130
|
|
|
1047
1131
|
**Parameters:**
|
|
1048
1132
|
|
|
1049
|
-
- `cli` (string, optional): Specific
|
|
1133
|
+
- `cli` (string, optional): Specific provider to list models for (`"claude"`, `"codex"`, `"gemini"`, `"grok"`, `"mistral"`, `"devin"`, `"cursor"`, or an enabled API provider name). When one or more `[providers.<name>]` API providers are enabled, the unfiltered response also carries an `apiProviders` array (each entry tagged `providerKind: "api"`); see [API providers (HTTP)](#api-providers-http).
|
|
1050
1134
|
|
|
1051
1135
|
**Response includes:**
|
|
1052
1136
|
|
|
@@ -1094,7 +1178,7 @@ inputs.
|
|
|
1094
1178
|
|
|
1095
1179
|
**Parameters:**
|
|
1096
1180
|
|
|
1097
|
-
- `cli` (string, optional): Provider filter (`"claude"`, `"codex"`, `"gemini"`, `"grok"`, `"mistral"`, `"devin"`,
|
|
1181
|
+
- `cli` (string, optional): Provider filter (`"claude"`, `"codex"`, `"gemini"`, `"grok"`, `"mistral"`, `"devin"`, `"cursor"`, `"grok_api"`, or an enabled API provider name)
|
|
1098
1182
|
- `includeSkills` (boolean, default `true`): Include bounded local skill discovery
|
|
1099
1183
|
- `includeProviderTools` (boolean, default `true`): Include provider-native tools extracted from discovered skills
|
|
1100
1184
|
- `includeUnsupported` (boolean, default `true`): Include explicit unsupported/degraded input records
|
|
@@ -1115,12 +1199,16 @@ Equivalent MCP resources:
|
|
|
1115
1199
|
- `provider-tools://grok_api`
|
|
1116
1200
|
- `provider-tools://mistral`
|
|
1117
1201
|
- `provider-tools://devin`
|
|
1202
|
+
- `provider-tools://cursor`
|
|
1203
|
+
- `provider-tools://<api-provider>` for each enabled `[providers.<name>]` API provider
|
|
1118
1204
|
|
|
1119
1205
|
`doctor --json` also emits a compact `provider_capabilities` block with the
|
|
1120
1206
|
same schema version, per-provider request tool names, supported feature names,
|
|
1121
1207
|
unsupported input names, config-surface counts, discovery counts, and resource
|
|
1122
1208
|
URIs. This block is intended for setup assistants that need a concise capability
|
|
1123
|
-
summary without local skill bodies or raw paths.
|
|
1209
|
+
summary without local skill bodies or raw paths. When API providers are enabled,
|
|
1210
|
+
`doctor --json` additionally emits an `api_providers` health block; see
|
|
1211
|
+
[API providers (HTTP)](#api-providers-http).
|
|
1124
1212
|
|
|
1125
1213
|
##### `cli_versions`
|
|
1126
1214
|
|
|
@@ -1128,7 +1216,7 @@ Report installed CLI versions.
|
|
|
1128
1216
|
|
|
1129
1217
|
**Parameters:**
|
|
1130
1218
|
|
|
1131
|
-
- `cli` (string, optional): Specific CLI to inspect ("claude", "codex", "gemini", "grok", "mistral", "devin")
|
|
1219
|
+
- `cli` (string, optional): Specific CLI to inspect ("claude", "codex", "gemini", "grok", "mistral", "devin", "cursor")
|
|
1132
1220
|
|
|
1133
1221
|
##### `cli_upgrade`
|
|
1134
1222
|
|
|
@@ -1136,7 +1224,7 @@ Plan or run an upgrade for one CLI.
|
|
|
1136
1224
|
|
|
1137
1225
|
**Parameters:**
|
|
1138
1226
|
|
|
1139
|
-
- `cli` (string, required): CLI to upgrade ("claude", "codex", "gemini", "grok", "mistral", "devin")
|
|
1227
|
+
- `cli` (string, required): CLI to upgrade ("claude", "codex", "gemini", "grok", "mistral", "devin", "cursor")
|
|
1140
1228
|
- `target` (string, optional): Package tag/version/target, default: `latest`
|
|
1141
1229
|
- `dryRun` (boolean, optional): Return the upgrade plan without running it, default: `true`
|
|
1142
1230
|
- `timeoutMs` (number, optional): Upgrade timeout when `dryRun=false`
|
|
@@ -1152,6 +1240,7 @@ Plan or run an upgrade for one CLI.
|
|
|
1152
1240
|
- Grok explicit target: `grok update --version <target>`
|
|
1153
1241
|
- Mistral (Vibe): dispatches to the detected installer (`pip`/`uv`/`brew`); errors with guidance when none is detected (Vibe ships no self-update command)
|
|
1154
1242
|
- Devin latest: `devin update` (self-update; explicit version targets are unsupported)
|
|
1243
|
+
- Cursor latest: `cursor-agent update` (self-update; explicit version targets are unsupported)
|
|
1155
1244
|
|
|
1156
1245
|
**Example dry run:**
|
|
1157
1246
|
|
|
@@ -1163,6 +1252,73 @@ Plan or run an upgrade for one CLI.
|
|
|
1163
1252
|
}
|
|
1164
1253
|
```
|
|
1165
1254
|
|
|
1255
|
+
## API providers (HTTP)
|
|
1256
|
+
|
|
1257
|
+
In addition to the spawnable CLI tools, the gateway can route requests to first-class **HTTP/API providers** (OpenRouter and other OpenAI-compatible endpoints, the Anthropic Messages API, and the xAI Responses API). These use Node's built-in HTTP client (`node:https`, or `node:http` for loopback endpoints) rather than spawning a CLI, and they run through the same request/job/validation/flight-recorder machinery as the CLI tools, so they reach parity for sessions, async jobs, dedup, retries, usage/cost capture, and cross-LLM validation.
|
|
1258
|
+
|
|
1259
|
+
### Configuring a provider
|
|
1260
|
+
|
|
1261
|
+
API providers are declared as `[providers.<name>]` blocks in `~/.llm-cli-gateway/config.toml` (override with `LLM_GATEWAY_CONFIG`). The `<name>` becomes the provider's identity across every tool and resource and **must not** collide with a spawnable CLI name (`claude`, `codex`, `gemini`, `grok`, `mistral`, `devin`, `cursor`); a collision is rejected with a warning and the provider is disabled.
|
|
1262
|
+
|
|
1263
|
+
```toml
|
|
1264
|
+
# OpenRouter (OpenAI-compatible). The key is read from the named env var at
|
|
1265
|
+
# request time and is never written to config, logs, the flight recorder, or
|
|
1266
|
+
# the dedup key.
|
|
1267
|
+
[providers.openrouter]
|
|
1268
|
+
kind = "openai-compatible" # "openai-compatible" | "anthropic" | "xai-responses"
|
|
1269
|
+
base_url = "https://openrouter.ai/api/v1"
|
|
1270
|
+
api_key_env = "OPENROUTER_API_KEY" # env var NAME, not the key itself
|
|
1271
|
+
default_model = "x-ai/grok-2"
|
|
1272
|
+
models = ["x-ai/grok-2", "anthropic/claude-sonnet-4.6"] # optional allowlist: an explicit model must be listed (omit model to use default_model)
|
|
1273
|
+
usage_include = true # OpenRouter token/cost reporting (usage:{include:true})
|
|
1274
|
+
|
|
1275
|
+
# Keyless-local: an openai-compatible provider on a loopback base_url (Ollama,
|
|
1276
|
+
# llama.cpp) is enabled with no api_key_env at all.
|
|
1277
|
+
[providers.ollama]
|
|
1278
|
+
kind = "openai-compatible"
|
|
1279
|
+
base_url = "http://127.0.0.1:11434/v1"
|
|
1280
|
+
default_model = "qwen2.5"
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
A provider is **enabled** when its `api_key_env` resolves to a non-empty value, OR it is a keyless-local `openai-compatible` provider on a loopback `base_url`. `base_url` must use `https` unless it targets localhost/loopback. A **schema-invalid** single `[providers.<name>]` block disables only itself (with a warning) and leaves the other providers untouched; a TOML **syntax** error anywhere in the file is different, it makes the whole config fall back to defaults.
|
|
1284
|
+
|
|
1285
|
+
The pre-existing `[providers.xai]` block keeps its dedicated `grok_api_request` tool and xAI identity. It is also exposed through the generic surface like any other enabled provider, so a configured xAI key registers **both** `grok_api_request` and the generic `api_xai_request` (and the `xai` entry appears in `apiProviders`, `models://xai`, etc.).
|
|
1286
|
+
|
|
1287
|
+
### Request tools
|
|
1288
|
+
|
|
1289
|
+
For each enabled provider, the gateway registers `api_<name>_request` (and `api_<name>_request_async` when async jobs are enabled). They accept the same shape as the CLI request tools:
|
|
1290
|
+
|
|
1291
|
+
- `prompt` (or the cache-aware `promptParts` `{ system?, tools?, context?, task }`, mutually exclusive), optional `system`
|
|
1292
|
+
- `model` (omit to use `default_model`; an explicit value is rejected if outside a configured `models` allowlist), `maxOutputTokens`, `temperature`, `topP`
|
|
1293
|
+
- `reasoningEffort` (`none|low|medium|high`): forwarded only by the `xai-responses` adapter; accepted but ignored by the other kinds
|
|
1294
|
+
- `timeoutMs`, `optimizePrompt`, `optimizeResponse`, `forceRefresh`, `correlationId`
|
|
1295
|
+
- `sessionId` / `createNewSession` for continuity on the synchronous `api_<name>_request` (see below); on the async variant they are currently inert
|
|
1296
|
+
|
|
1297
|
+
Responses (and the 50 MB output cap, dedup window, cancellation, retention) behave exactly as for the CLI tools, and HTTP requests are logged to the flight recorder with status/usage/cost like everything else.
|
|
1298
|
+
|
|
1299
|
+
### Continuity
|
|
1300
|
+
|
|
1301
|
+
Continuity is capability-typed per provider kind and never stores conversation content in the session record (the gateway's no-transcript-in-sessions invariant holds):
|
|
1302
|
+
|
|
1303
|
+
- **`xai-responses`** uses real server-side continuation: the gateway persists the provider's `previous_response_id` in session metadata and threads it back on resume, self-healing on a stale-handle 404.
|
|
1304
|
+
- **`openai-compatible`** and **`anthropic`** are stateless-resend: the session tracks active/owner state for principal isolation, but the caller resends prior context (no server-side conversation handle exists).
|
|
1305
|
+
|
|
1306
|
+
### Discovery surfaces
|
|
1307
|
+
|
|
1308
|
+
Enabled API providers appear, alongside the CLI providers, across the discovery surfaces. The model, capability, doctor, and resource surfaces below **omit** their API field entirely when no API providers are enabled, so their output is byte-identical to before; `llm_process_health` is the exception (it always carries an `apiProviders` array that is simply empty when none are enabled):
|
|
1309
|
+
|
|
1310
|
+
- **`list_models`** / **`list_available_models`**: an `apiProviders` array tagged `providerKind: "api"` with `defaultModel` and the optional allowlist, omitted entirely when no API providers are enabled.
|
|
1311
|
+
- **`llm_process_health`**: an always-present `outboundProviders.apiProviders` array (empty when none are enabled) carrying the same projection plus each provider's circuit-breaker state.
|
|
1312
|
+
- **`provider_tool_capabilities`**: per-`api`-kind capability metadata. Model + sampling + (for `xai-responses`) reasoning + continuity are supported; allow/deny tool lists, MCP servers, local skills, and workspace/worktree controls are not (those are CLI-only).
|
|
1313
|
+
- **`doctor --json`**: an `api_providers` health block (kind, `base_url`, `default_model`, models, the key env var name, whether the key is present, and login guidance), omitted entirely when none are enabled. The optional flag `doctor --json --probe-api-providers` adds a per-provider endpoint-reachability result (a bare `GET`, treating any HTTP response as reachable). The probe is **opt-in and off by default**: a normal `doctor` run opens no socket and spends no tokens (`reachable` stays `null`).
|
|
1314
|
+
- **MCP resources**: `models://<provider>` for every enabled provider and `sessions://<provider>` for continuity-tracked kinds, plus `provider-tools://<provider>`. The `provider-subcommands://` resources stay CLI-only (API providers have no subcommands).
|
|
1315
|
+
|
|
1316
|
+
The API key value is never emitted on any of these surfaces (only the env var name and a presence boolean). Because `base_url` is config-supplied and may legally carry URL userinfo, the diagnostic surfaces (`doctor`, login guidance) redact any embedded credentials before displaying it; the actual request path and the reachability probe still use the original configured URL.
|
|
1317
|
+
|
|
1318
|
+
### Security note
|
|
1319
|
+
|
|
1320
|
+
The resolved API key is excluded from `payloadJson`, the dedup key, logs, and the flight recorder. However, the **request prompt is persisted in plaintext** in the async job store (the SQLite file at `[persistence].path`, default `~/.llm-cli-gateway/logs.db`) and is not covered by secret redaction. This mirrors the CLI tools, whose prompt is persisted in `argsJson` whenever it is passed as a command argument (a few CLI paths instead stream the prompt over stdin and so do not persist it). Treat the job store as sensitive at rest. See [Security Considerations](#security-considerations).
|
|
1321
|
+
|
|
1166
1322
|
## Session Management
|
|
1167
1323
|
|
|
1168
1324
|
### How It Works
|
|
@@ -1418,6 +1574,8 @@ The gateway supports concurrent requests across different CLIs. Each request spa
|
|
|
1418
1574
|
## Security Considerations
|
|
1419
1575
|
|
|
1420
1576
|
- **Input Validation**: All prompts are validated (min 1 char, max 100k chars)
|
|
1577
|
+
- **API-provider keys**: For `[providers.<name>]` HTTP providers, the gateway reads the key from the named environment variable at request time only. The resolved key is excluded from the persisted `payloadJson`, the dedup key, logs, and the flight recorder, and is never surfaced on the discovery/diagnostic surfaces (which report only the env var name and a presence boolean). `base_url` userinfo is redacted on the diagnostic surfaces. See [API providers (HTTP)](#api-providers-http).
|
|
1578
|
+
- **Prompt persistence at rest**: Async job rows store the request **prompt in plaintext** (HTTP `payloadJson`, and CLI `argsJson` whenever the prompt is passed as a command argument rather than streamed over stdin); this is not covered by secret redaction. The SQLite job-store file (default `~/.llm-cli-gateway/logs.db`, configurable via `[persistence].path`) is `chmod`ed to `0o600` on non-Windows hosts; treat it as sensitive and scope/rotate it like any prompt log. Set `[persistence].backend = "none"` to disable the async job store entirely (the `*_request_async` / `llm_job_*` tools are then not registered).
|
|
1421
1579
|
- **Command Execution**: Uses `spawn` with separate arguments (not shell execution)
|
|
1422
1580
|
- **No Eval**: No dynamic code evaluation in our source (see "Socket alerts" below for the transitive `ajv` codegen case)
|
|
1423
1581
|
- **Sandboxing**: Consider running in containers for production use
|
|
@@ -77,6 +77,19 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
77
77
|
adapterCandidates: Object.freeze([]),
|
|
78
78
|
caveat: "Native ACP entrypoint `devin acp` (stdio JSON-RPC). Manual initialize + session/new smoke passed with the installed CLI managing credentials (`devin auth login`; WINDSURF_API_KEY for empty-env); empty-env smoke is expected to fail. Third native runtime pilot; runtime routing stays config-gated.",
|
|
79
79
|
}),
|
|
80
|
+
cursor: Object.freeze({
|
|
81
|
+
provider: "cursor",
|
|
82
|
+
displayName: "Cursor Agent CLI",
|
|
83
|
+
status: "native_smoke_passed",
|
|
84
|
+
supportKind: "native",
|
|
85
|
+
targetVersion: "cursor-agent 2026.06.29-2ad2186",
|
|
86
|
+
entrypoint: Object.freeze({ command: "cursor-agent", args: Object.freeze(["acp"]) }),
|
|
87
|
+
runtimeEnabledDefault: false,
|
|
88
|
+
shipRuntimePilot: true,
|
|
89
|
+
runtimePriority: 4,
|
|
90
|
+
adapterCandidates: Object.freeze([]),
|
|
91
|
+
caveat: "Native ACP entrypoint `cursor-agent acp` (stdio JSON-RPC) is available as a hidden advanced command. Manual initialize + session/new smoke passed locally (protocolVersion 1, session created; no agentInfo returned). Fourth native runtime pilot; runtime routing stays config-gated.",
|
|
92
|
+
}),
|
|
80
93
|
});
|
|
81
94
|
export function getAcpProviderRegistry() {
|
|
82
95
|
return ACP_PROVIDER_REGISTRY;
|
package/dist/api-provider.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Logger } from "./logger.js";
|
|
|
3
3
|
import { ApiHttpError } from "./api-http.js";
|
|
4
4
|
export type ApiProviderKind = "openai-compatible" | "anthropic" | "xai-responses";
|
|
5
5
|
export type ApiContinuity = "server-side-id" | "stateless-resend" | "none";
|
|
6
|
+
export declare function apiContinuityForKind(kind: ApiProviderKind): ApiContinuity;
|
|
6
7
|
export interface ApiChatMessage {
|
|
7
8
|
role: "system" | "user" | "assistant";
|
|
8
9
|
content: string;
|
package/dist/api-provider.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { createCircuitBreaker, withRetry } from "./retry.js";
|
|
2
2
|
import { logWarn, noopLogger } from "./logger.js";
|
|
3
3
|
import { ApiHttpError, buildEndpointUrl, isHttpTransient, postJson, DEFAULT_API_TIMEOUT_MS, } from "./api-http.js";
|
|
4
|
+
export function apiContinuityForKind(kind) {
|
|
5
|
+
switch (kind) {
|
|
6
|
+
case "xai-responses":
|
|
7
|
+
return "server-side-id";
|
|
8
|
+
case "openai-compatible":
|
|
9
|
+
case "anthropic":
|
|
10
|
+
return "stateless-resend";
|
|
11
|
+
default: {
|
|
12
|
+
const exhaustive = kind;
|
|
13
|
+
throw new Error(`Unknown api provider kind: ${String(exhaustive)}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
4
17
|
function numberOrUndefined(value) {
|
|
5
18
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
6
19
|
}
|
|
@@ -2,7 +2,7 @@ import type { Logger } from "./logger.js";
|
|
|
2
2
|
import type { ReviewIntegrityResult } from "./review-integrity.js";
|
|
3
3
|
export type ApprovalPolicy = "strict" | "balanced" | "permissive";
|
|
4
4
|
export type ApprovalStrategy = "legacy" | "mcp_managed";
|
|
5
|
-
export type ApprovalCli = "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin";
|
|
5
|
+
export type ApprovalCli = "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin" | "cursor";
|
|
6
6
|
export type ApprovalStatus = "approved" | "denied";
|
|
7
7
|
export interface ApprovalRequest {
|
|
8
8
|
cli: ApprovalCli;
|
|
@@ -4,11 +4,31 @@ import { JobStore } from "./job-store.js";
|
|
|
4
4
|
import { type FlightRecorderLike } from "./flight-recorder.js";
|
|
5
5
|
import type { ValidationRunStore } from "./job-store.js";
|
|
6
6
|
import { type ApiProvider, type ApiRequest, type ApiUsage } from "./api-provider.js";
|
|
7
|
+
import { type JobLimitsConfig } from "./config.js";
|
|
7
8
|
export declare function extractApiHttpStatus(error: unknown): number | null;
|
|
8
9
|
export declare function extractApiErrorBody(error: unknown): string | undefined;
|
|
9
|
-
export type LlmCli = "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin";
|
|
10
|
+
export type LlmCli = "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin" | "cursor";
|
|
10
11
|
export type JobProvider = LlmCli | (string & {});
|
|
11
|
-
export type AsyncJobStatus = "running" | "completed" | "failed" | "canceled" | "orphaned";
|
|
12
|
+
export type AsyncJobStatus = "queued" | "running" | "completed" | "failed" | "canceled" | "orphaned";
|
|
13
|
+
export declare function isAsyncJobInProgress(status: AsyncJobStatus): boolean;
|
|
14
|
+
export declare class JobSaturationError extends Error {
|
|
15
|
+
readonly provider: string;
|
|
16
|
+
readonly detail: string;
|
|
17
|
+
readonly retryable = true;
|
|
18
|
+
constructor(provider: string, detail: string);
|
|
19
|
+
}
|
|
20
|
+
export interface JobLimiterSnapshot {
|
|
21
|
+
maxRunning: number;
|
|
22
|
+
maxRunningPerProvider: number;
|
|
23
|
+
maxQueued: number;
|
|
24
|
+
running: number;
|
|
25
|
+
queued: number;
|
|
26
|
+
runningByProvider: Record<string, number>;
|
|
27
|
+
queuedByProvider: Record<string, number>;
|
|
28
|
+
rejected: number;
|
|
29
|
+
timedOut: number;
|
|
30
|
+
saturated: boolean;
|
|
31
|
+
}
|
|
12
32
|
export interface AsyncJobFlightRecorderEntry {
|
|
13
33
|
model: string;
|
|
14
34
|
prompt: string;
|
|
@@ -76,7 +96,11 @@ export declare class AsyncJobManager {
|
|
|
76
96
|
private processMonitor;
|
|
77
97
|
private store;
|
|
78
98
|
private flightRecorder;
|
|
79
|
-
|
|
99
|
+
private readonly limits;
|
|
100
|
+
private readonly limiter;
|
|
101
|
+
private readonly completedJobMemoryTtlMs;
|
|
102
|
+
private readonly maxJobOutputBytes;
|
|
103
|
+
constructor(logger?: Logger, onJobComplete?: ((cli: JobProvider, durationMs: number, success: boolean) => void) | undefined, store?: JobStore | null, flightRecorder?: FlightRecorderLike, limits?: JobLimitsConfig);
|
|
80
104
|
private buildOrphanFlightResult;
|
|
81
105
|
checkStalledJobs(now?: number): void;
|
|
82
106
|
hasStore(): boolean;
|
|
@@ -98,6 +122,7 @@ export declare class AsyncJobManager {
|
|
|
98
122
|
}): StartJobOutcome;
|
|
99
123
|
private finalizeHttpJob;
|
|
100
124
|
private fireOnComplete;
|
|
125
|
+
private releaseJobPermit;
|
|
101
126
|
private writeFlightComplete;
|
|
102
127
|
private safeExtractUsage;
|
|
103
128
|
private httpUsage;
|
|
@@ -109,6 +134,8 @@ export declare class AsyncJobManager {
|
|
|
109
134
|
getJobOwner(jobId: string): string | null | undefined;
|
|
110
135
|
startJob(cli: LlmCli, args: string[], correlationId: string, cwd?: string, idleTimeoutMs?: number, outputFormat?: string, forceRefresh?: boolean, env?: Record<string, string>, onComplete?: () => void, flightRecorderEntry?: AsyncJobFlightRecorderEntry, extractUsage?: AsyncJobUsageExtractor, writeFlightStart?: boolean, stdin?: string): AsyncJobSnapshot;
|
|
111
136
|
startJobWithDedup(cli: LlmCli, args: string[], correlationId: string, opts?: StartJobOptions): StartJobOutcome;
|
|
137
|
+
private launchProcessJob;
|
|
138
|
+
private failQueuedJob;
|
|
112
139
|
getJobSnapshot(jobId: string): AsyncJobSnapshot | null;
|
|
113
140
|
getJobSnapshots(jobIds: string[]): Record<string, AsyncJobSnapshot | null>;
|
|
114
141
|
getJobResult(jobId: string, maxChars?: number): AsyncJobResult | null;
|
|
@@ -129,6 +156,11 @@ export declare class AsyncJobManager {
|
|
|
129
156
|
zombieJobs: number;
|
|
130
157
|
jobs: JobHealth[];
|
|
131
158
|
};
|
|
159
|
+
acquireProcessSlot(provider: string): Promise<{
|
|
160
|
+
release: () => void;
|
|
161
|
+
}>;
|
|
162
|
+
getLimiterSnapshot(): JobLimiterSnapshot;
|
|
163
|
+
getConfiguredLimits(): JobLimitsConfig;
|
|
132
164
|
getJobOutputFormat(jobId: string): string | undefined;
|
|
133
165
|
getJobCli(jobId: string): JobProvider | undefined;
|
|
134
166
|
private snapshot;
|