feat(providers/qoder): add native Qoder provider backed by qodercli - #1107
feat(providers/qoder): add native Qoder provider backed by qodercli#1107kanmars wants to merge 17 commits into
Conversation
Implement a first-class QoderProvider for CloudCLI, modeled after the opencode provider, so CloudCLI can run via qodercli without the CLAUDE_CLI_PATH wrapper trick: - qoder.provider.ts: provider factory wiring runtime/models/auth/mcp/ skills/sessions/sessionSynchronizer facets - qoder-runtime.provider.js: spawn qodercli -p --output-format stream-json; permission-mode mapping (bypass_permissions / accept_edits); per-message attachments; session registration via stream events; JSONL-based token usage; per-session abort - qoder-models.provider.ts: models from qodercli --list-models with fallback to QODER_FALLBACK_MODELS (Auto/Ultimate/Performance/ Efficient/Lite/Cantus) - qoder-auth.provider.ts: qodercli --version + ~/.qoder/.auth/user credential check - qoder-sessions.provider.ts: normalizeMessage + fetchHistory from ~/.qoder/projects JSONL (thinking/text/tool_use/tool_result) - qoder-session-synchronizer.provider.ts: scan ~/.qoder/projects for new JSONL sessions, upsert into sessionsDb with ai-title naming - qoder-mcp.provider.ts: user scope from ~/.qoder/settings.json mcpServers, project scope from .mcp.json (stdio + http transports) - qoder-skills.provider.ts: project + user skill sources (qoder/ claude/agents conventions) - provider.registry.ts: register qoder; UI wiring for provider selection, auth modal, token usage, sessions, api-docs, i18n - tests: extend provider enumeration assertions to include qoder
- models: qodercli --list-models prints a literal MODEL header row; parseQoderModelsStdout now drops it so it cannot surface as a selectable model (selecting it passes -m MODEL and qodercli rejects it with exit 42). Adds a parser regression test against the real captured output. - runtime/sessions: make the runtime the single owner of the session_created event. readQoderSessionId now reads sessionID / sessionId / session_id so the live system/init control event is captured regardless of key spelling, and the sessions provider init branch no longer emits session_created (matches claude/opencode siblings).
…y id qodercli treats --session-id as 'create a session with this id' and rejects ids that already exist on disk with 'Session ID ... is already in use.' Continuing a conversation must pass --resume <providerSessionId> instead.
Replace the placeholder inline SVG with the official qoder.png (bundled via src/assets so Vite hashes it and rewrites the URL per deployment base). QoderLogo keeps its className API, so all SessionProviderLogo call sites are unchanged.
The token usage service derived Qoder transcript directories with the Claude-style replace(/[^a-zA-Z0-9-]/g, '-'), but qodercli only folds '/' -> '-'. Project paths containing dots (e.g. /home/admin/my.app) resolved to a nonexistent directory and surfaced as SESSION_FILE_NOT_FOUND, silently breaking token accounting. Add encodeQoderCwd to shared utils as the single source of truth for the on-disk encoding and route the models provider fallback through it too, so every transcript-path derivation stays consistent with what the CLI writes.
…ings to CLI Settings > Qoder > Permissions previously rendered blank because AgentCategoryContentSection only had claude/cursor/codex branches. Add a qoder branch reusing the tool-list panel (skipPermissions + allowed/ disallowed tools), persisted under the qoder-settings localStorage key the chat composer already reads. Backend: migrate the qoder permission mapping from the JS runtime into a new TypeScript module (qoder-permissions.provider.ts) that also consumes toolsSettings, mapping skipPermissions/bypassPermissions onto --permission-mode bypass_permissions and allowed/disallowed tools onto the matching qodercli flags, so the panel config actually reaches spawned qodercli processes. Covered by qoder-permissions.test.ts.
The Qoder permissions panel was reusing Claude's, so it advertised controls qodercli does not honor: Bash(cmd:*) sub-command scopes are ignored, and the quick-add list offered MultiEdit/Task/TodoRead/TodoWrite, which are not Qoder tools. Users could therefore believe they had granted only `git log` while the whole Bash tool stayed open. Give Qoder its own panel with bare tool names grouped by purpose, copy that states the measured semantics (allow list only skips prompts, deny beats allow), and a warning when bypass_permissions can override the block list. Add a Restrict tool set section mapped to `--tools`, the only flag that actually removes tools from the session. Since `--tools` is variadic it would swallow the trailing prompt, so the runtime now emits `--` before it.
…lpers Provider code had grown four private copies of the same primitives: a cwd encoder, a file-existence probe, a JSON config reader/writer, and the token usage arithmetic. Each copy was free to drift from what qodercli writes on disk, and none of them validated the session id they interpolated into a path. Adds isSafeSessionId, resolveQoderTranscriptPath, readJsonlEntries, aggregateQoderTranscriptTokenUsage and a shared fileExists so the qoder runtime, models provider, sessions provider, synchronizer and token usage service can converge on one derivation in the follow-up commits.
…sion Probing qodercli 1.1.13 shows preview models print as `Peach-07-17-DogFooding (qwen3.8-max-preview)` while only the leading token is a usable id — passing the printed string back as `-m` fails with `Invalid model`. The parser accepted any line that was not the MODEL header or a JSON fragment, so it offered that unusable string as a selectable model, and any future banner or warning row would have become one too. Rows are now matched against the two shapes the CLI actually emits (bare id, or id plus parenthesized alias); anything else is dropped with a warning.
…tring results Three defects found by probing qodercli 1.1.13 against the provider code: Session ids arrived from WebSocket and HTTP payloads and were interpolated straight into transcript paths, so an id containing `../` could read any .jsonl on the host. All three call sites now go through the shared resolver, which whitelists the id and owns the cwd encoding; the containment check it replaces compared a path against the directory it was joined from and could never fail. The permission block can end with the variadic `--tools`, which keeps consuming bare arguments, yet `--attachment` pairs were emitted after it. Attachments now precede that block so nothing but `--` can follow it. The CLI reports its final answer as `"result": "OK"`, a plain string, but only the Claude-style content array was handled, so the answer was dropped while the run still reported success. Also declares supportsTokenUsage false: every token field in a Qoder transcript is 0 because the CLI measures spend as credits plus a context usage ratio.
Transcripts grow without bound, yet three readers pulled whole files into memory: the runtime after every run, the models provider on every active-model query, and the synchronizer three separate times per session while resolving a title. All of them now stream rows, and the synchronizer collects its title candidates in a single pass. The sessions provider also stops scanning every directory under ~/.qoder/projects to locate a transcript — it derives the path the same way the models provider does, which removes a magic 2000-iteration cap that could silently give up. getSupportedModels spawns `qodercli --list-models` with a 20s timeout, and getCurrentActiveModel falls back to it whenever a session has no recorded model, bypassing providerModelsService's on-disk cache; successful catalogs are now memoized for five minutes. Drops the pre-upsert rename in synchronize(): createSession already writes the resolved name over custom_name in both of its branches.
…ations The provider shipped several stubs that could never do anything: two exports with no consumer, a permission options field that was always an empty object, a meta-row set and an attachment flag referenced only by `void` statements, and a local variable named `process` shadowing the global inside the abort path. Images were destructured and then discarded without a word to the user, so a Qoder run just looked like it ignored the picture; the runtime now says how many were dropped and why. The Skills tab was hidden for Qoder even though QoderSkillsProvider is registered and reads .qoder, .claude and .agents skill folders, making a working backend facet unreachable. It is now shown, with the install path the provider actually scans. Removes the plan permission mode from the frontend fallback: the capability matrix excludes it and the runtime emits no flag for it, so choosing plan silently did nothing. Adds the four qoder keys the zh-CN and zh-TW bundles were missing so the Chinese UI stops falling back to English.
…sync title priority Extract buildQoderArgs as a pure exported function from the qoder runtime so the ordering invariant — attachments before the variadic --tools, then the -- separator, then the prompt — can be pinned without spawning the CLI. Add coverage for QoderSessionsProvider.normalizeMessage (every event type incl. result string form), fetchHistory pagination, QoderSessionSynchronizer title priority and sidechain filtering, and the qoder branch of the provider token-usage service. Update providers/README.md with qoder across all six documentation locations.
- Pass images as --attachment flags to the Qoder CLI. - Append <images_input> tag to the prompt before <files_input>. - Parse <images_input> back out when normalizing session history. - Enable supportsImages for the qoder provider.
- Deduplicate message ids for multi-part result content - Avoid unconditional qoder model preload for every provider - Add shell resume fallback for qoder on unix/win32 - Buffer stderr and only emit as error on non-zero exit - Memoize active model transcript scans with 30s TTL - Normalize transcript model names against supported catalog
📝 WalkthroughWalkthroughQoder is added as a supported provider across backend runtime services, sessions, models, MCP, authentication, agent routes, chat, settings, onboarding, localization, documentation, and tests. ChangesQoder provider integration
Sequence Diagram(s)sequenceDiagram
participant User
participant ChatInterface
participant AgentRoutes
participant qoderRuntime
participant qodercli
User->>ChatInterface: Select Qoder model and permissions
ChatInterface->>AgentRoutes: Submit Qoder agent request
AgentRoutes->>qoderRuntime: Pass project, session, model, effort, and permissions
qoderRuntime->>qodercli: Spawn Qoder command
qodercli-->>qoderRuntime: Stream JSONL or text output
qoderRuntime-->>ChatInterface: Forward normalized messages and session events
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
server/modules/providers/list/qoder/qoder-models.provider.ts (1)
255-268: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAlign the two
resolveQoderJsonlPathimplementations.This file and
server/modules/providers/list/qoder/qoder-sessions.provider.ts(lines 34-48) both resolve the Qoder transcript path, but they disagree on two points:
- This version trusts
jsonl_pathwithout checking that the file still exists. The sessions version guards withfs.existsSyncand falls back toresolveQoderTranscriptPath.- This version looks the row up by
provider_session_idfirst, then by id. The sessions version looks it up by app session id only.If a recorded
jsonl_pathbecomes stale (project moved, transcript pruned),getCurrentActiveModelreads a missing file and reports the default model, whilefetchHistoryrecovers through the canonical resolver. Extract one shared resolver so both facets behave the same.♻️ Proposed fix to add the existence guard
const resolveQoderJsonlPath = (providerSessionId: string, projectPath?: string): string | null => { // Prefer the transcript path recorded by the synchronizer so cwd encoding // stays consistent with what the CLI actually wrote on disk. const storedPath = sessionsDb.getSessionByProviderSessionId(providerSessionId)?.jsonl_path ?? sessionsDb.getSessionById(providerSessionId)?.jsonl_path; - if (storedPath) { + if (storedPath && fs.existsSync(storedPath)) { return storedPath; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/modules/providers/list/qoder/qoder-models.provider.ts` around lines 255 - 268, Extract the shared Qoder transcript-path resolver used by resolveQoderJsonlPath in this file and qoder-sessions.provider.ts, aligning both callers to look up sessions by app session id only. Validate any stored jsonl_path with fs.existsSync before returning it, and otherwise fall back to resolveQoderTranscriptPath so getCurrentActiveModel and fetchHistory recover identically from stale or missing transcripts.server/modules/providers/list/qoder/qoder-sessions.provider.ts (1)
354-385: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winMerge token-usage accumulation into the normalization pass.
fetchHistoryalready buffers the transcript to computetotaland sort entries, so keeping the tail-paging pattern is acceptable. Eliminate the separate token-usage pass by accumulating usage during normalization instead of callingaggregateQoderTranscriptTokenUsage(rawMessages)on the retained buffer.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/modules/providers/list/qoder/qoder-sessions.provider.ts` around lines 354 - 385, The fetchHistory normalization flow should accumulate token usage while iterating through rawMessages, eliminating the separate aggregateQoderTranscriptTokenUsage(rawMessages) pass over the retained buffer. Update the normalization loop to collect the same cumulative usage result, then preserve the existing tokenUsage behavior and tail-pagination logic.server/modules/providers/tests/qoder-args.test.ts (1)
6-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test for
images.
baseOptionsomitsimages, and no test passes it.buildQoderArgshandles images separately from files: it emits--attachmentfor each image descriptor and appends the<images_input>tag throughappendImagesInputTag. Image attachment support is new in this PR, so the image branch has no coverage here. Add a case that mirrors the files test.💚 Proposed test case
+test('buildQoderArgs: images produce --attachment and an images_input tag', () => { + const args = buildQoderArgs({ + ...baseOptions, + command: undefined, + images: ['/path/to/image.png'], + }); + + const attachmentIndex = args.indexOf('--attachment'); + assert.ok(attachmentIndex > -1, 'expected --attachment'); + assert.equal(args[attachmentIndex + 1], '/path/to/image.png'); + + const lastArg = args.at(-1); + assert.ok(lastArg.includes('<images_input>'), 'prompt must contain images_input tag'); + assert.ok(lastArg.includes('/path/to/image.png'), 'prompt must contain the image path'); +}); + test('buildQoderArgs: no prompt and no files means no -- separator and no prompt arg', () => {Also applies to: 107-118
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/modules/providers/tests/qoder-args.test.ts` around lines 6 - 15, Add image coverage in the qoder argument tests: extend baseOptions with images and add a test mirroring the existing files case that passes image descriptors to buildQoderArgs. Assert each image produces a --attachment argument and that the generated prompt includes the <images_input> tag via appendImagesInputTag.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server/modules/providers/list/qoder/qoder-auth.provider.ts`:
- Around line 22-25: Update QoderAuthProvider.checkInstalled to be asynchronous
and replace spawn.sync with the non-blocking child-process API for qodercli
--version; enforce the five-second limit by calling child.kill() when the
timeout elapses, then resolve based on successful exit status and error state.
Propagate the async return through each caller of checkInstalled.
In `@server/modules/providers/list/qoder/qoder-runtime.provider.js`:
- Around line 354-381: Update the close handler around qoderProcess.aborted to
handle aborted runs before the code === 127 || code === null failure checks:
skip installation/error notifications and notifyTerminalState failure reporting,
then reject with an abort-specific error. Preserve the existing completion
behavior and normal exit/failure handling for non-aborted processes.
In `@server/modules/providers/list/qoder/qoder-sessions.provider.ts`:
- Around line 178-186: Update the attachment-handling flow in the session
mapping callback to track image and file attachments with separate flags instead
of the shared filesAttached flag. Guard parsedImages and parsedFiles
independently so later text parts still include whichever attachment type has
not yet been emitted, and update each flag only when its corresponding
attachments are present.
In `@server/shared/utils.ts`:
- Around line 1041-1044: Update the token aggregation logic around inputTokens,
outputTokens, cacheReadTokens, and cacheCreationTokens to validate each usage
field before addition. Convert values safely, and add them only when the result
is finite and non-negative; otherwise skip that field so malformed usage cannot
make totals NaN.
- Around line 990-991: Normalize the resolved cwd to forward-slash separators
before passing it to encodeQoderCwd in the transcript path construction. Update
the path.resolve(cwd) value used by encodeQoderCwd so Windows backslashes are
converted first, while preserving the existing path.join behavior with
projectsDir and sessionId.
In `@src/components/mcp/constants.ts`:
- Around line 19-24: Update the Qoder MCP configuration flow associated with
MCP_SUPPORTED_TRANSPORTS to reject or sanitize credential-bearing HTTP
destinations before writing settings.json or .mcp.json. Require HTTPS whenever
headers or bearer tokens are present, or strip those credentials for plaintext
HTTP, while preserving existing behavior for non-credentialed and HTTPS servers.
In
`@src/components/settings/view/tabs/agents-settings/sections/AgentCategoryTabsSection.tsx`:
- Line 34: Update the defaultValue selection in AgentCategoryTabsSection to use
the provider-specific translation key for shared skills when selectedAgent is
opencode or qoder, while retaining tabs.skills for other agents. Add or reuse
the corresponding tabs.sharedSkills and tabs.skills translation keys across the
supported locales.
In `@src/i18n/locales/en/settings.json`:
- Line 479: Add the missing project scope label to the mcpServers.scope map in
src/i18n/locales/en/settings.json at lines 479-479 and
src/i18n/locales/zh-CN/settings.json at lines 479-479, using the appropriate
localized text so non-Qoder project MCP servers resolve their scope labels
correctly.
---
Nitpick comments:
In `@server/modules/providers/list/qoder/qoder-models.provider.ts`:
- Around line 255-268: Extract the shared Qoder transcript-path resolver used by
resolveQoderJsonlPath in this file and qoder-sessions.provider.ts, aligning both
callers to look up sessions by app session id only. Validate any stored
jsonl_path with fs.existsSync before returning it, and otherwise fall back to
resolveQoderTranscriptPath so getCurrentActiveModel and fetchHistory recover
identically from stale or missing transcripts.
In `@server/modules/providers/list/qoder/qoder-sessions.provider.ts`:
- Around line 354-385: The fetchHistory normalization flow should accumulate
token usage while iterating through rawMessages, eliminating the separate
aggregateQoderTranscriptTokenUsage(rawMessages) pass over the retained buffer.
Update the normalization loop to collect the same cumulative usage result, then
preserve the existing tokenUsage behavior and tail-pagination logic.
In `@server/modules/providers/tests/qoder-args.test.ts`:
- Around line 6-15: Add image coverage in the qoder argument tests: extend
baseOptions with images and add a test mirroring the existing files case that
passes image descriptors to buildQoderArgs. Assert each image produces a
--attachment argument and that the generated prompt includes the <images_input>
tag via appendImagesInputTag.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ee389ffa-9438-440c-8dd1-ed658659397a
⛔ Files ignored due to path filters (1)
src/assets/qoder.pngis excluded by!**/*.png
📒 Files selected for processing (68)
public/api-docs.htmlserver/index.tsserver/modules/agent/agent.module.tsserver/modules/agent/agent.routes.tsserver/modules/agent/tests/agent.routes.test.tsserver/modules/commands/commands.routes.tsserver/modules/providers/README.mdserver/modules/providers/list/opencode/opencode-mcp.provider.tsserver/modules/providers/list/qoder/qoder-auth.provider.tsserver/modules/providers/list/qoder/qoder-mcp.provider.tsserver/modules/providers/list/qoder/qoder-models.provider.tsserver/modules/providers/list/qoder/qoder-permissions.provider.tsserver/modules/providers/list/qoder/qoder-runtime.provider.jsserver/modules/providers/list/qoder/qoder-session-synchronizer.provider.tsserver/modules/providers/list/qoder/qoder-sessions.provider.tsserver/modules/providers/list/qoder/qoder-skills.provider.tsserver/modules/providers/list/qoder/qoder.provider.tsserver/modules/providers/provider.registry.tsserver/modules/providers/provider.routes.tsserver/modules/providers/services/provider-capabilities.service.tsserver/modules/providers/services/provider-token-usage.service.tsserver/modules/providers/services/session-synchronizer.service.tsserver/modules/providers/services/sessions-watcher.service.tsserver/modules/providers/tests/mcp.test.tsserver/modules/providers/tests/provider-runtime.service.test.tsserver/modules/providers/tests/provider-token-usage.service.test.tsserver/modules/providers/tests/qoder-args.test.tsserver/modules/providers/tests/qoder-models.test.tsserver/modules/providers/tests/qoder-permissions.test.tsserver/modules/providers/tests/qoder-session-synchronizer.test.tsserver/modules/providers/tests/qoder-sessions.test.tsserver/modules/websocket/services/shell-websocket.service.tsserver/shared/types.tsserver/shared/utils.tssrc/components/chat/hooks/useChatComposerState.tssrc/components/chat/hooks/useChatProviderState.tssrc/components/chat/view/ChatInterface.tsxsrc/components/chat/view/subcomponents/ChatMessagesPane.tsxsrc/components/chat/view/subcomponents/CommandResultModal.tsxsrc/components/chat/view/subcomponents/MessageComponent.tsxsrc/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsxsrc/components/llm-logo-provider/QoderLogo.tsxsrc/components/llm-logo-provider/SessionProviderLogo.tsxsrc/components/mcp/constants.tssrc/components/onboarding/view/subcomponents/AgentConnectionsStep.tsxsrc/components/provider-auth/types.tssrc/components/provider-auth/view/ProviderLoginModal.tsxsrc/components/settings/constants/constants.tssrc/components/settings/hooks/useSettingsController.tssrc/components/settings/types/types.tssrc/components/settings/view/Settings.tsxsrc/components/settings/view/tabs/agents-settings/AgentListItem.tsxsrc/components/settings/view/tabs/agents-settings/AgentsSettingsTab.tsxsrc/components/settings/view/tabs/agents-settings/sections/AgentCategoryContentSection.tsxsrc/components/settings/view/tabs/agents-settings/sections/AgentCategoryTabsSection.tsxsrc/components/settings/view/tabs/agents-settings/sections/AgentSelectorSection.tsxsrc/components/settings/view/tabs/agents-settings/sections/content/AccountContent.tsxsrc/components/settings/view/tabs/agents-settings/sections/content/PermissionsContent.tsxsrc/components/settings/view/tabs/agents-settings/types.tssrc/components/sidebar/view/subcomponents/SidebarSessionItem.tsxsrc/components/skills/view/ProviderSkills.tsxsrc/i18n/locales/en/chat.jsonsrc/i18n/locales/en/settings.jsonsrc/i18n/locales/zh-CN/chat.jsonsrc/i18n/locales/zh-CN/settings.jsonsrc/i18n/locales/zh-TW/chat.jsonsrc/i18n/locales/zh-TW/settings.jsonsrc/types/app.ts
- abort: resolve instead of reject when process.aborted to prevent false "not installed" error messages on user-initiated session stops - sessions: split filesAttached into imagesAttached + filesAttached so image and file attachment suppression are tracked independently - utils: guard token aggregation with a safeCount helper that rejects NaN and negative values to prevent corrupted cumulative totals - utils: normalize Windows backslashes before encodeQoderCwd so drive letters are encoded correctly on Windows paths - auth: convert checkInstalled to async spawn to avoid blocking the event loop for up to 5 s on each provider status check - mcp/constants: restrict qoder to stdio transport only to prevent bearer tokens from being written to plaintext HTTP configs
- Skip failure notification on user-aborted runs - Use provider-specific sharedSkills i18n key instead of hardcoded defaultValue - Add mcpServers.scope.project labels to en/zh-CN locales - Align models resolver to look up by app session id and verify stored path exists - Merge token usage accumulation into the fetchHistory normalization pass - Add image attachment coverage to qoder-args tests
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
server/modules/providers/tests/qoder-args.test.ts (1)
138-147: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCover empty values explicitly.
This test passes
undefinedforcommandandfiles. It does not covercommand: ''orfiles: [], although the test name describes empty inputs. Add those cases to verify that empty values also produce no separator and no prompt argument.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/modules/providers/tests/qoder-args.test.ts` around lines 138 - 147, Expand the buildQoderArgs empty-input test to cover command: '' and files: [] in addition to undefined values. Verify each empty-value case produces no '--' separator and leaves '/home/user/project' as the final argument, preserving the existing expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/i18n/locales/es/settings.json`:
- Line 8: Update the sharedSkills label in the Spanish locale from the English
text to a Spanish translation such as “Habilidades compartidas”.
---
Nitpick comments:
In `@server/modules/providers/tests/qoder-args.test.ts`:
- Around line 138-147: Expand the buildQoderArgs empty-input test to cover
command: '' and files: [] in addition to undefined values. Verify each
empty-value case produces no '--' separator and leaves '/home/user/project' as
the final argument, preserving the existing expectations.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0074057a-904d-4aaa-8388-871049fba222
📒 Files selected for processing (9)
server/modules/providers/list/qoder/qoder-models.provider.tsserver/modules/providers/list/qoder/qoder-runtime.provider.jsserver/modules/providers/list/qoder/qoder-sessions.provider.tsserver/modules/providers/tests/qoder-args.test.tssrc/components/settings/view/tabs/agents-settings/sections/AgentCategoryTabsSection.tsxsrc/i18n/locales/en/settings.jsonsrc/i18n/locales/es/settings.jsonsrc/i18n/locales/ko/settings.jsonsrc/i18n/locales/zh-CN/settings.json
🚧 Files skipped from review as they are similar to previous changes (6)
- src/components/settings/view/tabs/agents-settings/sections/AgentCategoryTabsSection.tsx
- server/modules/providers/list/qoder/qoder-sessions.provider.ts
- server/modules/providers/list/qoder/qoder-models.provider.ts
- src/i18n/locales/en/settings.json
- src/i18n/locales/zh-CN/settings.json
- server/modules/providers/list/qoder/qoder-runtime.provider.js
| "permissions": "Permisos", | ||
| "mcpServers": "Servidores MCP", | ||
| "skills": "Skills", | ||
| "sharedSkills": "Shared Skills", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Translate the sharedSkills label in the Spanish locale.
Line 8 keeps the English text "Shared Skills". Use a Spanish label, such as "Habilidades compartidas", so the shared-skills tab is localized.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/i18n/locales/es/settings.json` at line 8, Update the sharedSkills label
in the Spanish locale from the English text to a Spanish translation such as
“Habilidades compartidas”.
|
Hi @viper151, This PR is ready for review. It adds first-class Qoder support to ClaudeCodeUI by integrating the local What this branch adds (vs.
|
Summary
This PR adds a first-class
qoderprovider that drives chat sessions through the localqoderclibinary, bringing the Qoder CLI into the same provider model as Claude, Codex, Cursor, and OpenCode. It implements all seven provider facets (runtime,models,auth,mcp,skills,sessions,sessionSynchronizer) and wires the provider into the chat composer, settings, permissions panel, and session synchronization pipeline.What changed
Backend provider implementation (
server/modules/providers/list/qoder/)qoder.provider.ts— new wrapper registering all Qoder facets withAbstractProvider.qoder-runtime.provider.js— executesqodercliwith deterministic argv ordering, safe cwd encoding viaencodeQoderCwd, session id validation, and support for image attachments in both runtime messages and session history.qoder-models.provider.ts— lists available Qoder models fromqodercli --list-models, with stable row parsing by shape, memoized catalog, and fallback metadata.qoder-auth.provider.ts— reports install/auth status and exposes token-based login for the provider.qoder-mcp.provider.ts— reads/writes Qoder-native MCP server config from~/.qoder/settings.json(user scope) and.mcp.json(project scope), supportingstdioandhttptransports.qoder-skills.provider.ts— discoversSKILL.mdfiles from Qoder, Claude, and Agents skill roots, emitting/skill-namecommands.qoder-sessions.provider.ts— normalizes live runtime events and fetches session history with unique message ids, correct pagination, and title priority handling.qoder-session-synchronizer.provider.ts— scans~/.qoder/projects/**/*.jsonl, encodes working directories safely, and upserts sessions intosessionsDbwith title recovery fromai-titlerows or the first user prompt.Shared infrastructure
server/shared/utils.tsfor transcript paths, jsonl handling, cwd encoding, and file timestamps.provider-capabilities.service.tsandprovider-token-usage.service.tsto recognize Qoder-specific capabilities and usage shapes.sessions-watcher.service.tsandsession-synchronizer.service.tsto include Qoder in the sync/watcher pipeline.Module wiring
qoderinprovider.registry.tsandprovider.routes.ts.server/shared/types.tsLLMProviderandsrc/types/app.tsLLMProvider.server/index.tsfor runtime lifecycle wiring.server/modules/agent/agent.routes.tsso agents can launch Qoder sessions.server/modules/commands/commands.routes.tsandwebsocket/services/shell-websocket.service.tswhere provider parsing needed extension.public/api-docs.htmlPROVIDER_ORDER.Frontend
src/assets/qoder.png) andQoderLogocomponent.ProviderSelectionEmptyState,SessionProviderLogo, and chat provider state hooks to include Qoder.ProviderLoginModalfor Qoder auth/setup flow.PermissionsContentand wired tools settings to the CLI.src/components/mcp/constants.ts.useSettingsControllerandAgentConnectionsStepto surface Qoder MCP/skill configuration.Tests
server/modules/providers/tests/qoder-args.test.tsserver/modules/providers/tests/qoder-models.test.tsserver/modules/providers/tests/qoder-permissions.test.tsserver/modules/providers/tests/qoder-sessions.test.tsserver/modules/providers/tests/qoder-session-synchronizer.test.tsAlso updated existing tests that needed Qoder-specific stubs or provider-order assertions.
Why
Qodercli is a standalone CLI product with its own session store, model catalog, MCP config, and skill layout. Treating it as a native provider lets users pick Qoder in the chat composer, reuse the existing session sidebar/sync infrastructure, and manage Qoder-specific permissions and MCP servers from the same settings UI as the other providers.
Screenshots
Test plan
npx tsc --noEmit -p server/tsconfig.jsonnpx eslint server/modules/providers/list/qoder/**/*.ts server/shared/types.ts server/shared/utils.tsnpx tsx --tsconfig server/tsconfig.json --test server/modules/providers/tests/qoder-args.test.tsnpx tsx --tsconfig server/tsconfig.json --test server/modules/providers/tests/qoder-models.test.tsnpx tsx --tsconfig server/tsconfig.json --test server/modules/providers/tests/qoder-permissions.test.tsnpx tsx --tsconfig server/tsconfig.json --test server/modules/providers/tests/qoder-sessions.test.tsnpx tsx --tsconfig server/tsconfig.json --test server/modules/providers/tests/qoder-session-synchronizer.test.tsqodercli~/.qoder/projectsNotes for reviewers
.jsfile to match the existing provider runtime pattern (Claude/Codex/Cursor/OpenCode runtimes are also.js)./with-so Qoder transcript paths remain safe on disk; this is shared with the session synchronizer viaencodeQoderCwd.qoderclibehavior rather than reusing the generic capability list.Summary by CodeRabbit