Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs-site/src/content/docs/reference/configuration/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ compatibility fallback. With it, the order is: discovered/configured limits (the
Azure metadata, model-ID metadata, enabled Context cap as the window, then 128k. An enabled
Context cap still limits the result. Built-in providers keep their existing behavior and ignore the flag.

`GET /api/providers` includes `inferContextWindow` only when configured, preserving both `true` and
`false`. To change it, `PATCH /api/providers?name=<provider>` accepts `true`, `false`, or `null` to
clear the setting and restore the default-off behavior; other types return `400`. Omitting it keeps
the current setting. A same-name `POST /api/providers` also keeps an omitted setting, including
`false` and any change committed while the save was validating. A submitted boolean wins, including
`false`, even if the stored value is `true`.

```json
{ "providers": { "my-relay": { "adapter": "anthropic", "baseUrl": "https://relay.example/v1", "inferContextWindow": true } } }
```
Expand Down Expand Up @@ -413,6 +420,10 @@ The destination is the adapter, the base URL (scheme and host compared without r

`hideRawReasoning` is an operator display policy, so a save of the same provider name keeps its stored value whenever the request omits it, including after a destination move. A value sent in the request wins, including `false`.

The optional `inferContextWindow` setting also survives same-name saves that omit it, including
destination moves. Its read, update, and clear behavior is described under
[Context windows on custom providers](#context-windows-on-custom-providers).

`PATCH /api/providers?name=<provider>` changes only the fields it names and keeps every other stored field, whatever the destination. It accepts all eight settings; `null` clears one. It also accepts a boolean `hideRawReasoning` or `null` to clear it. For the two reasoning lists an empty array is stored as an explicit opt-out rather than removed.

With `webSearchBridge` enabled, a search continuation stays bound to the API-key selection that
Expand Down
6 changes: 6 additions & 0 deletions docs-site/src/content/docs/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,12 @@ setting interacts with `claudeCode.enabled` across upgrades and downgrades.
| `GET, PUT /api/provider-context-caps` | Read or update global, all-provider, or one-provider context caps | 400 invalid request; 404 unknown provider |
| `GET /api/provider-presets` | Return GUI provider presets derived from the runtime registry | — |

Provider reads include the optional boolean `inferContextWindow` when configured. Provider PATCH
accepts only a boolean or `null` to clear this setting; omission preserves it. A same-name POST
also preserves an omitted setting, including the latest stored `false`; an explicit submitted
boolean wins. See [Context windows on custom providers](/reference/configuration/providers/#context-windows-on-custom-providers)
for its default-off, custom-provider-only behavior.

The provider context-cap response includes `caps` (active limits) and `values` (last selected
values, retained while disabled). Enabling a provider without `value` restores its selection,
or uses the global `contextCapValue` on first enable. This also applies to OpenAI: the switch
Expand Down
2 changes: 1 addition & 1 deletion scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -1155,7 +1155,7 @@
"management-provider-reset-replay.test.ts": "server",
"management-provider-synthetic-max.test.ts": "server",
"management-provider-upstream-websocket.test.ts": "server",
"management-provider-validation.test.ts": "server",
"management-provider-context-inference.test.ts": "server", "management-provider-validation.test.ts": "server",
"management-provider-verbosity.test.ts": "server",
"management-route-registry.test.ts": "server",
"protocol-routes.test.ts": "server",
Expand Down
7 changes: 7 additions & 0 deletions src/server/management/provider-overwrite-carry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@ export function applyProviderCompatPatchFields(
else return { error: "preserveResponsesReasoningContent must be a boolean" };
touched = true;
}
if (Object.hasOwn(rawBody, "inferContextWindow")) {
const value = rawBody.inferContextWindow;
if (value === null) delete next.inferContextWindow;
else if (typeof value === "boolean") next.inferContextWindow = value;
else return { error: "inferContextWindow must be a boolean or null" };
touched = true;
}
if (Object.hasOwn(rawBody, "reasoningWireFormat")) {
const value = rawBody.reasoningWireFormat;
if (value === null) {
Expand Down
7 changes: 7 additions & 0 deletions src/server/management/provider-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
requestPacing: p.requestPacing,
models: p.models ?? [],
contextWindow: p.contextWindow,
inferContextWindow: p.inferContextWindow,
modelContextWindows: p.modelContextWindows,
modelContextTiers: p.modelContextTiers,
modelCapabilities: p.modelCapabilities,
Expand Down Expand Up @@ -1365,6 +1366,12 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
if (submittedModelContextTiers && liveTiers) candidate.modelContextTiers = Object.assign(Object.create(null), liveTiers, candidate.modelContextTiers ?? {});
else if (!submittedModelContextTiers && liveTiers) candidate.modelContextTiers = { ...liveTiers };
else if (!submittedModelContextTiers) delete candidate.modelContextTiers;
// Full forms omit the opt-in; preserve the latest value, including a concurrent clear.
if (!Object.hasOwn(rawProvider, "inferContextWindow")) {
const liveInference = config.providers[name]?.inferContextWindow;
if (liveInference === undefined) delete candidate.inferContextWindow;
else candidate.inferContextWindow = liveInference;
}
const draft = { ...(registrationDraft ?? config), providers: { ...config.providers, [name]: candidate }, ...(body.setDefault === true ? { defaultProvider: name } : {}) };
const validation = validateConfigCandidate(draft); if (!validation.ok) { validationError = validation.error; return; }
try {
Expand Down
3 changes: 3 additions & 0 deletions structure/catalog-vendor-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ then Azure metadata, then this estimate, then an enabled context cap as the wind
retain their existing policies; OpenAI API metadata used for a custom relay is not Codex subscription
capacity. The lookup changes no configuration, credentials, pricing, or request model identity.

Management reads, strict boolean/null PATCH updates, and same-name overwrite preservation of the
optional flag follow the [provider API contract](gui-and-management-api.md#api-ownership).

Original-vendor tables precede aggregator tables. Exact model spellings win within a tier;
case-folding and dot-to-hyphen aliases are accepted only without conflicting windows. Aggregator
rows may supply a bare last-segment alias; floating `~` rows are excluded. Conflicting keys do not
Expand Down
2 changes: 1 addition & 1 deletion structure/gui-and-management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ per-request first-party callback reads that live object; a failed write leaves i
| Startup safety | `GET /api/startup-health` reports whether injected Codex routing is restart-safe, with secret-free service/shim diagnostics. `POST /api/startup-action` provides allowlisted one-click installation for the background service or launcher shim. On Windows a healthy script shim is CLI-only; Codex Desktop requires the background service for full protection. Optional Codex compatibility uses the [confirmed local runtime](clients/codex-desktop.md#optional-compatibility-runtime), [machine-local dashboard consent](clients/codex-desktop.md#dashboard-controls), and [revision-bound next-start preference with shared awaited teardown](clients/codex-desktop.md#proxy-startup-preference); saving that preference performs no runtime action. |
| Windows tray | `GET/POST /api/windows-tray` controls an owned, per-user HKCU login tray. The tray delegates fixed actions to the CLI and is never a proxy supervisor or restart-protection signal. |
| Updates | `GET /api/update/check`, `POST /api/update/run`, and `GET /api/update/status` own dashboard self-update state. A launched worker PID is persisted in `update-job.json`; dead PIDs recover immediately, while legacy active records without a PID recover only after ten minutes. Live PIDs remain exclusive regardless of record age. `GET /api/update/check` and `POST /api/update/run` await one per-channel asynchronous registry lookup and write successful results through to the package cache; run passes that result to the job starter. `GET /api/update/badge` only reads the cache and reports unknown after 40 hours, on missing cache, or on channel mismatch. The badge links to the update surface rather than gating other actions. `GET /api/update/badge?surface=desktop&session=<id>` projects only that process-local Tauri session; missing or expired state is unknown and never falls back to the package cache. `POST /api/update/desktop-snapshot` is a 1 KiB bounded display-state mutation with no install permission. It accepts the existing admin-token principal or a dedicated single-use, ten-second snapshot capability bound to nonce, method, path, PID, port and the SHA-256 digest of the exact body bytes; that capability cannot authorize another route. |
| Providers | Create/update/delete ordinary provider configs and enrich registry metadata. A `POST /api/providers` overwrite of an existing name keeps the nine operator compatibility settings (`PROVIDER_COMPAT_CARRY_FIELDS` in `src/server/management/provider-overwrite-carry.ts`) and the stored key pool only while the destination (adapter, normalized base URL, auth mode when named) is unchanged. It keeps omitted `hideRawReasoning` on every overwrite of the same name, including destination moves, because it is a display policy; a submitted boolean wins. It never merges the rest of the old row. `PATCH` is a field mask and keeps every field it does not name; `hideRawReasoning` and `preserveResponsesReasoningContent` accept a boolean or `null` to clear them. Custom Responses provider forms expose the latter as a default-off plaintext replay switch; unrelated settings saves preserve omission and curated/OpenAI destinations keep their existing defaults. The reserved `openai` card exposes Pool(default)/Direct account mode; `openai-apikey` remains the separate API route. |
| Providers | Create/update/delete ordinary provider configs and enrich registry metadata. A `POST /api/providers` overwrite of an existing name keeps the nine operator compatibility settings (`PROVIDER_COMPAT_CARRY_FIELDS` in `src/server/management/provider-overwrite-carry.ts`) and the stored key pool only while the destination (adapter, normalized base URL, auth mode when named) is unchanged. It keeps omitted `hideRawReasoning` on every overwrite of the same name, including destination moves, because it is a display policy; a submitted boolean wins. An omitted `inferContextWindow` likewise keeps the latest live same-name state after asynchronous validation, including `false` or a concurrent clear; a submitted boolean wins. It never merges the rest of the old row. `PATCH` is a field mask and keeps every field it does not name; `hideRawReasoning`, `preserveResponsesReasoningContent` and `inferContextWindow` accept a boolean or `null` to clear them, rejecting other types. `GET /api/providers` exposes configured `inferContextWindow` as an optional boolean without substituting a default; its [custom-only, default-off inference policy](catalog-vendor-context.md) is unchanged. Custom Responses provider forms expose `preserveResponsesReasoningContent` as a default-off plaintext replay switch; unrelated settings saves preserve omission and curated/OpenAI destinations keep their existing defaults. The reserved `openai` card exposes Pool(default)/Direct account mode; `openai-apikey` remains the separate API route. |
| Models | Fetch routed model lists, disabled model visibility, and catalog-facing ids. New non-OAuth registration holds exposure until authoritative discovery; 20 or more distinct switch rows start OFF without disabling the provider. Pending rows cannot accept visibility changes. Ordinary model reads also apply the [new-arrival catalog contract](catalog.md#shared-catalog) before returning models. With a matching persisted configuration, discovery records the baseline, NEW badges, and automatic disables together; HTTP listing does not wait for a separate Codex sync. Inventory already drifted from disk uses a detached policy projection: arrivals remain disabled in management rows without changing live config or disk. |
| OAuth | Login/status/logout for OAuth-backed providers, plus multiauth account management: `GET /api/oauth/accounts`, `PUT /api/oauth/accounts/active`, `PUT /api/oauth/accounts/alias`, `PUT /api/oauth/accounts/pause`, and `DELETE /api/oauth/accounts` list masked accounts per provider, switch the active one, edit its display-only alias, pause/resume a generic OAuth account, and remove one. Paused generic OAuth accounts are excluded from request selection, 429 failover, and proactive token refresh; pausing an active account selects the next usable account when available, and resuming an account restores an active selection if the current one remains paused. With no unpaused account, requests return 403 rather than a login error. Kiro account-list rows include the current automatic-selection projection and closed exclusion reason; an active singleton may still send. The login flow itself is `GET /api/oauth/providers`, `POST /api/oauth/login`, `POST /api/oauth/login/code`, `POST /api/oauth/login/cancel`, `POST /api/oauth/logout`, and `GET /api/oauth/status`; pool controls are `GET/PUT/PATCH /api/oauth/accounts/pool` and `POST /api/oauth/accounts/clear-cooldown`. Login accepts `addAccount: true` to force a fresh browser identity. Meta Muse login start and manual-code continuation require the server-resolved `gui-session` principal before credential acquisition or code submission (including reauth); see the [provider contract](providers-and-adapters.md). Device flows return a structured `deviceCode`; the GUI highlights and copies it before the user opens the verification page. |
| Key providers | `GET /api/key-providers` exposes API-key provider presets for setup and dashboard flows, and `GET/POST/DELETE /api/keys` owns the proxy's own admission keys. Machine links live in `src/server/management/link-routes.ts`: dashboard sessions reach `GET /api/link/candidates`, `POST /api/link/probe`, `POST /api/link/confirm-host` and `POST /api/link/apply`, meaning a paired session or, on a standalone runtime, the current loopback-issued session on trusted loopback ingress; `POST /api/link/join` instead requires an operator-paired session because it durably redirects local client traffic, and also refuses a runtime that is not standalone (`409 standalone_required`) or not listening on its configured port (`409 join_port_mismatch`) before any SSH, then restarts this runtime as a client; `GET /api/link/status` reports `joinAvailable` and typed `joinDenied` causes only to GUI-session callers, distinguishing pairing, runtime-role and port refusals without changing the admin/CLI DTO, so the dashboard enables the Child role only when the join gates pass; `GET /api/link/status`, `POST /api/link/{id}/reconnect` and `DELETE /api/link/{id}` also accept the admin token on a trusted loopback ingress, and reconnect restarts a Home-initiated link's tunnel now; `POST /api/link/issue` accepts only that admin token. Tailscale-identity sessions are refused on every link route. See [Remote Link](remote-link.md). Multi-key pool per key-auth provider: `GET /api/providers/keys`, `POST /api/providers/keys`, `PUT /api/providers/keys/active`, `PUT /api/providers/keys/alias`, `DELETE /api/providers/keys` masked list, add (upsert + activate), switch, rename, and remove keys. `provider.apiKey` always mirrors the active pool entry so routing stays single-key. |
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/test-layout-expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@
"management-provider-pinsless-validation.test.ts": "server", "management-provider-proto-override.test.ts": "server",
"management-provider-reasoning-lists.test.ts": "server", "management-provider-reset-replay.test.ts": "server",
"management-provider-synthetic-max.test.ts": "server", "management-provider-upstream-websocket.test.ts": "server",
"management-provider-validation.test.ts": "server", "management-provider-verbosity.test.ts": "server",
"management-provider-context-inference.test.ts": "server", "management-provider-validation.test.ts": "server", "management-provider-verbosity.test.ts": "server",
"management-route-registry.test.ts": "server", "protocol-routes.test.ts": "server",
"protocol-settings-route.test.ts": "server", "protocol-provider-summary.test.ts": "server",
"management-workflow-budget-routes.test.ts": "server", "managing-cli.test.ts": "service",
Expand Down
Loading
Loading