Bug Description
Providers cannot apply per-operation (op) behavior because the op tag is dropped before it reaches the provider layer. opts.op is set by every caller (e.g. capture.summarize), but buildCallInput() in the LLM facade doesn't copy it into the input object handed to provider.complete(). Any per-op switch inside a provider (request-body tweaks, routing, budget overrides) therefore silently never fires.
This bit us in practice: OpenRouter's DeepSeek models burn the full token budget and double latency on capture.summarize because the recommended thinking: { type: "disabled" } can't be applied per-op — the condition opts.op === "capture.summarize" inside the provider always evaluates against an op that was never forwarded.
Affected code (2.0.17)
dist/core/llm/client.js:
function buildCallInput(opts, jsonMode) {
return {
temperature: opts?.temperature ?? config.temperature,
maxTokens: opts?.maxTokens ?? config.maxTokens ?? DEFAULT_MAX_TOKENS,
jsonMode,
stop: opts?.stop,
// <-- opts?.op is dropped here
};
}
Callers pass op only into logging/metrics paths (callWithFallback(messages, input, opts, op) → record(...)/facadeLog), not into the provider input.
Suggested fix
One line in buildCallInput:
stop: opts?.stop,
op: opts?.op,
Then per-op provider behavior works, e.g. in core/llm/providers/openai.js (both complete and stream request builders):
if (opts.op === "capture.summarize") {
body.thinking = { type: "disabled" }; // OpenRouter reasoning-model kill-switch
}
Longer-term it may be worth defining which ops are meaningful (capture.summarize, retrieval.filter, skill.evolve, …) and documenting them, so per-op switches don't depend on string matching scattered across providers.
Impact
Without this, summarize-style calls on reasoning models (DeepSeek R-series etc. via OpenRouter) cost ~2× latency and excess tokens on every capture. We verified locally on 2.0.17: with the one-liner + provider switch, summarize latency drops back to non-thinking levels and token usage falls accordingly.
Environment
- Plugin: @memtensor/memos-local-plugin 2.0.17
- Agent: Hermes (Windows native); LLM via OpenRouter-compatible endpoint
Bug Description
Providers cannot apply per-operation (
op) behavior because the op tag is dropped before it reaches the provider layer.opts.opis set by every caller (e.g.capture.summarize), butbuildCallInput()in the LLM facade doesn't copy it into the input object handed toprovider.complete(). Any per-op switch inside a provider (request-body tweaks, routing, budget overrides) therefore silently never fires.This bit us in practice: OpenRouter's DeepSeek models burn the full token budget and double latency on
capture.summarizebecause the recommendedthinking: { type: "disabled" }can't be applied per-op — the conditionopts.op === "capture.summarize"inside the provider always evaluates against anopthat was never forwarded.Affected code (2.0.17)
dist/core/llm/client.js:Callers pass op only into logging/metrics paths (
callWithFallback(messages, input, opts, op)→record(...)/facadeLog), not into the provider input.Suggested fix
One line in
buildCallInput:Then per-op provider behavior works, e.g. in
core/llm/providers/openai.js(bothcompleteandstreamrequest builders):Longer-term it may be worth defining which ops are meaningful (
capture.summarize,retrieval.filter,skill.evolve, …) and documenting them, so per-op switches don't depend on string matching scattered across providers.Impact
Without this, summarize-style calls on reasoning models (DeepSeek R-series etc. via OpenRouter) cost ~2× latency and excess tokens on every capture. We verified locally on 2.0.17: with the one-liner + provider switch, summarize latency drops back to non-thinking levels and token usage falls accordingly.
Environment