Skip to content

feat(protocols): separate Messages API exposure from the Claude toggle (PF-04) - #5812

Closed
lidge-jun wants to merge 14 commits into
feat/pf05-inference-primitivesfrom
feat/pf04-api-surfaces
Closed

lidge-jun wants to merge 14 commits into
feat/pf05-inference-primitivesfrom
feat/pf04-api-surfaces

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

PF-04 of the protocol-first-class unit (devlog/_plan/260924_protocol_first_class/030_gui_and_management_api.md#pf-04-api-surface-settings). Stacked on #5811 (PF-05).

Messages API exposure becomes its own setting instead of riding on the Claude integration toggle, without ever reopening a surface an operator closed.

  • resolveApiSurfaceSettings is the only reader of Messages exposure: /v1/messages and /v1/messages/count_tokens share one gate, so they cannot disagree. Explicit apiSurfaces.messages.enabled wins; a malformed value closes the surface; absence inherits claudeCode.enabled (existing installs unchanged).
  • API access metadata adds surfaces: { responses, chat, messages } with enabled and source. claudeCodeEnabled is kept for older dashboards and now mirrors the resolved Messages surface, so an old dashboard never shows a closed endpoint as open.
  • PATCH /api/protocols/settings (messagesEnabled, unrepresentable, rollout), strictly validated, saved through the locked config path. Disabling Messages writes apiSurfaces.messages.enabled = false and claudeCode.enabled = false in one save, so an older binary after rollback stays closed; enabling writes only apiSurfaces.messages.enabled = true. A failed save restores the live config (409 on lock contention, 500 otherwise) without echoing error text.
  • The Claude settings route and native toggle wrote the claudeCode block inline in two places; that moves into one writer (src/claude/claude-code-block.ts) used by both and by the PATCH, keeping hand-edit protection unchanged.
  • Dashboard: Integrations → API / Keys shows Responses, Chat Completions and Messages as cards with state, endpoint and source; Messages has a toggle and a link to the Claude page and stays visible when disabled.
  • Docs: apiSurfaces in the server configuration reference, the PATCH route in the management API reference, structure owner docs.

Behavior to note for review: with apiSurfaces.messages.enabled: true set explicitly, turning Claude off on the Claude page no longer closes /v1/messages on this binary — the explicit surface setting wins, and the card shows the source. A hand-written apiSurfaces.messages.enabled: false with claudeCode.enabled: true is not rollback-safe; only dashboard-written states are.

Verification

  • bun x tsc --noEmit: exit 0. New root test files additionally typechecked with a temporary tsconfig: exit 0.

  • gui: bun x tsc -b, bun run lint, bun run lint:i18n: exit 0.

  • bun run structure:check: passed.

  • Tests (tests/server/protocol-settings-route.test.ts, tests/claude-integration/messages-surface-matrix.test.ts recording the upgrade/rollback matrix, gui/tests/api-surface-cards.test.tsx, extended tests/server/api-access-endpoints.test.ts) were written and registered but run in the full local suite below.

  • Full local run on the stack head (feat(protocols): protocol paths as a first-class concern — PF-01..PF-12 #5820, which contains this change): bun run test — the only failures are Lab CL-03/CL-07/CL-08/SEC-02 and release helper timeouts, which fail identically on a checkout without this stack (local environment), plus service/toggle cases that pass when run alone; cd gui && bun test --isolate tests — 2398 pass, 0 fail.

  • CI on this head: all required checks pass.

Screenshots

Captured from the stack head in an isolated home (fake providers, no real credentials).

API page, Responses / Chat Completions / Messages cards

API page, Responses / Chat Completions / Messages cards

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 25, 2026 03:44
@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 0e0c0186-5d8a-4e0c-b9ea-c55bc6b05d9a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-25T03:49:07.879175Z d0077bc PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added the enhancement New feature or request label Sep 25, 2026
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

✅ READY

  • all PR quality gates passed.

Hygiene

✅ Deterministic PR hygiene checks passed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0077bc541

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +97 to +98
const persist = ctx.deps.saveConfigPreservingClaudeCode
?? (await import("../../config")).saveConfigPreservingClaudeCode;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve the persistence function before mutating live config

When two PATCH /api/protocols/settings requests finish parsing concurrently, this await yields after the first request has already mutated the shared config but before its locked save. The second request can then mutate the same object, causing the first save to serialize the second request's state and return 200 even though its own change—potentially closing Messages—was lost; if that save fails, restoring the first snapshot can also erase the second request's successful mutation. Resolve/import the persistence function before taking the snapshot and applying the patch so the live mutation and synchronous save remain one uninterrupted operation.

Useful? React with 👍 / 👎.

@Ingwannu Ingwannu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed PF-04 layer at exact head d0077bc541fa0a86f7133240323f44e8724f3e7a against feat/pf05-inference-primitives. The focused bounded suites pass 41/41, but the settings writer has a concurrency blocker.

patchProtocolSettings() snapshots and mutates the shared live config, then awaits the fallback dynamic import that resolves saveConfigPreservingClaudeCode. Two concurrent PATCH requests can interleave during that await: request B mutates the same object before request A saves, so A can persist/return B's state; A's rollback on save failure can also erase B's successful mutation. Resolve the persistence function before snapshot/apply (or move the whole read-modify-save into the existing serialized mutation primitive) so mutation through synchronous locked persistence is uninterrupted. Add a concurrent PATCH regression covering both success and rollback interleavings.

Separately, the PR is still draft-gated for the required UI screenshot and its stack CI inherits failures from lower PF layers; approval must wait for those too.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 68 / 80

이 PR은 Messages API를 Claude 켜기 스위치에서 떼어 낸다. /v1/messages와 /v1/messages/count_tokens는 resolveApiSurfaceSettings 하나만 본다. 설정에 apiSurfaces.messages.enabled가 있으면 그 값이 이긴다. 값이 깨져 있으면 둘 다 닫힌다. 키가 없으면 예전처럼 claudeCode.enabled를 따른다.

대시보드 API 페이지는 Responses, Chat Completions, Messages를 카드로 보여 준다. Messages 카드는 꺼져 있어도 남고, 토글이 있다. 끄면 apiSurfaces.messages.enabled와 claudeCode.enabled를 한 번에 false로 저장한다. 켜면 표면 값만 true로 쓴다. 저장이 실패하면 메모리의 설정을 되돌리고, 잠금이 겹치면 409, 그 외에는 500이다. 오류 문장에 디스크 경로는 넣지 않는다.

Claude 설정 화면과 네이티브 토글이 claudeCode 블록을 각자 쓰던 코드는 commitClaudeCodeBlock 한곳으로 모였다. 인증 모드 표시를 찍는 동작은 같다.

베이스는 #5811의 feat/pf05-inference-primitives다. 같은 내용의 다른 열린 PR은 없다. src/types.ts나 src/config.ts를 나누는 변경은 없다.

라인 - src/server/management/protocol-routes.ts 90–104행 — 살아 있는 config를 고친 뒤에 await import("../../config")로 저장 함수를 불러온다. 그 사이에 다른 PATCH가 같은 객체를 고치면, 먼저 온 요청이 나중 요청의 상태를 저장할 수 있다. 먼저 온 요청의 저장이 실패하면 되돌리기가 나중 요청이 이미 성공한 변경도 지운다. 테스트는 deps.saveConfigPreservingClaudeCode를 넣어 이 await를 타지 않는다. 저장 함수를 스냅샷보다 먼저 정하고, 동시에 두 번 PATCH하는 성공과 되돌리기 경우를 테스트에 넣어라.

라인 - tests/cli/cli-headless-parity.test.ts 509행 — 기대한 목록은 빈 배열인데 /api/protocols, /api/protocols/plan, /api/protocols/settings가 들어 있다. 앞의 둘은 부모 스택에 이미 있다. /api/protocols/settings는 이 PR이 라우트 표에 넣은 길이다. 등록은 PF-12가 CLI를 가진다고 적혀 있다. 샤드 2는 6/43에서 여기서 멈춘다.

라인 - gates의 프랑스어 목록 검사는 api.plan.routeKind가 영어로 남아 실패한다. 이 키는 이 diff에 없다. gui/tests/apikeys-layout.test.ts 19행은 viewMode 글자가 없어야 하는데, 베이스의 previewModels 안에 그 글자가 들어 있다. 이 PR이 그 속성을 추가하지 않았다.

gui/tests/api-surface-cards.test.tsx와 tests/server/protocol-settings-route.test.ts는 CI에서 통과했다. tests/claude-integration/messages-surface-matrix.test.ts 이름은 초록 샤드 1, 3, 4 로그에 없다. 샤드 2가 앞에서 멈춘 뒤라 실행 기록이 없다.

메인테이너의 판단이 필요한 지점

apiSurfaces.messages.enabled를 true로 적어 두면, Claude 페이지에서 Claude를 꺼도 이 바이너리의 /v1/messages는 열린 채로 남는다. 카드에 출처가 보인다. 이 동작을 유지할지 정해 달라.

손으로 apiSurfaces.messages.enabled: false만 쓰고 claudeCode.enabled는 true로 두면, 옛 바이너리로 되돌렸을 때 Messages가 다시 열린다. 대시보드가 끈 상태만 옛 바이너리에서도 닫힌다.

enforce-target은 베이스가 dev일 때만 통과한다. 지금 베이스를 dev로 바꾸면 #5811 스택이 풀린다.

너의 추천

방향은 유지해라. 닫을 중복 PR은 없다. 베이스를 dev로 되돌리지 마라. 저장 함수를 고치기 전에 정하고, 동시에 두 PATCH가 겹치는 테스트를 넣어라. 그 전엔 머지하지 마라. 프랑스어 키와 previewModels 오탐은 부모에서 고쳐라. 패리티 목록의 세 경로는 PF-12가 CLI를 받기 전까지 예외로 둘지, 이 패킷에서 목록에 적을지 정해 달라. 초안은 UI 스크린샷이 빠져 있다.

이 댓글은 grok-bot이 작성했습니다

/v1/messages and count_tokens now share the one surface reader, so an explicit
apiSurfaces.messages value wins, a malformed one closes both, and absence still
inherits claudeCode.enabled.
… metadata

The keys payload gains surfaces.{responses,chat,messages} from the shared resolver.
claudeCodeEnabled stays for older dashboards and now mirrors the resolved Messages
state, so they never advertise a closed endpoint.
…ping writer

PUT /api/claude-code and the native Claude toggle each re-stated the auth-mode
migration stamp. The protocol settings route writes the same block next, so the
rule lives in commitClaudeCodeBlock rather than in a third copy.
Strict parsing refuses unknown keys and wrong types. Closing Messages also writes
claudeCode.enabled=false so an older binary after rollback stays closed; opening
writes only the explicit surface value. A snapshot lets the route undo a failed save.
The Messages toggle and protocol switches persist through the locked
saveConfigPreservingClaudeCode, undo the in-memory change when the save fails, and
answer with the fresh GET /api/protocols shape. Declared as a PF-12 deferred verb.
…adata

Strict body, both keys written on close, only the surface on open, rollback on a
failed save, 409 on lock contention, and surfaces in the API access metadata.
Absent inherits, dashboard-written false is closed on old and new binaries,
explicit true with Claude off is open only on the new one, invalid values close,
and count_tokens agrees with /v1/messages in every row.
parseApiSurfaces reads surfaces from the keys payload and answers undefined for an
older server, so the page can fall back instead of guessing. patchProtocolSettings
validates the fresh GET shape the server returns.
State, source and Messages toggle strings for the three API cards, including the
note that closing Messages also turns the Claude integration off.
Each card states whether the API is served, its endpoint, and who decided it. The
Messages card keeps showing while closed, carries the toggle, and links to the
Claude page. Without surfaces from the server the flat endpoint list stays.
The keys payload's surfaces are validated on read and in the session cache, and a
successful toggle reloads the payload from the same apiBase rather than guessing.
Three cards with state and source, a closed Messages card that stays visible, the
older-server fallback, and a toggle that PATCHes the target apiBase then reloads.
…r and API cards

Protocol paths now names the ingress reader, the PATCH writer and its rollback, and
why closing Messages also writes claudeCode.enabled; the dashboard doc covers the
three API cards and the older-server fallback.
The server config reference explains inheritance, fail-closed values and the
downgrade behavior of the dashboard toggle; the management API table gains the
settings route and its errors.
@lidge-jun
lidge-jun force-pushed the feat/pf05-inference-primitives branch from 243fd58 to 70c2c84 Compare September 25, 2026 04:32
@lidge-jun
lidge-jun force-pushed the feat/pf04-api-surfaces branch from d0077bc to 03189b6 Compare September 25, 2026 04:32
@github-actions
github-actions Bot marked this pull request as ready for review September 25, 2026 05:25
@devin-ai-integration devin-ai-integration Bot added the priority: P3 Low: new provider/client integration, large or experimental feature (>2000 LOC or >50 files), RFC/ro label Sep 25, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

Maintainer triage: priority: P3 — protocol-first-class series PF-04.

Criteria (P3): Low: new provider/client integration, large or experimental feature (>2000 LOC or >50 files), RFC/roadmap, or long-stale branch.

Related / overlapping PRs:

lidge-jun added a commit that referenced this pull request Sep 25, 2026
…12 (#5820)

Squash of the protocol-first-class stack #5808, #5809, #5810, #5811, #5812, #5813, #5814, #5815, #5816, #5817, #5819 and #5820. Every new lane sits behind a protocols.rollout switch that defaults off.
@lidge-jun

Copy link
Copy Markdown
Owner Author

Landed in dev as part of the single squash of the protocol-first-class stack: #5820 (0f4c8d4).

@lidge-jun lidge-jun closed this Sep 25, 2026
@lidge-jun
lidge-jun deleted the feat/pf04-api-surfaces branch September 26, 2026 01:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request priority: P3 Low: new provider/client integration, large or experimental feature (>2000 LOC or >50 files), RFC/ro

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants