Skip to content

Repository files navigation

deepseek-lane

A local proxy that connects Cursor to DeepSeek thinking models (deepseek-v4-pro / deepseek-v4-flash) via any OpenAI-compatible API. Designed for opencode subscriptions, compatible with any DeepSeek API endpoint.

Node version TypeScript Vite+ License

简体中文 · Why this exists · Features · Getting started · Usage · Configuration · Troubleshooting


Why this exists

DeepSeek thinking-mode tool calls require the complete multi-round reasoning_content chain to be sent back in later requests. Cursor omits that field, causing a 400 error:

Provider returned error: reasoning_content must be passed back to the API.

This proxy sits between Cursor and the upstream API, storing and restoring reasoning_content so DeepSeek thinking models work correctly in Cursor.

Features

  • reasoning_content injection — Restores reasoning_content that Cursor omits in tool-call requests, preventing DeepSeek's "reasoning_content must be passed back" error
  • Thinking display — Mirrors reasoning tokens into Cursor-visible collapsible Markdown blocks (<details><summary>Thinking</summary>...)
  • ngrok tunnel — Auto-starts a public HTTPS tunnel so Cursor can reach the proxy (Cursor blocks non-public API endpoints)
  • Conversation isolation — Scopes reasoning caches per conversation via SHA-256 hashes, preventing tool-call ID collisions across concurrent threads
  • Context caching compatible — Never injects synthetic thread IDs or timestamps, preserving DeepSeek's KV cache hit rates
  • Portable cache keys — Backfills cross-scope portable aliases so reasoning can be recovered even when conversation scope changes
  • Auto-recovery — Automatically recovers from history gaps when reasoning content is missing from cache, with a system notice explaining the truncated context
  • Legacy API compatibility — Converts functions/function_call to tools/tool_choice, normalizes reasoning_effort, strips Cursor thinking blocks from upstream payloads
  • SQLite cache — Persists reasoning content locally with configurable TTL and row limits
  • Interactive setup wizard — First-run guided configuration for API provider, model, and tunnel settings

How it works

Cursor sends requests to the proxy instead of the upstream API. The proxy:

  1. Receives a /v1/chat/completions request from Cursor
  2. Normalizes the payload — strips unsupported fields, converts legacy function calls, injects cached reasoning_content into tool-call messages that Cursor omitted it from
  3. Forwards the transformed request to the upstream API
  4. Rewrites the response — for streaming responses, accumulates SSE chunks, mirrors reasoning_content into visible Markdown blocks, and caches reasoning content to SQLite for future requests
  5. Returns the rewritten response to Cursor

The proxy uses SHA-256 hashes of the conversation context as cache keys, ensuring reasoning content is correctly matched across concurrent conversations with overlapping tool-call IDs.

Getting started

Prerequisites

Step 1: Set up ngrok

Cursor blocks non-public API URLs such as localhost, so the proxy needs a public HTTPS URL. ngrok can expose the local proxy without opening router ports.

  1. Create a free account at ngrok.com
  2. Find your authtoken on the ngrok dashboard
  3. Install and authenticate ngrok once:
brew install ngrok
ngrok config add-authtoken <your-token>

Note

You do not need to start ngrok manually — the proxy will start it automatically when you run dsl start.

Tip

You can also use Cloudflare Tunnel as an alternative. If your tool allows localhost API endpoints, skip ngrok with the --no-ngrok flag.

Step 2: Install and start the proxy

npm install -g deepseek-lane
dsl start

On first run, an interactive setup wizard will guide you through configuration: API provider, default model, port, thinking mode, reasoning effort, and ngrok settings. The config is saved to ~/.deepseek-lane/config.yaml.

When ngrok is enabled, the proxy prints the public URL on startup:

✓ Model: deepseek-v4-pro (thinking, max)
▸ Local:  http://127.0.0.1:9000/v1
▸ Public: https://your-tunnel.ngrok-free.dev/v1

Step 3: Configure Cursor

  1. Open Cursor Settings → Models → Add Custom Model
  2. Add a model with the name deepseek-v4-pro or deepseek-v4-flash
  3. Set the API key to your opencode subscription key (or DeepSeek API key)
  4. Set the base URL to the ngrok public URL with /v1 suffix: https://your-tunnel.ngrok-free.dev/v1
  5. Toggle the custom API with Cmd+Shift+0 (macOS) or Ctrl+Shift+0

Select deepseek-v4-pro or deepseek-v4-flash in the model picker and start chatting.

Usage

CLI commands

# Start with interactive setup wizard
dsl start

