Skip to content
Open
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ target/
*.node
*.node.build.json
*.b64.js
# `bun build --compile` leaves these next to the entrypoint when it is interrupted.
*.bun-build

# Environment
.env
Expand Down
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ bun run dev:doctor # verify PATH resolution of `gjc` points at this wor
bun run install:defaults # (re)install bundled default definitions
```

Removing build output (never touches sources, `node_modules/`, `.gjc/` state, or `artifacts/` evidence):

```sh
bun run clean # dist/, binaries/, coverage/, stray *.bun-build, *.tsbuildinfo
bun run clean:native # also drop compiled .node addons (rebuild via build:native)
bun scripts/clean.ts --dry-run # list targets without deleting
```

`clean` removes `packages/coding-agent/dist/`, so a `--binary`-linked `gjc` (see `dev:doctor`) stops resolving until you run `bun run --cwd=packages/coding-agent build` again. Source-linked setups are unaffected.

Verification (never run `tsc`/`npx tsc` directly at repo root; use these):

```sh
Expand Down
1 change: 1 addition & 0 deletions docs/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ These are consumed via `getEnvApiKey()` (`packages/ai/src/stream.ts`) unless not
| `OPENROUTER_API_KEY` | OpenRouter auth | Using OpenRouter models | Also used by image tool when preferred/auto provider is OpenRouter |
| `MISTRAL_API_KEY` | Mistral auth | Using Mistral models | |
| `ZAI_API_KEY` | z.ai auth | Using z.ai models | Also used by z.ai web search provider |
| `JUNIE_API_KEY` | JetBrains AI (Junie) auth | Using `jetbrains-junie` models | Access token from [junie.jetbrains.com/cli](https://junie.jetbrains.com/cli); sent as `Authorization: Bearer` |
| `MINIMAX_API_KEY` | MiniMax auth | Using `minimax` provider | |
| `AZURE_OPENAI_API_KEY` | Azure OpenAI auth | Using `azure-openai` / `azure-openai-responses` models | Pair with `AZURE_OPENAI_BASE_URL` or `AZURE_OPENAI_RESOURCE_NAME` |
| `MINIMAX_CODE_API_KEY` | MiniMax Code auth | Using `minimax-code` provider | |
Expand Down
2 changes: 2 additions & 0 deletions docs/external-control-readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ See [Environment Variables](./environment-variables.md#11-acp-permission-handlin

GJC's ACP session configuration carries the spec-defined `category` on the Mode, Model, and Thinking select options (`mode`, `model`, `thought_level`), which lets ACP clients such as Paseo discover models and thinking levels without provider-specific metadata. The model catalog is filtered to providers with usable stored credentials (`providers.list/active`), falling back to the full catalog on session hosts that do not expose that query.

Model profiles also appear in the ordinary **Model** picker as synthetic entries under the reserved namespace, e.g. `gajae-code/codex-eco` (displayed with the profile label, such as "Codex Eco"). Selecting one through the ACP `Model` select immediately switches the live session to the full profile without persisting `modelProfile.default`; persistence remains an explicit `/model` TUI choice or `gjc --mpreset codex-eco --default`. Only profiles whose providers have usable stored credentials are selectable; synthetic rows are already availability-filtered by the session host, so the Q29 active-provider filter never drops them. An unavailable-but-active profile stays visible as the current readback and, if selected, fails with the existing authentication-required error. The separate ACP startup `--mpreset`/Q27 `Preset` select is likewise session-scoped and non-persistent.

Sessions launched through an ACP client (e.g. `paseo run --provider gjc/...`) are broker-managed and appear in ACP `session/list`, so Paseo's import flow can attach them. Interactive `gjc` sessions host their own SDK endpoint and are not broker-registered, so they are not listed by ACP clients; use the GJC SDK/notifications surface to control those sessions.

## ACP conformance and Air release gates
Expand Down
35 changes: 35 additions & 0 deletions docs/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,12 @@ gjc --mpreset opencodego --default
```

The `/model` command opens to a preset landing view: presets are grouped by provider with live auth marks (✓/✗), highlighting a group expands its tiers, and selecting a tier shows the full role→model preview before applying for the session or as default. Typing jumps straight to model search, and `Browse all models` opens the classic tabbed model selector. In `/login`, `Add custom provider` is the first option for configuring credentials needed by custom or profile-required providers; after a successful provider login, the matching preset is recommended automatically.
External SDK/ACP clients (e.g. the Paseo TUI) can select profiles like ordinary
models: the SDK `models.list/current` (Q10) catalog exposes every usable profile
as a synthetic `gajae-code/<profile>` entry (e.g. `gajae-code/codex-eco`), and
selecting one through `model.set` (or the ACP Model picker) activates the
profile for the live session only. Persisting a profile remains an explicit TUI
choice, mirroring `gjc --mpreset <name> --default`. See [SDK model profiles](./sdk.md#model-profiles-as-synthetic-models-gajae-codeprofile).

MiniMax's OpenAI-compatible endpoint rejects multiple system messages and emits thinking in `reasoning_content`, so pin the public-safe compatibility fields when hand-authoring a custom provider:

Expand Down Expand Up @@ -283,6 +289,35 @@ providers:
models:
- id: glm-4.6
```

### JetBrains AI (Junie)

`jetbrains-junie` is a first-class provider serving JetBrains-hosted models through the documented
Ingrazzio gateway (`https://ingrazzio-cloud-prod.labs.jb.gg`).

Authenticate with an access token generated at [junie.jetbrains.com/cli](https://junie.jetbrains.com/cli):

```sh
export JUNIE_API_KEY=...
```

The token is sent as `Authorization: Bearer` — JetBrains AI rejects requests that also carry `x-api-key`, so
this provider never lets the Anthropic SDK attach one. Usage is billed against your JetBrains AI
subscription, so bundled per-token costs are zero. There is no OAuth login flow; the environment variable is
the only supported credential source.

The gateway multiplexes transports by model family:

| Family | Models | Transport | Prompt limit |
| --- | --- | --- | --- |
| Claude | `claude-sonnet-4-6` (default), `claude-sonnet-5`, `claude-opus-4-6`, `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, `claude-fable-5` | `anthropic-messages` | 1M |
| GPT | `gpt-5-2025-08-07`, `gpt-5.2-2025-12-11`, `gpt-5.4`, `gpt-5.5`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra` | `openai-completions` | 922K |
| GPT (Responses-only) | `gpt-5.3-codex` | `openai-responses` | 272K |

All models cap output at 128K. Junie also exposes Gemini and Grok, but those ride a proprietary Grazie
translation protocol that GJC does not implement, so they are deliberately not bundled. The bare
`opus`/`sonnet`/`gpt`/`grok` aliases are Junie CLI shorthands the gateway itself rejects.

### Allowed auth/discovery values

- `auth`: `apiKey` (default), `none`, or `oauth`; for `models.yml` custom models, `oauth` is accepted by schema but does not waive the `apiKey` requirement
Expand Down
7 changes: 7 additions & 0 deletions docs/sdk-app-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ Beyond frames, the WS surface exposes typed **control operations**
`usage.get`, `models.list/current`, `workflow.gates.list`, …). See the
[SDK wire protocol & machine interfaces](./sdk.md) for the complete catalog.

The `models.list/current` (Q10) catalog also lists model profiles as synthetic
`gajae-code/<profile>` entries (e.g. `gajae-code/codex-eco`). Treat them as
logical selections, not API providers: sending the id back through `model.set`
activates the profile for the live session only. Persisting remains an explicit
TUI choice. Request Q27 (`models.profiles.list`) when you need the
full profile catalog including unavailable profiles and their `available`
status. See [Model profiles as synthetic models](./sdk.md#model-profiles-as-synthetic-models-gajae-codeprofile).

## Creating and supervising sessions

Expand Down
59 changes: 59 additions & 0 deletions docs/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,65 @@ as a `model.set` input.
Malformed reasoning descriptors are not client-recoverable catalog data. The
query returns the SDK's safe `internal` error rather than exposing a partially
formed row or descriptor details.
### Model profiles as synthetic models (`gajae-code/<profile>`)

The Q10 catalog also exposes model profiles as logical synthetic models under
the reserved provider namespace `gajae-code`, e.g. `gajae-code/codex-eco`.
These rows let clients (such as ACP model pickers) offer presets like ordinary
models without provider-specific metadata:

```json
{
"provider": "gajae-code",
"id": "codex-eco",
"name": "Codex Eco",
"contextWindow": 222222,
"maxTokens": 8888,
"reasoning": false,
"thinking": { "validLevels": ["off"] },
"current": false
}
```

- `gajae-code/<profile>` is a **logical namespace, not a callable provider**. No
API transport, credentials, or streaming route is registered for it; send the
value back through the generic `model.set` control (or the ACP `Model`
select) to activate the profile.
- Synthetic rows are **availability-filtered**: only profiles whose required and
alternative providers have usable stored credentials are listed. The profile
id suffix is parsed losslessly after the first namespace slash, so profile ids
containing additional slashes or punctuation round-trip exactly.
- `contextWindow`/`maxTokens` mirror the profile's resolvable default model when
available and otherwise fall back to the shared unknown-model constants
(222222 / 8888); the profile's real default model remains authoritative.
- Synthetic rows are non-reasoning with `validLevels: ["off"]`: a `model.set`
on a synthetic id with any thinking level other than `off` is rejected with
`invalid_input`, and only an absent or `off` level is forwarded as a session
override.
- **Current-state semantics:** while a profile is active for the session, exactly
the synthetic row carries `current: true` with `currentThinkingLevel:
"inherit"`, and the underlying concrete row is not marked current. A persisted
`modelProfile.default` alone (without an in-session active marker) never
creates a synthetic current row. Selecting a concrete `provider/model` clears
the active marker and restores concrete current semantics.
- **Selecting a synthetic profile is session-scoped.** `model.set` with
`gajae-code/<profile>` activates the full profile in the live session without
writing `modelProfile.default`, `modelRoles`, or
`task.agentModelOverrides`. Persisting a profile remains an explicit TUI
choice (`/model` → default), mirroring `gjc --mpreset <profile> --default`.
Unknown or ambiguous synthetic ids fail with `invalid_input`; missing profile
credentials fail with the existing authentication-required error.
- `gajae-code` is **reserved**: a user-defined `models.yml` provider of the same
name disables the synthetic facade (rows are omitted and synthetic selection
is rejected) rather than being silently shadowed. Q27 (`models.profiles.list`)
remains the full profile catalog with explicit `available` status; Q10 is the
availability-aware facade for client selection.
`config.patch` mutations are serialized through the same session admission
boundary as profile activation and default-model selection, so a patch racing
a synthetic `gajae-code/*` selection (or another patch) is applied in a
deterministic order and is never lost or clobbered by an activation rollback.
The cost is the same as `model.set`: an external `config.patch` queues behind
any in-flight prompt admission rather than applying mid-turn.

## Prompt acceptance, termination, and reconciliation (Q26)

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,11 @@
"stats": "bun --cwd=packages/coding-agent src/cli.ts stats",
"build": "bun run --workspaces --if-present build",
"build:native": "bun --cwd=packages/natives run build",
"clean": "bun scripts/clean.ts",
"clean:native": "bun scripts/clean.ts --native",
"test": "bun run --parallel test:ts test:rs",
"test:ts": "bun run test:release && bun run --workspaces --if-present test",
"test:release": "bun test scripts/nightly-release.test.ts scripts/release-evidence.test.ts scripts/release-policy.test.ts scripts/release-publish-order.test.ts scripts/restart-sdk-broker.test.ts",
"test:release": "bun test scripts/clean.test.ts scripts/nightly-release.test.ts scripts/release-evidence.test.ts scripts/release-policy.test.ts scripts/release-publish-order.test.ts scripts/restart-sdk-broker.test.ts",
"generate-schemas": "bun scripts/generate-json-schemas.ts",
"check:schemas": "bun scripts/generate-json-schemas.ts --check",
"check:public-sync": "bun scripts/check-public-version-sync.ts",
Expand Down
1 change: 1 addition & 0 deletions packages/ai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
### Added

- Added opt-in `compat.supportsResponsesSessionAffinity` for OpenAI Responses custom relays. When enabled, supported `openai-responses` models may send `session_id` and `x-client-request-id` affinity headers to a custom endpoint; canonical OpenAI routing remains automatic and known non-OpenAI provider IDs remain excluded.
- Added the `jetbrains-junie` provider, serving JetBrains-hosted models over the documented Ingrazzio gateway `https://ingrazzio-cloud-prod.labs.jb.gg` (#3626). Auth is the officially documented `JUNIE_API_KEY` access token only — no OAuth login flow and no reverse-engineered client credentials. JetBrains AI rejects requests carrying `x-api-key`, so the provider passes `apiKey: null` to the Anthropic SDK and relies solely on the `Authorization: Bearer` header that `buildAnthropicHeaders` already emits for non-Anthropic hosts. The gateway multiplexes transports by family via the `X-LLM-Model` routing header: 7 Claude models on `anthropic-messages` (1M prompt window), 7 GPT models on `openai-completions` and `gpt-5.3-codex` on `openai-responses` (922K and 272K respectively); all cap output at 128K. The GPT lane pins a `/v1`-suffixed base URL because the OpenAI transports append a bare route while the Anthropic one supplies its own prefix. Ids come from Junie CLI's own catalog cross-checked against the 2470.4 jar; Gemini and Grok are excluded because their Grazie translation protocol is not implemented, and the bare `opus`/`sonnet` aliases are CLI shorthands the gateway rejects. Limits are the gateway's probed ceilings, not Junie CLI's smaller per-request budgets.

### Fixed

Expand Down
Loading
Loading