feat: add detail parameter to list/get mental models (#846)

* feat: add detail parameter to list/get mental models (#825)

Add a `detail` query parameter (metadata|content|full) to both list and get
mental model endpoints (HTTP + MCP) to control response size. This reduces
payload for agent boot flows and MCP clients where context budget is limited.

Closes #825

* fix: update Rust CLI for optional mental model fields

The generated Rust client now has content/source_query as Option<String>
after the detail parameter was added. Update CLI code to handle optionals.
This commit is contained in:
Nicolò Boschi 2026-04-02 11:52:45 +02:00 committed by GitHub
parent 7d6c570a3a
commit 8d1bfbbd2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 594 additions and 154 deletions

View file

@ -1534,13 +1534,14 @@ class MentalModelResponse(BaseModel):
id: str id: str
bank_id: str bank_id: str
name: str name: str
source_query: str source_query: str | None = None
content: str = Field( content: str | None = Field(
description="The mental model content as well-formatted markdown (auto-generated from reflect endpoint)" default=None,
description="The mental model content as well-formatted markdown (auto-generated from reflect endpoint)",
) )
tags: list[str] = FieldWithDefault(list) tags: list[str] = FieldWithDefault(list)
max_tokens: int = Field(default=2048) max_tokens: int | None = Field(default=None)
trigger: MentalModelTrigger = FieldWithDefault(MentalModelTrigger) trigger: MentalModelTrigger | None = Field(default=None)
last_refreshed_at: str | None = None last_refreshed_at: str | None = None
created_at: str | None = None created_at: str | None = None
reflect_response: dict | None = Field( reflect_response: dict | None = Field(
@ -3041,6 +3042,10 @@ def _register_routes(app: FastAPI):
bank_id: str, bank_id: str,
tags_filter: list[str] | None = Query(None, alias="tags", description="Filter by tags"), tags_filter: list[str] | None = Query(None, alias="tags", description="Filter by tags"),
tags_match: Literal["any", "all", "exact"] = Query("any", description="How to match tags"), tags_match: Literal["any", "all", "exact"] = Query("any", description="How to match tags"),
detail: Literal["metadata", "content", "full"] = Query(
"full",
description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)",
),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
offset: int = Query(0, ge=0), offset: int = Query(0, ge=0),
request_context: RequestContext = Depends(get_request_context), request_context: RequestContext = Depends(get_request_context),
@ -3051,6 +3056,7 @@ def _register_routes(app: FastAPI):
bank_id=bank_id, bank_id=bank_id,
tags=tags_filter, tags=tags_filter,
tags_match=tags_match, tags_match=tags_match,
detail=detail,
limit=limit, limit=limit,
offset=offset, offset=offset,
request_context=request_context, request_context=request_context,
@ -3078,6 +3084,10 @@ def _register_routes(app: FastAPI):
async def api_get_mental_model( async def api_get_mental_model(
bank_id: str, bank_id: str,
mental_model_id: str, mental_model_id: str,
detail: Literal["metadata", "content", "full"] = Query(
"full",
description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)",
),
request_context: RequestContext = Depends(get_request_context), request_context: RequestContext = Depends(get_request_context),
): ):
"""Get a mental model by ID.""" """Get a mental model by ID."""
@ -3085,6 +3095,7 @@ def _register_routes(app: FastAPI):
mental_model = await app.state.memory.get_mental_model( mental_model = await app.state.memory.get_mental_model(
bank_id=bank_id, bank_id=bank_id,
mental_model_id=mental_model_id, mental_model_id=mental_model_id,
detail=detail,
request_context=request_context, request_context=request_context,
) )
if mental_model is None: if mental_model is None:

View file

@ -6351,6 +6351,7 @@ class MemoryEngine(MemoryEngineInterface):
*, *,
tags: list[str] | None = None, tags: list[str] | None = None,
tags_match: str = "any", tags_match: str = "any",
detail: str = "full",
limit: int = 100, limit: int = 100,
offset: int = 0, offset: int = 0,
request_context: "RequestContext", request_context: "RequestContext",
@ -6361,6 +6362,7 @@ class MemoryEngine(MemoryEngineInterface):
bank_id: Bank identifier bank_id: Bank identifier
tags: Optional tags to filter by tags: Optional tags to filter by
tags_match: How to match tags - 'any', 'all', or 'exact' tags_match: How to match tags - 'any', 'all', or 'exact'
detail: Detail level - 'metadata', 'content', or 'full'
limit: Maximum number of results limit: Maximum number of results
offset: Offset for pagination offset: Offset for pagination
request_context: Request context for authentication request_context: Request context for authentication
@ -6402,13 +6404,14 @@ class MemoryEngine(MemoryEngineInterface):
*params, *params,
) )
return [self._row_to_mental_model(row) for row in rows] return [self._row_to_mental_model(row, detail=detail) for row in rows]
async def get_mental_model( async def get_mental_model(
self, self,
bank_id: str, bank_id: str,
mental_model_id: str, mental_model_id: str,
*, *,
detail: str = "full",
request_context: "RequestContext", request_context: "RequestContext",
) -> dict[str, Any] | None: ) -> dict[str, Any] | None:
"""Get a single pinned mental model by ID. """Get a single pinned mental model by ID.
@ -6416,6 +6419,7 @@ class MemoryEngine(MemoryEngineInterface):
Args: Args:
bank_id: Bank identifier bank_id: Bank identifier
mental_model_id: Pinned mental model UUID mental_model_id: Pinned mental model UUID
detail: Detail level - 'metadata', 'content', or 'full'
request_context: Request context for authentication request_context: Request context for authentication
Returns: Returns:
@ -6449,7 +6453,7 @@ class MemoryEngine(MemoryEngineInterface):
mental_model_id, mental_model_id,
) )
result = self._row_to_mental_model(row) if row else None result = self._row_to_mental_model(row, detail=detail) if row else None
# Post-operation hook (usage recording) # Post-operation hook (usage recording)
if result and self._operation_validator: if result and self._operation_validator:
@ -6847,34 +6851,47 @@ class MemoryEngine(MemoryEngineInterface):
return result == "DELETE 1" return result == "DELETE 1"
def _row_to_mental_model(self, row) -> dict[str, Any]: _MENTAL_MODEL_METADATA_FIELDS = frozenset({"id", "bank_id", "name", "tags", "last_refreshed_at", "created_at"})
"""Convert a database row to a mental model dict."""
reflect_response = row.get("reflect_response") def _row_to_mental_model(self, row, *, detail: str = "full") -> dict[str, Any]:
# Parse JSON string to dict if needed (asyncpg may return JSONB as string) """Convert a database row to a mental model dict.
if isinstance(reflect_response, str):
try: Args:
reflect_response = json.loads(reflect_response) row: Database row
except json.JSONDecodeError: detail: Detail level - 'metadata', 'content', or 'full'
reflect_response = None """
result: dict[str, Any] = {
"id": str(row["id"]),
"bank_id": row["bank_id"],
"name": row["name"],
"tags": row["tags"] or [],
"last_refreshed_at": row["last_refreshed_at"].isoformat() if row["last_refreshed_at"] else None,
"created_at": row["created_at"].isoformat() if row["created_at"] else None,
}
if detail == "metadata":
return result
trigger = row.get("trigger") trigger = row.get("trigger")
if isinstance(trigger, str): if isinstance(trigger, str):
try: try:
trigger = json.loads(trigger) trigger = json.loads(trigger)
except json.JSONDecodeError: except json.JSONDecodeError:
trigger = None trigger = None
return { result["source_query"] = row["source_query"]
"id": str(row["id"]), result["content"] = row["content"]
"bank_id": row["bank_id"], result["max_tokens"] = row.get("max_tokens")
"name": row["name"], result["trigger"] = trigger
"source_query": row["source_query"],
"content": row["content"], if detail == "full":
"tags": row["tags"] or [], reflect_response = row.get("reflect_response")
"max_tokens": row.get("max_tokens"), if isinstance(reflect_response, str):
"trigger": trigger, try:
"last_refreshed_at": row["last_refreshed_at"].isoformat() if row["last_refreshed_at"] else None, reflect_response = json.loads(reflect_response)
"created_at": row["created_at"].isoformat() if row["created_at"] else None, except json.JSONDecodeError:
"reflect_response": reflect_response, reflect_response = None
} result["reflect_response"] = reflect_response
return result
# ========================================================================= # =========================================================================
# Directives - Hard rules injected into prompts # Directives - Hard rules injected into prompts

View file

@ -1002,6 +1002,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
@mcp.tool() @mcp.tool()
async def list_mental_models( async def list_mental_models(
tags: list[str] | None = None, tags: list[str] | None = None,
detail: str = "full",
bank_id: str | None = None, bank_id: str | None = None,
) -> str: ) -> str:
""" """
@ -1013,6 +1014,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
Args: Args:
tags: Optional tags to filter by (returns models matching any tag) tags: Optional tags to filter by (returns models matching any tag)
detail: Detail level - 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response). Default: 'full'
bank_id: Optional bank to list from (defaults to session bank). Use for cross-bank operations. bank_id: Optional bank to list from (defaults to session bank). Use for cross-bank operations.
""" """
try: try:
@ -1023,6 +1025,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
models = await memory.list_mental_models( models = await memory.list_mental_models(
bank_id=target_bank, bank_id=target_bank,
tags=tags, tags=tags,
detail=detail,
request_context=_get_request_context(config), request_context=_get_request_context(config),
) )
return json.dumps({"items": models}, indent=2, default=str) return json.dumps({"items": models}, indent=2, default=str)
@ -1038,6 +1041,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
@mcp.tool() @mcp.tool()
async def list_mental_models( async def list_mental_models(
tags: list[str] | None = None, tags: list[str] | None = None,
detail: str = "full",
) -> dict: ) -> dict:
""" """
List mental models (pinned reflections) for this memory bank. List mental models (pinned reflections) for this memory bank.
@ -1048,6 +1052,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
Args: Args:
tags: Optional tags to filter by (returns models matching any tag) tags: Optional tags to filter by (returns models matching any tag)
detail: Detail level - 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response). Default: 'full'
""" """
try: try:
target_bank = config.bank_id_resolver() target_bank = config.bank_id_resolver()
@ -1057,6 +1062,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
models = await memory.list_mental_models( models = await memory.list_mental_models(
bank_id=target_bank, bank_id=target_bank,
tags=tags, tags=tags,
detail=detail,
request_context=_get_request_context(config), request_context=_get_request_context(config),
) )
return {"items": models} return {"items": models}
@ -1076,16 +1082,18 @@ def _register_get_mental_model(mcp: FastMCP, memory: MemoryEngine, config: MCPTo
@mcp.tool() @mcp.tool()
async def get_mental_model( async def get_mental_model(
mental_model_id: str, mental_model_id: str,
detail: str = "full",
bank_id: str | None = None, bank_id: str | None = None,
) -> str: ) -> str:
""" """
Get a specific mental model by ID. Get a specific mental model by ID.
Returns the full mental model including its generated content, source query, Returns the mental model with the requested detail level. Use list_mental_models
and metadata. Use list_mental_models first to discover available model IDs. first to discover available model IDs.
Args: Args:
mental_model_id: The ID of the mental model to retrieve mental_model_id: The ID of the mental model to retrieve
detail: Detail level - 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response). Default: 'full'
bank_id: Optional bank (defaults to session bank). Use for cross-bank operations. bank_id: Optional bank (defaults to session bank). Use for cross-bank operations.
""" """
try: try:
@ -1096,6 +1104,7 @@ def _register_get_mental_model(mcp: FastMCP, memory: MemoryEngine, config: MCPTo
model = await memory.get_mental_model( model = await memory.get_mental_model(
bank_id=target_bank, bank_id=target_bank,
mental_model_id=mental_model_id, mental_model_id=mental_model_id,
detail=detail,
request_context=_get_request_context(config), request_context=_get_request_context(config),
) )
if model is None: if model is None:
@ -1113,15 +1122,17 @@ def _register_get_mental_model(mcp: FastMCP, memory: MemoryEngine, config: MCPTo
@mcp.tool() @mcp.tool()
async def get_mental_model( async def get_mental_model(
mental_model_id: str, mental_model_id: str,
detail: str = "full",
) -> dict: ) -> dict:
""" """
Get a specific mental model by ID. Get a specific mental model by ID.
Returns the full mental model including its generated content, source query, Returns the mental model with the requested detail level. Use list_mental_models
and metadata. Use list_mental_models first to discover available model IDs. first to discover available model IDs.
Args: Args:
mental_model_id: The ID of the mental model to retrieve mental_model_id: The ID of the mental model to retrieve
detail: Detail level - 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response). Default: 'full'
""" """
try: try:
target_bank = config.bank_id_resolver() target_bank = config.bank_id_resolver()
@ -1131,6 +1142,7 @@ def _register_get_mental_model(mcp: FastMCP, memory: MemoryEngine, config: MCPTo
model = await memory.get_mental_model( model = await memory.get_mental_model(
bank_id=target_bank, bank_id=target_bank,
mental_model_id=mental_model_id, mental_model_id=mental_model_id,
detail=detail,
request_context=_get_request_context(config), request_context=_get_request_context(config),
) )
if model is None: if model is None:

View file

@ -1,5 +1,6 @@
"""Tests for the shared MCP tools module.""" """Tests for the shared MCP tools module."""
import json
from datetime import datetime, timezone from datetime import datetime, timezone
from unittest.mock import AsyncMock, MagicMock from unittest.mock import AsyncMock, MagicMock
@ -75,25 +76,63 @@ class TestBuildContentDict:
# ========================================================================= # =========================================================================
_MENTAL_MODEL_METADATA_FIELDS = frozenset({"id", "bank_id", "name", "tags", "last_refreshed_at", "created_at"})
_FULL_MENTAL_MODELS = [
{
"id": "mm-1",
"bank_id": "test-bank",
"name": "Coding Prefs",
"source_query": "coding preferences?",
"content": "Prefers Python",
"tags": ["coding"],
"max_tokens": 2048,
"trigger": {"interval": "daily"},
"last_refreshed_at": "2026-01-01T00:00:00",
"created_at": "2026-01-01T00:00:00",
"reflect_response": {"text": "Prefers Python", "based_on": {"world_facts": [{"id": "f1", "text": "Python is popular"}]}},
},
{
"id": "mm-2",
"bank_id": "test-bank",
"name": "Goals",
"source_query": "current goals?",
"content": "Ship v2",
"tags": [],
"max_tokens": 2048,
"trigger": None,
"last_refreshed_at": "2026-01-01T00:00:00",
"created_at": "2026-01-01T00:00:00",
"reflect_response": {"text": "Ship v2", "based_on": {}},
},
]
def _apply_detail(model: dict, detail: str) -> dict:
"""Simulate engine detail filtering for mocks."""
if detail == "metadata":
return {k: v for k, v in model.items() if k in _MENTAL_MODEL_METADATA_FIELDS}
if detail == "content":
return {k: v for k, v in model.items() if k != "reflect_response"}
return model
@pytest.fixture @pytest.fixture
def mock_memory(): def mock_memory():
"""Create a mock MemoryEngine with all MCP tool methods.""" """Create a mock MemoryEngine with all MCP tool methods."""
memory = MagicMock() memory = MagicMock()
# Mental model methods
memory.list_mental_models = AsyncMock( # Mental model methods — simulate engine detail filtering
return_value=[ async def _list_mental_models(**kwargs):
{"id": "mm-1", "name": "Coding Prefs", "source_query": "coding preferences?", "content": "Prefers Python"}, detail = kwargs.get("detail", "full")
{"id": "mm-2", "name": "Goals", "source_query": "current goals?", "content": "Ship v2"}, return [_apply_detail(m, detail) for m in _FULL_MENTAL_MODELS]
]
) async def _get_mental_model(**kwargs):
memory.get_mental_model = AsyncMock( detail = kwargs.get("detail", "full")
return_value={ return _apply_detail(_FULL_MENTAL_MODELS[0], detail)
"id": "mm-1",
"name": "Coding Prefs", memory.list_mental_models = AsyncMock(side_effect=_list_mental_models)
"source_query": "coding preferences?", memory.get_mental_model = AsyncMock(side_effect=_get_mental_model)
"content": "Prefers Python",
}
)
memory.create_mental_model = AsyncMock(return_value={"id": "mm-new"}) memory.create_mental_model = AsyncMock(return_value={"id": "mm-new"})
memory.submit_async_refresh_mental_model = AsyncMock(return_value={"operation_id": "op-123"}) memory.submit_async_refresh_mental_model = AsyncMock(return_value={"operation_id": "op-123"})
memory.update_mental_model = AsyncMock( memory.update_mental_model = AsyncMock(
@ -390,12 +429,12 @@ class TestGetMentalModel:
assert mock_memory.get_mental_model.call_args.kwargs["bank_id"] == "other-bank" assert mock_memory.get_mental_model.call_args.kwargs["bank_id"] == "other-bank"
async def test_get_not_found_multi_bank(self, mcp_server_with_mental_models, mock_memory): async def test_get_not_found_multi_bank(self, mcp_server_with_mental_models, mock_memory):
mock_memory.get_mental_model.return_value = None mock_memory.get_mental_model.side_effect = AsyncMock(return_value=None)
result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(mental_model_id="missing") result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(mental_model_id="missing")
assert "not found" in result assert "not found" in result
async def test_get_not_found_single_bank(self, mcp_server_single_bank, mock_memory): async def test_get_not_found_single_bank(self, mcp_server_single_bank, mock_memory):
mock_memory.get_mental_model.return_value = None mock_memory.get_mental_model.side_effect = AsyncMock(return_value=None)
result = await _tools(mcp_server_single_bank)["get_mental_model"].fn(mental_model_id="missing") result = await _tools(mcp_server_single_bank)["get_mental_model"].fn(mental_model_id="missing")
assert isinstance(result, dict) assert isinstance(result, dict)
assert "not found" in result["error"] assert "not found" in result["error"]
@ -415,6 +454,97 @@ class TestGetMentalModel:
assert "error" in result assert "error" in result
@pytest.mark.asyncio
class TestListMentalModelsDetail:
"""Test the detail parameter for list_mental_models."""
async def test_list_detail_full_includes_reflect_response(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["list_mental_models"].fn(detail="full")
parsed = json.loads(result)
item = parsed["items"][0]
assert "reflect_response" in item
assert "content" in item
assert "source_query" in item
async def test_list_detail_content_excludes_reflect_response(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["list_mental_models"].fn(detail="content")
parsed = json.loads(result)
item = parsed["items"][0]
assert "reflect_response" not in item
assert "content" in item
assert "source_query" in item
assert "trigger" in item
async def test_list_detail_metadata_only_has_core_fields(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["list_mental_models"].fn(detail="metadata")
parsed = json.loads(result)
item = parsed["items"][0]
assert item["id"] == "mm-1"
assert item["name"] == "Coding Prefs"
assert "tags" in item
assert "content" not in item
assert "source_query" not in item
assert "reflect_response" not in item
assert "trigger" not in item
async def test_list_detail_default_is_full(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["list_mental_models"].fn()
parsed = json.loads(result)
item = parsed["items"][0]
assert "reflect_response" in item
async def test_list_detail_single_bank_metadata(self, mcp_server_single_bank, mock_memory):
result = await _tools(mcp_server_single_bank)["list_mental_models"].fn(detail="metadata")
assert isinstance(result, dict)
item = result["items"][0]
assert "id" in item
assert "name" in item
assert "content" not in item
assert "reflect_response" not in item
@pytest.mark.asyncio
class TestGetMentalModelDetail:
"""Test the detail parameter for get_mental_model."""
async def test_get_detail_full_includes_reflect_response(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(
mental_model_id="mm-1", detail="full"
)
parsed = json.loads(result)
assert "reflect_response" in parsed
assert "content" in parsed
async def test_get_detail_content_excludes_reflect_response(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(
mental_model_id="mm-1", detail="content"
)
parsed = json.loads(result)
assert "reflect_response" not in parsed
assert "content" in parsed
assert "source_query" in parsed
async def test_get_detail_metadata_only_has_core_fields(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(
mental_model_id="mm-1", detail="metadata"
)
parsed = json.loads(result)
assert parsed["id"] == "mm-1"
assert parsed["name"] == "Coding Prefs"
assert "tags" in parsed
assert "content" not in parsed
assert "reflect_response" not in parsed
assert "trigger" not in parsed
async def test_get_detail_single_bank_content(self, mcp_server_single_bank, mock_memory):
result = await _tools(mcp_server_single_bank)["get_mental_model"].fn(
mental_model_id="mm-1", detail="content"
)
assert isinstance(result, dict)
assert "content" in result
assert "reflect_response" not in result
@pytest.mark.asyncio @pytest.mark.asyncio
class TestCreateMentalModel: class TestCreateMentalModel:
async def test_create_multi_bank(self, mcp_server_with_mental_models, mock_memory): async def test_create_multi_bank(self, mcp_server_with_mental_models, mock_memory):
@ -702,12 +832,12 @@ class TestMentalModelInputValidation:
mock_memory.update_mental_model.assert_not_called() mock_memory.update_mental_model.assert_not_called()
async def test_not_found_error_includes_bank_id_multi_bank(self, mcp_server_with_mental_models, mock_memory): async def test_not_found_error_includes_bank_id_multi_bank(self, mcp_server_with_mental_models, mock_memory):
mock_memory.get_mental_model.return_value = None mock_memory.get_mental_model.side_effect = AsyncMock(return_value=None)
result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(mental_model_id="missing") result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(mental_model_id="missing")
assert "test-bank" in result assert "test-bank" in result
async def test_not_found_error_includes_bank_id_single_bank(self, mcp_server_single_bank, mock_memory): async def test_not_found_error_includes_bank_id_single_bank(self, mcp_server_single_bank, mock_memory):
mock_memory.get_mental_model.return_value = None mock_memory.get_mental_model.side_effect = AsyncMock(return_value=None)
result = await _tools(mcp_server_single_bank)["get_mental_model"].fn(mental_model_id="missing") result = await _tools(mcp_server_single_bank)["get_mental_model"].fn(mental_model_id="missing")
assert isinstance(result, dict) assert isinstance(result, dict)
assert "fixed-bank" in result["error"] assert "fixed-bank" in result["error"]

View file

@ -550,14 +550,14 @@ impl ApiClient {
pub fn list_mental_models(&self, bank_id: &str, _verbose: bool) -> Result<types::MentalModelListResponse> { pub fn list_mental_models(&self, bank_id: &str, _verbose: bool) -> Result<types::MentalModelListResponse> {
self.runtime.block_on(async { self.runtime.block_on(async {
let response = self.client.list_mental_models(bank_id, None, None, None, None, None).await?; let response = self.client.list_mental_models(bank_id, None, None, None, None, None, None).await?;
Ok(response.into_inner()) Ok(response.into_inner())
}) })
} }
pub fn get_mental_model(&self, bank_id: &str, mental_model_id: &str, _verbose: bool) -> Result<types::MentalModelResponse> { pub fn get_mental_model(&self, bank_id: &str, mental_model_id: &str, _verbose: bool) -> Result<types::MentalModelResponse> {
self.runtime.block_on(async { self.runtime.block_on(async {
let response = self.client.get_mental_model(bank_id, mental_model_id, None).await?; let response = self.client.get_mental_model(bank_id, mental_model_id, None, None).await?;
Ok(response.into_inner()) Ok(response.into_inner())
}) })
} }

View file

@ -43,9 +43,11 @@ pub fn list(
); );
// Show content preview // Show content preview
let preview: String = mental_model.content.chars().take(80).collect(); if let Some(ref content) = mental_model.content {
let ellipsis = if mental_model.content.len() > 80 { "..." } else { "" }; let preview: String = content.chars().take(80).collect();
println!(" {}{}", ui::dim(&preview), ellipsis); let ellipsis = if content.len() > 80 { "..." } else { "" };
println!(" {}{}", ui::dim(&preview), ellipsis);
}
println!(); println!();
} }
@ -326,11 +328,15 @@ fn print_mental_model_detail(mental_model: &types::MentalModelResponse) {
ui::print_section_header(&mental_model.name); ui::print_section_header(&mental_model.name);
println!(" {} {}", ui::dim("ID:"), ui::gradient_start(&mental_model.id)); println!(" {} {}", ui::dim("ID:"), ui::gradient_start(&mental_model.id));
println!(" {} {}", ui::dim("Source Query:"), &mental_model.source_query); if let Some(ref source_query) = mental_model.source_query {
println!(" {} {}", ui::dim("Source Query:"), source_query);
}
println!(); if let Some(ref content) = mental_model.content {
println!("{}", ui::gradient_text("─── Content ───")); println!();
println!(); println!("{}", ui::gradient_text("─── Content ───"));
println!("{}", &mental_model.content); println!();
println!(); println!("{}", content);
println!();
}
} }

View file

@ -656,6 +656,23 @@ paths:
title: Tags Match title: Tags Match
type: string type: string
style: form style: form
- description: "Detail level: 'metadata' (names/tags only), 'content' (adds\
\ content/config), 'full' (includes reflect_response)"
explode: true
in: query
name: detail
required: false
schema:
default: full
description: "Detail level: 'metadata' (names/tags only), 'content' (adds\
\ content/config), 'full' (includes reflect_response)"
enum:
- metadata
- content
- full
title: Detail
type: string
style: form
- explode: true - explode: true
in: query in: query
name: limit name: limit
@ -810,6 +827,23 @@ paths:
title: Mental Model Id title: Mental Model Id
type: string type: string
style: simple style: simple
- description: "Detail level: 'metadata' (names/tags only), 'content' (adds\
\ content/config), 'full' (includes reflect_response)"
explode: true
in: query
name: detail
required: false
schema:
default: full
description: "Detail level: 'metadata' (names/tags only), 'content' (adds\
\ content/config), 'full' (includes reflect_response)"
enum:
- metadata
- content
- full
title: Detail
type: string
style: form
- explode: false - explode: false
in: header in: header
name: authorization name: authorization
@ -4534,12 +4568,10 @@ components:
title: Name title: Name
type: string type: string
source_query: source_query:
title: Source Query nullable: true
type: string type: string
content: content:
description: The mental model content as well-formatted markdown (auto-generated nullable: true
from reflect endpoint)
title: Content
type: string type: string
tags: tags:
default: [] default: []
@ -4547,8 +4579,7 @@ components:
type: string type: string
type: array type: array
max_tokens: max_tokens:
default: 2048 nullable: true
title: Max Tokens
type: integer type: integer
trigger: trigger:
$ref: '#/components/schemas/MentalModelTrigger-Output' $ref: '#/components/schemas/MentalModelTrigger-Output'
@ -4563,10 +4594,8 @@ components:
nullable: true nullable: true
required: required:
- bank_id - bank_id
- content
- id - id
- name - name
- source_query
title: MentalModelResponse title: MentalModelResponse
MentalModelTrigger-Input: MentalModelTrigger-Input:
description: Trigger settings for a mental model. description: Trigger settings for a mental model.

View file

@ -288,9 +288,16 @@ type ApiGetMentalModelRequest struct {
ApiService *MentalModelsAPIService ApiService *MentalModelsAPIService
bankId string bankId string
mentalModelId string mentalModelId string
detail *string
authorization *string authorization *string
} }
// Detail level: &#39;metadata&#39; (names/tags only), &#39;content&#39; (adds content/config), &#39;full&#39; (includes reflect_response)
func (r ApiGetMentalModelRequest) Detail(detail string) ApiGetMentalModelRequest {
r.detail = &detail
return r
}
func (r ApiGetMentalModelRequest) Authorization(authorization string) ApiGetMentalModelRequest { func (r ApiGetMentalModelRequest) Authorization(authorization string) ApiGetMentalModelRequest {
r.authorization = &authorization r.authorization = &authorization
return r return r
@ -342,6 +349,12 @@ func (a *MentalModelsAPIService) GetMentalModelExecute(r ApiGetMentalModelReques
localVarQueryParams := url.Values{} localVarQueryParams := url.Values{}
localVarFormParams := url.Values{} localVarFormParams := url.Values{}
if r.detail != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "detail", r.detail, "form", "")
} else {
var defaultValue string = "full"
r.detail = &defaultValue
}
// to determine the Content-Type header // to determine the Content-Type header
localVarHTTPContentTypes := []string{} localVarHTTPContentTypes := []string{}
@ -541,6 +554,7 @@ type ApiListMentalModelsRequest struct {
bankId string bankId string
tags *[]string tags *[]string
tagsMatch *string tagsMatch *string
detail *string
limit *int32 limit *int32
offset *int32 offset *int32
authorization *string authorization *string
@ -558,6 +572,12 @@ func (r ApiListMentalModelsRequest) TagsMatch(tagsMatch string) ApiListMentalMod
return r return r
} }
// Detail level: &#39;metadata&#39; (names/tags only), &#39;content&#39; (adds content/config), &#39;full&#39; (includes reflect_response)
func (r ApiListMentalModelsRequest) Detail(detail string) ApiListMentalModelsRequest {
r.detail = &detail
return r
}
func (r ApiListMentalModelsRequest) Limit(limit int32) ApiListMentalModelsRequest { func (r ApiListMentalModelsRequest) Limit(limit int32) ApiListMentalModelsRequest {
r.limit = &limit r.limit = &limit
return r return r
@ -633,6 +653,12 @@ func (a *MentalModelsAPIService) ListMentalModelsExecute(r ApiListMentalModelsRe
var defaultValue string = "any" var defaultValue string = "any"
r.tagsMatch = &defaultValue r.tagsMatch = &defaultValue
} }
if r.detail != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "detail", r.detail, "form", "")
} else {
var defaultValue string = "full"
r.detail = &defaultValue
}
if r.limit != nil { if r.limit != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "limit", r.limit, "form", "") parameterAddToHeaderOrQuery(localVarQueryParams, "limit", r.limit, "form", "")
} else { } else {

View file

@ -24,12 +24,11 @@ type MentalModelResponse struct {
Id string `json:"id"` Id string `json:"id"`
BankId string `json:"bank_id"` BankId string `json:"bank_id"`
Name string `json:"name"` Name string `json:"name"`
SourceQuery string `json:"source_query"` SourceQuery NullableString `json:"source_query,omitempty"`
// The mental model content as well-formatted markdown (auto-generated from reflect endpoint) Content NullableString `json:"content,omitempty"`
Content string `json:"content"`
Tags []string `json:"tags,omitempty"` Tags []string `json:"tags,omitempty"`
MaxTokens *int32 `json:"max_tokens,omitempty"` MaxTokens NullableInt32 `json:"max_tokens,omitempty"`
Trigger *MentalModelTriggerOutput `json:"trigger,omitempty"` Trigger NullableMentalModelTriggerOutput `json:"trigger,omitempty"`
LastRefreshedAt NullableString `json:"last_refreshed_at,omitempty"` LastRefreshedAt NullableString `json:"last_refreshed_at,omitempty"`
CreatedAt NullableString `json:"created_at,omitempty"` CreatedAt NullableString `json:"created_at,omitempty"`
ReflectResponse map[string]interface{} `json:"reflect_response,omitempty"` ReflectResponse map[string]interface{} `json:"reflect_response,omitempty"`
@ -41,15 +40,11 @@ type _MentalModelResponse MentalModelResponse
// This constructor will assign default values to properties that have it defined, // This constructor will assign default values to properties that have it defined,
// and makes sure properties required by API are set, but the set of arguments // and makes sure properties required by API are set, but the set of arguments
// will change when the set of required properties is changed // will change when the set of required properties is changed
func NewMentalModelResponse(id string, bankId string, name string, sourceQuery string, content string) *MentalModelResponse { func NewMentalModelResponse(id string, bankId string, name string) *MentalModelResponse {
this := MentalModelResponse{} this := MentalModelResponse{}
this.Id = id this.Id = id
this.BankId = bankId this.BankId = bankId
this.Name = name this.Name = name
this.SourceQuery = sourceQuery
this.Content = content
var maxTokens int32 = 2048
this.MaxTokens = &maxTokens
return &this return &this
} }
@ -58,8 +53,6 @@ func NewMentalModelResponse(id string, bankId string, name string, sourceQuery s
// but it doesn't guarantee that properties required by API are set // but it doesn't guarantee that properties required by API are set
func NewMentalModelResponseWithDefaults() *MentalModelResponse { func NewMentalModelResponseWithDefaults() *MentalModelResponse {
this := MentalModelResponse{} this := MentalModelResponse{}
var maxTokens int32 = 2048
this.MaxTokens = &maxTokens
return &this return &this
} }
@ -135,52 +128,88 @@ func (o *MentalModelResponse) SetName(v string) {
o.Name = v o.Name = v
} }
// GetSourceQuery returns the SourceQuery field value // GetSourceQuery returns the SourceQuery field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *MentalModelResponse) GetSourceQuery() string { func (o *MentalModelResponse) GetSourceQuery() string {
if o == nil { if o == nil || IsNil(o.SourceQuery.Get()) {
var ret string var ret string
return ret return ret
} }
return *o.SourceQuery.Get()
return o.SourceQuery
} }
// GetSourceQueryOk returns a tuple with the SourceQuery field value // GetSourceQueryOk returns a tuple with the SourceQuery field value if set, nil otherwise
// and a boolean to check if the value has been set. // and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *MentalModelResponse) GetSourceQueryOk() (*string, bool) { func (o *MentalModelResponse) GetSourceQueryOk() (*string, bool) {
if o == nil { if o == nil {
return nil, false return nil, false
} }
return &o.SourceQuery, true return o.SourceQuery.Get(), o.SourceQuery.IsSet()
} }
// SetSourceQuery sets field value // HasSourceQuery returns a boolean if a field has been set.
func (o *MentalModelResponse) HasSourceQuery() bool {
if o != nil && o.SourceQuery.IsSet() {
return true
}
return false
}
// SetSourceQuery gets a reference to the given NullableString and assigns it to the SourceQuery field.
func (o *MentalModelResponse) SetSourceQuery(v string) { func (o *MentalModelResponse) SetSourceQuery(v string) {
o.SourceQuery = v o.SourceQuery.Set(&v)
}
// SetSourceQueryNil sets the value for SourceQuery to be an explicit nil
func (o *MentalModelResponse) SetSourceQueryNil() {
o.SourceQuery.Set(nil)
} }
// GetContent returns the Content field value // UnsetSourceQuery ensures that no value is present for SourceQuery, not even an explicit nil
func (o *MentalModelResponse) UnsetSourceQuery() {
o.SourceQuery.Unset()
}
// GetContent returns the Content field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *MentalModelResponse) GetContent() string { func (o *MentalModelResponse) GetContent() string {
if o == nil { if o == nil || IsNil(o.Content.Get()) {
var ret string var ret string
return ret return ret
} }
return *o.Content.Get()
return o.Content
} }
// GetContentOk returns a tuple with the Content field value // GetContentOk returns a tuple with the Content field value if set, nil otherwise
// and a boolean to check if the value has been set. // and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *MentalModelResponse) GetContentOk() (*string, bool) { func (o *MentalModelResponse) GetContentOk() (*string, bool) {
if o == nil { if o == nil {
return nil, false return nil, false
} }
return &o.Content, true return o.Content.Get(), o.Content.IsSet()
} }
// SetContent sets field value // HasContent returns a boolean if a field has been set.
func (o *MentalModelResponse) HasContent() bool {
if o != nil && o.Content.IsSet() {
return true
}
return false
}
// SetContent gets a reference to the given NullableString and assigns it to the Content field.
func (o *MentalModelResponse) SetContent(v string) { func (o *MentalModelResponse) SetContent(v string) {
o.Content = v o.Content.Set(&v)
}
// SetContentNil sets the value for Content to be an explicit nil
func (o *MentalModelResponse) SetContentNil() {
o.Content.Set(nil)
}
// UnsetContent ensures that no value is present for Content, not even an explicit nil
func (o *MentalModelResponse) UnsetContent() {
o.Content.Unset()
} }
// GetTags returns the Tags field value if set, zero value otherwise. // GetTags returns the Tags field value if set, zero value otherwise.
@ -215,68 +244,88 @@ func (o *MentalModelResponse) SetTags(v []string) {
o.Tags = v o.Tags = v
} }
// GetMaxTokens returns the MaxTokens field value if set, zero value otherwise. // GetMaxTokens returns the MaxTokens field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *MentalModelResponse) GetMaxTokens() int32 { func (o *MentalModelResponse) GetMaxTokens() int32 {
if o == nil || IsNil(o.MaxTokens) { if o == nil || IsNil(o.MaxTokens.Get()) {
var ret int32 var ret int32
return ret return ret
} }
return *o.MaxTokens return *o.MaxTokens.Get()
} }
// GetMaxTokensOk returns a tuple with the MaxTokens field value if set, nil otherwise // GetMaxTokensOk returns a tuple with the MaxTokens field value if set, nil otherwise
// and a boolean to check if the value has been set. // and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *MentalModelResponse) GetMaxTokensOk() (*int32, bool) { func (o *MentalModelResponse) GetMaxTokensOk() (*int32, bool) {
if o == nil || IsNil(o.MaxTokens) { if o == nil {
return nil, false return nil, false
} }
return o.MaxTokens, true return o.MaxTokens.Get(), o.MaxTokens.IsSet()
} }
// HasMaxTokens returns a boolean if a field has been set. // HasMaxTokens returns a boolean if a field has been set.
func (o *MentalModelResponse) HasMaxTokens() bool { func (o *MentalModelResponse) HasMaxTokens() bool {
if o != nil && !IsNil(o.MaxTokens) { if o != nil && o.MaxTokens.IsSet() {
return true return true
} }
return false return false
} }
// SetMaxTokens gets a reference to the given int32 and assigns it to the MaxTokens field. // SetMaxTokens gets a reference to the given NullableInt32 and assigns it to the MaxTokens field.
func (o *MentalModelResponse) SetMaxTokens(v int32) { func (o *MentalModelResponse) SetMaxTokens(v int32) {
o.MaxTokens = &v o.MaxTokens.Set(&v)
}
// SetMaxTokensNil sets the value for MaxTokens to be an explicit nil
func (o *MentalModelResponse) SetMaxTokensNil() {
o.MaxTokens.Set(nil)
} }
// GetTrigger returns the Trigger field value if set, zero value otherwise. // UnsetMaxTokens ensures that no value is present for MaxTokens, not even an explicit nil
func (o *MentalModelResponse) UnsetMaxTokens() {
o.MaxTokens.Unset()
}
// GetTrigger returns the Trigger field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *MentalModelResponse) GetTrigger() MentalModelTriggerOutput { func (o *MentalModelResponse) GetTrigger() MentalModelTriggerOutput {
if o == nil || IsNil(o.Trigger) { if o == nil || IsNil(o.Trigger.Get()) {
var ret MentalModelTriggerOutput var ret MentalModelTriggerOutput
return ret return ret
} }
return *o.Trigger return *o.Trigger.Get()
} }
// GetTriggerOk returns a tuple with the Trigger field value if set, nil otherwise // GetTriggerOk returns a tuple with the Trigger field value if set, nil otherwise
// and a boolean to check if the value has been set. // and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *MentalModelResponse) GetTriggerOk() (*MentalModelTriggerOutput, bool) { func (o *MentalModelResponse) GetTriggerOk() (*MentalModelTriggerOutput, bool) {
if o == nil || IsNil(o.Trigger) { if o == nil {
return nil, false return nil, false
} }
return o.Trigger, true return o.Trigger.Get(), o.Trigger.IsSet()
} }
// HasTrigger returns a boolean if a field has been set. // HasTrigger returns a boolean if a field has been set.
func (o *MentalModelResponse) HasTrigger() bool { func (o *MentalModelResponse) HasTrigger() bool {
if o != nil && !IsNil(o.Trigger) { if o != nil && o.Trigger.IsSet() {
return true return true
} }
return false return false
} }
// SetTrigger gets a reference to the given MentalModelTriggerOutput and assigns it to the Trigger field. // SetTrigger gets a reference to the given NullableMentalModelTriggerOutput and assigns it to the Trigger field.
func (o *MentalModelResponse) SetTrigger(v MentalModelTriggerOutput) { func (o *MentalModelResponse) SetTrigger(v MentalModelTriggerOutput) {
o.Trigger = &v o.Trigger.Set(&v)
}
// SetTriggerNil sets the value for Trigger to be an explicit nil
func (o *MentalModelResponse) SetTriggerNil() {
o.Trigger.Set(nil)
}
// UnsetTrigger ensures that no value is present for Trigger, not even an explicit nil
func (o *MentalModelResponse) UnsetTrigger() {
o.Trigger.Unset()
} }
// GetLastRefreshedAt returns the LastRefreshedAt field value if set, zero value otherwise (both if not set or set to explicit null). // GetLastRefreshedAt returns the LastRefreshedAt field value if set, zero value otherwise (both if not set or set to explicit null).
@ -409,16 +458,20 @@ func (o MentalModelResponse) ToMap() (map[string]interface{}, error) {
toSerialize["id"] = o.Id toSerialize["id"] = o.Id
toSerialize["bank_id"] = o.BankId toSerialize["bank_id"] = o.BankId
toSerialize["name"] = o.Name toSerialize["name"] = o.Name
toSerialize["source_query"] = o.SourceQuery if o.SourceQuery.IsSet() {
toSerialize["content"] = o.Content toSerialize["source_query"] = o.SourceQuery.Get()
}
if o.Content.IsSet() {
toSerialize["content"] = o.Content.Get()
}
if !IsNil(o.Tags) { if !IsNil(o.Tags) {
toSerialize["tags"] = o.Tags toSerialize["tags"] = o.Tags
} }
if !IsNil(o.MaxTokens) { if o.MaxTokens.IsSet() {
toSerialize["max_tokens"] = o.MaxTokens toSerialize["max_tokens"] = o.MaxTokens.Get()
} }
if !IsNil(o.Trigger) { if o.Trigger.IsSet() {
toSerialize["trigger"] = o.Trigger toSerialize["trigger"] = o.Trigger.Get()
} }
if o.LastRefreshedAt.IsSet() { if o.LastRefreshedAt.IsSet() {
toSerialize["last_refreshed_at"] = o.LastRefreshedAt.Get() toSerialize["last_refreshed_at"] = o.LastRefreshedAt.Get()
@ -440,8 +493,6 @@ func (o *MentalModelResponse) UnmarshalJSON(data []byte) (err error) {
"id", "id",
"bank_id", "bank_id",
"name", "name",
"source_query",
"content",
} }
allProperties := make(map[string]interface{}) allProperties := make(map[string]interface{})

View file

@ -648,6 +648,7 @@ class MentalModelsApi:
self, self,
bank_id: StrictStr, bank_id: StrictStr,
mental_model_id: StrictStr, mental_model_id: StrictStr,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
authorization: Optional[StrictStr] = None, authorization: Optional[StrictStr] = None,
_request_timeout: Union[ _request_timeout: Union[
None, None,
@ -670,6 +671,8 @@ class MentalModelsApi:
:type bank_id: str :type bank_id: str
:param mental_model_id: (required) :param mental_model_id: (required)
:type mental_model_id: str :type mental_model_id: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param authorization: :param authorization:
:type authorization: str :type authorization: str
:param _request_timeout: timeout setting for this request. If one :param _request_timeout: timeout setting for this request. If one
@ -697,6 +700,7 @@ class MentalModelsApi:
_param = self._get_mental_model_serialize( _param = self._get_mental_model_serialize(
bank_id=bank_id, bank_id=bank_id,
mental_model_id=mental_model_id, mental_model_id=mental_model_id,
detail=detail,
authorization=authorization, authorization=authorization,
_request_auth=_request_auth, _request_auth=_request_auth,
_content_type=_content_type, _content_type=_content_type,
@ -724,6 +728,7 @@ class MentalModelsApi:
self, self,
bank_id: StrictStr, bank_id: StrictStr,
mental_model_id: StrictStr, mental_model_id: StrictStr,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
authorization: Optional[StrictStr] = None, authorization: Optional[StrictStr] = None,
_request_timeout: Union[ _request_timeout: Union[
None, None,
@ -746,6 +751,8 @@ class MentalModelsApi:
:type bank_id: str :type bank_id: str
:param mental_model_id: (required) :param mental_model_id: (required)
:type mental_model_id: str :type mental_model_id: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param authorization: :param authorization:
:type authorization: str :type authorization: str
:param _request_timeout: timeout setting for this request. If one :param _request_timeout: timeout setting for this request. If one
@ -773,6 +780,7 @@ class MentalModelsApi:
_param = self._get_mental_model_serialize( _param = self._get_mental_model_serialize(
bank_id=bank_id, bank_id=bank_id,
mental_model_id=mental_model_id, mental_model_id=mental_model_id,
detail=detail,
authorization=authorization, authorization=authorization,
_request_auth=_request_auth, _request_auth=_request_auth,
_content_type=_content_type, _content_type=_content_type,
@ -800,6 +808,7 @@ class MentalModelsApi:
self, self,
bank_id: StrictStr, bank_id: StrictStr,
mental_model_id: StrictStr, mental_model_id: StrictStr,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
authorization: Optional[StrictStr] = None, authorization: Optional[StrictStr] = None,
_request_timeout: Union[ _request_timeout: Union[
None, None,
@ -822,6 +831,8 @@ class MentalModelsApi:
:type bank_id: str :type bank_id: str
:param mental_model_id: (required) :param mental_model_id: (required)
:type mental_model_id: str :type mental_model_id: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param authorization: :param authorization:
:type authorization: str :type authorization: str
:param _request_timeout: timeout setting for this request. If one :param _request_timeout: timeout setting for this request. If one
@ -849,6 +860,7 @@ class MentalModelsApi:
_param = self._get_mental_model_serialize( _param = self._get_mental_model_serialize(
bank_id=bank_id, bank_id=bank_id,
mental_model_id=mental_model_id, mental_model_id=mental_model_id,
detail=detail,
authorization=authorization, authorization=authorization,
_request_auth=_request_auth, _request_auth=_request_auth,
_content_type=_content_type, _content_type=_content_type,
@ -871,6 +883,7 @@ class MentalModelsApi:
self, self,
bank_id, bank_id,
mental_model_id, mental_model_id,
detail,
authorization, authorization,
_request_auth, _request_auth,
_content_type, _content_type,
@ -898,6 +911,10 @@ class MentalModelsApi:
if mental_model_id is not None: if mental_model_id is not None:
_path_params['mental_model_id'] = mental_model_id _path_params['mental_model_id'] = mental_model_id
# process the query parameters # process the query parameters
if detail is not None:
_query_params.append(('detail', detail))
# process the header parameters # process the header parameters
if authorization is not None: if authorization is not None:
_header_params['authorization'] = authorization _header_params['authorization'] = authorization
@ -1235,6 +1252,7 @@ class MentalModelsApi:
bank_id: StrictStr, bank_id: StrictStr,
tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None, tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None,
tags_match: Annotated[Optional[StrictStr], Field(description="How to match tags")] = None, tags_match: Annotated[Optional[StrictStr], Field(description="How to match tags")] = None,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
limit: Optional[Annotated[int, Field(le=1000, strict=True, ge=1)]] = None, limit: Optional[Annotated[int, Field(le=1000, strict=True, ge=1)]] = None,
offset: Optional[Annotated[int, Field(strict=True, ge=0)]] = None, offset: Optional[Annotated[int, Field(strict=True, ge=0)]] = None,
authorization: Optional[StrictStr] = None, authorization: Optional[StrictStr] = None,
@ -1261,6 +1279,8 @@ class MentalModelsApi:
:type tags: List[str] :type tags: List[str]
:param tags_match: How to match tags :param tags_match: How to match tags
:type tags_match: str :type tags_match: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param limit: :param limit:
:type limit: int :type limit: int
:param offset: :param offset:
@ -1293,6 +1313,7 @@ class MentalModelsApi:
bank_id=bank_id, bank_id=bank_id,
tags=tags, tags=tags,
tags_match=tags_match, tags_match=tags_match,
detail=detail,
limit=limit, limit=limit,
offset=offset, offset=offset,
authorization=authorization, authorization=authorization,
@ -1323,6 +1344,7 @@ class MentalModelsApi:
bank_id: StrictStr, bank_id: StrictStr,
tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None, tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None,
tags_match: Annotated[Optional[StrictStr], Field(description="How to match tags")] = None, tags_match: Annotated[Optional[StrictStr], Field(description="How to match tags")] = None,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
limit: Optional[Annotated[int, Field(le=1000, strict=True, ge=1)]] = None, limit: Optional[Annotated[int, Field(le=1000, strict=True, ge=1)]] = None,
offset: Optional[Annotated[int, Field(strict=True, ge=0)]] = None, offset: Optional[Annotated[int, Field(strict=True, ge=0)]] = None,
authorization: Optional[StrictStr] = None, authorization: Optional[StrictStr] = None,
@ -1349,6 +1371,8 @@ class MentalModelsApi:
:type tags: List[str] :type tags: List[str]
:param tags_match: How to match tags :param tags_match: How to match tags
:type tags_match: str :type tags_match: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param limit: :param limit:
:type limit: int :type limit: int
:param offset: :param offset:
@ -1381,6 +1405,7 @@ class MentalModelsApi:
bank_id=bank_id, bank_id=bank_id,
tags=tags, tags=tags,
tags_match=tags_match, tags_match=tags_match,
detail=detail,
limit=limit, limit=limit,
offset=offset, offset=offset,
authorization=authorization, authorization=authorization,
@ -1411,6 +1436,7 @@ class MentalModelsApi:
bank_id: StrictStr, bank_id: StrictStr,
tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None, tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None,
tags_match: Annotated[Optional[StrictStr], Field(description="How to match tags")] = None, tags_match: Annotated[Optional[StrictStr], Field(description="How to match tags")] = None,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
limit: Optional[Annotated[int, Field(le=1000, strict=True, ge=1)]] = None, limit: Optional[Annotated[int, Field(le=1000, strict=True, ge=1)]] = None,
offset: Optional[Annotated[int, Field(strict=True, ge=0)]] = None, offset: Optional[Annotated[int, Field(strict=True, ge=0)]] = None,
authorization: Optional[StrictStr] = None, authorization: Optional[StrictStr] = None,
@ -1437,6 +1463,8 @@ class MentalModelsApi:
:type tags: List[str] :type tags: List[str]
:param tags_match: How to match tags :param tags_match: How to match tags
:type tags_match: str :type tags_match: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param limit: :param limit:
:type limit: int :type limit: int
:param offset: :param offset:
@ -1469,6 +1497,7 @@ class MentalModelsApi:
bank_id=bank_id, bank_id=bank_id,
tags=tags, tags=tags,
tags_match=tags_match, tags_match=tags_match,
detail=detail,
limit=limit, limit=limit,
offset=offset, offset=offset,
authorization=authorization, authorization=authorization,
@ -1494,6 +1523,7 @@ class MentalModelsApi:
bank_id, bank_id,
tags, tags,
tags_match, tags_match,
detail,
limit, limit,
offset, offset,
authorization, authorization,
@ -1530,6 +1560,10 @@ class MentalModelsApi:
_query_params.append(('tags_match', tags_match)) _query_params.append(('tags_match', tags_match))
if detail is not None:
_query_params.append(('detail', detail))
if limit is not None: if limit is not None:
_query_params.append(('limit', limit)) _query_params.append(('limit', limit))

View file

@ -17,7 +17,7 @@ import pprint
import re # noqa: F401 import re # noqa: F401
import json import json
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
from typing import Any, ClassVar, Dict, List, Optional from typing import Any, ClassVar, Dict, List, Optional
from hindsight_client_api.models.mental_model_trigger_output import MentalModelTriggerOutput from hindsight_client_api.models.mental_model_trigger_output import MentalModelTriggerOutput
from typing import Optional, Set from typing import Optional, Set
@ -30,10 +30,10 @@ class MentalModelResponse(BaseModel):
id: StrictStr id: StrictStr
bank_id: StrictStr bank_id: StrictStr
name: StrictStr name: StrictStr
source_query: StrictStr source_query: Optional[StrictStr] = None
content: StrictStr = Field(description="The mental model content as well-formatted markdown (auto-generated from reflect endpoint)") content: Optional[StrictStr] = None
tags: Optional[List[StrictStr]] = None tags: Optional[List[StrictStr]] = None
max_tokens: Optional[StrictInt] = 2048 max_tokens: Optional[StrictInt] = None
trigger: Optional[MentalModelTriggerOutput] = None trigger: Optional[MentalModelTriggerOutput] = None
last_refreshed_at: Optional[StrictStr] = None last_refreshed_at: Optional[StrictStr] = None
created_at: Optional[StrictStr] = None created_at: Optional[StrictStr] = None
@ -82,6 +82,26 @@ class MentalModelResponse(BaseModel):
# override the default output from pydantic by calling `to_dict()` of trigger # override the default output from pydantic by calling `to_dict()` of trigger
if self.trigger: if self.trigger:
_dict['trigger'] = self.trigger.to_dict() _dict['trigger'] = self.trigger.to_dict()
# set to None if source_query (nullable) is None
# and model_fields_set contains the field
if self.source_query is None and "source_query" in self.model_fields_set:
_dict['source_query'] = None
# set to None if content (nullable) is None
# and model_fields_set contains the field
if self.content is None and "content" in self.model_fields_set:
_dict['content'] = None
# set to None if max_tokens (nullable) is None
# and model_fields_set contains the field
if self.max_tokens is None and "max_tokens" in self.model_fields_set:
_dict['max_tokens'] = None
# set to None if trigger (nullable) is None
# and model_fields_set contains the field
if self.trigger is None and "trigger" in self.model_fields_set:
_dict['trigger'] = None
# set to None if last_refreshed_at (nullable) is None # set to None if last_refreshed_at (nullable) is None
# and model_fields_set contains the field # and model_fields_set contains the field
if self.last_refreshed_at is None and "last_refreshed_at" in self.model_fields_set: if self.last_refreshed_at is None and "last_refreshed_at" in self.model_fields_set:
@ -115,7 +135,7 @@ class MentalModelResponse(BaseModel):
"source_query": obj.get("source_query"), "source_query": obj.get("source_query"),
"content": obj.get("content"), "content": obj.get("content"),
"tags": obj.get("tags"), "tags": obj.get("tags"),
"max_tokens": obj.get("max_tokens") if obj.get("max_tokens") is not None else 2048, "max_tokens": obj.get("max_tokens"),
"trigger": MentalModelTriggerOutput.from_dict(obj["trigger"]) if obj.get("trigger") is not None else None, "trigger": MentalModelTriggerOutput.from_dict(obj["trigger"]) if obj.get("trigger") is not None else None,
"last_refreshed_at": obj.get("last_refreshed_at"), "last_refreshed_at": obj.get("last_refreshed_at"),
"created_at": obj.get("created_at"), "created_at": obj.get("created_at"),

View file

@ -1435,13 +1435,13 @@ export type MentalModelResponse = {
/** /**
* Source Query * Source Query
*/ */
source_query: string; source_query?: string | null;
/** /**
* Content * Content
* *
* The mental model content as well-formatted markdown (auto-generated from reflect endpoint) * The mental model content as well-formatted markdown (auto-generated from reflect endpoint)
*/ */
content: string; content?: string | null;
/** /**
* Tags * Tags
*/ */
@ -1449,8 +1449,8 @@ export type MentalModelResponse = {
/** /**
* Max Tokens * Max Tokens
*/ */
max_tokens?: number; max_tokens?: number | null;
trigger?: MentalModelTriggerOutput; trigger?: MentalModelTriggerOutput | null;
/** /**
* Last Refreshed At * Last Refreshed At
*/ */
@ -3331,6 +3331,12 @@ export type ListMentalModelsData = {
* How to match tags * How to match tags
*/ */
tags_match?: "any" | "all" | "exact"; tags_match?: "any" | "all" | "exact";
/**
* Detail
*
* Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
*/
detail?: "metadata" | "content" | "full";
/** /**
* Limit * Limit
*/ */
@ -3458,7 +3464,14 @@ export type GetMentalModelData = {
*/ */
mental_model_id: string; mental_model_id: string;
}; };
query?: never; query?: {
/**
* Detail
*
* Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
*/
detail?: "metadata" | "content" | "full";
};
url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}"; url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}";
}; };

View file

@ -186,19 +186,52 @@ Enable automatic refresh for mental models that need to stay current. Disable it
</TabItem> </TabItem>
</Tabs> </Tabs>
### Detail Levels
Both **List** and **Get** endpoints accept an optional `detail` query parameter that controls how much data is returned. This is useful for reducing response size, especially in agent boot flows or MCP clients where context budget is limited.
| Level | Fields Returned | Use Case |
|-------|----------------|----------|
| `metadata` | `id`, `bank_id`, `name`, `tags`, `last_refreshed_at`, `created_at` | Inventory — "what models exist?" |
| `content` | All metadata fields + `source_query`, `content`, `max_tokens`, `trigger` | Agent boot — "what do the models say?" |
| `full` (default) | All fields including `reflect_response` | Deep inspection — "what evidence backs this model?" |
```bash
# List only names and tags (smallest response)
curl "$BASE_URL/v1/default/banks/$BANK_ID/mental-models?detail=metadata"
# List with content but without provenance chains
curl "$BASE_URL/v1/default/banks/$BANK_ID/mental-models?detail=content"
# Get full detail (default behavior)
curl "$BASE_URL/v1/default/banks/$BANK_ID/mental-models/$MODEL_ID?detail=full"
```
The `detail` parameter is also available in the MCP tools:
```json
{"bank_id": "my-bank", "detail": "metadata"}
```
:::tip
Use `detail=content` for agent orientation flows. It includes everything the agent needs to understand the models without the heavyweight `reflect_response` provenance chains, which can exceed 200KB for banks with many models.
:::
### Response Fields ### Response Fields
| Field | Type | Description | | Field | Type | Detail Level | Description |
|-------|------|-------------| |-------|------|-------------|-------------|
| `id` | string | Unique mental model ID | | `id` | string | metadata | Unique mental model ID |
| `bank_id` | string | Memory bank ID | | `bank_id` | string | metadata | Memory bank ID |
| `name` | string | Human-readable name | | `name` | string | metadata | Human-readable name |
| `source_query` | string | The query used to generate content | | `tags` | list | metadata | Tags for filtering |
| `content` | string | The generated mental model text | | `last_refreshed_at` | string | metadata | When the mental model was last updated |
| `tags` | list | Tags for filtering | | `created_at` | string | metadata | When the mental model was created |
| `last_refreshed_at` | string | When the mental model was last updated | | `source_query` | string | content | The query used to generate content |
| `created_at` | string | When the mental model was created | | `content` | string | content | The generated mental model text |
| `reflect_response` | object | Full reflect response including `based_on` facts | | `max_tokens` | int | content | Maximum tokens for the mental model content |
| `trigger` | object | content | Trigger settings (see [Automatic Refresh](#automatic-refresh)) |
| `reflect_response` | object | full | Full reflect response including `based_on` provenance facts |
--- ---

View file

@ -971,6 +971,23 @@
}, },
"description": "How to match tags" "description": "How to match tags"
}, },
{
"name": "detail",
"in": "query",
"required": false,
"schema": {
"enum": [
"metadata",
"content",
"full"
],
"type": "string",
"description": "Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)",
"default": "full",
"title": "Detail"
},
"description": "Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)"
},
{ {
"name": "limit", "name": "limit",
"in": "query", "in": "query",
@ -1129,6 +1146,23 @@
"title": "Mental Model Id" "title": "Mental Model Id"
} }
}, },
{
"name": "detail",
"in": "query",
"required": false,
"schema": {
"enum": [
"metadata",
"content",
"full"
],
"type": "string",
"description": "Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)",
"default": "full",
"title": "Detail"
},
"description": "Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)"
},
{ {
"name": "authorization", "name": "authorization",
"in": "header", "in": "header",
@ -6706,11 +6740,25 @@
"title": "Name" "title": "Name"
}, },
"source_query": { "source_query": {
"type": "string", "anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Source Query" "title": "Source Query"
}, },
"content": { "content": {
"type": "string", "anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Content", "title": "Content",
"description": "The mental model content as well-formatted markdown (auto-generated from reflect endpoint)" "description": "The mental model content as well-formatted markdown (auto-generated from reflect endpoint)"
}, },
@ -6723,13 +6771,25 @@
"default": [] "default": []
}, },
"max_tokens": { "max_tokens": {
"type": "integer", "anyOf": [
"title": "Max Tokens", {
"default": 2048 "type": "integer"
},
{
"type": "null"
}
],
"title": "Max Tokens"
}, },
"trigger": { "trigger": {
"$ref": "#/components/schemas/MentalModelTrigger-Output", "anyOf": [
"default": {} {
"$ref": "#/components/schemas/MentalModelTrigger-Output"
},
{
"type": "null"
}
]
}, },
"last_refreshed_at": { "last_refreshed_at": {
"anyOf": [ "anyOf": [
@ -6771,9 +6831,7 @@
"required": [ "required": [
"id", "id",
"bank_id", "bank_id",
"name", "name"
"source_query",
"content"
], ],
"title": "MentalModelResponse", "title": "MentalModelResponse",
"description": "Response model for a mental model (stored reflect response)." "description": "Response model for a mental model (stored reflect response)."