Compare commits

..

33 Commits

Author SHA1 Message Date
Robin Newhouse 3d0f624f84 chore: add changeset for code-simplifier skill 2026-01-22 16:06:55 -08:00
Robin Newhouse e68d55a402 feat(skills): add code-simplifier skill definition
Introduces a new skill definition for the `code-simplifier` in the `.cline/skills` directory. This skill provides a framework for refining code clarity, consistency, and maintainability while strictly preserving functionality and adhering to project-specific standards.
2026-01-22 16:04:27 -08:00
Bee c093ca1760 refactor: replace console with Logger service (#8741)
* chore: add grit rule to enforce Logger service over console calls

Add a new Grit linting rule that detects direct console method usage
(log, debug, error, warn, info) and prompts developers to use the
Logger service instead for consistent logging practices.

The rule is configured in biome.jsonc to apply to most source files
while excluding test files, webview-ui, evals, standalone, e2e tests,
and scripts where direct console usage may be acceptable.

* support variadic args

* wip: migrate console to Logger

* migrate rest of console logger

* Switch to Logger

* Migrations

* shared

* use shared

* revert format change

* Update tests to stub Logger instead of console

* verbose in dev mode
2026-01-22 13:16:37 -08:00
Seb Duerr dc85299a73 Remove deprecated zai-glm-4.6 model from Cerebras provider (#8717) 2026-01-22 12:58:07 -08:00
ClineXDiego 4533ed3ea8 fix: support drag & drop files from SSH remote workspaces (#8804)
Add vscode-remote: scheme to the valid URI filter for drag & drop operations.
This allows files from SSH Remote workspaces to be dropped into the chat.

Fixes #7606
2026-01-22 12:37:11 -08:00
Max 66d7664d1b improve cline command permission parsing logic (#8544)
- cline command permission flag can now parse subshells correctly and
validate that subshells don't contain disallowed commands.

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-01-22 11:11:53 -08:00
dependabot[bot] 50aeb7098f chore(deps): bump undici (#8783)
Bumps [undici](https://github.com/nodejs/undici) to 6.23.0 and updates ancestor dependency . These dependencies need to be updated together.


Updates `undici` from 6.22.0 to 6.23.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.22.0...v6.23.0)

Updates `undici` from 7.16.0 to 7.19.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.22.0...v6.23.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 6.23.0
  dependency-type: indirect
- dependency-name: undici
  dependency-version: 7.19.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: ClineXDiego <diego@cline.bot>
2026-01-22 10:50:49 -08:00
github-actions[bot] c2e6eafb45 v3.52.0 Release Notes (#8633)
- Users with ChatGPT Plus or Pro subscriptions can now use GPT-5 models directly through Cline without needing an API key. Authentication is handled via OAuth through OpenAI's authentication system.
- Grok models are now moving out of free tier and into paid plans.
- Introduces comprehensive Jupyter Notebook support for Cline, enabling AI-assisted editing of `.ipynb` files with full cell-level context awareness.

- Bugs in DiffViewProvider for file editing
- Ollama's recommended models to use correct identifiers

Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2026-01-22 10:48:32 -08:00
Juan Pablo Flores 0220628c39 Adds Open AI Codex docs (#8791)
* feat(docs): add OpenAI Codex provider setup instructions and update model selection guidance

* fix(docs): improve clarity and formatting in OpenAI Codex documentation

* Update docs/provider-config/openai-codex.mdx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update docs/provider-config/openai-codex.mdx

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Tony Loehr <turingxo@gmail.com>
2026-01-22 09:30:12 -08:00
CandiedUniverse 5052220195 feat(hooks): Make hooks always enabled and remove its feature setting. [CLINE-1179] (#8777)
* feat(hooks): Standardize on calling getHooksEnabledSafe().

* feat(hooks): Hard-code getHookEnabledSafe() to return true unless on Windows.

* feat(hooks): Remove hooks setting from the CLI.

* feat(hooks): Remove hooks toggle from the Feature Settings UI.

* feat(hooks): Remove hooksEnabled toggles from settings/task APIs.

* feat(hooks): Stop using hooksEnabled setting.

* feat(hooks): npm run changeset

* feat(hooks): Simplify getHooksEnabledSafe() function signature.

* feat(hooks): Remove hooks setting migration.

feat(hooks): Remove hooksEnabled from updateSettingsCli() conversion.

feat(hooks): Use 'reserved' for removed fields in UpdateSettingsRequest protobuf.
2026-01-22 09:26:16 -08:00
CandiedUniverse abf3081e56 Rules: Wire up conditional rules functionality [ENG-1470] (#8669)
* feat(rules): Write technical design / implementation plan doc.

* update frontmatter plan

* feat(rules): Initial implementation based on plan doc.

* feat(rules): Add tool-call path harvesting for path-scoped Cline Rules.

* chore(rules): exclude internal paths-frontmatter plan doc from PR

* fix(rules): use latest user message for paths frontmatter context

* feat(rules): Implement conditional_rules_applied say type.

* feat(rules): changes as per Cline's code review feedback

* feat(rules): npm run changeset

* feat(rules): Changes as per ellipsis-dev feedback.

* feat(rules): Changes as per code review feedback (i.e. don't bloat the task context).

* feat(rules): Fix failing unit tests.
2026-01-22 09:25:59 -08:00
Michael Gutin 125cb78a31 Switch background element now has a 3:1 contrast ration with thumb and rule row background in vs code light and dark themes (#8747) 2026-01-22 12:16:51 -05:00
CandiedUniverse 17539228ea feat(cli): Add note about ctrl+c to exit the CLI. [CLINE-1162] (#8796)
* feat(cli): Add note about ctrl+c to exit the CLI.

* feat(cli): npm run changeset
2026-01-22 09:01:57 -08:00
Ara bdb7cc36fa feat(DiffEditRow): add button to open file in editor (#8564)
* refactor(diff): return result object with line tracking metadata

Change constructNewFileContent to return an object containing newContent
and line number information instead of just the string content. Add
charIndexToLineNumber helper function to support tracking where changes
occur in the file.

Update all callers and tests to access the newContent property from
the result object.

* minor fix

* fix: hide line numbers when not available from backend

* chore: add changeset

* feat: add startLineNumbers support to ApplyPatchHandler

* fix: split V4A @@ chunks into separate Patch objects for proper line numbers

* fix(DiffEditRow): preserve +/- prefix in diff line display for backwards compatibility

* fix line numbers
2026-01-21 21:38:45 -08:00
Tony Loehr d3e1065707 added jupyter docs (#8742)
* added jupyter docs

* fix jupyter docs and add gifs

* fix jupyter docs 2

* fix jupyter gif placement
2026-01-21 16:15:26 -08:00
Tomás Barreiro 6303951f20 Use getAllFlagsAndPayloads when fetching PostHog feature flags (#8774)
* Use

* Fix typo

* Remove old code
2026-01-21 13:12:34 -08:00
Ara fc184e07a2 feat: update free onboarding models with Kat Coder Pro and Devstral (#8773)
* feat: update free onboarding models with Kat Coder Pro and Devstral

- Replace xAI Grok Code Fast 1 with KwaiKAT Kat Coder Pro as primary free model
- Add Mistral Devstral 2512 as additional free model option
- Update model specifications (context window, image/cache support)

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-21 12:22:10 -08:00
Ara e88f4ea412 fix: update package name from cline to claude-dev in changesets (#8772)
Rename package identifier in changeset frontmatter to reflect
the correct package name for version tracking.
2026-01-21 11:35:54 -08:00
Saoud Rizwan 133ade3f1a feat(prompt): add stderr redirect guidance for command execution (#8765)
* feat(prompt): add stderr redirect guidance for command execution

* test: update system prompt snapshots
2026-01-21 11:09:18 -08:00
Max 857411c035 fix initial prompt bugs (#8428)
* fix initial prompt bugs

- going straight to act wasn't working in interactive mode.
- slash command autocomplete wasn't working in the initial prompt
- refactored some naming to be more clear

* Apply suggestion from @ellipsis-dev[bot]

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* Apply suggestion from @ellipsis-dev[bot]

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* Apply suggestion from @ellipsis-dev[bot]

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
Co-authored-by: CandiedUniverse <132302818+candieduniverse@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2026-01-21 09:40:08 -08:00
Robin Newhouse c59f068584 fix: improve Jupyter notebook diff view and LLM context handling (#8759)
* fix: restore switchToSpecializedEditor for Jupyter notebook diff views

This restores the notebook diff view functionality that was accidentally
removed during rebase. The method was incorrectly identified as dead code,
but it was being called in update() when isFinal is true.

Restored functionality:
- Abstract method definition in DiffViewProvider
- Call to switchToSpecializedEditor() in update() after final content
- Full implementation in VscodeDiffViewProvider for notebook diff views
  - Temporary file management for modified content
  - File system watcher for synchronization
  - Proper cleanup in resetDiffView()
- No-op implementations in ExternalDiffViewProvider and FileEditProvider
- Test stub in DiffViewProvider.test.ts

* fix: open notebooks in Jupyter editor after save, strip outputs for LLM

- Override showFile in VscodeDiffViewProvider to open .ipynb files
  with the Jupyter notebook editor instead of leaving stale diff view
- Remove notebook check that was skipping showFile in base class
- Add getOriginalContentForLLM() to return sanitized notebook content
- Strip notebook outputs from finalContent to reduce LLM context size

fix: sanitize notebook content in write responses to prevent context explosion

Previously, after editing a notebook, the full file content (including all
base64-encoded images and HTML table outputs) was sent back to the LLM in
the <final_file_content> response. This caused context to explode to ~200K
tokens for simple edits on notebooks with rendered outputs.

Changes:
- Add stripAllOutputs option to sanitizeNotebookForLLM()
- Apply sanitization in DiffViewProvider.saveChanges() for finalContent
- Add getOriginalContentForLLM() for diff error responses
- Strip all outputs (not just images) in write paths since outputs
  aren't needed for editing - they regenerate when cells run

Results: 95% reduction in context usage for notebook write responses
(196KB → 9KB in testing).

* chore: add changeset for Jupyter notebook diff view fix

* fix: show error message when Jupyter extension is missing for notebook diffs

* refactor: move os require to top-level import

---------

Co-authored-by: Max <maxpaulus43@gmail.com>
2026-01-21 08:07:03 -08:00
Bee f7561c31fa fix: prevent race condition in telemetry service initialization (#8764) 2026-01-21 07:23:21 -08:00
Jose Castelli f0a97dafc8 fix: fixing testing framework and removing old integration tests [PF-413] (#8727)
fix: fixing testing framework and removing old integration tests [PF-413] #8727
2026-01-21 14:38:03 +01:00
Saoud Rizwan d422f689a6 docs(storage): document StateManager multi-instance behavior
Adds a detailed comment explaining that each VS Code window has its own
StateManager cache, which is why settings like plan/act mode don't sync
between running instances. The cache is populated from disk only during
initialize() and never re-read, providing natural isolation.
2026-01-20 20:50:55 -08:00
Robin Newhouse 00efcc1c2b fix: prevent duplicate diff errors when parallel tool calling is enabled (#8763)
* fix: prevent duplicate diff errors when parallel tool calling is enabled

When a `replace_in_file` diff fails during streaming with parallel tool
calling enabled (GPT-5/Codex models), the same error message was being
added to userMessageContent on each streaming chunk, causing hundreds of
duplicates and context window overflow.

Root cause: The duplicate prevention added in 09276ebf4 used the
`didAlreadyUseTool` flag, but this flag is intentionally not set when
parallel tool calling is enabled (per 00e9d6f52). This was because
`didAlreadyUseTool` is designed to block subsequent tools, which is the
opposite of what parallel tool calling needs.

The fix adds per-call_id tracking via `diffErrorPushedForCallIds` Set:
- Track which specific tool calls have already had their error pushed
- Works for both parallel and non-parallel tool calling
- Different parallel tool calls can each report their own errors
- Same call_id only pushes error once, regardless of streaming chunks

This is compatible with the parallel tool calling design because GPT-5
models (which auto-enable parallel tool calling) use native tool calling
and always have a call_id. The mechanism is separate from didAlreadyUseTool
which controls flow (blocking tools) vs this which prevents duplicates.

Related commits:
- 09276ebf4: fix: prevent duplicate error messages during streamed edit
  tool failures (only worked when parallel tool calling disabled)
- 00e9d6f52: feat: add experimental parallel tool calling support
  (deliberately excluded didAlreadyUseTool from parallel mode)

* test: add unit tests for diffErrorPushedForCallIds duplicate prevention

Tests cover:
- Basic Set behavior (initialization, tracking, clearing)
- Duplicate prevention logic for parallel tool calling
- Edge cases (empty/undefined call_id, rapid streaming chunks)
- Reset behavior between API requests

* Add changeset

* refactor: rename diffErrorPushedForCallIds to errorPushedForCallIds

Generalizes the tracking mechanism per reviewer feedback from @abeatrix.

The more generic name allows the same pattern to be reused for other
tool handlers that may need duplicate error prevention in the future,
not just diff-related errors.

No functional changes - just renaming.
2026-01-20 20:48:29 -08:00
Robin Newhouse 59251e9de6 fix: ensure document finalization in approval flow (#8757)
When file operations use the approval flow (isFinal=false), the document
content was not being properly finalized before user approval. This caused
content duplication when shortening files - old content at the end was
preserved instead of being replaced.

Root cause: FileProviderOperations passed isFinal=false to
DiffViewProvider.update(), which:
1. Popped the last line (treated as "incomplete" for streaming)
2. Limited the replacement range to currentLine + 1
3. Skipped truncation of trailing content

Fix: Always pass isFinal=true to update() since the content IS complete.
The isFinal parameter in FileProviderOperations now only controls whether
to save after the update, not the update behavior itself.

This follows the philosophy of dd35448a9 by fixing at the source rather
than adding cleanup logic.
2026-01-20 15:52:12 -08:00
Saoud Rizwan 2df952f571 fix: OpenAI Codex provider improvements based on OpenAI feedback (#8754)
* style: improve OAuth success page design

* fix: hide thinking budget slider for OpenAI Codex provider

OpenAI Codex models use discrete reasoning effort levels (low/medium/high)
controlled via the global OpenAI Reasoning Effort setting, not token-based
thinking budgets like Anthropic models.

* fix: hide thinking toggle with display:none instead of disabled state

* fix: hide cost display for OpenAI Codex provider

Subscription-based provider has no per-token costs, so showing $0.00 is misleading.
2026-01-20 15:31:39 -08:00
Bee 6e7dc55781 fix: disable delete button for favorited history item (#8753)
* fix: disable delete button for favorited history item

- Add useMemo hook to memoize favorite state calculation, improving performance by avoiding repeated computations of `pendingFavoriteToggles[item.id] ?? item.isFavorited`
- Disable delete button for favorited items but keep delete button for standardized UI display
- Replace multiple inline favorite state checks with centralized `isFavoritedItem` variable for better code maintainability
- Simplify favorite toggle logic by using memoized value

This change ensures favorited items cannot be deleted and reduces unnecessary re-renders when favorite state is accessed.

* isFavoritedItem
2026-01-20 13:28:42 -08:00
Tomás Barreiro ec879fc549 Do not update the providers if the currently configured one is valid (#8751) 2026-01-20 21:49:39 +01:00
Tomás Barreiro 75b050de5c Init Sync Worker when applying remote config (#8749) 2026-01-20 19:52:16 +01:00
Thanh Nguyen 0c4acd9457 docs: fix outdated Ollama model names in documentation (#8551)
* docs: fix outdated Ollama model names in documentation

Fixes #7918

- Updated qwen3-coder-30b to qwen2.5-coder:32b (correct identifier)
- Replaced devstral-small with codellama:34b-code (existing model)
- Changed ollama run to ollama pull for initial download

* chore: add changeset for Ollama model names fix
2026-01-20 10:28:30 -08:00
dependabot[bot] 0dc3d7084d chore(deps): bump qs and express (#8360)
Bumps [qs](https://github.com/ljharb/qs) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `qs` from 6.13.0 to 6.14.1
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.13.0...v6.14.1)

Updates `express` from 5.0.1 to 5.2.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/v5.0.1...v5.2.1)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.1
  dependency-type: indirect
- dependency-name: express
  dependency-version: 5.2.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-20 09:49:02 -08:00
Tomás Barreiro 3cfafe6583 Clean old Feature Flags (#8748) 2026-01-20 09:47:07 -08:00
518 changed files with 4869 additions and 9695 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Add claude 4.5 haiku
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
improve cline command permission validation logic. add cline command permission man page documentation
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add code-simplifier skill for refining code clarity and maintainability
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Expose --version in cline cli command
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Log Persistence errors to PostHog
-13
View File
@@ -1,13 +0,0 @@
---
"claude-dev": patch
---
feat: add OpenAI Codex (ChatGPT Plus/Pro) provider
Users with ChatGPT Plus or Pro subscriptions can now use GPT-5 models directly through Cline without needing an API key. Authentication is handled via OAuth through OpenAI's authentication system.
Models available:
- gpt-5.2-codex (default)
- gpt-5.1-codex-max
- gpt-5.1-codex-mini
- gpt-5.2
@@ -1,9 +0,0 @@
---
"claude-dev": patch
---
Fix two bugs in DiffViewProvider file editing:
1. **Line boundary validation**: Add `safelyTruncateDocument()` to prevent out-of-bounds line errors on JetBrains hosts (fixes #8423, #8429). The gRPC protocol strictly validates line numbers, causing "truncateDocument INTERNAL: Wrong line" errors when `truncateDocument()` was called with a line number >= document line count.
2. **Content concatenation on final update**: When replacing content without a trailing newline, the old content at line N+1 was concatenated to the new content. Fixed by extending the replacement range to cover the entire document on final update.
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
OpenAI GPT-5 Codex models are now using Apply Patch tool for diff edits.
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix issue where tool call ids are invalid when switching between models using the chat completion format and the responses api format.
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
fix: add chat output on skill use
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Adding telemetry for background exec terminal
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
This pull request introduces comprehensive Jupyter Notebook support for Cline, enabling AI-assisted editing of `.ipynb` files with full cell-level context awareness. The feature allows users to seamlessly work with Jupyter notebooks using Cline's AI capabilities while preserving the notebook's JSON structure.
+5
View File
@@ -0,0 +1,5 @@
---
"cline": patch
---
Remove deprecated zai-glm-4.6 model from Cerebras provider
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Wiring-up "conditionals" for Cline Rules files.
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
fix: removes retry message from UI after retry succeeds
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Support native tool calling for LM Studio and Ollama provider
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
add claude 4.5 opus into sap provider.
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Fix drag & drop files from SSH remote workspaces into chat
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Limite Vertex and LiteLLM options when they're remote configured
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix crash when the Context Menu has a type but no options
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Updated switch component to adhere to 3:1 contrast accessibility standard for both light and dark vscode themes
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Throttle the remote config fetch
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Improve history view filter menu
-5
View File
@@ -1,5 +0,0 @@
---
"cline": patch
---
Add git worktree management UI for running parallel Cline sessions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Remove hooks feature setting and make it always enabled (except on Windows).
+48
View File
@@ -0,0 +1,48 @@
---
name: code-simplifier
description: Simplify and refine code for clarity, consistency, and maintainability while preserving all functionality. Focuses on recently modified code unless instructed otherwise.
---
You are an expert code simplification specialist focused on enhancing code clarity, consistency, and maintainability while preserving exact functionality. Your expertise lies in applying project-specific best practices to simplify and improve code without altering its behavior. You prioritize readable, explicit code over overly compact solutions. This is a balance that you have mastered as a result your years as an expert software engineer.
You will analyze recently modified code and apply refinements that:
1. **Preserve Functionality**: Never change what the code does - only how it does it. All original features, outputs, and behaviors must remain intact.
2. **Apply Project Standards**: Follow the established coding standards from the project's configuration files (e.g., `.clinerules/`, `.editorconfig`, linter configs, README, CONTRIBUTING) including patterns like:
- Consistent import/include ordering and organization
- Idiomatic function and method declaration style for the language
- Explicit type annotations where the language supports them
- Framework-appropriate patterns and conventions
- Language-idiomatic error handling
- Consistent naming conventions (camelCase, snake_case, etc.)
3. **Enhance Clarity**: Simplify code structure by:
- Reducing unnecessary complexity and nesting
- Eliminating redundant code and abstractions
- Improving readability through clear variable and function names
- Consolidating related logic
- Removing unnecessary comments that describe obvious code
- IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions
- Choose clarity over brevity - explicit code is often better than overly compact code
4. **Maintain Balance**: Avoid over-simplification that could:
- Reduce code clarity or maintainability
- Create overly clever solutions that are hard to understand
- Combine too many concerns into single functions or components
- Remove helpful abstractions that improve code organization
- Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners)
- Make the code harder to debug or extend
5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.
Your refinement process:
1. Identify the recently modified code sections
2. Analyze for opportunities to improve elegance and consistency
3. Apply project-specific best practices and coding standards
4. Ensure all functionality remains unchanged
5. Verify the refined code is simpler and more maintainable
6. Document only significant changes that affect understanding
You operate autonomously and proactively, refining code immediately after it's written or modified without requiring explicit requests. Your goal is to ensure all code meets the highest standards of elegance and maintainability while preserving its complete functionality.
+3 -12
View File
@@ -187,20 +187,11 @@ jobs:
if: steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm ci
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache-dependency-path: cli/go.sum
- name: Build CLI binaries
run: npm run compile-cli-all-platforms
- name: Download ripgrep binaries
run: npm run download-ripgrep
- name: Compile NPM package
run: npm run compile-standalone-npm
- name: Compile Standalone
run: npm run compile-standalone
- name: Install testing platform dependencies
if: steps.testing-platform-cache.outputs.cache-hit != 'true'
@@ -213,7 +204,7 @@ jobs:
# This prevents the job from showing as failed and avoids distracting developers
# until the integration tests are ready to be enforced.
run: |
npm run test:tp-orchestrator -- tests/specs/ --count=1 --coverage || true
npm run test:tp-orchestrator -- tests/specs/ --count=1 --coverage
- name: Save Coverage Reports
uses: actions/upload-artifact@v4
+15 -2
View File
@@ -1,10 +1,23 @@
# Changelog
## [3.52.0]
### Added
- Users with ChatGPT Plus or Pro subscriptions can now use GPT-5 models directly through Cline without needing an API key. Authentication is handled via OAuth through OpenAI's authentication system.
- Grok models are now moving out of free tier and into paid plans.
- Introduces comprehensive Jupyter Notebook support for Cline, enabling AI-assisted editing of `.ipynb` files with full cell-level context awareness.
### Fixed
- Bugs in DiffViewProvider for file editing
- Ollama's recommended models to use correct identifiers
## [3.51.0]
### Added
- Adding OpenAI gpt-5.2-codex model to the model picker
- Adding OpenAI gpt-5.2-codex model to the model picker
## [3.50.0]
@@ -1737,4 +1750,4 @@ Add Opus 4.1 through Claude Code
## [0.0.6]
- Initial release
- Initial release
+23
View File
@@ -147,6 +147,29 @@
"src/dev/grit/vscode-api.grit"
]
},
{
// Do not use console logging directly, use the Logger service instead.
"plugins": [
"src/dev/grit/console-log.grit"
],
"includes": [
"**",
"!**/webview-ui/**",
"!**/evals/**",
"!**/standalone/**",
"!**/e2e/**",
"!**/test/**",
"!**/__tests__/**",
"!**/*.test.ts",
"!**/*.stories.ts",
"!src/dev/**",
"!**/*.mjs",
"!**/*.js",
"!**/scripts/**",
"!**/*.tsx",
"!**/testing-platform/**"
]
},
{
"includes": [
"**",
+76 -87
View File
@@ -10,11 +10,12 @@ import (
"strings"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"github.com/cline/cli/pkg/cli"
"github.com/cline/cli/pkg/cli/auth"
"github.com/cline/cli/pkg/cli/display"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/output"
"github.com/cline/cli/pkg/cli/slash"
"github.com/cline/cli/pkg/common"
"github.com/cline/grpc-go/cline"
"github.com/spf13/cobra"
@@ -71,8 +72,6 @@ see the manual page: man cline`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
var instanceAddress string
// Validate workspace paths exist
if err := common.ValidateDirsExist(workspaces); err != nil {
return err
@@ -89,13 +88,13 @@ see the manual page: man cline`,
if global.Config.Verbose {
fmt.Println("Starting new Cline instance...")
}
instance, err := global.Clients.StartNewInstance(ctx, allWorkspaces...)
instance, err := global.Instances.StartNewInstance(ctx, allWorkspaces...)
if err != nil {
return fmt.Errorf("failed to start new instance: %w", err)
}
instanceAddress = instance.Address
global.Config.CoreAddress = instance.CoreAddress
if global.Config.Verbose {
fmt.Printf("Started instance at %s\n\n", instanceAddress)
fmt.Printf("Started instance at %s\n\n", global.Config.CoreAddress)
}
// Set up cleanup on exit
@@ -103,38 +102,35 @@ see the manual page: man cline`,
if global.Config.Verbose {
fmt.Println("\nCleaning up instance...")
}
registry := global.Clients.GetRegistry()
if err := global.KillInstanceByAddress(context.Background(), registry, instanceAddress); err != nil {
registry := global.Instances.GetRegistry()
if err := global.KillInstanceByAddress(context.Background(), registry, global.Config.CoreAddress); err != nil {
if global.Config.Verbose {
fmt.Printf("Warning: Failed to clean up instance: %v\n", err)
}
}
}()
}
// Check if user has credentials configured
if !isUserReadyToUse(ctx, instanceAddress) {
// Create renderer for welcome messages
renderer := display.NewRenderer(global.Config.OutputFormat)
fmt.Printf("\n%s\n\n", renderer.Dim("Hey there! Looks like you're new here. Let's get you set up"))
// Check if user has credentials configured
if !isUserReadyToUse(ctx) {
// Create renderer for welcome messages
renderer := display.NewRenderer(global.Config.OutputFormat)
fmt.Printf("\n%s\n\n", renderer.Dim("Hey there! Looks like you're new here. Let's get you set up"))
if err := auth.HandleAuthMenuNoArgs(ctx); err != nil {
// Check if user cancelled - exit cleanly
if err == huh.ErrUserAborted {
return nil
}
return fmt.Errorf("auth setup failed: %w", err)
if err := auth.HandleAuthMenuNoArgs(ctx); err != nil {
// Check if user cancelled - exit cleanly
if err == huh.ErrUserAborted {
return nil
}
// Re-check after auth wizard
if !isUserReadyToUse(ctx, instanceAddress) {
return fmt.Errorf("credentials still not configured - please run 'cline auth' to complete setup")
}
fmt.Printf("\n%s\n\n", renderer.Dim("✓ Setup complete, you can now use the Cline CLI"))
return fmt.Errorf("auth setup failed: %w", err)
}
} else {
// User specified --address flag, use that
instanceAddress = coreAddress
// Re-check after auth wizard
if !isUserReadyToUse(ctx) {
return fmt.Errorf("credentials still not configured - please run 'cline auth' to complete setup")
}
fmt.Printf("\n%s\n\n", renderer.Dim("✓ Setup complete, you can now use the Cline CLI"))
}
// Get content from both args and stdin
@@ -143,10 +139,13 @@ see the manual page: man cline`,
return fmt.Errorf("failed to read prompt: %w", err)
}
// If no prompt from args or stdin, show interactive input
if prompt == "" {
// If no prompt (or just a mode switch with no message), show interactive input
// Loop to allow mode switches without a message
bannerShown := false
for prompt == "" {
// Pass the mode flag and workspaces to banner so it shows correct info
prompt, err = promptForInitialTask(ctx, instanceAddress, mode, allWorkspaces)
prompt, err = promptForInitialTask(ctx, mode, allWorkspaces, !bannerShown)
bannerShown = true
if err != nil {
// Check if user cancelled - exit cleanly without error
if err == huh.ErrUserAborted {
@@ -154,6 +153,23 @@ see the manual page: man cline`,
}
return err
}
// Check if user entered a mode switch command
if newMode, remaining, isModeSwitch := slash.ParseModeSwitch(prompt); isModeSwitch {
mode = newMode
prompt = remaining
// If just a mode switch with no message, continue loop to re-prompt
if prompt == "" {
renderer := display.NewRenderer(global.Config.OutputFormat)
if mode == "act" {
fmt.Printf("\n%s\n\n", renderer.Success("Switched to act mode"))
} else {
fmt.Printf("\n%s\n\n", renderer.Success("Switched to plan mode"))
}
continue
}
}
if prompt == "" {
return fmt.Errorf("prompt required")
}
@@ -171,7 +187,7 @@ see the manual page: man cline`,
Mode: mode,
Settings: settings,
Yolo: yolo,
Address: instanceAddress,
Address: global.Config.CoreAddress,
Verbose: verbose,
Workspaces: allWorkspaces,
})
@@ -207,51 +223,30 @@ see the manual page: man cline`,
}
}
func promptForInitialTask(ctx context.Context, instanceAddress, modeFlag string, workspaces []string) (string, error) {
// Show session banner before the initial input
showSessionBanner(ctx, instanceAddress, modeFlag, workspaces)
var prompt string
// Create custom theme with mode-colored cursor and title
theme := huh.ThemeCharm()
// Set cursor and title color based on mode
modeColor := lipgloss.Color("3") // Yellow for plan
if modeFlag == "act" {
modeColor = lipgloss.Color("39") // Blue for act
func promptForInitialTask(ctx context.Context, modeFlag string, workspaces []string, showBanner bool) (string, error) {
// Show session banner before the initial input (only on first prompt)
if showBanner {
showSessionBanner(ctx, modeFlag, workspaces)
}
theme.Focused.TextInput.Cursor = theme.Focused.TextInput.Cursor.Foreground(modeColor)
theme.Focused.Title = theme.Focused.Title.Foreground(modeColor)
form := huh.NewForm(
huh.NewGroup(
huh.NewText().
Title("Start a new Cline task").
Description("What would you like Cline to help you with?").
Placeholder("e.g., Create a REST API with authentication...").
Lines(5).
Value(&prompt),
),
).WithWidth(48).WithTheme(theme)
err := form.Run()
prompt, err := output.PromptForInitialTask(
"Start a new Cline task",
"/plan or /act to switch modes\ntab to autocomplete commands\nctrl+e to open editor\nctrl+c to exit",
modeFlag,
slash.NewRegistry(ctx),
)
if err != nil {
// Check if user cancelled with Control-C
if err == huh.ErrUserAborted {
// Return a special error that indicates clean cancellation
// This allows deferred cleanup to run
if err == output.ErrUserAborted {
return "", huh.ErrUserAborted
}
return "", err
}
return strings.TrimSpace(prompt), nil
return prompt, nil
}
// showSessionBanner displays session info before initial prompt
func showSessionBanner(ctx context.Context, instanceAddress, modeFlag string, workspaces []string) {
func showSessionBanner(ctx context.Context, modeFlag string, workspaces []string) {
bannerInfo := display.BannerInfo{
Version: global.CliVersion,
Mode: modeFlag, // Use the mode from command flag, not state
@@ -265,21 +260,18 @@ func showSessionBanner(ctx context.Context, instanceAddress, modeFlag string, wo
bannerInfo.Workdirs = workspaces
// Get provider/model using auth functions (same logic as auth menu)
manager, err := cli.NewTaskManagerForAddress(ctx, instanceAddress)
if err == nil {
if providerList, err := auth.GetProviderConfigurations(ctx, manager); err == nil {
// Show provider/model for the mode we'll be using
var providerDisplay *auth.ProviderDisplay
if bannerInfo.Mode == "plan" && providerList.PlanProvider != nil {
providerDisplay = providerList.PlanProvider
} else if bannerInfo.Mode == "act" && providerList.ActProvider != nil {
providerDisplay = providerList.ActProvider
}
if providerList, err := auth.GetProviderConfigurations(ctx); err == nil {
// Show provider/model for the mode we'll be using
var providerDisplay *auth.ProviderDisplay
if bannerInfo.Mode == "plan" && providerList.PlanProvider != nil {
providerDisplay = providerList.PlanProvider
} else if bannerInfo.Mode == "act" && providerList.ActProvider != nil {
providerDisplay = providerList.ActProvider
}
if providerDisplay != nil {
bannerInfo.Provider = auth.GetProviderIDForEnum(providerDisplay.Provider)
bannerInfo.ModelID = providerDisplay.ModelID
}
if providerDisplay != nil {
bannerInfo.Provider = auth.GetProviderIDForEnum(providerDisplay.Provider)
bannerInfo.ModelID = providerDisplay.ModelID
}
}
@@ -292,25 +284,22 @@ func showSessionBanner(ctx context.Context, instanceAddress, modeFlag string, wo
// isUserReadyToUse checks if the user has completed initial setup
// Returns true if welcomeViewCompleted flag is set OR user is authenticated
// Matches extension logic: welcomeViewCompleted = Boolean(globalState.welcomeViewCompleted || user?.uid)
func isUserReadyToUse(ctx context.Context, instanceAddress string) bool {
manager, err := cli.NewTaskManagerForAddress(ctx, instanceAddress)
func isUserReadyToUse(ctx context.Context) bool {
grpcClient, err := global.GetClientForAddress(ctx, global.Config.CoreAddress)
if err != nil {
return false
}
// Get state
state, err := manager.GetClient().State.GetLatestState(ctx, &cline.EmptyRequest{})
state, err := grpcClient.State.GetLatestState(ctx, &cline.EmptyRequest{})
if err != nil {
return false
}
// Parse state JSON
stateMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(state.StateJson), &stateMap); err != nil {
return false
}
// Check 1: welcomeViewCompleted flag
if welcomeCompleted, ok := stateMap["welcomeViewCompleted"].(bool); ok && welcomeCompleted {
return true
}
+10 -10
View File
@@ -23,7 +23,7 @@ func TestMultiInstanceDefaultUnchanged(t *testing.T) {
if len(out1.CoreInstances) != 1 {
t.Fatalf("expected 1 instance, got %d", len(out1.CoreInstances))
}
firstAddr := out1.CoreInstances[0].Address
firstAddr := out1.CoreInstances[0].CoreAddress
waitForAddressHealthy(t, firstAddr, defaultTimeout)
// Start second instance
@@ -56,29 +56,29 @@ func TestDefaultJsonUpdateAfterRemoval(t *testing.T) {
// Choose second as new default
target := out.CoreInstances[1]
waitForAddressHealthy(t, target.Address, defaultTimeout)
waitForAddressHealthy(t, target.CoreAddress, defaultTimeout)
// Set as default
_ = mustRunCLI(ctx, t, "instance", "use", target.Address)
_ = mustRunCLI(ctx, t, "instance", "use", target.CoreAddress)
// Verify default switched
out = listInstancesJSON(ctx, t)
if out.DefaultInstance != target.Address {
t.Fatalf("default_instance not updated to %s (got %s)", target.Address, out.DefaultInstance)
if out.DefaultInstance != target.CoreAddress {
t.Fatalf("default_instance not updated to %s (got %s)", target.CoreAddress, out.DefaultInstance)
}
// Kill the default instance using runtime PID discovery
corePID := getCorePID(t, target.Address)
corePID := getCorePID(t, target.CoreAddress)
if corePID <= 0 {
t.Fatalf("could not find PID for core process at %s", target.Address)
t.Fatalf("could not find PID for core process at %s", target.CoreAddress)
}
t.Logf("Killing cline-core process PID %d for instance %s", corePID, target.Address)
t.Logf("Killing cline-core process PID %d for instance %s", corePID, target.CoreAddress)
if err := syscall.Kill(corePID, syscall.SIGKILL); err != nil {
t.Fatalf("kill pid %d: %v", corePID, err)
}
// Wait for removal
waitForAddressRemoved(t, target.Address, longTimeout)
waitForAddressRemoved(t, target.CoreAddress, longTimeout)
// Clean up dangling host process (SIGKILL leaves these behind by design)
t.Logf("Cleaning up dangling host process on port %d", target.HostPort())
@@ -91,7 +91,7 @@ func TestDefaultJsonUpdateAfterRemoval(t *testing.T) {
if len(out.CoreInstances) > 0 {
found := false
for _, it := range out.CoreInstances {
if out.DefaultInstance == it.Address {
if out.DefaultInstance == it.CoreAddress {
found = true
break
}
+3 -3
View File
@@ -117,7 +117,7 @@ func listInstancesJSON(ctx context.Context, t *testing.T) common.InstancesOutput
func hasAddress(in common.InstancesOutput, addr string) bool {
for _, it := range in.CoreInstances {
if it.Address == addr {
if it.CoreAddress == addr {
return true
}
}
@@ -126,7 +126,7 @@ func hasAddress(in common.InstancesOutput, addr string) bool {
func getByAddress(in common.InstancesOutput, addr string) (common.CoreInstanceInfo, bool) {
for _, it := range in.CoreInstances {
if it.Address == addr {
if it.CoreAddress == addr {
return it, true
}
}
@@ -313,7 +313,7 @@ func getCorePIDViaRPC(t *testing.T, address string) int {
defer cancel()
// Get client for the address
client, err := global.Clients.GetRegistry().GetClient(ctx, address)
client, err := global.Instances.GetRegistry().GetClient(ctx, address)
if err != nil {
t.Logf("Warning: failed to get client for %s, falling back to lsof: %v", address, err)
return getCorePIDViaLsof(t, address)
+10 -10
View File
@@ -26,7 +26,7 @@ func TestMixedLocalhostVs127Coexist(t *testing.T) {
t.Fatalf("expected at least 1 instance")
}
inst := out.CoreInstances[0]
waitForAddressHealthy(t, inst.Address, defaultTimeout)
waitForAddressHealthy(t, inst.CoreAddress, defaultTimeout)
// Manually add a SQLite entry for the same port but 127.0.0.1 host
addr127 := fmt.Sprintf("127.0.0.1:%d", inst.CorePort())
@@ -37,12 +37,12 @@ func TestMixedLocalhostVs127Coexist(t *testing.T) {
}
// Verify both addresses appear and are healthy
waitForAddressHealthy(t, inst.Address, defaultTimeout)
waitForAddressHealthy(t, inst.CoreAddress, defaultTimeout)
waitForAddressHealthy(t, addr127, defaultTimeout)
out = listInstancesJSON(ctx, t)
if !hasAddress(out, inst.Address) || !hasAddress(out, addr127) {
t.Fatalf("expected both %s and %s present", inst.Address, addr127)
if !hasAddress(out, inst.CoreAddress) || !hasAddress(out, addr127) {
t.Fatalf("expected both %s and %s present", inst.CoreAddress, addr127)
}
}
@@ -58,7 +58,7 @@ func TestStartStopStress(t *testing.T) {
before := listInstancesJSON(ctx, t)
beforeSet := map[string]struct{}{}
for _, it := range before.CoreInstances {
beforeSet[it.Address] = struct{}{}
beforeSet[it.CoreAddress] = struct{}{}
}
// Start a new instance
@@ -69,8 +69,8 @@ func TestStartStopStress(t *testing.T) {
waitFor(t, defaultTimeout, func() (bool, string) {
after := listInstancesJSON(ctx, t)
for _, it := range after.CoreInstances {
if _, ok := beforeSet[it.Address]; !ok {
newAddr = it.Address
if _, ok := beforeSet[it.CoreAddress]; !ok {
newAddr = it.CoreAddress
return true, ""
}
}
@@ -88,12 +88,12 @@ func TestStartStopStress(t *testing.T) {
}
// Get PID using runtime discovery
corePID := getCorePID(t, info.Address)
corePID := getCorePID(t, info.CoreAddress)
if corePID <= 0 {
t.Fatalf("could not find PID for new instance at %s", info.Address)
t.Fatalf("could not find PID for new instance at %s", info.CoreAddress)
}
t.Logf("Killing new instance %s (PID %d) for iteration %d", info.Address, corePID, i)
t.Logf("Killing new instance %s (PID %d) for iteration %d", info.CoreAddress, corePID, i)
if err := syscall.Kill(corePID, syscall.SIGKILL); err != nil {
t.Fatalf("kill pid %d: %v", corePID, err)
}
+1 -1
View File
@@ -55,7 +55,7 @@ func readInstancesFromSQLite(t *testing.T, clineDir string) []common.CoreInstanc
// Create InstanceInfo
info := common.CoreInstanceInfo{
Address: heldBy,
CoreAddress: heldBy,
HostServiceAddress: lockTarget,
Status: grpc_health_v1.HealthCheckResponse_UNKNOWN, // Will be updated by health check
LastSeen: time.Unix(lockedAt/1000, 0), // Convert from milliseconds
+14 -14
View File
@@ -29,7 +29,7 @@ func TestStartAndList(t *testing.T) {
t.Fatalf("expected 1 instance, got %d", len(out.CoreInstances))
}
addr := out.CoreInstances[0].Address
addr := out.CoreInstances[0].CoreAddress
t.Logf("Instance address: %s, status: %s", addr, out.CoreInstances[0].Status)
t.Logf("Waiting for address %s to become healthy...", addr)
@@ -44,8 +44,8 @@ func TestStartAndList(t *testing.T) {
if out.DefaultInstance == "" {
t.Fatalf("default_instance not set")
}
if out.DefaultInstance != out.CoreInstances[0].Address {
t.Fatalf("expected default_instance=%s, got %s", out.CoreInstances[0].Address, out.DefaultInstance)
if out.DefaultInstance != out.CoreInstances[0].CoreAddress {
t.Fatalf("expected default_instance=%s, got %s", out.CoreInstances[0].CoreAddress, out.DefaultInstance)
}
t.Logf("TestStartAndList completed successfully")
@@ -64,7 +64,7 @@ func TestTaskNewDefault(t *testing.T) {
if len(out.CoreInstances) != 1 {
t.Fatalf("expected 1 instance, got %d", len(out.CoreInstances))
}
addr := out.CoreInstances[0].Address
addr := out.CoreInstances[0].CoreAddress
waitForAddressHealthy(t, addr, defaultTimeout)
// Create a new task at default (success is sufficient)
@@ -108,21 +108,21 @@ func TestCrashCleanup(t *testing.T) {
// Test 1: Graceful shutdown (SIGTERM) - should clean up both processes
gracefulTarget := out.CoreInstances[0]
waitForAddressHealthy(t, gracefulTarget.Address, defaultTimeout)
waitForAddressHealthy(t, gracefulTarget.CoreAddress, defaultTimeout)
// Get PID using runtime discovery
gracefulPID := getCorePID(t, gracefulTarget.Address)
gracefulPID := getCorePID(t, gracefulTarget.CoreAddress)
if gracefulPID <= 0 {
t.Fatalf("could not find PID for graceful target at %s", gracefulTarget.Address)
t.Fatalf("could not find PID for graceful target at %s", gracefulTarget.CoreAddress)
}
t.Logf("Testing graceful shutdown (SIGTERM) for instance %s (PID %d)", gracefulTarget.Address, gracefulPID)
t.Logf("Testing graceful shutdown (SIGTERM) for instance %s (PID %d)", gracefulTarget.CoreAddress, gracefulPID)
if err := syscall.Kill(gracefulPID, syscall.SIGTERM); err != nil {
t.Fatalf("kill SIGTERM pid %d: %v", gracefulPID, err)
}
// Wait for registry cleanup
waitForAddressRemoved(t, gracefulTarget.Address, longTimeout)
waitForAddressRemoved(t, gracefulTarget.CoreAddress, longTimeout)
// Verify both core and host ports are freed (no dangling processes)
waitForPortsClosed(t, gracefulTarget.CorePort(), gracefulTarget.HostPort(), defaultTimeout)
@@ -132,21 +132,21 @@ func TestCrashCleanup(t *testing.T) {
// Test 2: Crash cleanup (SIGKILL) - creates dangling host process that we must clean up
crashTarget := out.CoreInstances[1]
waitForAddressHealthy(t, crashTarget.Address, defaultTimeout)
waitForAddressHealthy(t, crashTarget.CoreAddress, defaultTimeout)
// Get PID using runtime discovery
crashPID := getCorePID(t, crashTarget.Address)
crashPID := getCorePID(t, crashTarget.CoreAddress)
if crashPID <= 0 {
t.Fatalf("could not find PID for crash target at %s", crashTarget.Address)
t.Fatalf("could not find PID for crash target at %s", crashTarget.CoreAddress)
}
t.Logf("Testing crash cleanup (SIGKILL) for instance %s (PID %d)", crashTarget.Address, crashPID)
t.Logf("Testing crash cleanup (SIGKILL) for instance %s (PID %d)", crashTarget.CoreAddress, crashPID)
if err := syscall.Kill(crashPID, syscall.SIGKILL); err != nil {
t.Fatalf("kill SIGKILL pid %d: %v", crashPID, err)
}
// Wait for registry cleanup
waitForAddressRemoved(t, crashTarget.Address, longTimeout)
waitForAddressRemoved(t, crashTarget.CoreAddress, longTimeout)
// Verify the instance is removed from SQLite (no file to check anymore)
// The waitForAddressRemoved already confirms the instance is gone from the registry
+58
View File
@@ -298,6 +298,64 @@ cline task view
\f[I]# Start interactive chat with this task\f[R]
cline task chat
.EE
.SH ENVIRONMENT
.TP
\f[B]CLINE_COMMAND_PERMISSIONS\f[R]
JSON configuration for restricting which shell commands Cline can
execute.
When set, commands are validated against allow/deny patterns before
execution.
When not set, all commands are allowed (backward compatibility).
.RS
.PP
Format:
\f[CR]{"allow": ["pattern1", "pattern2"], "deny": ["pattern3"], "allowRedirects": true}\f[R]
.PP
\f[B]Fields:\f[R]
.IP \(bu 2
\f[B]allow\f[R] (array of strings): Glob patterns for allowed commands.
If specified, only matching commands are permitted.
Uses \f[CR]*\f[R] to match any characters and \f[CR]?\f[R] to match a
single character.
.IP \(bu 2
\f[B]deny\f[R] (array of strings): Glob patterns for denied commands.
Deny rules take precedence over allow rules.
.IP \(bu 2
\f[B]allowRedirects\f[R] (boolean): Whether to allow shell redirects
(\f[CR]>\f[R], \f[CR]>>\f[R], \f[CR]<\f[R], etc.).
Defaults to false.
.PP
\f[B]Rule evaluation:\f[R]
.IP "1." 3
Check for dangerous characters (backticks outside single quotes,
unquoted newlines)
.IP "2." 3
Parse command into segments split by operators (\f[CR]&&\f[R],
\f[CR]||\f[R], \f[CR]|\f[R], \f[CR];\f[R])
.IP "3." 3
If redirects detected and \f[CR]allowRedirects\f[R] is not true, command
is denied
.IP "4." 3
Each segment is validated against deny rules first, then allow rules
.IP "5." 3
Subshell contents (\f[CR]$(...)\f[R] and \f[CR](...)\f[R]) are
recursively validated
.IP "6." 3
All segments must pass for the command to be allowed
.PP
\f[B]Examples:\f[R]
.IP
.EX
\f[I]# Allow only npm and git commands\f[R]
export CLINE_COMMAND_PERMISSIONS=\(aq{"allow": ["npm *", "git *"]}\(aq
\f[I]# Allow development commands but deny dangerous ones\f[R]
export CLINE_COMMAND_PERMISSIONS=\(aq{"allow": ["npm *", "git *", "node *"], "deny": ["rm \-rf *", "sudo *"]}\(aq
\f[I]# Allow file operations with redirects\f[R]
export CLINE_COMMAND_PERMISSIONS=\(aq{"allow": ["cat *", "echo *"], "allowRedirects": true}\(aq
.EE
.RE
.SH ARCHITECTURE
Cline operates on a three\-layer architecture:
.TP
+36
View File
@@ -323,6 +323,42 @@ cline task view
cline task chat
```
# ENVIRONMENT
**CLINE_COMMAND_PERMISSIONS**
: JSON configuration for restricting which shell commands Cline can execute. When set, commands are validated against allow/deny patterns before execution. When not set, all commands are allowed.
Format: `{"allow": ["pattern1", "pattern2"], "deny": ["pattern3"], "allowRedirects": true}`
**Fields:**
- **allow** (array of strings): Glob patterns for allowed commands. If specified, only matching commands are permitted. Uses `*` to match any characters and `?` to match a single character. Setting allow on anything will deny all others.
- **deny** (array of strings): Glob patterns for denied commands. Deny rules take precedence over allow rules.
- **allowRedirects** (boolean): Whether to allow shell redirects (`>`, `>>`, `<`, etc.). Defaults to false.
**Rule evaluation:**
1. Check for dangerous characters (backticks outside single quotes, unquoted newlines)
2. Parse command into segments split by operators (`&&`, `||`, `|`, `;`)
3. If redirects detected and `allowRedirects` is not true, command is denied
4. Each segment is validated against deny rules first, then allow rules
5. Subshell contents (`$(...)` and `(...)`) are recursively validated
6. All segments must pass for the command to be allowed
**Examples:**
```bash
# Allow only npm and git commands.
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"]}'
# Allow development commands but deny dangerous ones. Deny not strictly required here since allow is set.
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *", "node *"], "deny": ["rm -rf *", "sudo *"]}'
# Allow file operations with redirects
export CLINE_COMMAND_PERMISSIONS='{"allow": ["cat *", "echo *"], "allowRedirects": true}'
```
# ARCHITECTURE
Cline operates on a three-layer architecture:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "cline",
"version": "1.0.9",
"version": "1.0.10",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"main": "cline-core.js",
"bin": {
+8 -10
View File
@@ -46,21 +46,21 @@ const (
// It spawns a fresh instance for auth operations and cleans it up when done
func RunAuthFlow(ctx context.Context, args []string) error {
// Spawn a fresh instance for auth operations
instanceInfo, err := global.Clients.StartNewInstance(ctx)
instanceInfo, err := global.Instances.StartNewInstance(ctx)
if err != nil {
return fmt.Errorf("failed to start auth instance: %w", err)
}
// Cleanup when done (success, error, or panic)
defer func() {
verboseLog("Shutting down auth instance at %s", instanceInfo.Address)
if err := global.KillInstanceByAddress(context.Background(), global.Clients.GetRegistry(), instanceInfo.Address); err != nil {
verboseLog("Shutting down auth instance at %s", instanceInfo.CoreAddress)
if err := global.KillInstanceByAddress(context.Background(), global.Instances.GetRegistry(), instanceInfo.CoreAddress); err != nil {
verboseLog("Warning: Failed to kill auth instance: %v", err)
}
}()
// Store instance address in context for all auth handlers to use
authCtx := context.WithValue(ctx, authInstanceAddressKey, instanceInfo.Address)
authCtx := context.WithValue(ctx, authInstanceAddressKey, instanceInfo.CoreAddress)
// Route to existing auth flow
return HandleAuthCommand(authCtx, args)
@@ -108,12 +108,10 @@ func HandleAuthMenuNoArgs(ctx context.Context) error {
// Get current provider config for display
var currentProvider string
var currentModel string
if manager, err := createTaskManager(ctx); err == nil {
if providerList, err := GetProviderConfigurations(ctx, manager); err == nil {
if providerList.ActProvider != nil {
currentProvider = GetProviderDisplayName(providerList.ActProvider.Provider)
currentModel = providerList.ActProvider.ModelID
}
if providerList, err := GetProviderConfigurations(ctx); err == nil {
if providerList.ActProvider != nil {
currentProvider = GetProviderDisplayName(providerList.ActProvider.Provider)
currentModel = providerList.ActProvider.ModelID
}
}
+6 -2
View File
@@ -29,13 +29,17 @@ type ProviderListResult struct {
}
// GetProviderConfigurations retrieves and parses provider configurations from Cline Core state
func GetProviderConfigurations(ctx context.Context, manager *task.Manager) (*ProviderListResult, error) {
func GetProviderConfigurations(ctx context.Context) (*ProviderListResult, error) {
if global.Config.Verbose {
fmt.Println("[DEBUG] Retrieving provider configurations from Cline Core")
}
// Get latest state from Cline Core
state, err := manager.GetClient().State.GetLatestState(ctx, &cline.EmptyRequest{})
grpcClient, err := global.GetDefaultClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get provider configs due to unable to get gRPC client: %w", err)
}
state, err := grpcClient.State.GetLatestState(ctx, &cline.EmptyRequest{})
if err != nil {
return nil, fmt.Errorf("failed to get state: %w", err)
}
+5 -6
View File
@@ -215,7 +215,7 @@ func (pw *ProviderWizard) handleAddOcaProvider() error {
// handleListProviders retrieves and displays configured providers
func (pw *ProviderWizard) handleListProviders() error {
result, err := GetProviderConfigurations(pw.ctx, pw.manager)
result, err := GetProviderConfigurations(pw.ctx)
if err != nil {
return fmt.Errorf("failed to retrieve provider configurations: %w", err)
}
@@ -413,7 +413,7 @@ func (pw *ProviderWizard) manualModelEntry(provider cline.ApiProvider) (string,
// handleChangeModel allows changing the model for any configured provider
func (pw *ProviderWizard) handleChangeModel() error {
// Step 1: Get current provider configurations
result, err := GetProviderConfigurations(pw.ctx, pw.manager)
result, err := GetProviderConfigurations(pw.ctx)
if err != nil {
return fmt.Errorf("failed to retrieve provider configurations: %w", err)
}
@@ -585,11 +585,11 @@ func getProviderModelIDFromState(stateData map[string]interface{}, provider clin
return ""
}
// getProviderAPIKeyFromState retrieves the API key for a specific provider from state
// getProviderAPIKeyFromState retrieves the API key for a specific provider from state
func getProviderAPIKeyFromState(stateData map[string]interface{}, provider cline.ApiProvider) string {
// OCA uses account authentication, not API keys. Consider it "present" if authenticated.
if provider == cline.ApiProvider_OCA {
if state, _ := GetLatestOCAState(context.TODO(), 2 * time.Second); state != nil && state.User != nil {
if state, _ := GetLatestOCAState(context.TODO(), 2*time.Second); state != nil && state.User != nil {
// Return a sentinel non-empty string so upstream checks pass.
return "OCA_AUTH_VERIFIED"
}
@@ -648,7 +648,7 @@ func convertMapToOpenRouterModelInfo(data map[string]interface{}) *cline.OpenRou
// handleRemoveProvider allows removing a configured provider by clearing its API key
func (pw *ProviderWizard) handleRemoveProvider() error {
// Step 1: Get current provider configurations
result, err := GetProviderConfigurations(pw.ctx, pw.manager)
result, err := GetProviderConfigurations(pw.ctx)
if err != nil {
return fmt.Errorf("failed to retrieve provider configurations: %w", err)
}
@@ -748,7 +748,6 @@ func (pw *ProviderWizard) clearProviderAPIKey(provider cline.ApiProvider) error
return RemoveProviderPartial(pw.ctx, pw.manager, provider)
}
func signOutOca(ctx context.Context) error {
client, err := global.GetDefaultClient(ctx)
if err != nil {
+3 -3
View File
@@ -40,7 +40,7 @@ func ensureConfigManager(ctx context.Context, address string) error {
}
// Always set the instance we're using as the default
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
if err := registry.SetDefaultInstance(instanceAddress); err != nil {
// Log warning but don't fail - this is not critical
fmt.Printf("Warning: failed to set default instance: %v\n", err)
@@ -123,11 +123,11 @@ func setCommand() *cobra.Command {
Use: "set <key=value> [key=value...]",
Aliases: []string{"s"},
Short: "Set configuration variables",
Long: `Set one or more global configuration variables using key=value format.
Long: `Set one or more global configuration variables using key=value format.
This command merges the provided settings with existing values, preserving
unspecified fields. Only the fields you explicitly set will be updated.`,
Args: cobra.MinimumNArgs(1),
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
+2 -3
View File
@@ -32,8 +32,8 @@ func NewManager(ctx context.Context, address string) (*Manager, error) {
// Get the actual address being used
clientAddress := address
if address == "" && global.Clients != nil {
clientAddress = global.Clients.GetRegistry().GetDefaultInstance()
if address == "" && global.Instances != nil {
clientAddress = global.Instances.GetRegistry().GetDefaultInstance()
}
return &Manager{
@@ -110,7 +110,6 @@ func (m *Manager) ListSettings(ctx context.Context) error {
"dictationSettings",
"autoCondenseThreshold",
"autoApprovalSettings",
"hooksEnabled",
}
// Render each field using the renderer
+1 -1
View File
@@ -79,7 +79,7 @@ func RenderField(key string, value interface{}, censor bool) error {
"planActSeparateModelsSetting", "enableCheckpointsSetting",
"terminalReuseEnabled", "mcpResponsesCollapsed", "strictPlanModeEnabled",
"useAutoCondense", "yoloModeToggled", "shellIntegrationTimeout",
"terminalOutputLineLimit", "autoCondenseThreshold", "hooksEnabled":
"terminalOutputLineLimit", "autoCondenseThreshold":
fmt.Printf("%s: %s\n", camelToKebab(key), formatValue(value, key, censor))
return nil
@@ -14,21 +14,20 @@ import (
"github.com/cline/grpc-go/cline"
)
// ClineClients manages Cline instances using the new registry system
type ClineClients struct {
registry *ClientRegistry
type ClineInstances struct {
registry *InstanceRegistry
}
// NewClineClients creates a new ClineClients instance
func NewClineClients(configPath string) *ClineClients {
registry := NewClientRegistry(configPath)
return &ClineClients{
// NewClineInstances creates a new ClineInstances instance
func NewClineInstances(configPath string) *ClineInstances {
registry := NewInstanceRegistry(configPath)
return &ClineInstances{
registry: registry,
}
}
// Initialize performs cleanup of stale instances
func (c *ClineClients) Initialize(ctx context.Context) error {
func (c *ClineInstances) Initialize(ctx context.Context) error {
// Clean up stale entries (direct SQLite operations)
_ = c.registry.CleanupStaleInstances(ctx)
@@ -36,7 +35,8 @@ func (c *ClineClients) Initialize(ctx context.Context) error {
}
// StartNewInstance starts a new Cline instance and waits for cline-core to self-register
func (c *ClineClients) StartNewInstance(ctx context.Context, workspaces ...string) (*common.CoreInstanceInfo, error) {
// An "instance" is a pair of cline-core and cline-host processes
func (c *ClineInstances) StartNewInstance(ctx context.Context, workspaces ...string) (*common.CoreInstanceInfo, error) {
// Find available ports
corePort, hostPort, err := common.FindAvailablePortPair()
if err != nil {
@@ -102,7 +102,7 @@ func (c *ClineClients) StartNewInstance(ctx context.Context, workspaces ...strin
if Config.Verbose {
fmt.Println("Services started and registered successfully!")
fmt.Printf(" Address: %s\n", instance.Address)
fmt.Printf(" Address: %s\n", instance.CoreAddress)
fmt.Printf(" Core Port: %d\n", instance.CorePort())
fmt.Printf(" Host Bridge Port: %d\n", instance.HostPort())
fmt.Printf(" Process PID: %d\n", coreCmd.Process.Pid)
@@ -120,7 +120,7 @@ func (c *ClineClients) StartNewInstance(ctx context.Context, workspaces ...strin
}
// StartNewInstanceAtPort starts a new Cline instance at the specified port and waits for self-registration
func (c *ClineClients) StartNewInstanceAtPort(ctx context.Context, corePort int, workspaces ...string) (*common.CoreInstanceInfo, error) {
func (c *ClineInstances) StartNewInstanceAtPort(ctx context.Context, corePort int, workspaces ...string) (*common.CoreInstanceInfo, error) {
// Find available host port (core port + 1000)
hostPort := corePort + 1000
coreAddress := fmt.Sprintf("localhost:%d", corePort)
@@ -189,7 +189,7 @@ func (c *ClineClients) StartNewInstanceAtPort(ctx context.Context, corePort int,
if Config.Verbose {
fmt.Println("Services started and registered successfully!")
fmt.Printf(" Address: %s\n", instance.Address)
fmt.Printf(" Address: %s\n", instance.CoreAddress)
fmt.Printf(" Core Port: %d\n", instance.CorePort())
fmt.Printf(" Host Bridge Port: %d\n", instance.HostPort())
fmt.Printf(" Process PID: %d\n", coreCmd.Process.Pid)
@@ -207,12 +207,12 @@ func (c *ClineClients) StartNewInstanceAtPort(ctx context.Context, corePort int,
}
// GetRegistry returns the client registry
func (c *ClineClients) GetRegistry() *ClientRegistry {
func (c *ClineInstances) GetRegistry() *InstanceRegistry {
return c.registry
}
// EnsureInstanceAtAddress ensures an instance exists at the given address, starting one if needed
func (c *ClineClients) EnsureInstanceAtAddress(ctx context.Context, address string) error {
func (c *ClineInstances) EnsureInstanceAtAddress(ctx context.Context, address string) error {
// Expect host:port everywhere
normalized := address
if normalized == "" {
@@ -230,7 +230,7 @@ func (c *ClineClients) EnsureInstanceAtAddress(ctx context.Context, address stri
return fmt.Errorf("invalid address format %s", address)
}
// Use IPv6-compatible localhost detection
// Use IPv6-compatible localhost detection
if common.IsLocalAddress(host) {
_, err := c.StartNewInstanceAtPort(ctx, port)
if err != nil {
@@ -305,7 +305,7 @@ func startClineHost(hostPort int, workspaces []string) (*exec.Cmd, error) {
}
// KillInstanceByAddress kills a Cline instance by its address
func KillInstanceByAddress(ctx context.Context, registry *ClientRegistry, address string) error {
func KillInstanceByAddress(ctx context.Context, registry *InstanceRegistry, address string) error {
// Check if the instance exists in the registry
_, err := registry.GetInstance(address)
if err != nil {
@@ -355,9 +355,9 @@ func KillInstanceByAddress(ctx context.Context, registry *ClientRegistry, addres
defaultInstance := registry.GetDefaultInstance()
if defaultInstance == address || defaultInstance == "" {
if len(instances) > 0 {
if err := registry.SetDefaultInstance(instances[0].Address); err == nil {
if err := registry.SetDefaultInstance(instances[0].CoreAddress); err == nil {
if Config.Verbose {
fmt.Printf("Updated default instance to: %s\n", instances[0].Address)
fmt.Printf("Updated default instance to: %s\n", instances[0].CoreAddress)
}
}
}
+12 -11
View File
@@ -22,8 +22,8 @@ type GlobalConfig struct {
}
var (
Config *GlobalConfig
Clients *ClineClients
Config *GlobalConfig
Instances *ClineInstances
// Version info - set at build time via ldflags
// Version is the Cline Core version (from root package.json)
@@ -61,11 +61,11 @@ func InitializeGlobalConfig(cfg *GlobalConfig) error {
// Otherwise lipgloss auto-detects terminal capabilities (default behavior)
Config = cfg
Clients = NewClineClients(cfg.ConfigPath)
Instances = NewClineInstances(cfg.ConfigPath)
// Initialize the clients registry
ctx := context.Background()
if err := Clients.Initialize(ctx); err != nil {
if err := Instances.Initialize(ctx); err != nil {
return fmt.Errorf("failed to initialize clients: %w", err)
}
@@ -76,25 +76,25 @@ func InitializeGlobalConfig(cfg *GlobalConfig) error {
func GetDefaultClient(ctx context.Context) (*client.ClineClient, error) {
if Config.CoreAddress != "" && Config.CoreAddress != fmt.Sprintf("localhost:%d", common.DEFAULT_CLINE_CORE_PORT) {
// User specified a specific address, use that
return Clients.GetRegistry().GetClient(ctx, Config.CoreAddress)
return Instances.GetRegistry().GetClient(ctx, Config.CoreAddress)
}
// Use the default instance from registry
return Clients.GetRegistry().GetDefaultClient(ctx)
return Instances.GetRegistry().GetDefaultClient(ctx)
}
// GetClientForAddress returns a client for a specific address
func GetClientForAddress(ctx context.Context, address string) (*client.ClineClient, error) {
return Clients.GetRegistry().GetClient(ctx, address)
return Instances.GetRegistry().GetClient(ctx, address)
}
// EnsureDefaultInstance ensures a default instance exists
func EnsureDefaultInstance(ctx context.Context) error {
if Clients == nil {
if Instances == nil {
return fmt.Errorf("global clients not initialized")
}
registry := Clients.GetRegistry()
registry := Instances.GetRegistry()
// First, check if there are any instances already registered in SQLite
instances := registry.ListInstances()
@@ -108,11 +108,12 @@ func EnsureDefaultInstance(ctx context.Context) error {
if registry.GetDefaultInstance() == "" {
// No instances exist, start a new one
// Note: StartNewInstance will automatically set it as default since it's the first instance
_, err := Clients.StartNewInstance(ctx)
_, err := Instances.StartNewInstance(ctx)
if err != nil {
return fmt.Errorf("failed to start new default instance: %w", err)
}
}
return nil
}
}
+27 -28
View File
@@ -18,28 +18,28 @@ import (
"google.golang.org/grpc/health/grpc_health_v1"
)
// ClientRegistry manages Cline client connections using direct SQLite operations
type ClientRegistry struct {
// InstanceRegistry manages Cline client connections using direct SQLite operations
type InstanceRegistry struct {
lockManager *sqlite.LockManager
configPath string
}
// NewClientRegistry creates a new client registry
func NewClientRegistry(configPath string) *ClientRegistry {
// NewInstanceRegistry creates a new instance registry
func NewInstanceRegistry(configPath string) *InstanceRegistry {
lockManager, err := sqlite.NewLockManager(configPath)
if err != nil {
// Log error but continue - we can still function without SQLite
log.Fatalf("Warning: Failed to initialize SQLite lock manager: %v\n", err)
}
return &ClientRegistry{
return &InstanceRegistry{
lockManager: lockManager,
configPath: configPath,
}
}
// GetDefaultInstance returns the default instance address from settings file
func (r *ClientRegistry) GetDefaultInstance() string {
func (r *InstanceRegistry) GetDefaultInstance() string {
defaultAddr, err := sqlite.GetDefaultInstance(r.configPath)
if err != nil {
return ""
@@ -48,7 +48,7 @@ func (r *ClientRegistry) GetDefaultInstance() string {
}
// SetDefaultInstance sets the default instance (writes default.json)
func (r *ClientRegistry) SetDefaultInstance(address string) error {
func (r *InstanceRegistry) SetDefaultInstance(address string) error {
// Verify the instance exists in SQLite
if r.lockManager != nil {
exists, err := r.lockManager.HasInstanceAtAddress(address)
@@ -64,7 +64,7 @@ func (r *ClientRegistry) SetDefaultInstance(address string) error {
}
// GetInstance returns instance information directly from SQLite
func (r *ClientRegistry) GetInstance(address string) (*common.CoreInstanceInfo, error) {
func (r *InstanceRegistry) GetInstance(address string) (*common.CoreInstanceInfo, error) {
if r.lockManager == nil {
return nil, fmt.Errorf("lock manager not available")
}
@@ -73,7 +73,7 @@ func (r *ClientRegistry) GetInstance(address string) (*common.CoreInstanceInfo,
}
// GetClient returns a connected client for the given address (created on-demand)
func (r *ClientRegistry) GetClient(ctx context.Context, address string) (*client.ClineClient, error) {
func (r *InstanceRegistry) GetClient(ctx context.Context, address string) (*client.ClineClient, error) {
// Verify instance exists in SQLite
if r.lockManager != nil {
exists, err := r.lockManager.HasInstanceAtAddress(address)
@@ -104,7 +104,7 @@ func (r *ClientRegistry) GetClient(ctx context.Context, address string) (*client
}
// GetDefaultClient returns a client for the default instance
func (r *ClientRegistry) GetDefaultClient(ctx context.Context) (*client.ClineClient, error) {
func (r *InstanceRegistry) GetDefaultClient(ctx context.Context) (*client.ClineClient, error) {
defaultAddr := r.GetDefaultInstance()
if defaultAddr == "" {
return nil, fmt.Errorf("no default instance configured")
@@ -117,7 +117,7 @@ func (r *ClientRegistry) GetDefaultClient(ctx context.Context) (*client.ClineCli
// Database is unavailable - Return error instead of attempting cleanup
return nil, fmt.Errorf("cannot verify default instance: database unavailable: %w", err)
}
if !exists {
// Instance doesn't exist in database but config file references it
// This is a stale config - remove it and try to find another instance
@@ -127,14 +127,14 @@ func (r *ClientRegistry) GetDefaultClient(ctx context.Context) (*client.ClineCli
} else {
fmt.Printf("Removed stale default instance config (instance %s not found in database)\n", defaultAddr)
}
// Try to find and set a new default instance
instances := r.ListInstances()
if len(instances) > 0 {
if err := r.EnsureDefaultInstance(instances); err != nil {
return nil, fmt.Errorf("failed to set new default instance: %w", err)
}
// Retry with the new default
newDefaultAddr := r.GetDefaultInstance()
if newDefaultAddr != "" {
@@ -142,7 +142,7 @@ func (r *ClientRegistry) GetDefaultClient(ctx context.Context) (*client.ClineCli
return r.GetClient(ctx, newDefaultAddr)
}
}
return nil, fmt.Errorf("no default instance configured")
}
}
@@ -151,7 +151,7 @@ func (r *ClientRegistry) GetDefaultClient(ctx context.Context) (*client.ClineCli
}
// ListInstances returns all registered instances directly from SQLite
func (r *ClientRegistry) ListInstances() []*common.CoreInstanceInfo {
func (r *InstanceRegistry) ListInstances() []*common.CoreInstanceInfo {
if r.lockManager == nil {
return []*common.CoreInstanceInfo{}
}
@@ -170,7 +170,7 @@ func (r *ClientRegistry) ListInstances() []*common.CoreInstanceInfo {
}
// HasInstanceAtAddress checks if an instance exists at the given address (delegates to SQLite)
func (r *ClientRegistry) HasInstanceAtAddress(address string) bool {
func (r *InstanceRegistry) HasInstanceAtAddress(address string) bool {
if r.lockManager == nil {
return false
}
@@ -185,7 +185,7 @@ func (r *ClientRegistry) HasInstanceAtAddress(address string) bool {
}
// CleanupStaleInstances removes stale instances using direct SQLite operations
func (r *ClientRegistry) CleanupStaleInstances(ctx context.Context) error {
func (r *InstanceRegistry) CleanupStaleInstances(ctx context.Context) error {
if r.lockManager == nil {
return nil
}
@@ -202,15 +202,15 @@ func (r *ClientRegistry) CleanupStaleInstances(ctx context.Context) error {
// Try to gracefully shutdown the paired host process before cleanup
fmt.Printf("Attempting to shutdown dangling host service %s for stale cline core instance %s\n",
instance.HostServiceAddress, instance.Address)
instance.HostServiceAddress, instance.CoreAddress)
r.tryShutdownHostProcess(instance.HostServiceAddress)
// Remove from SQLite database
if err := r.lockManager.RemoveInstanceLock(instance.Address); err != nil {
return fmt.Errorf("failed to remove stale instance %s: %w", instance.Address, err)
if err := r.lockManager.RemoveInstanceLock(instance.CoreAddress); err != nil {
return fmt.Errorf("failed to remove stale instance %s: %w", instance.CoreAddress, err)
}
fmt.Printf("Removed stale instance: %s\n", instance.Address)
fmt.Printf("Removed stale instance: %s\n", instance.CoreAddress)
}
}
@@ -218,8 +218,7 @@ func (r *ClientRegistry) CleanupStaleInstances(ctx context.Context) error {
}
// tryShutdownHostProcess attempts to gracefully shutdown a host process via RPC
// Best effort, don't throw errors i guess
func (r *ClientRegistry) tryShutdownHostProcess(hostServiceAddress string) {
func (r *InstanceRegistry) tryShutdownHostProcess(hostServiceAddress string) {
err := common.RetryOperation(3, 2*time.Second, func() error {
// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
@@ -252,7 +251,7 @@ func (r *ClientRegistry) tryShutdownHostProcess(hostServiceAddress string) {
}
// ListInstancesCleaned performs cleanup and returns instances with health checks
func (r *ClientRegistry) ListInstancesCleaned(ctx context.Context) ([]*common.CoreInstanceInfo, error) {
func (r *InstanceRegistry) ListInstancesCleaned(ctx context.Context) ([]*common.CoreInstanceInfo, error) {
// 1. Clean up stale entries (best-effort)
_ = r.CleanupStaleInstances(ctx)
@@ -268,7 +267,7 @@ func (r *ClientRegistry) ListInstancesCleaned(ctx context.Context) ([]*common.Co
}
// EnsureDefaultInstance ensures a default instance is set if instances exist but no default is configured
func (r *ClientRegistry) EnsureDefaultInstance(instances []*common.CoreInstanceInfo) error {
func (r *InstanceRegistry) EnsureDefaultInstance(instances []*common.CoreInstanceInfo) error {
currentDefault := r.GetDefaultInstance()
// If we have no instances, clear any stale default and remove settings file
@@ -283,13 +282,13 @@ func (r *ClientRegistry) EnsureDefaultInstance(instances []*common.CoreInstanceI
// If we have instances but no default, pick the first one
if currentDefault == "" {
return sqlite.SetDefaultInstance(r.configPath, instances[0].Address)
return sqlite.SetDefaultInstance(r.configPath, instances[0].CoreAddress)
}
// Validate current default still exists in the instances
defaultExists := false
for _, instance := range instances {
if instance.Address == currentDefault {
if instance.CoreAddress == currentDefault {
defaultExists = true
break
}
@@ -297,7 +296,7 @@ func (r *ClientRegistry) EnsureDefaultInstance(instances []*common.CoreInstanceI
if !defaultExists {
// Current default doesn't exist, pick a new one from available instances
return sqlite.SetDefaultInstance(r.configPath, instances[0].Address)
return sqlite.SetDefaultInstance(r.configPath, instances[0].CoreAddress)
}
return nil
+25 -25
View File
@@ -92,12 +92,12 @@ func newInstanceKillCommand() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if global.Clients == nil {
if global.Instances == nil {
return fmt.Errorf("clients not initialized")
}
ctx := cmd.Context()
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
if killAllCLI {
return killAllCLIInstances(ctx, registry)
@@ -112,7 +112,7 @@ func newInstanceKillCommand() *cobra.Command {
return cmd
}
func killAllCLIInstances(ctx context.Context, registry *global.ClientRegistry) error {
func killAllCLIInstances(ctx context.Context, registry *global.InstanceRegistry) error {
// Get all instances from registry
instances, err := registry.ListInstancesCleaned(ctx)
if err != nil {
@@ -135,7 +135,7 @@ func killAllCLIInstances(ctx context.Context, registry *global.ClientRegistry) e
cliInstances = append(cliInstances, instance)
} else {
skippedNonCLI++
fmt.Printf("⊘ Skipping %s instance: %s\n", platform, instance.Address)
fmt.Printf("⊘ Skipping %s instance: %s\n", platform, instance.CoreAddress)
}
}
}
@@ -160,16 +160,16 @@ func killAllCLIInstances(ctx context.Context, registry *global.ClientRegistry) e
// Kill all CLI instances
for _, instance := range cliInstances {
result := killInstanceProcess(ctx, registry, instance.Address)
result := killInstanceProcess(ctx, registry, instance.CoreAddress)
killResults = append(killResults, result)
if result.err != nil {
fmt.Printf("✗ Failed to kill %s: %v\n", instance.Address, result.err)
fmt.Printf("✗ Failed to kill %s: %v\n", instance.CoreAddress, result.err)
} else if result.alreadyDead {
fmt.Printf("⚠ Instance %s appears to be already dead\n", instance.Address)
fmt.Printf("⚠ Instance %s appears to be already dead\n", instance.CoreAddress)
} else {
fmt.Printf("✓ Killed %s (PID %d)\n", instance.Address, result.pid)
killedAddresses[instance.Address] = true
fmt.Printf("✓ Killed %s (PID %d)\n", instance.CoreAddress, result.pid)
killedAddresses[instance.CoreAddress] = true
}
}
@@ -190,8 +190,8 @@ func killAllCLIInstances(ctx context.Context, registry *global.ClientRegistry) e
// Check if any of the killed instances are still in the registry
stillPresent := []string{}
for _, remaining := range remainingInstances {
if killedAddresses[remaining.Address] {
stillPresent = append(stillPresent, remaining.Address)
if killedAddresses[remaining.CoreAddress] {
stillPresent = append(stillPresent, remaining.CoreAddress)
}
}
@@ -247,7 +247,7 @@ type killResult struct {
err error
}
func killInstanceProcess(ctx context.Context, registry *global.ClientRegistry, address string) killResult {
func killInstanceProcess(ctx context.Context, registry *global.InstanceRegistry, address string) killResult {
// Get gRPC client and process info
client, err := registry.GetClient(ctx, address)
if err != nil {
@@ -276,12 +276,12 @@ func newInstanceListCommand() *cobra.Command {
Short: "List all registered Cline instances",
Long: `List all registered Cline instances with their status and connection details.`,
RunE: func(cmd *cobra.Command, args []string) error {
if global.Clients == nil {
if global.Instances == nil {
return fmt.Errorf("clients not initialized")
}
ctx := cmd.Context()
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
// Load, cleanup stale local entries, and update health
instances, err := registry.ListInstancesCleaned(ctx)
@@ -310,7 +310,7 @@ func newInstanceListCommand() *cobra.Command {
var rows []instanceRow
for _, instance := range instances {
isDefault := ""
if instance.Address == defaultInstance {
if instance.CoreAddress == defaultInstance {
isDefault = "✓"
}
@@ -324,7 +324,7 @@ func newInstanceListCommand() *cobra.Command {
platform := platformNA
if instance.Status == grpc_health_v1.HealthCheckResponse_SERVING {
// Get PID from core
if client, err := registry.GetClient(ctx, instance.Address); err == nil {
if client, err := registry.GetClient(ctx, instance.CoreAddress); err == nil {
if processInfo, err := client.State.GetProcessInfo(ctx, &cline.EmptyRequest{}); err == nil {
pid = fmt.Sprintf("%d", processInfo.ProcessId)
// Update version from RPC if available
@@ -341,7 +341,7 @@ func newInstanceListCommand() *cobra.Command {
}
rows = append(rows, instanceRow{
address: instance.Address,
address: instance.CoreAddress,
status: instance.Status.String(),
version: instance.Version,
lastSeen: lastSeen,
@@ -428,11 +428,11 @@ func newInstanceDefaultCommand() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
address := args[0]
if global.Clients == nil {
if global.Instances == nil {
return fmt.Errorf("clients not initialized")
}
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
// Verify the instance exists
_, err := registry.GetInstance(address)
@@ -464,34 +464,34 @@ func newInstanceNewCommand() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if global.Clients == nil {
if global.Instances == nil {
return fmt.Errorf("clients not initialized")
}
fmt.Println("Starting new Cline instance...")
instance, err := global.Clients.StartNewInstance(ctx)
instance, err := global.Instances.StartNewInstance(ctx)
if err != nil {
return fmt.Errorf("failed to start instance: %w", err)
}
fmt.Printf("Successfully started new instance:\n")
fmt.Printf(" Address: %s\n", instance.Address)
fmt.Printf(" Address: %s\n", instance.CoreAddress)
fmt.Printf(" Core Port: %d\n", instance.CorePort())
fmt.Printf(" Host Bridge Port: %d\n", instance.HostPort())
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
// If --default flag provided, set this instance as the default
if setDefault {
if err := registry.SetDefaultInstance(instance.Address); err != nil {
if err := registry.SetDefaultInstance(instance.CoreAddress); err != nil {
fmt.Printf("Warning: Failed to set as default: %v\n", err)
} else {
fmt.Printf(" Status: Set as default instance\n")
}
} else {
// Otherwise, check if EnsureDefaultInstance already set it as default
if registry.GetDefaultInstance() == instance.Address {
if registry.GetDefaultInstance() == instance.CoreAddress {
fmt.Printf(" Status: Default instance\n")
}
}
+72 -1
View File
@@ -348,7 +348,7 @@ func (m *InputModel) handleSubmit() (tea.Model, tea.Cmd) {
return ChangeInputTypeMsg{
InputType: InputTypeFeedback,
Title: "Your feedback",
Placeholder: "/plan or /act to switch modes\nctrl+e to open editor",
Placeholder: "/plan or /act to switch modes\nctrl+e to open editor\nctrl+c to exit",
}
}
}
@@ -547,3 +547,74 @@ func (m *InputModel) openEditor() tea.Cmd {
func (m *InputModel) SetSlashRegistry(registry *slash.Registry) {
m.completion.SetRegistry(registry)
}
// initialPromptWrapper wraps InputModel to capture the submit result for initial task prompts
type initialPromptWrapper struct {
model *InputModel
result string
cancelled bool
}
func (w *initialPromptWrapper) Init() tea.Cmd {
return w.model.Init()
}
func (w *initialPromptWrapper) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case InputSubmitMsg:
w.result = msg.Value
clearCodes := w.model.ClearScreen()
if clearCodes != "" {
fmt.Print(clearCodes)
}
return w, tea.Quit
case InputCancelMsg:
w.cancelled = true
clearCodes := w.model.ClearScreen()
if clearCodes != "" {
fmt.Print(clearCodes)
}
return w, tea.Quit
}
// Forward to wrapped model
_, cmd := w.model.Update(msg)
return w, cmd
}
func (w *initialPromptWrapper) View() string {
return w.model.View()
}
// ErrUserAborted is returned when the user cancels the input prompt
var ErrUserAborted = fmt.Errorf("user aborted")
// PromptForInitialTask displays an interactive prompt for the initial task with slash command autocomplete.
// Returns the entered text, or ErrUserAborted if cancelled.
func PromptForInitialTask(title, placeholder, mode string, registry *slash.Registry) (string, error) {
model := NewInputModelWithRegistry(
InputTypeMessage,
title,
placeholder,
mode,
registry,
)
wrapper := &initialPromptWrapper{
model: &model,
}
p := tea.NewProgram(wrapper)
_, err := p.Run()
if err != nil {
return "", fmt.Errorf("input prompt failed: %w", err)
}
if wrapper.cancelled {
return "", ErrUserAborted
}
return strings.TrimSpace(wrapper.result), nil
}
+39 -22
View File
@@ -2,10 +2,11 @@ package slash
import (
"context"
"fmt"
"strings"
"sync"
"github.com/cline/grpc-go/client"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/grpc-go/cline"
)
@@ -32,25 +33,31 @@ var cliLocalCommands = []Command{
}
// NewRegistry creates a new slash command registry
func NewRegistry() *Registry {
return &Registry{
commands: make([]Command, 0),
func NewRegistry(ctx context.Context) *Registry {
defaultCommands := append([]Command{}, cliLocalCommands...)
r := &Registry{
commands: defaultCommands,
}
r.FetchFromBackend(ctx)
return r
}
// FetchFromBackend fetches available commands from cline-core backend
func (r *Registry) FetchFromBackend(ctx context.Context, c *client.ClineClient) error {
resp, err := c.Slash.GetAvailableSlashCommands(ctx, &cline.EmptyRequest{})
if err != nil {
return err
func (r *Registry) FetchFromBackend(ctx context.Context) error {
grpcClient, err := global.GetDefaultClient(ctx)
if err != nil && global.Config.Verbose {
fmt.Printf("Warning: could not get gRPC client: %v\n", err)
return nil
}
resp, err := grpcClient.Slash.GetAvailableSlashCommands(ctx, &cline.EmptyRequest{})
if err != nil && global.Config.Verbose {
fmt.Printf("Warning: could not get gRPC client: %v\n", err)
return nil
}
r.mu.Lock()
defer r.mu.Unlock()
// Start with CLI-local commands
r.commands = append([]Command{}, cliLocalCommands...)
// Add backend commands (only CLI-compatible ones)
for _, cmd := range resp.Commands {
if cmd.CliCompatible {
@@ -66,17 +73,6 @@ func (r *Registry) FetchFromBackend(ctx context.Context, c *client.ClineClient)
return nil
}
// GetCommands returns all available commands
func (r *Registry) GetCommands() []Command {
r.mu.RLock()
defer r.mu.RUnlock()
// Return a copy to avoid race conditions
result := make([]Command, len(r.commands))
copy(result, r.commands)
return result
}
// GetMatching returns commands that start with the given prefix (case-insensitive)
func (r *Registry) GetMatching(prefix string) []Command {
r.mu.RLock()
@@ -123,3 +119,24 @@ func (r *Registry) HasCommands() bool {
defer r.mu.RUnlock()
return len(r.commands) > 0
}
// ParseModeSwitch checks if message starts with /act or /plan and extracts the mode and remaining message.
// Returns (mode, remainingMessage, isModeSwitch).
// This is a package-level function so it can be used both during initial task creation
// and during interactive input handling.
func ParseModeSwitch(message string) (string, string, bool) {
trimmed := strings.TrimSpace(message)
lower := strings.ToLower(trimmed)
if strings.HasPrefix(lower, "/plan") {
remaining := strings.TrimSpace(trimmed[5:])
return "plan", remaining, true
}
if strings.HasPrefix(lower, "/act") {
remaining := strings.TrimSpace(trimmed[4:])
return "act", remaining, true
}
return "", message, false
}
+7 -7
View File
@@ -19,20 +19,20 @@ import (
// Handles localhost/127.0.0.1 equivalence by returning both forms.
func normalizeAddressVariants(address string) []string {
variants := []string{address}
// Extract host and port
host, port, err := net.SplitHostPort(address)
if err != nil {
return variants
}
// Add the alternate form for localhost/127.0.0.1
if host == "localhost" {
variants = append(variants, net.JoinHostPort("127.0.0.1", port))
} else if host == "127.0.0.1" {
variants = append(variants, net.JoinHostPort("localhost", port))
}
return variants
}
@@ -182,7 +182,7 @@ func (lm *LockManager) GetInstanceInfo(address string) (*common.CoreInstanceInfo
query := common.SelectInstanceLockByHolderSQL
variants := normalizeAddressVariants(address)
var heldBy, lockTarget string
var lockedAt int64
var lastErr error
@@ -193,7 +193,7 @@ func (lm *LockManager) GetInstanceInfo(address string) (*common.CoreInstanceInfo
if err == nil {
// Found it!
return &common.CoreInstanceInfo{
Address: heldBy,
CoreAddress: heldBy,
HostServiceAddress: lockTarget,
Status: grpc_health_v1.HealthCheckResponse_UNKNOWN,
LastSeen: time.Unix(lockedAt/1000, 0),
@@ -204,7 +204,7 @@ func (lm *LockManager) GetInstanceInfo(address string) (*common.CoreInstanceInfo
lastErr = err
}
}
// None of the variants were found
if lastErr != nil {
return nil, fmt.Errorf("failed to query instance: %w", lastErr)
@@ -235,7 +235,7 @@ func (lm *LockManager) ListInstancesWithHealthCheck(ctx context.Context) ([]*com
}
info := &common.CoreInstanceInfo{
Address: lock.HeldBy,
CoreAddress: lock.HeldBy,
HostServiceAddress: lock.LockTarget,
Status: status,
LastSeen: time.Unix(lock.LockedAt/1000, 0),
+5 -10
View File
@@ -80,7 +80,7 @@ func ensureTaskManager(ctx context.Context, address string) error {
}
// Always set the instance we're using as the default
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
if err := registry.SetDefaultInstance(instanceAddress); err != nil {
// Log warning but don't fail - this is not critical
fmt.Printf("Warning: failed to set default instance: %v\n", err)
@@ -91,10 +91,10 @@ func ensureTaskManager(ctx context.Context, address string) error {
// ensureInstanceAtAddress ensures an instance exists at the given address
func ensureInstanceAtAddress(ctx context.Context, address string) error {
if global.Clients == nil {
if global.Instances == nil {
return fmt.Errorf("global clients not initialized")
}
return global.Clients.EnsureInstanceAtAddress(ctx, address)
return global.Instances.EnsureInstanceAtAddress(ctx, address)
}
func newTaskNewCommand() *cobra.Command {
@@ -117,7 +117,7 @@ func newTaskNewCommand() *cobra.Command {
ctx := cmd.Context()
// Check if an instance exists when no address specified
if address == "" && global.Clients.GetRegistry().GetDefaultInstance() == "" {
if address == "" && global.Instances.GetRegistry().GetDefaultInstance() == "" {
fmt.Println("No instances available for creating tasks")
return nil
}
@@ -230,7 +230,7 @@ func newTaskSendCommand() *cobra.Command {
ctx := cmd.Context()
// Check if an instance exists when no address specified
if address == "" && global.Clients.GetRegistry().GetDefaultInstance() == "" {
if address == "" && global.Instances.GetRegistry().GetDefaultInstance() == "" {
fmt.Println("No instances available for sending messages")
return nil
}
@@ -620,11 +620,6 @@ func CleanupTaskManager() {
}
}
// NewTaskManagerForAddress is an exported wrapper around task.NewManagerForAddress
func NewTaskManagerForAddress(ctx context.Context, address string) (*task.Manager, error) {
return task.NewManagerForAddress(ctx, address)
}
// CreateAndFollowTask creates a new task and immediately follows it in interactive mode
// This is used by the root command to provide a streamlined UX
func CreateAndFollowTask(ctx context.Context, prompt string, opts TaskOptions) error {
+29 -44
View File
@@ -13,38 +13,41 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/output"
"github.com/cline/cli/pkg/cli/slash"
"github.com/cline/cli/pkg/cli/types"
)
// InputHandler manages interactive user input during follow mode
type InputHandler struct {
manager *Manager
coordinator *StreamCoordinator
cancelFunc context.CancelFunc
mu sync.RWMutex
isRunning bool
pollTicker *time.Ticker
program *tea.Program
programRunning bool
programDoneChan chan struct{} // Signals when program actually exits
resultChan chan output.InputSubmitMsg
cancelChan chan struct{}
feedbackApproval bool // Track if we're in feedback after approval
feedbackApproved bool // Track the approval decision
approvalMessage *types.ClineMessage // Store the approval message for determining action
ctx context.Context // Context for restart callback
manager *Manager
coordinator *StreamCoordinator
cancelFunc context.CancelFunc
mu sync.RWMutex
isRunning bool
pollTicker *time.Ticker
program *tea.Program
programRunning bool
programDoneChan chan struct{} // Signals when program actually exits
resultChan chan output.InputSubmitMsg
cancelChan chan struct{}
feedbackApproval bool // Track if we're in feedback after approval
feedbackApproved bool // Track the approval decision
approvalMessage *types.ClineMessage // Store the approval message for determining action
slashCommandRegistry *slash.Registry // Slash command registry for autocomplete
ctx context.Context // Context for restart callback
}
// NewInputHandler creates a new input handler
func NewInputHandler(manager *Manager, coordinator *StreamCoordinator, cancelFunc context.CancelFunc) *InputHandler {
return &InputHandler{
manager: manager,
coordinator: coordinator,
cancelFunc: cancelFunc,
isRunning: false,
pollTicker: time.NewTicker(500 * time.Millisecond),
resultChan: make(chan output.InputSubmitMsg, 1),
cancelChan: make(chan struct{}, 1),
manager: manager,
coordinator: coordinator,
cancelFunc: cancelFunc,
isRunning: false,
pollTicker: time.NewTicker(500 * time.Millisecond),
resultChan: make(chan output.InputSubmitMsg, 1),
slashCommandRegistry: slash.NewRegistry(context.Background()),
cancelChan: make(chan struct{}, 1),
}
}
@@ -163,7 +166,7 @@ func (ih *InputHandler) Start(ctx context.Context, errChan chan error) {
if shouldSend {
// Check for mode switch commands first
newMode, remainingMessage, isModeSwitch := ih.parseModeSwitch(message)
newMode, remainingMessage, isModeSwitch := slash.ParseModeSwitch(message)
if isModeSwitch {
// Create styles for mode switch messages (respect global color profile)
actStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Bold(true)
@@ -284,9 +287,9 @@ func (ih *InputHandler) promptForInput(ctx context.Context) (string, bool, error
model := output.NewInputModelWithRegistry(
output.InputTypeMessage,
"Cline is ready for your message...",
"/plan or /act to switch modes\nctrl+e to open editor\ntab to autocomplete commands",
"/plan or /act to switch modes\ntab to autocomplete commands\nctrl+e to open editor\nctrl+c to exit",
currentMode,
ih.manager.GetSlashRegistry(),
ih.slashCommandRegistry,
)
return ih.runInputProgram(ctx, model)
@@ -302,7 +305,7 @@ func (ih *InputHandler) promptForApproval(ctx context.Context, msg *types.ClineM
"Let Cline use this tool?",
"",
ih.manager.GetCurrentMode(),
ih.manager.GetSlashRegistry(), // Pass registry for feedback input after approval
ih.slashCommandRegistry,
)
message, shouldSend, err := ih.runInputProgram(ctx, model)
@@ -478,24 +481,6 @@ func (w *inputProgramWrapper) View() string {
return w.model.View()
}
// parseModeSwitch checks if message starts with /act or /plan and extracts the mode and remaining message
func (ih *InputHandler) parseModeSwitch(message string) (string, string, bool) {
trimmed := strings.TrimSpace(message)
lower := strings.ToLower(trimmed)
if strings.HasPrefix(lower, "/plan") {
remaining := strings.TrimSpace(trimmed[5:])
return "plan", remaining, true
}
if strings.HasPrefix(lower, "/act") {
remaining := strings.TrimSpace(trimmed[4:])
return "act", remaining, true
}
return "", message, false
}
// handleSpecialCommand processes special commands like /cancel, /exit
func (ih *InputHandler) handleSpecialCommand(ctx context.Context, message string) bool {
switch strings.ToLower(strings.TrimSpace(message)) {
+7 -34
View File
@@ -14,7 +14,6 @@ import (
"github.com/cline/cli/pkg/cli/display"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/handlers"
"github.com/cline/cli/pkg/cli/slash"
"github.com/cline/cli/pkg/cli/types"
"github.com/cline/grpc-go/client"
"github.com/cline/grpc-go/cline"
@@ -38,7 +37,6 @@ type Manager struct {
systemRenderer *display.SystemMessageRenderer
streamingDisplay *display.StreamingDisplay
handlerRegistry *handlers.HandlerRegistry
slashRegistry *slash.Registry
isStreamingMode bool
isInteractive bool
currentMode string // "plan" or "act"
@@ -68,7 +66,6 @@ func NewManager(client *client.ClineClient) *Manager {
systemRenderer: systemRenderer,
streamingDisplay: streamingDisplay,
handlerRegistry: registry,
slashRegistry: slash.NewRegistry(),
currentMode: "plan", // Default mode
}
}
@@ -83,9 +80,6 @@ func NewManagerForAddress(ctx context.Context, address string) (*Manager, error)
manager := NewManager(client)
manager.clientAddress = address
// Fetch slash commands from backend (non-blocking, errors are logged)
manager.fetchSlashCommands(ctx)
return manager, nil
}
@@ -99,29 +93,13 @@ func NewManagerForDefault(ctx context.Context) (*Manager, error) {
manager := NewManager(client)
// Get the default instance address
if global.Clients != nil {
manager.clientAddress = global.Clients.GetRegistry().GetDefaultInstance()
if global.Instances != nil {
manager.clientAddress = global.Instances.GetRegistry().GetDefaultInstance()
}
// Fetch slash commands from backend (non-blocking, errors are logged)
manager.fetchSlashCommands(ctx)
return manager, nil
}
// fetchSlashCommands fetches available slash commands from the backend
// This is non-blocking and errors are logged but don't prevent manager creation
func (m *Manager) fetchSlashCommands(ctx context.Context) {
if err := m.slashRegistry.FetchFromBackend(ctx, m.client); err != nil {
if global.Config.Verbose {
m.renderer.RenderDebug("Failed to fetch slash commands: %v", err)
}
// Non-fatal: CLI-local commands are still available
} else if global.Config.Verbose {
m.renderer.RenderDebug("Loaded %d slash commands", len(m.slashRegistry.GetCommands()))
}
}
// SwitchToInstance switches the manager to use a different Cline instance
func (m *Manager) SwitchToInstance(ctx context.Context, address string) error {
m.mu.Lock()
@@ -1206,11 +1184,11 @@ func (m *Manager) displayMessage(msg *types.ClineMessage, isLast, isPartial bool
m.mu.RUnlock()
dc := &handlers.DisplayContext{
State: m.state,
Renderer: m.renderer,
ToolRenderer: m.toolRenderer,
HookRenderer: m.hookRenderer,
SystemRenderer: m.systemRenderer,
State: m.state,
Renderer: m.renderer,
ToolRenderer: m.toolRenderer,
HookRenderer: m.hookRenderer,
SystemRenderer: m.systemRenderer,
IsLast: isLast,
IsPartial: isPartial,
Verbose: global.Config.Verbose,
@@ -1320,11 +1298,6 @@ func (m *Manager) GetCurrentMode() string {
return m.currentMode
}
// GetSlashRegistry returns the slash command registry
func (m *Manager) GetSlashRegistry() *slash.Registry {
return m.slashRegistry
}
// extractModeFromState extracts the current mode from state JSON
func (m *Manager) extractModeFromState(stateJson string) string {
var rawState map[string]interface{}
-6
View File
@@ -290,12 +290,6 @@ func setSimpleField(settings *cline.Settings, key, value string) error {
return err
}
settings.ActModeAwsBedrockCustomSelected = boolPtr(val)
case "hooks_enabled":
val, err := parseBool(value)
if err != nil {
return err
}
settings.HooksEnabled = boolPtr(val)
case "azure_identity":
val, err := parseBool(value)
if err != nil {
+2 -2
View File
@@ -10,7 +10,7 @@ import (
// This is the canonical definition used across all CLI packages
type CoreInstanceInfo struct {
// Full core address including port
Address string `json:"address"`
CoreAddress string `json:"address"`
// Host bridge service address that core holds (host is ALWAYS running on localhost FYI)
HostServiceAddress string `json:"host_port"`
Status grpc_health_v1.HealthCheckResponse_ServingStatus `json:"status"`
@@ -20,7 +20,7 @@ type CoreInstanceInfo struct {
}
func (c *CoreInstanceInfo) CorePort() int {
_, port, _ := ParseHostPort(c.Address)
_, port, _ := ParseHostPort(c.CoreAddress)
return port
}
-39
View File
@@ -1,39 +0,0 @@
package hostbridge
import (
"log"
"github.com/cline/grpc-go/host"
)
// WatchService implements the host.WatchServiceServer interface
type WatchService struct {
host.UnimplementedWatchServiceServer
coreAddress string
verbose bool
}
// NewWatchService creates a new WatchService
func NewWatchService(coreAddress string, verbose bool) *WatchService {
return &WatchService{
coreAddress: coreAddress,
verbose: verbose,
}
}
// SubscribeToFile subscribes to file change notifications
func (s *WatchService) SubscribeToFile(req *host.SubscribeToFileRequest, stream host.WatchService_SubscribeToFileServer) error {
if s.verbose {
log.Printf("SubscribeToFile called for path: %s", req.GetPath())
}
// For console implementation, we'll just log that we would watch the file
// In a real implementation, we'd use fsnotify or similar to watch file changes
log.Printf("[Cline] Would watch file: %s", req.GetPath())
// Keep the stream open but don't send any events for now
// In a real implementation, we'd send FileChangeEvent messages when files change
<-stream.Context().Done()
return nil
}
+6 -4
View File
@@ -5,6 +5,7 @@ import (
"log"
"os"
"github.com/cline/grpc-go/cline"
"github.com/cline/grpc-go/host"
)
@@ -43,24 +44,25 @@ func (s *WorkspaceService) GetWorkspacePaths(ctx context.Context, req *host.GetW
// SaveOpenDocumentIfDirty saves an open document if it has unsaved changes
func (s *WorkspaceService) SaveOpenDocumentIfDirty(ctx context.Context, req *host.SaveOpenDocumentIfDirtyRequest) (*host.SaveOpenDocumentIfDirtyResponse, error) {
if s.verbose {
log.Printf("SaveOpenDocumentIfDirty called for path: %s", req.GetPath())
log.Printf("SaveOpenDocumentIfDirty called for path: %v", req.FilePath)
}
// For console implementation, we'll assume the document is already saved
// In a real implementation, we'd check if the file has unsaved changes
f := false
return &host.SaveOpenDocumentIfDirtyResponse{
WasSaved: false, // Assume no changes to save
WasSaved: &f, // Assume no changes to save
}, nil
}
// GetDiagnostics returns diagnostic information for a file
func (s *WorkspaceService) GetDiagnostics(ctx context.Context, req *host.GetDiagnosticsRequest) (*host.GetDiagnosticsResponse, error) {
if s.verbose {
log.Printf("GetDiagnostics called for path: %s", req.GetPath())
log.Printf("GetDiagnostics called")
}
// For console implementation, return empty diagnostics
return &host.GetDiagnosticsResponse{
Diagnostics: []*host.Diagnostic{},
FileDiagnostics: []*cline.FileDiagnostics{},
}, nil
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 MiB

+2
View File
@@ -148,6 +148,7 @@
"features/hooks/samples"
]
},
"features/jupyter-notebooks",
"features/multiroot-workspace",
"features/plan-and-act",
"features/skills",
@@ -198,6 +199,7 @@
"provider-config/anthropic",
"provider-config/claude-code",
"provider-config/openai",
"provider-config/openai-codex",
"provider-config/openrouter",
"provider-config/cerebras",
"provider-config/deepseek",
+112
View File
@@ -0,0 +1,112 @@
---
title: "Jupyter Notebooks"
description: "AI-assisted editing of Jupyter notebooks with cell-level context awareness"
---
# Jupyter Notebooks
Cline provides comprehensive support for Jupyter notebooks (`.ipynb` files), enabling AI-assisted editing with full cell-level context awareness. This feature was developed in collaboration with Amazon to bring AI coding assistance to data science workflows.
## Getting started
Jupyter notebook support is a built-in feature of Cline. To use it, you just need to have the Jupyter notebook extension enabled in VS Code. Once you open any `.ipynb` file, you'll see AI-assisted buttons in your notebook interface.
## How to use
### Generate Cell
Click the sparkle icon (✨) in the notebook toolbar to generate new cells with AI assistance.
![Generate Cell demo](../assets/jupyter-generate-cell.gif)
**How it works:** The AI receives context from surrounding cells, so it understands the variables and imports already in scope. This means you can reference existing DataFrames, functions, and other objects without re-explaining them.
**Example prompt:** "Create a visualization showing the correlation matrix of numeric columns with a heatmap"
The cell is inserted with proper notebook JSON structure, preserving metadata and ready to execute.
### Explain Cell
Click the Explain button in any cell's title bar to get a detailed explanation of what the cell does.
This is useful for:
- Revisiting old notebooks
- Onboarding to a teammate's analysis
- Understanding complex transformations
**How it works:** Cline extracts the full cell context, including outputs, so explanations can reference actual results like column names, row counts, and computed values.
### Improve Cell
Click the Improve button in any cell's title bar to enhance existing cells with AI suggestions.
![Explain and Improve Cell demo](../assets/jupyter-explain-improve-cell.gif)
Use this to:
- Optimize slow pandas operations
- Add error handling
- Refactor for clarity
- Convert loops to vectorized operations
**How it works:** Cline suggests improvements while preserving the cell's position and metadata in the notebook structure. The AI explains what was changed and why.
## How cell context works
Unlike traditional file editing, Jupyter notebooks are JSON documents containing arrays of cells. Each cell has its own type, source content, metadata, execution count, and outputs.
When you use a Jupyter command, Cline extracts structured context that includes:
- **Cell type** (code, markdown, or raw)
- **Source content** as an array of lines
- **Cell metadata** and configuration
- **Execution count** for code cells
- **Outputs** including data, text, and error traces
This structured representation allows the AI to understand not just the code, but its context within the notebook and its actual output.
### JSON structure preservation
Cline is designed to work carefully with the cell JSON structure, aiming to:
- Keep cell boundaries intact
- Preserve execution counts
- Maintain cell metadata
- Keep outputs associated with their source cells
The AI is specifically prompted to preserve notebook structure, though you should always review changes to ensure your notebook format remains correct.
## Keyboard shortcuts
You can bind any of these commands to keyboard shortcuts for faster access:
1. Open VS Code keyboard shortcuts (Cmd/Ctrl + K, Cmd/Ctrl + S)
2. Search for `cline.jupyterGenerateCell`, `cline.jupyterExplainCell`, or `cline.jupyterImproveCell`
3. Assign your preferred key combinations
## Tips for best results
**For Generate Cell:**
- Be specific about what you want the cell to do
- Reference existing variables by name (the AI can see them)
- Mention preferred libraries if you have a preference (e.g., "use seaborn" or "use plotly")
**For Explain Cell:**
- Works best on cells that have been executed (outputs provide additional context)
- Good for complex chained operations like pandas groupby/merge sequences
**For Improve Cell:**
- Mention what aspect you want to improve (performance, readability, error handling)
- The AI will explain the changes it suggests
## Limitations
- Notebook support requires the Jupyter notebook extension to be enabled in VS Code
- Cell context extraction depends on VS Code's notebook API
- Very large notebooks may require more context than some models can handle efficiently
## Related
- [Cline Tools Guide](/exploring-clines-tools/cline-tools-guide) -- Overview of all Cline commands
- [Model Selection Guide](/core-features/model-selection-guide) -- Choosing the right model for your workflow
+19 -2
View File
@@ -25,11 +25,28 @@ The easiest way to get started is with **Cline** as your provider:
## Alternative: Use Another Provider
If you prefer to use your own API keys, you can select from providers like:
If you prefer to use your own accounts or API keys, you have several options:
### Sign in with OpenAI (Recommended)
The easiest way to use OpenAI models is with **OpenAI Codex**—no API keys needed:
1. Select **"OpenAI Codex"** from the API Provider dropdown
2. Click **"Sign in with OpenAI"**
3. Authorize Cline in your browser
4. Choose your model
<Card title="OpenAI Codex Setup Guide" icon="key" href="/provider-config/openai-codex">
See the full setup guide with screenshots and troubleshooting tips.
</Card>
### Other Providers
You can also use API keys with these providers:
- **OpenRouter** - Great value, multiple models
- **Anthropic** - Direct access to Claude models
- **OpenAI** - Access to GPT models
- **OpenAI** - Access to GPT models via API key
- **Google Gemini** - Google's AI models
- **Ollama** - Run models locally on your computer
-1
View File
@@ -18,7 +18,6 @@ Cerebras delivers the world's fastest AI inference through their revolutionary w
Cline supports the following Cerebras models:
- `zai-glm-4.6` - Fast general-purpose model on Cerebras (up to 1,000 tokens/s). To be deprecated soon.
- `zai-glm-4.7` - Highly capable general-purpose model on Cerebras (up to 1,000 tokens/s), competitive with leading proprietary models on coding tasks.
- `qwen-3-235b-a22b-instruct-2507` - Advanced instruction-following model
- `qwen-3-235b-a22b-thinking-2507` - Reasoning model with step-by-step thinking
+105
View File
@@ -0,0 +1,105 @@
---
title: "OpenAI Codex"
sidebarTitle: "OpenAI Codex"
description: "Connect your OpenAI account to Cline via OAuth for seamless access to OpenAI models."
---
OpenAI Codex lets you connect your OpenAI account directly to Cline using OAuth, meaning you don't need to manage API keys. Simply sign in with your OpenAI account through a one-click browser authentication, and you'll automatically have access to all the models available on your OpenAI plan. No additional configuration required.
<Tip>
If you prefer to use API keys instead, see the [OpenAI (API Key)](/provider-config/openai) provider configuration.
</Tip>
## Prerequisites
- An [OpenAI account](https://platform.openai.com/signup)
- An active OpenAI subscription or API access plan
<Note>
The models available to you depend on your OpenAI account's subscription tier. See [OpenAI's pricing page](https://openai.com/pricing) for details on what's included in each plan.
</Note>
## Connecting Your OpenAI Account
<Steps>
<Step title="Open Cline Settings">
Click the settings gear icon in the top-right corner of the Cline extension.
</Step>
<Step title="Select OpenAI Codex Provider">
Choose **"OpenAI Codex"** from the "API Provider" dropdown menu.
</Step>
<Step title="Sign In with OpenAI">
Click the **"Sign in with OpenAI"** button. This will open a browser window for authentication.
</Step>
<Step title="Authorize Cline">
In the browser window that opens:
1. Sign in to your OpenAI account (if not already signed in)
2. Review the permissions Cline is requesting
3. Click **"Authorize"** to grant access
</Step>
<Step title="Select Your Model">
Once authorized, you'll be returned to Cline. Choose your desired model from the **"Model"** dropdown.
</Step>
</Steps>
<Note>
If a model doesn't appear in your dropdown, it may not be available on your OpenAI subscription tier.
</Note>
For the most current list of available models and their capabilities, see the official [OpenAI Models documentation](https://platform.openai.com/docs/models).
## Managing Your Connection
### Disconnecting Your Account
To disconnect your OpenAI account from Cline:
1. Open Cline Settings
2. With OpenAI Codex selected as the provider, click **"Sign out"**
3. Confirm the disconnection
This removes the OAuth connection. You can reconnect at any time by signing in again.
### Token Refresh
OAuth tokens are automatically refreshed by Cline. If you encounter authentication errors:
1. Try disconnecting and reconnecting your account
2. Ensure your OpenAI account is in good standing
3. Check that your subscription is active
## Troubleshooting
### "Authentication failed" error
- Ensure you're signing in with the correct OpenAI account
- Check that your OpenAI subscription is active
- Try clearing your browser cache and signing in again
### Models not appearing
- The available models depend on your OpenAI subscription tier
- Some models may require specific plan upgrades
- Check [OpenAI's pricing page](https://openai.com/pricing) for model availability by plan
### OAuth window doesn't open
- Check if pop-ups are blocked in your browser
- Try using a different browser
- Ensure you have a stable internet connection
### Connection keeps expiring
- This is rare but can happen if your OpenAI account session expired
- Disconnect and reconnect to refresh your authentication
## Good to Know
Cline only requests the permissions necessary to make API calls on your behalf. Your OpenAI credentials are never stored by Cline.
When using OpenAI Codex, Cline accesses models through your OpenAI/ChatGPT subscription. There is no separate per-token API billing or consumption of OpenAI API credits for this provider; usage and limits are governed by your ChatGPT (or OpenAI account) subscription plan, not by Cline.
## Related Documentation
- [OpenAI (API Key)](/provider-config/openai): Alternative configuration using API keys
- [Model Selection Guide](/core-features/model-selection-guide): Help choosing the right model
+4 -4
View File
@@ -73,16 +73,16 @@ Open VS Code and configure Cline:
### Recommended Models
For the best experience with Cline, use **Qwen3 Coder 30B**. This model provides strong coding capabilities and reliable tool use for local development.
For the best experience with Cline, use **Qwen 2.5 Coder 32B**. This model provides strong coding capabilities and reliable tool use for local development.
To download it:
```bash
ollama run qwen3-coder-30b
ollama pull qwen2.5-coder:32b
```
Other capable models include:
- `mistral-small` - Good balance of performance and speed
- `devstral-small` - Optimized for coding tasks
- `mistral-small:latest` - Good balance of performance and speed
- `codellama:34b-code` - Optimized for coding tasks
### Important Notes
@@ -1,99 +0,0 @@
---
description: Use Bun instead of Node.js, npm, pnpm, or vite.
globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json"
alwaysApply: false
---
Default to using Bun instead of Node.js.
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
- Use `bun test` instead of `jest` or `vitest`
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
- Bun automatically loads .env, so don't use dotenv.
## APIs
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
- `Bun.redis` for Redis. Don't use `ioredis`.
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
- `WebSocket` is built-in. Don't use `ws`.
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
- Bun.$`ls` instead of execa.
## Frontend
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
Server:
```ts#index.ts
import index from "./index.html"
Bun.serve({
routes: {
"/": index,
"/api/users/:id": {
GET: (req) => {
return new Response(JSON.stringify({ id: req.params.id }));
},
},
},
// optional websocket support
websocket: {
open: (ws) => {
ws.send("Hello, world!");
},
message: (ws, message) => {
ws.send(message);
},
close: (ws) => {
// handle close
}
},
development: {
hmr: true,
console: true,
}
})
```
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
```html#index.html
<html>
<body>
<h1>Hello, world!</h1>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>
```
With the following `frontend.tsx`:
```tsx#frontend.tsx
import React from "react";
// import .css files directly and it works
import './index.css';
import { createRoot } from "react-dom/client";
const root = createRoot(document.body);
export default function Frontend() {
return <h1>Hello, world!</h1>;
}
root.render(<Frontend />);
```
Then, run index.ts
```sh
bun --hot ./index.ts
```
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
-34
View File
@@ -1,34 +0,0 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
-15
View File
@@ -1,15 +0,0 @@
# @cline/models
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.2.16. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
-25
View File
@@ -1,25 +0,0 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "@cline/models",
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
}
}
-1
View File
@@ -1 +0,0 @@
console.log("Hello via Bun!")
-11
View File
@@ -1,11 +0,0 @@
{
"name": "@cline/models",
"module": "index.ts",
"type": "module",
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
}
}
-8
View File
@@ -1,8 +0,0 @@
// Import all providers to trigger registration
import "./providers/anthropic"
import "./providers/openai"
import "./providers/gemini"
// Export the registry and types for client use
export { ModelRegistry, modelRegistry, type ProviderModels, type RegistryOutput } from "./registry"
export { ApiFormat, type ModelInfo } from "./types"
-8
View File
@@ -1,8 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "aihubmix"
const MODELS: Record<string, ModelInfo> = {}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-201
View File
@@ -1,201 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "anthropic"
const ANTHROPIC_MODELS: Record<string, ModelInfo> = {
"claude-sonnet-4-5-20250929": {
contextWindow: 200_000,
maxOutputTokens: 8192,
pricing: {
input: 3.0,
output: 15.0,
cacheWrite: 3.75,
cacheRead: 0.3,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: { enabled: true },
},
"claude-sonnet-4-5-20250929:1m": {
contextWindow: 1_000_000,
maxOutputTokens: 8192,
pricing: {
input: 3.0,
output: 15.0,
cacheWrite: 3.75,
cacheRead: 0.3,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: { enabled: true },
},
"claude-haiku-4-5-20251001": {
contextWindow: 200_000,
maxOutputTokens: 8192,
pricing: {
input: 1.0,
output: 5.0,
cacheWrite: 1.25,
cacheRead: 0.1,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: { enabled: true },
},
"claude-sonnet-4-20250514": {
contextWindow: 200_000,
maxOutputTokens: 8192,
pricing: {
input: 3.0,
output: 15.0,
cacheWrite: 3.75,
cacheRead: 0.3,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: { enabled: true },
},
"claude-sonnet-4-20250514:1m": {
contextWindow: 1_000_000,
maxOutputTokens: 8192,
pricing: {
input: 3.0,
output: 15.0,
cacheWrite: 3.75,
cacheRead: 0.3,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: { enabled: true },
},
"claude-opus-4-5-20251101": {
contextWindow: 200_000,
maxOutputTokens: 8192,
pricing: {
input: 5.0,
output: 25.0,
cacheWrite: 6.25,
cacheRead: 0.5,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: { enabled: true },
},
"claude-opus-4-1-20250805": {
contextWindow: 200_000,
maxOutputTokens: 8192,
pricing: {
input: 15.0,
output: 75.0,
cacheWrite: 18.75,
cacheRead: 1.5,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: { enabled: true },
},
"claude-opus-4-20250514": {
contextWindow: 200_000,
maxOutputTokens: 8192,
pricing: {
input: 15.0,
output: 75.0,
cacheWrite: 18.75,
cacheRead: 1.5,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: { enabled: true },
},
"claude-3-7-sonnet-20250219": {
contextWindow: 200_000,
maxOutputTokens: 8192,
pricing: {
input: 3.0,
output: 15.0,
cacheWrite: 3.75,
cacheRead: 0.3,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: { enabled: true },
},
"claude-3-5-sonnet-20241022": {
contextWindow: 200_000,
maxOutputTokens: 8192,
pricing: {
input: 3.0,
output: 15.0,
cacheWrite: 3.75,
cacheRead: 0.3,
},
capabilities: {
images: true,
promptCache: true,
},
},
"claude-3-5-haiku-20241022": {
contextWindow: 200_000,
maxOutputTokens: 8192,
pricing: {
input: 0.8,
output: 4.0,
cacheWrite: 1.0,
cacheRead: 0.08,
},
capabilities: {
images: true,
promptCache: true,
},
},
"claude-3-opus-20240229": {
contextWindow: 200_000,
maxOutputTokens: 4096,
pricing: {
input: 15.0,
output: 75.0,
cacheWrite: 18.75,
cacheRead: 1.5,
},
capabilities: {
images: true,
promptCache: true,
},
},
"claude-3-haiku-20240307": {
contextWindow: 200_000,
maxOutputTokens: 4096,
pricing: {
input: 0.25,
output: 1.25,
cacheWrite: 0.3,
cacheRead: 0.03,
},
capabilities: {
images: true,
promptCache: true,
},
},
}
// Register Anthropic models with the registry
modelRegistry.registerProvider(PROVIDER_NAME, ANTHROPIC_MODELS)
-177
View File
@@ -1,177 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "asksage"
const MODELS: Record<string, ModelInfo> = {
"gpt-4o": {
maxOutputTokens: 4096,
contextWindow: 128_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"gpt-4o-gov": {
maxOutputTokens: 4096,
contextWindow: 128_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"gpt-4.1": {
maxOutputTokens: 32_768,
contextWindow: 1_047_576,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"claude-35-sonnet": {
maxOutputTokens: 8192,
contextWindow: 200_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"aws-bedrock-claude-35-sonnet-gov": {
maxOutputTokens: 8192,
contextWindow: 200_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"claude-37-sonnet": {
maxOutputTokens: 8192,
contextWindow: 200_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"claude-4-sonnet": {
maxOutputTokens: 8192,
contextWindow: 200_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"claude-4-opus": {
maxOutputTokens: 8192,
contextWindow: 200_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"google-gemini-2.5-pro": {
maxOutputTokens: 65536,
contextWindow: 1_048_576,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"google-claude-45-sonnet": {
maxOutputTokens: 64000,
contextWindow: 200_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"google-claude-4-opus": {
maxOutputTokens: 32000,
contextWindow: 200_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"gpt-5": {
maxOutputTokens: 65536,
contextWindow: 2_097_152,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"gpt-5-mini": {
maxOutputTokens: 32768,
contextWindow: 1_048_576,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"gpt-5-nano": {
maxOutputTokens: 16384,
contextWindow: 262_144,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-8
View File
@@ -1,8 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "azure"
const MODELS: Record<string, ModelInfo> = {}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-176
View File
@@ -1,176 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "baseten"
const MODELS: Record<string, ModelInfo> = {
"zai-org/GLM-4.6": {
maxOutputTokens: 200_000,
contextWindow: 200_000,
pricing: {
input: 0.6,
output: 2.2,
},
capabilities: {
images: false,
promptCache: false,
},
reasoning: {
enabled: true,
},
description: "Frontier open model with advanced agentic, reasoning and coding capabilities",
},
"moonshotai/Kimi-K2-Thinking": {
maxOutputTokens: 163_800,
contextWindow: 262_000,
pricing: {
input: 0.6,
output: 2.5,
},
capabilities: {
images: false,
promptCache: false,
},
reasoning: {
enabled: true,
},
description: "Kimi K2 Thinking - A model with enhanced reasoning capabilities from Kimi K2",
},
"deepseek-ai/DeepSeek-R1": {
maxOutputTokens: 131_072,
contextWindow: 163_840,
pricing: {
input: 2.55,
output: 5.95,
},
capabilities: {
images: false,
promptCache: false,
},
reasoning: {
enabled: true,
},
description: "DeepSeek's first-generation reasoning model",
},
"deepseek-ai/DeepSeek-R1-0528": {
maxOutputTokens: 131_072,
contextWindow: 163_840,
pricing: {
input: 2.55,
output: 5.95,
},
capabilities: {
images: false,
promptCache: false,
},
reasoning: {
enabled: true,
},
description: "The latest revision of DeepSeek's first-generation reasoning model",
},
"deepseek-ai/DeepSeek-V3-0324": {
maxOutputTokens: 131_072,
contextWindow: 163_840,
pricing: {
input: 0.77,
output: 0.77,
},
capabilities: {
images: false,
promptCache: false,
},
reasoning: {
enabled: true,
},
description: "Fast general-purpose LLM with enhanced reasoning capabilities",
},
"deepseek-ai/DeepSeek-V3.1": {
maxOutputTokens: 131_072,
contextWindow: 163_840,
pricing: {
input: 0.5,
output: 1.5,
},
capabilities: {
images: false,
promptCache: false,
},
reasoning: {
enabled: true,
},
description: "Extremely capable general-purpose LLM with hybrid reasoning capabilities and advanced tool calling",
},
"deepseek-ai/DeepSeek-V3.2": {
maxOutputTokens: 131_072,
contextWindow: 163_840,
pricing: {
input: 0.3,
output: 0.45,
},
capabilities: {
images: false,
promptCache: false,
},
reasoning: {
enabled: true,
},
description: "DeepSeek's hybrid reasoning model with efficient long context scaling with GPT-5 level performance",
},
"Qwen/Qwen3-235B-A22B-Instruct-2507": {
maxOutputTokens: 262_144,
contextWindow: 262_144,
pricing: {
input: 0.22,
output: 0.8,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Mixture-of-experts LLM with math and reasoning capabilities",
},
"Qwen/Qwen3-Coder-480B-A35B-Instruct": {
maxOutputTokens: 262_144,
contextWindow: 262_144,
pricing: {
input: 0.38,
output: 1.53,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Mixture-of-experts LLM with advanced coding and reasoning capabilities",
},
"openai/gpt-oss-120b": {
maxOutputTokens: 128_072,
contextWindow: 128_072,
pricing: {
input: 0.1,
output: 0.5,
},
capabilities: {
images: false,
promptCache: false,
},
reasoning: {
enabled: true,
},
description: "Extremely capable general-purpose LLM with strong, controllable reasoning capabilities",
},
"moonshotai/Kimi-K2-Instruct-0905": {
maxOutputTokens: 168_000,
contextWindow: 262_000,
pricing: {
input: 0.6,
output: 2.5,
},
capabilities: {
images: false,
promptCache: false,
},
description: "State of the art language model for agentic and coding tasks. September Update.",
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-8
View File
@@ -1,8 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "bedrock"
const MODELS: Record<string, ModelInfo> = {}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-88
View File
@@ -1,88 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "cerebras"
const MODELS: Record<string, ModelInfo> = {
"zai-glm-4.6": {
maxOutputTokens: 40000,
contextWindow: 131072,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Fast general-purpose model on Cerebras (up to 1,000 tokens/s). To be deprecated soon.",
},
"zai-glm-4.7": {
maxOutputTokens: 40000,
contextWindow: 131072,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description:
"Highly capable general-purpose model on Cerebras (up to 1,000 tokens/s), competitive with leading proprietary models on coding tasks.",
},
"gpt-oss-120b": {
maxOutputTokens: 65536,
contextWindow: 128000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Intelligent general purpose model with 3,000 tokens/s",
},
"qwen-3-235b-a22b-instruct-2507": {
maxOutputTokens: 64000,
contextWindow: 64000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Intelligent model with ~1400 tokens/s",
},
"llama-3.3-70b": {
maxOutputTokens: 64000,
contextWindow: 64000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Powerful model with ~2600 tokens/s",
},
"qwen-3-32b": {
maxOutputTokens: 64000,
contextWindow: 64000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description: "SOTA coding performance with ~2500 tokens/s",
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-8
View File
@@ -1,8 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "claude-code"
const MODELS: Record<string, ModelInfo> = {}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-37
View File
@@ -1,37 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "deepseek"
const MODELS: Record<string, ModelInfo> = {
"deepseek-chat": {
maxOutputTokens: 8_000,
contextWindow: 128_000,
pricing: {
input: 0, // technically there is no input price, it's all either a cache hit or miss (ApiOptions will not show this). Input is the sum of cache reads and writes
output: 1.1,
cacheWrite: 0.27,
cacheRead: 0.07,
},
capabilities: {
images: false,
promptCache: true, // supports context caching, but not in the way anthropic does it (deepseek reports input tokens and reads/writes in the same usage report) FIXME: we need to show users cache stats how deepseek does it
},
},
"deepseek-reasoner": {
maxOutputTokens: 8_000,
contextWindow: 128_000,
pricing: {
input: 0, // technically there is no input price, it's all either a cache hit or miss (ApiOptions will not show this)
output: 2.19,
cacheWrite: 0.55,
cacheRead: 0.14,
},
capabilities: {
images: false,
promptCache: true, // supports context caching, but not in the way anthropic does it (deepseek reports input tokens and reads/writes in the same usage report) FIXME: we need to show users cache stats how deepseek does it
},
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-8
View File
@@ -1,8 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "dify"
const MODELS: Record<string, ModelInfo> = {}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-57
View File
@@ -1,57 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "doubao"
const MODELS: Record<string, ModelInfo> = {
"doubao-1-5-pro-256k-250115": {
maxOutputTokens: 12_288,
contextWindow: 256_000,
pricing: {
input: 0.7,
output: 1.3,
},
capabilities: {
images: false,
promptCache: false,
},
},
"doubao-1-5-pro-32k-250115": {
maxOutputTokens: 12_288,
contextWindow: 32_000,
pricing: {
input: 0.11,
output: 0.3,
},
capabilities: {
images: false,
promptCache: false,
},
},
"deepseek-v3-250324": {
maxOutputTokens: 12_288,
contextWindow: 128_000,
pricing: {
input: 0.55,
output: 2.19,
},
capabilities: {
images: false,
promptCache: false,
},
},
"deepseek-r1-250120": {
maxOutputTokens: 32_768,
contextWindow: 64_000,
pricing: {
input: 0.27,
output: 1.09,
},
capabilities: {
images: false,
promptCache: false,
},
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-78
View File
@@ -1,78 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "fireworks"
const MODELS: Record<string, ModelInfo> = {
"accounts/fireworks/models/kimi-k2-instruct-0905": {
maxOutputTokens: 16384,
contextWindow: 262144,
pricing: {
input: 0.6,
output: 2.5,
cacheRead: 0.15,
},
capabilities: {
images: false,
promptCache: true,
},
description:
"Kimi K2 model gets a new version update: Agentic coding: more accurate, better generalization across scaffolds. Frontend coding: improved aesthetics and functionalities on web, 3d, and other tasks. Context length: extended from 128k to 256k, providing better long-horizon support.",
},
"accounts/fireworks/models/qwen3-235b-a22b-instruct-2507": {
maxOutputTokens: 32768,
contextWindow: 256000,
pricing: {
input: 0.22,
output: 0.88,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Latest Qwen3 thinking model, competitive against the best closed source models in Jul 2025.",
},
"accounts/fireworks/models/qwen3-coder-480b-a35b-instruct": {
maxOutputTokens: 32768,
contextWindow: 256000,
pricing: {
input: 0.45,
output: 1.8,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Qwen3's most agentic code model to date.",
},
"accounts/fireworks/models/deepseek-r1-0528": {
maxOutputTokens: 20480,
contextWindow: 160000,
pricing: {
input: 3,
output: 8,
},
capabilities: {
images: false,
promptCache: false,
},
description:
"05/28 updated checkpoint of Deepseek R1. Its overall performance is now approaching that of leading models, such as O3 and Gemini 2.5 Pro. Compared to the previous version, the upgraded model shows significant improvements in handling complex reasoning tasks, and this version also offers a reduced hallucination rate, enhanced support for function calling, and better experience for vibe coding. Note that fine-tuning for this model is only available through contacting fireworks at https://fireworks.ai/company/contact-us.",
},
"accounts/fireworks/models/deepseek-v3": {
maxOutputTokens: 16384,
contextWindow: 128000,
pricing: {
input: 0.9,
output: 0.9,
},
capabilities: {
images: false,
promptCache: false,
},
description:
"A strong Mixture-of-Experts (MoE) language model with 671B total parameters with 37B activated for each token from Deepseek. Note that fine-tuning for this model is only available through contacting fireworks at https://fireworks.ai/company/contact-us.",
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-300
View File
@@ -1,300 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "gemini"
const GEMINI_MODELS: Record<string, ModelInfo> = {
"gemini-3-pro-preview": {
contextWindow: 1_048_576,
maxOutputTokens: 65536,
pricing: {
input: 4.0,
output: 18.0,
cacheRead: 0.4,
},
pricingTiers: [
{
contextWindow: 200000,
input: 2.0,
output: 12.0,
cacheRead: 0.2,
},
{
contextWindow: Infinity,
input: 4.0,
output: 18.0,
cacheRead: 0.4,
},
],
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
thinkingLevel: "high",
supportsThinkingLevel: true,
},
},
"gemini-3-flash-preview": {
contextWindow: 1_048_576,
maxOutputTokens: 65536,
pricing: {
input: 0.5,
output: 3.0,
cacheWrite: 0.05,
},
pricingTiers: [
{
contextWindow: 200000,
input: 0.3,
output: 2.5,
cacheRead: 0.03,
},
{
contextWindow: Infinity,
input: 0.3,
output: 2.5,
cacheRead: 0.03,
},
],
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
thinkingLevel: "low",
supportsThinkingLevel: true,
},
supportsGlobalEndpoint: true,
},
"gemini-2.5-pro": {
contextWindow: 1_048_576,
maxOutputTokens: 65536,
pricing: {
input: 2.5,
output: 15,
cacheRead: 0.625,
},
pricingTiers: [
{
contextWindow: 200000,
input: 1.25,
output: 10,
cacheRead: 0.31,
},
{
contextWindow: Infinity,
input: 2.5,
output: 15,
cacheRead: 0.625,
},
],
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
maxBudgetTokens: 32767,
},
},
"gemini-2.5-flash-lite-preview-06-17": {
contextWindow: 1_000_000,
maxOutputTokens: 64000,
pricing: {
input: 0.1,
output: 0.4,
cacheRead: 0.025,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
maxBudgetTokens: 24576,
},
supportsGlobalEndpoint: true,
description: "Preview version - may not be available in all regions",
},
"gemini-2.5-flash": {
contextWindow: 1_048_576,
maxOutputTokens: 65536,
pricing: {
input: 0.3,
output: 2.5,
cacheRead: 0.075,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
maxBudgetTokens: 24576,
outputPrice: 3.5,
},
},
"gemini-2.0-flash-001": {
contextWindow: 1_048_576,
maxOutputTokens: 8192,
pricing: {
input: 0.1,
output: 0.4,
cacheRead: 0.025,
cacheWrite: 1.0,
},
capabilities: {
images: true,
promptCache: true,
},
},
"gemini-2.0-flash-lite-preview-02-05": {
contextWindow: 1_048_576,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"gemini-2.0-pro-exp-02-05": {
contextWindow: 2_097_152,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"gemini-2.0-flash-thinking-exp-01-21": {
contextWindow: 1_048_576,
maxOutputTokens: 65_536,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"gemini-2.0-flash-thinking-exp-1219": {
contextWindow: 32_767,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"gemini-2.0-flash-exp": {
contextWindow: 1_048_576,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"gemini-1.5-flash-002": {
contextWindow: 1_048_576,
maxOutputTokens: 8192,
pricing: {
input: 0.15,
output: 0.6,
cacheRead: 0.0375,
cacheWrite: 1.0,
},
pricingTiers: [
{
contextWindow: 128000,
input: 0.075,
output: 0.3,
cacheRead: 0.01875,
},
{
contextWindow: Infinity,
input: 0.15,
output: 0.6,
cacheRead: 0.0375,
},
],
capabilities: {
images: true,
promptCache: true,
},
},
"gemini-1.5-flash-exp-0827": {
contextWindow: 1_048_576,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"gemini-1.5-flash-8b-exp-0827": {
contextWindow: 1_048_576,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"gemini-1.5-pro-002": {
contextWindow: 2_097_152,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"gemini-1.5-pro-exp-0827": {
contextWindow: 2_097_152,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"gemini-exp-1206": {
contextWindow: 2_097_152,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: false,
},
},
}
modelRegistry.registerProvider(PROVIDER_NAME, GEMINI_MODELS)
-166
View File
@@ -1,166 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "groq"
const MODELS: Record<string, ModelInfo> = {
"openai/gpt-oss-120b": {
maxOutputTokens: 32766, // Model fails if you try to use more than 32K tokens
contextWindow: 131_072,
pricing: {
input: 0.15,
output: 0.75,
},
capabilities: {
images: false,
promptCache: false,
},
description:
"A state-of-the-art 120B open-weight Mixture-of-Experts language model optimized for strong reasoning, tool use, and efficient deployment on large GPUs",
},
"openai/gpt-oss-20b": {
maxOutputTokens: 32766, // Model fails if you try to use more than 32K tokens
contextWindow: 131_072,
pricing: {
input: 0.1,
output: 0.5,
},
capabilities: {
images: false,
promptCache: false,
},
description:
"A compact 20B open-weight Mixture-of-Experts language model designed for strong reasoning and tool use, ideal for edge devices and local inference.",
},
// Compound Beta Models - Hybrid architectures optimized for tool use
"compound-beta": {
maxOutputTokens: 8192,
contextWindow: 128000,
pricing: {
input: 0.0,
output: 0.0,
},
capabilities: {
images: false,
promptCache: false,
},
description:
"Compound model using Llama 4 Scout for core reasoning with Llama 3.3 70B for routing and tool use. Excellent for plan/act workflows.",
},
"compound-beta-mini": {
maxOutputTokens: 8192,
contextWindow: 128000,
pricing: {
input: 0.0,
output: 0.0,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Lightweight compound model for faster inference while maintaining tool use capabilities.",
},
// DeepSeek Models - Reasoning-optimized
"deepseek-r1-distill-llama-70b": {
maxOutputTokens: 131072,
contextWindow: 131072,
pricing: {
input: 0.75,
output: 0.99,
},
capabilities: {
images: false,
promptCache: false,
},
description:
"DeepSeek R1 reasoning capabilities distilled into Llama 70B architecture. Excellent for complex problem-solving and planning.",
},
// Llama 4 Models
"meta-llama/llama-4-maverick-17b-128e-instruct": {
maxOutputTokens: 8192,
contextWindow: 131072,
pricing: {
input: 0.2,
output: 0.6,
},
capabilities: {
images: true,
promptCache: false,
},
description: "Meta's Llama 4 Maverick 17B model with 128 experts, supports vision and multimodal tasks.",
},
"meta-llama/llama-4-scout-17b-16e-instruct": {
maxOutputTokens: 8192,
contextWindow: 131072,
pricing: {
input: 0.11,
output: 0.34,
},
capabilities: {
images: true,
promptCache: false,
},
description: "Meta's Llama 4 Scout 17B model with 16 experts, optimized for fast inference and general tasks.",
},
// Llama 3.3 Models
"llama-3.3-70b-versatile": {
maxOutputTokens: 32768,
contextWindow: 131072,
pricing: {
input: 0.59,
output: 0.79,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Meta's latest Llama 3.3 70B model optimized for versatile use cases with excellent performance and speed.",
},
// Llama 3.1 Models - Fast inference
"llama-3.1-8b-instant": {
maxOutputTokens: 131072,
contextWindow: 131072,
pricing: {
input: 0.05,
output: 0.08,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Fast and efficient Llama 3.1 8B model optimized for speed, low latency, and reliable tool execution.",
},
// Moonshot Models
"moonshotai/kimi-k2-instruct": {
maxOutputTokens: 16384,
contextWindow: 131072,
pricing: {
input: 1.0,
output: 3.0,
cacheRead: 0.5, // 50% discount for cached input tokens
},
capabilities: {
images: false,
promptCache: true,
},
description:
"Kimi K2 is Moonshot AI's state-of-the-art Mixture-of-Experts (MoE) language model with 1 trillion total parameters and 32 billion activated parameters.",
},
"moonshotai/kimi-k2-instruct-0905": {
maxOutputTokens: 16384,
contextWindow: 262144,
pricing: {
input: 0.6,
output: 2.5,
cacheRead: 0.15,
},
capabilities: {
images: false,
promptCache: true,
},
description:
"Kimi K2 model gets a new version update: Agentic coding: more accurate, better generalization across scaffolds. Frontend coding: improved aesthetics and functionalities on web, 3d, and other tasks. Context length: extended from 128k to 256k, providing better long-horizon support.",
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-8
View File
@@ -1,8 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "hicap"
const MODELS: Record<string, ModelInfo> = {}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
@@ -1,9 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "vercel-ai-gateway"
const MODELS: Record<string, ModelInfo> = {}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
modelRegistry.setDefaultModel(PROVIDER_NAME, "DeepSeek-V3")
-102
View File
@@ -1,102 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "huggingface"
const MODELS: Record<string, ModelInfo> = {
"openai/gpt-oss-120b": {
contextWindow: 131_072,
maxOutputTokens: 32766,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description:
"Large open-weight reasoning model for high-end desktops and data centers, built for complex coding, math, and general AI tasks.",
},
"openai/gpt-oss-20b": {
contextWindow: 131_072,
maxOutputTokens: 32766,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description:
"Medium open-weight reasoning model that runs on most desktops, balancing strong reasoning with broad accessibility.",
},
"moonshotai/Kimi-K2-Instruct": {
contextWindow: 131_072,
maxOutputTokens: 131_072,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Advanced reasoning model with superior performance across coding, math, and general capabilities.",
},
"deepseek-ai/DeepSeek-V3-0324": {
contextWindow: 64_000,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Advanced reasoning model with superior performance across coding, math, and general capabilities.",
},
"deepseek-ai/DeepSeek-R1": {
contextWindow: 64_000,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description: "DeepSeek's reasoning model with step-by-step thinking capabilities.",
},
"deepseek-ai/DeepSeek-R1-0528": {
contextWindow: 64_000,
maxOutputTokens: 64_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description: "DeepSeek's reasoning model's latest version with step-by-step thinking capabilities",
},
"meta-llama/Llama-3.1-8B-Instruct": {
contextWindow: 128_000,
maxOutputTokens: 8192,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
description: "Efficient 8B parameter Llama model for general-purpose tasks.",
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-24
View File
@@ -1,24 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "liteLlm"
const MODELS: Record<string, ModelInfo> = {
"anthropic/claude-3-7-sonnet-20250219": {
contextWindow: 128_000,
maxOutputTokens: 8_192,
pricing: {
input: 0,
output: 0,
cacheWrite: 0,
cacheRead: 0,
},
capabilities: {
images: true,
promptCache: true,
},
temperature: 0,
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-8
View File
@@ -1,8 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "meta"
const MODELS: Record<string, ModelInfo> = {}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-49
View File
@@ -1,49 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "minimax"
const MODELS: Record<string, ModelInfo> = {
"MiniMax-M2.1": {
maxOutputTokens: 128_000,
contextWindow: 192_000,
pricing: {
input: 0.3,
output: 1.2,
cacheWrite: 0.0375,
cacheRead: 0.03,
},
capabilities: {
images: false,
promptCache: true,
},
},
"MiniMax-M2.1-lightning": {
maxOutputTokens: 128_000,
contextWindow: 192_000,
pricing: {
input: 0.3,
output: 2.4,
cacheWrite: 0.0375,
cacheRead: 0.03,
},
capabilities: {
images: false,
promptCache: true,
},
},
"MiniMax-M2": {
maxOutputTokens: 128_000,
contextWindow: 192_000,
pricing: {
input: 0.3,
output: 1.2,
},
capabilities: {
images: false,
promptCache: false,
},
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-213
View File
@@ -1,213 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "mistral"
const MODELS: Record<string, ModelInfo> = {
"devstral-2512": {
contextWindow: 256_000,
maxOutputTokens: 256_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"labs-devstral-small-2512": {
contextWindow: 256_000,
maxOutputTokens: 256_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"mistral-large-2512": {
contextWindow: 256_000,
maxOutputTokens: 256_000,
pricing: {
input: 0.5,
output: 1.5,
},
capabilities: {
images: false,
promptCache: false,
},
},
"ministral-14b-2512": {
contextWindow: 256_000,
maxOutputTokens: 256_000,
pricing: {
input: 0.2,
output: 0.2,
},
capabilities: {
images: false,
promptCache: false,
},
},
"mistral-large-2411": {
contextWindow: 128_000,
maxOutputTokens: 128_000,
pricing: {
input: 2.0,
output: 6.0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"pixtral-large-2411": {
contextWindow: 131_000,
maxOutputTokens: 131_000,
pricing: {
input: 2.0,
output: 6.0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"ministral-3b-2410": {
contextWindow: 128_000,
maxOutputTokens: 128_000,
pricing: {
input: 0.04,
output: 0.04,
},
capabilities: {
images: false,
promptCache: false,
},
},
"ministral-8b-2410": {
contextWindow: 128_000,
maxOutputTokens: 128_000,
pricing: {
input: 0.1,
output: 0.1,
},
capabilities: {
images: false,
promptCache: false,
},
},
"mistral-small-latest": {
contextWindow: 128_000,
maxOutputTokens: 128_000,
pricing: {
input: 0.1,
output: 0.3,
},
capabilities: {
images: true,
promptCache: false,
},
},
"mistral-medium-latest": {
contextWindow: 128_000,
maxOutputTokens: 128_000,
pricing: {
input: 0.4,
output: 2.0,
},
capabilities: {
images: true,
promptCache: false,
},
},
"mistral-small-2501": {
contextWindow: 32_000,
maxOutputTokens: 32_000,
pricing: {
input: 0.1,
output: 0.3,
},
capabilities: {
images: false,
promptCache: false,
},
},
"pixtral-12b-2409": {
contextWindow: 128_000,
maxOutputTokens: 128_000,
pricing: {
input: 0.15,
output: 0.15,
},
capabilities: {
images: true,
promptCache: false,
},
},
"open-mistral-nemo-2407": {
contextWindow: 128_000,
maxOutputTokens: 128_000,
pricing: {
input: 0.15,
output: 0.15,
},
capabilities: {
images: false,
promptCache: false,
},
},
"open-codestral-mamba": {
contextWindow: 256_000,
maxOutputTokens: 256_000,
pricing: {
input: 0.15,
output: 0.15,
},
capabilities: {
images: false,
promptCache: false,
},
},
"codestral-2501": {
contextWindow: 256_000,
maxOutputTokens: 256_000,
pricing: {
input: 0.3,
output: 0.9,
},
capabilities: {
images: false,
promptCache: false,
},
},
"devstral-small-2505": {
contextWindow: 131_072,
maxOutputTokens: 128_000,
pricing: {
input: 0.1,
output: 0.3,
},
capabilities: {
images: false,
promptCache: false,
},
},
"devstral-medium-latest": {
contextWindow: 131_072,
maxOutputTokens: 128_000,
pricing: {
input: 0.4,
output: 2.0,
},
capabilities: {
images: false,
promptCache: false,
},
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-80
View File
@@ -1,80 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "moonshot"
const MODELS: Record<string, ModelInfo> = {
"kimi-k2-0905-preview": {
contextWindow: 262144,
maxOutputTokens: 16384,
pricing: {
input: 0.6,
output: 2.5,
},
capabilities: {
images: false,
promptCache: false,
},
temperature: 0.6,
},
"kimi-k2-0711-preview": {
contextWindow: 131_072,
maxOutputTokens: 32_000,
pricing: {
input: 0.6,
output: 2.5,
},
capabilities: {
images: false,
promptCache: false,
},
temperature: 0.6,
},
"kimi-k2-turbo-preview": {
contextWindow: 262_144,
maxOutputTokens: 32_000,
pricing: {
input: 2.4,
output: 10,
},
capabilities: {
images: false,
promptCache: false,
},
temperature: 0.6,
},
"kimi-k2-thinking": {
contextWindow: 262_144,
maxOutputTokens: 32_000,
pricing: {
input: 0.6,
output: 2.5,
},
capabilities: {
images: false,
promptCache: false,
},
reasoning: {
enabled: true,
},
temperature: 1.0,
},
"kimi-k2-thinking-turbo": {
contextWindow: 262_144,
maxOutputTokens: 32_000,
pricing: {
input: 2.4,
output: 10,
},
capabilities: {
images: false,
promptCache: false,
},
reasoning: {
enabled: true,
},
temperature: 1.0,
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-273
View File
@@ -1,273 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "nebius"
const MODELS: Record<string, ModelInfo> = {
"Qwen/Qwen2.5-32B-Instruct-fast": {
maxOutputTokens: 8_192,
contextWindow: 32_768,
pricing: {
input: 0.13,
output: 0.4,
},
capabilities: {
images: false,
promptCache: false,
},
},
"deepseek-ai/DeepSeek-V3": {
maxOutputTokens: 32_000,
contextWindow: 96_000,
pricing: {
input: 0.5,
output: 1.5,
},
capabilities: {
images: false,
promptCache: false,
},
},
"deepseek-ai/DeepSeek-V3-0324-fast": {
maxOutputTokens: 128_000,
contextWindow: 128_000,
pricing: {
input: 2,
output: 6,
},
capabilities: {
images: false,
promptCache: false,
},
},
"deepseek-ai/DeepSeek-R1": {
maxOutputTokens: 32_000,
contextWindow: 96_000,
pricing: {
input: 0.8,
output: 2.4,
},
capabilities: {
images: false,
promptCache: false,
},
},
"deepseek-ai/DeepSeek-R1-fast": {
maxOutputTokens: 32_000,
contextWindow: 96_000,
pricing: {
input: 2,
output: 6,
},
capabilities: {
images: false,
promptCache: false,
},
},
"deepseek-ai/DeepSeek-R1-0528": {
maxOutputTokens: 128_000,
contextWindow: 163_840,
pricing: {
input: 0.8,
output: 2.4,
},
capabilities: {
images: false,
promptCache: false,
},
},
"meta-llama/Llama-3.3-70B-Instruct-fast": {
maxOutputTokens: 32_000,
contextWindow: 96_000,
pricing: {
input: 0.25,
output: 0.75,
},
capabilities: {
images: false,
promptCache: false,
},
},
"Qwen/Qwen2.5-Coder-32B-Instruct-fast": {
maxOutputTokens: 128_000,
contextWindow: 128_000,
pricing: {
input: 0.1,
output: 0.3,
},
capabilities: {
images: false,
promptCache: false,
},
},
"Qwen/Qwen3-4B-fast": {
maxOutputTokens: 32_000,
contextWindow: 41_000,
pricing: {
input: 0.08,
output: 0.24,
},
capabilities: {
images: false,
promptCache: false,
},
},
"Qwen/Qwen3-30B-A3B-fast": {
maxOutputTokens: 32_000,
contextWindow: 41_000,
pricing: {
input: 0.3,
output: 0.9,
},
capabilities: {
images: false,
promptCache: false,
},
},
"Qwen/Qwen3-235B-A22B": {
maxOutputTokens: 32_000,
contextWindow: 41_000,
pricing: {
input: 0.2,
output: 0.6,
},
capabilities: {
images: false,
promptCache: false,
},
},
"openai/gpt-oss-120b": {
maxOutputTokens: 32766, // Quantization: fp4
contextWindow: 131_000,
pricing: {
input: 0.15,
output: 0.6,
},
capabilities: {
images: false,
promptCache: false,
},
},
"moonshotai/Kimi-K2-Instruct": {
maxOutputTokens: 16384, // Quantization: fp4
contextWindow: 131_000,
pricing: {
input: 0.5,
output: 2.4,
},
capabilities: {
images: false,
promptCache: true,
},
},
"Qwen/Qwen3-Coder-480B-A35B-Instruct": {
maxOutputTokens: 163800, // Quantization: fp8
contextWindow: 262_000,
pricing: {
input: 0.4,
output: 1.8,
},
capabilities: {
images: false,
promptCache: false,
},
},
"openai/gpt-oss-20b": {
maxOutputTokens: 32766, // Quantization: fp4
contextWindow: 131_000,
pricing: {
input: 0.05,
output: 0.2,
},
capabilities: {
images: false,
promptCache: false,
},
},
"zai-org/GLM-4.5": {
maxOutputTokens: 98304, // Quantization: fp8
contextWindow: 128_000,
pricing: {
input: 0.6,
output: 2.2,
},
capabilities: {
images: false,
promptCache: true,
},
},
"zai-org/GLM-4.5-Air": {
maxOutputTokens: 98304, // Quantization: fp8
contextWindow: 128_000,
pricing: {
input: 0.2,
output: 1.2,
},
capabilities: {
images: false,
promptCache: true,
},
},
"deepseek-ai/DeepSeek-R1-0528-fast": {
maxOutputTokens: 128000, // Quantization: fp4
contextWindow: 164_000,
pricing: {
input: 2.0,
output: 6.0,
},
capabilities: {
images: false,
promptCache: false,
},
},
"Qwen/Qwen3-235B-A22B-Instruct-2507": {
maxOutputTokens: 64000, // Quantization: fp8
contextWindow: 262_000,
pricing: {
input: 0.2,
output: 0.6,
},
capabilities: {
images: false,
promptCache: false,
},
},
"Qwen/Qwen3-30B-A3B": {
maxOutputTokens: 32000, // Quantization: fp8
contextWindow: 41_000,
pricing: {
input: 0.1,
output: 0.3,
},
capabilities: {
images: false,
promptCache: false,
},
},
"Qwen/Qwen3-32B": {
maxOutputTokens: 16384, // Quantization: fp8
contextWindow: 41_000,
pricing: {
input: 0.1,
output: 0.3,
},
capabilities: {
images: false,
promptCache: false,
},
},
"Qwen/Qwen3-32B-fast": {
maxOutputTokens: 16384, // Quantization: fp8
contextWindow: 41_000,
pricing: {
input: 0.2,
output: 0.6,
},
capabilities: {
images: false,
promptCache: false,
},
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-37
View File
@@ -1,37 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "nousResearch"
const MODELS: Record<string, ModelInfo> = {
"Hermes-4-405B": {
maxOutputTokens: 8192,
contextWindow: 128_000,
pricing: {
input: 0.09,
output: 0.37,
},
capabilities: {
images: false,
promptCache: false,
},
description:
"This is the largest model in the Hermes 4 family, and it is the fullest expression of our design, focused on advanced reasoning and creative depth rather than optimizing inference speed or cost.",
},
"Hermes-4-70B": {
maxOutputTokens: 8192,
contextWindow: 128_000,
pricing: {
input: 0.05,
output: 0.2,
},
capabilities: {
images: false,
promptCache: false,
},
description:
"This incarnation of Hermes 4 balances scale and size. It handles complex reasoning tasks, while staying fast and cost effective. A versatile choice for many use cases.",
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-8
View File
@@ -1,8 +0,0 @@
import { modelRegistry } from "../registry"
import type { ModelInfo } from "../types"
const PROVIDER_NAME = "oca"
const MODELS: Record<string, ModelInfo> = {}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-78
View File
@@ -1,78 +0,0 @@
import { modelRegistry } from "../registry"
import { ApiFormat, type ModelInfo } from "../types"
const PROVIDER_NAME = "openai-codex"
const MODELS: Record<string, ModelInfo> = {
"gpt-5.2-codex": {
maxOutputTokens: 128_000,
contextWindow: 400_000,
pricing: {
// Subscription-based: no per-token costs
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
},
apiFormat: ApiFormat.OPENAI_RESPONSES,
description: "GPT-5.2 Codex: OpenAI's flagship coding model via ChatGPT subscription",
},
"gpt-5.1-codex-max": {
maxOutputTokens: 128_000,
contextWindow: 400_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
},
apiFormat: ApiFormat.OPENAI_RESPONSES,
description: "GPT-5.1 Codex Max: Maximum capability coding model via ChatGPT subscription",
},
"gpt-5.1-codex-mini": {
maxOutputTokens: 128_000,
contextWindow: 400_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
},
apiFormat: ApiFormat.OPENAI_RESPONSES,
description: "GPT-5.1 Codex Mini: Faster version for coding tasks via ChatGPT subscription",
},
"gpt-5.2": {
maxOutputTokens: 128_000,
contextWindow: 400_000,
pricing: {
input: 0,
output: 0,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
},
apiFormat: ApiFormat.OPENAI_RESPONSES,
description: "GPT-5.2: Latest GPT model via ChatGPT subscription",
},
}
modelRegistry.registerProvider(PROVIDER_NAME, MODELS)
-405
View File
@@ -1,405 +0,0 @@
import { modelRegistry } from "../registry"
import { ApiFormat, type ModelInfo } from "../types"
const PROVIDER_NAME = "openai"
const OPENAI_MODELS: Record<string, ModelInfo> = {
"gpt-5.2": {
contextWindow: 272000,
maxOutputTokens: 8_192,
pricing: {
input: 1.75,
output: 14.0,
cacheRead: 0.175,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
temperature: 1,
systemRole: "developer",
},
"gpt-5.2-codex": {
contextWindow: 400000,
maxOutputTokens: 8_192,
pricing: {
input: 1.75,
output: 14.0,
cacheRead: 0.175,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
apiFormat: ApiFormat.OPENAI_RESPONSES,
temperature: 1,
systemRole: "developer",
},
"gpt-5.1-2025-11-13": {
contextWindow: 272000,
maxOutputTokens: 8_192,
pricing: {
input: 1.25,
output: 10.0,
cacheRead: 0.125,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
temperature: 1,
systemRole: "developer",
},
"gpt-5.1": {
contextWindow: 272000,
maxOutputTokens: 8_192,
pricing: {
input: 1.25,
output: 10.0,
cacheRead: 0.125,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
temperature: 1,
systemRole: "developer",
},
"gpt-5.1-codex": {
contextWindow: 400000,
maxOutputTokens: 8_192,
pricing: {
input: 1.25,
output: 10.0,
cacheRead: 0.125,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
apiFormat: ApiFormat.OPENAI_RESPONSES,
temperature: 1,
systemRole: "developer",
},
"gpt-5.1-chat-latest": {
contextWindow: 400000,
maxOutputTokens: 8_192,
pricing: {
input: 1.25,
output: 10,
cacheRead: 0.125,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
temperature: 1,
systemRole: "developer",
},
"gpt-5-2025-08-07": {
contextWindow: 272000,
maxOutputTokens: 8_192,
pricing: {
input: 1.25,
output: 10.0,
cacheRead: 0.125,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
temperature: 1,
systemRole: "developer",
},
"gpt-5-codex": {
contextWindow: 400000,
maxOutputTokens: 8_192,
pricing: {
input: 1.25,
output: 10.0,
cacheRead: 0.125,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
apiFormat: ApiFormat.OPENAI_RESPONSES,
temperature: 1,
systemRole: "developer",
},
"gpt-5-mini-2025-08-07": {
contextWindow: 272000,
maxOutputTokens: 8_192,
pricing: {
input: 0.25,
output: 2.0,
cacheRead: 0.025,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
temperature: 1,
systemRole: "developer",
},
"gpt-5-nano-2025-08-07": {
contextWindow: 272000,
maxOutputTokens: 8_192,
pricing: {
input: 0.05,
output: 0.4,
cacheRead: 0.005,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
temperature: 1,
systemRole: "developer",
},
"gpt-5-chat-latest": {
contextWindow: 400000,
maxOutputTokens: 8_192,
pricing: {
input: 1.25,
output: 10,
cacheRead: 0.125,
},
capabilities: {
images: true,
promptCache: true,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
temperature: 1,
systemRole: "developer",
},
o3: {
contextWindow: 200_000,
maxOutputTokens: 100_000,
pricing: {
input: 2.0,
output: 8.0,
cacheRead: 0.5,
},
capabilities: {
images: true,
promptCache: true,
tools: false,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
systemRole: "developer",
},
"o4-mini": {
contextWindow: 200_000,
maxOutputTokens: 100_000,
pricing: {
input: 1.1,
output: 4.4,
cacheRead: 0.275,
},
capabilities: {
images: true,
promptCache: true,
tools: false,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
systemRole: "developer",
},
"gpt-4.1": {
contextWindow: 1_047_576,
maxOutputTokens: 32_768,
pricing: {
input: 2,
output: 8,
cacheRead: 0.5,
},
capabilities: {
images: true,
promptCache: true,
},
temperature: 0,
},
"gpt-4.1-mini": {
contextWindow: 1_047_576,
maxOutputTokens: 32_768,
pricing: {
input: 0.4,
output: 1.6,
cacheRead: 0.1,
},
capabilities: {
images: true,
promptCache: true,
},
temperature: 0,
},
"gpt-4.1-nano": {
contextWindow: 1_047_576,
maxOutputTokens: 32_768,
pricing: {
input: 0.1,
output: 0.4,
cacheRead: 0.025,
},
capabilities: {
images: true,
promptCache: true,
},
temperature: 0,
},
"o3-mini": {
contextWindow: 200_000,
maxOutputTokens: 100_000,
pricing: {
input: 1.1,
output: 4.4,
cacheRead: 0.55,
},
capabilities: {
images: false,
promptCache: true,
tools: false,
},
reasoning: {
enabled: true,
supportsEffortLevel: true,
},
systemRole: "developer",
},
// don't support tool use yet
o1: {
contextWindow: 200_000,
maxOutputTokens: 100_000,
pricing: {
input: 15,
output: 60,
cacheRead: 7.5,
},
capabilities: {
images: true,
promptCache: false,
streaming: false,
},
},
"o1-preview": {
contextWindow: 128_000,
maxOutputTokens: 32_768,
pricing: {
input: 15,
output: 60,
cacheRead: 7.5,
},
capabilities: {
images: true,
promptCache: true,
streaming: false,
},
},
"o1-mini": {
contextWindow: 128_000,
maxOutputTokens: 65_536,
pricing: {
input: 1.1,
output: 4.4,
cacheRead: 0.55,
},
capabilities: {
images: true,
promptCache: true,
streaming: false,
},
},
"gpt-4o": {
contextWindow: 128_000,
maxOutputTokens: 4_096,
pricing: {
input: 2.5,
output: 10,
cacheRead: 1.25,
},
capabilities: {
images: true,
promptCache: true,
},
temperature: 0,
},
"gpt-4o-mini": {
contextWindow: 128_000,
maxOutputTokens: 16_384,
pricing: {
input: 0.15,
output: 0.6,
cacheRead: 0.075,
},
capabilities: {
images: true,
promptCache: true,
},
temperature: 0,
},
"chatgpt-4o-latest": {
contextWindow: 128_000,
maxOutputTokens: 16_384,
pricing: {
input: 5,
output: 15,
},
capabilities: {
images: true,
promptCache: false,
},
temperature: 0,
},
}
// Register OpenAI models with the registry
modelRegistry.registerProvider(PROVIDER_NAME, OPENAI_MODELS)

Some files were not shown because too many files have changed in this diff Show More