Commit Graph
4818 Commits
Author SHA1 Message Date
Alex f8bb0002af Merge pull request #2536 from arc53/better-dropdown-prompt
feat: better prompt dropdown
2026-06-12 16:47:51 +01:00
Alex 73edd0da61 feat: better prompt dropdown 2026-06-12 16:35:58 +01:00
Alex b8dc9c223e Merge pull request #2533 from arc53/prompt-presets-revamp
Revamp default prompt presets, prompt templating, and tool naming
2026-06-11 21:06:06 +01:00
Alex b8eaa5a68d fix: mini tool call issues 2026-06-11 16:49:52 +01:00
Alex 636d4d35d1 feat(prompts): revamp preset prompts, tool naming, and prompt templating
Rewrite the default/creative/strict presets (classic + agentic) into
structured sections: grounding and cite-by-title guidance, insufficient-
context behavior, current date, respond-in-user-language, scoped mermaid
usage, an untrusted-content guardrail, and a conditional XML-tagged
document context block. A memory directory listing is injected at render
time via the template prefetch mechanism so the model starts oriented
without burning a tool call.

Fixes along the way:
- Agentic preset swap was dead code: _get_prompt_content cached the
  classic preset before create_agent's swap check ran, so agentic and
  research agents always got the classic preset. The swap now happens
  inside _get_prompt_content.
- Jinja autoescape corrupted document content in custom prompts
  (< -> &lt;); prompts are not HTML, autoescape is now off.
- Literal {summaries} leaked into the prompt when no docs were
  retrieved; the placeholder is now stripped.
- Agentic/research prompts referenced tool names from a dropped naming
  scheme (search_internal, reason_think); they now reference the real
  names (search, reason).
- The strict preset told the model to "be very creative and use your
  imagination" right after "never make up information".
- extract_tool_usages recorded intermediate attribute chains as
  bare-tool usages, which meant "run all actions" at prefetch; only
  maximal chains are recorded now.
- Headless runs retrieved docs but never rendered them into the
  prompt; the prompt is now rendered like the streaming path.
- Default tools were unreachable by name in prompt templates
  (prefetch results were keyed by synthetic id only); defaults now
  claim the name key unless an explicit row shadows it.

Tool layer: memory/notes/todo actions are namespaced (memory_view,
note_overwrite, todo_create, ...) with legacy unprefixed names still
accepted via prefix stripping; duplicate action names across tools are
disambiguated with the owning tool's name instead of numeric suffixes;
thin tool descriptions rewritten (brave, duckduckgo, telegram, ntfy,
cryptoprice, read_webpage, internal_search, think).

Docs are now wrapped per chunk in <document index>/<source>/<content>
tags for citation-by-title support.
2026-06-11 11:18:46 +01:00
Alex 10cc16eab0 Merge pull request #2530 from arc53/oidc-login
feat: oidc login
2026-06-10 17:36:36 +01:00
Alex 78c3fc1593 feat(frontend): top-right profile dropdown for OIDC sessions
Replace the OIDC user chip in the sidebar with an account avatar in the
top-right action bar (the existing UserButton placeholder). The avatar — a
brand-purple circle with the user's initial, or the IdP picture — opens a
popover showing name, email, and a sign-out action, reusing the existing
Avatar/Popover primitives with a hover ring. Renders only under
AUTH_TYPE=oidc.
2026-06-10 15:17:42 +01:00
Alex 321d88d5be fix(oidc): close re-review gaps in the hardening commit
Follow-up to the OIDC security hardening, from a max-effort re-review:

- Refresh now re-checks the denylist immediately before minting, against the
  (possibly remapped) identity but anchored on the original session `iat` — so a
  back-channel logout / SCIM deny that lands during the IdP grant, or one
  targeting the refreshed sub/sid, still blocks renewal instead of being escaped
  by the renewed token's fresh iat. Completes the watermark revocation fix.
- SCIM PUT `userName` immutability check is now case-insensitive, matching the
  case-insensitive list/create — a differently-cased userName echo no longer
  400s "userName is immutable" and blocks deprovision. Completes the SCIM
  case-insensitivity fix.
