Commit Graph
735 Commits
Author SHA1 Message Date
dayuan.jiang 65e3dd1dde fix(providers): keep thinking on the newest Claude models, fix the Bedrock ids
Measured on Bedrock: Claude Opus 4.7, 4.8 and every Claude 5 model reject
a thinking budget ("thinking.type.enabled") and a temperature, and all of
them accept adaptive thinking. The retry used to drop thinking, so with
a thinking budget set these models did not think at all. It now switches
to adaptive thinking with display "summarized"; without that setting the
models think but send no thinking text to show.

The suggested Bedrock ids had no region prefix, and the newer models only
answer through an inference profile id: "anthropic.claude-sonnet-5-5"
fails with "on-demand throughput isn't supported". Each suggested id was
called once; the Claude ones now start with "global.", Llama, Pixtral and
Nova 2 Lite with "us.". Nova Premier (end of life) and the ids without a
working profile are gone. The docs example had the same problem and a
wrong date.
2026-10-04 21:18:45 +09:00
dayuan.jiang 89a712bca4 feat(providers): suggest the Claude 5 and GPT 6 models, show GPT 6 reasoning
The suggested models stopped at Claude Opus 4.8 and GPT 5.5. They now
start with Claude Opus 5.5, Sonnet 5.5 and Fable 5.1, and gpt-6.1-sol,
gpt-6-sol, gpt-6-luna and gpt-6-astra (ids checked against the provider
lists). The Bedrock list is unchanged until its ids are checked.

