Files
1jehuang--jcode/docs/PROVIDER_SESSION_SHARED_CONTRACT_AUDIT.md
wehub-resource-sync a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

12 KiB

Provider, Session, and Shared-Contract Boundary Audit

Status: 2026-04-16 audit note

This document audits the current provider, session, and shared-contract seams in the jcode workspace and recommends the next realistic crate moves that improve modularity without creating high-churn dependency cycles.

It is intentionally conservative. The goal is to identify boundaries that are both:

  • structurally useful
  • low enough churn to be worth turning into workspace crates now

See also:

Executive summary

The next clean workspace moves are not a full Provider trait extraction and not a full session.rs split.

The best next steps are:

  1. Add a small jcode-shared-contracts crate for the serde-only protocol/session overlap types that already act like shared contracts.
  2. After that, add a narrow jcode-session-contracts crate for session metadata/replay/view structs that are widely reused but do not need the full Session runtime.
  3. If we want one more provider-side move before a larger provider refactor, extract the pure provider identity/selection layer into jcode-provider-core or a small jcode-provider-selection crate.

The main things to avoid for now:

  • extracting Provider / EventStream into a shared crate
  • extracting all of protocol.rs
  • extracting all of session.rs
  • moving provider_catalog.rs wholesale into a crate

Those look tempting, but today they would mostly convert existing high-churn coupling into workspace-crate churn.

Current workspace boundary state

Already landed and directionally good:

  • crates/jcode-provider-metadata
  • crates/jcode-provider-core
  • crates/jcode-provider-openrouter
  • crates/jcode-provider-gemini

A useful property of the current extracted crates is that they are still leaf-like support crates.

Current local workspace dependency picture for those crates:

  • jcode-provider-core: no local workspace deps
  • jcode-provider-metadata: no local workspace deps
  • jcode-provider-openrouter: no local workspace deps
  • jcode-provider-gemini: no local workspace deps

That is the right pattern to preserve. The next crate moves should keep producing small, leaf-ish crates instead of creating new central hubs that everything recompiles through.

Hotspots and coupling observed

Relevant file sizes in the main crate:

  • src/session.rs: 2730 lines
  • src/provider/mod.rs: 2283 lines
  • src/protocol.rs: 1198 lines
  • src/provider/openrouter.rs: 1132 lines
  • src/provider/gemini.rs: 1117 lines
  • src/provider_catalog.rs: 775 lines
  • src/plan.rs: 17 lines

High-level coupling observed during the audit:

  • src/provider/mod.rs directly references auth, logging, bus, message, and usage
  • src/session.rs directly references message, protocol, plan, storage, and support modules
  • src/protocol.rs directly references bus, config, message, plan, provider, session, and side_panel
  • src/provider_catalog.rs is especially tied to env, storage, and logging

That means the biggest blockers are not the already-extracted support crates. They are the remaining mixed runtime/facade modules in the main crate.

Dependency shape

flowchart LR
    P[provider/mod.rs] --> AUTH[auth]
    P --> MSG[message]
    P --> BUS[bus]
    P --> USAGE[usage]

    S[session.rs] --> MSG
    S --> PROTO[protocol.rs]
    S --> PLAN[plan.rs]
    S --> STORE[storage]

    PROTO --> BUS
    PROTO --> CFG[config]
    PROTO --> MSG
    PROTO --> PLAN
    PROTO --> PROVIDER_TYPES[provider types]
    PROTO --> SESSION_TYPES[session types]

The key architectural smell is that some types that are effectively shared contracts still live inside large mixed-responsibility modules.

Provider boundary audit

What is already in a good state

The existing provider crate moves were well chosen:

  • jcode-provider-metadata holds stable login/profile catalog data
  • jcode-provider-core holds route/cost/shared HTTP client/core value types
  • jcode-provider-openrouter holds OpenRouter-specific catalog/cache/ranking/model-spec support
  • jcode-provider-gemini holds Gemini Code Assist schema/types/support helpers

These are all relatively pure support surfaces.

What is not a good next move yet

Do not extract Provider / EventStream yet

src/provider/mod.rs is still deeply entangled with:

  • crate::message::{Message, StreamEvent, ToolDefinition}
  • auth-driven behavior
  • runtime selection/failover
  • logging and bus notifications
  • provider-specific compaction and transport behavior

Moving the trait now would likely create a new shared crate that still changes whenever runtime/provider behavior changes.

That would improve directory layout, but not boundary quality.

Do not move provider_catalog.rs wholesale yet

src/provider_catalog.rs is not just metadata. It currently mixes:

  • catalog/profile values
  • env mutation
  • auth probing helpers
  • config-file lookup
  • logging/warnings

That facade is still too runtime-aware to become a clean leaf crate as-is.

Best realistic provider move

Option A: extract provider identity + pure selection

Most realistic provider-side move after the current support crates:

  • move the provider identity enum currently represented by ActiveProvider
  • move src/provider/selection.rs
  • optionally move pure fallback ordering helpers that do not depend on auth/runtime state

Target:

  • either a new crates/jcode-provider-selection
  • or a small provider_identity / selection module inside jcode-provider-core

Why this is realistic:

  • selection.rs is already pure logic
  • it does not need Message, EventStream, auth state, or storage
  • it would shave some policy code out of src/provider/mod.rs
  • it creates a stable place for provider-order and provider-name normalization rules

Why this should stay narrow:

  • once the code starts touching account failover, auth checks, runtime availability, or logging, it stops being a good crate boundary

Session boundary audit

Why session.rs should not be extracted wholesale yet