- Drop the unconditional state-cookie deletion on every callback exit: it let
  one tab's callback clear another in-flight tab's cookie, breaking concurrent
  logins. The cookie self-expires (max_age) and the Redis state is single-use,
  so the delete wasn't needed.

Tests added for the refresh revocation re-check and the case-insensitive PUT.
2026-06-10 14:15:32 +01:00
Alex a3d09e8890 fix(oidc): harden session security from PR review (CSRF, revocation, refresh)
Address the high/medium correctness findings on the OIDC/SCIM PR:

- Login CSRF / session fixation: bind `state` to a Secure/HttpOnly/SameSite=Lax
  cookie at login and require the callback to echo it, so a code+state captured
  from another browser can't silently sign a victim into the attacker's account.
- Require `exp` on session JWTs under AUTH_TYPE=oidc (require_exp), so an
  exp-less HS256 token signed with JWT_SECRET_KEY can't authenticate forever or
  outlive the denylist.
- Denylist now keys revocation on an `iat` watermark instead of a deletable
  flag: a fresh login (newer iat) self-supersedes a revocation without clearing
  it, so sessions revoked on other devices stay revoked. Drops the
  login/SCIM-reactivation denylist-clearing paths (allow_user/allow_idp_sub).
- Refresh: gate the disabled-account check on the post-grant identity (not just
  the old sub); attempt the IdP grant before consuming the refresh token and
  return a retryable 503 (restoring the token) on transient IdP errors instead
  of force-logging-out a live session.
- Gate the oidc blueprint at request time on AUTH_TYPE=oidc, so non-oidc
  deployments cleanly 404 these routes instead of 500-ing on an unset
  OIDC_ISSUER (mirrors SCIM_ENABLED).
- Surface revocation write failures: back-channel logout returns 502, and SCIM
  deactivation rolls back and returns 503, when the denylist write fails — so
  the IdP retries instead of recording a logout/deprovision that didn't revoke.
- Back-channel logout: require `jti`, run the replay check unconditionally, and
  reject stale `iat` beyond the replay-cache window.
- Make migration 0017 idempotent (IF NOT EXISTS) so re-apply can't wedge startup.
- SCIM userName matching is case-insensitive (caseExact=false) for the list
  filter and create-dedup.

Tests added/updated across test_oidc.py, test_scim.py, test_auth.py,
test_app_routes.py and the SCIM integration test.
2026-06-10 13:58:58 +01:00
Alex 496dfc42a0 Merge pull request #2531 from arc53/new-visibility
feat: visibility settings
2026-06-10 01:46:58 +01:00
Alex fc88d11594 fix: test 2026-06-10 01:46:26 +01:00
Alex 160736f075 feat: visibility settings 2026-06-10 01:40:53 +01:00
Alex 054f0f1b8b feat: oidc renewal, groups, logout, scim 2026-06-10 00:57:18 +01:00
Alex 8bc777a428 feat: oidc login 2026-06-09 23:24:44 +01:00
Alex f31910569f Merge pull request #2529 from arc53/fix-v1-continuation
feat: better tool call followup on compat mode
2026-06-09 19:08:30 +01:00
Alex 5d8187126d fix: tests 2026-06-09 19:08:15 +01:00
Alex 28d62f4199 feat: better tool call followup on compat mode 2026-06-09 18:20:14 +01:00
Alex 3ef973409e Merge pull request #2528 from arc53/conversation-visibility
feat: conv visibility
2026-06-09 00:30:50 +01:00
Alex 82bacfdb2b feat: conv visibility 2026-06-08 23:46:28 +01:00
Alex 6d4d96d12d Merge pull request #2527 from arc53/better-thread
Better thread
2026-06-08 22:41:50 +01:00
Alex f8e42cdce1 chore: update docs 2026-06-08 22:12:57 +01:00
Alex 73ed1cf607 feat: async events on asgi 2026-06-08 22:02:21 +01:00
Alex 1f06f31aa3 feat: better shutdowns 2026-06-08 15:07:31 +01:00
Alex 547d1a9036 Merge pull request #2526 from arc53/fix-notification
fix: revoke stale UI surfaces when durable state goes terminal
2026-06-08 13:48:36 +01:00
Alex 59e9523666 fix: revoke stale UI surfaces when durable state goes terminal
The "Tool approval needed" toast (and several sibling surfaces) could
linger after the state they represent was already gone. User-scoped SSE
events (tool.approval.required, schedule.autopaused, attachment.queued,
…) are durable and replayed on reconnect, but no terminal path emitted a
matching clearing event and the reconciler only wrote operator-facing
stack_logs — so a failed/expired message replayed its approval prompt
with nothing to act on, and the toast trusted event presence over the
actual message state.

