Merge pull request #49 from gabrii/code-cleanup

Code cleanup
This commit is contained in:
Gabriel Gavilan
2025-09-30 17:33:49 +02:00
committed by GitHub
23 changed files with 1225 additions and 408 deletions
+5 -9
View File
@@ -1,9 +1,7 @@
"""The app module, containing the app factory function."""
import logging
import sys
from flask import Flask
from rich.traceback import install as install_rich_traceback
from . import commands
from .blueprint import blueprint
@@ -16,9 +14,9 @@ def create_app(config_object="app.settings"):
"""
app = Flask(__name__.split(".")[0])
app.config.from_object(config_object)
configure_logging(app)
register_commands(app)
register_blueprints(app)
configure_logger(app)
return app
@@ -34,8 +32,6 @@ def register_commands(app):
app.cli.add_command(commands.lint)
def configure_logger(app):
"""Configure loggers."""
handler = logging.StreamHandler(sys.stdout)
if not app.logger.handlers:
app.logger.addHandler(handler)
def configure_logging(app):
"""Configure logging."""
install_rich_traceback()
+5 -4
View File
@@ -2,7 +2,9 @@
from functools import wraps
from flask import Response, current_app, request
from flask import current_app, request
from .exceptions import CursorConfigurationError
def valid_brearer_token():
@@ -20,8 +22,8 @@ def require_auth(func):
if valid_brearer_token():
return func(*args, **kwargs)
else:
error_message = (
"\nAuthentication with Cursor-Azure-GPT-5 service failed.\n\n"
raise CursorConfigurationError(
"Authentication with Cursor-Azure-GPT-5 service failed.\n\n"
"These value of:\n"
"\tCursor Settings > Models > API Keys > OpenAI API Key\n\n"
"Must match the value of:\n"
@@ -29,6 +31,5 @@ def require_auth(func):
"Ensure the values match exactly, and try again.\n"
"If modifying the .env file, restart the service for the changes to apply."
)
return Response(error_message, 400)
return wrapper
+10 -13
View File
@@ -64,19 +64,16 @@ class AzureAdapter:
resp_content = resp.text
body = request_kwargs.get("json", {})
if "instructions" in body:
body["instructions"] = body["instructions"][:7] + "..."
if "tools" in body:
body["tools"] = f"...redacted {len(body['tools'])} tools..."
if "input" in body:
body["input"] = f"...redacted {len(body['input'])} input items..."
if "prompt_cache_key" in body:
body["prompt_cache_key"] = re.sub(
r"(...)(.*)(...)", "\\1***\\3", body["prompt_cache_key"]
)
body["instructions"] = body.get("instructions", "no instructions")[:16] + "..."
body["tools"] = f"...redacted {len(body.get('tools', 'no tools'))} tools..."
body["input"] = (
f"...redacted {len(body.get('input', 'no input'))} input items..."
)
body["prompt_cache_key"] = re.sub(
r"(...)(.*)(...)",
"\\1***\\3",
body.get("prompt_cache_key", "no prompt_cache_key"),
)
report = {
"endpoint": re.sub(
r"(//.)(.*?)(.\.)", "\\1***\\3", request_kwargs.get("url")
+31 -39
View File
@@ -10,6 +10,8 @@ from typing import Any, Dict, List
from flask import Request, current_app
from ..exceptions import CursorConfigurationError, ServiceConfigurationError
class RequestAdapter:
"""Handle pre-request adaptation for the Azure Responses API.
@@ -43,11 +45,9 @@ class RequestAdapter:
for m in messages:
role = m.get("role")
c = m.get("content")
content = m.get("content")
if role in {"system", "developer"}:
text = c
if text:
instructions_parts.append(text)
instructions_parts.append(content)
continue
# For user/assistant/tools as inputs
if role == "tool":
@@ -55,19 +55,18 @@ class RequestAdapter:
item = {
"type": "function_call_output",
"output": c,
"output": content,
"status": "completed",
"call_id": call_id,
}
input_items.append(item)
else:
text = c
item = {
"role": role or "user",
"content": [
{
"type": "input_text" if role == "user" else "output_text",
"text": text,
"text": content,
},
],
}
@@ -87,26 +86,22 @@ class RequestAdapter:
instructions = "\n\n".join(instructions_parts) if instructions_parts else None
return {
"input": input_items if input_items else None,
"instructions": instructions,
"input": input_items if input_items else None,
}
def _transform_tools_for_responses(self, tools: Any) -> Any:
out: List[Dict[str, Any]] = []
for t in tools:
ttype = t.get("type")
if ttype == "function" and isinstance(t.get("function"), dict):
f = t["function"]
transformed: Dict[str, Any] = {
"type": "function",
"name": f.get("name"),
}
if "description" in f:
transformed["description"] = f["description"]
if "parameters" in f:
transformed["parameters"] = f["parameters"]
transformed["strict"] = False
out.append(transformed)
for tool in tools:
function = tool.get("function")
transformed: Dict[str, Any] = {
"type": "function",
"name": function.get("name"),
"description": function.get("description"),
"parameters": function.get("parameters"),
"strict": False,
}
out.append(transformed)
return out
# ---- Main adaptation (always streaming completions-like) ----
@@ -134,39 +129,31 @@ class RequestAdapter:
# Map Chat/Completions to Responses (always streaming)
messages = payload.get("messages") or []
tools_in = payload.get("tools") or []
tool_choice_in = payload.get("tool_choice")
prompt_cache_key = payload.get("user") or payload.get("prompt_cache_key")
mapped = (
responses_body = (
self._messages_to_responses_input_and_instructions(messages)
if isinstance(messages, list)
else {"input": None, "instructions": None}
)
responses_body: Dict[str, Any] = {}
if mapped.get("instructions"):
responses_body["instructions"] = mapped["instructions"]
if mapped.get("input") is not None:
responses_body["input"] = mapped["input"]
responses_body["model"] = settings["AZURE_DEPLOYMENT"]
# Transform tools and tool choice
if tools_in:
responses_body["tools"] = self._transform_tools_for_responses(tools_in)
if tool_choice_in is not None:
responses_body["tool_choice"] = tool_choice_in
responses_body["tools"] = self._transform_tools_for_responses(
payload.get("tools", [])
)
responses_body["tool_choice"] = payload.get("tool_choice")
if prompt_cache_key is not None:
responses_body["prompt_cache_key"] = prompt_cache_key
responses_body["prompt_cache_key"] = payload.get("user")
# Always streaming
responses_body["stream"] = True
reasoning_effort = inbound_model.replace("gpt-", "").lower()
if reasoning_effort not in {"high", "medium", "low", "minimal"}:
raise ValueError(
"Model name must be either gpt-high, gpt-medium, gpt-low, or gpt-minimal"
raise CursorConfigurationError(
"Model name must be either gpt-high, gpt-medium, gpt-low, or gpt-minimal."
f"\n\nGot: {inbound_model}"
)
responses_body["reasoning"] = {
@@ -177,6 +164,11 @@ class RequestAdapter:
# but allowing it for now to be able to test it on other models
if settings["AZURE_SUMMARY_LEVEL"] in {"auto", "detailed", "concise"}:
responses_body["reasoning"]["summary"] = settings["AZURE_SUMMARY_LEVEL"]
else:
raise ServiceConfigurationError(
"AZURE_SUMMARY_LEVEL must be either auto, detailed, or concise."
f"\n\nGot: {settings['AZURE_SUMMARY_LEVEL']}"
)
# No need to pass verbosity if it's set to medium, as it's the model's default
if settings["AZURE_VERBOSITY_LEVEL"] in {"low", "high"}:
+63 -84
View File
@@ -15,6 +15,9 @@ from flask import Response, stream_with_context
from ..common.sse import chunks_to_sse, sse_to_events
# Centralized events that should end a <think> block before handling
THINKING_STOP_EVENTS = {"response.output_text.delta", "response.output_item.added"}
class ResponseAdapter:
"""Handle post-request adaptation from Azure Responses API to Flask.
@@ -64,115 +67,84 @@ class ResponseAdapter:
# ---- Event handlers (per SSE event) ----
def _output_item__added(
self, obj: Optional[Dict[str, Any]]
) -> Iterable[Dict[str, Any]]:
"""Handle response.output_item.added events and emit chunks as needed."""
) -> Optional[Dict[str, Any]]:
"""Handle response.output_item.added events and emit a single chunk."""
item_type = obj.get("item", {}).get("type")
if item_type == "reasoning":
self._thinking = True
return [
self._build_completion_chunk(
delta={"role": "assistant", "content": "<think>\n\n"}
)
]
return self._build_completion_chunk(
delta={"role": "assistant", "content": "<think>\n\n"}
)
if item_type == "function_call":
out: list[Dict[str, Any]] = []
if self._thinking:
out.append(
self._build_completion_chunk(
delta={"role": "assistant", "content": "</think>\n\n"}
)
)
self._thinking = False
self._tool_calls += 1
name = obj.get("item", {}).get("name")
arguments = obj.get("item", {}).get("arguments")
call_id = obj.get("item", {}).get("call_id")
out.append(
self._build_completion_chunk(
delta={
"role": "assistant",
"content": None,
"tool_calls": [
{
"index": self._tool_calls - 1,
"id": call_id or "",
"type": "function",
"function": {
"name": name or "",
"arguments": arguments or "",
},
}
],
}
)
)
self._called_function = True
return out
return []
def _function_call_arguments__delta(
self, obj: Optional[Dict[str, Any]]
) -> Iterable[Dict[str, Any]]:
"""Handle response.function_call.arguments.delta events."""
out: list[Dict[str, Any]] = []
arguments_delta = obj.get("delta", "") if isinstance(obj, dict) else ""
out.append(
self._build_completion_chunk(
return self._build_completion_chunk(
delta={
"role": "assistant",
"content": None,
"tool_calls": [
{
"index": self._tool_calls - 1,
"function": {"arguments": arguments_delta},
"id": call_id or "",
"type": "function",
"function": {
"name": name or "",
"arguments": arguments or "",
},
}
]
],
}
)
return None
def _function_call_arguments__delta(
self, obj: Optional[Dict[str, Any]]
) -> Optional[Dict[str, Any]]:
"""Handle response.function_call.arguments.delta events."""
arguments_delta = obj.get("delta", "") if isinstance(obj, dict) else ""
return self._build_completion_chunk(
delta={
"tool_calls": [
{
"index": self._tool_calls - 1,
"function": {"arguments": arguments_delta},
}
]
}
)
return out
def _reasoning_summary_text__delta(
self, obj: Optional[Dict[str, Any]]
) -> Iterable[Dict[str, Any]]:
"""Handle reasoning.summary_text.delta events and emit text chunks."""
return [
self._build_completion_chunk(
delta={
"role": "assistant",
"content": (obj.get("delta", "") if isinstance(obj, dict) else ""),
}
)
]
) -> Optional[Dict[str, Any]]:
"""Handle reasoning.summary_text.delta events and emit text chunk."""
return self._build_completion_chunk(
delta={
"role": "assistant",
"content": (obj.get("delta", "") if isinstance(obj, dict) else ""),
}
)
def _reasoning_summary_text__done(
self, obj: Optional[Dict[str, Any]]
) -> Iterable[Dict[str, Any]]:
"""Handle reasoning.summary_text.done events and close think block."""
return [
self._build_completion_chunk(delta={"role": "assistant", "content": "\n\n"})
]
) -> Optional[Dict[str, Any]]:
"""Handle reasoning.summary_text.done events and close with blank line inside think block."""
return self._build_completion_chunk(
delta={"role": "assistant", "content": "\n\n"}
)
def _output_text__delta(
self, obj: Optional[Dict[str, Any]]
) -> Iterable[Dict[str, Any]]:
"""Handle response.output_text.delta events and emit text chunks."""
out: list[Dict[str, Any]] = []
if self._thinking:
out.append(
self._build_completion_chunk(
delta={"role": "assistant", "content": "</think>\n\n"}
)
)
self._thinking = False
out.append(
self._build_completion_chunk(
delta={
"role": "assistant",
"content": (obj.get("delta", "") if isinstance(obj, dict) else ""),
}
)
) -> Optional[Dict[str, Any]]:
"""Handle response.output_text.delta events and emit text chunk."""
return self._build_completion_chunk(
delta={
"role": "assistant",
"content": (obj.get("delta", "") if isinstance(obj, dict) else ""),
}
)
return out
def adapt(self, upstream_resp: Any) -> Response:
"""Adapt an upstream Azure streaming response into SSE for Flask."""
@@ -196,10 +168,17 @@ class ResponseAdapter:
handler = getattr(self, handler_name, None)
if not handler:
continue
# Centrally close <think> blocks whenever a stop event is seen
if self._thinking and (ev.event in THINKING_STOP_EVENTS):
yield self._build_completion_chunk(
delta={"role": "assistant", "content": "</think>\n\n"}
)
self._thinking = False
res = handler(ev.json)
if res is not None:
for chunk in res:
yield chunk
yield res
finally:
# Emit finish reason at the end of stream
if self._tool_calls > 0:
+7 -25
View File
@@ -4,11 +4,7 @@ This module defines the application blueprint, configures logging, and
forwards incoming HTTP requests to the configured backend implementation.
"""
import sys
from flask import Blueprint, jsonify, request
from loguru import logger
from rich.traceback import install as install_rich_traceback
from .auth import require_auth
from .azure.adapter import AzureAdapter
@@ -18,30 +14,10 @@ from .common.recording import (
init_last_recording,
record_payload,
)
from .exceptions import ConfigurationError
blueprint = Blueprint("blueprint", __name__)
# Pretty tracebacks for easier debugging
install_rich_traceback(show_locals=False)
# Configure Loguru to print colorful logs to stdout
logger.remove()
logger.add(
sys.stdout,
colorize=True,
enqueue=False,
backtrace=False,
diagnose=False,
format=(
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> "
"| <level>{level: <8}</level> "
"| <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> "
"- <level>{message}</level>"
),
)
ALL_METHODS = [
"GET",
"POST",
@@ -103,3 +79,9 @@ def models():
],
}
)
@blueprint.errorhandler(ConfigurationError)
def configuration_error(e: ConfigurationError):
"""Return a 400 JSON error payload for ValueError."""
return e.get_response_content(), 400
+2 -10
View File
@@ -41,13 +41,6 @@ def test(coverage, filter):
@click.command()
@click.option(
"-f",
"--fix-imports",
default=True,
is_flag=True,
help="Fix imports using isort, before linting",
)
@click.option(
"-c",
"--check",
@@ -55,7 +48,7 @@ def test(coverage, filter):
is_flag=True,
help="Don't make any changes to files, just confirm they are formatted correctly",
)
def lint(fix_imports, check):
def lint(check):
"""Lint and check code style with black, flake8 and isort."""
skip = [
"requirements",
@@ -85,7 +78,6 @@ def lint(fix_imports, check):
if check:
isort_args.append("--check")
black_args.append("--check")
if fix_imports:
execute_tool("Fixing import order", "isort", *isort_args)
execute_tool("Fixing import order", "isort", *isort_args)
execute_tool("Formatting style", "black", *black_args)
execute_tool("Checking code style", "flake8")
+147 -121
View File
@@ -2,22 +2,28 @@
import json
import os
import re
import time
import uuid
from typing import Any, Dict, List
from flask import Request
from rich.console import Console
from rich import box
from rich.console import Console, Group
from rich.json import JSON
from rich.markdown import Markdown
from rich.padding import Padding
from rich.panel import Panel
from rich.table import Table
# Global console instance for consistent logging across modules
console = Console()
# --- Request logging helpers ---
ROLE_COLORS = {
"tool": "magenta",
"system": "yellow",
"user": "cyan",
"assistant": "light_green",
}
def should_redact() -> bool:
@@ -92,6 +98,13 @@ def _capture_request_details(req: Request, request_id: str) -> Dict[str, Any]:
return details
def escape_tags(text: str) -> str:
"""Escapes xml-like tags in text so that they are visible when rendered as Markdown."""
return re.sub(
"(<[^<\n]+?)(>)", "\\1>`\n", re.sub("(<)([^>\n]+?>)", "\n`<\\2", text)
).replace(">`\n\n\n`<", ">`\n\n`<")
def log_request(req: Request) -> str:
"""Pretty-print a Flask request using Rich and return the request id."""
request_id = uuid.uuid4().hex[:8]
@@ -103,132 +116,145 @@ def log_request(req: Request) -> str:
# Rich pretty print of the full request details
console.rule(f"[bold]Request #{rid}[/bold] — {method} {path}")
console.print(Panel.fit("Headers"))
console.print(details.get("headers"))
console.print(Panel.fit("Args / Form / JSON"))
json_payload = details.get("json")
cleaned_json = json_payload
if isinstance(json_payload, dict):
# Remove verbose fields to log them separately
cleaned_json = {
k: v
for k, v in json_payload.items()
if k
not in {
"tools",
}
}
# Remove verbose fields to log them separately
cleaned_json = {
k: v if k not in {"tools", "messages"} else "Pretty-printed below ↓"
for k, v in json_payload.items()
}
console.print_json(
data={
"query_args": details.get("query_args"),
"form": details.get("form"),
"json": cleaned_json,
}
data=cleaned_json,
indent=None,
)
# Separate section for chat messages (role + content)
messages = []
if isinstance(json_payload, dict):
maybe_messages = json_payload.get("messages")
if isinstance(maybe_messages, list):
messages = maybe_messages
messages = json_payload.get("messages", [])
tools = json_payload.get("tools", [])
if messages:
console.rule(f"Messages ({len(messages)})")
for idx, tool in enumerate(tools, start=1):
table = Table(
caption="[italic]Required fields are marked with *[/italic]",
pad_edge=False,
box=box.SIMPLE,
leading=2,
)
for idx, msg in enumerate(messages, start=1):
role = ""
content_val: Any = ""
name = None
if isinstance(msg, dict):
role = str(msg.get("role", ""))
content_val = msg.get("content", "")
name = msg.get("name")
tool_call_id = msg.get("tool_call_id")
title = (
f"Message {idx}: {role}"
if not name
else f"Message {idx}: {role} ({name}) - {tool_call_id}"
table.add_column("Name", justify="right", style="cyan", no_wrap=True)
table.add_column("Description", style="white")
for tool in tools:
function = tool.get("function")
required = function.get("parameters").get("required")
params_table = Table(
show_header=False,
show_edge=True,
show_lines=True,
title="Parameters",
title_justify="left",
expand=True,
leading=3,
)
console.rule(title)
console.print(
Padding(
Markdown(
content_val.replace("<", "\n`<")
.replace(">", ">`\n")
.replace(">`\n\n\n`<", ">`\n\n`<")
),
(1, 0),
params_table.add_column("Name", justify="right", no_wrap=True, style="cyan")
params_table.add_column("Description", style="white")
for param_name, param_value in (
function.get("parameters").get("properties").items()
):
param_type = param_value.get("type")
if param_type == "array":
param_type += f"({param_value.get('items').get('type')})"
if param_name in required:
param_type = f"[bold]*{param_type}[/bold]"
param_type = f"[magenta]{param_type}[/magenta]"
param_name = f"{param_name}"
params_table.add_row(
Group(param_name, param_type),
f"{param_value.get("description")}",
)
table.add_row(
f"{function.get('name')}",
Group(
Markdown(escape_tags(function.get("description"))),
params_table,
),
)
console.print(
Panel(
table,
title=f"[italic]{0}/{len(messages)}[/italic] [bold]<tools>[/bold]",
title_align="left",
subtitle="[bold]</tools>[/bold]",
subtitle_align="right",
border_style=ROLE_COLORS["tool"],
)
)
for idx, msg in enumerate(messages, start=1):
role = str(msg.get("role", ""))
content_val = msg.get("content", "")
name = msg.get("name")
tool_call_id = msg.get("tool_call_id")
message_title = (
f"[italic]{idx}/{len(messages)}[/italic] [bold]<{role}>[/bold]"
if not name
else f"[italic]{idx}/{len(messages)}[/italic] [bold]<{role} name={name} id={tool_call_id}>[/bold]"
)
message_elements = []
message_elements.append(
Padding(
Markdown(
escape_tags(content_val),
),
(1, 0),
)
)
tool_calls = msg.get("tool_calls", [])
for tool_call in tool_calls:
function = tool_call.get("function", {})
arguments = function.get("arguments")
tool_elements = []
tool_title = f"[bold]<tool_call id={tool_call.get('id')}>[/bold]"
tool_elements.append(
f"[bold][magenta]{function.get('name')}[/magenta] [blue]([/blue][/bold]",
)
try:
tool_elements.append(Padding(JSON(arguments), (0, 4)))
except json.JSONDecodeError:
tool_elements.append(
Padding("[red]Invalid JSON generated by the model:[/red]", (0, 4))
)
tool_elements.append(arguments)
tool_elements.append("[bold][blue])[/blue][/bold]")
message_elements.append(
Panel(
Group(*tool_elements),
title=tool_title,
border_style=ROLE_COLORS["tool"],
)
)
tool_calls = msg.get("tool_calls", [])
for tool_call in tool_calls:
function = tool_call.get("function", {})
arguments = function.get("arguments")
console.print(
Padding(
Panel.fit(f"Tool call [italic]{tool_call.get('id')}[italic]"),
(0, 4),
)
message_style = ROLE_COLORS.get(role, "red")
message_subtitle = message_title[message_title.find("<") :].replace("<", "</")
message_subtitle = (
"[bold]"
+ message_subtitle[
: (
message_subtitle.find(" ")
if " " in message_subtitle
else message_subtitle.find(">")
)
console.print(
Padding(
f"[bold][magenta]{function.get('name')}[/magenta] ([/bold]",
(0, 4),
)
)
if arguments:
try:
console.print(Padding(JSON(arguments), (0, 8)))
except json.JSONDecodeError:
console.print("[red]Invalid JSON generated by the model:[/red]")
print(arguments)
console.print(Padding("[bold])[/bold]", (0, 4)))
if tool_calls:
console.print()
]
+ ">[/bold]"
)
console.print(
Panel(
Group(*message_elements),
title=message_title,
title_align="left",
subtitle=message_subtitle,
subtitle_align="right",
border_style=message_style,
)
)
return request_id
# --- SSE logging helpers, keeping for future use if we enable SSE logging ---
# from .sse import SSEEvent
# def _clean_payload(obj: Any) -> Any:
# """Default cleaning to reduce noisy fields in logs.
# - If obj is a dict, remove top-level 'tools'
# - If it contains a nested 'response' dict, also remove its 'tools'
# Returns a shallow-cleaned copy when applicable; otherwise returns the input unchanged.
# """
# if not isinstance(obj, dict):
# return obj
# # Shallow copy top-level
# cleaned = {k: v for k, v in obj.items()}
# if "tools" in cleaned:
# cleaned = {k: v for k, v in cleaned.items() if k != "tools"}
# resp = cleaned.get("response")
# if isinstance(resp, dict) and "tools" in resp:
# # Shallow copy nested response to drop tools
# new_resp = {k: v for k, v in resp.items() if k != "tools"}
# cleaned = {**cleaned, "response": new_resp}
# return cleaned
# def log_event(ev: SSEEvent) -> None:
# """Pretty-print one SSE event using Rich.
# - Title reflects whether the event had an 'event' name and its index
# - If payload parses as JSON (ev.json), it is cleaned and printed as JSON; otherwise raw text is printed
# """
# obj = ev.json
# if obj is not None:
# title = (
# f"SSE JSON #{ev.index}" if not ev.event else f"SSE {ev.event} #{ev.index}"
# )
# console.print(Panel.fit(title))
# console.print_json(data=_clean_payload(obj))
# else:
# title = f"SSE data #{ev.index}"
# if ev.event:
# title = f"SSE {ev.event} #{ev.index}"
# console.print(Panel.fit(title))
# console.print(ev.data)
+2 -4
View File
@@ -12,7 +12,7 @@ import re
from functools import wraps
from typing import Any, Dict
from flask import current_app, has_app_context
from flask import current_app
RECORDINGS_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "recordings")
@@ -25,9 +25,7 @@ def config_bypass(func):
@wraps(func)
def wrapper(*args, **kwargs):
enabled = False
if has_app_context():
enabled = current_app.config["RECORD_TRAFFIC"]
enabled = current_app.config["RECORD_TRAFFIC"]
if not enabled:
return None
return func(*args, **kwargs)
+18 -86
View File
@@ -7,7 +7,7 @@ This module provides helpers to decode and encode SSE streams, including:
"""
import json
from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Any, Dict, Iterable, Iterator, List, Optional
from .recording import record_sse
@@ -31,17 +31,6 @@ class SSEEvent:
retry: Optional[int] = None
# Monotonic sequence number (1-based) within a stream, set by the decoder
index: int = 0
# Lazy JSON cache (computed on first access of .json)
_json_cached: bool = field(default=False, init=False, repr=False)
_json_value: Optional[Any] = field(default=None, init=False, repr=False)
@property
def is_done(self) -> bool:
"""Return True if this event marks the end of the stream.
The end-of-stream sentinel is the literal string "[DONE]".
"""
return self.data.strip() == "[DONE]"
@property
def json(self) -> Optional[Any]:
@@ -49,19 +38,9 @@ class SSEEvent:
Returns None if the data is empty, invalid JSON, or the [DONE] sentinel.
"""
if not self._json_cached:
val: Optional[Any]
text = (self.data or "").strip()
if self.is_done or not text:
val = None
else:
try:
val = json.loads(text)
except json.JSONDecodeError:
val = None
self._json_value = val
self._json_cached = True
return self._json_value
text = (self.data or "").strip()
val: Optional[Any] = json.loads(text)
return val
class SSEDecoder:
@@ -92,11 +71,8 @@ class SSEDecoder:
.strip()
.decode(self.encoding, errors="replace")
)
elif line.startswith(b"data:"):
part = line[5:]
if part.startswith(b" "):
part = part[1:]
data_parts.append(part)
else:
data_parts.append(line[5:].strip())
data_text = (
b"\n".join(data_parts).decode(self.encoding, errors="replace")
@@ -107,8 +83,6 @@ class SSEDecoder:
def feed(self, chunk: bytes) -> Iterator[SSEEvent]:
"""Feed a new bytes chunk and yield any complete parsed events."""
if not chunk:
return
self.buffer += chunk
self.full_buffer += chunk
while True:
@@ -119,11 +93,10 @@ class SSEDecoder:
self.buffer = self.buffer[idx + 1 :]
stripped = line.rstrip(b"\r\n")
if stripped == b"":
if self._event_lines:
ev = self._parse_event(self._event_lines)
self._seq += 1
ev.index = self._seq
yield ev
ev = self._parse_event(self._event_lines)
self._seq += 1
ev.index = self._seq
yield ev
self._event_lines = []
else:
self._event_lines.append(stripped)
@@ -157,13 +130,10 @@ def encode_sse_data(data: str) -> bytes:
as per the SSE spec. Optionally include event and id.
"""
out = bytearray()
if data == "":
out.extend(b"data:\n")
else:
for line in data.splitlines():
out.extend(b"data: ")
out.extend(line.encode("utf-8"))
out.extend(b"\n")
for line in data.splitlines():
out.extend(b"data: ")
out.extend(line.encode("utf-8"))
out.extend(b"\n")
out.extend(b"\n")
return bytes(out)
@@ -174,9 +144,7 @@ def encode_sse_json(obj: Any) -> bytes:
return encode_sse_data(payload)
def chunks_to_sse(
chunks: Iterable[Dict[str, Any]], *, add_done: bool = True
) -> Iterator[bytes]:
def chunks_to_sse(chunks: Iterable[Dict[str, Any]]) -> Iterator[bytes]:
"""Encode an iterator of JSON-able dicts into SSE byte messages.
If add_done is True, a final [DONE] sentinel event is yielded.
@@ -188,48 +156,12 @@ def chunks_to_sse(
buffer += sse
yield sse
finally:
if add_done:
sse = done_event_bytes()
buffer += sse
yield sse
sse = done_event_bytes()
buffer += sse
yield sse
record_sse(buffer, "downstream_response")
def done_event_bytes() -> bytes:
"""Return the SSE-encoded [DONE] sentinel as bytes."""
return encode_sse_data("[DONE]")
# Keeping for future use if we enable OpenAI backend
# def sse_to_chunks(
# stream: Iterable[bytes], *, skip_done: bool = True, encoding: str = "utf-8"
# ) -> Iterator[Dict[str, Any]]:
# """Convert an SSE byte-stream to an iterator of JSON dicts.
#
# - Collects multi-line data fields per SSE spec
# - Uses event.json to avoid repeated json.loads
# - Skips the [DONE] sentinel by default
# """
# for ev in sse_to_events(stream, encoding=encoding):
# if skip_done and ev.is_done:
# continue
# if ev.json is None:
# continue
# yield ev.json
#
#
# def sse_to_json_events(
# stream: Iterable[bytes], *, skip_done: bool = True, encoding: str = "utf-8"
# ) -> Iterator[Tuple[Optional[str], Dict[str, Any]]]:
# """Yield (event, json_obj) pairs for events whose data parses as JSON.
#
# Non-JSON events are skipped. The [DONE] sentinel is skipped if skip_done
# is True.
# """
# for ev in sse_to_events(stream, encoding=encoding):
# if skip_done and ev.is_done:
# continue
# obj = ev.json
# if obj is None:
# continue
# yield (ev.event, obj)
+25
View File
@@ -0,0 +1,25 @@
"""Exceptions for the application."""
class ConfigurationError(ValueError):
"""Exception raised for configuration errors."""
preamble = None
def get_response_content(self):
"""Returns a formated error message inclduing the preamble and the message."""
message = self.args[0].replace("\n", "\n\t")
return f"{self.preamble}\n\n\t{message}"
class ServiceConfigurationError(ConfigurationError):
"""Exception raised for configuration errors in the service configuration."""
preamble = "Service configuration error, check your .env file."
class CursorConfigurationError(ConfigurationError):
"""Exception raised for configuration errors in Cursor configuration."""
preamble = "Cursor configuration error, check your Cursor settings."
-1
View File
@@ -6,7 +6,6 @@ Flask==3.1.2
# Logging
rich==14.1.0
loguru==0.7.3
# Requests
requests==2.32.5
@@ -0,0 +1,396 @@
{
"model": "gpt-minimal",
"temperature": 0,
"user": "REDACTED",
"messages": [
{
"role": "system",
"content": "REDACTED"
},
{
"role": "user",
"content": "REDACTED"
},
{
"role": "user",
"content": "REDACTED"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"explanation": {
"type": "string",
"description": "REDACTED"
},
"query": {
"type": "string",
"description": "REDACTED"
},
"target_directories": {
"type": "array",
"items": {
"type": "string"
},
"description": "REDACTED"
},
"search_only_prs": {
"type": "boolean",
"description": "REDACTED"
}
},
"required": [
"explanation",
"query",
"target_directories"
]
}
}
},
{
"type": "function",
"function": {
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "REDACTED"
},
"is_background": {
"type": "boolean",
"description": "REDACTED"
},
"explanation": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"command",
"is_background"
]
}
}
},
{
"type": "function",
"function": {
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "REDACTED"
},
"path": {
"type": "string",
"description": "REDACTED"
},
"glob": {
"type": "string",
"description": "REDACTED"
},
"output_mode": {
"type": "string",
"description": "REDACTED",
"enum": [
"content",
"files_with_matches",
"count"
]
},
"-B": {
"type": "number",
"description": "REDACTED"
},
"-A": {
"type": "number",
"description": "REDACTED"
},
"-C": {
"type": "number",
"description": "REDACTED"
},
"-i": {
"type": "boolean",
"description": "REDACTED"
},
"type": {
"type": "string",
"description": "REDACTED"
},
"head_limit": {
"type": "number",
"description": "REDACTED"
},
"multiline": {
"type": "boolean",
"description": "REDACTED"
}
},
"required": [
"pattern"
]
}
}
},
{
"type": "function",
"function": {
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_file": {
"type": "string",
"description": "REDACTED"
},
"explanation": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"target_file"
]
}
}
},
{
"type": "function",
"function": {
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"paths": {
"description": "REDACTED",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_notebook": {
"type": "string",
"description": "REDACTED"
},
"cell_idx": {
"type": "number",
"description": "REDACTED"
},
"is_new_cell": {
"type": "boolean",
"description": "REDACTED"
},
"cell_language": {
"type": "string",
"description": "REDACTED"
},
"old_string": {
"type": "string",
"description": "REDACTED"
},
"new_string": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"target_notebook",
"cell_idx",
"is_new_cell",
"cell_language",
"old_string",
"new_string"
]
}
}
},
{
"type": "function",
"function": {
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"merge": {
"type": "boolean",
"description": "REDACTED"
},
"todos": {
"type": "array",
"description": "REDACTED",
"items": {
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "REDACTED"
},
"status": {
"type": "string",
"enum": [
"pending",
"in_progress",
"completed",
"cancelled"
],
"description": "REDACTED"
},
"id": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"content",
"status",
"id"
]
},
"minItems": 2
}
},
"required": [
"merge",
"todos"
]
}
}
},
{
"type": "function",
"function": {
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_file": {
"type": "string",
"description": "REDACTED"
},
"instructions": {
"type": "string",
"description": "REDACTED"
},
"code_edit": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"target_file",
"instructions",
"code_edit"
]
}
}
},
{
"type": "function",
"function": {
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_file": {
"type": "string",
"description": "REDACTED"
},
"offset": {
"type": "integer",
"description": "REDACTED"
},
"limit": {
"type": "integer",
"description": "REDACTED"
}
},
"required": [
"target_file"
]
}
}
},
{
"type": "function",
"function": {
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_directory": {
"type": "string",
"description": "REDACTED"
},
"ignore_globs": {
"type": "array",
"items": {
"type": "string"
},
"description": "REDACTED"
}
},
"required": [
"target_directory"
]
}
}
},
{
"type": "function",
"function": {
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_directory": {
"type": "string",
"description": "REDACTED"
},
"glob_pattern": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"glob_pattern"
]
}
}
}
],
"tool_choice": "auto",
"stream": true
}
@@ -0,0 +1,10 @@
data: {"id":"chatcmpl-ERO7qkefORSgjuOmbThzZoG8","object":"chat.completion.chunk","created":1758093803,"model":"gpt-minimal","choices":[{"index":0,"delta":{"role":"assistant","content":"<think>\n\n"},"finish_reason":null}]}
data: {"id":"chatcmpl-ERO7qkefORSgjuOmbThzZoG8","object":"chat.completion.chunk","created":1758093804,"model":"gpt-minimal","choices":[{"index":0,"delta":{"role":"assistant","content":"</think>\n\n"},"finish_reason":null}]}
data: {"id":"chatcmpl-ERO7qkefORSgjuOmbThzZoG8","object":"chat.completion.chunk","created":1758093804,"model":"gpt-minimal","choices":[{"index":0,"delta":{"role":"assistant","content":"pong"},"finish_reason":null}]}
data: {"id":"chatcmpl-ERO7qkefORSgjuOmbThzZoG8","object":"chat.completion.chunk","created":1758093806,"model":"gpt-minimal","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
@@ -0,0 +1,400 @@
{
"instructions": "REDACTED",
"input": [
{
"role": "user",
"content": [
{
"type": "input_text",
"text": "REDACTED"
}
]
},
{
"role": "user",
"content": [
{
"type": "input_text",
"text": "REDACTED"
}
]
}
],
"model": "gpt-5",
"tools": [
{
"type": "function",
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"explanation": {
"type": "string",
"description": "REDACTED"
},
"query": {
"type": "string",
"description": "REDACTED"
},
"target_directories": {
"type": "array",
"items": {
"type": "string"
},
"description": "REDACTED"
},
"search_only_prs": {
"type": "boolean",
"description": "REDACTED"
}
},
"required": [
"explanation",
"query",
"target_directories"
]
},
"strict": false
},
{
"type": "function",
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "REDACTED"
},
"is_background": {
"type": "boolean",
"description": "REDACTED"
},
"explanation": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"command",
"is_background"
]
},
"strict": false
},
{
"type": "function",
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "REDACTED"
},
"path": {
"type": "string",
"description": "REDACTED"
},
"glob": {
"type": "string",
"description": "REDACTED"
},
"output_mode": {
"type": "string",
"description": "REDACTED",
"enum": [
"content",
"files_with_matches",
"count"
]
},
"-B": {
"type": "number",
"description": "REDACTED"
},
"-A": {
"type": "number",
"description": "REDACTED"
},
"-C": {
"type": "number",
"description": "REDACTED"
},
"-i": {
"type": "boolean",
"description": "REDACTED"
},
"type": {
"type": "string",
"description": "REDACTED"
},
"head_limit": {
"type": "number",
"description": "REDACTED"
},
"multiline": {
"type": "boolean",
"description": "REDACTED"
}
},
"required": [
"pattern"
]
},
"strict": false
},
{
"type": "function",
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_file": {
"type": "string",
"description": "REDACTED"
},
"explanation": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"target_file"
]
},
"strict": false
},
{
"type": "function",
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"paths": {
"description": "REDACTED",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": []
},
"strict": false
},
{
"type": "function",
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_notebook": {
"type": "string",
"description": "REDACTED"
},
"cell_idx": {
"type": "number",
"description": "REDACTED"
},
"is_new_cell": {
"type": "boolean",
"description": "REDACTED"
},
"cell_language": {
"type": "string",
"description": "REDACTED"
},
"old_string": {
"type": "string",
"description": "REDACTED"
},
"new_string": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"target_notebook",
"cell_idx",
"is_new_cell",
"cell_language",
"old_string",
"new_string"
]
},
"strict": false
},
{
"type": "function",
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"merge": {
"type": "boolean",
"description": "REDACTED"
},
"todos": {
"type": "array",
"description": "REDACTED",
"items": {
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "REDACTED"
},
"status": {
"type": "string",
"enum": [
"pending",
"in_progress",
"completed",
"cancelled"
],
"description": "REDACTED"
},
"id": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"content",
"status",
"id"
]
},
"minItems": 2
}
},
"required": [
"merge",
"todos"
]
},
"strict": false
},
{
"type": "function",
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_file": {
"type": "string",
"description": "REDACTED"
},
"instructions": {
"type": "string",
"description": "REDACTED"
},
"code_edit": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"target_file",
"instructions",
"code_edit"
]
},
"strict": false
},
{
"type": "function",
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_file": {
"type": "string",
"description": "REDACTED"
},
"offset": {
"type": "integer",
"description": "REDACTED"
},
"limit": {
"type": "integer",
"description": "REDACTED"
}
},
"required": [
"target_file"
]
},
"strict": false
},
{
"type": "function",
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_directory": {
"type": "string",
"description": "REDACTED"
},
"ignore_globs": {
"type": "array",
"items": {
"type": "string"
},
"description": "REDACTED"
}
},
"required": [
"target_directory"
]
},
"strict": false
},
{
"type": "function",
"name": "REDACTED",
"description": "REDACTED",
"parameters": {
"type": "object",
"properties": {
"target_directory": {
"type": "string",
"description": "REDACTED"
},
"glob_pattern": {
"type": "string",
"description": "REDACTED"
}
},
"required": [
"glob_pattern"
]
},
"strict": false
}
],
"tool_choice": "auto",
"prompt_cache_key": "REDACTED",
"stream": true,
"reasoning": {
"effort": "minimal",
"summary": "detailed"
},
"store": false,
"stream_options": {
"include_obfuscation": false
},
"truncation": "auto"
}
File diff suppressed because one or more lines are too long
+7 -2
View File
@@ -101,8 +101,13 @@ class ReplyBase:
def assert_upstream_request(self, mock: Any) -> None:
"""Assert upstream request matches the recorded upstream request."""
expected_upstream_request_json = json.loads(self.expected_upstream_request_body)
assert mock.last_request.json() == expected_upstream_request_json
if self.expected_upstream_request_body:
expected_upstream_request_json = json.loads(
self.expected_upstream_request_body
)
assert mock.last_request.json() == expected_upstream_request_json
else:
assert mock.last_request is None
def assert_downstream_response(self, response) -> None:
"""Assert downstream response matches the recorded downstream response."""
+3 -3
View File
@@ -21,7 +21,7 @@ Check "azure_response" for the error details:
\t "foo": "bar"
\t },
\t "request_body": {
\t "instructions": "REDACTE...",
\t "instructions": "REDACTED...",
\t "input": "...redacted 2 input items...",
\t "model": "gpt-5",
\t "tools": "...redacted 11 tools...",
@@ -59,7 +59,7 @@ Check "azure_response" for the error details:
\t "error": "Bad API Key or whatever"
\t },
\t "request_body": {
\t "instructions": "REDACTE...",
\t "instructions": "REDACTED...",
\t "input": "...redacted 2 input items...",
\t "model": "gpt-5",
\t "tools": "...redacted 11 tools...",
@@ -95,7 +95,7 @@ Check "azure_response" for the error details:
\t "azure_status_code": 500,
\t "azure_response": "Internal Server Error",
\t "request_body": {
\t "instructions": "REDACTE...",
\t "instructions": "REDACTED...",
\t "input": "...redacted 2 input items...",
\t "model": "gpt-5",
\t "tools": "...redacted 11 tools...",
+39
View File
@@ -0,0 +1,39 @@
"""Functional tests using WebTest.
See: http://webtest.readthedocs.org/
"""
from .replay_base import ReplyBase
class TestBadSummaryLevel(ReplyBase):
"""Test a single ping-pong interaction, no tool calls."""
expected_upstream_request_body = None
expected_downstream_status_code = 400
expected_downstream_response_body = b"""Service configuration error, check your .env file.
\tAZURE_SUMMARY_LEVEL must be either auto, detailed, or concise.
\t
\tGot: foo"""
def modify_settings(self, app) -> None:
"""Set invalid summary level in settings."""
app.config["AZURE_SUMMARY_LEVEL"] = "foo"
class TestBadModelName(ReplyBase):
"""Test a single ping-pong interaction, no tool calls."""
expected_upstream_request_body = None
expected_downstream_status_code = 400
expected_downstream_response_body = b"""Cursor configuration error, check your Cursor settings.
\tModel name must be either gpt-high, gpt-medium, gpt-low, or gpt-minimal.
\t
\tGot: foo-minimal"""
@property
def downstream_request_body(self) -> str:
"""Set invalid model name in request body."""
return super().downstream_request_body.replace("gpt-", "foo-")
+12
View File
@@ -0,0 +1,12 @@
"""Functional tests using WebTest.
See: http://webtest.readthedocs.org/
"""
from .replay_base import ReplyBase
class TestSSEWithoutClosingNewLines(ReplyBase):
"""Test the replay of an SSE response without closing new lines."""
recording = "sse_without_closing_new_lines"
+5 -4
View File
@@ -15,11 +15,12 @@ class TestModelInvalidJson(ReplyBase):
def test(self, testapp, requests_mock, mocker):
"""Test logging of context containing an invalid JSON tool call."""
console_print = mocker.patch(
"rich.console.Console.print", return_value=mocker.Mock()
)
mocker.patch("rich.console.Console.print")
console_print = mocker.patch("rich.padding.Padding.__init__", return_value=None)
super().test(testapp, requests_mock)
console_print.assert_any_call("[red]Invalid JSON generated by the model:[/red]")
console_print.assert_any_call(
"[red]Invalid JSON generated by the model:[/red]", (0, 4)
)
def test_redact_headers():
+3 -3
View File
@@ -8,11 +8,11 @@ class TestModels:
"""Models."""
def test_models_endpoint_returns_400(self, testapp):
"""Ensure /models endpoint returns HTTP 400."""
"""Ensure /models endpoint returns HTTP 400 wihtout auth."""
testapp.get("/models", status=400)
def test_models_endpoint_returns_200(self, testapp):
"""Ensure /models endpoint returns HTTP 400."""
"""Ensure /models endpoint returns HTTP 400 with auth."""
response = testapp.get(
"/models",
status=200,
@@ -25,5 +25,5 @@ class TestModels:
assert '"gpt-minimal"' in content
def test_health_endpoint_returns_200(self, testapp):
"""Ensure /health endpoint returns HTTP 200."""
"""Ensure /health endpoint returns HTTP 200 without auth."""
testapp.get("/health", status=200)
+3
View File
@@ -71,6 +71,9 @@ class TestRecording(ReplyBase):
# Unrelated folder
os.makedirs(os.path.join(tmp_path, "foo"))
# Smaller index than last recording index
os.makedirs(os.path.join(tmp_path, "-10"))
super().test(testapp, requests_mock)
assert os.path.exists(os.path.join(tmp_path, "124"))