The reasoning summary was only turned on for model ids containing o1,
o3, o4 or gpt-5, so GPT 6 models showed no thinking. It now matches the
o-series and gpt-5 or later by version, like the OpenAI SDK.
2026-10-04 20:51:12 +09:00
dayuan.jiang 6a99915446 fix(chat): clearer provider errors and no empty bubble, found with real models
- An error object sent inside the stream (OpenRouter's { code, message })
  showed as "[object Object]"; its message and status code are read now.
- A problem+json "detail" is added to the message: NVIDIA only said
  "Gone" for a retired model. 410 counts as model not found.
- "Cannot connect to API" from the SDK gets the connection hint.
- Text that is only whitespace (Kimi K2.6 sends a space before a tool
  call) no longer shows an empty bubble.
- allowSystemInMessages stops the warning on every request. Our system
  messages carry cache points; a client's own system messages are already
  dropped by the empty-content filter.
2026-10-04 20:15:51 +09:00
dayuan.jiang 6f5f7b668b fix(mcp-server): reject text between tags, which draw.io cannot open
draw.io reads any text inside a page as compressed page data, so a stray
text node makes the whole page fail with an atob error. gpt-5-mini sends
new cells with a literal "\n" between the tags; the edit card said
Complete while draw.io showed the error and kept the old diagram.

Validation now reports text between tags, and auto-fix turns a literal
\n, \t or \r between tags into whitespace. Other text goes back to the
model as an error. The compressed data directly under <diagram> is fine.
2026-10-04 20:15:44 +09:00
dayuan.jiang 6236124338 feat(settings): fetch the model list from any provider and flag models that cannot draw
The "Fetch models" button asks the provider for its models (OpenAI-style
/models, Anthropic, Google, Ollama, OpenRouter, Vercel Gateway, AIHubMix)
and shows them in a searchable picker. This replaces the route that only
worked for AIHubMix.

A snapshot of models.dev (MIT) says which models support tool calls.
Models without them get a "no tool calls" badge in the picker and a hint
in the model list, since drawing needs tool calls. Refresh the snapshot
with scripts/update-model-catalog.mjs.
2026-10-04 14:02:54 +09:00
dayuan.jiang ed1722ee9d refactor(settings): drop an unused index parameter 2026-10-04 13:49:57 +09:00
dayuan.jiang ab1a58f999 feat(errors): classify provider errors and show a hint the user can act on
- lib/llm-errors.ts sorts an error into about a dozen kinds (key
  rejected, no access, unknown model, no credit, rate limited, context
  too long, no image input, no tool calls, output cut off, provider down,
  cannot connect, timeout): first texts that name the cause precisely,
  then the HTTP status code, then general texts. It unwraps RetryError and
  hides keys and Bearer tokens in the provider's message
- The chat route uses it for errors before the stream and, through
  toUIMessageStreamResponse's onError, for errors in the stream. Errors
  of the model's own tool call stay as they are: the same text goes back
  to the model so it can fix the call
- The chat shows the hint in the user's language, then the provider's
  message; a rejected key, missing access or unknown model adds an "Open
  model settings" button. The Test button shows the same hints
- Fixes: our message "API key is required when using a custom base URL"
  was replaced by "Authentication failed" because it contains "key"; a
  provider's "Rate limit exceeded" opened this site's quota toast; an
  error body like {"error": ...} was shown as raw JSON; the Test button
  matched "401" in the message, where providers rarely put it
- Remove the string matching fallbacks in the chat panel
2026-10-04 13:49:34 +09:00
dayuan.jiang 99890e9e37 feat(settings): link to each provider's key page and clean up base URLs
- A "Get API key" link next to the API Key field for the 19 providers
  that have a key page (from env.example and the providers' docs). 17
  answered 200 to curl; OpenAI's is behind a Cloudflare challenge and
  DeepSeek's behind a regional block, both checked in Chrome
- Base URLs drop spaces, trailing slashes and a pasted endpoint path
  (/chat/completions, /completions, /messages, /responses), which the
  SDK would otherwise append a second time and get a 404. getAIModel does
  this for the chat and the Test button; the field does it on blur and
  shows the URL requests go to
2026-10-04 13:37:39 +09:00
dayuan.jiang c24aae6de0 refactor(providers): one model factory for chat and the settings Test button
- getAIModel resolves credentials (client key, server env vars, the
  existing SSRF rules) and createModel builds the model by SDK. The
  provider-by-provider switch shrinks from 24 cases to the few that
  differ (lib/ai-providers.ts 1531 -> 1106 lines)
- /api/validate-model calls getAIModel instead of its own 24-case switch
  (503 -> 175 lines), which had drifted from the chat: it built Azure
  with createOpenAI, Kimi and MiMo with createOpenAI instead of
  createDeepSeek, and the official OpenAI endpoint with Chat Completions.
  A passing test now means the chat works
- Plain OpenAI-compatible providers (SiliconFlow, SGLang, ModelScope,
  GLM, Qwen, Qiniu, Novita, Atlas Cloud, EdgeOne, Doubao, MiniMax in
  OpenAI mode, AIHubMix on a custom URL) use @ai-sdk/openai-compatible,
  which reads reasoning_content, so their reasoning shows, and accepts
  SGLang's stream as is (its 95-line stream rewrite is gone).
  includeUsage keeps token usage for quotas. <think> tags in their text
  become reasoning (extractReasoningMiddleware)
- SGLang without a base URL used OpenAI's endpoint; it now defaults to
  http://127.0.0.1:8000/v1 like the Test button did
- Chat requests to a client base URL refuse redirects, as the Test
  button already did (redirectGuardedFetch moves to lib/ssrf-protection)
- The Test button streams like the chat (the ModelScope special case is
  gone), times out after 15 s, does not retry, asks the model to call a
  ping tool and warns when it answers without one, and tests all models
  at once. The time each test took shows on its check mark
- Unknown provider names are rejected with Object.hasOwn, and the error
  texts list providers from PROVIDER_INFO instead of hand-kept lists
2026-10-04 13:31:47 +09:00
dayuan.jiang b7c26916cd refactor(chat): check the last tool part with the SDK's isToolUIPart
Drop the hand-written MessagePart and ChatMessage types that only served
this check.
2026-10-04 13:18:34 +09:00
dayuan.jiang 22a1d3f03b refactor: simpler streaming preview and small AI SDK cleanups
- useChat throttles streamed message updates (experimental_throttle,
  150 ms), replacing the two hand-written 150 ms timers of the
  display_diagram and edit_diagram previews (94 lines less). The preview
  now only runs while the input streams; once it is complete the tool
  handler takes over, so a queued preview can no longer redraw an edit
  the handler rejected and rolled back. Measured on a streamed 60-cell
  diagram: 41 redraws at least 97 ms apart, before 37 with gaps down to
  48 ms
- The diagram check endpoint uses streamText with Output.object instead
  of the deprecated streamObject, and returns its fixed result as a plain
  text response; new route test
- Import createGateway/gateway from ai and drop the direct
  @ai-sdk/gateway dependency
- The per-request message structure logs only print with
  DEBUG_LLM_PAYLOAD=true
- Remove an empty onFinish callback
2026-10-04 13:16:38 +09:00
dayuan.jiang ac62a58c9f fix(providers): update the v6 SDK packages and fix Claude and Gemini settings
- Update ai to 6.0.300 and the @ai-sdk providers to their latest v6-line
  versions. @ai-sdk/anthropic 3.0.47 did not know claude-opus-4-7/4-8
  and capped their output at 32000 tokens; 3.0.127 allows 128000
- Drop the fine-grained-tool-streaming beta header for the Anthropic API:
  the provider now streams tool input per tool (eager_input_streaming)
- Claude 4.7 and later reject a non-default temperature/top_p/top_k and
  the extended thinking budget with a 400. A middleware retries once
  without them, so TEMPERATURE and *_THINKING_BUDGET_TOKENS no longer
  break those models
- Prompt caching also reaches Claude on the Anthropic API and OpenRouter;
  before, only Bedrock got a cache marker
- GOOGLE_TOP_K and GOOGLE_TOP_P never reached Gemini: they were sent as
  Google provider options, which drops them. They are call settings now.
  GOOGLE_CANDIDATE_COUNT and GOOGLE_REASONING_EFFORT, which the provider
  does not support, are removed
- Add @ai-sdk/openai-compatible as a direct dependency
2026-10-04 13:07:56 +09:00
dayuan.jiang d32eb25523 refactor(web): share prompt examples and the shape library with the MCP server
- The three XML examples (swimlanes, two edges, waypoints) that the web
  system prompt, the display_diagram description and the MCP drawing
  guide each had a copy of now live in packages/mcp-server/src/xml-examples.ts
- The shape library group list and reader come from the MCP
  shape-library.ts; getShapeLibrary takes the folder, and the web route
  passes docs/shape-libraries under the working directory as before.
  Only the 30 known library names are read. Error texts differ slightly
- next.config.ts traces docs/shape-libraries/*.md for /api/chat, since
  the read now happens in another module
- Every prompt is byte-for-byte unchanged: the web system prompt for five
  model ids with and without minimal style, and the MCP drawing guide,
  compared before and after; the library list and swimlane example match
  the old tool description text exactly
2026-10-04 12:58:24 +09:00
dayuan.jiang bf4e95dcfb refactor(web): edit and wrap diagrams with the MCP server's code
- edit_diagram runs the MCP server's editDiagram: every new_xml is checked
  first, one cell per operation, and after the edit only the target page
  is checked, rejecting only errors this edit introduced. An unrelated
  problem elsewhere in the document no longer blocks every edit. The
  error lists each failed operation
- The streaming edit preview uses the MCP applyDiagramOperations
- Delete applyDiagramOperations (292 lines) and wrapWithMxFile from
  lib/utils.ts, and the unused hand-copied scripts/test-diagram-operations.mjs
- One blank document (BLANK_MXFILE) for the web app and the MCP preview,
  replacing four copies
- Saving a .drawio wraps a bare model with normalizeToMxfile
- The empty-diagram check uses hasCells, which also counts cells wrapped
  in a UserObject/object
- DiagramOperation is the MCP type
- The wrapped-cell and empty-diagram tests now run against the MCP code
- New e2e test: edit_diagram changes the canvas, and a failing edit
  leaves it as it was
2026-10-04 12:51:20 +09:00
dayuan.jiang e49e9f0c47 refactor(web): validate and repair diagram XML with the MCP server's engine
- Delete the web app's own copy of the XML checks and repairs from
  lib/utils.ts (1,074 lines). loadDiagram now uses the MCP server's
  validateAndFixXml without the strict checks, because the XML may hold
  the user's own diagram
- display_diagram and append_diagram prepare the model's XML with the new
  shared prepareNewDiagram, also used by the MCP create_new_diagram: wrap,
  validate strictly and auto-fix while it is still a bare model (where
  duplicate ids are renamed), then turn it into an mxfile
- The streaming preview of display_diagram no longer redraws the model's
  raw cells after the tool handler loaded the checked diagram, and drops
  a queued preview once the input is complete. That redraw lost
  auto-fixes and UserObject/object wrappers, so a linked cell lost its
  label; it also showed a second error toast
- The web repair regression tests now run against the MCP functions
- New e2e test checks the canvas content after display_diagram
- Fix the e2e upload tests, whose file input locator also matched the
  template import input
2026-10-04 12:44:01 +09:00
dayuan.jiang b7c543ca70 refactor(mcp-server): make the diagram modules usable from the web app
The web app will reuse the MCP server's XML engine instead of its own
copy in lib/utils.ts, so these modules now run in the browser too.

- Relative imports end in .ts, rewritten to .js by tsc
  (rewriteRelativeImportExtensions); Next.js resolves them directly
- Every module uses the global DOMParser/XMLSerializer: native in the
  browser, linkedom in Node via installDomPolyfill. pages.ts parsed with
  linkedom but serialized with the global serializer, which throws in
  the browser
- The saxes syntax check moves to xml-syntax.ts, so the browser does not
  pull in linkedom; it now also rejects undeclared prefixes such as
  xlink:, as the browser does
- Page decompression uses pako and atob instead of node:zlib and Buffer
- hasCells moves to pages.ts, away from the file system code
- The duplicate cell id check counts UserObject/object ids
- wrapCellsInModel drops comments and text before the first cell, which
  the web app accepts today
- validateAndFixXml takes { strict: false } for diagrams with user content
- Web tests run these modules with a browser DOM (jsdom)
- saxes becomes a direct dependency of the web app
2026-10-04 12:25:53 +09:00
dayuan.jiang 289e6bc25f fix: log Editable SVG saves, use local dates in file names, drop unused Electron dialogs
- /api/log-save accepted only drawio, png and svg, so saving as Editable
  SVG (xmlsvg) got a 400 and was never recorded in Langfuse
- The default download name and the template export name used the UTC
  date, which is the previous day on mornings in East Asia
- Remove the Electron openFile/saveFile IPC handlers, their preload
  bindings and types; nothing in the app calls them
2026-10-04 11:52:53 +09:00
dayuan.jiang 899924ba98 fix(mcp-server): fix duplicate page exports and auto-save deleting user files
- Preview page: keep an MCP export open until the server has its result.
  A poll answered before that still saw the request and started the same
  export again, so a parallel page export could write the previous
  page's image into its file
- Auto-save only removes its own mcp-*.drawio files, so a DRAWIO_DATA_DIR
  that also holds the user's diagrams keeps them
- screenshot_diagram captures a page that has no id attribute by loading
  just that page, like export_diagram
- An empty <Array as="points"/> no longer hides orphan mxPoints that
  come after it
- POST /api/state refuses a push without xml, which used to wipe the
  stored diagram
- Clear exportOptions when an export ends, reuse hasCells for the empty
  diagram check, and reword two log lines
2026-10-04 07:38:17 +09:00
dayuan.jiang 392c8af84a docs(mcp-server): describe the new MCP features in all READMEs
- MCP server README: 13 tools, drawing rules and shape libraries,
  screenshots, all-or-nothing edits, auto-save and how to continue a
  diagram later, DRAWIO_DATA_DIR and DEBUG, offline use with a local
  draw.io, and what to do when a background tab makes exports time out
- Fix two errors: History is a button at the top right of the preview
  page, and exports are not limited to .drawio
- Claude Code plugin README: same tool list, formats and settings
- Root READMEs (English, Chinese, Japanese): short list of what the MCP
  server can do
2026-10-04 07:12:45 +09:00
dayuan.jiang 127562b0e5 feat(mcp-server): add theme menu, dark mode and editable SVG to the preview
- Pass themes=1 and dark=auto to the draw.io iframe, so the Extras menu
  offers the Theme submenu and draw.io follows the system dark mode; the
  header and dialogs follow it too through prefers-color-scheme
- Download dialog: new Editable SVG (.drawio.svg) format through draw.io's
  xmlsvg export, default name diagram-YYYY-MM-DD, Enter saves and Escape
  closes it and the history dialog
- export_diagram accepts format "drawio.svg" and detects the .drawio.svg
  extension
- Use system fonts instead of Google Fonts, so the page works offline with
  a self-hosted draw.io (DRAWIO_BASE_URL)
2026-10-04 07:10:01 +09:00
dayuan.jiang f52f95025f refactor(mcp-server): move the preview page into src/preview
The 580-line page template in http-server.ts becomes index.html,
preview.css and preview.js, copied to dist/preview by the build and
filled at request time. The rendered page is unchanged apart from the
session id and draw.io origin now coming from a small config script.
Biome skips the folder because of the {{placeholders}}, as it never
linted the old template string either.
2026-10-04 06:46:09 +09:00
dayuan.jiang 667f678eb3 feat(mcp-server): auto-save each session's diagram to a .drawio file
- Save the latest diagram of every session 1 second after each change
  (AI write, browser edit, history restore) to ~/.next-ai-drawio/<id>.drawio,
  keep the newest 50, flush on shutdown; DRAWIO_DATA_DIR changes the folder
  and "off" disables it, like the web app's IndexedDB sessions
- start_session names the file, so a resumed conversation can reopen the
  diagram with load_diagram after the MCP process restarted
- Fix PNG/SVG exports randomly timing out: a previous export's 10 second
  timer cleared the export in progress, and a late reply could be taken
  for the current one; exports are now numbered
2026-10-04 06:42:44 +09:00
dayuan.jiang 81ad317375 feat(mcp-server): add screenshot_diagram so the model can check its render
- New read-only screenshot_diagram tool returns the rendered page as a PNG
  plus the web app's visual checklist (overlaps, edges crossing shapes,
  readability, layout, rendering errors), replacing the web app's
  separate vision model with the host model's own vision
- PNG exports use draw.io's width and pageId options: screenshots stay
  under ~140,000 base64 characters and page exports no longer swap the
  page on screen
- Fail fast with a clear message when the preview tab stopped polling
  (browsers throttle background tabs)
- Mention the screenshot step in the drawing guide and instructions
2026-10-04 06:29:33 +09:00
dayuan.jiang 9de281627e feat(mcp-server): bring the web app's drawing knowledge to MCP
- Add a drawing guide adapted from the web system prompt (layout, edge
  routing, styles, minimal style, editing rules), returned by
  start_session, a new get_drawing_guide tool and the diagram-workflow prompt
- Add get_shape_library with the 30 icon libraries; the build copies
  docs/shape-libraries into dist and CI checks the packed files
- Accept bare mxCell lists in create_new_diagram and add_page; the server
  adds the wrapper and root cells
- Send server instructions, shorten create_new_diagram's description to
  fit Claude Code's 2,048 character limit, and annotate every tool
- Fix dead links and the totals in docs/shape-libraries/README.md
2026-10-04 06:15:46 +09:00
dayuan.jiang 6d67a0ec69 fix(mcp-server): make edit_diagram all-or-nothing and fix preview sync races
- edit_diagram applies nothing when any operation fails, rejects invalid or
  multi-cell new_xml, validates only the target page, and returns the
  current page XML on every rejection (including stale edits)
- Fix get_diagram reading the old diagram right after an AI write: the
  preview pushed its sync reply with a newer version than it was taken at
- Keep a user edit that loses the race with an AI write in history and
  tell the user in the preview
- Autofix removes only exact foreign tags (a stray <mxGraph/> deleted
  <mxGraphModel>), fixes tag case, drops orphan <mxPoint>s, and rejects
  unknown element names in model XML
- Edit empty and compressed pages; PNG exports use the page on screen;
  tag download exports; reload from the server after a page export
- Expand ~ in paths, tell the model when the browser sync timed out,
  use registerPrompt, require SDK ^1.31.0
2026-10-03 22:03:44 +09:00
dayuan.jiang a46787c1b8 fix(mcp-server): fix XSS and crashes, make XML validation strict
- Validate and escape the mcp session id; only serve localhost Host/Origin
- Malformed URLs and session ids return errors instead of crashing the process
- Strict XML syntax check with saxes (linkedom never reports parse errors)
- autoFixXml no longer corrupts valid XML; attribute newlines serialized as entities
- Sessions stay alive while polled; browser pushes carry a base version (409 on conflict)
- Page tools respect the edit gate; UTF-8 bodies decoded correctly
- Export replies matched to requests and serialized; xml sync export handled
- UserObject/object cells addressable by id; history restored by stable id; logs off stdout
2026-10-03 17:45:41 +09:00
dayuan.jiang 95f4b4b92b fix(electron): decrypt keys after ready and harden navigation and IPC
- Apply preset env after app ready, so Windows/Linux get decrypted keys
- Never re-encrypt ciphertext; restore env when switching or removing presets
- Block navigation away from the app, open external links in the browser, check IPC senders
- Keep inherited proxy settings, default NO_PROXY for localhost
- Serialize server start/restart, kill stuck processes, follow port changes
- Atomic config writes, keep corrupt files as backups, remember the server port
- Menu and settings window stay in sync; dev script gets the decrypted preset env
- Use app.isPackaged, parse inline .env comments, drop .env files from the bundle
2026-10-03 17:45:41 +09:00
dayuan.jiang 3193d20e00 fix(model-config): keep model selection valid and fix admin panel edge cases
- Fall back to the default server model when a saved one disappears
- Sync model config across tabs
- Validation uses the base path and sends the access code
- Model ids edited as drafts (no empty, duplicate or padded ids)
- Credential changes reset validation; stale validation results are dropped
- Admin: generateId over HTTP, env-locked group switches, discard and toggle fixes,
  clearing a secret field keeps the saved key, first provider not auto-default when .env sets AI_MODEL
- Model selector items use unique values
2026-10-03 17:45:41 +09:00
dayuan.jiang 87edf2e19d fix(chat-input): stop template dialogs from sending and fix attachment races
- Template dialogs no longer submit the outer chat form
- Sending is blocked while files or URLs are still extracting
- File and URL extraction no longer drop or resurrect entries
- IME composition Enter no longer sends
- Tool call cards show the error text; keyboard handling on cards fixed
- Template import available when empty, edit dialog resets, saved templates refresh
- Only png/jpeg/gif/webp images accepted, SVG sent as text; PDF objects released
- parse-url request sends the access code
2026-10-03 17:45:41 +09:00
dayuan.jiang 5c7613ea09 fix(diagram): fix autosave staleness and XML repair corrupting valid diagrams
- Autosave guard reads refs, so edits after a theme or dark mode switch are kept
- Duplicate-id check and rename run per page; repair loop no longer quadratic
- autoFixXml no longer breaks style values, rich text &quot; or single-line cells
- extractCompleteMxCells keeps the cell after a self-closing cell
- Better truncation detection; object/UserObject wrapped cells are editable
- Exports for thumbnail, PNG and save are routed by tag instead of a shared resolver
- History stores the full document; storage errors are reported, no auto-deletion of chats
- IndexedDB connection reopens after errors; focus refresh throttled
- Keep ?session= on locale redirect, map zh-Hant to zh-tw for draw.io
2026-10-03 17:45:41 +09:00
dayuan.jiang 79b4c52741 fix(chat): keep saved diagrams and pages when restoring, editing and retrying
- Restored sessions no longer replay the last display_diagram over the saved diagram
- Failed or stopped edit_diagram restores the canvas
- Message snapshots keep the full multi-page document
- "Improve with suggestions" uses the normal send path (headers, xml, retry counters)
- Editing a message keeps its file/URL sections; cached example edits work
- New chat's first autosave no longer resets the UI
- Validation retries counted per user turn; validate-diagram sends the access code
- Cached examples only match the example files on an empty canvas
- Template sends keep attachments and wait for extraction
2026-10-03 17:45:41 +09:00
dayuan.jiang 528b6e54c8 fix(api): require access codes and limit sizes on helper routes
- Shared checkAccessCode for validate-diagram, validate-model, parse-url, verify-access-code
- parse-url: 5 MB streamed body limit; validate-diagram: 5 MB image limit
- validate-model refuses redirects when private URLs are blocked
- Admin settings state shared across module instances via globalThis
- Server model ids: unique slugs (non-ASCII names encoded), duplicates rejected
- Panel Bedrock credentials stored as ADMIN_AWS_* so the DynamoDB client keeps its own
- Locale redirect keeps basePath and query; EdgeOne function drops open CORS and checks the access code
- Providers payload reports whether .env sets a default model
2026-10-03 17:45:41 +09:00
dayuan.jiang 366480426d fix(chat): close credential leaks and harden the chat route
- Vertex: a client-supplied base URL only works with the client's own Vertex key
- Accept only data: URLs for file parts in every message, so the server never downloads them
- Output budget retry accounts for the thinking budget Bedrock/Anthropic add, and reads
  Volcengine, DashScope, SGLang and vLLM rejections; falls back to 16000 once
- x-max-output-tokens can only lower the budget on server credentials
- On server credentials only server models or AI_MODEL entries can be used
- Drop tool results together with the invalid tool calls they belong to
- Count quota tokens as input + output (cached tokens were counted twice)
- Private-URL check for custom base URLs, end Langfuse traces on error/abort/early return
- Fix repairToolCall ordering and placeholder, align edit_diagram prompt with operations
- Panel Bedrock keys are read from ADMIN_AWS_*; forward the access code to EdgeOne
- isMinimalDiagram only treats root cells as an empty canvas
2026-10-03 17:45:41 +09:00
renovate[bot] a45e5b6796 fix(deps): update minor and patch dependencies (#948)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-10-02 06:42:01 +00:00
renovate[bot] 027cd88c90 fix(deps): update minor and patch dependencies (#932)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-01 12:55:24 +00:00
Dayuan Jiang 155ef4f7ac fix: raise the output budget so reasoning models reach the tool call (#927)
* fix: raise the output budget so reasoning models reach the tool call

A reasoning model spends the output budget in order: thinking first, then prose,
then the tool call. With 16000 the thinking alone can consume all of it, so the
turn ends with finishReason "length" before display_diagram is ever called. The
canvas stays empty and nothing surfaces in the UI, because no tool call means no
tool error, and the client never reads finishReason.

Measured on openrouter deepseek/deepseek-v4-flash, the model from the report:
- max_tokens=800 with reasoning on returns reasoning_tokens=800, empty content,
  finish_reason length. So reasoning is billed against this budget, not exempt.
- refining an existing diagram (19k chars of XML in the input) produced 49142
  chars of reasoning, zero tool calls, finishReason "length" at 16000
- the same request at 40000 finished and called edit_diagram with 12 operations

64000 cannot just be sent to every model: bedrock claude-3-haiku caps at 4096,
nova-lite at 10000, and the openrouter deepseek-r1 endpoint counts input and
output against one 64000 ceiling. All three name the real limit in the 400, so
parse it and retry once. Verified: nova-lite logs "64000 rejected, retrying with
10000" and then completes its tool call.

Also expose the budget in Settings. It is sent as a header rather than read from
env only, so desktop users can raise it themselves without an env file.

vercel.json goes back to the 300s it had before #238 traded it for $2-4/month.
That is now Vercel's own default, and billing pauses while the function waits on
the model, so the saving that motivated 120s no longer applies. edgeone.json is
left alone: its 120 may be that platform's actual ceiling.

* fix: only reinterpret an error as a budget rejection when it says so

Review of the first commit found the retry could fire on errors that have
nothing to do with the budget, which would replace a readable provider error
with a truncated response: exactly the symptom this PR exists to remove.

- Drop the generic "lower than N" pattern. For the Bedrock message it was dead
  code, since "model limit of N" matches first with the same number. Left live,
  it would read a number out of any message shaped like "must be lower than 2".
- Skip errors whose status is not 400 or 422, so auth and rate-limit failures
  are never reinterpreted.
- Require the parsed ceiling to be at least 1024. Below that a diagram cannot
  come out whole, so retrying would hide the error behind broken XML.
- Validate MAX_OUTPUT_TOKENS from env the same way as the header, so a stray
  "-1" falls back instead of reaching the provider.

Adds tests for the retry wrapper itself, which had none: it retries once with
the named ceiling, leaves a 401 alone, does not retry when the ceiling is not
smaller, propagates a second rejection, and preserves the other call options.

Re-verified against the live APIs: bedrock nova-lite still logs "64000 rejected,
retrying with 10000" and completes its tool call, and deepseek-v4-flash still
finishes normally at 64000.
2026-08-22 20:47:10 +09:00
nb213andbinyangzhu000-sudo 12903cd516 docs: acknowledge Atlas Cloud sponsorship (#919)
* docs: acknowledge Atlas Cloud sponsorship

Signed-off-by: binyangzhu000-sudo <[email protected]>

* docs: add Atlas Cloud logo assets

Signed-off-by: binyangzhu000-sudo <[email protected]>

---------

Signed-off-by: binyangzhu000-sudo <[email protected]>
Co-authored-by: binyangzhu000-sudo <[email protected]>
2026-08-22 08:43:45 +09:00
Dayuan Jiang 96bca2b37b fix: always send maxOutputTokens; default 16000 (#915)
Unset does not mean the model's maximum — the provider fills in its own, and
Bedrock's is 4096 (measured: converse with no inferenceConfig on
us.anthropic.claude-opus-5 returns stopReason=max_tokens at exactly 4096).

A 30-cell diagram is ~3000 tokens of XML, so anything larger arrived as truncated
JSON and nothing reached the canvas. Small diagrams fitted, which made it look
intermittent.
2026-08-10 17:07:30 +09:00
NgoQuocViet2001anddayuan.jiang fd758b9e87 fix: preserve multi-page diagrams after export (#895)
* fix: preserve multi-page diagrams after export

* fix: keep chartXML sourced from autosave to preserve multi-page state

The export event's data.xml (xmlsvg format) contains compressed <diagram>
payloads, which would break applyDiagramOperations/replaceNodes consumers
that need plain <root> elements. Instead of writing export results into
chartXML, stop overwriting it entirely: autosave already delivers the full
uncompressed multi-page document, and loadDiagram covers AI-driven updates.

Also stop overwriting chartXMLRef with the page-only export before sending
a chat message, so session persistence never sees single-page XML.

Keep the data.xml preference for .drawio file downloads (compressed pages
are a valid drawio format).

---------

Co-authored-by: dayuan.jiang <[email protected]>
2026-08-08 12:30:22 +09:00
Dayuan Jiang 81da9fad83 ci: exclude public/ from Biome and disable automerge for Biome updates (#912)
Biome 2.5.7 (auto-merged by Renovate in #904 with failing checks) started
parsing SVG files, breaking lint on generated assets in public/. Exclude
the whole public/ directory instead of just *.svg so future parser
changes can't hit generated files again.

Biome minor updates can also introduce new lint rules for source code,
so require manual review for its Renovate PRs instead of automerge.
2026-08-08 12:30:07 +09:00
Dayuan Jiang 6493652ff0 fix: prevent model selector label overflow (#910)
* fix: constrain model selector label width

* ci: ignore generated SVG assets in Biome
2026-08-08 10:31:46 +09:00
renovate[bot] be8f26d6b1 fix(deps): update minor and patch dependencies (#904)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-07 00:40:42 +00:00
Kobi Hikri cd02b2de92 ci: attach provenance and SBOM attestations to the published image (#902)
* ci: attach provenance and SBOM attestations to the published image

* ci: restore trailing newline at end of file
2026-08-06 11:03:08 +09:00
renovate[bot] 6e653942b0 chore(deps): update radix ui packages (#903)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-01 09:06:40 +00:00
nb213andbinyangzhu000-sudo c8463aefa7 Add Atlas Cloud provider support (#896)
* Add Atlas Cloud provider support

* fix: restore files removed by Atlas provider PR

---------

Co-authored-by: binyangzhu000-sudo <[email protected]>
2026-07-30 23:27:18 +09:00
Dayuan Jiang 4b07228320 feat(mcp): add load_diagram tool to load .drawio files into the session (#893)
* feat(mcp): add load_diagram tool to load .drawio files into the session

Loading a file previously required the agent to read the file itself and
pass the entire XML through create_new_diagram - wasteful for large
diagrams and impossible for draw.io's compressed save format.

load_diagram takes a file path; the server reads it, decompresses any
compressed pages (base64 -> raw deflate -> URI-decode, per page), and
replaces the session document. The loaded XML is deliberately NOT marked
as seen by the edit gate: the model only supplied a path, so it must
call get_diagram once before editing.

* chore(mcp): version 0.2.3

* fix(mcp): report package.json version in the MCP handshake

The McpServer metadata version was a separate hardcoded string that
never matched the published version (stuck at 0.1.2, then 0.3.0 while
npm shipped 0.2.x). Read it from package.json at startup instead —
works from both src/ (tsx) and dist/ (published build).
2026-07-12 19:54:42 +09:00
NgoQuocViet2001anddayuan.jiang f3a85558d8 fix(mcp): replace edit_diagram 30s time gate with content comparison (#890)
* fix(mcp): keep diagram context valid during edits

Closes #885

* fix(mcp): replace edit_diagram time gate with content comparison

The 30s wall-clock gate rejected slow-but-correct clients (#885).
Instead of a timeout, remember the exact state-store XML the model
last saw (get_diagram / create_new_diagram / edit_diagram / page CRUD)
and reject edit_diagram only when the live browser state differs -
i.e. the user made edits the model hasn't seen yet. Slow reasoning
no longer trips the gate, while unseen manual edits still do.

* docs(mcp): align edit_diagram/get_diagram descriptions with content-based gate

The 'You MUST call get_diagram BEFORE this tool' requirement and the
'Skipping get_diagram WILL cause user's changes to be LOST' warning no
longer match server behavior: a stale edit is rejected with no side
effects, never silently applied. Describe the freshness check instead,
and direct get_diagram usage at its real purpose - learning the current
diagram content when the model doesn't already know it.

* fix(mcp): compare diagram content structurally in the edit gate

draw.io re-serialises the document when pushing state back (attribute
order, pretty-printing, regenerated diagram ids, viewport attributes,
mxfile host), so byte comparison could flag an unchanged diagram as
stale. Fingerprint what a user can actually change instead - page set,
page names, and each page's root cell tree with sorted attributes -
keeping byte equality as the fast path. A bare mxGraphModel now also
fingerprints identically to its single-page mxfile wrapping.

* fix(mcp): don't compare page names against bare mxGraphModel pushes

A bare <mxGraphModel> pushed by the embed/sync path carries no page name,
so normalizeToMxfile invents "Page-1" — falsely reading any custom page
name as a content change and re-triggering the stale rejection on every
edit. When either side of the gate comparison is a bare mxGraphModel,
fingerprint cell trees only; full-mxfile comparisons still detect renames.

* chore(mcp): bump version to 0.2.2

* chore(mcp): sync package-lock.json version to 0.2.2

---------

Co-authored-by: dayuan.jiang <[email protected]>
2026-07-12 15:33:20 +09:00
Dayuan Jiang 4f09d9461a ci: auto-publish mcp-server to npm via OIDC trusted publishing (#891)
Publishes @next-ai-drawio/mcp-server when packages/mcp-server changes on
main and the package.json version isn't on npm yet. Uses npm trusted
publishing (OIDC) - no token secret, no OTP, works with the strictest
2FA setting.
2026-07-12 15:33:07 +09:00
4984be82a1 feat: add MiMo (Xiaomi) as AI provider (#887)
* feat: add MiMo (Xiaomi) as AI provider

* fix: correct MiMo default base URL, suggested models, and reasoning support

- Default base URL was the China Token Plan endpoint (tp- keys only);
  switch to https://api.xiaomimimo.com/v1 which works with standard
  pay-as-you-go sk- keys. Token Plan users can override in settings.
- Replace deprecated mimo-v2-flash suggestion with mimo-v2.5
  (v2 series was deprecated on 2026-06-30).
- Use createDeepSeek instead of createOpenAI so reasoning_content is
  passed back during multi-turn tool calls (MiMo returns 400 without
  it), matching the existing Kimi implementation.
- Add mimo to SINGLE_SYSTEM_PROVIDERS so system messages are merged.
- Fold validate-model case into the shared OpenAI-compatible group.
- Drop the Bot icon special case; models.dev serves a real xiaomi logo
  via PROVIDER_LOGO_MAP.
- Document MIMO_API_KEY/MIMO_BASE_URL in env.example and
  docs/{en,cn,ja}/ai-providers.md.

* feat: show base URL hint for MiMo in provider settings

MiMo has two endpoints tied to key type: pay-as-you-go keys (sk-...)
use the default api.xiaomimimo.com/v1, while Token Plan keys (tp-...)
require token-plan-cn.xiaomimimo.com/v1. Surface this under the Base
URL field like the existing MiniMax hint, in all four locales.

---------

Co-authored-by: mapengfei <[email protected]>
Co-authored-by: dayuan.jiang <[email protected]>
2026-07-12 09:10:12 +09:00
Dayuan Jiang 5bfd7b2468 fix: SSRF in /api/parse-url via DNS bypass and redirects (#878)
* fix: resolve DNS before SSRF check and block redirects in parse-url

isPrivateUrl() did string-only hostname matching and never resolved DNS,
so a public-looking name that maps to an internal IP (e.g.
127-0-0-1.sslip.io -> 127.0.0.1) passed the check while fetch/extract
later resolved it and reached internal services (GHSA-wqcv-5qvx-vx75).

- isPrivateUrl is now async: it keeps the fast string/literal-IP path,
  then resolves the hostname via DNS and rejects if any address is private.
- parse-url now fetches the page itself with redirect: "error" and parses
  via extractFromHtml(), since article-extractor follows redirects
  internally and drops a redirect option, which allowed a public URL to
  302 to an internal host.
- Update validate-model call site to await; add regression tests.

* fix: preserve charset detection and block CGNAT range in parse-url SSRF fix

Follow-up to the multi-reviewer review of the SSRF fix:

- Restore charset handling lost when switching from extract() to
  response.text(): non-UTF-8 pages (Shift_JIS/GBK/EUC/Big5, common on CJK
  sites this project targets) decoded as mojibake. Now read the body as
  bytes, detect charset from Content-Type / <meta charset>, and decode
  with TextDecoder before extractFromHtml.
- Wrap extractFromHtml in try/catch: it throws (not returns null) on
  empty/non-HTML bodies, which previously surfaced as a 500 instead of the
  intended 400.
- Add 100.64.0.0/10 (RFC 6598 CGNAT) to isPrivateIp; it is routable inside
  some cloud internal networks and was a residual SSRF target.
- Add tests for CGNAT, its boundaries, 0.0.0.0, and DNS-resolved IPv6.
2026-06-28 12:41:29 +09:00