Backend — emit a user-facing event on every terminal path:
- reconciler deletes pending_tool_state and publishes
  tool.approval.cleared when a stuck message is failed;
  cleanup_pending_tool_state does the same for TTL-reaped rows
- reconciler now publishes source.ingest.failed (stalled ingest),
  schedule.run.failed (timeout/pending) and schedule.completed (once)
- schedules PATCH-resume / DELETE publish schedule.resumed / .cancelled
  so a stale schedule.autopaused can't outvote them on replay
- store_attachment gains the on_poison terminal-event hook the ingest
  tasks already have; mcp_oauth_task gains a soft/hard time limit so a
  hung flow self-reports mcp.oauth.failed
- tighten the reconciler exemption to the (conversation_id, user_id)
  composite key

Frontend — stop trusting event presence over truth:
- notificationsSlice.resolveToolApproval evicts the matching
  tool.approval.required and persists its (stable) id dismissed so the
  backlog replay stays suppressed
- ToolApprovalToast drops approval events older than the resumable TTL
  window as a backstop for a lost clearing event
- schedulesSlice handles schedule.resumed / .cancelled / .completed
- upload dismissals now outlive the SSE backlog retention window

Tests cover the new clearing events at the reconciler, slice and
dispatch layers.
2026-06-07 13:17:50 +01:00
Alex 64e7210cf1 Merge pull request #2523 from arc53/feat/v1-response-format
feat(v1): response_format / response_schema (Structured Outputs) on /v1/chat/completions
2026-06-04 14:57:53 +01:00
Alex 45cae8f6b8 test(v1): cover structured-output, reasoning-leak, multimodal and stateless continuation
Raise patch coverage on the new v1 code paths:

- translator: content_to_text (text/image/string parts), extract_response_schema
  (response_format json_schema/json_object + response_schema shapes),
  _split_leaked_reasoning (single/multi/double-quoted/brace-in-value),
  translate_request structured-output + sampling + multimodal surfacing
  (normal and continuation branches), and translate_response /
  translate_stream_event reasoning-leak gating (answer + structured_answer).
- stream_processor: build_continuation_from_messages — pending-call rebuild,
  invalid/dict argument handling, last-tool-call selection, prior-message
  slice, and the no-tool-calls error.

translator patch coverage 54% -> ~100% of changed lines; stream_processor
continuation path fully covered.
2026-06-04 13:39:48 +01:00
Alex b212c5cedd test(v1): align tests with valid-chunk envelope and leak-strip signature
- test_standard_clients_can_ignore_docsgpt: docsgpt-only events now ride
  on a valid empty chat.completion.chunk (choices[0].delta == {}) so
  strict OpenAI clients accept the frame; assert the envelope plus the
  docsgpt extension instead of the absence of choices.
- split continuation persistence into stateful (conversation_id present
  -> save) and stateless (no conversation_id -> no save) cases.
- _fake_translate_stream_event mock accepts strip_reasoning_leak kwarg.
2026-06-04 13:03:04 +01:00
Alex 37417f99c8 fix(v1): address review feedback (multimodal provider gate, save default, leak scope, continuation sampling)
- Drop multimodal content for non-OpenAI-family providers (Google/Anthropic
  raised on OpenAI image_url parts) so multimodal requests degrade to text
  instead of returning 500.
- Stateless continuations (no conversation_id) default save_conversation to
  False, avoiding an orphan conversation with an empty question on every tool
  round (stateful continuations still default True).
- Only strip leaked reasoning from content for structured requests
  (response_format / json_schema / json_object); legitimate answers that mention
  the marker text are no longer corrupted.
