You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by li...@apache.org on 2026/09/26 14:38:01 UTC
(maka) branch main updated: fix(runtime-host): ask auxiliary calls for the least reasoning each model accepts (#5730)
This is an automated email from the ASF dual-hosted git repository.
liugddx pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git
The following commit(s) were added to refs/heads/main by this push:
new 17d7fe6c61 fix(runtime-host): ask auxiliary calls for the least reasoning each model accepts (#5730)
17d7fe6c61 is described below
commit 17d7fe6c61f8f35860e1e6a2a244524b079a43a8
Author: Guangdong Liu <li...@gmail.com>
AuthorDate: Sat Sep 26 22:37:56 2026 +0800
fix(runtime-host): ask auxiliary calls for the least reasoning each model accepts (#5730)
* fix(runtime-host): ask auxiliary calls for the least reasoning each model accepts
Daily Review and prompt suggestions ask for no reasoning with
thinkingLevel 'off'. On models that do not declare 'off',
resolveThinkingLevel drops it and the request carries no effort at all,
so the provider runs its default reasoning (medium on GPT-5, o3 and
GPT-6; dynamic on Gemini 3). Prompt suggestions refused every such
model instead.
leastReasoningThinkingLevel picks 'off' where the model declares it, or
where an omitted parameter already means no extended reasoning (no
declared levels, Anthropic Messages), and otherwise the model's lowest
declared level, which the wire builders send explicitly. Auxiliary calls
opt in with reasoning: 'least'; Daily Review and prompt suggestions do.
Suggestions no longer refuse reasoning-only models and give them a
1,024-token budget, since reasoning tokens count against it.
Fixes #5690
Generated-by: Claude Code
Co-Authored-By: Claude Opus 5.5 (1M context) <no...@anthropic.com>
* fix(runtime): send Kimi Coding Plan its lowest effort for least reasoning
The Anthropic Messages exception in leastReasoningThinkingLevel held for
Claude, where an omitted thinking/effort means no extended thinking, but
not for Kimi Coding Plan: k3, k3-256k and kimi-for-coding think by
default when no level is sent, so a least-reasoning call ran at the
route's default thinking. Keep 'off' only for Claude on Anthropic
Messages; every other route gets its lowest declared level explicitly.
Generated-by: Claude Code
Co-Authored-By: Claude Opus 5.5 (1M context) <no...@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <no...@anthropic.com>
---
.../__tests__/execution-model-composition.test.ts | 145 ++++++++++++++++++++-
.../src/server/execution-model-authority.ts | 63 +++++----
.../src/__tests__/model-factory-thinking.test.ts | 91 ++++++++++++-
packages/runtime/src/model-factory.ts | 31 +++++
4 files changed, 301 insertions(+), 29 deletions(-)
diff --git a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts
index d1363f967a..83c6840378 100644
--- a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts
+++ b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts
@@ -6170,7 +6170,7 @@ function closeServer(server: Server): Promise<void> {
});
}
-test('prompt suggestions enforce reasoning-off, reject truncation, do not retry and skip reasoning-only models', async () => {
+test('prompt suggestions use the least reasoning each model accepts, reject truncation and do not retry', async () => {
const MODEL_ID = 'gpt-5.2';
const base = await mkdtemp(join(tmpdir(), 'maka-host-goal-evaluator-'));
const provider = await startProvider();
@@ -6239,10 +6239,30 @@ test('prompt suggestions enforce reasoning-off, reject truncation, do not retry
newId: () => `suggestion-${fetches}`,
createFetchTransport: () => ({
close: async () => {},
- fetch: (async (_url, init) => {
+ fetch: (async (url, init) => {
fetches++;
requestBody = JSON.parse(String(init?.body));
if (mode === 'failure') return new Response('unavailable', { status: 503 });
+ if (String(url).endsWith('/responses')) {
+ return Response.json({
+ id: 'reply',
+ object: 'response',
+ created_at: 1,
+ model: 'gpt-5',
+ status: 'incomplete',
+ incomplete_details: { reason: 'max_output_tokens' },
+ output: [
+ {
+ type: 'message',
+ id: 'message',
+ role: 'assistant',
+ status: 'incomplete',
+ content: [{ type: 'output_text', text: 'partial', annotations: [] }],
+ },
+ ],
+ usage: { input_tokens: 7, output_tokens: 3, total_tokens: 10 },
+ });
+ }
return Response.json({
id: 'reply',
object: 'chat.completion',
@@ -6286,14 +6306,127 @@ test('prompt suggestions enforce reasoning-off, reject truncation, do not retry
});
assert.equal(changed.kind, 'committed');
await publishConnectionModel(policy, connection.connectionId, 'gpt-5');
- await assert.rejects(
- suggest({ ...source, header: { ...session, model: 'gpt-5' } }, new AbortController().signal),
- /disable reasoning/,
+ mode = 'length';
+ assert.equal(
+ await suggest(
+ { ...source, header: { ...session, model: 'gpt-5' } },
+ new AbortController().signal,
+ ),
+ undefined,
+ );
+ assert.equal(fetches, 3);
+ assert.equal(
+ (requestBody.reasoning as { effort?: unknown } | undefined)?.effort,
+ 'minimal',
+ 'a model without off must be asked for its least effort, not left on the provider default',
+ );
+ assert.equal(
+ requestBody.max_output_tokens,
+ 1_024,
+ 'reasoning at the least effort needs room before the visible line',
);
- assert.equal(fetches, 2, 'unsupported off must not reach the provider');
} finally {
await owner.close();
await provider.close();
await rm(base, { recursive: true, force: true });
}
});
+
+test('prompt suggestions ask Kimi K3 for its lowest effort instead of its default thinking', async () => {
+ const MODEL_ID = 'k3';
+ const base = await mkdtemp(join(tmpdir(), 'maka-host-kimi-suggestion-'));
+ const capability = await resolveStorageRoot({
+ path: join(base, 'interactive'),
+ kind: 'interactive',
+ });
+ const owner = await tryAcquireInteractiveRootOwner(capability);
+ assert.ok(owner);
+ if (!owner) return;
+
+ const policy = await openInteractiveRuntimePolicyStoresForWrite(owner.lease);
+ const usage = await openInteractiveUsageStoresForWrite(owner.lease);
+ const execution = await openInteractiveExecutionStoresForWrite(owner.lease);
+ try {
+ const created = await policy.connectionCatalog.create({
+ expectedCatalogRevision: 0,
+ connection: {
+ slug: 'kimi-coding-plan',
+ name: 'Kimi Coding Plan',
+ providerType: 'kimi-coding-plan',
+ enabled: true,
+ enabledModelIds: [MODEL_ID],
+ },
+ });
+ assert.equal(created.kind, 'committed');
+ if (created.kind !== 'committed') return;
+ const connection = created.snapshot.connections[0];
+ assert.ok(connection);
+ if (!connection) return;
+ const credential = await policy.credentialVault.set({
+ locator: { scope: 'connection', connectionId: connection.connectionId, kind: 'api_key' },
+ expected: null,
+ secret: API_KEY,
+ });
+ assert.equal(credential.kind, 'committed');
+ await publishConnectionModel(policy, connection.connectionId, MODEL_ID);
+ const session = await execution.sessionStore.create({
+ cwd: capability.canonicalPath,
+ llmConnectionId: connection.connectionId,
+ llmConnectionSlug: 'kimi-coding-plan',
+ model: MODEL_ID,
+ permissionMode: 'ask',
+ });
+
+ let requestBody: Record<string, unknown> = {};
+ const suggest = createHostPromptSuggestionModel({
+ runtimePolicy: policy,
+ oauthCredentials: new HostOAuthExecutionAuthority(policy),
+ usage,
+ requestDrain: () => assert.fail('prompt suggestion telemetry must not drain the Host'),
+ newId: () => 'kimi-suggestion',
+ createFetchTransport: () => ({
+ close: async () => {},
+ fetch: (async (_url, init) => {
+ requestBody = JSON.parse(String(init?.body));
+ return Response.json({
+ id: 'msg-1',
+ type: 'message',
+ role: 'assistant',
+ model: MODEL_ID,
+ content: [{ type: 'text', text: 'Yes, add the tests next.' }],
+ stop_reason: 'end_turn',
+ stop_sequence: null,
+ usage: { input_tokens: 7, output_tokens: 6 },
+ });
+ }) as typeof fetch,
+ }),
+ });
+
+ const text = await suggest(
+ {
+ sessionId: session.id,
+ turnId: 'turn',
+ terminalEventId: 'terminal',
+ header: session,
+ messages: [],
+ },
+ new AbortController().signal,
+ );
+
+ assert.equal(text, 'Yes, add the tests next.');
+ assert.deepEqual(
+ requestBody.thinking,
+ { type: 'adaptive' },
+ 'K3 must be sent an explicit thinking mode, not left on its default',
+ );
+ assert.match(
+ JSON.stringify(requestBody),
+ /"effort":"low"/,
+ 'K3 must be asked for its lowest effort, not its default maximum',
+ );
+ assert.equal(requestBody.max_tokens, 1_024);
+ } finally {
+ await owner.close();
+ await rm(base, { recursive: true, force: true });
+ }
+});
diff --git a/packages/runtime-host/src/server/execution-model-authority.ts b/packages/runtime-host/src/server/execution-model-authority.ts
index b801627435..4f5fbb6d47 100644
--- a/packages/runtime-host/src/server/execution-model-authority.ts
+++ b/packages/runtime-host/src/server/execution-model-authority.ts
@@ -25,7 +25,7 @@ import {
PROVIDER_REGISTRY,
type RuntimeExecutionConnection,
} from '@maka/core/llm-connections';
-import { thinkingVariantsForConnection } from '@maka/core/model-thinking';
+import type { ThinkingLevel } from '@maka/core/model-thinking';
import { isModelExplicitlyUnsupportedForChat } from '@maka/core/model-catalog';
import { declaredModelApiProtocol } from '@maka/core/model-thinking';
import { parseRequestHeaders, type RuntimePolicy } from '@maka/core/runtime-policy';
@@ -49,7 +49,11 @@ import {
llmCallUsageFields,
recordLlmCallStrict,
} from '@maka/runtime/telemetry';
-import { buildProviderOptions, getAIModel } from '@maka/runtime/model-factory';
+import {
+ buildProviderOptions,
+ getAIModel,
+ leastReasoningThinkingLevel,
+} from '@maka/runtime/model-factory';
import { stableHash } from '@maka/runtime/request-shape';
import { buildSessionRecapMessages } from '@maka/runtime/session-recap';
import {
@@ -375,6 +379,7 @@ export function createHostDailyReviewModel(
callKind: 'daily_review',
callId: `daily_review_${callId}`,
abortSignal: effectiveAbortSignal,
+ reasoning: 'least',
buildRequest: () => ({ prompt, maxOutputTokens: 2_048 }),
});
return {
@@ -392,6 +397,9 @@ export function createHostDailyReviewModel(
});
}
+const PROMPT_SUGGESTION_MAX_OUTPUT_TOKENS = 128;
+const PROMPT_SUGGESTION_REASONING_MAX_OUTPUT_TOKENS = 1_024;
+
export function createHostPromptSuggestionModel(input: HostSessionEffectModelInput) {
const authority = createAuxiliaryModelCallAuthority(input);
return async (
@@ -401,26 +409,24 @@ export function createHostPromptSuggestionModel(input: HostSessionEffectModelInp
const result = await runHostAuxiliaryModelCall(authority, {
transportContextId: source.sessionId,
telemetrySessionId: source.sessionId,
- header: { ...source.header, thinkingLevel: 'off' },
+ header: source.header,
callKind: 'prompt_suggestion',
callId: `prompt_suggestion_${source.terminalEventId}_${authority.newId()}`,
abortSignal,
- buildRequest: (target) => {
- const variants = thinkingVariantsForConnection(target.connection, target.model);
- if (variants.length && !variants.includes('off')) {
- throw new AuxiliaryModelCallConfigurationError(
- 'Prompt suggestions require a model that can disable reasoning',
- );
- }
- return {
- prompt: buildPromptSuggestionPrompt(
- source.messages,
- source.header.role === 'workhub_coordination',
- ),
- maxOutputTokens: 128,
- maxRetries: 0,
- };
- },
+ reasoning: 'least',
+ buildRequest: (_target, thinkingLevel) => ({
+ prompt: buildPromptSuggestionPrompt(
+ source.messages,
+ source.header.role === 'workhub_coordination',
+ ),
+ // Reasoning tokens count against the output budget. A model that cannot
+ // turn reasoning off spends some at its lowest effort before the line.
+ maxOutputTokens:
+ thinkingLevel === 'off'
+ ? PROMPT_SUGGESTION_MAX_OUTPUT_TOKENS
+ : PROMPT_SUGGESTION_REASONING_MAX_OUTPUT_TOKENS,
+ maxRetries: 0,
+ }),
});
return result.finishReason === 'length' ? undefined : result.text;
};
@@ -587,7 +593,16 @@ interface HostAuxiliaryModelCallInput {
readonly callKind: ModelCallKind;
readonly callId: string;
readonly abortSignal: AbortSignal;
- readonly buildRequest: (target: ResolvedExecutionTarget) => AuxiliaryModelRequest;
+ /**
+ * `'least'` replaces the header's thinking level with the least reasoning
+ * the resolved model accepts (see `leastReasoningThinkingLevel`). Without it
+ * the call reasons at the header's level, as the Session's own turns do.
+ */
+ readonly reasoning?: 'least';
+ readonly buildRequest: (
+ target: ResolvedExecutionTarget,
+ thinkingLevel: ThinkingLevel | undefined,
+ ) => AuxiliaryModelRequest;
}
function createAuxiliaryModelCallAuthority(
@@ -648,7 +663,11 @@ async function runHostAuxiliaryModelCall(
const pricingSnapshot = await readAuxiliaryPreflight(authority, input.abortSignal, () =>
readDuringBackendCreation(() => authority.usage.pricing.snapshot(), input.abortSignal),
);
- const request = input.buildRequest(target);
+ const thinkingLevel =
+ input.reasoning === 'least'
+ ? leastReasoningThinkingLevel(target.connection, target.model)
+ : input.header.thinkingLevel;
+ const request = input.buildRequest(target, thinkingLevel);
const pricing = buildPricingLookup(pricingSnapshot.overrides);
const transport = authority.createFetchTransport(
toRuntimePolicyProxy(target.networkProxy, target.proxySecret),
@@ -693,7 +712,7 @@ async function runHostAuxiliaryModelCall(
const providerOptions = buildProviderOptions(
target.connection,
target.model,
- input.header.thinkingLevel,
+ thinkingLevel,
runtime,
);
const model = getAIModel({
diff --git a/packages/runtime/src/__tests__/model-factory-thinking.test.ts b/packages/runtime/src/__tests__/model-factory-thinking.test.ts
index b153a07319..fb189516f5 100644
--- a/packages/runtime/src/__tests__/model-factory-thinking.test.ts
+++ b/packages/runtime/src/__tests__/model-factory-thinking.test.ts
@@ -24,7 +24,7 @@ import { lookupModelMetadata } from '@maka/core/model-metadata';
import { thinkingVariantsForModel, type ThinkingLevel } from '@maka/core/model-thinking';
import { isRetiredProvider } from '@maka/core/provider-registry';
-import { buildProviderOptions, getAIModel } from '../model-factory.js';
+import { buildProviderOptions, getAIModel, leastReasoningThinkingLevel } from '../model-factory.js';
import { resolveModelRuntime } from '../model-runtime.js';
function conn(providerType: LlmConnection['providerType'], slug = 'test'): LlmConnection {
@@ -1283,3 +1283,92 @@ describe('buildProviderOptions: Command Code thinking level', () => {
assert.deepEqual(buildProviderOptions(conn('commandcode'), 'not-a-model', 'high'), {});
});
});
+
+describe('leastReasoningThinkingLevel', () => {
+ test('asks for the lowest declared effort where a dropped off would leave provider-default reasoning', () => {
+ const cases: ReadonlyArray<
+ readonly [LlmConnection['providerType'], string, ThinkingLevel, Record<string, unknown>]
+ > = [
+ ['openai', 'gpt-5', 'minimal', { openai: { reasoningEffort: 'minimal' } }],
+ ['openai', 'o3', 'low', { openai: { reasoningEffort: 'low' } }],
+ ['openai', 'gpt-6-sol', 'low', { openai: { reasoningEffort: 'low' } }],
+ ['openai-codex', 'gpt-6-astra', 'low', { openai: { reasoningEffort: 'low' } }],
+ ['openai', 'gpt-5.5', 'off', { openai: { reasoningEffort: 'none' } }],
+ [
+ 'google',
+ 'gemini-3.5-flash',
+ 'minimal',
+ { google: { thinkingConfig: { thinkingLevel: 'minimal' } } },
+ ],
+ ['google', 'gemini-2.5-flash', 'off', { google: { thinkingConfig: { thinkingBudget: 0 } } }],
+ ];
+ for (const [providerType, modelId, level, wire] of cases) {
+ const connection = conn(providerType);
+ assert.equal(
+ leastReasoningThinkingLevel(connection, modelId),
+ level,
+ `${providerType}/${modelId}`,
+ );
+ const options = buildProviderOptions(connection, modelId, level) as Record<
+ string,
+ Record<string, unknown>
+ >;
+ for (const [namespace, expected] of Object.entries(wire)) {
+ for (const [key, value] of Object.entries(expected as Record<string, unknown>)) {
+ assert.deepEqual(
+ key === 'thinkingConfig'
+ ? Object.fromEntries(
+ Object.keys(value as object).map((field) => [
+ field,
+ (options[namespace]?.thinkingConfig as Record<string, unknown> | undefined)?.[
+ field
+ ],
+ ]),
+ )
+ : options[namespace]?.[key],
+ value,
+ `${providerType}/${modelId} must send ${namespace}.${key} explicitly`,
+ );
+ }
+ }
+ }
+ });
+
+ test('keeps off where an omitted parameter already means no extended reasoning', () => {
+ // Claude on Anthropic Messages: omitting effort/thinking is non-thinking,
+ // while the lowest effort would switch adaptive thinking on. That holds on
+ // a gateway serving Claude over the same wire.
+ for (const providerType of ['anthropic', 'opencode'] as const) {
+ const connection = conn(providerType);
+ assert.equal(
+ leastReasoningThinkingLevel(connection, 'claude-opus-4-8'),
+ 'off',
+ `${providerType}/claude-opus-4-8`,
+ );
+ const options = (buildProviderOptions(connection, 'claude-opus-4-8', 'off').anthropic ??
+ {}) as Record<string, unknown>;
+ assert.equal(options.effort, undefined);
+ assert.equal(options.thinking, undefined);
+ }
+ // Models with no declared levels keep the previous request unchanged.
+ assert.equal(leastReasoningThinkingLevel(conn('openai'), 'gpt-4o'), 'off');
+ });
+
+ test('asks Kimi Coding Plan for its lowest effort, since it thinks by default when none is sent', () => {
+ // K3 and kimi-for-coding speak Anthropic Messages but are not Claude: an
+ // omitted level leaves the route on its default, maximum thinking.
+ const kimi = conn('kimi-coding-plan', 'kimi-coding-plan');
+ for (const [modelId, thinking] of [
+ ['k3', { type: 'adaptive' }],
+ ['k3-256k', { type: 'adaptive' }],
+ ['kimi-for-coding', { type: 'enabled', budgetTokens: 1_024 }],
+ ] as const) {
+ const level = leastReasoningThinkingLevel(kimi, modelId);
+ assert.equal(level, 'low', modelId);
+ assert.deepEqual(buildProviderOptions(kimi, modelId, level).anthropic, {
+ thinking,
+ effort: 'low',
+ });
+ }
+ });
+});
diff --git a/packages/runtime/src/model-factory.ts b/packages/runtime/src/model-factory.ts
index de32d99fca..1e77d9c562 100644
--- a/packages/runtime/src/model-factory.ts
+++ b/packages/runtime/src/model-factory.ts
@@ -43,6 +43,7 @@ import type { ThinkingLevel } from '@maka/core/model-thinking';
import {
modelOverride,
resolveThinkingLevel,
+ THINKING_LEVELS,
supportsCustomFastServiceTier,
thinkingOptionsForModel,
thinkingVariantsForConnection,
@@ -454,6 +455,36 @@ function claudeThinkingMode(
: 'legacy';
}
+/**
+ * The level that asks this model for the least reasoning it accepts, for
+ * callers that want none but cannot rely on `'off'` surviving
+ * `resolveThinkingLevel`.
+ *
+ * `'off'` is used when the model declares it. It is also used when the model
+ * declares no levels, or is a Claude model on Anthropic Messages, because on
+ * those paths an omitted parameter already means no extended thinking, while
+ * the lowest effort would switch adaptive thinking on. That holds for Claude,
+ * not for every Anthropic Messages route: Kimi's K3 and kimi-for-coding think
+ * by default when no level is sent.
+ *
+ * Everywhere else a dropped `'off'` omits the effort parameter, and the
+ * provider then runs its default reasoning (medium on GPT-5, GPT-6 and o3,
+ * dynamic on Gemini 3). So this returns the model's lowest declared level
+ * instead, which the wire builders send explicitly.
+ */
+export function leastReasoningThinkingLevel(
+ connection: RuntimeExecutionConnection,
+ modelId: string,
+ runtime = resolveModelRuntime(connection, modelId),
+): ThinkingLevel {
+ const variants = thinkingVariantsForConnection(connection, modelId);
+ if (variants.length === 0 || variants.includes('off')) return 'off';
+ if (runtime.wire === 'anthropic-messages' && claudeFamilyId(modelId).startsWith('claude-')) {
+ return 'off';
+ }
+ return THINKING_LEVELS.find((level) => variants.includes(level)) ?? 'off';
+}
+
export function buildProviderOptions(
connection: RuntimeExecutionConnection,
modelId: string,
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org