Commit Graph

565 Commits

Author SHA1 Message Date
YuTengjing 697f66a800 ✨ feat: expose router affinity hooks (#19648) 2026-09-18 17:40:03 +08:00
YuTengjing b3f55110f0 ✨ feat: capture provider-native stream diagnostics (#19643) 2026-09-17 01:52:41 +08:00
Arvin Xu 119c09ab39 ♻️ refactor(mecha): decide the model parameters of an LLM call in the shared core (#19586)
* ♻️ refactor(mecha): decide the model parameters of an LLM call in the shared core

Move the model-parameter rules out of the server's per-attempt hints into
`@lobechat/mecha` as `resolveModelParams(request, providers)`: which extend
params the model consumes (user row → provider card → canonical card),
where the reasoning effort comes from (topic pin, only for the pinned
model and, in a group topic, the answering agent → model-instance config →
sub-agent override), when assistant reasoning must be replayed (Kimi,
DeepSeek thinking, Meta, card support, provider fallback), the capability
defaults the context may assume, the history window, and the stream flag.

The server adapter now only supplies lookups (`createServerModelParamsProviders`)
and the run's frozen snapshots. `stream` falls back to the agent's
`chatConfig.enableStreaming` when the operation did not pin one; the
DeepSeek model-id predicates move to a dependency-free module so the shared
package can import them without the model bank.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 🐛 fix(server): trace the streaming mode the request really uses

The chat span recorded `ctx.stream ?? true` while the payload now honours
the agent's chat config, so a newly non-streaming request was labelled as
streaming in telemetry. The built context rides along on the LLM trace
input and both the payload and the span read the same resolved value.
Also point the add-model-provider integration map at the shared
resolver as the policy owner.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 21:39:34 +08:00
YuTengjing 81dd84cebb 🐛 fix: classify oversized model requests (#19568) 2026-09-15 16:15:14 +08:00
YuTengjing 8a3e849a1c 🐛 fix: preserve flat JSON provider errors (#19404) 2026-09-11 14:55:51 +08:00
Innei aba9e8150c 👷 build: migrate the test suite to vitest 5 (#19355)
* 👷 build: upgrade vitest to 5.0.0

Bump vitest and @vitest/coverage-v8 to 5.0.0 and migrate the configs for Vitest 4/5: drop the removed coverage.all option and replace poolOptions.forks.singleFork with maxWorkers/isolate.

* 👷 build: shim jest-dom matcher types for vitest 5

Vitest 5 exposes Assertion<R, T> and no longer reads the jest.Matchers augmentation that @testing-library/jest-dom ships, so its matchers vanish from expect(). Re-declare them on the Vitest interface, and replace the removed describe.sequential with { concurrent: false }.

* 👷 build: migrate the test suite to vitest 5

Vitest 5 (and @vitest/coverage-v8) replaces the esbuild dep optimizer with Rolldown, removed environmentMatchGlobs/coverage.all/poolOptions, forwards `new` to mock implementations, requires top-level vi.mock, and no longer resets automocks in restoreAllMocks. Migrate the root config to app/server projects with Rolldown optimizer options, shim the jest-dom matchers on Assertion<R, T>, and update the affected tests (constructor mocks, hoisted mocks, awaited assertions, explicit mock types, per-test mock defaults).

* 👷 build: make the openapi knowledge-base FileService mock constructable

tests/ was outside the per-area sweep; vitest 5 forwards `new` to the mock implementation, so the arrow implementation threw "is not a constructor".

* 👷 build: point the CI blob-report artifacts at the vitest 5 output dir

Vitest 5 writes blob reports to .vitest/blob instead of .vitest-reports, so the sharded jobs uploaded nothing and the merge jobs failed with ENOENT. Track the new directory in .gitignore too.

* 👷 build: refresh the desktop lockfile for vitest 5

apps/desktop/pnpm-lock.yaml is committed and the desktop build action installs with --frozen-lockfile, so the vitest/override change has to be reflected in it.

* 👷 build: keep the pre-vitest-4 coverage exclusions explicit

Vitest 4+ ships an empty coverageConfigDefaults.exclude, so configs that spread it silently lost the default exclusions (node_modules, dist, *.config.*) and counted more files. Inline the previous list.

* 🎨 style: format the migrated vitest configs

* 👷 build: keep canary's new class mocks constructable under vitest 5

exploreGraph/dispatchAuthorization/scopedDevices (canary #19375, #19352) mock classes with arrow implementations; vitest 5 constructs the implementation when the mock is newed, so the arrows throw 'is not a constructor'.
2026-09-10 22:18:49 +08:00
YuTengjing 306468e5cb 🐛 fix: support the canonical DeepSeek Flash model alias (#19385) 2026-09-10 17:03:17 +08:00
Arvin Xu 8a3e225247 🐛 fix(model-runtime): keep answer text when a delta carries reasoning and content (#19378)
Some OpenAI-compatible providers batch several tokens into one SSE frame, so the
tail of the reasoning and the head of the answer arrive in the same delta. The
transform returned only the reasoning chunk and dropped the content, which made
the reply start mid-sentence and lose whatever opening the system prompt asked
for.

Carry the reasoning chunk alongside the content branch instead of returning
early, so both survive in reasoning -> text order.

Measured over 24h of production traffic before the fix: ~10% of replies affected
on one custom proxy, ~0% on providers that never pack both fields into a frame.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 13:34:38 +08:00
Innei f6ba7c15c6 ⚡️ perf(model-bank): keep the aiModels catalog off the boot path (#19291)
* ⚡️ perf(model-bank): keep the aiModels catalog off the boot path

The model-bank root barrel re-exports the whole aiModels catalog (1.4 MB
raw), and a handful of value imports on the boot path (search config,
reasoning params, image/video defaults, provider enum) dragged it into the
first screen. Boot-path and route code now import the subpaths, the package
declares sideEffects: false and exposes ./utils and ./knowledgeCutoff, and an
eslint rule forbids value imports of the root barrel across src.

Also reach @lobechat/model-runtime/errors/specs and
@lobechat/utils/client/apiKeyManager directly so services/chat stops pulling
the error pattern tables and dompurify.

Web production build: first-screen gzip 2299 KB -> 2154 KB.

Claude-Session: https://claude.ai/code/session_01QKr8CsFLYmrgqbRTSipTNH

* 🐛 fix(lint): carry the model-bank ban through every scoped override

Flat config replaces no-restricted-imports per file instead of merging, so a
separate src/** block was dropped by the boot-path and feature overrides.
The ban now lives in performanceRestrictedImportPaths, which every
createRestrictedImportRule call includes; tests keep the base restrictions
only.

Claude-Session: https://claude.ai/code/session_01QKr8CsFLYmrgqbRTSipTNH

* ✅ test(upload): assert rejected store actions without act()

Wrapping a rejecting store action in act() and asserting with rejects let
the callback run again after the spies were restored, which sent a real
checkFileHash request and surfaced as an unhandled rejection in the shard.
Store actions do not render, so the assertion awaits them directly.

Claude-Session: https://claude.ai/code/session_01QKr8CsFLYmrgqbRTSipTNH
2026-09-09 02:33:59 +08:00
AmAzing- 18f6dba56e 🐛 fix(opencode-go): preserve conversation session headers (#19235) 2026-09-07 19:11:52 +08:00
AmAzing- fc48479dd4 🐛 fix(model-runtime): route OpenCode Go Muse Spark through Responses (#19109) 2026-09-07 17:09:11 +08:00
YuTengjing 8d852279db ✨ feat(provider): add Unsloth provider support (#18339)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-06 23:28:17 +08:00
YuTengjing 8ea955ddc5 ✨ feat(model-runtime): add Meta AI provider with Muse Spark 1.3 (#19152)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 14:15:05 +08:00
sxjeru 02a253b9b7 💄 style: add Qwen3.8 Max model support with hybrid reasoning effort control (#17863)
Co-authored-by: yutengjing <ytj2713151713@gmail.com>
2026-09-05 22:59:18 +08:00
sxjeru e7fcb63685 💄 style: add new Gemini 3.8 Flash model (#19077)
Co-authored-by: yutengjing <ytj2713151713@gmail.com>
2026-09-05 22:28:41 +08:00
YuTengjing 7dd670e7a7 ✨ feat(model): add GPT-6 Astra support (#19163)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 04:25:34 +08:00
AmAzing- 7ce4ff0b7f 🐛 fix(openapi): defer heterogeneous upstream protocol routing (#19135) 2026-09-04 17:54:00 +08:00
YuTengjing 20b7c0f6c6 ✨ feat: add Gemini 3.8 Flash and fix React artifact preview (#19058) 2026-09-03 02:22:10 +08:00
YuTengjing f279402dbc 🐛 fix(model-runtime): ask Fable 5.1 to call the tool when tool_choice falls back to auto (#19024)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 11:35:41 +08:00
Arvin Xu 8996397b2a ✨ feat(model-runtime): classify 9 error patterns from 2026-09 triage (#19004)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 09:17:04 +08:00
Arvin Xu 49d1a94110 🐛 fix(model-runtime): echo reasoning back for GLM and Kimi thinking models (#19008)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 09:15:37 +08:00
Arvin Xu 89cca7696f 🐛 fix(model-runtime): classify SubscriptionPlanLimit in the error spec table (#19006)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 09:15:02 +08:00
YuTengjing 2399e4afbb ✨ feat(model-runtime): support Claude Fable 5.1 tool_choice rules (#19016) 2026-09-02 03:11:18 +08:00
Innei 3280844f5d ⚡️ perf(test): cut root suite from 397s to 287s, dedupe logger mocks, fix blind stream tests (#18804)
* ⚡️ perf(test): stop loading antd in the global vitest setup

setupFiles re-evaluate per test file, so `import { theme } from 'antd'` —
present only to set `theme.defaultConfig.hashed = false` — cost every one of
the 1836 root-suite files ~596ms of module evaluation.

Nothing asserts on unhashed antd class names, so the flag can go with it.

Full root suite: 396.95s -> 283.88s wall, setup 2027s -> 256s, no test changes.

Prebundling antd via `deps.optimizer` was measured as an alternative and
rejected: a prebundled module resolves to the optimizer chunk while
`vi.mock('<bare-specifier>')` registers on the bare id, so the mock silently
stops applying (23 suites started reading the real implementation).

Claude-Session: https://claude.ai/code/session_01K9fwqjBqMYMsYDddo9b4JN

* ✅ test(desktop,cli): lift per-file logger mocks into the setup files

52 desktop suites and 38 CLI suites each carried an identical
`vi.mock('…/utils/logger')` whose only job was silencing output — the desktop
logger also pulls electron-log and `@/env` in at module scope.

Register one stub per app instead:
- desktop `__mocks__/setup.ts` stubs `createLogger`
- new `apps/cli/tests/setup.ts` spreads the real module and replaces every `log`
  method with a spy, so suites that assert on log calls keep working

Suites needing different behavior still override with their own `vi.mock`;
`logger.test.ts` in each app opts out via `vi.unmock`.

desktop 122/122 pass. CLI unit failures go 9 -> 8 (all pre-existing;
`src/settings/index.test.ts` now passes).

Claude-Session: https://claude.ai/code/session_01K9fwqjBqMYMsYDddo9b4JN

* 🐛 fix(test): make model-runtime stream tests actually assert

Three suites drove a stream end to end and then asserted only that something
existed, so they stayed green against a broken implementation.

- zhipu `handleStream`: 8 cases built tool_call chunks, read the whole stream
  into `chunks`, then asserted `expect(chunks).toBeDefined()`. Driving
  `instance.chat()` with a mocked `create` yields `choices: []`, so the
  transform under test never ran. Rewritten to call
  `params.chatCompletion.handleStream` directly and assert the emitted SSE
  `event: tool_calls` indices — 365 -> 147 lines, and the
  all-placeholders branch is now covered.
- google `should handle text messages correctly`: the mock enqueued a bare
  string, which actually throws `Cannot use 'in' operator to search for
  'promptFeedback'` once the body is read. Now enqueues a real candidates
  chunk and asserts the SSE text payload.
- azureOpenai `should handle o1 series models without streaming` asserted only
  that `transformResponseToStream` ran, never that o1 forces `stream: false`.

Each was mutation-verified: deleting the negative-index fix, the placeholder
filter, the google thought filter, or the o1 stream override now fails these
tests and previously did not.

186 files / 4650 tests pass.

Claude-Session: https://claude.ai/code/session_01K9fwqjBqMYMsYDddo9b4JN
2026-08-29 17:39:09 +08:00
YuTengjing 774e04c44b 🐛 fix: support media refs for tool result images (#18743) 2026-08-28 19:45:12 +08:00
YuTengjing fe6b36ab1a 🐛 fix: align chat output cost estimate ratio (#18748) 2026-08-27 17:30:16 +08:00
YuTengjing 705772ab88 ✨ feat: support GLM-5.3-Flash (#18723) 2026-08-26 23:34:53 +08:00
YuTengjing 3ba63cd033 ✨ feat(model-runtime): improve routing context and fallback (#18596) 2026-08-24 11:34:40 +08:00
LiJian 5cf4638a1c 🐛 fix(heterogeneous-agent): make the server-default binding usable end to end (#18621)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: AmAzing129 <115673583+AmAzing129@users.noreply.github.com>
2026-08-23 17:46:56 +08:00
AmAzing- 486207cbb7 ✨ feat(heterogeneous-agent): add server-default operation auth (server half) (#18558) 2026-08-22 00:12:40 +08:00
Arvin Xu 5072403ae0 🐛 fix(agent-runtime): stop sanitizeNullBytes from corrupting escape text (#18507)
* 🐛 fix(agent-runtime): stop sanitizeNullBytes from corrupting escape text

sanitizeNullBytes round-tripped objects through JSON.stringify -> string
surgery -> JSON.parse. On any payload whose TEXT spells out a unicode
escape for U+0000 -- regex character classes, GROUP_KEY_SEP constants,
smali / Kotlin metadata, base64 dumps, i.e. everything a coding agent
reads out of real source files -- stringify doubles that text's leading
backslash, so the six-character needle matches one character late.
Stripping it eats the escape's own backslash and leaves an orphan
trailing backslash, and the re-parse dies with
`SyntaxError: Bad escaped character in JSON at position N`.

Thrown from the tool-result persist path (messagePlugins.state), that
killed the whole operation: 316 failed agent_operations across 116 users
since 2026-04-30, provider-agnostic, burning entire multi-minute runs.

Walk the value and rewrite decoded strings instead, the way stripNulDeep
in @lobechat/heterogeneous-agents already does. Decoded values can never
reach an escape sequence, so literal escape text survives untouched. The
corrupted-Unicode recovery (NUL + two hex digits -> the character it
meant) is preserved, just applied to string values rather than JSON text.

Also persist a bounded `stack` on formatErrorForState's unclassified
Error branch. `name` + `message` alone made this class of 500 impossible
to locate from a stored operation -- the pg branch already keeps its
extra context, this one dropped everything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ✨ feat(model-runtime): classify harness JSON.parse throws as E7008

A `SyntaxError` out of a harness-side `JSON.parse` landed on the bare
`InternalServerError` fallback: no attribution, no category, no
severity, no `countAsFailure`, no stable id. 316 such operations over
four months were indistinguishable from any other 500 on the dashboards.

Add `HarnessJsonParseError` (E7008, category `stream`, attribution
`harness`, `countAsFailure: true`, `retryable: false`) and register the
two V8 phrasings — "… in JSON at position N" and "Unexpected end of JSON
input" — in the pattern registry. Placed after every provider section so
an upstream body quoting a parse failure keeps its provider code, and
before the `AgentRuntimeError` crash fallbacks so this class does not
dissolve into that catch-all bucket.

`retryable: false` states the fact rather than changing behavior: the
same corrupt payload re-parses to the same failure, so retrying only
re-burns the run's tokens. Nothing in the agent runtime acts on
`retryable` today — it is classification metadata.

Ships the en-US source string plus the hand-written zh-CN mirror; the
remaining locales fall back to English until the daily i18n workflow
picks them up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 13:35:17 +08:00
YuTengjing eee2a00541 🐛 fix(model-runtime): map PPIO and Qiniu upstream model metadata onto model cards (#18484) 2026-08-19 11:43:48 +08:00
YuTengjing 089cd2cd96 🐛 fix(model-runtime): map ZenMux capability metadata to model cards (#18483) 2026-08-19 11:27:57 +08:00
Arvin Xu 941403a556 chore: remove internal LOBE-XXX issue references from code comments (#18327)
* chore: remove internal LOBE-XXX issue references from code comments

Rewrite code comments that referenced internal Linear issue IDs
(LOBE-XXXX) into self-contained descriptions of the scenario, keeping
code semantics unchanged. User-facing locale strings and LOBE_* constant
names are left untouched.

* chore: remove internal LOBE-XXX issue references from SQL migration comments

---------

Co-authored-by: arvinxx <arvinxx@users.noreply.github.com>
2026-08-15 12:15:19 +08:00
sxjeru 14ec086c22 💄 style: add Gemini 3.7 Flash & GLM-5.3 model support (#18289)
Co-authored-by: yutengjing <ytj2713151713@gmail.com>
2026-08-15 00:11:29 +08:00
YuTengjing 9130ed27f6 ✨ feat: add GLM-5.3 always-on thinking and reasoning effort (#18301) 2026-08-14 19:37:39 +08:00
YuTengjing 5852e0974f ✨ feat: add Gemini 3.7 Flash descriptions and runtime coverage (#18290) 2026-08-14 16:38:59 +08:00
YuTengjing 97a9c295cb 🐛 fix(model-runtime): classify media request errors (#18279) 2026-08-14 10:56:09 +08:00
AmAzing- ae1a5066c4 🐛 fix(model-runtime): parse xAI reasoning text deltas (#18259) 2026-08-13 20:03:40 +08:00
YuTengjing c7f2712ca4 ✨ feat: add Grok 4.6 reasoning effort support (#18225) 2026-08-12 20:19:35 +04:00
Tsuki a58d181309 ✨ feat(chat): support web voice messages (#17744)
* ✨ feat(chat): support web voice messages

* 💄 style(chat): align voice message composer

* 💄 style(chat): match voice recorder input scale

* 🐛 fix(chat): animate voice message waveform

* 💄 style(chat): preserve composer height during recording

* 🐛 fix(chat): ignore inherited voice send hover

* ✨ feat(chat): normalize voice messages across providers

* 🐛 fix(chat): make voice message retries cancellation-safe

* 🐛 fix(chat): restore LobeHub voice capability

* 🐛 fix(chat): make voice messages optimistic and context-safe

* 🐛 fix(chat): drop obsolete topic loading cleanup

* 🐛 fix(chat): align voice capability gating

* ✅ test(chat): fix voice context typing

* 🔨 chore: resolve canary rebase conflicts

* ✅ test(database): align audio metadata query expectations

* 🐛 fix: block voice recording while generating
2026-08-09 19:42:32 -07:00
Tsuki cd474dfcc3 ✨ feat(model-runtime): estimate audio input costs (#17949)
* ✨ feat(model-runtime): estimate audio input costs

* 📝 docs(model-runtime): clarify audio usage comments

* 🐛 fix(model-runtime): avoid ambiguous cached audio billing

* 🐛 fix(file): persist uploaded audio duration
2026-08-09 12:52:21 -07:00
YuTengjing 03929d283b ♻️ refactor: migrate reasoning effort params to user-level model-instance config (#17985)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 18:46:56 +08:00
Arvin Xu 43504be18f 🧹 chore: clean LOBE-XXX code markers (2026-08-04) (#17890)
🧹 chore: clean LOBE-XXX markers from source code

- Replace LOBE-12572 references with descriptive context about assistant prefill poisoning
- Replace LOBE-12593 references with context about view-mode toggle scroll bug
- Replace LOBE-12381 references with context about BM25 index fast fields
- Remove @see links to resolved Linear issues (LOBE-12379, LOBE-12575)
- Remove LOBE-XXX markers from test descriptions
2026-08-06 01:33:44 +08:00
Yichen fad36790eb ✨ feat(video): add MiniMax-H3 with official v2 API support (#17827)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: 11 <1>
Co-authored-by: YuTengjing <ytj2713151713@gmail.com>
2026-08-05 21:13:20 +08:00
sxjeru f6ddb0675e 🐛 fix: stop retrying Google content blocks and end client runs immediately (#17937)
Co-authored-by: YuTengjing <ytj2713151713@gmail.com>
2026-08-05 20:57:12 +08:00
YuTengjing 1ab0014e11 ✨ feat: refresh default and hosted model metadata (#17933) 2026-08-05 16:55:36 +08:00
YuTengjing 073eb6e4b0 🐛 fix: preserve active branch and provider failure evidence (#17860) 2026-08-04 16:53:05 +08:00
YuTengjing 8a088c6498 🐛 fix(openai): handle Codex model prefixes (#17851) 2026-08-03 11:33:16 +08:00
sxjeru ba69b32909 🐛 fix(model-runtime): support advanced reasoning parameters for Cerebras and Groq (#16469)
Co-authored-by: YuTengjing <ytj2713151713@gmail.com>
2026-08-02 14:33:49 +08:00