Skip to content

Feat/ai custom endpoint - #41

Merged
do-md merged 2 commits into
mainfrom
feat/ai-custom-endpoint
Sep 13, 2026
Merged

do-md merged 2 commits into
mainfrom
feat/ai-custom-endpoint

Conversation

@do-md

@do-md do-md commented Sep 13, 2026

Copy link
Copy Markdown
Owner

What this changes

Which layer does this touch?

  • Application layer (macOS app, web app, plugins, helper libraries) — MIT
  • Editor kernel (.packages/@do-md/core/) — GPL-3.0

How it was verified

Contributor License Agreement

  • I have read the CLA and will sign it on this pull request when the
    automated check asks.

Agents could only talk to the two hardcoded presets, which locks out
reverse-proxied subscriptions and every provider we ship no preset for
(DeepSeek, Moonshot, a local server). Closes #28.

Provider ids become plain strings: the built-in presets stay in code,
while user-defined endpoints live in config as {id, label, baseUrl}
entries referenced by agents. Keeping the endpoint an entity rather than
an AgentConfig field means several agents can share one endpoint and its
API key — and deleting the last agent that uses a custom endpoint takes
the endpoint and its key with it, so no orphan credentials are left in
ai.json.

normalizeChatEndpoint accepts the three shapes people actually paste: a
bare host gets /v1/chat/completions, a version segment (/v1, /v1beta,
/openai/v1) only gets /chat/completions, and a full chat-completions URL
is passed through untouched as the escape hatch for gateways that route
differently.

completeAgentChat now takes the resolved endpoint instead of a provider
id, so the transport layer no longer owns an endpoint table; a run whose
custom endpoint has since been deleted fails with a clear message rather
than a bad request.

scripts/verify-ai-endpoint covers URL normalization and the provider
registry (upsert in place, key retained across edits, key removed with
the endpoint, dangling reference resolves to empty).
On desktop with a Chinese IME on, the picker vanished the instant it
appeared and the agent could never be summoned again. WKWebView holds a
character's keydown back until the composition engine releases it
(keyCode 229), so it arrives a few milliseconds AFTER the beforeinput
that opened the picker — and PickPane's "any other key dismisses" rule
then closed the picker on the very keystroke that opened it. It looked
intermittent because the first "@" of a session is typically typed in
ASCII mode; writing one Chinese instruction in the composer leaves the
IME in Chinese and every later "@" dies.

The keydown still carries its original hardware timestamp, which
predates the popover, so the picker now only accepts dismissal from keys
pressed after it appeared. Using the trigger event's timestamp rather
than the mount time keeps a character typed a few ms after the "@" a
real dismissal. Keys owned by a live composition are ignored too.

Also guard the composer's Enter with isComposing: confirming an IME
candidate was submitting a half-typed instruction.
@vercel

vercel Bot commented Sep 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
domd Ready Ready Preview Sep 13, 2026 2:46pm UTC

@do-md
do-md merged commit 2695a20 into main Sep 13, 2026
3 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 13, 2026

This branch was successfully deployed

1 active deployment
Preview — a291bd8c Deployed Sep 13, 2026 by vercel[bot]
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants