mirror of
https://github.com/cline/cline.git
synced 2026-09-02 15:52:29 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a07554f191 | |||
| 474c655240 | |||
| b5157a2376 | |||
| b14db72140 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix: do not retry request automatically on auth failure.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix task timeline display height.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Ensure tool arguments are streamed during file operations when native tool calling is enabled.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Added thinking level setting for Gemini 3.0 Pro
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Feat: add thought signature support for Gemini SDK
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Feat: Enable native tool calling for Baseten and Kimi K2 models
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Replaces generic robot icon with Cline logo across VS Code UI
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add Kimi K2 Thinking to Baseten Provider
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Skip MCP tool with invalid name (e.g. name too long) when native tool calling is enabled.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix Anthropic provider missing signature param when thinking is enabled.
|
||||
Vendored
-21
@@ -165,27 +165,6 @@
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"name": "Open Storybook",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"storybook"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/webview-ui",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"serverReadyAction": {
|
||||
"pattern": "Local:.*http://localhost:([0-9]+)",
|
||||
"uriFormat": "http://localhost:%s",
|
||||
"action": "openExternally"
|
||||
},
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
-20
@@ -263,26 +263,6 @@
|
||||
"watch"
|
||||
],
|
||||
"command": "rm -rf ${workspaceFolder}/dist/tmp/user && mkdir -p ${workspaceFolder}/dist/tmp/user"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "storybook",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"isBackground": false,
|
||||
"label": "npm: storybook",
|
||||
"dependsOn": [
|
||||
"npm: protos",
|
||||
"npm: build:webview"
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
+6
-22
@@ -1,30 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## [3.38.1]
|
||||
## 3.37.1
|
||||
|
||||
### Fixed
|
||||
- Fixed handling of 'signature' field in sanitizeAnthropicContentBlock to properly preserve it when thinking is enabled, as required by Anthropic's API.
|
||||
|
||||
## [3.38.0]
|
||||
|
||||
### Added
|
||||
- Gemini 3 Pro Preview model
|
||||
- AquaVoice Avalon model for voice-to-text dictation
|
||||
|
||||
### Fixed
|
||||
- Automatic context truncation when AWS Bedrock token usage rate limits are exceeded
|
||||
- Removed new_task tool from system prompts, updated slash command prompts, and added helper function for native tool calling validation
|
||||
|
||||
|
||||
## [3.37.1]
|
||||
|
||||
- Comprehensive changes to better support GPT 5.1 - System prompt, tools, deep-planning, focus chain, etc.
|
||||
- Add AGENTS.md support
|
||||
- feat(models): Add free minimax/mimax-m2 model to the model picker
|
||||
- cf8dd1c: Comprehensive changes to better support GPT 5.1 - System prompt, tools, deep-planning, focus chain, etc.
|
||||
- 02abbcf: Add AGENTS.md support
|
||||
- 855db7d: feat(models): Add free minimax/mimax-m2 model to the model picker
|
||||
|
||||
## [3.37.0]
|
||||
|
||||
### Added
|
||||
## Added
|
||||
|
||||
- GPT-5.1 with model-specific prompting: tailored system prompts, tool usage, focus chain, and deep-planning optimizations
|
||||
- Nous Research provider with Hermes 4 model family and custom system prompts
|
||||
@@ -34,7 +18,7 @@
|
||||
- Expanded HTTP proxy support throughout the codebase
|
||||
- Improved focus chain prompting for frontier models (Anthropic, OpenAI, Gemini, xAI)
|
||||
|
||||
### Fixed
|
||||
## Fixed
|
||||
|
||||
- Duplicate tool results prevention through existence checking
|
||||
- XML entity escaping in model content processor
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>
|
||||
<json>
|
||||
<![CDATA[
|
||||
{
|
||||
"fontFamily": "cline-bot",
|
||||
"majorVersion": 1,
|
||||
"minorVersion": 0,
|
||||
"fontURL": "https://cline.bot",
|
||||
"designerURL": "https://cline.bot",
|
||||
"licenseURL": "https://cline.bot",
|
||||
"version": "Version 1.0",
|
||||
"fontId": "cline-bot",
|
||||
"psName": "cline-bot",
|
||||
"subFamily": "Regular",
|
||||
"fullName": "cline-bot",
|
||||
"description": "Font generated by IcoMoon."
|
||||
}
|
||||
]]>
|
||||
</json>
|
||||
</metadata>
|
||||
<defs>
|
||||
<font id="cline-bot" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="cline" data-tags="cline" horiz-adv-x="977" d="M964.553 383.11l-60.285 121.406v69.495c0 115.545-92.939 209.321-207.647 209.321h-102.986c7.536 15.071 11.722 32.654 11.722 51.074 0 64.471-51.912 116.383-115.545 116.383s-115.545-51.912-115.545-116.383 4.186-35.166 11.722-51.074h-102.986c-114.708 0-207.647-93.776-207.647-209.321v-69.495l-61.959-121.406c-5.861-11.722-5.861-26.793 0-38.515l61.959-119.732v-69.495c0-115.545 92.939-209.321 207.647-209.321h415.294c114.708 0 207.647 93.776 207.647 209.321v69.495l60.285 119.732c5.861 11.722 5.861 25.956 0 38.515v0zM426.178 284.311c0-52.749-42.702-95.451-94.613-95.451s-94.613 42.702-94.613 95.451v169.132c0 52.749 42.702 95.451 94.613 95.451s94.613-42.702 94.613-95.451v-169.132zM731.787 284.311c0-52.749-42.702-95.451-94.613-95.451s-94.613 42.702-94.613 95.451v169.132c0 52.749 42.702 95.451 94.613 95.451s94.613-42.702 94.613-95.451v-169.132z" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
Binary file not shown.
@@ -517,7 +517,7 @@ func (pw *ProviderWizard) applyModelChange(provider cline.ApiProvider, modelID s
|
||||
ModelInfo: modelInfo,
|
||||
}
|
||||
|
||||
return UpdateProviderPartial(pw.ctx, pw.manager, provider, updates, true)
|
||||
return UpdateProviderPartial(pw.ctx, pw.manager, provider, updates, false)
|
||||
}
|
||||
|
||||
// SwitchToBYOProvider switches to a BYO provider that's already configured.
|
||||
|
||||
@@ -221,7 +221,83 @@ func (p *ToolResultParser) ParseCodeDefinitions(content string) string {
|
||||
|
||||
// ParseWebFetch formats webFetch tool results with content preview
|
||||
func (p *ToolResultParser) ParseWebFetch(content, url string) string {
|
||||
return ""
|
||||
if content == "" {
|
||||
return fmt.Sprintf("*Fetched content from %s (empty response)*", url)
|
||||
}
|
||||
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
var result strings.Builder
|
||||
|
||||
// Try to extract title
|
||||
var title string
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "#") && !strings.HasPrefix(trimmed, "##") {
|
||||
title = strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if title != "" {
|
||||
result.WriteString(fmt.Sprintf("**Title:** %s\n\n", title))
|
||||
}
|
||||
|
||||
// Show preview of content
|
||||
result.WriteString("**Preview:**\n")
|
||||
|
||||
charCount := 0
|
||||
maxChars := 500
|
||||
previewLines := []string{}
|
||||
|
||||
for _, line := range lines {
|
||||
// Skip markdown headers
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if charCount+len(trimmed) > maxChars {
|
||||
break
|
||||
}
|
||||
|
||||
previewLines = append(previewLines, trimmed)
|
||||
charCount += len(trimmed)
|
||||
}
|
||||
|
||||
result.WriteString(strings.Join(previewLines, " "))
|
||||
result.WriteString("...\n\n")
|
||||
|
||||
// Extract sections
|
||||
sections := []string{}
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "##") {
|
||||
section := strings.TrimSpace(strings.TrimPrefix(trimmed, "##"))
|
||||
sections = append(sections, section)
|
||||
if len(sections) >= 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(sections) > 0 {
|
||||
result.WriteString("**Sections Found:**\n")
|
||||
for _, section := range sections {
|
||||
result.WriteString(fmt.Sprintf("- %s\n", section))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
// Word count estimate
|
||||
wordCount := len(strings.Fields(content))
|
||||
result.WriteString(fmt.Sprintf("*[Full content: ~%s]*", p.formatWordCount(wordCount)))
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// detectLanguage returns syntax highlighting language based on file extension
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# Node modules
|
||||
node_modules
|
||||
npm-debug.log
|
||||
|
||||
# Build artifacts
|
||||
dist
|
||||
dist-standalone
|
||||
build
|
||||
*.log
|
||||
|
||||
# Generated code
|
||||
src/generated
|
||||
|
||||
# CLI build artifacts
|
||||
cli/bin
|
||||
cli/dist
|
||||
|
||||
# Webview build artifacts
|
||||
webview-ui/dist
|
||||
webview-ui/build
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
# Tests
|
||||
tests
|
||||
*.test.js
|
||||
*.spec.js
|
||||
|
||||
# CI/CD
|
||||
.github
|
||||
.gitlab-ci.yml
|
||||
@@ -0,0 +1,49 @@
|
||||
FROM node:22-slim
|
||||
|
||||
# TARGETARCH enables multi-architecture support without emulation warnings:
|
||||
# - Docker automatically sets TARGETARCH to the build platform's architecture
|
||||
# - On arm64 machines (Apple Silicon): TARGETARCH=arm64, uses linux-arm64 binaries
|
||||
# - On amd64 machines (Intel/AMD): TARGETARCH=amd64, uses linux-x64 binaries
|
||||
# The corresponding platform-specific binaries and native modules (better-sqlite3)
|
||||
# are pre-built by scripts/package-standalone.mjs during the build process.
|
||||
ARG TARGETARCH
|
||||
|
||||
# Install only runtime dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git curl ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /opt/cline
|
||||
|
||||
# Copy the entire pre-built distribution
|
||||
COPY dist-standalone/ ./
|
||||
|
||||
# Create symlink for Linux native modules
|
||||
# Map Docker's TARGETARCH (arm64/amd64) to Node's platform naming (x64 for amd64)
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
ln -sf /opt/cline/binaries/linux-x64/node_modules/better-sqlite3 /opt/cline/node_modules/better-sqlite3; \
|
||||
else \
|
||||
ln -sf /opt/cline/binaries/linux-$TARGETARCH/node_modules/better-sqlite3 /opt/cline/node_modules/better-sqlite3; \
|
||||
fi
|
||||
|
||||
# Set up CLI binaries
|
||||
# The Linux binaries are already in /opt/cline/bin/ from dist-standalone
|
||||
# Just need to create symlinks to the platform-specific ones
|
||||
RUN cd /opt/cline/bin && \
|
||||
ln -sf cline-linux-$TARGETARCH cline && \
|
||||
ln -sf cline-host-linux-$TARGETARCH cline-host && \
|
||||
chmod +x cline-linux-$TARGETARCH cline-host-linux-$TARGETARCH cline cline-host
|
||||
|
||||
# Add binaries to PATH
|
||||
ENV PATH="/opt/cline/bin:${PATH}"
|
||||
ENV NODE_ENV=production
|
||||
ENV CLINE_HOME=/root/.cline
|
||||
|
||||
RUN mkdir -p $CLINE_HOME
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/opt/cline/bin/cline"]
|
||||
CMD ["--help"]
|
||||
+2
-2
@@ -189,7 +189,6 @@
|
||||
"provider-config/fireworks",
|
||||
"provider-config/zai",
|
||||
"provider-config/gcp-vertex-ai",
|
||||
"provider-config/baseten",
|
||||
{
|
||||
"group": "AWS Bedrock",
|
||||
"pages": [
|
||||
@@ -216,7 +215,8 @@
|
||||
"provider-config/vscode-language-model-api",
|
||||
"provider-config/sap-aicore",
|
||||
"provider-config/vercel-ai-gateway",
|
||||
"provider-config/requesty"
|
||||
"provider-config/requesty",
|
||||
"provider-config/baseten"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
Generated
+34
-6
@@ -5146,6 +5146,28 @@
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/has-bigints": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||
@@ -6468,9 +6490,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -10213,6 +10235,12 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/stack-utils": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
|
||||
@@ -10581,9 +10609,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz",
|
||||
"integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz",
|
||||
"integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pump": "^3.0.0",
|
||||
|
||||
@@ -14,9 +14,5 @@
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"mintlify": "^4.2.23"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1",
|
||||
"js-yaml": "^4.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Baseten"
|
||||
description: "Learn how to configure and use Baseten's Model APIs with Cline. Access frontier open-source models with enterprise-grade performance, reliability, and competitive pricing."
|
||||
---
|
||||
|
||||
Baseten provides on-demand frontier model APIs designed for production applications, not just experimentation. Built on the Baseten Inference Stack, these APIs deliver optimized inference for leading open-source models from OpenAI, DeepSeek, Moonshot AI, and Alibaba Cloud.
|
||||
Baseten provides on-demand frontier model APIs designed for production applications, not just experimentation. Built on the Baseten Inference Stack, these APIs deliver enterprise-grade performance and reliability with optimized inference for leading open-source models from OpenAI, DeepSeek, Meta, Moonshot AI, and Alibaba Cloud.
|
||||
|
||||
**Website:** [https://www.baseten.co/products/model-apis/](https://www.baseten.co/products/model-apis/)
|
||||
|
||||
@@ -14,21 +14,13 @@ Baseten provides on-demand frontier model APIs designed for production applicati
|
||||
3. **Create a Key:** Generate a new API key. Give it a descriptive name (e.g., "Cline").
|
||||
4. **Copy the Key:** Copy the API key immediately and store it securely.
|
||||
|
||||
### Configuration in Cline
|
||||
|
||||
1. **Open Cline Settings:** Click the settings icon (⚙️) in the Cline panel.
|
||||
2. **Select Provider:** Choose "Baseten" from the "API Provider" dropdown.
|
||||
3. **Enter API Key:** Paste your Baseten API key into the "Baseten API Key" field.
|
||||
4. **Select Model:** Choose your desired model from the "Model" dropdown.
|
||||
|
||||
**IMPORTANT: For Kimi K2 Thinking:** To use the `moonshotai/Kimi-K2-Thinking` model, you must enable **Native Tool Call (Experimental)** in Cline settings. This setting allows Cline to call tools through their native tool processor and is required for this reasoning model to function properly.
|
||||
|
||||
### Supported Models
|
||||
|
||||
Cline supports all current models under Baseten Model APIs, including:
|
||||
For the most updated pricing, please visit: https://www.baseten.co/products/model-apis/
|
||||
Note: Kimi K2 0711, Llama 4 Maverick, and Llama 4 Scout Model APIs have been deprecated at 5pm PT on October 8th.
|
||||
https://www.baseten.co/resources/changelog/model-api-deprecation-notice-kimi-k2-0711-scout-maverick/
|
||||
|
||||
- `moonshotai/Kimi-K2-Thinking` (Moonshot AI) - Enhanced reasoning capabilities with step-by-step thought processes (262K context) - \$0.60/\$2.50 per 1M tokens
|
||||
- `zai-org/GLM-4.6` (Z AI) - Frontier open model with advanced agentic, reasoning and coding capabilities by Z AI (200k context) \$0.60/\$2.20 per 1M tokens
|
||||
- `moonshotai/Kimi-K2-Instruct-0905` (Moonshot AI) - September update with enhanced capabilities (262K context) - \$0.60/\$2.50 per 1M tokens
|
||||
- `openai/gpt-oss-120b` (OpenAI) - 120B MoE with strong reasoning capabilities (128K context) - \$0.10/\$0.50 per 1M tokens
|
||||
@@ -39,6 +31,13 @@ For the most updated pricing, please visit: https://www.baseten.co/products/mode
|
||||
- `deepseek-ai/DeepSeek-V3.1` - Hybrid reasoning with advanced tool calling (163K context) - \$0.50/\$1.50 per 1M tokens
|
||||
- `deepseek-ai/DeepSeek-V3-0324` - Fast general-purpose with enhanced reasoning (163K context) - \$0.77/\$0.77 per 1M tokens
|
||||
|
||||
### Configuration in Cline
|
||||
|
||||
1. **Open Cline Settings:** Click the settings icon (⚙️) in the Cline panel.
|
||||
2. **Select Provider:** Choose "Baseten" from the "API Provider" dropdown.
|
||||
3. **Enter API Key:** Paste your Baseten API key into the "Baseten API Key" field.
|
||||
4. **Select Model:** Choose your desired model from the "Model" dropdown.
|
||||
|
||||
### Production-First Architecture
|
||||
|
||||
Baseten's Model APIs are built for production environments with several key advantages:
|
||||
@@ -60,17 +59,47 @@ Baseten's Model APIs are built for production environments with several key adva
|
||||
|
||||
#### Developer Experience
|
||||
- **OpenAI compatible API** - migrate by swapping a single URL
|
||||
- **Drop-in replacement** for closed models with comprehensive observability and analytics
|
||||
- **Drop-in replacement** for closed models with comprehensive observability
|
||||
- **Seamless scaling** from Model APIs to dedicated deployments
|
||||
|
||||
### Special Features
|
||||
|
||||
#### Function Calling & Tool Use
|
||||
All Baseten models support structured outputs, function calling, and tool use as part of the Baseten Inference Stack, making them ideal for agentic applications and coding workflows.
|
||||
All Baseten models support structured outputs, function calling, and tool use as part of the Baseten Inference Stack, making them ideal for agentic applications.
|
||||
|
||||
#### Reasoning Capabilities
|
||||
DeepSeek models offer enhanced reasoning with step-by-step thought processes, while maintaining production-ready performance.
|
||||
|
||||
#### Long Context Support
|
||||
- **Up to 1 million tokens** for Llama 4 models (Maverick and Scout)
|
||||
- **262K tokens** for Qwen3 models
|
||||
- **163K tokens** for DeepSeek models
|
||||
- **Perfect for code repositories** and complex multi-turn conversations
|
||||
|
||||
#### Quantization Optimizations
|
||||
Models are deployed with advanced quantization techniques (fp4, fp8, fp16) for optimal performance while maintaining quality.
|
||||
|
||||
### Migration from Other Providers
|
||||
|
||||
Baseten's OpenAI compatibility makes migration straightforward:
|
||||
|
||||
**From OpenAI:**
|
||||
- Swap `api.openai.com` with `inference.baseten.co/v1`
|
||||
- Keep existing request/response formats
|
||||
- Benefit from significant cost savings
|
||||
|
||||
**From Other Providers:**
|
||||
- Use standard OpenAI SDK format
|
||||
- Maintain existing prompting strategies
|
||||
- Access to newer open-source models
|
||||
|
||||
### Tips and Notes
|
||||
|
||||
- **Dynamic Model Updates:** Cline automatically fetches the latest model list from Baseten, ensuring access to new models as they're released in real time.
|
||||
- **Model Selection:** Choose models based on your specific use case - reasoning models for complex tasks, coding models for development work, and flagship models for general applications.
|
||||
- **Cost Optimization:** Baseten offers some of the most competitive pricing in the market, especially for open-source models.
|
||||
- **Context Windows:** Take advantage of large context windows (up to 1M tokens) for including substantial codebases and documentation.
|
||||
- **Enterprise Ready:** Baseten is designed for production use with enterprise-grade security, compliance, and reliability.
|
||||
- **Dynamic Model Updates:** Cline automatically fetches the latest model list from Baseten, ensuring access to new models as they're released.
|
||||
- **Multi-Cloud Capacity Management (MCM):** Baseten's multi-cloud infrastructure ensures high availability and low latency globally.
|
||||
- **Support:** Baseten provides dedicated support for production deployments and can work with you on dedicated resources as you scale.
|
||||
|
||||
|
||||
Generated
+1422
File diff suppressed because it is too large
Load Diff
+45
-46
@@ -1,48 +1,47 @@
|
||||
{
|
||||
"name": "cline-evals",
|
||||
"version": "0.1.0",
|
||||
"description": "Evaluation scripts and tools for Cline",
|
||||
"main": "cli/dist/index.js",
|
||||
"scripts": {
|
||||
"build:cli": "cd cli && tsc",
|
||||
"start:cli": "cd cli && node dist/index.js",
|
||||
"dev:cli": "cd cli && ts-node src/index.ts",
|
||||
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"cline",
|
||||
"evaluation",
|
||||
"benchmark",
|
||||
"diff-edits"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.0",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"chalk": "5.6.2",
|
||||
"dotenv": "^16.5.0",
|
||||
"commander": "^9.4.1",
|
||||
"execa": "^5.1.1",
|
||||
"node-fetch": "^2.7.0",
|
||||
"ora": "^5.4.1",
|
||||
"sqlite": "^4.1.2",
|
||||
"tiktoken": "^1.0.21",
|
||||
"uuid": "^9.0.0",
|
||||
"yargs": "^17.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@types/yargs": "^17.0.19",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1",
|
||||
"js-yaml": "^4.1.1"
|
||||
}
|
||||
"name": "cline-evals",
|
||||
"version": "0.1.0",
|
||||
"description": "Evaluation scripts and tools for Cline",
|
||||
"main": "cli/dist/index.js",
|
||||
"scripts": {
|
||||
"build:cli": "cd cli && tsc",
|
||||
"start:cli": "cd cli && node dist/index.js",
|
||||
"dev:cli": "cd cli && ts-node src/index.ts",
|
||||
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"cline",
|
||||
"evaluation",
|
||||
"benchmark",
|
||||
"diff-edits"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.0",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"chalk": "5.6.2",
|
||||
"dotenv": "^16.5.0",
|
||||
"commander": "^9.4.1",
|
||||
"execa": "^5.1.1",
|
||||
"node-fetch": "^2.7.0",
|
||||
"ora": "^5.4.1",
|
||||
"sqlite": "^4.1.2",
|
||||
"tiktoken": "^1.0.21",
|
||||
"uuid": "^9.0.0",
|
||||
"yargs": "^17.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@types/yargs": "^17.0.19",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+933
-407
File diff suppressed because it is too large
Load Diff
+15
-21
@@ -2,7 +2,7 @@
|
||||
"name": "claude-dev",
|
||||
"displayName": "Cline",
|
||||
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
|
||||
"version": "3.38.1",
|
||||
"version": "3.37.1",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -46,15 +46,6 @@
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"icons": {
|
||||
"cline-icon": {
|
||||
"description": "cline",
|
||||
"default": {
|
||||
"fontPath": "assets/icons/cline-bot.woff",
|
||||
"fontCharacter": "\\e900"
|
||||
}
|
||||
}
|
||||
},
|
||||
"walkthroughs": [
|
||||
{
|
||||
"id": "ClineWalkthrough",
|
||||
@@ -183,7 +174,10 @@
|
||||
"command": "cline.generateGitCommitMessage",
|
||||
"title": "Generate Commit Message with Cline",
|
||||
"category": "Cline",
|
||||
"icon": "$(cline-icon)"
|
||||
"icon": {
|
||||
"light": "assets/icons/robot_panel_light.png",
|
||||
"dark": "assets/icons/robot_panel_dark.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "cline.abortGitCommitMessage",
|
||||
@@ -312,6 +306,8 @@
|
||||
"compile-cli-all-platforms": "scripts/build-cli-all-platforms.sh",
|
||||
"compile-cli-man-page": "pandoc cli/man/cline.1.md -s -t man -o cli/man/cline.1",
|
||||
"build:npm": "scripts/build-npm-package.sh",
|
||||
"build:docker:dev": "node scripts/build-docker-dev.mjs",
|
||||
"docker:shell": "node scripts/docker-shell.mjs",
|
||||
"test:install": "bash scripts/test-install.sh",
|
||||
"dev:cli:watch": "node scripts/dev-cli-watch.mjs",
|
||||
"postcompile-standalone": "node scripts/package-standalone.mjs",
|
||||
@@ -365,8 +361,7 @@
|
||||
"docs": "cd docs && npm run dev",
|
||||
"docs:check-links": "cd docs && npm run check",
|
||||
"docs:rename-file": "cd docs && npm run rename",
|
||||
"report-issue": "node scripts/report-issue.js",
|
||||
"storybook": "cd webview-ui && npm run storybook"
|
||||
"report-issue": "node scripts/report-issue.js"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": [
|
||||
@@ -424,7 +419,7 @@
|
||||
"@bufbuild/protobuf": "^2.2.5",
|
||||
"@cerebras/cerebras_cloud_sdk": "^1.35.0",
|
||||
"@google-cloud/vertexai": "^1.9.3",
|
||||
"@google/genai": "^1.30.0",
|
||||
"@google/genai": "^1.11.0",
|
||||
"@grpc/grpc-js": "^1.9.15",
|
||||
"@grpc/reflection": "^1.0.4",
|
||||
"@mistralai/mistralai": "^1.5.0",
|
||||
@@ -449,8 +444,8 @@
|
||||
"@opentelemetry/sdk-trace-node": "^1.30.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.37.0",
|
||||
"@playwright/test": "^1.55.1",
|
||||
"@sap-ai-sdk/ai-api": "^2.1.0",
|
||||
"@sap-ai-sdk/orchestration": "^2.1.0",
|
||||
"@sap-ai-sdk/ai-api": "^1.17.0",
|
||||
"@sap-ai-sdk/orchestration": "^1.17.0",
|
||||
"@sentry/browser": "^9.12.0",
|
||||
"@streamparser/json": "^0.0.22",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
@@ -468,6 +463,7 @@
|
||||
"exceljs": "^4.4.0",
|
||||
"execa": "^9.5.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"firebase": "^11.2.0",
|
||||
"fzf": "^0.5.2",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"globby": "^14.0.2",
|
||||
@@ -477,6 +473,7 @@
|
||||
"image-size": "^2.0.2",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mammoth": "^1.11.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"nice-grpc": "^2.1.12",
|
||||
@@ -484,7 +481,7 @@
|
||||
"ollama": "^0.5.13",
|
||||
"open": "^10.1.2",
|
||||
"open-graph-scraper": "^6.9.0",
|
||||
"openai": "^6.9.0",
|
||||
"openai": "^4.83.0",
|
||||
"os-name": "^6.0.0",
|
||||
"p-mutex": "^1.0.0",
|
||||
"p-timeout": "^6.1.4",
|
||||
@@ -509,10 +506,7 @@
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": ">=3.1.1",
|
||||
"tar": "^7.5.2",
|
||||
"vite": "^7.1.11",
|
||||
"js-yaml": "^4.1.1"
|
||||
"tar-fs": ">=3.1.1"
|
||||
},
|
||||
"c8": {
|
||||
"reporter": [
|
||||
|
||||
@@ -69,18 +69,6 @@ service FileService {
|
||||
|
||||
// Opens or creates a focus chain checklist markdown file for editing
|
||||
rpc openFocusChainFile(StringRequest) returns (Empty);
|
||||
|
||||
// Refreshes all hook toggles (discovers hooks and their enabled state)
|
||||
rpc refreshHooks(EmptyRequest) returns (HooksToggles);
|
||||
|
||||
// Toggles a hook on or off via chmod +x/-x
|
||||
rpc toggleHook(ToggleHookRequest) returns (ToggleHookResponse);
|
||||
|
||||
// Creates a new hook from template
|
||||
rpc createHook(CreateHookRequest) returns (CreateHookResponse);
|
||||
|
||||
// Deletes an existing hook file
|
||||
rpc deleteHook(DeleteHookRequest) returns (DeleteHookResponse);
|
||||
}
|
||||
|
||||
// Response for refreshRules operation
|
||||
@@ -220,61 +208,3 @@ message ToggleWorkflowRequest {
|
||||
bool enabled = 3;
|
||||
RuleScope scope = 4; // Scope of the workflow (local, global, or remote)
|
||||
}
|
||||
|
||||
// Maps from hook name to enabled/disabled status
|
||||
message HookInfo {
|
||||
string name = 1;
|
||||
bool enabled = 2;
|
||||
string absolutePath = 3;
|
||||
}
|
||||
|
||||
message WorkspaceHooks {
|
||||
string workspace_name = 1;
|
||||
repeated HookInfo hooks = 2;
|
||||
}
|
||||
|
||||
message HooksToggles {
|
||||
repeated HookInfo global_hooks = 1;
|
||||
repeated WorkspaceHooks workspace_hooks = 2;
|
||||
bool is_windows = 3; // Whether the system is Windows (toggles disabled)
|
||||
}
|
||||
|
||||
// Request to toggle a hook
|
||||
message ToggleHookRequest {
|
||||
Metadata metadata = 1;
|
||||
string hook_name = 2; // Name of the hook (e.g., "TaskStart")
|
||||
bool is_global = 3; // Whether this is a global or workspace hook
|
||||
bool enabled = 4; // Whether to enable (chmod +x) or disable (chmod -x)
|
||||
optional string workspace_name = 5; // For multi-root workspaces, specifies which workspace
|
||||
}
|
||||
|
||||
// Response for toggleHook operation
|
||||
message ToggleHookResponse {
|
||||
HooksToggles hooks_toggles = 1;
|
||||
}
|
||||
|
||||
// Request to create a hook
|
||||
message CreateHookRequest {
|
||||
Metadata metadata = 1;
|
||||
string hook_name = 2; // Name of the hook to create
|
||||
bool is_global = 3; // Whether to create in global or workspace hooks directory
|
||||
optional string workspace_name = 4; // For multi-root workspaces, specifies which workspace
|
||||
}
|
||||
|
||||
// Response for createHook operation
|
||||
message CreateHookResponse {
|
||||
HooksToggles hooks_toggles = 1;
|
||||
}
|
||||
|
||||
// Request to delete a hook
|
||||
message DeleteHookRequest {
|
||||
Metadata metadata = 1;
|
||||
string hook_name = 2; // Name of the hook to delete
|
||||
bool is_global = 3; // Whether this is a global or workspace hook
|
||||
optional string workspace_name = 4; // For multi-root workspaces, specifies which workspace
|
||||
}
|
||||
|
||||
// Response for deleteHook operation
|
||||
message DeleteHookResponse {
|
||||
HooksToggles hooks_toggles = 1;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,6 @@ message OpenRouterModelInfo {
|
||||
optional bool supports_global_endpoint = 11;
|
||||
repeated ModelTier tiers = 12;
|
||||
optional string name = 13;
|
||||
optional double temperature = 14;
|
||||
}
|
||||
|
||||
// Shared response message for model information
|
||||
@@ -589,7 +588,6 @@ message ModelsApiConfiguration {
|
||||
optional string plan_mode_aihubmix_model_id = 135;
|
||||
optional OpenAiCompatibleModelInfo plan_mode_aihubmix_model_info = 136;
|
||||
optional string plan_mode_nous_research_model_id = 137;
|
||||
optional string gemini_plan_mode_thinking_level = 138;
|
||||
|
||||
// Act mode configurations
|
||||
optional ApiProvider act_mode_api_provider = 200;
|
||||
@@ -630,5 +628,4 @@ message ModelsApiConfiguration {
|
||||
optional string act_mode_aihubmix_model_id = 235;
|
||||
optional OpenAiCompatibleModelInfo act_mode_aihubmix_model_info = 236;
|
||||
optional string act_mode_nous_research_model_id = 237;
|
||||
optional string gemini_act_mode_thinking_level = 238;
|
||||
}
|
||||
|
||||
+1
-15
@@ -362,7 +362,7 @@ message UpdateSettingsRequest {
|
||||
optional int32 subagent_terminal_output_line_limit = 30;
|
||||
optional string cline_env = 31;
|
||||
optional bool native_tool_call_enabled = 32;
|
||||
optional OnboardingModelGroup onboarding_models = 33;
|
||||
optional bool show_onboarding_flow = 33;
|
||||
}
|
||||
|
||||
message UpdateTerminalConnectionTimeoutRequest {
|
||||
@@ -390,17 +390,3 @@ message OnboardingProgressRequest {
|
||||
optional bool completed = 3;
|
||||
optional string model_selected = 4;
|
||||
}
|
||||
|
||||
message OnboardingModelGroup {
|
||||
repeated OnboardingModel models = 1;
|
||||
}
|
||||
|
||||
message OnboardingModel {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
int32 score = 3;
|
||||
int32 latency = 4;
|
||||
string badge = 5;
|
||||
string group = 6;
|
||||
OpenRouterModelInfo info = 7;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execSync } from "child_process"
|
||||
|
||||
/**
|
||||
* Build Docker image for Cline CLI
|
||||
* This script builds a Docker image using pre-built binaries from dist-standalone/
|
||||
*
|
||||
* Prerequisites:
|
||||
* - Run `npm run compile-standalone` first to build all platform binaries
|
||||
* - Run `npm run compile-cli` first to build CLI binaries
|
||||
*/
|
||||
|
||||
function runCommand(command, description) {
|
||||
console.log(`\n${description}...`)
|
||||
try {
|
||||
execSync(command, { stdio: "inherit" })
|
||||
console.log("✓ Success\n")
|
||||
} catch (error) {
|
||||
console.error(`✗ Failed: ${error.message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
function getCommandOutput(command) {
|
||||
try {
|
||||
return execSync(command, { encoding: "utf-8" }).trim()
|
||||
} catch (error) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
function buildPrerequisites() {
|
||||
console.log("Building prerequisites...\n")
|
||||
|
||||
// Build standalone (includes cline-core and platform-specific native modules)
|
||||
runCommand("npm run compile-standalone", "Running npm run compile-standalone")
|
||||
|
||||
// Build CLI binaries for all platforms
|
||||
runCommand("npm run compile-cli-all-platforms", "Running npm run compile-cli-all-platforms")
|
||||
|
||||
console.log("✓ All prerequisites built successfully\n")
|
||||
}
|
||||
|
||||
function main() {
|
||||
console.log("🐳 Building Cline CLI Docker Image\n")
|
||||
|
||||
// Remove existing container to ensure clean state after rebuild
|
||||
const containerId = getCommandOutput(`docker ps -aq --filter "name=^cline-cli-dev$"`)
|
||||
if (containerId) {
|
||||
console.log("🗑️ Removing existing container to ensure fresh start...")
|
||||
try {
|
||||
execSync(`docker rm -f cline-cli-dev`, { stdio: "inherit" })
|
||||
console.log("✓ Container removed\n")
|
||||
} catch (error) {
|
||||
console.log("Note: Container cleanup failed, continuing anyway\n")
|
||||
}
|
||||
}
|
||||
|
||||
buildPrerequisites()
|
||||
|
||||
// Build Docker image for native platform
|
||||
// Docker will automatically use the correct architecture (arm64 on Apple Silicon, amd64 on Intel)
|
||||
runCommand("docker build -f docker/Dockerfile -t cline-cli:dev .", "Building Docker image")
|
||||
|
||||
console.log("✅ Docker image built successfully!")
|
||||
console.log("\n📋 Next steps:\n")
|
||||
console.log("Interactive shell:")
|
||||
console.log(" npm run docker:shell\n")
|
||||
console.log("This will:")
|
||||
console.log(" • Reuse existing 'cline-cli-dev' container if running")
|
||||
console.log(" • Start stopped container if it exists")
|
||||
console.log(" • Create new persistent container if none exists")
|
||||
console.log(" • Mount current directory at /workspace")
|
||||
console.log(" • Provide all CLI commands (cline auth, cline task, etc.)")
|
||||
console.log("\nContainer persists between sessions. To remove:")
|
||||
console.log(" docker rm -f cline-cli-dev\n")
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execSync } from "child_process"
|
||||
import { platform } from "os"
|
||||
|
||||
const CONTAINER_NAME = "cline-cli-dev"
|
||||
|
||||
function runCommand(command) {
|
||||
try {
|
||||
return execSync(command, { encoding: "utf-8" }).trim()
|
||||
} catch (error) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentDirectory() {
|
||||
// Get current working directory in a cross-platform way
|
||||
return process.cwd()
|
||||
}
|
||||
|
||||
function main() {
|
||||
console.log("🐳 Cline CLI Docker Shell\n")
|
||||
|
||||
// Check if container exists (running or stopped)
|
||||
const containerId = runCommand(`docker ps -a --filter "name=^${CONTAINER_NAME}$" --format "{{.ID}}"`)
|
||||
|
||||
if (containerId) {
|
||||
// Check if container is running
|
||||
const isRunning = runCommand(`docker ps --filter "id=${containerId}" --format "{{.ID}}"`)
|
||||
|
||||
if (isRunning) {
|
||||
console.log(`📦 Connecting to running container: ${CONTAINER_NAME}\n`)
|
||||
try {
|
||||
execSync(`docker exec -it ${containerId} /bin/bash`, { stdio: "inherit" })
|
||||
} catch (error) {
|
||||
// User exited shell normally
|
||||
}
|
||||
} else {
|
||||
console.log(`▶️ Starting stopped container: ${CONTAINER_NAME}\n`)
|
||||
try {
|
||||
execSync(`docker start ${containerId}`, { stdio: "inherit" })
|
||||
execSync(`docker exec -it ${containerId} /bin/bash`, { stdio: "inherit" })
|
||||
} catch (error) {
|
||||
// User exited shell normally
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`🚀 Creating new container: ${CONTAINER_NAME}\n`)
|
||||
const cwd = getCurrentDirectory()
|
||||
|
||||
try {
|
||||
// Use different volume mount syntax for Windows vs Unix
|
||||
const isWindows = platform() === "win32"
|
||||
const volumeMount = isWindows ? `${cwd.replace(/\\/g, "/")}:/workspace` : `${cwd}:/workspace`
|
||||
|
||||
execSync(
|
||||
`docker run -it --name ${CONTAINER_NAME} -v "${volumeMount}" -w /workspace --entrypoint /bin/bash cline-cli:dev`,
|
||||
{ stdio: "inherit" },
|
||||
)
|
||||
} catch (error) {
|
||||
// User exited shell normally
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -11,7 +11,7 @@ import fs from "fs"
|
||||
import https from "https"
|
||||
import path from "path"
|
||||
import { pipeline } from "stream/promises"
|
||||
import * as tar from "tar"
|
||||
import tar from "tar"
|
||||
import { promisify } from "util"
|
||||
import { createGunzip } from "zlib"
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ const TARGET_PLATFORMS = [
|
||||
{ platform: "darwin", arch: "x64", targetDir: "darwin-x64" },
|
||||
{ platform: "darwin", arch: "arm64", targetDir: "darwin-arm64" },
|
||||
{ platform: "linux", arch: "x64", targetDir: "linux-x64" },
|
||||
{ platform: "linux", arch: "arm64", targetDir: "linux-arm64" },
|
||||
]
|
||||
const SUPPORTED_BINARY_MODULES = ["better-sqlite3"]
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler } from "../../core/api/index"
|
||||
import { ApiStream } from "../../core/api/transform/stream"
|
||||
@@ -33,7 +33,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
console.log("[DIFY DEBUG] createMessage called with:", {
|
||||
systemPromptLength: systemPrompt?.length || 0,
|
||||
messagesCount: messages?.length || 0,
|
||||
@@ -255,7 +255,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
// Dify's context is managed by `conversation_id`. The `query` should be the last user message.
|
||||
// The system prompt is typically configured in the Dify App itself.
|
||||
const lastUserMessage = messages.filter((m) => m.role === "user").pop()
|
||||
|
||||
@@ -9,6 +9,14 @@ export interface EnvironmentConfig {
|
||||
appBaseUrl: string
|
||||
apiBaseUrl: string
|
||||
mcpBaseUrl: string
|
||||
firebase: {
|
||||
apiKey: string
|
||||
authDomain: string
|
||||
projectId: string
|
||||
storageBucket?: string
|
||||
messagingSenderId?: string
|
||||
appId?: string
|
||||
}
|
||||
}
|
||||
|
||||
class ClineEndpoint {
|
||||
@@ -55,6 +63,14 @@ class ClineEndpoint {
|
||||
appBaseUrl: "https://staging-app.cline.bot",
|
||||
apiBaseUrl: "https://core-api.staging.int.cline.bot",
|
||||
mcpBaseUrl: "https://core-api.staging.int.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyASSwkwX1kSO8vddjZkE5N19QU9cVQ0CIk",
|
||||
authDomain: "cline-staging.firebaseapp.com",
|
||||
projectId: "cline-staging",
|
||||
storageBucket: "cline-staging.firebasestorage.app",
|
||||
messagingSenderId: "853479478430",
|
||||
appId: "1:853479478430:web:2de0dba1c63c3262d4578f",
|
||||
},
|
||||
}
|
||||
case Environment.local:
|
||||
return {
|
||||
@@ -62,6 +78,11 @@ class ClineEndpoint {
|
||||
appBaseUrl: "http://localhost:3000",
|
||||
apiBaseUrl: "http://localhost:7777",
|
||||
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyD8wtkd1I-EICuAg6xgAQpRdwYTvwxZG2w",
|
||||
authDomain: "cline-preview.firebaseapp.com",
|
||||
projectId: "cline-preview",
|
||||
},
|
||||
}
|
||||
default:
|
||||
return {
|
||||
@@ -69,6 +90,14 @@ class ClineEndpoint {
|
||||
appBaseUrl: "https://app.cline.bot",
|
||||
apiBaseUrl: "https://api.cline.bot",
|
||||
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
|
||||
firebase: {
|
||||
apiKey: "AIzaSyC5rx59Xt8UgwdU3PCfzUF7vCwmp9-K2vk",
|
||||
authDomain: "cline-prod.firebaseapp.com",
|
||||
projectId: "cline-prod",
|
||||
storageBucket: "cline-prod.firebasestorage.app",
|
||||
messagingSenderId: "941048379330",
|
||||
appId: "1:941048379330:web:45058eedeefc5cdfcc485b",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiConfiguration, ModelInfo, QwenApiRegions } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { AIhubmixHandler } from "./providers/aihubmix"
|
||||
import { AnthropicHandler } from "./providers/anthropic"
|
||||
@@ -47,8 +47,9 @@ import { ApiStream, ApiStreamUsageChunk } from "./transform/stream"
|
||||
export type CommonApiHandlerOptions = {
|
||||
onRetryAttempt?: ApiConfiguration["onRetryAttempt"]
|
||||
}
|
||||
|
||||
export interface ApiHandler {
|
||||
createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ClineTool[], useResponseApi?: boolean): ApiStream
|
||||
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream
|
||||
getModel(): ApiHandlerModel
|
||||
getApiStreamUsage?(): Promise<ApiStreamUsageChunk | undefined>
|
||||
}
|
||||
@@ -94,7 +95,6 @@ function createHandlerForProvider(
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
geminiThinkingLevel: mode === "plan" ? options.geminiPlanModeThinkingLevel : options.geminiActModeThinkingLevel,
|
||||
})
|
||||
case "bedrock":
|
||||
return new AwsBedrockHandler({
|
||||
@@ -167,7 +167,6 @@ function createHandlerForProvider(
|
||||
geminiBaseUrl: options.geminiBaseUrl,
|
||||
thinkingBudgetTokens:
|
||||
mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens,
|
||||
thinkingLevel: mode === "plan" ? options.geminiPlanModeThinkingLevel : options.geminiActModeThinkingLevel,
|
||||
apiModelId: mode === "plan" ? options.planModeApiModelId : options.actModeApiModelId,
|
||||
ulid: options.ulid,
|
||||
})
|
||||
@@ -252,7 +251,6 @@ function createHandlerForProvider(
|
||||
openRouterProviderSorting: options.openRouterProviderSorting,
|
||||
openRouterModelId: mode === "plan" ? options.planModeOpenRouterModelId : options.actModeOpenRouterModelId,
|
||||
openRouterModelInfo: mode === "plan" ? options.planModeOpenRouterModelInfo : options.actModeOpenRouterModelInfo,
|
||||
geminiThinkingLevel: mode === "plan" ? options.geminiPlanModeThinkingLevel : options.geminiActModeThinkingLevel,
|
||||
})
|
||||
case "litellm":
|
||||
return new LiteLlmHandler({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { afterEach, beforeEach, describe, it } from "mocha"
|
||||
import sinon from "sinon"
|
||||
import "should"
|
||||
import { ClaudeCodeHandler } from "@core/api/providers/claude-code"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
|
||||
describe("ClaudeCodeHandler", () => {
|
||||
let handler: ClaudeCodeHandler
|
||||
@@ -71,7 +71,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
@@ -140,7 +140,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
@@ -199,7 +199,7 @@ describe("ClaudeCodeHandler", () => {
|
||||
runClaudeCodeStub.returns(mockGenerator() as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const usageData: any[] = []
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Anthropic from "@anthropic-ai/sdk"
|
||||
import { LiteLlmHandler, type LiteLlmModelInfoResponse } from "@core/api/providers/litellm"
|
||||
import { convertToOpenAiMessages } from "@core/api/transform/openai-format"
|
||||
import { expect } from "chai"
|
||||
import sinon from "sinon"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { mockFetchForTesting } from "@/shared/net"
|
||||
|
||||
const fakeClient = {
|
||||
@@ -109,7 +109,7 @@ describe("LiteLlmHandler", () => {
|
||||
|
||||
it("sends the system prompt and messages with the openai format", async () => {
|
||||
const systemPrompt = "Test System Prompt"
|
||||
const messages: ClineStorageMessage[] = [
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "first message",
|
||||
@@ -161,7 +161,7 @@ describe("LiteLlmHandler", () => {
|
||||
|
||||
it("inserts the cache control in the system prompt and the last two user messages", async () => {
|
||||
const systemPrompt = "Test System Prompt"
|
||||
const messages: ClineStorageMessage[] = [
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: "first message",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { afterEach, before, beforeEach, describe, it } from "mocha"
|
||||
import "should"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiHandlerOptions } from "@shared/api"
|
||||
import axios from "axios"
|
||||
import sinon from "sinon"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { OllamaHandler } from "../ollama"
|
||||
|
||||
describe("OllamaHandler", () => {
|
||||
@@ -59,7 +59,7 @@ describe("OllamaHandler", () => {
|
||||
} as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
const usageInfo = []
|
||||
@@ -114,7 +114,7 @@ describe("OllamaHandler", () => {
|
||||
}
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
// Start the request and catch the error
|
||||
let errorMessage = ""
|
||||
@@ -158,7 +158,7 @@ describe("OllamaHandler", () => {
|
||||
} as any)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
|
||||
@@ -204,7 +204,7 @@ describe("OllamaHandler", () => {
|
||||
}
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: ClineStorageMessage[] = [{ role: "user", content: "Hello" }]
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = []
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { AnthropicModelId, anthropicDefaultModelId, anthropicModels, CLAUDE_SONNET_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { sanitizeAnthropicMessages } from "../transform/anthropic-format"
|
||||
@@ -43,7 +43,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: AnthropicTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
|
||||
const model = this.getModel()
|
||||
@@ -74,7 +74,19 @@ export class AnthropicHandler implements ApiHandler {
|
||||
case "claude-opus-4-1-20250805":
|
||||
case "claude-3-opus-20240229":
|
||||
case "claude-3-haiku-20240307": {
|
||||
const anthropicMessages = sanitizeAnthropicMessages(messages, true)
|
||||
/*
|
||||
The latest message will be the new user message, one before will be the assistant message from a previous request, and the user message before that will be a previously cached user message. So we need to mark the latest user message as ephemeral to cache it for the next request, and mark the second to last user message as ephemeral to let the server know the last message to retrieve from the cache for the current request..
|
||||
*/
|
||||
const userMsgIndices = messages.reduce((acc, msg, index) => {
|
||||
if (msg.role === "user") {
|
||||
acc.push(index)
|
||||
}
|
||||
return acc
|
||||
}, [] as number[])
|
||||
const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
|
||||
const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
|
||||
|
||||
const anthropicMessages = sanitizeAnthropicMessages(messages, lastUserMsgIndex, secondLastMsgUserIndex)
|
||||
|
||||
stream = await client.messages.create(
|
||||
{
|
||||
@@ -94,7 +106,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
messages: anthropicMessages,
|
||||
// tools, // cache breakpoints go from tools > system > messages, and since tools dont change, we can just set the breakpoint at the end of system (this avoids having to set a breakpoint at the end of tools which by itself does not meet min requirements for haiku caching)
|
||||
stream: true,
|
||||
tools: nativeToolsOn ? tools : undefined,
|
||||
tools: nativeToolsOn ? (tools as AnthropicTool[]) : undefined,
|
||||
// tool_choice options:
|
||||
// - none: disables tool use, even if tools are provided. Claude will not call any tools.
|
||||
// - auto: allows Claude to decide whether to call any provided tools or not. This is the default value when tools are provided.
|
||||
@@ -123,9 +135,9 @@ export class AnthropicHandler implements ApiHandler {
|
||||
max_tokens: model.info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
system: [{ text: systemPrompt, type: "text" }],
|
||||
messages: sanitizeAnthropicMessages(messages, false),
|
||||
tools: nativeToolsOn ? tools : undefined,
|
||||
tool_choice: { type: "auto" },
|
||||
messages: sanitizeAnthropicMessages(messages),
|
||||
// tools,
|
||||
// tool_choice: { type: "auto" },
|
||||
stream: true,
|
||||
})
|
||||
break
|
||||
@@ -204,7 +216,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
case "content_block_delta":
|
||||
switch (chunk.delta.type) {
|
||||
case "thinking_delta":
|
||||
// 'reasoning' type just displays in the UI, but ant_thinking will be used to send the thinking traces back to the API
|
||||
// 'reasoning' type just displays in the UI, but reasoning with signature will be used to send the thinking traces back to the API
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: chunk.delta.thinking,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { AskSageModelId, askSageDefaultModelId, askSageDefaultURL, askSageModels, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -47,7 +47,7 @@ export class AskSageHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
try {
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { BasetenModelId, basetenDefaultModelId, basetenModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { ToolCallProcessor } from "../transform/tool-call-processor"
|
||||
|
||||
interface BasetenHandlerOptions extends CommonApiHandlerOptions {
|
||||
basetenApiKey?: string
|
||||
@@ -100,11 +98,10 @@ export class BasetenHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const maxTokens = this.getOptimalMaxTokens(model)
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
@@ -118,22 +115,21 @@ export class BasetenHandler implements ApiHandler {
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
temperature: 0,
|
||||
tools,
|
||||
tool_choice: tools && tools.length > 0 ? "auto" : undefined,
|
||||
})
|
||||
|
||||
let didOutputUsage = false
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk?.choices?.[0]?.delta
|
||||
const delta = chunk.choices[0]?.delta
|
||||
|
||||
// Handle reasoning field if present (for reasoning models with parsed output)
|
||||
if (delta && "reasoning" in delta && delta?.reasoning) {
|
||||
const reasoning = typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning)
|
||||
if ((delta as any)?.reasoning) {
|
||||
const reasoningContent = (delta as any).reasoning as string
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning,
|
||||
reasoning: reasoningContent,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle content field
|
||||
@@ -144,10 +140,6 @@ export class BasetenHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (delta?.tool_calls) {
|
||||
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls)
|
||||
}
|
||||
|
||||
// Handle usage information - only output once
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
yield* this.yieldUsage(model.info, chunk.usage)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
// Import proper AWS SDK types
|
||||
import type { ContentBlock, Message } from "@aws-sdk/client-bedrock-runtime"
|
||||
import {
|
||||
@@ -11,7 +12,6 @@ import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
import { BedrockModelId, bedrockDefaultModelId, bedrockModels, CLAUDE_SONNET_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI, calculateApiCostQwen } from "@utils/cost"
|
||||
import { ExtensionRegistryInfo } from "@/registry"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
@@ -121,7 +121,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ maxRetries: 4 })
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
// cross region inference requires prefixing the model id with the region
|
||||
const rawModelId = await this.getModelId()
|
||||
|
||||
@@ -342,7 +342,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createDeepseekMessage(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -480,7 +480,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* First uses convertToR1Format to merge consecutive messages with the same role,
|
||||
* then converts to the string format that DeepSeek R1 expects
|
||||
*/
|
||||
private formatDeepseekR1Prompt(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
private formatDeepseekR1Prompt(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
// First use convertToR1Format to merge consecutive messages with the same role
|
||||
const r1Messages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
|
||||
@@ -513,7 +513,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* Estimates token count based on text length (approximate)
|
||||
* Note: This is a rough estimation, as the actual token count depends on the tokenizer
|
||||
*/
|
||||
private estimateInputTokens(systemPrompt: string, messages: ClineStorageMessage[]): number {
|
||||
private estimateInputTokens(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): number {
|
||||
// For Deepseek R1, we estimate the token count of the formatted prompt
|
||||
// The formatted prompt includes special tokens and consistent formatting
|
||||
const formattedPrompt = this.formatDeepseekR1Prompt(systemPrompt, messages)
|
||||
@@ -680,7 +680,11 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
console.error("Error processing Converse API response:", error)
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] Failed to process response: ${error instanceof Error ? error.message : String(error)}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,20 +703,9 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
text: `[ERROR] Model stream error: ${chunk.modelStreamErrorException.message}`,
|
||||
}
|
||||
} else if (chunk.validationException) {
|
||||
// Check if this is a context window error - if so, throw it
|
||||
// so the retry mechanism can handle truncation
|
||||
const message = chunk.validationException.message || ""
|
||||
const isContextError = /input.*too long|context.*exceed|maximum.*token|input length.*max.*tokens/i.test(message)
|
||||
|
||||
if (isContextError) {
|
||||
// Throw as exception so context management can handle it
|
||||
throw chunk.validationException
|
||||
}
|
||||
|
||||
// Otherwise yield as error text
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] Validation error: ${message}`,
|
||||
text: `[ERROR] Validation error: ${chunk.validationException.message}`,
|
||||
}
|
||||
} else if (chunk.throttlingException) {
|
||||
yield {
|
||||
@@ -786,7 +779,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createAnthropicMessage(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
enable1mContextWindow: boolean,
|
||||
@@ -842,7 +835,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* Formats messages for models using the Converse API specification
|
||||
* Used by both Anthropic and Nova models to avoid code duplication
|
||||
*/
|
||||
private formatMessagesForConverseAPI(messages: ClineStorageMessage[]): Message[] {
|
||||
private formatMessagesForConverseAPI(messages: Anthropic.Messages.MessageParam[]): Message[] {
|
||||
return messages.map((message) => {
|
||||
// Determine role (user or assistant)
|
||||
const role = message.role === "user" ? ConversationRole.USER : ConversationRole.ASSISTANT
|
||||
@@ -975,7 +968,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createNovaMessage(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -1015,7 +1008,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createOpenAIMessage(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
@@ -1150,7 +1143,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
*/
|
||||
private async *createQwenMessage(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import Cerebras from "@cerebras/cerebras_cloud_sdk"
|
||||
import { CerebrasModelId, cerebrasDefaultModelId, cerebrasModels, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -46,7 +46,7 @@ export class CerebrasHandler implements ApiHandler {
|
||||
baseDelay: 5000, // Start with 5 second delay
|
||||
maxDelay: 60000, // Allow up to 60 second delays to respect rate limits
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
|
||||
// Convert Anthropic messages to Cerebras format
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { filterMessagesForClaudeCode } from "@/integrations/claude-code/message-filter"
|
||||
import { runClaudeCode } from "@/integrations/claude-code/run"
|
||||
import { ClaudeCodeModelId, claudeCodeDefaultModelId, claudeCodeModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { type ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { type ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
@@ -24,7 +24,7 @@ export class ClaudeCodeHandler implements ApiHandler {
|
||||
baseDelay: 2000,
|
||||
maxDelay: 15000,
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
// Filter out image blocks since Claude Code doesn't support them
|
||||
const filteredMessages = filterMessagesForClaudeCode(messages)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { shouldSkipReasoningForModel } from "@utils/model-utils"
|
||||
import axios from "axios"
|
||||
@@ -7,9 +8,7 @@ import { ClineEnv } from "@/config"
|
||||
import { ClineAccountService } from "@/services/account/ClineAccountService"
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import { buildClineExtraHeaders } from "@/services/EnvUtils"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "@/shared/ClineAccount"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -27,7 +26,6 @@ interface ClineHandlerOptions extends CommonApiHandlerOptions {
|
||||
openRouterModelId?: string
|
||||
openRouterModelInfo?: ModelInfo
|
||||
clineAccountId?: string
|
||||
geminiThinkingLevel?: string
|
||||
}
|
||||
|
||||
export class ClineHandler implements ApiHandler {
|
||||
@@ -98,7 +96,7 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
try {
|
||||
const client = await this.ensureClient()
|
||||
|
||||
@@ -116,13 +114,11 @@ export class ClineHandler implements ApiHandler {
|
||||
this.options.thinkingBudgetTokens,
|
||||
this.options.openRouterProviderSorting,
|
||||
tools,
|
||||
this.options.geminiThinkingLevel,
|
||||
)
|
||||
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
|
||||
for await (const chunk of stream) {
|
||||
Logger.debug("ClineHandler chunk:" + JSON.stringify(chunk))
|
||||
// openrouter returns an error object instead of the openai sdk throwing an error
|
||||
if ("error" in chunk) {
|
||||
const error = chunk.error as OpenRouterErrorResponse["error"]
|
||||
@@ -153,7 +149,6 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
const delta = choice?.delta
|
||||
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
@@ -185,7 +180,7 @@ export class ClineHandler implements ApiHandler {
|
||||
"reasoning_details" in delta &&
|
||||
delta.reasoning_details &&
|
||||
// @ts-ignore-next-line
|
||||
delta?.reasoning_details?.length && // exists and non-0
|
||||
delta.reasoning_details.length && // exists and non-0
|
||||
!shouldSkipReasoningForModel(this.options.openRouterModelId)
|
||||
) {
|
||||
yield {
|
||||
@@ -195,8 +190,6 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
console.log("didOutputUsage", didOutputUsage, chunk.usage)
|
||||
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
// @ts-ignore-next-line
|
||||
let totalCost = (chunk.usage.cost || 0) + (chunk.usage.cost_details?.upstream_inference_cost || 0)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { DeepSeekModelId, deepSeekDefaultModelId, deepSeekModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -75,7 +75,7 @@ export class DeepSeekHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ModelInfo } from "../../../shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
@@ -97,7 +97,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
console.log("[DIFY DEBUG] createMessage called with:", {
|
||||
systemPromptLength: systemPrompt?.length || 0,
|
||||
messagesCount: messages?.length || 0,
|
||||
@@ -384,7 +384,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: ClineStorageMessage[]): string {
|
||||
private convertMessagesToQuery(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): string {
|
||||
// Dify's context is managed by `conversation_id`. The `query` should be the last user message.
|
||||
// The system prompt is typically configured in the Dify App itself.
|
||||
const lastUserMessage = messages.filter((m) => m.role === "user").pop()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { DoubaoModelId, doubaoDefaultModelId, doubaoModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -50,7 +50,7 @@ export class DoubaoHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { FireworksModelId, fireworksDefaultModelId, fireworksModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -41,7 +41,7 @@ export class FireworksHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.fireworksModelId ?? ""
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
// Restore GenerateContentConfig import and add GenerateContentResponseUsageMetadata
|
||||
import {
|
||||
ApiError,
|
||||
@@ -6,11 +7,10 @@ import {
|
||||
type GenerateContentResponseUsageMetadata,
|
||||
GoogleGenAI,
|
||||
FunctionDeclaration as GoogleTool,
|
||||
ThinkingLevel,
|
||||
Part,
|
||||
} from "@google/genai"
|
||||
import { GeminiModelId, geminiDefaultModelId, geminiModels, ModelInfo } from "@shared/api"
|
||||
import { telemetryService } from "@/services/telemetry"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { RetriableError, withRetry } from "../retry"
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
@@ -28,7 +28,6 @@ interface GeminiHandlerOptions extends CommonApiHandlerOptions {
|
||||
geminiApiKey?: string
|
||||
geminiBaseUrl?: string
|
||||
thinkingBudgetTokens?: number
|
||||
thinkingLevel?: string
|
||||
apiModelId?: string
|
||||
ulid?: string
|
||||
}
|
||||
@@ -111,49 +110,34 @@ export class GeminiHandler implements ApiHandler {
|
||||
baseDelay: 2000,
|
||||
maxDelay: 15000,
|
||||
})
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: GoogleTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: GoogleTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const { id: modelId, info } = this.getModel()
|
||||
const contents = messages.map(convertAnthropicMessageToGemini)
|
||||
|
||||
// Configure thinking budget if supported
|
||||
const _thinkingBudget = this.options.thinkingBudgetTokens ?? 0
|
||||
const maxBudget = info.thinkingConfig?.maxBudget ?? 24576
|
||||
const thinkingBudget = Math.min(_thinkingBudget, maxBudget)
|
||||
// When ThinkingLevel is defineded, thinking budget cannot be zero
|
||||
// and only level is used to control thinking behavior.
|
||||
let thinkingLevel: ThinkingLevel | undefined
|
||||
if (this.options.thinkingLevel === "low") {
|
||||
thinkingLevel = ThinkingLevel.LOW
|
||||
} else if (this.options.thinkingLevel === "high") {
|
||||
thinkingLevel = ThinkingLevel.HIGH
|
||||
}
|
||||
const thinkingBudget = this.options.thinkingBudgetTokens ?? 0
|
||||
const _maxBudget = info.thinkingConfig?.maxBudget ?? 0
|
||||
|
||||
// Set up base generation config
|
||||
const requestConfig: GenerateContentConfig = {
|
||||
// Add base URL if configured
|
||||
httpOptions: this.options.geminiBaseUrl ? { baseUrl: this.options.geminiBaseUrl } : undefined,
|
||||
systemInstruction: systemPrompt,
|
||||
...{ systemInstruction: systemPrompt },
|
||||
// Set temperature (default to 0)
|
||||
// Gemini 3.0 recommends 1.0
|
||||
temperature: info.temperature ?? 1,
|
||||
temperature: 0,
|
||||
}
|
||||
|
||||
// Add thinking config if the model supports it
|
||||
requestConfig.thinkingConfig = {
|
||||
// Turn off thinking:
|
||||
// thinkingBudget: 0
|
||||
// Turn on dynamic thinking:
|
||||
// thinkingBudget: -1
|
||||
// Turn on fixed thinking budget:
|
||||
thinkingBudget: thinkingLevel ? undefined : thinkingBudget,
|
||||
thinkingLevel,
|
||||
includeThoughts: thinkingBudget > 0 || !!thinkingLevel,
|
||||
if (thinkingBudget > 0) {
|
||||
requestConfig.thinkingConfig = {
|
||||
thinkingBudget: thinkingBudget,
|
||||
includeThoughts: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Generate content using the configured parameters
|
||||
const sdkCallStartTime = Date.now()
|
||||
let responseId: string | undefined
|
||||
let sdkFirstChunkTime: number | undefined
|
||||
let ttftSdkMs: number | undefined
|
||||
let apiSuccess = false
|
||||
@@ -164,8 +148,7 @@ export class GeminiHandler implements ApiHandler {
|
||||
let thoughtsTokenCount = 0 // Initialize thought token counts
|
||||
let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined
|
||||
|
||||
const isNativeToolCallsEnabled = tools?.length
|
||||
if (isNativeToolCallsEnabled) {
|
||||
if (tools?.length) {
|
||||
requestConfig.tools = [{ functionDeclarations: tools }]
|
||||
requestConfig.toolConfig = {
|
||||
// Force the model to call 'any' function.
|
||||
@@ -193,45 +176,56 @@ export class GeminiHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
// Handle thinking content from Gemini's response
|
||||
const parts = chunk?.candidates?.[0]?.content?.parts || []
|
||||
for (const part of parts) {
|
||||
if (part.thought && part.text) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.responseId,
|
||||
reasoning: part.text || "",
|
||||
signature: part.thoughtSignature,
|
||||
}
|
||||
} else if (part.text) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: part.text,
|
||||
id: chunk.responseId,
|
||||
signature: part.thoughtSignature,
|
||||
const candidateForThoughts = chunk?.candidates?.[0]
|
||||
const partsForThoughts = candidateForThoughts?.content?.parts
|
||||
let thoughts = "" // Initialize as empty string
|
||||
|
||||
if (partsForThoughts) {
|
||||
// This ensures partsForThoughts is a Part[] array
|
||||
for (const part of partsForThoughts) {
|
||||
const { thought, text } = part as Part
|
||||
if (thought && text) {
|
||||
// Ensure part.text exists
|
||||
// Handle the thought part
|
||||
thoughts += text + "\n" // Append thought and a newline
|
||||
}
|
||||
}
|
||||
if (part.functionCall) {
|
||||
const functionCall = part.functionCall
|
||||
const args = Object.entries(functionCall.args || {}).filter(([_key, val]) => !!val)
|
||||
if (functionCall.args && args.length > 0) {
|
||||
}
|
||||
|
||||
if (thoughts.trim() !== "") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: thoughts.trim(),
|
||||
}
|
||||
thoughts = "" // Reset thoughts after yielding
|
||||
}
|
||||
|
||||
if (chunk.text) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk.text,
|
||||
}
|
||||
}
|
||||
|
||||
if (tools && chunk.functionCalls && chunk.functionCalls?.length > 0) {
|
||||
for (const functionCall of chunk.functionCalls) {
|
||||
if (functionCall.args) {
|
||||
console.log("[GeminiHandler] tool call received:", functionCall)
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: chunk.responseId,
|
||||
tool_call: {
|
||||
function: {
|
||||
id: chunk.responseId,
|
||||
id: functionCall.id || functionCall.name,
|
||||
name: functionCall.name,
|
||||
arguments: JSON.stringify(functionCall.args),
|
||||
},
|
||||
},
|
||||
signature: part.thoughtSignature,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usageMetadata) {
|
||||
responseId = chunk.responseId
|
||||
lastUsageMetadata = chunk.usageMetadata
|
||||
promptTokens = lastUsageMetadata.promptTokenCount ?? promptTokens
|
||||
outputTokens = lastUsageMetadata.candidatesTokenCount ?? outputTokens
|
||||
@@ -257,7 +251,6 @@ export class GeminiHandler implements ApiHandler {
|
||||
cacheReadTokens,
|
||||
cacheWriteTokens: 0,
|
||||
totalCost,
|
||||
id: responseId,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { GroqModelId, groqDefaultModelId, groqModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -192,7 +192,7 @@ export class GroqHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const modelFamily = this.detectModelFamily(model.id)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { hicapModelInfoSaneDefaults, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -44,7 +44,7 @@ export class HicapHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.hicapModelId ?? ""
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { HuaweiCloudMaasModelId, huaweiCloudMaasDefaultModelId, huaweiCloudMaasModels, ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -62,7 +62,7 @@ export class HuaweiCloudMaaSHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { HuggingFaceModelId, huggingFaceDefaultModelId, huggingFaceModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -69,7 +69,7 @@ export class HuggingFaceHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
try {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { LiteLLMModelInfo, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { isAnthropicModelId } from "@/utils/model-utils"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
@@ -184,7 +183,7 @@ export class LiteLlmHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const formattedMessages = convertToOpenAiMessages(messages)
|
||||
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam | Anthropic.Messages.TextBlockParam = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import type { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -40,7 +40,7 @@ export class LmStudioHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ retryAllErrors: true })
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { MinimaxModelId, ModelInfo, minimaxDefaultModelId, minimaxModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
@@ -46,7 +45,7 @@ export class MinimaxHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ClineTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Mistral } from "@mistralai/mistralai"
|
||||
import { HTTPClient } from "@mistralai/mistralai/lib/http"
|
||||
import { Tool as MistralTool } from "@mistralai/mistralai/models/components/tool"
|
||||
import { MistralModelId, ModelInfo, mistralDefaultModelId, mistralModels } from "@shared/api"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -48,7 +48,7 @@ export class MistralHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const stream = await client.chat
|
||||
.stream({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ModelInfo, MoonshotModelId, moonshotDefaultModelId, moonshotModels } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -40,7 +40,7 @@ export class MoonshotHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, type NebiusModelId, nebiusDefaultModelId, nebiusModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -39,7 +39,7 @@ export class NebiusHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, NousResearchModelId, nousResearchDefaultModelId, nousResearchModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -37,7 +37,7 @@ export class NousResearchHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { LiteLLMModelInfo, liteLlmDefaultModelId, liteLlmModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI, { APIError, OpenAIError } from "openai"
|
||||
import type { FinalRequestOptions, Headers as OpenAIHeaders } from "openai/core"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { OcaAuthService } from "@/services/auth/oca/OcaAuthService"
|
||||
import {
|
||||
@@ -9,7 +11,6 @@ import {
|
||||
} from "@/services/auth/oca/utils/constants"
|
||||
import { createOcaHeaders } from "@/services/auth/oca/utils/utils"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, type CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -37,7 +38,7 @@ export class OcaHandler implements ApiHandler {
|
||||
|
||||
protected initializeClient(options: OcaHandlerOptions) {
|
||||
return new (class OCIOpenAI extends OpenAI {
|
||||
protected override async prepareOptions(opts: any): Promise<void> {
|
||||
protected override async prepareOptions(opts: FinalRequestOptions<unknown>): Promise<void> {
|
||||
const token = await OcaAuthService.getInstance().getAuthToken()
|
||||
if (!token) {
|
||||
throw new OpenAIError("Unable to handle auth, Oracle Code Assist (OCA) access token is not available")
|
||||
@@ -54,7 +55,7 @@ export class OcaHandler implements ApiHandler {
|
||||
status: number | undefined,
|
||||
error: Object | undefined,
|
||||
message: string | undefined,
|
||||
headers: any | undefined,
|
||||
headers: OpenAIHeaders | undefined,
|
||||
): APIError {
|
||||
interface OciError {
|
||||
code?: string
|
||||
@@ -74,8 +75,7 @@ export class OcaHandler implements ApiHandler {
|
||||
if (opcRequestId) {
|
||||
ociErrorMessage += `\n(${OCI_HEADER_OPC_REQUEST_ID}: ${opcRequestId})`
|
||||
}
|
||||
const statusCode = typeof status === "number" ? status : 500
|
||||
return super.makeStatusError(statusCode, error ?? {}, ociErrorMessage, headers)
|
||||
return super.makeStatusError(status, error, ociErrorMessage, headers)
|
||||
}
|
||||
})({
|
||||
baseURL:
|
||||
@@ -139,7 +139,7 @@ export class OcaHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const formattedMessages = convertToOpenAiMessages(messages)
|
||||
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
|
||||
@@ -151,7 +151,7 @@ export class OcaHandler implements ApiHandler {
|
||||
|
||||
// Configuration for extended thinking
|
||||
const budgetTokens = this.options.thinkingBudgetTokens || 0
|
||||
const reasoningOn = budgetTokens !== 0
|
||||
const reasoningOn = budgetTokens !== 0 ? true : false
|
||||
const thinkingConfig = reasoningOn ? { type: "enabled", budget_tokens: budgetTokens } : undefined
|
||||
|
||||
let temperature: number | undefined = this.options.ocaModelInfo?.temperature ?? 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { type ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { type Config, type Message, Ollama } from "ollama"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import type { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOllamaMessages } from "../transform/ollama-format"
|
||||
@@ -48,7 +48,7 @@ export class OllamaHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry({ retryAllErrors: true })
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const ollamaMessages: Message[] = [{ role: "system", content: systemPrompt }, ...convertToOllamaMessages(messages)]
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, OpenAiNativeModelId, openAiNativeDefaultModelId, openAiNativeModels } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionReasoningEffort, ChatCompletionTool } from "openai/resources/chat/completions"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { convertToOpenAIResponsesInput } from "../transform/openai-response-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { getOpenAIToolParams, ToolCallProcessor } from "../transform/tool-call-processor"
|
||||
|
||||
@@ -63,20 +61,7 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
@withRetry()
|
||||
async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
useResponseFormat = false,
|
||||
): ApiStream {
|
||||
if (useResponseFormat) {
|
||||
yield* this.createResponseStream(systemPrompt, messages, tools)
|
||||
} else {
|
||||
yield* this.createCompletionStream(systemPrompt, messages, tools)
|
||||
}
|
||||
}
|
||||
|
||||
private async *createCompletionStream(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
@@ -200,208 +185,6 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private async *createResponseStream(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
// Convert messages to Responses API input format
|
||||
const input = convertToOpenAIResponsesInput(messages)
|
||||
|
||||
// Convert ChatCompletion tools to Responses API format if provided
|
||||
const responseTools = tools
|
||||
?.filter((tool) => tool.type === "function")
|
||||
.map((tool: any) => ({
|
||||
type: "function" as const,
|
||||
name: tool.function.name,
|
||||
description: tool.function.description,
|
||||
parameters: tool.function.parameters,
|
||||
strict: tool.function.strict ?? true, // Responses API defaults to strict mode
|
||||
}))
|
||||
|
||||
Logger.debug("OpenAI Responses Input: " + JSON.stringify(input))
|
||||
|
||||
// const lastAssistantMessage = [...messages].reverse().find((msg) => msg.role === "assistant" && msg.id)
|
||||
// const previous_response_id = lastAssistantMessage?.id
|
||||
|
||||
// Create the response using Responses API
|
||||
const stream = await client.responses.create({
|
||||
model: model.id,
|
||||
instructions: systemPrompt,
|
||||
input,
|
||||
stream: true,
|
||||
tools: responseTools,
|
||||
// previous_response_id,
|
||||
// store: true,
|
||||
reasoning: { effort: "medium", summary: "auto" },
|
||||
// include: ["reasoning.encrypted_content"],
|
||||
})
|
||||
|
||||
// Process the response stream
|
||||
for await (const chunk of stream) {
|
||||
Logger.debug("OpenAI Responses Chunk: " + JSON.stringify(chunk))
|
||||
|
||||
// Handle different event types from Responses API
|
||||
if (chunk.type === "response.output_item.added") {
|
||||
const item = chunk.item
|
||||
if (item.type === "function_call" && item.id) {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: item.id,
|
||||
tool_call: {
|
||||
call_id: item.call_id,
|
||||
function: {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
arguments: item.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (item.type === "reasoning" && item.encrypted_content && item.id) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: item.id,
|
||||
reasoning: "",
|
||||
redacted_data: item.encrypted_content,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.output_item.done") {
|
||||
const item = chunk.item
|
||||
if (item.type === "function_call") {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
id: item.id || item.call_id,
|
||||
tool_call: {
|
||||
call_id: item.call_id,
|
||||
function: {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
arguments: item.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (item.type === "reasoning") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: item.id,
|
||||
details: item.summary,
|
||||
reasoning: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_part.added") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
reasoning: chunk.part.text,
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_text.delta") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
reasoning: chunk.delta,
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_summary_part.done") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
id: chunk.item_id,
|
||||
details: chunk.part,
|
||||
reasoning: "",
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.output_text.delta") {
|
||||
// Handle text content deltas
|
||||
if (chunk.delta) {
|
||||
yield {
|
||||
id: chunk.item_id,
|
||||
type: "text",
|
||||
text: chunk.delta,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.reasoning_text.delta") {
|
||||
// Handle reasoning content deltas
|
||||
if (chunk.delta) {
|
||||
yield {
|
||||
id: chunk.item_id,
|
||||
type: "reasoning",
|
||||
reasoning: chunk.delta,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.function_call_arguments.delta") {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
function: {
|
||||
id: chunk.item_id,
|
||||
name: chunk.item_id,
|
||||
arguments: chunk.delta,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (chunk.type === "response.function_call_arguments.done") {
|
||||
// Handle completed function call
|
||||
if (chunk.item_id && chunk.name && chunk.arguments) {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
function: {
|
||||
id: chunk.item_id,
|
||||
name: chunk.name,
|
||||
arguments: chunk.arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
chunk.type === "response.incomplete" &&
|
||||
chunk.response?.status === "incomplete" &&
|
||||
chunk.response?.incomplete_details?.reason === "max_output_tokens"
|
||||
) {
|
||||
console.log("Ran out of tokens")
|
||||
if (chunk.response?.output_text?.length > 0) {
|
||||
console.log("Partial output:", chunk.response.output_text)
|
||||
} else {
|
||||
console.log("Ran out of tokens during reasoning")
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.type === "response.completed" && chunk.response?.usage) {
|
||||
// Handle usage information when response is complete
|
||||
const usage = chunk.response.usage
|
||||
const inputTokens = usage.input_tokens || 0
|
||||
const outputTokens = usage.output_tokens || 0
|
||||
const cacheReadTokens = usage.output_tokens_details?.reasoning_tokens || 0
|
||||
const cacheWriteTokens = usage.input_tokens_details?.cached_tokens || 0
|
||||
const totalTokens = usage.total_tokens || 0
|
||||
Logger.log(`Total tokens from Responses API usage: ${totalTokens}`)
|
||||
const totalCost = calculateApiCostOpenAI(model.info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
|
||||
const nonCachedInputTokens = Math.max(0, inputTokens - cacheReadTokens - cacheWriteTokens)
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: nonCachedInputTokens,
|
||||
outputTokens: outputTokens,
|
||||
cacheWriteTokens: cacheWriteTokens,
|
||||
cacheReadTokens: cacheReadTokens,
|
||||
totalCost: totalCost,
|
||||
id: chunk.response.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: OpenAiNativeModelId; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in openAiNativeModels) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { azureOpenAiDefaultApiVersion, ModelInfo, OpenAiCompatibleModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI, { AzureOpenAI } from "openai"
|
||||
import type { ChatCompletionReasoningEffort, ChatCompletionTool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -65,7 +65,11 @@ export class OpenAiHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ChatCompletionTool[]): ApiStream {
|
||||
async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.openAiModelId ?? ""
|
||||
const isDeepseekReasoner = modelId.includes("deepseek-reasoner")
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import { StateManager } from "@core/storage/StateManager"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import { shouldSkipReasoningForModel } from "@utils/model-utils"
|
||||
import axios from "axios"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -21,7 +20,6 @@ interface OpenRouterHandlerOptions extends CommonApiHandlerOptions {
|
||||
openRouterProviderSorting?: string
|
||||
reasoningEffort?: string
|
||||
thinkingBudgetTokens?: number
|
||||
geminiThinkingLevel?: string
|
||||
}
|
||||
|
||||
export class OpenRouterHandler implements ApiHandler {
|
||||
@@ -56,7 +54,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
this.lastGenerationId = undefined
|
||||
|
||||
@@ -69,7 +67,6 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
this.options.thinkingBudgetTokens,
|
||||
this.options.openRouterProviderSorting,
|
||||
tools,
|
||||
this.options.geminiThinkingLevel,
|
||||
)
|
||||
|
||||
let didOutputUsage: boolean = false
|
||||
@@ -217,11 +214,11 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.openRouterModelId || openRouterDefaultModelId
|
||||
const cachedModelInfo = StateManager.get().getModelInfo("openRouter", modelId)
|
||||
return {
|
||||
id: modelId,
|
||||
info: cachedModelInfo || openRouterDefaultModelInfo,
|
||||
const modelId = this.options.openRouterModelId
|
||||
const modelInfo = this.options.openRouterModelInfo
|
||||
if (modelId && modelInfo) {
|
||||
return { id: modelId, info: modelInfo }
|
||||
}
|
||||
return { id: openRouterDefaultModelId, info: openRouterDefaultModelInfo }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { promises as fs } from "node:fs"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, QwenCodeModelId, qwenCodeDefaultModelId, qwenCodeModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -177,7 +177,7 @@ export class QwenCodeHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
await this.ensureAuthenticated()
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import {
|
||||
InternationalQwenModelId,
|
||||
internationalQwenDefaultModelId,
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
} from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -81,7 +81,7 @@ export class QwenHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const isDeepseekReasoner = model.id.includes("deepseek-r1")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, requestyDefaultModelId, requestyDefaultModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import { toRequestyServiceStringUrl } from "@/shared/clients/requesty"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -59,7 +59,7 @@ export class RequestyHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, SambanovaModelId, sambanovaDefaultModelId, sambanovaModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -42,7 +42,7 @@ export class SambanovaHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
|
||||
@@ -4,11 +4,10 @@ import {
|
||||
ConversationRole as BedrockConversationRole,
|
||||
type Message as BedrockMessage,
|
||||
} from "@aws-sdk/client-bedrock-runtime"
|
||||
import { ChatMessage, OrchestrationClient, OrchestrationModuleConfig } from "@sap-ai-sdk/orchestration"
|
||||
import { ChatMessages, LlmModuleConfig, OrchestrationClient, TemplatingModuleConfig } from "@sap-ai-sdk/orchestration"
|
||||
import { ModelInfo, SapAiCoreModelId, sapAiCoreDefaultModelId, sapAiCoreModels } from "@shared/api"
|
||||
import axios from "axios"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { getAxiosSettings } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -116,7 +115,7 @@ namespace Bedrock {
|
||||
* Formats messages for models using the Converse API specification
|
||||
* Used by both Anthropic and Nova models to avoid code duplication
|
||||
*/
|
||||
export function formatMessagesForConverseAPI(messages: ClineStorageMessage[]): BedrockMessage[] {
|
||||
export function formatMessagesForConverseAPI(messages: Anthropic.Messages.MessageParam[]): BedrockMessage[] {
|
||||
return messages.map((message) => {
|
||||
// Determine role (user or assistant)
|
||||
const role = message.role === "user" ? BedrockConversationRole.USER : BedrockConversationRole.ASSISTANT
|
||||
@@ -316,7 +315,7 @@ namespace Gemini {
|
||||
*/
|
||||
export function prepareRequestPayload(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
model: { id: SapAiCoreModelId; info: ModelInfo },
|
||||
thinkingBudgetTokens?: number,
|
||||
): any {
|
||||
@@ -459,7 +458,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
if (this.options.sapAiCoreUseOrchestrationMode) {
|
||||
yield* this.createMessageWithOrchestration(systemPrompt, messages)
|
||||
} else {
|
||||
@@ -491,31 +490,29 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
this.isAiCoreEnvSetup = true
|
||||
}
|
||||
|
||||
private async *createMessageWithOrchestration(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
private async *createMessageWithOrchestration(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
try {
|
||||
// Ensure AI Core environment variable is set up (only runs once)
|
||||
this.ensureAiCoreEnvSetup()
|
||||
const model = this.getModel()
|
||||
|
||||
const orchestrationConfig: OrchestrationModuleConfig = {
|
||||
promptTemplating: {
|
||||
model: {
|
||||
name: model.id,
|
||||
},
|
||||
prompt: {
|
||||
template: [
|
||||
{
|
||||
role: "system",
|
||||
content: systemPrompt,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// Define the LLM to be used by the Orchestration pipeline
|
||||
const llm: LlmModuleConfig = {
|
||||
model_name: model.id,
|
||||
}
|
||||
|
||||
const orchestrationClient = new OrchestrationClient(orchestrationConfig, {
|
||||
resourceGroup: this.options.sapAiResourceGroup || "default",
|
||||
})
|
||||
const templating: TemplatingModuleConfig = {
|
||||
template: [
|
||||
{
|
||||
role: "system",
|
||||
content: systemPrompt,
|
||||
},
|
||||
],
|
||||
}
|
||||
const orchestrationClient = new OrchestrationClient(
|
||||
{ llm, templating },
|
||||
{ resourceGroup: this.options.sapAiResourceGroup || "default" },
|
||||
)
|
||||
|
||||
const sapMessages = this.convertMessageParamToSAPMessages(messages)
|
||||
|
||||
@@ -541,7 +538,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private async *createMessageWithDeployments(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
private async *createMessageWithDeployments(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const token = await this.getToken()
|
||||
const headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
@@ -1043,8 +1040,8 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
return { id: sapAiCoreDefaultModelId, info: sapAiCoreModels[sapAiCoreDefaultModelId] }
|
||||
}
|
||||
private convertMessageParamToSAPMessages(messages: ClineStorageMessage[]): ChatMessage[] {
|
||||
private convertMessageParamToSAPMessages(messages: Anthropic.Messages.MessageParam[]): ChatMessages {
|
||||
// Use the existing OpenAI converter since the logic is identical
|
||||
return convertToOpenAiMessages(messages) as ChatMessage[]
|
||||
return convertToOpenAiMessages(messages) as ChatMessages
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -42,7 +42,7 @@ export class TogetherHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.options.togetherModelId ?? ""
|
||||
const isDeepseekReasoner = modelId.includes("deepseek-reasoner")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -47,7 +47,7 @@ export class VercelAIGatewayHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.getModel().id
|
||||
const modelInfo = this.getModel().info
|
||||
@@ -82,7 +82,8 @@ export class VercelAIGatewayHandler implements ApiHandler {
|
||||
if ("reasoning" in delta && delta.reasoning) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning),
|
||||
// @ts-ignore-next-line
|
||||
reasoning: delta.reasoning,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,16 +102,22 @@ export class VercelAIGatewayHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
if (!didOutputUsage && chunk.usage) {
|
||||
const inputTokens = chunk.usage.prompt_tokens || 0
|
||||
const outputTokens =
|
||||
(chunk.usage.completion_tokens || 0) + (chunk.usage.completion_tokens_details?.reasoning_tokens || 0)
|
||||
|
||||
const cacheReadTokens = chunk.usage.prompt_tokens_details?.cached_tokens || 0
|
||||
// @ts-ignore - Vercel AI Gateway extends OpenAI types
|
||||
const totalCost = (chunk.usage.cost || 0) + (chunk.usage.cost_details?.upstream_inference_cost || 0)
|
||||
const cacheWriteTokens = chunk.usage.cache_creation_input_tokens || 0
|
||||
|
||||
yield {
|
||||
type: "usage",
|
||||
cacheWriteTokens: 0,
|
||||
cacheReadTokens: chunk.usage.prompt_tokens_details?.cached_tokens || 0,
|
||||
inputTokens: (chunk.usage.prompt_tokens || 0) - (chunk.usage.prompt_tokens_details?.cached_tokens || 0),
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
totalCost,
|
||||
inputTokens: inputTokens,
|
||||
outputTokens: outputTokens,
|
||||
cacheWriteTokens: cacheWriteTokens,
|
||||
cacheReadTokens: cacheReadTokens,
|
||||
// @ts-expect-error - Vercel AI Gateway extends OpenAI types
|
||||
totalCost: chunk.usage.cost || 0,
|
||||
}
|
||||
didOutputUsage = true
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
|
||||
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk"
|
||||
import { FunctionDeclaration as GoogleTool } from "@google/genai"
|
||||
import { ModelInfo, VertexModelId, vertexDefaultModelId, vertexModels } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -67,7 +67,7 @@ export class VertexHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ClineTool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: ClineTool[]): ApiStream {
|
||||
const model = this.getModel()
|
||||
const modelId = model.id
|
||||
|
||||
@@ -103,6 +103,13 @@ export class VertexHandler implements ApiHandler {
|
||||
case "claude-3-5-haiku@20241022":
|
||||
case "claude-3-opus@20240229":
|
||||
case "claude-3-haiku@20240307": {
|
||||
// Find indices of user messages for cache control
|
||||
const userMsgIndices = messages.reduce(
|
||||
(acc, msg, index) => (msg.role === "user" ? [...acc, index] : acc),
|
||||
[] as number[],
|
||||
)
|
||||
const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
|
||||
const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
|
||||
stream = await clientAnthropic.beta.messages.create(
|
||||
{
|
||||
model: modelId,
|
||||
@@ -116,7 +123,7 @@ export class VertexHandler implements ApiHandler {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
],
|
||||
messages: sanitizeAnthropicMessages(messages, true),
|
||||
messages: sanitizeAnthropicMessages(messages, lastUserMsgIndex, secondLastMsgUserIndex),
|
||||
stream: true,
|
||||
tools: tools?.length ? (tools as AnthropicTool[]) : undefined,
|
||||
// tool_choice options:
|
||||
@@ -142,7 +149,7 @@ export class VertexHandler implements ApiHandler {
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
messages: sanitizeAnthropicMessages(messages, false),
|
||||
messages: sanitizeAnthropicMessages(messages),
|
||||
stream: true,
|
||||
tools: tools?.length ? (tools as AnthropicTool[]) : undefined,
|
||||
// tool_choice options:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "@shared/vsCodeSelectorUtils"
|
||||
import { calculateApiCostAnthropic } from "@utils/cost"
|
||||
import * as vscode from "vscode"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { ApiHandler, CommonApiHandlerOptions, SingleCompletionHandler } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -366,7 +366,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
// Ensure clean state before starting a new request
|
||||
this.ensureCleanState()
|
||||
const client: vscode.LanguageModelChat = await this.getClient()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ModelInfo, XAIModelId, xaiDefaultModelId, xaiModels } from "@shared/api"
|
||||
import { shouldSkipReasoningForModel } from "@utils/model-utils"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ChatCompletionReasoningEffort } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -44,7 +44,7 @@ export class XAIHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const modelId = this.getModel().id
|
||||
// ensure reasoning effort is either "low" or "high" for grok-3-mini
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import {
|
||||
internationalZAiDefaultModelId,
|
||||
internationalZAiModelId,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
} from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { version as extensionVersion } from "../../../../package.json"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
@@ -76,7 +76,7 @@ export class ZAiHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], tools?: OpenAITool[]): ApiStream {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
|
||||
@@ -1,90 +1,76 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ClineStorageMessage, convertClineStorageToAnthropicMessage } from "@/shared/messages/content"
|
||||
import Anthropic from "@anthropic-ai/sdk"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
|
||||
/**
|
||||
* Converts Cline storage messages to Anthropic API format with optional cache control.
|
||||
* Adds ephemeral cache control to the last two user messages to prevent them from being
|
||||
* stored in Anthropic's cache.
|
||||
*
|
||||
* @param clineMessages - Array of Cline storage messages to convert
|
||||
* @param lastUserMsgIndex - Optional index of the last user message
|
||||
* @param secondLastMsgUserIndex - Optional index of the second-to-last user message
|
||||
* @returns Array of Anthropic-compatible messages with cache control applied
|
||||
* Sanitize Anthropic messages by removing reasoning details and adding ephemeral cache control
|
||||
* to the last two user messages to prevent them from being stored in Anthropic's cache.
|
||||
*/
|
||||
export function sanitizeAnthropicMessages(
|
||||
clineMessages: Array<ClineStorageMessage | Anthropic.MessageParam>,
|
||||
supportCache: boolean,
|
||||
): Array<Anthropic.MessageParam> {
|
||||
// The latest message will be the new user message, one before will be the assistant message from a previous request,
|
||||
// and the user message before that will be a previously cached user message. So we need to mark the latest user message
|
||||
// as ephemeral to cache it for the next request, and mark the second to last user message as ephemeral to let the server
|
||||
// know the last message to retrieve from the cache for the current request.
|
||||
const userMsgIndices = clineMessages.reduce((acc, msg, index) => {
|
||||
if (msg.role === "user") {
|
||||
acc.push(index)
|
||||
}
|
||||
return acc
|
||||
}, [] as number[])
|
||||
// Set to -1 if there are no user messages so the indices are invalid
|
||||
const indicesLength = userMsgIndices.length ?? -1
|
||||
const lastUserMsgIndex = userMsgIndices[indicesLength - 1]
|
||||
const secondLastMsgUserIndex = userMsgIndices[indicesLength - 2]
|
||||
|
||||
return clineMessages.map((msg, index) => {
|
||||
const anthropicMsg = convertClineStorageToAnthropicMessage(msg)
|
||||
|
||||
// Add cache control to the last two user messages
|
||||
if (supportCache && (index === lastUserMsgIndex || index === secondLastMsgUserIndex)) {
|
||||
return addCacheControl(anthropicMsg)
|
||||
messages: Array<ClineStorageMessage>,
|
||||
lastUserMsgIndex?: number,
|
||||
secondLastMsgUserIndex?: number,
|
||||
): Array<Anthropic.Messages.MessageParam> {
|
||||
return messages.map((_message, index) => {
|
||||
const message = removeUnknownParams(_message)
|
||||
const addCacheControl = lastUserMsgIndex !== undefined && secondLastMsgUserIndex !== undefined
|
||||
// Construct message
|
||||
if (addCacheControl && (index === lastUserMsgIndex || index === secondLastMsgUserIndex)) {
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
},
|
||||
]
|
||||
: message.content.map((content, contentIndex) =>
|
||||
contentIndex === message.content.length - 1
|
||||
? {
|
||||
...content,
|
||||
cache_control: {
|
||||
type: "ephemeral",
|
||||
},
|
||||
}
|
||||
: content,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
return anthropicMsg
|
||||
return {
|
||||
...message,
|
||||
content:
|
||||
typeof message.content === "string"
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
},
|
||||
]
|
||||
: message.content,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const isThinkingBlock = (
|
||||
block: Anthropic.ContentBlockParam,
|
||||
): block is Anthropic.Messages.ThinkingBlockParam | Anthropic.Messages.RedactedThinkingBlockParam => {
|
||||
return block.type === "thinking" || block.type === "redacted_thinking"
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds ephemeral cache control to the last content block of a message.
|
||||
* Returns a new message object without mutating the original.
|
||||
*
|
||||
* @param message - The Anthropic message to add cache control to
|
||||
* @returns A new message with cache control added to the last content block
|
||||
* Remove reasoning details and other known params that are not Anthropic specific.
|
||||
*/
|
||||
function addCacheControl(message: Anthropic.MessageParam): Anthropic.MessageParam {
|
||||
// Convert string content to array format
|
||||
if (typeof message.content === "string") {
|
||||
return {
|
||||
...message,
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: message.content,
|
||||
cache_control: { type: "ephemeral" },
|
||||
} satisfies Anthropic.TextBlockParam,
|
||||
],
|
||||
}
|
||||
function removeUnknownParams(param: ClineStorageMessage): Anthropic.Messages.MessageParam {
|
||||
// Construct new content array with known Anthropic content blocks only.
|
||||
return {
|
||||
role: param.role === "user" ? "user" : "assistant",
|
||||
content: Array.isArray(param.content)
|
||||
? param.content.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
// Ensure reasoning_details is removed
|
||||
reasoning_details: undefined,
|
||||
}
|
||||
})
|
||||
: param.content, // String content remains unchanged
|
||||
}
|
||||
|
||||
// Handle array content - add cache control to the last block
|
||||
const content = [...message.content]
|
||||
const lastIndex = content.length - 1
|
||||
|
||||
if (lastIndex >= 0) {
|
||||
const lastBlock = content[lastIndex]
|
||||
|
||||
// Only add cache_control to block types that support it (not ThinkingBlockParam)
|
||||
if (!isThinkingBlock(lastBlock)) {
|
||||
content[lastIndex] = {
|
||||
...lastBlock,
|
||||
cache_control: { type: "ephemeral" },
|
||||
} satisfies Anthropic.ContentBlockParam
|
||||
}
|
||||
}
|
||||
|
||||
return { ...message, content }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Content, GenerateContentResponse, Part } from "@google/genai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
|
||||
export function convertAnthropicContentToGemini(content: string | ClineStorageMessage["content"]): Part[] {
|
||||
export function convertAnthropicContentToGemini(content: string | Anthropic.ContentBlockParam[]): Part[] {
|
||||
if (typeof content === "string") {
|
||||
return [{ text: content }]
|
||||
}
|
||||
@@ -10,7 +9,7 @@ export function convertAnthropicContentToGemini(content: string | ClineStorageMe
|
||||
.flatMap((block): Part | undefined => {
|
||||
switch (block.type) {
|
||||
case "text":
|
||||
return { text: block.text, thoughtSignature: block.signature }
|
||||
return { text: block.text }
|
||||
case "image":
|
||||
if (block.source.type !== "base64") {
|
||||
throw new Error("Unsupported image source type")
|
||||
@@ -27,7 +26,6 @@ export function convertAnthropicContentToGemini(content: string | ClineStorageMe
|
||||
name: block.name,
|
||||
args: block.input as Record<string, unknown>,
|
||||
},
|
||||
thoughtSignature: block.signature,
|
||||
}
|
||||
case "tool_result":
|
||||
return {
|
||||
|
||||
@@ -10,15 +10,6 @@ import {
|
||||
ClineUserToolResultContentBlock,
|
||||
} from "@/shared/messages/content"
|
||||
|
||||
/**
|
||||
* Converts an array of ClineStorageMessage objects to OpenAI's Completions API format.
|
||||
*
|
||||
* Handles conversion of Cline-specific content types (tool uses, tool results, images, reasoning details)
|
||||
* into OpenAI's expected message structure, including tool_calls and tool_call_id fields.
|
||||
*
|
||||
* @param anthropicMessages - Array of ClineStorageMessage objects to be converted
|
||||
* @returns Array of OpenAI.Chat.ChatCompletionMessageParam objects
|
||||
*/
|
||||
export function convertToOpenAiMessages(
|
||||
anthropicMessages: Omit<ClineStorageMessage, "modelInfo">[],
|
||||
): OpenAI.Chat.ChatCompletionMessageParam[] {
|
||||
@@ -159,7 +150,6 @@ export function convertToOpenAiMessages(
|
||||
// delete part.reasoning_details
|
||||
}
|
||||
if (part.type === "thinking" && part.thinking) {
|
||||
// Reasoning details should have been moved to the text block
|
||||
thinkingBlock.push(part)
|
||||
}
|
||||
})
|
||||
@@ -174,26 +164,15 @@ export function convertToOpenAiMessages(
|
||||
}
|
||||
|
||||
// Process tool use messages
|
||||
const tool_calls: OpenAI.Chat.ChatCompletionMessageToolCall[] = toolMessages.map((toolMessage) => {
|
||||
const toolDetails = toolMessage.reasoning_details
|
||||
if (toolDetails?.length) {
|
||||
if (Array.isArray(toolDetails)) {
|
||||
reasoningDetails.push(...toolDetails)
|
||||
} else {
|
||||
reasoningDetails.push(toolDetails)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: toolMessage.id,
|
||||
type: "function",
|
||||
function: {
|
||||
name: toolMessage.name,
|
||||
// json string
|
||||
arguments: JSON.stringify(toolMessage.input),
|
||||
},
|
||||
}
|
||||
})
|
||||
const tool_calls: OpenAI.Chat.ChatCompletionMessageToolCall[] = toolMessages.map((toolMessage) => ({
|
||||
id: toolMessage.id,
|
||||
type: "function",
|
||||
function: {
|
||||
name: toolMessage.name,
|
||||
// json string
|
||||
arguments: JSON.stringify(toolMessage.input),
|
||||
},
|
||||
}))
|
||||
|
||||
// Set content to blank when tool_calls are present but content has no text, per OpenAI API spec
|
||||
const hasToolCalls = tool_calls.length > 0
|
||||
@@ -360,30 +339,29 @@ export function convertToAnthropicMessage(completion: OpenAI.Chat.Completions.Ch
|
||||
}
|
||||
try {
|
||||
if (openAiMessage?.tool_calls?.length) {
|
||||
const functionCalls = openAiMessage.tool_calls.filter((tc: any) => tc?.type === "function" && tc.function)
|
||||
if (functionCalls.length > 0) {
|
||||
anthropicMessage.content.push(
|
||||
...functionCalls.map((toolCall: any): Anthropic.ToolUseBlock => {
|
||||
let parsedInput = {}
|
||||
anthropicMessage.content.push(
|
||||
...openAiMessage.tool_calls
|
||||
.map((toolCall): Anthropic.ToolUseBlock => {
|
||||
const parsedName = toolCall.type === "function" && toolCall.function.name
|
||||
let parsedInput = toolCall.function.arguments
|
||||
try {
|
||||
parsedInput = JSON.parse(toolCall.function?.arguments || "{}")
|
||||
parsedInput = JSON.parse(toolCall.function.arguments || "{}")
|
||||
} catch (error) {
|
||||
console.error("Failed to parse tool arguments:", error)
|
||||
}
|
||||
return {
|
||||
type: "tool_use",
|
||||
id: toolCall.id,
|
||||
name: toolCall.function?.name || UNIQUE_ERROR_TOOL_NAME,
|
||||
name: parsedName || UNIQUE_ERROR_TOOL_NAME,
|
||||
input: parsedInput,
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return anthropicMessage
|
||||
})
|
||||
// Filter out any tool uses with the UNIQUE_ERROR_TOOL_NAME, which indicates a parsing error
|
||||
.filter((toolUse) => toolUse.name !== UNIQUE_ERROR_TOOL_NAME),
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error converting OpenAI message to Anthropic format:", error)
|
||||
console.error("Failed to process tool calls:", error)
|
||||
}
|
||||
|
||||
return anthropicMessage
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
import { ResponseInput, ResponseInputMessageContentList, ResponseReasoningItem } from "openai/resources/responses/responses"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
|
||||
/**
|
||||
* Converts an array of ClineStorageMessage objects (extension of Anthropic format) to a ResponseInput array to use with OpenAI's Responses API.
|
||||
*
|
||||
* ## Key Differences from Chat Completions API
|
||||
*
|
||||
* The Responses API has stricter requirements than the Chat Completions API:
|
||||
*
|
||||
* ### Chat Completions API:
|
||||
* - Messages are simple role/content pairs
|
||||
* - System prompts are separate messages with role="system"
|
||||
* - No explicit reasoning item structure
|
||||
* - More forgiving about message ordering
|
||||
*
|
||||
* ### Responses API:
|
||||
* - Uses an "input" array of heterogeneous items (messages, reasoning, function_calls, etc.)
|
||||
* - System prompts go in an "instructions" field, not as messages
|
||||
* - Reasoning items MUST be immediately followed by a message or function_call
|
||||
* - Strict ordering requirements match training data distribution
|
||||
*
|
||||
* ## The Reasoning Item Constraint
|
||||
*
|
||||
* **THE CRITICAL ERROR:** "Item 'rs_...' of type 'reasoning' was provided without its required following item"
|
||||
*
|
||||
* This error occurs when reasoning items are orphaned or separated from their corresponding output.
|
||||
*
|
||||
* ### What Causes This Error:
|
||||
* ```
|
||||
* ❌ WRONG - Reasoning orphaned between turns:
|
||||
* [
|
||||
* { role: "user", content: [...] },
|
||||
* { type: "reasoning", id: "rs_abc", summary: [...] }, // ← ORPHANED!
|
||||
* { type: "message", role: "assistant", content: [...] },
|
||||
* { role: "user", content: [...] }
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* ### The Fix - Keep Complete Assistant Turns Together:
|
||||
* ```
|
||||
* ✅ CORRECT - Reasoning paired with its message:
|
||||
* [
|
||||
* { role: "user", content: [...] },
|
||||
* { type: "reasoning", id: "rs_abc", summary: [...] },
|
||||
* { type: "message", role: "assistant", content: [...] }, // ← Immediately follows reasoning
|
||||
* { role: "user", content: [...] }
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* **Per OpenAI Engineering Guidance:**
|
||||
* - ❌ WRONG: `content += filter(lambda x: x.type == "reasoning", resp.output)`
|
||||
* - ✅ CORRECT: `content += resp.output`
|
||||
*
|
||||
* Never extract only reasoning items - always include the complete output sequence
|
||||
* (reasoning + message/function_call) as provided by the API.
|
||||
*
|
||||
* ## Implementation Strategy
|
||||
*
|
||||
* 1. **Separate processing for assistant vs user messages** - Assistant turns need special
|
||||
* handling to maintain reasoning-message pairing
|
||||
* 2. **Collect all assistant items together** - Gather reasoning, messages, and function_calls
|
||||
* for the entire assistant turn before validating
|
||||
* 3. **Validate pairing within each turn** - Ensure each reasoning item is immediately followed
|
||||
* by a message or function_call, inserting placeholders if needed
|
||||
* 4. **Flush complete turns atomically** - Add all items from an assistant turn together to
|
||||
* maintain proper sequencing
|
||||
*
|
||||
* @link https://community.openai.com/t/openai-api-error-function-call-was-provided-without-its-required-reasoning-item-the-real-issue/1355347
|
||||
*
|
||||
* @param messages - Array of ClineStorageMessage objects to be converted
|
||||
* @returns ResponseInput array containing the transformed messages with proper reasoning pairing
|
||||
*/
|
||||
export function convertToOpenAIResponsesInput(messages: ClineStorageMessage[]): ResponseInput {
|
||||
const allItems: any[] = []
|
||||
const toolUseIdToCallId = new Map<string, string>()
|
||||
|
||||
for (const m of messages) {
|
||||
if (typeof m.content === "string") {
|
||||
allItems.push({ role: m.role, content: [{ type: "input_text", text: m.content }] })
|
||||
continue
|
||||
}
|
||||
|
||||
if (m.role === "assistant") {
|
||||
// For assistant messages, we must ensure reasoning items are IMMEDIATELY followed
|
||||
// by their corresponding message or function_call. Process the entire assistant
|
||||
// turn and ensure proper pairing.
|
||||
const assistantItems: any[] = []
|
||||
|
||||
for (const part of m.content) {
|
||||
switch (part.type) {
|
||||
case "thinking":
|
||||
// Include reasoning item if it has a call_id, even if thinking is empty
|
||||
// This is required because the API expects reasoning items to be paired with
|
||||
// their corresponding function_calls, and will error if a function_call
|
||||
// references a reasoning item that wasn't sent
|
||||
if (part.call_id && part.call_id.length > 0) {
|
||||
assistantItems.push({
|
||||
id: part.call_id,
|
||||
type: "reasoning",
|
||||
summary: part.thinking
|
||||
? [
|
||||
{
|
||||
type: "summary_text",
|
||||
text: part.thinking,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
} as ResponseReasoningItem)
|
||||
}
|
||||
break
|
||||
case "redacted_thinking":
|
||||
// Include reasoning item with encrypted content if it has a call_id
|
||||
// Even if data is missing, we need to maintain the reasoning-function_call pairing
|
||||
if (part.call_id && part.call_id.length > 0) {
|
||||
const reasoningItem: any = {
|
||||
id: part.call_id,
|
||||
type: "reasoning",
|
||||
summary: [],
|
||||
}
|
||||
// Only include encrypted_content if data exists
|
||||
if (part.data) {
|
||||
reasoningItem.encrypted_content = part.data
|
||||
}
|
||||
assistantItems.push(reasoningItem as ResponseReasoningItem)
|
||||
}
|
||||
break
|
||||
case "text":
|
||||
assistantItems.push({
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: part.text }],
|
||||
})
|
||||
break
|
||||
case "image":
|
||||
assistantItems.push({
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: `[image:${part.source.media_type}]` }],
|
||||
})
|
||||
break
|
||||
case "tool_use": {
|
||||
const call_id = part.call_id || part.id
|
||||
if (part.call_id) {
|
||||
toolUseIdToCallId.set(part.id, part.call_id)
|
||||
}
|
||||
assistantItems.push({
|
||||
type: "function_call",
|
||||
call_id,
|
||||
id: part.id,
|
||||
name: part.name,
|
||||
arguments: JSON.stringify(part.input ?? {}),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure every reasoning item is followed by a message or function_call
|
||||
for (let i = 0; i < assistantItems.length; i++) {
|
||||
const item = assistantItems[i]
|
||||
if (item.type === "reasoning") {
|
||||
const nextItem = assistantItems[i + 1]
|
||||
if (!nextItem || (nextItem.type !== "message" && nextItem.type !== "function_call")) {
|
||||
// Insert a placeholder message immediately after this reasoning item
|
||||
assistantItems.splice(i + 1, 0, {
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "" }],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allItems.push(...assistantItems)
|
||||
} else {
|
||||
// User messages - collect all content
|
||||
const messageContent: ResponseInputMessageContentList = []
|
||||
|
||||
for (const part of m.content) {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
messageContent.push({ type: "input_text", text: part.text })
|
||||
break
|
||||
case "image":
|
||||
messageContent.push({
|
||||
type: "input_image",
|
||||
detail: "auto",
|
||||
image_url: `data:${part.source.media_type};base64,${part.source.data}`,
|
||||
})
|
||||
break
|
||||
case "tool_result": {
|
||||
// Flush any pending message content before adding tool result
|
||||
if (messageContent.length > 0) {
|
||||
allItems.push({ role: m.role, content: [...messageContent] })
|
||||
messageContent.length = 0
|
||||
}
|
||||
const call_id = part.call_id || toolUseIdToCallId.get(part.tool_use_id) || part.tool_use_id
|
||||
allItems.push({
|
||||
type: "function_call_output",
|
||||
call_id,
|
||||
output: typeof part.content === "string" ? part.content : JSON.stringify(part.content),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flush any remaining user message content
|
||||
if (messageContent.length > 0) {
|
||||
allItems.push({ role: m.role, content: [...messageContent] })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allItems
|
||||
}
|
||||
@@ -21,7 +21,6 @@ export async function createOpenRouterStream(
|
||||
thinkingBudgetTokens?: number,
|
||||
openRouterProviderSorting?: string,
|
||||
tools?: Array<ChatCompletionTool>,
|
||||
geminiThinkingLevel?: string,
|
||||
) {
|
||||
// Convert Anthropic messages to OpenAI format
|
||||
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
@@ -139,10 +138,6 @@ export async function createOpenRouterStream(
|
||||
topP = 0.95
|
||||
openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
}
|
||||
if (model.id.startsWith("google/gemini-3.0") || model.id === "google/gemini-3.0") {
|
||||
// Recommended value from google
|
||||
temperature = 1.0
|
||||
}
|
||||
|
||||
let reasoning: { max_tokens: number } | undefined
|
||||
switch (model.id) {
|
||||
@@ -166,12 +161,7 @@ export async function createOpenRouterStream(
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (
|
||||
thinkingBudgetTokens &&
|
||||
model.info?.thinkingConfig &&
|
||||
thinkingBudgetTokens > 0 &&
|
||||
!(model.id.includes("gemini") && geminiThinkingLevel)
|
||||
) {
|
||||
if (thinkingBudgetTokens && model.info?.thinkingConfig && thinkingBudgetTokens > 0) {
|
||||
temperature = undefined // extended thinking does not support non-1 temperature
|
||||
reasoning = { max_tokens: thinkingBudgetTokens }
|
||||
break
|
||||
@@ -199,9 +189,6 @@ export async function createOpenRouterStream(
|
||||
...(providerPreferences ? { provider: providerPreferences } : {}),
|
||||
...(isClaudeSonnet1m ? { provider: { order: ["anthropic", "google-vertex/global"], allow_fallbacks: false } } : {}),
|
||||
...getOpenAIToolParams(tools),
|
||||
...(model.id.includes("gemini") && geminiThinkingLevel
|
||||
? { thinking_config: { thinking_level: geminiThinkingLevel, include_thoughts: true } }
|
||||
: {}),
|
||||
})
|
||||
|
||||
return stream
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
export type ApiStream = AsyncGenerator<ApiStreamChunk> & { id?: string }
|
||||
export type ApiStream = AsyncGenerator<ApiStreamChunk>
|
||||
export type ApiStreamChunk = ApiStreamTextChunk | ApiStreamThinkingChunk | ApiStreamUsageChunk | ApiStreamToolCallsChunk
|
||||
|
||||
export interface ApiStreamTextChunk {
|
||||
type: "text"
|
||||
/**
|
||||
* Text content generated by the model
|
||||
*/
|
||||
text: string
|
||||
/**
|
||||
* The response ID associated with this chunk
|
||||
*/
|
||||
id?: string
|
||||
/**
|
||||
* The thought signature associated with this chunk used by Gemini
|
||||
*/
|
||||
signature?: string
|
||||
}
|
||||
|
||||
export interface ApiStreamUsageChunk {
|
||||
@@ -25,74 +14,27 @@ export interface ApiStreamUsageChunk {
|
||||
cacheReadTokens?: number
|
||||
thoughtsTokenCount?: number // openrouter
|
||||
totalCost?: number // openrouter
|
||||
/**
|
||||
* The response ID associated with this response
|
||||
*/
|
||||
id?: string
|
||||
}
|
||||
|
||||
export interface ApiStreamToolCallsChunk {
|
||||
type: "tool_calls"
|
||||
/**
|
||||
* The tool call information
|
||||
*/
|
||||
tool_call: ApiStreamToolCall
|
||||
/**
|
||||
* The response ID associated with this chunk
|
||||
*/
|
||||
id?: string
|
||||
/**
|
||||
* The thought signature associated with this chunk used by Gemini
|
||||
*/
|
||||
signature?: string
|
||||
}
|
||||
|
||||
export interface ApiStreamToolCall {
|
||||
/**
|
||||
* The call ID associated with this tool call
|
||||
*/
|
||||
call_id?: string
|
||||
call_id?: string // The call / request ID associated with this tool call
|
||||
// Information about the tool being called
|
||||
function: {
|
||||
/**
|
||||
* The tool call ID
|
||||
*/
|
||||
id?: string
|
||||
/**
|
||||
* Name of the tool
|
||||
*/
|
||||
id?: string // The tool call ID
|
||||
name?: string
|
||||
/**
|
||||
* The arguments passed to the tool execution
|
||||
*/
|
||||
arguments?: any
|
||||
}
|
||||
}
|
||||
|
||||
export interface ApiStreamThinkingChunk {
|
||||
type: "reasoning"
|
||||
/**
|
||||
* The reasoning text generated by the model.
|
||||
* Redacted reasoning block will have this field set to "[REDACTED]" or an empty string.
|
||||
*/
|
||||
reasoning: string
|
||||
/**
|
||||
* openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
* This is also where we store the summary details for OpenAI.
|
||||
*/
|
||||
details?: unknown
|
||||
/**
|
||||
* It's used when sending the thinking block back to the API.
|
||||
* API expects this in completed form, not as array of deltas.
|
||||
* Also used by Gemini for thought signature associated with this chunk
|
||||
*/
|
||||
details?: unknown // openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
signature?: string
|
||||
/**
|
||||
* redacted data
|
||||
*/
|
||||
redacted_data?: string
|
||||
/**
|
||||
* The response ID associated with this chunk
|
||||
*/
|
||||
id?: string
|
||||
}
|
||||
|
||||
+9
-146
@@ -2,12 +2,7 @@ import type { ToolUse } from "@core/assistant-message"
|
||||
import { JSONParser } from "@streamparser/json"
|
||||
import { McpHub } from "@/services/mcp/McpHub"
|
||||
import { CLINE_MCP_TOOL_IDENTIFIER } from "@/shared/mcp"
|
||||
import {
|
||||
ClineAssistantRedactedThinkingBlock,
|
||||
ClineAssistantThinkingBlock,
|
||||
ClineAssistantToolUseBlock,
|
||||
ClineReasoningDetailParam,
|
||||
} from "@/shared/messages/content"
|
||||
import { ClineAssistantToolUseBlock } from "@/shared/messages/content"
|
||||
import { ClineDefaultTool } from "@/shared/tools"
|
||||
|
||||
export interface PendingToolUse {
|
||||
@@ -15,7 +10,6 @@ export interface PendingToolUse {
|
||||
name: string
|
||||
input: string
|
||||
parsedInput?: unknown
|
||||
signature?: string
|
||||
jsonParser?: JSONParser
|
||||
call_id?: string
|
||||
}
|
||||
@@ -25,23 +19,6 @@ interface ToolUseDeltaBlock {
|
||||
type?: string
|
||||
name?: string
|
||||
input?: string
|
||||
signature?: string
|
||||
}
|
||||
|
||||
export interface ReasoningDelta {
|
||||
id?: string
|
||||
reasoning?: string
|
||||
signature?: string
|
||||
details?: any[]
|
||||
redacted_data?: any
|
||||
}
|
||||
|
||||
export interface PendingReasoning {
|
||||
id?: string
|
||||
content: string
|
||||
signature: string
|
||||
redactedThinking: ClineAssistantRedactedThinkingBlock[]
|
||||
summary: unknown[] | ClineReasoningDetailParam[]
|
||||
}
|
||||
|
||||
const ESCAPE_MAP: Record<string, string> = {
|
||||
@@ -54,40 +31,10 @@ const ESCAPE_MAP: Record<string, string> = {
|
||||
|
||||
const ESCAPE_PATTERN = /\\[ntr"\\]/g
|
||||
|
||||
export class StreamResponseHandler {
|
||||
private toolUseHandler = new ToolUseHandler()
|
||||
private reasoningHandler = new ReasoningHandler()
|
||||
|
||||
private _requestId: string | undefined
|
||||
|
||||
public setRequestId(id?: string) {
|
||||
if (!this._requestId && id) {
|
||||
this._requestId = id
|
||||
}
|
||||
}
|
||||
|
||||
public get requestId() {
|
||||
return this._requestId
|
||||
}
|
||||
|
||||
public getHandlers() {
|
||||
return {
|
||||
toolUseHandler: this.toolUseHandler,
|
||||
reasonsHandler: this.reasoningHandler,
|
||||
}
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this._requestId = undefined
|
||||
this.toolUseHandler = new ToolUseHandler()
|
||||
this.reasoningHandler = new ReasoningHandler()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles streaming native tool use blocks and converts them to ClineAssistantToolUseBlock format
|
||||
*/
|
||||
class ToolUseHandler {
|
||||
export class ToolUseHandler {
|
||||
private pendingToolUses = new Map<string, PendingToolUse>()
|
||||
|
||||
processToolUseDelta(delta: ToolUseDeltaBlock, call_id?: string): void {
|
||||
@@ -103,17 +50,12 @@ class ToolUseHandler {
|
||||
if (delta.name) {
|
||||
pending.name = delta.name
|
||||
}
|
||||
|
||||
if (delta.signature) {
|
||||
pending.signature = delta.signature
|
||||
}
|
||||
|
||||
if (delta.input) {
|
||||
pending.input += delta.input
|
||||
try {
|
||||
pending.jsonParser?.write(delta.input)
|
||||
} catch {
|
||||
// Expected during streaming - JSONParser may not have complete JSON yet
|
||||
// Expected during streaming
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,17 +82,15 @@ class ToolUseHandler {
|
||||
id: pending.id,
|
||||
name: pending.name,
|
||||
input,
|
||||
signature: pending.signature,
|
||||
call_id: pending.call_id,
|
||||
}
|
||||
}
|
||||
|
||||
getAllFinalizedToolUses(summary?: ClineAssistantToolUseBlock["reasoning_details"]): ClineAssistantToolUseBlock[] {
|
||||
getAllFinalizedToolUses(): ClineAssistantToolUseBlock[] {
|
||||
const results: ClineAssistantToolUseBlock[] = []
|
||||
for (const id of this.pendingToolUses.keys()) {
|
||||
const toolUse = this.getFinalizedToolUse(id)
|
||||
if (toolUse) {
|
||||
results.push({ ...toolUse, reasoning_details: summary })
|
||||
results.push(toolUse)
|
||||
}
|
||||
}
|
||||
return results
|
||||
@@ -162,24 +102,19 @@ class ToolUseHandler {
|
||||
|
||||
getPartialToolUsesAsContent(): ToolUse[] {
|
||||
const results: ToolUse[] = []
|
||||
const pendingToolUses = this.pendingToolUses.values()
|
||||
|
||||
for (const pending of pendingToolUses) {
|
||||
for (const pending of this.pendingToolUses.values()) {
|
||||
if (!pending.name) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to get the most up-to-date parsed input
|
||||
// Priority: parsedInput (from JSONParser) > fallback to manual parsing
|
||||
let input: any = {}
|
||||
if (pending.parsedInput != null) {
|
||||
input = pending.parsedInput
|
||||
} else if (pending.input) {
|
||||
// Try full JSON parse first
|
||||
try {
|
||||
input = JSON.parse(pending.input)
|
||||
} catch {
|
||||
// Fall back to extracting partial fields from incomplete JSON
|
||||
input = this.extractPartialJsonFields(pending.input)
|
||||
}
|
||||
}
|
||||
@@ -196,12 +131,10 @@ class ToolUseHandler {
|
||||
},
|
||||
partial: true,
|
||||
isNativeToolCall: true,
|
||||
signature: pending.signature,
|
||||
call_id: pending.call_id,
|
||||
})
|
||||
} else {
|
||||
const params: Record<string, string> = {}
|
||||
if (typeof input === "object" && input !== null) {
|
||||
if (typeof input === "object") {
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
params[key] = typeof value === "string" ? value : JSON.stringify(value)
|
||||
}
|
||||
@@ -211,14 +144,12 @@ class ToolUseHandler {
|
||||
name: pending.name as ClineDefaultTool,
|
||||
params: params as any,
|
||||
partial: true,
|
||||
signature: pending.signature,
|
||||
isNativeToolCall: true,
|
||||
call_id: pending.call_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Ensure all returned tool uses are marked as partial
|
||||
return results.map((t) => ({ ...t, partial: true }))
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
@@ -234,7 +165,6 @@ class ToolUseHandler {
|
||||
parsedInput: undefined,
|
||||
jsonParser,
|
||||
call_id,
|
||||
signature: undefined,
|
||||
}
|
||||
|
||||
jsonParser.onValue = (info: any) => {
|
||||
@@ -260,70 +190,3 @@ class ToolUseHandler {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles streaming reasoning content and converts it to the appropriate message format
|
||||
*/
|
||||
class ReasoningHandler {
|
||||
private pendingReasoning: PendingReasoning | null = null
|
||||
|
||||
processReasoningDelta(delta: ReasoningDelta): void {
|
||||
// Initialize pending reasoning if we have an ID but no pending reasoning yet
|
||||
if (!this.pendingReasoning) {
|
||||
this.pendingReasoning = {
|
||||
id: delta.id,
|
||||
content: "",
|
||||
signature: "",
|
||||
redactedThinking: [],
|
||||
summary: [],
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.pendingReasoning) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update fields from delta
|
||||
if (delta.reasoning) {
|
||||
this.pendingReasoning.content += delta.reasoning
|
||||
}
|
||||
if (delta.signature) {
|
||||
this.pendingReasoning.signature = delta.signature
|
||||
}
|
||||
if (delta.details) {
|
||||
if (Array.isArray(delta.details)) {
|
||||
this.pendingReasoning.summary.push(...delta.details)
|
||||
} else {
|
||||
this.pendingReasoning.summary.push(delta.details)
|
||||
}
|
||||
}
|
||||
if (delta.redacted_data) {
|
||||
this.pendingReasoning.redactedThinking.push({
|
||||
type: "redacted_thinking",
|
||||
data: delta.redacted_data,
|
||||
call_id: delta.id || this.pendingReasoning.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentReasoning(): ClineAssistantThinkingBlock | null {
|
||||
if (!this.pendingReasoning) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
type: "thinking",
|
||||
thinking: this.pendingReasoning.content,
|
||||
signature: this.pendingReasoning.signature,
|
||||
summary: this.pendingReasoning.summary,
|
||||
}
|
||||
}
|
||||
|
||||
getRedactedThinking(): ClineAssistantRedactedThinkingBlock[] {
|
||||
return this.pendingReasoning?.redactedThinking || []
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.pendingReasoning = null
|
||||
}
|
||||
}
|
||||
@@ -52,46 +52,16 @@ export interface ToolUse {
|
||||
// params is a partial record, allowing only some or none of the possible parameters to be used
|
||||
params: Partial<Record<ToolParamName, string>>
|
||||
partial: boolean
|
||||
/**
|
||||
* Whether this tool use was initiated by a native tool call
|
||||
*/
|
||||
// Whether this tool use was initiated by a native tool call
|
||||
isNativeToolCall?: boolean
|
||||
/**
|
||||
* The call / response ID this tool use is associated with.
|
||||
*/
|
||||
call_id?: string // optional call ID for tracking tool use calls
|
||||
/**
|
||||
* Thought signature associated with this tool use, used by Gemini
|
||||
*/
|
||||
signature?: string
|
||||
}
|
||||
|
||||
export interface ReasoningStreamContent {
|
||||
type: "reasoning"
|
||||
/**
|
||||
* The reasoning text generated by the model.
|
||||
* Redacted reasoning block will have this field set to "[REDACTED]" or an empty string.
|
||||
*/
|
||||
reasoning: string
|
||||
/**
|
||||
* openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
*/
|
||||
details?: any
|
||||
/**
|
||||
* It's used when sending the thinking block back to the API.
|
||||
* API expects this in completed form, not as array of deltas.
|
||||
*/
|
||||
details?: any // openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
|
||||
signature?: string
|
||||
/**
|
||||
* whether this reasoning block has been redacted
|
||||
*/
|
||||
redacted?: boolean
|
||||
/**
|
||||
* redacted data
|
||||
*/
|
||||
data?: string
|
||||
/**
|
||||
* Indicates whether this is a partial reasoning block
|
||||
*/
|
||||
redacted?: boolean // whether this reasoning block has been redacted
|
||||
data?: string // redacted data
|
||||
partial: boolean
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ export function checkContextWindowExceededError(error: unknown): boolean {
|
||||
checkIsOpenAIContextWindowError(error) ||
|
||||
checkIsOpenRouterContextWindowError(error) ||
|
||||
checkIsAnthropicContextWindowError(error) ||
|
||||
checkIsCerebrasContextWindowError(error) ||
|
||||
checkIsBedrockContextWindowError(error)
|
||||
checkIsCerebrasContextWindowError(error)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,45 +70,3 @@ function checkIsCerebrasContextWindowError(response: any): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function checkIsBedrockContextWindowError(error: any): boolean {
|
||||
try {
|
||||
// Bedrock returns ValidationException for context window errors
|
||||
const errorType = error?.name ?? error?.error?.type ?? error?.__type
|
||||
const errorCode = error?.code ?? error?.error?.code ?? error?.$metadata?.httpStatusCode
|
||||
|
||||
// Handle nested error structures (e.g., through Vercel AI SDK)
|
||||
const nestedError = error?.error?.param
|
||||
const nestedErrorCode = nestedError?.statusCode ?? error?.details?.code
|
||||
const nestedMessage = nestedError?.message ?? nestedError?.error
|
||||
|
||||
const message: string = String(error?.message || error?.error?.message || nestedMessage || "")
|
||||
|
||||
// Check for ValidationException with HTTP 400
|
||||
const isValidationException =
|
||||
errorType === "ValidationException" ||
|
||||
errorType === "AI_APICallError" ||
|
||||
String(errorCode) === "400" ||
|
||||
String(nestedErrorCode) === "400" ||
|
||||
error?.code === "stream_initialization_failed"
|
||||
|
||||
if (!isValidationException) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Known Bedrock context window error patterns
|
||||
const BEDROCK_CONTEXT_PATTERNS = [
|
||||
/maximum tokens.*exceeds.*model limit/i,
|
||||
/input length and max_tokens exceed context limit/i,
|
||||
/context length.*exceeds/i,
|
||||
/total number of tokens.*exceeds.*limit/i,
|
||||
/requested.*tokens.*exceeds.*limit/i,
|
||||
/reduce.*length.*messages.*completion/i,
|
||||
/input is too long/i,
|
||||
] as const
|
||||
|
||||
return BEDROCK_CONTEXT_PATTERNS.some((pattern) => pattern.test(message))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { CreateHookRequest, CreateHookResponse } from "@shared/proto/cline/file"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { HookDiscoveryCache } from "../../hooks/HookDiscoveryCache"
|
||||
import { getHookTemplate } from "../../hooks/templates"
|
||||
import { isValidHookType, resolveHooksDirectory, VALID_HOOK_TYPES } from "../../hooks/utils"
|
||||
import { Controller } from ".."
|
||||
import { refreshHooks } from "./refreshHooks"
|
||||
|
||||
export async function createHook(
|
||||
controller: Controller,
|
||||
request: CreateHookRequest,
|
||||
globalHooksDirOverride?: string,
|
||||
): Promise<CreateHookResponse> {
|
||||
const { hookName, isGlobal, workspaceName } = request
|
||||
|
||||
// Validate hook name is one of the valid hook types
|
||||
if (!isValidHookType(hookName)) {
|
||||
throw new Error(`Invalid hook type: "${hookName}". Valid hook types are: ${VALID_HOOK_TYPES.join(", ")}`)
|
||||
}
|
||||
|
||||
// Determine target directory
|
||||
const hooksDir = await resolveHooksDirectory(isGlobal, workspaceName, globalHooksDirOverride)
|
||||
|
||||
// Ensure directory exists
|
||||
await fs.mkdir(hooksDir, { recursive: true })
|
||||
|
||||
const hookPath = path.join(hooksDir, hookName)
|
||||
|
||||
// Check if already exists
|
||||
try {
|
||||
await fs.stat(hookPath)
|
||||
throw new Error(`Hook ${hookName} already exists at ${hookPath}`)
|
||||
} catch (error) {
|
||||
// Good - file doesn't exist yet
|
||||
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Get template content
|
||||
const templateContent = getHookTemplate(hookName)
|
||||
|
||||
// Write file WITHOUT executable permissions (644) so hook is toggled off by default
|
||||
// User can enable it later when they're ready
|
||||
const mode = 0o644
|
||||
await fs.writeFile(hookPath, templateContent, { mode })
|
||||
|
||||
// Invalidate hook discovery cache
|
||||
await HookDiscoveryCache.getInstance().invalidateAll()
|
||||
|
||||
// Return updated hooks state
|
||||
const hooksToggles = await refreshHooks(controller, undefined, globalHooksDirOverride)
|
||||
return CreateHookResponse.create({ hooksToggles })
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { DeleteHookRequest, DeleteHookResponse } from "@shared/proto/cline/file"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { HookDiscoveryCache } from "../../hooks/HookDiscoveryCache"
|
||||
import { resolveHooksDirectory } from "../../hooks/utils"
|
||||
import { Controller } from ".."
|
||||
import { refreshHooks } from "./refreshHooks"
|
||||
|
||||
export async function deleteHook(
|
||||
controller: Controller,
|
||||
request: DeleteHookRequest,
|
||||
globalHooksDirOverride?: string,
|
||||
): Promise<DeleteHookResponse> {
|
||||
const { hookName, isGlobal, workspaceName } = request
|
||||
|
||||
// Determine hook path
|
||||
const hooksDir = await resolveHooksDirectory(isGlobal, workspaceName, globalHooksDirOverride)
|
||||
|
||||
const hookPath = path.join(hooksDir, hookName)
|
||||
|
||||
// Verify hook exists before attempting deletion
|
||||
try {
|
||||
await fs.stat(hookPath)
|
||||
} catch {
|
||||
throw new Error(`Hook ${hookName} does not exist at ${hookPath}`)
|
||||
}
|
||||
|
||||
// Delete the hook file
|
||||
await fs.unlink(hookPath)
|
||||
|
||||
// Invalidate hook discovery cache
|
||||
await HookDiscoveryCache.getInstance().invalidateAll()
|
||||
|
||||
// Return updated hooks state
|
||||
const hooksToggles = await refreshHooks(controller, undefined, globalHooksDirOverride)
|
||||
return DeleteHookResponse.create({ hooksToggles })
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import { HookInfo, HooksToggles, WorkspaceHooks } from "@shared/proto/cline/file"
|
||||
import fs from "fs/promises"
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { VALID_HOOK_TYPES } from "../../hooks/utils"
|
||||
import { Controller } from ".."
|
||||
|
||||
export async function refreshHooks(
|
||||
_controller: Controller,
|
||||
_request?: any,
|
||||
globalHooksDirOverride?: string,
|
||||
): Promise<HooksToggles> {
|
||||
const globalHooksDir = globalHooksDirOverride || path.join(os.homedir(), "Documents", "Cline", "Hooks")
|
||||
const isWindows = process.platform === "win32"
|
||||
|
||||
// Collect global hooks
|
||||
const globalHooks: HookInfo[] = []
|
||||
for (const hookName of VALID_HOOK_TYPES) {
|
||||
const hookPath = path.join(globalHooksDir, hookName)
|
||||
try {
|
||||
const stat = await fs.stat(hookPath)
|
||||
if (stat.isFile()) {
|
||||
globalHooks.push(
|
||||
HookInfo.create({
|
||||
name: hookName,
|
||||
enabled: await isExecutable(hookPath),
|
||||
absolutePath: hookPath,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
// File doesn't exist, skip
|
||||
}
|
||||
}
|
||||
|
||||
// Collect workspace hooks from all workspace folders
|
||||
const workspacePaths = await HostProvider.workspace.getWorkspacePaths({})
|
||||
const workspaceHooksList: WorkspaceHooks[] = []
|
||||
|
||||
for (const workspacePath of workspacePaths.paths) {
|
||||
const workspaceHooksDir = path.join(workspacePath, ".clinerules", "hooks")
|
||||
const hooks: HookInfo[] = []
|
||||
|
||||
for (const hookName of VALID_HOOK_TYPES) {
|
||||
const hookPath = path.join(workspaceHooksDir, hookName)
|
||||
try {
|
||||
const stat = await fs.stat(hookPath)
|
||||
if (stat.isFile()) {
|
||||
hooks.push(
|
||||
HookInfo.create({
|
||||
name: hookName,
|
||||
enabled: await isExecutable(hookPath),
|
||||
absolutePath: hookPath,
|
||||
}),
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
// File doesn't exist, skip
|
||||
}
|
||||
}
|
||||
|
||||
// Add all workspaces, even if they have no hooks yet
|
||||
// This allows users to create their first hook via the dropdown
|
||||
const workspaceName = path.basename(workspacePath)
|
||||
workspaceHooksList.push(
|
||||
WorkspaceHooks.create({
|
||||
workspaceName,
|
||||
hooks,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return HooksToggles.create({
|
||||
globalHooks,
|
||||
workspaceHooks: workspaceHooksList,
|
||||
isWindows,
|
||||
})
|
||||
}
|
||||
|
||||
async function isExecutable(filePath: string): Promise<boolean> {
|
||||
if (process.platform === "win32") {
|
||||
// On Windows, files are "enabled" if they exist
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.access(filePath, fs.constants.X_OK)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { ToggleHookRequest, ToggleHookResponse } from "@shared/proto/cline/file"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { HookDiscoveryCache } from "../../hooks/HookDiscoveryCache"
|
||||
import { resolveHooksDirectory } from "../../hooks/utils"
|
||||
import { Controller } from ".."
|
||||
import { refreshHooks } from "./refreshHooks"
|
||||
|
||||
export async function toggleHook(
|
||||
controller: Controller,
|
||||
request: ToggleHookRequest,
|
||||
globalHooksDirOverride?: string,
|
||||
): Promise<ToggleHookResponse> {
|
||||
const { hookName, isGlobal, enabled, workspaceName } = request
|
||||
|
||||
// Determine hook path
|
||||
const hooksDir = await resolveHooksDirectory(isGlobal, workspaceName, globalHooksDirOverride)
|
||||
|
||||
const hookPath = path.join(hooksDir, hookName)
|
||||
|
||||
// Verify hook exists
|
||||
try {
|
||||
await fs.stat(hookPath)
|
||||
} catch {
|
||||
throw new Error(`Hook ${hookName} does not exist at ${hookPath}`)
|
||||
}
|
||||
|
||||
// On Windows, we can't use chmod, so we just return the current state
|
||||
// without modifying the file. The frontend will disable the toggle.
|
||||
if (process.platform !== "win32") {
|
||||
// Toggle executable bit (Unix-like systems only)
|
||||
await fs.chmod(hookPath, enabled ? 0o755 : 0o644)
|
||||
}
|
||||
|
||||
// Invalidate cache
|
||||
await HookDiscoveryCache.getInstance().invalidateAll()
|
||||
|
||||
// Return updated state
|
||||
const hooksToggles = await refreshHooks(controller, undefined, globalHooksDirOverride)
|
||||
return ToggleHookResponse.create({ hooksToggles })
|
||||
}
|
||||
@@ -1,29 +1,29 @@
|
||||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { buildApiHandler } from "@core/api"
|
||||
import { tryAcquireTaskLockWithRetry } from "@core/task/TaskLockUtils"
|
||||
import { detectWorkspaceRoots } from "@core/workspace/detection"
|
||||
import { setupWorkspaceManager } from "@core/workspace/setup"
|
||||
import type { WorkspaceRootManager } from "@core/workspace/WorkspaceRootManager"
|
||||
import { WorkspaceRootManager } from "@core/workspace/WorkspaceRootManager"
|
||||
import { cleanupLegacyCheckpoints } from "@integrations/checkpoints/CheckpointMigration"
|
||||
import { downloadTask } from "@integrations/misc/export-markdown"
|
||||
import { ClineAccountService } from "@services/account/ClineAccountService"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
import type { ApiProvider, ModelInfo } from "@shared/api"
|
||||
import type { ChatContent } from "@shared/ChatContent"
|
||||
import type { ExtensionState, Platform } from "@shared/ExtensionMessage"
|
||||
import type { HistoryItem } from "@shared/HistoryItem"
|
||||
import type { McpMarketplaceCatalog, McpMarketplaceItem } from "@shared/mcp"
|
||||
import type { Settings } from "@shared/storage/state-keys"
|
||||
import type { Mode } from "@shared/storage/types"
|
||||
import type { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import type { UserInfo } from "@shared/UserInfo"
|
||||
import { ApiProvider, ModelInfo } from "@shared/api"
|
||||
import { ChatContent } from "@shared/ChatContent"
|
||||
import { ExtensionState, Platform } from "@shared/ExtensionMessage"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { McpMarketplaceCatalog, McpMarketplaceItem } from "@shared/mcp"
|
||||
import { Settings } from "@shared/storage/state-keys"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { TelemetrySetting } from "@shared/TelemetrySetting"
|
||||
import { UserInfo } from "@shared/UserInfo"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import axios from "axios"
|
||||
import fs from "fs/promises"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import * as path from "path"
|
||||
import type { FolderLockWithRetryResult } from "src/core/locks/types"
|
||||
import type * as vscode from "vscode"
|
||||
import * as vscode from "vscode"
|
||||
import { ClineEnv } from "@/config"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { ExtensionRegistryInfo } from "@/registry"
|
||||
@@ -35,7 +35,7 @@ import { getDistinctId } from "@/services/logging/distinctId"
|
||||
import { telemetryService } from "@/services/telemetry"
|
||||
import { getAxiosSettings } from "@/shared/net"
|
||||
import { ShowMessageType } from "@/shared/proto/host/window"
|
||||
import type { AuthState } from "@/shared/proto/index.cline"
|
||||
import { AuthState } from "@/shared/proto/index.cline"
|
||||
import { getLatestAnnouncementId } from "@/utils/announcements"
|
||||
import { getCwd, getDesktopDir } from "@/utils/path"
|
||||
import { PromptRegistry } from "../prompts/system-prompt"
|
||||
@@ -47,11 +47,10 @@ import {
|
||||
writeMcpMarketplaceCatalogToCache,
|
||||
} from "../storage/disk"
|
||||
import { fetchRemoteConfig } from "../storage/remote-config/fetch"
|
||||
import { type PersistenceErrorEvent, StateManager } from "../storage/StateManager"
|
||||
import { PersistenceErrorEvent, StateManager } from "../storage/StateManager"
|
||||
import { Task } from "../task"
|
||||
import type { StreamingResponseHandler } from "./grpc-handler"
|
||||
import { StreamingResponseHandler } from "./grpc-handler"
|
||||
import { sendMcpMarketplaceCatalogEvent } from "./mcp/subscribeToMcpMarketplaceCatalog"
|
||||
import { getClineOnboardingModels } from "./models/getClineOnboardingModels"
|
||||
import { appendClineStealthModels } from "./models/refreshOpenRouterModels"
|
||||
import { checkCliInstallation } from "./state/checkCliInstallation"
|
||||
import { sendStateUpdate } from "./state/subscribeToState"
|
||||
@@ -847,7 +846,6 @@ export class Controller {
|
||||
|
||||
async getStateToPostToWebview(): Promise<ExtensionState> {
|
||||
// Get API configuration from cache for immediate access
|
||||
const onboardingModels = getClineOnboardingModels()
|
||||
const apiConfiguration = this.stateManager.getApiConfiguration()
|
||||
const lastShownAnnouncementId = this.stateManager.getGlobalStateKey("lastShownAnnouncementId")
|
||||
const taskHistory = this.stateManager.getGlobalStateKey("taskHistory")
|
||||
@@ -960,7 +958,7 @@ export class Controller {
|
||||
defaultTerminalProfile,
|
||||
isNewUser,
|
||||
welcomeViewCompleted,
|
||||
onboardingModels,
|
||||
showOnboardingFlow: featureFlagsService.getOnboardingEnabled(),
|
||||
mcpResponsesCollapsed,
|
||||
terminalOutputLineLimit,
|
||||
maxConsecutiveMistakes,
|
||||
@@ -982,7 +980,7 @@ export class Controller {
|
||||
},
|
||||
hooksEnabled: {
|
||||
user: this.stateManager.getGlobalStateKey("hooksEnabled"),
|
||||
featureFlag: featureFlagsService.getHooksEnabled(),
|
||||
featureFlag: true, // Hooks feature is now always available
|
||||
},
|
||||
lastDismissedInfoBannerVersion,
|
||||
lastDismissedModelBannerVersion,
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { featureFlagsService } from "@/services/feature-flags"
|
||||
import { CLINE_ONBOARDING_MODELS } from "@/shared/cline/onboarding"
|
||||
import { OnboardingModel, OnboardingModelGroup } from "@/shared/proto/cline/state"
|
||||
|
||||
type OnboardingModelOverride = OnboardingModel & { hidden?: boolean }
|
||||
|
||||
let cached: OnboardingModelGroup | null = null
|
||||
|
||||
export function getClineOnboardingModels(): OnboardingModelGroup {
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
const remoteOverrides = featureFlagsService.getOnboardingOverrides()
|
||||
const models = new Map<string, OnboardingModel>(CLINE_ONBOARDING_MODELS.map((model) => [model.id, model]))
|
||||
|
||||
// Apply remote overrides if available
|
||||
if (remoteOverrides) {
|
||||
for (const [id, override] of Object.entries(remoteOverrides) as [string, OnboardingModelOverride][]) {
|
||||
if (override.hidden) {
|
||||
models.delete(id)
|
||||
} else {
|
||||
const baseModel = models.get(id)
|
||||
models.set(id, mergeModelWithOverride(baseModel, override))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cached = { models: Array.from(models.values()) }
|
||||
return cached
|
||||
}
|
||||
|
||||
function mergeModelWithOverride(baseModel: OnboardingModel | undefined, override: OnboardingModelOverride): OnboardingModel {
|
||||
const baseInfo = baseModel?.info
|
||||
const overrideInfo = override.info
|
||||
|
||||
// Merge info with proper defaults
|
||||
const mergedInfo = {
|
||||
...baseInfo,
|
||||
...overrideInfo,
|
||||
supportsPromptCache: overrideInfo?.supportsPromptCache ?? baseInfo?.supportsPromptCache ?? false,
|
||||
tiers: overrideInfo?.tiers ?? baseInfo?.tiers ?? [],
|
||||
}
|
||||
|
||||
// Return merged model, using base as foundation if available
|
||||
return baseModel ? { ...baseModel, ...override, info: mergedInfo } : { ...override, info: mergedInfo }
|
||||
}
|
||||
|
||||
export function clearOnboardingModelsCache(): void {
|
||||
cached = null
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import axios from "axios"
|
||||
import cloneDeep from "clone-deep"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { StateManager } from "@/core/storage/StateManager"
|
||||
import {
|
||||
ANTHROPIC_MAX_THINKING_BUDGET,
|
||||
CLAUDE_SONNET_1M_TIERS,
|
||||
@@ -79,7 +78,7 @@ interface OpenRouterRawModelInfo {
|
||||
export async function refreshOpenRouterModels(controller: Controller): Promise<Record<string, ModelInfo>> {
|
||||
const openRouterModelsFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.openRouterModels)
|
||||
|
||||
let models: Record<string, ModelInfo> = {}
|
||||
const models: Record<string, ModelInfo> = {}
|
||||
try {
|
||||
const response = await axios.get("https://openrouter.ai/api/v1/models", getAxiosSettings())
|
||||
|
||||
@@ -243,17 +242,12 @@ export async function refreshOpenRouterModels(controller: Controller): Promise<R
|
||||
// If we failed to fetch models, try to read cached models
|
||||
const cachedModels = await controller.readOpenRouterModels()
|
||||
if (cachedModels) {
|
||||
models = cachedModels
|
||||
// Cached models are already in application format (ModelInfo)
|
||||
return appendClineStealthModels(cachedModels as Record<string, ModelInfo>)
|
||||
}
|
||||
}
|
||||
|
||||
// Append stealth models if any
|
||||
const finalModels = appendClineStealthModels(models)
|
||||
|
||||
// Store in StateManager's in-memory cache
|
||||
StateManager.get().setModelsCache("openRouter", finalModels)
|
||||
|
||||
return finalModels
|
||||
return appendClineStealthModels(models)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EmptyRequest } from "@shared/proto/cline/common"
|
||||
import { OpenRouterCompatibleModelInfo, OpenRouterModelInfo } from "@shared/proto/cline/models"
|
||||
import axios from "axios"
|
||||
import { toRequestyServiceUrl } from "@/shared/clients/requesty"
|
||||
import { getAxiosSettings } from "@/shared/net"
|
||||
import { toRequestyServiceUrl } from "@/shared/clients/requesty"
|
||||
import { Controller } from ".."
|
||||
|
||||
/**
|
||||
|
||||
@@ -110,8 +110,6 @@ export async function updateApiConfigurationProto(
|
||||
actModeAihubmixModelInfo: protoApiConfiguration.actModeAihubmixModelInfo
|
||||
? fromProtobufOpenAiCompatibleModelInfo(protoApiConfiguration.actModeAihubmixModelInfo)
|
||||
: undefined,
|
||||
geminiPlanModeThinkingLevel: protoApiConfiguration.geminiPlanModeThinkingLevel,
|
||||
geminiActModeThinkingLevel: protoApiConfiguration.geminiActModeThinkingLevel,
|
||||
}
|
||||
|
||||
// Update the API configuration in storage
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const input = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
|
||||
console.log(JSON.stringify({
|
||||
cancel: false,
|
||||
contextModification: "COMPLETED: " + input.taskComplete.taskMetadata.result,
|
||||
errorMessage: ""
|
||||
}));
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
console.error("Hook execution error");
|
||||
process.exit(1);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user