Created
March 25, 2026 18:04
-
-
Save igmarin/4169b1552327f3ae27111b053911d8a7 to your computer and use it in GitHub Desktop.
Proxy between Cursor and Ollama
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from flask import Flask, request, Response | |
| import requests | |
| import json | |
| app = Flask(__name__) | |
| OLLAMA_URL = "http://localhost:11434/v1" | |
| def forward(path, body): | |
| try: | |
| target_url = f"{OLLAMA_URL}/{path}" | |
| print(f"\n{'='*50}") | |
| print(f"→ PATH: /{path}") | |
| print(f"→ BODY RAW: {json.dumps(body)[:500]}") | |
| # Normalize: Cursor Responses API uses "input", Ollama uses "messages" | |
| raw_messages = body.get('messages') or body.get('input') or [] | |
| if not raw_messages: | |
| raw_messages = [{"role": "user", "content": "hello"}] | |
| clean_messages = [] | |
| for msg in raw_messages: | |
| role = msg.get('role') | |
| if not role: | |
| continue # skip objects without role | |
| content = msg.get('content', '') | |
| if isinstance(content, list): | |
| texts = [c.get('text', '') for c in content | |
| if isinstance(c, dict) and c.get('type') in ('input_text', 'text')] | |
| content = '\n'.join(filter(None, texts)) or 'hello' | |
| if content: # add only if there is content | |
| clean_messages.append({'role': role, 'content': content}) | |
| if not clean_messages: | |
| clean_messages = [{"role": "user", "content": "hello"}] | |
| body['messages'] = clean_messages | |
| body.pop('input', None) | |
| body.pop('tools', None) | |
| body.pop('tool_choice', None) | |
| body.pop('store', None) | |
| body.pop('stream_options', None) | |
| body.pop('metadata', None) | |
| body.pop('user', None) | |
| is_stream = body.get('stream', False) | |
| print(f"→ MESSAGES: {len(body['messages'])} | LAST: {body['messages'][-1]['content'][:100]}") | |
| resp = requests.post( | |
| target_url, | |
| json=body, | |
| headers={"Content-Type": "application/json"}, | |
| stream=is_stream, | |
| timeout=120 | |
| ) | |
| print(f"← STATUS: {resp.status_code}") | |
| if not is_stream: | |
| print(f"← BODY: {resp.text[:300]}") | |
| return Response( | |
| resp.iter_content(chunk_size=1024) if is_stream else resp.content, | |
| status=resp.status_code, | |
| content_type=resp.headers.get('content-type', 'application/json') | |
| ) | |
| except Exception as e: | |
| print(f"💥 ERROR: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return Response( | |
| json.dumps({"error": {"message": str(e), "type": "proxy_error"}}), | |
| status=500, | |
| content_type='application/json' | |
| ) | |
| @app.route('/v1/<path:path>', methods=['GET', 'POST', 'OPTIONS']) | |
| def proxy_v1(path): | |
| if request.method == 'OPTIONS': | |
| r = Response() | |
| r.headers['Access-Control-Allow-Origin'] = '*' | |
| r.headers['Access-Control-Allow-Headers'] = '*' | |
| r.headers['Access-Control-Allow-Methods'] = '*' | |
| return r | |
| return forward(path, request.get_json(silent=True) or {}) | |
| @app.route('/<path:path>', methods=['GET', 'POST', 'OPTIONS']) | |
| def proxy_root(path): | |
| if request.method == 'OPTIONS': | |
| r = Response() | |
| r.headers['Access-Control-Allow-Origin'] = '*' | |
| r.headers['Access-Control-Allow-Headers'] = '*' | |
| r.headers['Access-Control-Allow-Methods'] = '*' | |
| return r | |
| return forward(path, request.get_json(silent=True) or {}) | |
| if __name__ == '__main__': | |
| print("🚀 Proxy on port 8080 → Ollama 11434") | |
| app.run(host='0.0.0.0', port=8080, debug=False) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
export HOMEBREW_AUTO_UPDATE_SECS=604800
alias brewup="brew update && brew upgrade && brew autoremove && brew cleanup && brew doctor"