mirror of
https://github.com/cline/cline.git
synced 2026-09-02 07:42:19 +08:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d0f624f84 | |||
| e68d55a402 | |||
| c093ca1760 | |||
| dc85299a73 | |||
| 4533ed3ea8 | |||
| 66d7664d1b | |||
| 50aeb7098f | |||
| c2e6eafb45 | |||
| 0220628c39 | |||
| 5052220195 | |||
| abf3081e56 | |||
| 125cb78a31 | |||
| 17539228ea | |||
| bdb7cc36fa | |||
| d3e1065707 | |||
| 6303951f20 | |||
| fc184e07a2 | |||
| e88f4ea412 | |||
| 133ade3f1a | |||
| 857411c035 | |||
| c59f068584 | |||
| f7561c31fa | |||
| f0a97dafc8 | |||
| d422f689a6 | |||
| 00efcc1c2b | |||
| 59251e9de6 | |||
| 2df952f571 | |||
| 6e7dc55781 | |||
| ec879fc549 | |||
| 75b050de5c | |||
| 0c4acd9457 | |||
| 0dc3d7084d | |||
| 3cfafe6583 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add claude 4.5 haiku
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
improve cline command permission validation logic. add cline command permission man page documentation
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add code-simplifier skill for refining code clarity and maintainability
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Expose --version in cline cli command
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Log Persistence errors to PostHog
|
||||
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
OpenAI GPT-5 Codex models are now using Apply Patch tool for diff edits.
|
||||
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: add chat output on skill use
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Adding telemetry for background exec terminal
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cline": patch
|
||||
---
|
||||
|
||||
Remove deprecated zai-glm-4.6 model from Cerebras provider
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Wiring-up "conditionals" for Cline Rules files.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: removes retry message from UI after retry succeeds
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Support native tool calling for LM Studio and Ollama provider
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
add claude 4.5 opus into sap provider.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix drag & drop files from SSH remote workspaces into chat
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Limite Vertex and LiteLLM options when they're remote configured
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix crash when the Context Menu has a type but no options
|
||||
@@ -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
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Throttle the remote config fetch
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Improve history view filter menu
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"cline": patch
|
||||
---
|
||||
|
||||
Add git worktree management UI for running parallel Cline sessions
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Remove hooks feature setting and make it always enabled (except on Windows).
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 |
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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`.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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 +0,0 @@
|
||||
console.log("Hello via Bun!")
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "@cline/models",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
Reference in New Issue
Block a user