Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: levelcodeai/levelcode
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.9.2
Choose a base ref
...
head repository: levelcodeai/levelcode
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.0.0
Choose a head ref
  • 9 commits
  • 9 files changed
  • 2 contributors

Commits on Jul 24, 2026

  1. ci: move release actions off the deprecated Node 20 runtime

    Every release run logs a Node 20 deprecation warning per step, and closes with:
    "The following actions target Node.js 20 but are being forced to run on Node.js
    24: actions/cache@v4, actions/checkout@v4, actions/setup-node@v4,
    actions/upload-artifact@v4."
    
    Nothing is broken — GitHub is shimming them onto Node 24 — but the shim is
    temporary, so pin majors that target node24 natively:
    
      checkout          v4 -> v7
      setup-node        v4 -> v7
      cache             v4 -> v6
      upload-artifact   v4 -> v7
      download-artifact v4 -> v8
    
    Node 24 actions require Actions Runner >= 2.327.1; the hosted images run 2.335.1
    (per the v0.9.2 build log) and self-update, so nothing to do there.
    
    The majors were picked against how this workflow actually uses them, and each
    one's breaking change was checked rather than assumed:
      · setup-node v6 limits automatic caching to npm — this workflow never sets
        setup-node's `cache:` input (npm caching is the explicit actions/cache step),
        so it's a no-op here.
      · checkout v7 blocks fork checkouts for pull_request_target / workflow_run —
        neither is a trigger (tag push + workflow_dispatch).
      · upload-artifact v7's unzipped "direct uploads" are opt-in via `archive:`;
        the default still zips, so the .app.zip round-trip is unchanged.
      · download-artifact v8 now ERRORS on a digest mismatch instead of warning.
        Kept deliberately: a corrupted app bundle must not reach a release we then
        sign and notarize. A spurious failure is recoverable by re-running the job.
    
    Verified every input this workflow passes is still declared in the new majors
    (notably download-artifact's `merge-multiple`, which the draft-release job
    depends on), that all five now report `using: node24`, and that the YAML still
    parses with both matrix arches and triggers intact.
    ndemianc committed Jul 24, 2026
    Configuration menu
    Copy the full SHA
    0866bee View commit details
    Browse the repository at this point in the history