- Forward sampling params (temperature, max_tokens, ...) on continuation turns.
- An explicit json_object request clears an agent-configured json_schema so it
  isn't silently overridden.
- Drop max_tokens when max_completion_tokens is also sent (OpenAI rejects both).
- Don't build a chatcmpl-None completion id from the placeholder "None" id event.
2026-06-04 12:55:47 +01:00
Alex 9eb7878ad3 feat(v1): support multimodal content arrays (text + image_url)
OpenAI-compatible clients send multimodal user turns as a `content` array of
typed parts. translate_request previously assigned the array straight to the
question, breaking the string-only retrieval / token-budgeting / history paths
(HTTP 500). Now:

- content_to_text() extracts text from content arrays for the question,
  history and system prompt, so the string paths work unchanged.
- The full content array (text + image_url parts) is preserved as
  `multimodal_content`, threaded to the agent and emitted as the final user
  message so images reach the model. Token budgeting uses the text only.

The content array (incl. image_url) now reaches the LLM call intact; images
render for vision-capable models. A text-only upstream model will reject the
image_url variant, as expected.
2026-06-04 12:23:08 +01:00
Alex 534ffc4e5b feat(v1): stateless tool continuation + sampling-param passthrough
- Stateless tool continuation. OpenAI-compatible clients (opencode, etc.)
  resend the full messages array — system, user, assistant(tool_calls),
  tool(results) — but no conversation_id, so the prior
  "conversation_id required for tool continuation" 400 broke every tool call.
  When no conversation_id is present, rebuild the agent + pending tool calls +
  tool results directly from the resent messages
  (StreamProcessor.build_continuation_from_messages) instead of loading
  server-side pending_tool_state, and call gen_continuation.

- Forward OpenAI sampling params (temperature, max_tokens,
  max_completion_tokens, top_p, frequency_penalty, presence_penalty, stop,
  seed) from the request to the LLM gen call; the agent otherwise uses its
  configured defaults.
