feat: add /v1/tools/* REST router for cross-platform agent access#6272
feat: add /v1/tools/* REST router for cross-platform agent access#6272
Conversation
Part of #6265 — platform tools router for cross-platform agent access. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extracts pure functions from LangChain conversation tools: get_conversations_text() and search_conversations_text(). No agent_config_context dependency — takes uid directly. Includes parse_iso_date() utility for timezone-aware date parsing. Part of #6265. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extracts get_memories_text() and search_memories_text() from LangChain memory tools. Filters locked memories, supports date ranges and vector search. Part of #6265. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extracts get_action_items_text(), create_action_item_text(), and update_action_item_text() from LangChain tools. Handles due date validation, past-date rejection, default 24h due date, and FCM notifications. Part of #6265. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7 endpoints exposing backend RAG capabilities as direct REST:
- GET/POST conversations (list + semantic search)
- GET/POST memories (list + semantic search)
- GET/POST/PATCH action-items (list + create + update)
Uses ToolResponse envelope: {tool_name, result_text, is_error}.
Auth via Firebase token (same as all other endpoints).
Part of #6265.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Part of #6265. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
32 tests covering: date parsing, conversation/memory/action-item retrieval, search, creation, updates, locked item filtering, limit caps, error handling, and response envelope. Part of #6265. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Part of #6265. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Registers get_conversations, search_conversations, get_memories, search_memories, get_action_items, create_action_item, update_action_item in the MCP tool list. Forwards calls to Swift via the bridge pipe. Part of #6265. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Routes 7 new tool names to executeBackendTool() which calls APIClient methods for the /v1/tools/* endpoints on the Python backend. Part of #6265. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds ToolResponse model and 7 methods calling /v1/tools/* on the Python backend (pythonBackendURL). Handles GET with query params and POST/PATCH with JSON bodies for conversations, memories, and action items. Part of #6265. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR introduces a new Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Client as Desktop/Web Client
participant MCP as omi-tools-stdio (TS)
participant Swift as ChatToolExecutor (Swift)
participant API as APIClient (Swift)
participant Backend as FastAPI /v1/tools/*
participant DB as Firestore + Pinecone
Client->>MCP: tools/call (e.g. get_conversations)
MCP->>Swift: pipe JSON {type:tool_use, callId, name, input}
Swift->>API: toolGetConversations(...)
API->>Backend: GET /v1/tools/conversations?... (Firebase Auth)
Backend->>DB: conversations_db.get_conversations(uid,...)
DB-->>Backend: conversations data
Backend-->>API: ToolResponse {tool_name, result_text, is_error}
API-->>Swift: ToolResponse
Swift-->>MCP: pipe JSON {type:tool_result, callId, result}
MCP-->>Client: MCP result {content:[{type:text, text:result_text}]}
|
|
|
||
|
|
||
| def _ok(tool_name: str, text: str) -> dict: | ||
| return {"tool_name": tool_name, "result_text": text, "is_error": text.startswith("Error")} |
There was a problem hiding this comment.
Fragile
is_error detection by string prefix
is_error is derived solely from text.startswith("Error"). Messages like "No changes specified." (from update_action_item_text when no fields are provided) or any future service message that uses a different prefix (e.g., "Failed to …") will silently produce is_error=False even though the call didn't succeed. A dedicated sentinel or explicit return type would be more reliable.
| return {"tool_name": tool_name, "result_text": text, "is_error": text.startswith("Error")} | |
| def _ok(tool_name: str, text: str, is_error: bool = False) -> dict: | |
| return {"tool_name": tool_name, "result_text": text, "is_error": is_error} |
Service functions could then signal errors explicitly, or the existing convention can be kept but documented with a constant prefix guard.
| now = datetime.now(datetime.now().astimezone().tzinfo) | ||
| action_item_data['due_at'] = now + timedelta(hours=24) |
There was a problem hiding this comment.
Default
due_at uses convoluted local-timezone computation
datetime.now(datetime.now().astimezone().tzinfo) calls datetime.now() twice and goes through a naive→local conversion to obtain the timezone. The rest of the function already uses datetime.now(timezone.utc) correctly. Using local server timezone here is also inconsistent with UTC-based validation above (line 143).
| now = datetime.now(datetime.now().astimezone().tzinfo) | |
| action_item_data['due_at'] = now + timedelta(hours=24) | |
| now = datetime.now(timezone.utc) | |
| action_item_data['due_at'] = now + timedelta(hours=24) |
Adds is_locked filtering to get_action_items_text() and rejection in update_action_item_text(), matching routers/action_items.py guards. Review fix for #6272. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When only start_date or end_date is provided, fills in the missing bound to avoid passing $lte: None to Pinecone query_vectors. Review fix for #6272. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Applies tools:search (60/hr) to conversation/memory search and tools:mutate (60/hr) to action item create/update. Read-only list endpoints remain unthrottled. Review fix for #6272. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests: locked action items filtered from list, locked updates rejected, start-date-only search sets ends_at, end-date-only search sets starts_at. Review fix for #6272. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses tester feedback: adds 35 new tests covering router endpoints via TestClient, rate limiting policy verification, conversation lock filtering, DB/vector error handling, and boundary conditions. Total: 71 tests (was 36). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wraps conversations_db.get_conversations call in try/except to match the error handling pattern used in all other service functions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Completes error handling coverage across all service functions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CP9 Changed-Path Coverage Checklist
L1 SynthesisAll backend changed paths (P1-P10) were proven via isolated FastAPI server with mocked deps on VPS. Happy-path curl verified all 7 endpoints return correct L2 SynthesisDesktop paths (P11-P13) proven by successful by AI for @beastoin |
|
lgtm |
After rebasing onto main, code from PRs #6272 and #6065 referenced pythonBackendURL which no longer exists in our branch. Since baseURL already points to the Python backend (api.omi.me), these endpoints correctly default to baseURL when customBaseURL is nil. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
/v1/tools/*REST router with 7 endpoints for cross-platform agent access (issue Desktop floating bar: conversation/memory search tools missing from ACP bridge #6265)utils/retrieval/tool_services/) for conversations, memories, and action itemsChatToolExecutorAPIClientmethods for all tool endpoints usingpythonBackendURLEndpoints
/v1/tools/conversations/v1/tools/conversations/search/v1/tools/memories/v1/tools/memories/search/v1/tools/action-items/v1/tools/action-items/v1/tools/action-items/{id}Security
get_current_user_uid(same as all existing endpoints)tools:search(60/hr) for search endpoints,tools:mutate(60/hr) for create/updateis_lockedfiltering on all list/search endpoints; locked item rejection on mutations$lte: Nonein Pinecone queries)Deploy steps
Order matters: backend MUST deploy before desktop update reaches users.
1. Backend (Cloud Run + GKE)
gh workflow run "Deploy Backend to Cloud RUN" --repo BasedHardware/omi -f environment=prod -f branch=mainBuilds
backend/Dockerfile, pushes to GCR, deploys 3 Cloud Run services (backend, backend-sync, backend-integration) + restarts backend-listen on GKE.Verify after deploy:
2. Desktop (auto-triggered)
The
desktop_auto_release.ymlworkflow fires automatically on push to main whendesktop/**files change. It deploys desktop-backend to Cloud Run (dev → prod), computes next semver, tags it, and Codemagic picks up the tag to build+distribute the macOS app.Manual trigger (if needed):
Rollback
Safe to revert — all changes are additive (new router, new files, new Swift methods). No existing endpoints or desktop features modified.
Changed files
Backend (Python)
backend/main.py— register tools routerbackend/routers/tools.py— 7 REST endpointsbackend/utils/retrieval/tool_services/— shared service layer (conversations, memories, action items)backend/utils/rate_limit_config.py— addtools:searchandtools:mutatepoliciesbackend/test.sh— add test filebackend/tests/unit/test_tools_router.py— 72 unit testsDesktop (macOS)
desktop/Desktop/Sources/APIClient.swift— new methods for all 7 tool endpointsdesktop/Desktop/Sources/Providers/ChatToolExecutor.swift— tool executor integrationdesktop/acp-bridge/src/omi-tools-stdio.ts— ACP bridge tool definitions (7 tools)Test plan
Deployment status
Deploy Backend to Cloud RUN(prod, main)Closes #6265
🤖 Generated with Claude Code