Repository navigation
Conversation
…ible API POST /v1/chat/completions (with and without streaming) and GET /v1/models, off until openai_api.enabled is on, behind their own key in the keychain (POST /api/openai-api/key with the window's token). Each request's `user` gets its own named chat on the "api" channel, and only the last user message is sent. Errors use OpenAI's shape and carry x-should-retry: false, so OpenAI's SDKs don't retry a failed turn and run its tools again. Approvals for API turns and a Settings card are still open (questions in existence-master#211). Refs existence-master#211
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Comment |
A call an API turn needs the user's OK for posts a kind "approval" notification with the turn's approval_request: the window's existing Allow and Deny card answers it, and paired chats get it like a task's approval (held while the user is in the app), through the channel's own approval buttons. Its status follows the answer (allow, deny, expired), so every place that shows it settles. The request waits for the answer. A call that was declined or not answered in time is never claimed: the reply ends with a line saying it was not done. Each API turn runs in its own task: Stop everything ends it with "Stopped." (and any approval it waited on), and a client that hangs up no longer cuts it short. Chats and Activity say "From another app (API)". Refs existence-master#211
|
@itsskofficial pushed
One thing to check with you: I couldn't find an engine path that already sends The Settings card question is still open in the description, so I've kept this a draft for now. |
|
@Tanmay-Ts, a heads-up from testing a similar PR (#487, For
Your approvals-as-notifications approach is exactly right for the asking part. |
What and why
Closes #211 (draft: one question below is open)
Other apps on this computer (editors, scripts, shortcuts) can chat with Sentient, with its memory and tools, through any OpenAI client pointed at
<engine>/v1, and they are never a way around approvals.The API (
sentient/gateway/routes/openai_api.py)POST /v1/chat/completions, without streaming (achat.completionwithusage) and withstream: true(chat.completion.chunks, thendata: [DONE]), andGET /v1/models(sentient).openai_api.enabledis on (OpenAIApiConfig, mirrored indesktop/src/lib/types.ts)./v1takes only Sentient's own key, made withPOST /api/openai-api/key(window token) into the keychain entryopenai_api_key; never the window's token.usergets its own named chat ("API"when absent) onchannel: "api"; only the last user message is sent, because Sentient keeps the chat's history.x-should-retry: false: OpenAI's SDKs retry a 5xx twice by default, which would run the turn and its tools again (the real test caught that).Approvals ask in the app (option A, as you chose in #211)
kind: "approval"notification whosepayloadis the turn'sapproval_requestplussession_idandsource: "api". The window's existing Allow and Deny card for that kind answers it (POST /api/approvals), and the request waits for the answer.ap:buttons (ChannelService.is_live_approval,_deliver_live_approval), which answer throughapp.approvals.payload.statusfollows the answer, in the card's words:allow,deny, orexpired(no answer withintools.approvals.timeout_s, or stopped).STATUS_LABELSgainedallowanddeny, so delivered messages settle.delta.content) ends withNot done: "<tool>" needed your OK in Sentient and didn't get it.The model is already toldNOT DONE...; this line makes it certain for a client that only sees text.Stop everything and where it shows
stopped, or aStopped.error line when streaming), and an approval it waited on becomesexpired. A client that hangs up no longer cuts the turn short; it runs to the end and is saved, like the window's.desktop/src/features/channels/meta.tsx), and Activity saysFrom another app (API), in the chat "<name>"(activity/timeline.py).docs/API.md(section 2 "OpenAI-compatible API", section 14 delivery) andchanges/211.added.md.A note on "the existing path"
I couldn't find an engine path that already raises
kind: "approval"notifications: task approvals pause the run and go out askind: "task",payload.event = "question"with anapprovalblock, answered through the task. What already existed for a live approval was the window'sApprovalActionscard forkind: "approval"(unused by the engine until now) and each channel'ssend_approval()withap:buttons. So this uses both, and makesChannelServicehold and send these the same way it does task approvals. If you meant a different path, tell me and I'll move it there.Open question
Settings card. Settings has no place for this yet: a schema form only shows where a section's component places it, and making a key needs a button. Today it is turned on with
PATCH /api/config {"openai_api": {"enabled": true}}and the key comes fromPOST /api/openai-api/key. Should I add a small card under Settings > Advanced (the switch, a "Make a key" button that shows the key once, and the base URL) in this PR, would you rather do the desktop part, or leave it for a follow-up?How I tested it
tests/test_openai_api.py(14 tests): off by default; the key and the window's token; key rotation;/v1/models; replies with and without streaming; one named chat peruser, last user message only; a request without a user message; a failed turn through the real OpenAI SDK saved once; and, for approvals, a call allowed in the app (and Activity's "From another app (API)"), denied (theNot doneline, nothing written), not answered in time (expired), Stop everything while it waits (409,expired, nothing written), and a streamed turn that waits for the answer.tests/channels/test_api_approvals.py(2 tests): an API turn's approval reaches a paired Telegram chat while nobody is in the app, and Allow there lets it carry on, Deny there means not done. Both fail withChannelServiceunchanged.Not doneline the deny and timeout tests fail; withoutx-should-retrythe SDK test fails with['hi', 'hi', 'hi'] == ['hi'].pytest -q(Windows): 2735 passed, 2 skipped, with the two Windows fixes from fix(activity): find a chat tool call's details by its id, not its timestamp #486 applied on top (without them, those two tests fail on this machine as onmain; see Activity loses a chat tool call's details on Windows when messages share a timestamp #485).ruff check sentient tests packaging: clean.npm run typechecknot run locally (nonode_moduleshere); CI runs it.Real test
29995cb(python -m sentient serve --port 8792), new emptySENTIENT_HOMEunder.test-homes/, randomSENTIENT_GATEWAY_TOKEN, holding.ollama.lock. Windows 11. Client: the officialopenaiPython package (2.54.0), default settings.ollama_chat/qwen3:8bisn't installed here and Ollama wasn't running), so every turn ended in an error. That checks the wiring, auth, chats and error path, not a real reply.Before
x-should-retry: false, step 6 saved the question three times for one request (the SDK's retries); after it, once.3dfb69d) on a real engine. It needs a model that makes a tool call, and there's none on this machine; the tests above script it withFakeProvider, including the Telegram delivery.Checklist
pytest/npm run typecheckpass locally for what I touched (typecheck not run locally, see above)ruff check sentient testsis cleandocs/API.mdupdated if the desktop ↔ engine contract changedchanges/(changes/211.added.md)