# Stop the proxy
dsl stop

# Restart in background
dsl restart

# Check if running
dsl status

# View recent logs
dsl log

CLI options

# Use a different port
dsl start --port 9001

# Point to a custom API endpoint
dsl start --base-url https://api.deepseek.com/v1

# Verbose logging
dsl start --verbose

# Run without ngrok (local-only)
dsl start --no-ngrok

# Clear the reasoning cache
dsl start --clear-reasoning-cache

# Skip interactive wizard
dsl start --no-interactive
Option Description
--config <path> Config file path
--host <host> Bind host (default: 127.0.0.1)
--port <port> Bind port (default: 9000)
--model <model> Upstream model name
--base-url <url> Upstream API base URL
--thinking <mode> Thinking mode: enabled / disabled
--reasoning-effort <level> low / medium / high / max / xhigh
--request-timeout <s> Upstream request timeout in seconds
--missing-reasoning-strategy <s> recover (default) or reject
--clear-reasoning-cache Clear the local reasoning cache and exit
--ngrok / --no-ngrok Enable/disable ngrok tunnel
--verbose / --no-verbose Enable/disable verbose logging
--display-reasoning / --no-display-reasoning Show reasoning in visible content
--collapsible-reasoning / --no-collapsible-reasoning Use collapsible Markdown for reasoning

Configuration

Auto-generated at ~/.deepseek-lane/config.yaml on first run:

base_url: https://opencode.ai/zen/go/v1
model: deepseek-v4-pro
thinking: enabled
reasoning_effort: max
display_reasoning: true
collapsible_reasoning: true

host: 127.0.0.1
port: 9000
ngrok: true
verbose: false
request_timeout: 300
max_request_body_bytes: 20971520

missing_reasoning_strategy: recover
reasoning_cache_max_age_seconds: 2592000
reasoning_cache_max_rows: 100000

CLI flags override config file values.

Data files

Path Description
~/.deepseek-lane/config.yaml Configuration file
~/.deepseek-lane/reasoning_content.sqlite3 Reasoning content cache
~/.deepseek-lane/pid Process ID file
~/.deepseek-lane/log Log output

API endpoints

POST /v1/chat/completions

Main endpoint. Accepts the standard OpenAI chat completions format. Requires Authorization: Bearer <key> header.

GET /v1/models

Returns available model list.

GET /healthz

Health check — returns {"ok": true}.

Project structure

deepseek-lane/
├── packages/
│   ├── core/                 @deepseek-lane/core
│   │   ├── src/config.ts           YAML config loading, CLI arg merging
│   │   ├── src/logging.ts          Colored terminal output
│   │   ├── src/reasoning-store.ts  SQLite reasoning cache
│   │   ├── src/server.ts           Hono HTTP server, request routing, streaming
│   │   ├── src/streaming.ts        SSE stream accumulator, display adapter
│   │   ├── src/transform.ts        Request normalization, recovery logic
│   │   ├── src/tunnel.ts           ngrok subprocess management
│   │   ├── src/types.ts            TypeScript type definitions
│   │   └── src/__tests__/          Test suite
│   └── cli/                  deepseek-lane
│       ├── src/cli.ts              Commander-based CLI entry point
│       ├── src/daemon.ts           PID management, background process
│       └── src/commands/           start, stop, restart, status, log
├── vite.config.ts                  Vite+ configuration
├── tsconfig.base.json              Shared TypeScript config
└── package.json                    Workspace root

Troubleshooting

Proxy won't start, port in use

lsof -ti:9000 | xargs kill

ngrok errors

Ensure ngrok is installed and authenticated:

ngrok config check

The proxy looks for the ngrok API at http://127.0.0.1:4040/api.

"reasoning_content must be passed back"

This error means the reasoning cache was cleared or missed. The default recover strategy handles this automatically. If the issue persists, clear the cache:

dsl start --clear-reasoning-cache

Verbose debugging

Run with full request/response logging:

dsl start --verbose --no-ngrok

Development

git clone <your-repo-url>
cd deepseek-lane
vp install
Command Description
vp dev Start with auto-reload and --no-ngrok
vp test Run test suite
vp check Lint, format, and type check
vp fmt Format all source files
vp pack Build the core library

Acknowledgments

Inspired by yxlao/deepseek-cursor-proxy.

About

A local proxy that connects Cursor to DeepSeek thinking models, fixing the reasoning_content must be passed back error. 连接 Cursor 与 DeepSeek 推理模型的本地代理,修复 reasoning_content must be passed back 问题。

Resources

Stars

24 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages