diff --git a/routstr/core/settings.py b/routstr/core/settings.py index 685f8970..7507a48e 100644 --- a/routstr/core/settings.py +++ b/routstr/core/settings.py @@ -6,7 +6,7 @@ import os from datetime import datetime, timezone from typing import Any -from pydantic.v1 import BaseModel, BaseSettings, Field, validator +from pydantic.v1 import BaseModel, BaseSettings, Field from sqlmodel.ext.asyncio.session import AsyncSession @@ -37,13 +37,6 @@ class Settings(BaseSettings): # Cashu cashu_mints: list[str] = Field(default_factory=list, env="CASHU_MINTS") - - @validator("cashu_mints", pre=True, each_item=True) - def normalize_mint_url(cls, v: str) -> str: - if isinstance(v, str): - return v.rstrip("/") - return v - receive_ln_address: str = Field(default="", env="RECEIVE_LN_ADDRESS") primary_mint: str = Field(default="", env="PRIMARY_MINT_URL") primary_mint_unit: str = Field(default="sat", env="PRIMARY_MINT_UNIT") diff --git a/routstr/proxy.py b/routstr/proxy.py index 1d5aaa96..58ef12d4 100644 --- a/routstr/proxy.py +++ b/routstr/proxy.py @@ -16,7 +16,6 @@ from .core.db import ( create_session, get_session, ) -from .core.settings import settings from .payment.helpers import ( calculate_discounted_max_cost, check_token_balance, @@ -26,7 +25,6 @@ from .payment.helpers import ( from .payment.models import Model from .upstream import BaseUpstreamProvider from .upstream.helpers import init_upstreams -from .wallet import deserialize_token_from_string logger = get_logger(__name__) proxy_router = APIRouter() @@ -148,24 +146,6 @@ async def proxy( else: model_id = request_body_dict.get("model", "unknown") - if "https://testnut.cashu.space" in settings.cashu_mints: - try: - token_str = None - if x_cashu_header := headers.get("x-cashu"): - token_str = x_cashu_header - elif auth_header := headers.get("authorization"): - parts = auth_header.split(" ") - if len(parts) > 1 and not parts[1].startswith("sk-"): - token_str = parts[1] - - if token_str: - token_obj = deserialize_token_from_string(token_str) - if token_obj.mint == "https://testnut.cashu.space": - model_id = "mock/gpt-420-mock" - request_body_dict["model"] = model_id - except Exception: - pass - model_obj = get_model_instance(model_id) if not model_obj: return create_error_response( diff --git a/routstr/upstream/fake.py b/routstr/upstream/fake.py deleted file mode 100644 index cfb530d1..00000000 --- a/routstr/upstream/fake.py +++ /dev/null @@ -1,265 +0,0 @@ -import asyncio -import json -import random -from typing import AsyncIterator - -from fastapi import Request -from fastapi.responses import Response, StreamingResponse - -from ..core.db import ApiKey, AsyncSession -from ..payment.models import Architecture, Model, Pricing -from .base import BaseUpstreamProvider - - -class MockUpstreamProvider(BaseUpstreamProvider): - """Fack Mock Upstream provider specifically for Testing.""" - - provider_type = "mock" - - async def forward_request( - self, - request: Request, - path: str, - headers: dict, - request_body: bytes | None, - key: ApiKey, - max_cost_for_model: int, - session: AsyncSession, - model_obj: Model, - ) -> Response | StreamingResponse: - if path.endswith("chat/completions"): - is_streaming = False - if request_body: - request_data = json.loads(request_body) - is_streaming = request_data.get("stream", False) - - if is_streaming: - - async def fake_streaming_response( - chunk_size: int | None = None, - ) -> AsyncIterator[bytes]: - suffix = random.randint(1000, 9999) - req_id = f"gen-mock-stream-{suffix}" - created = 1766138895 - model = "mock/gpt-420-mock" - - def make_chunk( - delta: dict, - finish_reason: str | None = None, - usage: dict | None = None, - ) -> bytes: - chunk = { - "id": req_id, - "provider": "MockProvider", - "model": model, - "object": "chat.completion.chunk", - "created": created, - "choices": [ - { - "index": 0, - "delta": delta, - "finish_reason": finish_reason, - "native_finish_reason": "completed" - if finish_reason - else None, - "logprobs": None, - } - ], - } - if usage: - chunk["usage"] = usage - return f"data: {json.dumps(chunk)}\n\n".encode() - - # 1. Initial chunk - yield make_chunk({"role": "assistant", "content": ""}) - await asyncio.sleep(0.02) - - # 2. Reasoning chunks - reasoning_tokens = ["Mock", " reason", "ing", "..."] - for token in reasoning_tokens: - delta = { - "role": "assistant", - "content": "", - "reasoning": token, - "reasoning_details": [ - { - "type": "reasoning.summary", - "summary": token, - "format": "openai-responses-v1", - "index": 0, - } - ], - } - yield make_chunk(delta) - await asyncio.sleep(0.03) - - # 3. Content chunks - content_tokens = ["This", " is", " a", " mock", " stream", "."] - for token in content_tokens: - yield make_chunk({"role": "assistant", "content": token}) - await asyncio.sleep(0.03) - - # 4. Finish chunk - yield make_chunk( - {"role": "assistant", "content": ""}, finish_reason="stop" - ) - - # 5. Usage chunk - usage_data = { - "prompt_tokens": 10, - "completion_tokens": 20, - "total_tokens": 30, - "cost": 0.001, - "is_byok": False, - "prompt_tokens_details": { - "cached_tokens": 0, - "audio_tokens": 0, - "video_tokens": 0, - }, - "cost_details": { - "upstream_inference_cost": None, - "upstream_inference_prompt_cost": 0, - "upstream_inference_completions_cost": 0.001, - }, - "completion_tokens_details": { - "reasoning_tokens": 10, - "image_tokens": 0, - }, - } - - usage_chunk = { - "id": req_id, - "provider": "MockProvider", - "model": model, - "object": "chat.completion.chunk", - "created": created, - "choices": [ - { - "index": 0, - "delta": {"role": "assistant", "content": ""}, - "finish_reason": None, - "native_finish_reason": None, - "logprobs": None, - } - ], - "usage": usage_data, - } - yield f"data: {json.dumps(usage_chunk)}\n\n".encode() - - # 6. DONE - yield b"data: [DONE]\n\n" - - # 7. Cost - cost_chunk = { - "cost": { - "base_msats": 0, - "input_msats": 2, - "output_msats": 10, - "total_msats": 12, - } - } - yield f"data: {json.dumps(cost_chunk)}\n\n".encode() - - return StreamingResponse( - fake_streaming_response(), - 200, - ) - - else: - suffix = random.randint(1000, 9999) - content_dict = { - "id": f"gen-mock-{suffix}", - "provider": "MockProvider", - "model": "mock/gpt-5-mini", - "object": "chat.completion", - "created": 1766138655, - "choices": [ - { - "logprobs": None, - "finish_reason": "length", - "native_finish_reason": "max_output_tokens", - "index": 0, - "message": { - "role": "assistant", - "content": f"Mock Content {suffix}", - "refusal": None, - "reasoning": f"Mock Reasoning {suffix}", - "reasoning_details": [ - { - "format": "openai-responses-v1", - "index": 0, - "type": "reasoning.summary", - "summary": f"Mock Summary {suffix}", - }, - { - "id": f"rs_mock_{suffix}", - "format": "openai-responses-v1", - "index": 0, - "type": "reasoning.encrypted", - "data": "mock_encrypted_data", - }, - ], - }, - } - ], - "usage": { - "prompt_tokens": 10, - "completion_tokens": 10, - "total_tokens": 20, - "cost": 0, - "is_byok": False, - "prompt_tokens_details": { - "cached_tokens": 0, - "audio_tokens": 0, - "video_tokens": 0, - }, - "cost_details": { - "upstream_inference_cost": None, - "upstream_inference_prompt_cost": 0, - "upstream_inference_completions_cost": 0, - }, - "completion_tokens_details": { - "reasoning_tokens": 5, - "image_tokens": 0, - }, - }, - "cost": { - "base_msats": 0, - "input_msats": 0, - "output_msats": 0, - "total_msats": 0, - }, - } - return Response(json.dumps(content_dict).encode(), 200) - - elif path.endswith("embeddings"): - raise NotImplementedError - elif path.endswith("responses"): - raise NotImplementedError - else: - raise NotImplementedError - - async def fetch_models(self) -> list[Model]: - return [ - Model( - id="mock/gpt-420-mock", - name="mock/gpt-420-mock", - created=0, - description="mock model for testing", - context_length=8192, - architecture=Architecture( - modality="text", - input_modalities=["text"], - output_modalities=["text"], - tokenizer="", - instruct_type=None, - ), - pricing=Pricing(prompt=0.01, completion=0.01), - ), - ] - - def transform_model_name(self, model_id: str) -> str: - return "fake-model" - - async def get_balance(self) -> float | None: - return 420.69 diff --git a/routstr/upstream/helpers.py b/routstr/upstream/helpers.py index af333522..c2417d40 100644 --- a/routstr/upstream/helpers.py +++ b/routstr/upstream/helpers.py @@ -218,14 +218,6 @@ async def init_upstreams() -> list[BaseUpstreamProvider]: results = await asyncio.gather(*tasks) upstreams = [p for p in results if p is not None] - if "https://testnut.cashu.space" in settings.cashu_mints: - from .fake import MockUpstreamProvider - - mock_provider = MockUpstreamProvider("mock", "mock") - await mock_provider.refresh_models_cache() - upstreams.append(mock_provider) - logger.info("Initialized MockUpstreamProvider for testnut mint") - return upstreams diff --git a/routstr/wallet.py b/routstr/wallet.py index 7e8981b2..ce87b560 100644 --- a/routstr/wallet.py +++ b/routstr/wallet.py @@ -313,8 +313,6 @@ async def periodic_payout() -> None: try: async with db.create_session() as session: for mint_url in settings.cashu_mints: - if mint_url == "https://testnut.cashu.space": - continue for unit in ["sat", "msat"]: wallet = await get_wallet(mint_url, unit) proofs = get_proofs_per_mint_and_unit(