From e5aa58c53861c65ecbdfdd7c049f05f663a29ae9 Mon Sep 17 00:00:00 2001 From: xiaominghao Date: Thu, 21 May 2026 19:33:20 +0800 Subject: [PATCH] fix(api): reject blank retain content --- hindsight-api-slim/hindsight_api/api/http.py | 7 ++++++ .../engine/providers/openai_compatible_llm.py | 22 +++++++++++-------- .../tests/test_none_llm_provider.py | 15 +++++++++++++ .../references/developer/models.md | 1 + skills/hindsight-docs/references/faq.md | 1 + 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/hindsight-api-slim/hindsight_api/api/http.py b/hindsight-api-slim/hindsight_api/api/http.py index c357f4116..e931da08c 100644 --- a/hindsight-api-slim/hindsight_api/api/http.py +++ b/hindsight-api-slim/hindsight_api/api/http.py @@ -466,6 +466,13 @@ class MemoryItem(BaseModel): description="Optional tags for visibility scoping. Memories with tags can be filtered during recall.", ) + @field_validator("content") + @classmethod + def validate_content(cls, v: str) -> str: + if not v.strip(): + raise ValueError("content cannot be empty") + return v + @field_validator("tags", mode="before") @classmethod def coerce_tags(cls, v): diff --git a/hindsight-api-slim/hindsight_api/engine/providers/openai_compatible_llm.py b/hindsight-api-slim/hindsight_api/engine/providers/openai_compatible_llm.py index 9bb6fdba0..0f02949d5 100644 --- a/hindsight-api-slim/hindsight_api/engine/providers/openai_compatible_llm.py +++ b/hindsight-api-slim/hindsight_api/engine/providers/openai_compatible_llm.py @@ -306,15 +306,19 @@ def __init__( self.api_key = "local" # Validate API key for cloud providers - if self.provider in ( - "openai", - "groq", - "minimax", - "deepseek", - "openrouter", - "zai", - "opencode-go", - ) and not self.api_key: + if ( + self.provider + in ( + "openai", + "groq", + "minimax", + "deepseek", + "openrouter", + "zai", + "opencode-go", + ) + and not self.api_key + ): raise ValueError(f"API key is required for {self.provider}") # Service tier configuration (from config, not env vars) diff --git a/hindsight-api-slim/tests/test_none_llm_provider.py b/hindsight-api-slim/tests/test_none_llm_provider.py index 3122348f4..034868c7b 100644 --- a/hindsight-api-slim/tests/test_none_llm_provider.py +++ b/hindsight-api-slim/tests/test_none_llm_provider.py @@ -248,6 +248,21 @@ async def test_http_retain_works(none_api_client): assert response.status_code == 200 +@pytest.mark.asyncio +@pytest.mark.parametrize("content", ["", " \n\t"]) +async def test_http_retain_rejects_blank_content(none_api_client, content): + """Retain endpoint should reject empty or whitespace-only content.""" + bank_id = f"test_none_http_retain_blank_{datetime.now(timezone.utc).timestamp()}" + + response = await none_api_client.post( + f"/v1/default/banks/{bank_id}/memories", + json={"items": [{"content": content, "context": "test"}]}, + ) + + assert response.status_code == 422 + assert "content cannot be empty" in str(response.json()["detail"]) + + @pytest.mark.asyncio async def test_http_recall_works(none_api_client): """Recall endpoint should work with provider=none.""" diff --git a/skills/hindsight-docs/references/developer/models.md b/skills/hindsight-docs/references/developer/models.md index b50e625a4..51278726a 100644 --- a/skills/hindsight-docs/references/developer/models.md +++ b/skills/hindsight-docs/references/developer/models.md @@ -30,6 +30,7 @@ Used for fact extraction, entity resolution, mental model consolidation, and ans - MiniMax - DeepSeek - z.ai +- opencode-go - Volcano Engine - OpenRouter - OpenAI Codex diff --git a/skills/hindsight-docs/references/faq.md b/skills/hindsight-docs/references/faq.md index 8b8b96a62..f4f8f2aca 100644 --- a/skills/hindsight-docs/references/faq.md +++ b/skills/hindsight-docs/references/faq.md @@ -76,6 +76,7 @@ Browse all supported integrations in the Integrations Hub. - MiniMax - DeepSeek - z.ai +- opencode-go - Volcano Engine - OpenRouter - OpenAI Codex