Commits on Jul 25, 2026

  1. Merge pull request #40 from levelcodeai/fix/actions-node24

    ci: move release actions off the deprecated Node 20 runtime
    ndemianc authored Jul 25, 2026
    Configuration menu
    Copy the full SHA
    7ba72f1 View commit details
    Browse the repository at this point in the history
  2. feat(ai): offer Claude Opus 5 on OpenRouter (BYOK) + 1M-context caps

    Gateway (LevelCode Cloud) users get Opus 5 from the server roster with no editor
    release — that side is thin.ly. This is the BYOK half: the OpenRouter picker, the
    local caps table, and the sketch cost meter.
    
    - providers/index.js — a picker row for `anthropic/claude-opus-5`. Placed second,
      under the flagship, since it is the frontier pick most people reach for.
    
    - providers/catalog.js — an explicit CAPS row, for the same reason Kimi K3 has one:
      the `claude-` heuristic reports 200K, so a 1M window would drive the in-run
      context meter to cry "full" at a fifth of the real budget.
    
    - sketch/pricing.js — Opus 5 ties Opus 4.8's $5/$25, which the `claude-opus` family
      fallback already produced. The FAST variant does NOT: it is $10/$50, and the
      fallback was quietly pricing it at half. Both are pinned now, so the meter is
      honest whichever slug a BYOK user types.
    
    - sketch.js — MODEL_TIERS openrouter.powerful moves 4.8 → 5. Same price, newer
      model. It also fixes a latent wart: the old id was never in the OpenRouter
      registry, so a per-node model override naming it failed validation and silently
      fell back. Revert this one line if you'd rather pin the tier to 4.8.
    
    Prices/context confirmed against the OpenRouter models API (2026-07-24):
    $5/M in · $25/M out · $0.50/M cached · 1M ctx (fast variant: $10/$50).
    
    Verified at runtime, not just in tests: the row renders in the picker, caps
    resolve to 1,000,000, tools/vision on, and isAnthropicFamily matches so explicit
    prompt-cache breakpoints are sent. Full gate green (24 suites).
    ndemianc committed Jul 25, 2026
    Configuration menu
    Copy the full SHA
    ea9407e View commit details
    Browse the repository at this point in the history
  3. fix(ai): Opus 4.8 is 1M on OpenRouter — split the caps row by route

    The CAPS row for Opus 4.8 said 200K, so the in-run context meter warned "full"
    at a fifth of the real budget for anyone on the OpenRouter/gateway route. Every
    OpenRouter endpoint for that model (Anthropic first-party, Bedrock, Azure,
    Google) advertises 1M/128K.
    
    Fixed under the OpenRouter slug ONLY, and deliberately not under the bare id:
    
      'claude-opus-4-8'           → 200000   direct Anthropic provider
      'anthropic/claude-opus-4-8' → 1000000  OpenRouter / gateway
    
    Those are two different routes sharing one key today, and I can only vouch for
    one of them. This extension sends no `anthropic-beta` header on the native path,
    so I have not confirmed the long window is reachable there — and overstating it
    would be the worse failure, telling the user they have room the API will refuse.
    Exact-id lookup beats basename, so the rows coexist and each route reports its
    own truth. If the native path does support 1M unheadered, the bare row is a
    one-line follow-up.
    
    Tests pin both sides of the split plus the unlisted-Claude heuristic default.
    Full gate green (24 suites).
    ndemianc committed Jul 25, 2026
    Configuration menu
    Copy the full SHA
    a4777a8 View commit details
    Browse the repository at this point in the history
  4. Merge pull request #42 from levelcodeai/feat/opus-5-openrouter

    feat(ai): Claude Opus 5 on OpenRouter (BYOK) + correct the Opus 1M context windows
    ndemianc authored Jul 25, 2026
    Configuration menu
    Copy the full SHA
    8ac0aef View commit details
    Browse the repository at this point in the history
  5. fix(ai): refresh an expired token when loading the Cloud model roster

    On a fresh open of LevelCode, a paid plan's model picker showed only TWO models
    (the free engine + the flagship) instead of the full roster — no Opus 4.8, no
    Kimi K3, and none of the "×credits · ctx · turns left" detail or "coming soon"
    rows. That is the OFFLINE fallback branch of pickCloudModel, reached whenever
    fetchCloudRoster returns no models.
    
    Cause: fetchCloudRoster returned null on ANY non-OK response and, unlike the
    profile fetch (webHandoffUrl) and the agent loop, did NOT refresh the token on a
    401. Access tokens are short-lived, so the FIRST roster request after a fresh
    open is made with last session's expired token → 401 → null → 2-model fallback.
    The account WEB page looked correct because it authenticates differently and
    refreshes.
    
    Fix, mirroring the proven webHandoffUrl retry:
    - On 401, refreshCloudToken() once and retry with the fresh token.
    - Gate on the stored TOKEN, not the cloudSignedIn flag — the flag can still be
      false mid-activation while a valid token exists, which was a second path to the
      same degraded menu.
    - On a genuinely transient failure, return the last-known-good roster
      (cloudRoster) instead of collapsing to the 2-model fallback; the menu just omits
      the credits/turns detail it can't recompute offline.
    
    Not unit-tested — this is vscode-requiring host glue with no pure seam to extract;
    it is verified by reading and by matching webHandoffUrl's battle-tested pattern
    (now the second of two 401-refresh call sites). I could not reproduce the live
    401 without a running, signed-in editor, so this fixes the identified code path
    rather than an observed repro. Full gate: 24 suites, 0 failures.
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    ndemianc and claude committed Jul 25, 2026
    Configuration menu
    Copy the full SHA
    f4278bb View commit details
    Browse the repository at this point in the history
  6. fix(ai): correct the cache comment; guard the refreshed token (PR #43

    …review)
    
    1. The comment claimed "credits/turns aren't cached", but cloudRoster = data.models
       caches the per-model fields including turns_left, so the cached-roster fallback
       CAN show a slightly stale "≈ turns left". Only the account-level credit BALANCE
       is genuinely not carried (the "$X credits left" header is omitted until the next
       good fetch). Comment now says exactly that.
    
    2. After refreshCloudToken() succeeds, the refreshed secret was used unchecked —
       if it came back falsy the retry would send `Authorization: Bearer null`, noise
       that masks the real 401. Now the retry only fires when the refreshed token is
       truthy; otherwise it falls through to the cached-roster path.
    
    Comment-and-guard only, no behaviour change on the happy path. Full gate: 24
    suites, 0 failures.
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    ndemianc and claude committed Jul 25, 2026
    Configuration menu
    Copy the full SHA
    ef99a8f View commit details
    Browse the repository at this point in the history
  7. fix(ai): fall back to the cached roster unless data.models is a real …

    …array (PR #43 review)
    
    The retry returned `data || cached()`, so any truthy JSON that lacked a valid
    `models` array — a 200 with an error object, a partial response, a schema drift —
    was returned as-is. pickCloudModel then sees "no models" and collapses to the
    2-model fallback even though cloudRoster still holds a good list, defeating the
    whole point of the cache.
    
    Now a payload only counts as a roster when data.models is an array; otherwise it
    returns cached(). Also fixes the mirror case where a 200 arrives but json() failed
    (data null) — same fall-through to the cache.
    
    Full gate: 24 suites, 0 failures.
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    ndemianc and claude committed Jul 25, 2026
    Configuration menu
    Copy the full SHA
    367dc64 View commit details
    Browse the repository at this point in the history
  8. Merge pull request #43 from levelcodeai/fix/cloud-roster-token-refresh

    fix(ai): refresh an expired token when loading the Cloud model roster
    ndemianc authored Jul 25, 2026
    Configuration menu
    Copy the full SHA
    faa8eb5 View commit details
    Browse the repository at this point in the history
Loading