src/session.rs is large, but it is not one thing. It currently mixes:

  • persisted session data structures
  • runtime session state
  • journaling / file persistence helpers
  • replay-event persistence
  • startup/remote snapshot helpers
  • image rendering helpers

A whole-file crate extraction would drag in more coupling than it removes.

Current blockers:

  • StoredMessage depends on crate::message::{ContentBlock, Message, Role, ToolCall}
  • replay-event types currently depend on crate::protocol::SwarmMemberStatus
  • replay-event plan snapshots currently depend on crate::plan::PlanItem
  • the session module also owns persistence and storage concerns

So the next move should be a session-contract slice, not a full session crate.

Best realistic session move

Option B: narrow jcode-session-contracts

After shared contracts are extracted first, move the session types that are:

  • serde-only
  • reused outside session.rs
  • not tied to storage or the full Session runtime

Good first candidates:

  • SessionStatus
  • SessionImproveMode
  • StoredDisplayRole
  • StoredTokenUsage
  • StoredCompactionState
  • StoredMemoryInjection
  • RenderedImageSource
  • RenderedImage
  • StoredReplayEvent and StoredReplayEventKind once their swarm/plan payloads stop pointing back into protocol.rs

What should stay in the main crate for now:

  • Session
  • StoredMessage
  • session journaling/file IO
  • session startup/load/save orchestration
  • message-to-image rendering functions

Why this is realistic:

  • these contract structs already have broad fanout across agent, server, replay, and TUI code
  • they are semantically session-level contracts, not session-runtime behavior
  • the move becomes much cleaner once shared swarm/protocol payloads are extracted first

Shared-contract boundary audit

This is the highest-leverage next seam.

There are several small, serde-only types that are clearly shared contracts already, but they currently live inside large modules:

  • PlanItem in src/plan.rs
  • TranscriptMode in src/protocol.rs
  • CommDeliveryMode in src/protocol.rs
  • FeatureToggle in src/protocol.rs
  • SessionActivitySnapshot in src/protocol.rs
  • SwarmMemberStatus in src/protocol.rs
  • AgentInfo in src/protocol.rs
  • ContextEntry in src/protocol.rs
  • SwarmChannelInfo in src/protocol.rs
  • AwaitedMemberStatus in src/protocol.rs
  • NotificationType in src/protocol.rs

These are used across server, tool, TUI, replay, and session persistence flows, but they do not need the rest of protocol.rs.

Best overall next move

Option C: add jcode-shared-contracts

Recommended contents for the first pass:

  • PlanItem
  • TranscriptMode
  • CommDeliveryMode
  • FeatureToggle
  • SessionActivitySnapshot
  • swarm-related status/info structs:
    • SwarmMemberStatus
    • AgentInfo
    • ContextEntry
    • SwarmChannelInfo
    • AwaitedMemberStatus
    • NotificationType

Why this is the best next move:

  • it breaks the session.rs -> protocol.rs / plan.rs dependency knot at the contract layer
  • it gives replay/session persistence a clean dependency for swarm and plan snapshots
  • it trims protocol.rs without trying to extract Request and ServerEvent yet
  • it preserves the current successful pattern of a small, leaf-ish support crate with mostly serde types

Minimal dependency goal:

  • serde
  • nothing else, if possible

Phase 1

Create crates/jcode-shared-contracts.

Expected immediate moves:

  • src/plan.rs contents
  • the small shared structs/enums listed above from src/protocol.rs

Keep in main crate for now:

  • Request
  • ServerEvent
  • encode_event / decode_request

Phase 2

Create crates/jcode-session-contracts.

Do this only after Phase 1, so session replay types can point at jcode_shared_contracts::* instead of crate::protocol::* or crate::plan::*.

Phase 3

If a provider-side move is still desired before a larger provider refactor, extract only:

  • provider identity enum
  • pure selection/fallback ordering helpers

Do not include:

  • Provider trait
  • EventStream
  • account failover
  • auth state inspection
  • runtime provider availability
  • logging/bus side effects

Moves to explicitly defer

These should be treated as later-stage refactors, not next-step crate moves.

Defer: full protocol.rs crate

Reason:

  • Request and ServerEvent still pull in message, provider, session, side_panel, and bus
  • extracting the whole file now would create a broad, high-fanout crate instead of a clean contract crate

Defer: full session.rs crate

Reason:

  • the file mixes contracts, runtime state, rendering, journaling, and persistence
  • StoredMessage still anchors the session layer to message.rs

Defer: full provider trait / impl crate split

Reason:

  • the trait seam is still mixed with runtime behavior and provider-specific execution policy
  • moving it now would likely centralize churn rather than reduce it

Defer: full provider_catalog.rs extraction

Reason:

  • the file is still a runtime facade around env/config/auth probing, not just metadata

Why this order avoids dependency-cycle mistakes

The sequence matters:

  1. extract small shared contracts first
  2. then extract session contracts that depend on those shared contracts
  3. only then revisit deeper provider or protocol extraction

That order avoids creating crates that need to point back into the main crate for basic DTOs, which is exactly how high-churn dependency cycles usually start.

  1. Add crates/jcode-shared-contracts with serde-only types from plan.rs and the small protocol/session overlap set.
  2. Update session.rs, protocol.rs, server, tool, replay, and TUI imports to point at that crate.
  3. Re-measure touched-file compile times for:
    • src/session.rs
    • src/protocol.rs
    • src/provider/mod.rs
  4. If the new seam stays clean, follow with a narrow jcode-session-contracts extraction.
  5. Revisit provider trait extraction only after message/runtime/provider-execution seams are thinner.