2026-06-04 12:17:27 +01:00
Alex b1f220ec05 fix(v1): make docsgpt extension SSE frames valid chat.completion.chunks
Strict OpenAI clients (e.g. opencode / the Vercel AI SDK) validate every
streaming `data:` frame as a chat.completion.chunk. The id / source /
tool_call / tool_calls_pending events were emitted as bare `{"docsgpt": ...}`
objects with no `choices`, which those clients reject ("expected array,
received undefined" on `choices`).

Wrap the extension in an otherwise-empty chunk
(choices:[{index:0,delta:{},finish_reason:null}]) with a top-level `docsgpt`
field that OpenAI clients ignore, and skip the placeholder "None"
conversation_id frame emitted when the call is not persisted.
2026-06-04 12:07:30 +01:00
Alex 472918f577 feat(v1): support response_format / response_schema on /v1/chat/completions
Make the OpenAI-compatible Chat Completions endpoint honor per-request
Structured Outputs and keep it OpenAI-compatible.

- translate_request now forwards the request's `response_format` (json_schema)
  or a `response_schema` convenience field to the agent as its json_schema,
  overriding the agent-configured schema for that request.
- Honor `response_format.json_schema.strict` (default true); strict:false
  passes the schema through without forcing additionalProperties:false /
  all-required (OpenAI's lenient mode).
- Support `response_format {"type":"json_object"}` via the provider's native
  JSON mode.
- Keep `content` clean: some models echo their reasoning into content as
  stringified `{'type': 'thought', ...}` reprs when response_format is set.
  Strip those from content and reroute them to `reasoning_content` (OpenAI
  never puts reasoning in content), for both streaming and non-streaming.

Flow: translate_request -> StreamProcessor._configure_agent ->
Agent.json_schema / json_schema_strict / json_object -> _llm_gen ->
prepare_structured_output_format(schema, strict).
2026-06-04 11:51:13 +01:00
Alex c05b457b55 Merge pull request #2522 from arc53/local-call-full-compat
fix: issues with local tool calling on non openai endpoint
2026-06-03 23:24:03 +01:00
Alex 82426e3860 fix: cosmetic nit 2026-06-03 23:23:50 +01:00
Alex fbd37e627c fix: issues with local tool calling on non openai endpoint 2026-06-03 22:50:03 +01:00
Alex 8ec9474cc6 Merge pull request #2521 from arc53/responses-api
feat: OpenAI Responses API support (per-model api_flavor)
2026-06-03 15:11:09 +01:00
Alex 9a69e89096 fix: assistant input content must be output_text, not input_text
Live /v1/responses rejects input_text for the assistant role
(input[N].content[0] -> "Supported values are: 'output_text' and
'refusal'"). Revert the previous review-driven change: encode assistant
turns as output_text and user/system turns as input_text. The SDK type
union suggested input_* only, but the API enforces output_text per-role —
confirmed against the live endpoint.
2026-06-03 15:00:14 +01:00
Alex 27f9ddc8e0 fix: address PR review — Responses input encoding + finish Azure removal
- Replay prior-turn assistant text as input_text, not output_text: a
  Responses easy-input message only accepts input_* content parts, so the
  old shape would 400 on the second turn of every conversation (default
  store=false resends prior turns inline).
- Always request include=["reasoning.encrypted_content"] so in-turn
  reasoning carryover works whether or not the response is also stored
  server-side (OPENAI_RESPONSES_STORE).
- Add detail="auto" to input_image parts.
- Remove the now-dead azure_openai option from setup.sh / setup.ps1 and
  the docs, completing the AzureOpenAILLM removal.
- Tests: correct the input_text / image-detail / include assertions and
  add parallel tool-call and stream-error coverage.
2026-06-03 14:56:28 +01:00
Alex 02d1458467 Merge origin/main into responses-api
Reconcile the reasoning-passthrough change (#2520) with the Responses API
work: dedupe the shared reasoning_effort capability, keep upstream's
upstream_model_id YAML plumbing and the _apply_reasoning_effort chat path,
and run the Responses branch ahead of it so responses-flavor models skip
the chat-only top-level reasoning_effort injection.
2026-06-03 14:03:15 +01:00
Alex 142a9201f4 feat: add OpenAI Responses API support via per-model api_flavor
Route opted-in models to /v1/responses while keeping Chat Completions as
the default. The flavor is selected per-model in the catalog YAML and
threaded through ModelCapabilities (with a new reasoning_effort field).

- Translate chat-shaped messages/tools/structured-output into the
  Responses input / text.format / flattened-tool shapes at the provider
  boundary; stored history and intra-turn tool round-trips need no
  migration.
- Normalize the typed SSE event stream back into the existing handler
  contract (content / thought / tool-call chunks) so the streaming
  accumulator and OpenAI handler stay unchanged.
- Carry encrypted reasoning items across the in-turn tool loop
  (provider-local, keyed by call_id) for stateless reasoning continuity.
- OPENAI_RESPONSES_STORE opt-in enables server-side state and
  previous_response_id chaining across turns, persisted in message
  metadata.
- Set gpt-5.5 to api_flavor: responses, reasoning_effort: medium.
- Remove AzureOpenAILLM and the azure_openai provider/catalog.
2026-06-03 13:43:04 +01:00
Alex 6711a68e9a Merge pull request #2520 from arc53/reasoning-passthrough
feat: resoning passthrough
2026-06-02 21:00:56 +01:00
Alex ae6400a709 feat: resoning passthrough 2026-06-02 17:27:02 +02:00
Alex 84630d1314 Merge pull request #2518 from arc53/backup-fix
fix: backup model response parsing
2026-06-01 11:01:23 +01:00
Alex a5f735575f fix: backup model response parsing 2026-06-01 11:30:37 +02:00
Alex bcb763227c Merge pull request #2517 from arc53/better-retry
Better retry
2026-05-31 17:01:07 +01:00
Alex badd3830b7 feat: log gen end 2026-05-31 18:00:51 +02:00
Alex 85a2811e44 feat: log gen starts 2026-05-31 17:42:42 +02:00
Alex 7bb54e0f10 Fix: change model retry strategy 2026-05-31 16:43:40 +02:00
PavelandAlex be31deb4b1 Unify nav bar icons (lucide) and fix sidebar alignment (#2513)
* Icons-audit

* fix: linting

---------

Co-authored-by: Alex <a@tushynski.me>
2026-05-30 16:29:50 +01:00