Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/centralize-model-input-limit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@moonshot-ai/kimi-code": patch
"@moonshot-ai/kimi-code-sdk": patch
---

Unify model input limit resolution across compaction and status reporting.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
isRetryableGenerateError,
} from '#/kosong/contract/errors';
import { createUserMessage, type Message } from '#/kosong/contract/message';
import { getModelInputTokenLimit } from '#/kosong/contract/capability';
import type { Tool } from '#/kosong/contract/tool';
import { inputTotal, type TokenUsage } from '#/kosong/contract/usage';
import { IEventBus } from '#/app/event/eventBus';
Expand Down Expand Up @@ -173,7 +174,7 @@ export class AgentFullCompactionService extends Disposable implements IAgentFull

private getEffectiveMaxContextTokens(): number {
const capability = this.profile.data().modelCapabilities;
const configured = capability.max_input_tokens ?? capability.max_context_tokens;
const configured = getModelInputTokenLimit(capability);
const modelAlias = this.profile.data().modelAlias;
const observed =
modelAlias === undefined ? undefined : this.observedMaxContextTokensByModel.get(modelAlias);
Expand Down
5 changes: 3 additions & 2 deletions packages/agent-core-v2/src/agent/fullCompaction/strategy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Message } from '#/kosong/contract/message';
import { getModelInputTokenLimit } from '#/kosong/contract/capability';
import type { ProfileModelContext } from '#/agent/profile/profile';
import type { CompactionSource } from './types';
import { estimateTokensForMessage } from '#/kosong/contract/tokens';
Expand Down Expand Up @@ -71,14 +72,14 @@ export class RuntimeCompactionStrategy implements CompactionStrategy {
private delegate(): DefaultCompactionStrategy {
const model = this.context();
return new DefaultCompactionStrategy(
() => model.modelCapabilities.max_input_tokens ?? model.modelCapabilities.max_context_tokens,
() => getModelInputTokenLimit(model.modelCapabilities),
this.config(model),
);
}

private windowDelegate(): DefaultCompactionStrategy {
return new DefaultCompactionStrategy(
() => this.context().modelCapabilities.max_input_tokens ?? this.context().modelCapabilities.max_context_tokens,
() => getModelInputTokenLimit(this.context().modelCapabilities),
DEFAULT_COMPACTION_CONFIG,
);
}
Expand Down
10 changes: 6 additions & 4 deletions packages/agent-core-v2/src/agent/profile/profileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@
import { InstantiationType } from '#/_base/di/extensions';
import { Disposable } from '#/_base/di/lifecycle';
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
import { UNKNOWN_CAPABILITY, type ModelCapability } from '#/kosong/contract/capability';
import {
UNKNOWN_CAPABILITY,
getModelInputTokenLimit,
type ModelCapability,
} from '#/kosong/contract/capability';
import { type SamplingOptions, type ThinkingEffort } from '#/kosong/contract/provider';
import { IModelCatalog, type Model } from '#/kosong/model/catalog';
import { type ModelOverrides } from '#/kosong/model/model.types';
Expand Down Expand Up @@ -587,9 +591,7 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
thinkingEffort: includeThinkingEffort
? this.getEffectiveThinkingLevel()
: undefined,
maxContextTokens:
this.getModelCapabilities().max_input_tokens ??
this.getModelCapabilities().max_context_tokens,
maxContextTokens: getModelInputTokenLimit(this.getModelCapabilities()),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { IAgentProfileService } from '#/agent/profile/profile';
import { IAgentSwarmService } from '#/agent/swarm/swarm';
import { IConfigService } from '#/app/config/config';
import { IModelCatalog } from '#/kosong/model/catalog';
import { getModelInputTokenLimit } from '#/kosong/contract/capability';
import { ISessionLifecycleService } from '#/app/sessionLifecycle/sessionLifecycle';
import { ErrorCodes, Error2 } from '#/errors';
import { ensureMainAgent } from '#/session/agentLifecycle/mainAgent';
Expand Down Expand Up @@ -160,14 +161,11 @@ export class SessionLegacyService implements ISessionLegacyService {
const swarm = agent.accessor.get(IAgentSwarmService);

const model = profile.getModel();
const caps = profile.getModelCapabilities() as {
max_context_tokens?: number;
max_input_tokens?: number;
};
const caps = profile.getModelCapabilities();
const maxTokens =
model === ''
? resolveDefaultModelContextTokens(agent)
: (caps.max_input_tokens ?? caps.max_context_tokens ?? 0);
: getModelInputTokenLimit(caps);
const tokens = contextSize.get().size;
const planData = await plan.status();

Expand Down Expand Up @@ -210,7 +208,7 @@ function resolveDefaultModelContextTokens(agent: IAgentScopeHandle): number {
if (typeof defaultModel !== 'string' || defaultModel.length === 0) return 0;
try {
const capabilities = agent.accessor.get(IModelCatalog).get(defaultModel).capabilities;
return capabilities.max_input_tokens ?? capabilities.max_context_tokens;
return getModelInputTokenLimit(capabilities);
} catch {
return 0;
}
Expand Down
11 changes: 8 additions & 3 deletions packages/agent-core-v2/src/kosong/contract/capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
* so callers can gate requests against what the model accepts without
* dispatching the request and watching it fail upstream.
*
* `UNKNOWN_CAPABILITY` is the marker value returned when nothing is known
* about a model: `max_context_tokens: 0` means "unknown"; callers that do
* not gate on context length can ignore the field.
* `getModelInputTokenLimit` resolves the prompt/input ceiling before callers
* make compaction or status decisions. `UNKNOWN_CAPABILITY` is the marker
* value returned when nothing is known about a model:
* `max_context_tokens: 0` means "unknown".
*/

export interface ModelCapability {
Expand All @@ -21,6 +22,10 @@ export interface ModelCapability {
readonly dynamically_loaded_tools?: boolean;
}

export function getModelInputTokenLimit(capability: ModelCapability | undefined): number {
return capability?.max_input_tokens ?? capability?.max_context_tokens ?? 0;
}

const UNKNOWN_CAPABILITY_MARKER = Symbol.for('moonshot-ai.kosong.UNKNOWN_CAPABILITY');

export const UNKNOWN_CAPABILITY: ModelCapability = Object.freeze(
Expand Down
26 changes: 25 additions & 1 deletion packages/agent-core-v2/test/kosong/provider/composition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { isUnknownCapability, UNKNOWN_CAPABILITY } from '#/kosong/contract/capability';
import {
getModelInputTokenLimit,
isUnknownCapability,
UNKNOWN_CAPABILITY,
type ModelCapability,
} from '#/kosong/contract/capability';
import { APIConnectionError } from '#/kosong/contract/errors';
import type { Message } from '#/kosong/contract/message';
import type {
Expand Down Expand Up @@ -300,6 +305,25 @@ describe('resolveCapability', () => {
});
});

describe('getModelInputTokenLimit', () => {
const capability: ModelCapability = {
image_in: false,
video_in: false,
audio_in: false,
thinking: false,
tool_use: true,
max_context_tokens: 128_000,
};

it.each([
['the declared input cap', { ...capability, max_input_tokens: 64_000 }, 64_000],
['the total context window when no input cap is declared', capability, 128_000],
['zero when the capability is unavailable', undefined, 0],
])('resolves %s', (_label, value, expected) => {
expect(getModelInputTokenLimit(value)).toBe(expected);
});
});

describe('explainCapability', () => {
it('reports the definition level when the pair declares a capability', () => {
const { capability, source } = registry.explainCapability('openai', 'gpt-4o', 'kimi');
Expand Down
3 changes: 2 additions & 1 deletion packages/agent-core/src/agent/compaction/full.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
APIRequestTooLargeError,
APIStatusError,
createUserMessage,
getModelInputTokenLimit,
isImageFormatError,
} from '@moonshot-ai/kosong';

Expand Down Expand Up @@ -125,7 +126,7 @@ export class FullCompaction {

getEffectiveMaxContextTokens(): number {
const capability = this.agent.config.modelCapabilities;
const configured = capability.max_input_tokens ?? capability.max_context_tokens;
const configured = getModelInputTokenLimit(capability);
const modelAlias = this.agent.config.modelAlias;
const observed =
modelAlias === undefined ? undefined : this.observedMaxContextTokensByModel.get(modelAlias);
Expand Down
9 changes: 7 additions & 2 deletions packages/agent-core/src/agent/context/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { createToolMessage, type ContentPart, type Message } from '@moonshot-ai/kosong';
import {
createToolMessage,
getModelInputTokenLimit,
type ContentPart,
type Message,
} from '@moonshot-ai/kosong';

import type { Agent } from '..';
import { ErrorCodes, KimiError } from '../../errors';
Expand Down Expand Up @@ -220,7 +225,7 @@ export class ContextMemory {
const importTokenCount = estimateTokensForMessages([message]);
const totalTokenCount = currentTokenCount + importTokenCount;
const capability = this.agent.config.modelCapabilities;
const maxContextTokens = capability.max_input_tokens ?? capability.max_context_tokens;
const maxContextTokens = getModelInputTokenLimit(capability);
if (maxContextTokens > 0 && totalTokenCount > maxContextTokens) {
throw new KimiError(
ErrorCodes.CONTEXT_OVERFLOW,
Expand Down
4 changes: 2 additions & 2 deletions packages/agent-core/src/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ErrorCodes, KimiError, makeErrorPayload } from '#/errors';
import { log } from '#/logging/logger';
import type { Logger } from '#/logging/types';
import type { AgentAPI, AgentEvent, KimiConfig, SDKAgentRPC, UsageStatus } from '#/rpc';
import { generate, type ChatProvider } from '@moonshot-ai/kosong';
import { generate, getModelInputTokenLimit, type ChatProvider } from '@moonshot-ai/kosong';

import type { EnabledPluginSessionStart, PluginCommandDef } from '#/plugin';
import { expandCommandArguments } from '../plugin/commands';
Expand Down Expand Up @@ -674,7 +674,7 @@ export class Agent {

const contextTokens = this.context.tokenCount;
const capability = this.config.modelCapabilities;
const maxContextTokens = capability.max_input_tokens ?? capability.max_context_tokens;
const maxContextTokens = getModelInputTokenLimit(capability);
const contextUsage =
maxContextTokens !== undefined && maxContextTokens > 0
? contextTokens / maxContextTokens
Expand Down
1 change: 1 addition & 0 deletions packages/agent-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export {
export { resolveLoggingConfig } from './logging/resolve-config';
export type { ResolveLoggingInput } from './logging/resolve-config';
export { installGlobalProxyDispatcher } from './utils/proxy';
export { getModelInputTokenLimit } from '@moonshot-ai/kosong';
export type {
LogContext,
LogEntry,
Expand Down
3 changes: 2 additions & 1 deletion packages/agent-core/src/services/session/sessionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
type UndoSessionRequest,
type UndoSessionResponse,
} from '@moonshot-ai/protocol';
import { getModelInputTokenLimit } from '@moonshot-ai/kosong';

import { IApprovalService } from '../approval/approval';
import { ICoreProcessService } from '../coreProcess/coreProcess';
Expand Down Expand Up @@ -473,7 +474,7 @@ export class SessionService extends Disposable implements ISessionService {
]);

const capability = config.modelCapabilities;
const maxContextTokens = capability?.max_input_tokens ?? capability?.max_context_tokens ?? 0;
const maxContextTokens = getModelInputTokenLimit(capability);
const contextTokens = context.tokenCount;
const contextUsage = maxContextTokens > 0 ? Math.min(1, contextTokens / maxContextTokens) : 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
IAgentProfileService,
IAgentUsageService,
IWireService,
getModelInputTokenLimit,
type IAgentScopeHandle,
type UsageStatus,
} from '@moonshot-ai/agent-core-v2';
Expand Down Expand Up @@ -134,7 +135,7 @@ export function readLegacyStatus(agent: IAgentScopeHandle): LegacyStatusSnapshot
const measured = wire.getModel(ContextSizeModel);
const contextTokens = Math.max(contextSize.get().size, measured.tokens);
const capabilities = profile.getModelCapabilities();
const maxContextTokens = capabilities.max_input_tokens ?? capabilities.max_context_tokens;
const maxContextTokens = getModelInputTokenLimit(capabilities);
const model = profile.getModel();
return { usage, contextTokens, maxContextTokens, model };
}
Expand Down
5 changes: 5 additions & 0 deletions packages/kosong/src/capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export interface ModelCapability {
readonly dynamically_loaded_tools?: boolean;
}

/** Return the model's prompt/input limit, or 0 when the limit is unknown. */
export function getModelInputTokenLimit(capability: ModelCapability | undefined): number {
return capability?.max_input_tokens ?? capability?.max_context_tokens ?? 0;
}

const UNKNOWN_CAPABILITY_MARKER = Symbol.for('moonshot-ai.kosong.UNKNOWN_CAPABILITY');

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/kosong/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export { KimiChatProvider } from './providers/kimi';
export type { ExtraBody, GenerationKwargs, KimiOptions, ThinkingConfig } from './providers/kimi';

// Model capability matrix
export { isUnknownCapability, UNKNOWN_CAPABILITY } from './capability';
export { getModelInputTokenLimit, isUnknownCapability, UNKNOWN_CAPABILITY } from './capability';
export type { ModelCapability } from './capability';

// Model catalog (models.dev-style) metadata
Expand Down
26 changes: 24 additions & 2 deletions packages/kosong/test/capability.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
/**
* `ModelCapability` type and `UNKNOWN_CAPABILITY` default — pure
* `ModelCapability` type, input-limit resolution, and `UNKNOWN_CAPABILITY` default — pure
* type/helper layer. Per-wire `getModelCapability` tables live in
* `capability-providers.test.ts`.
*/

import { UNKNOWN_CAPABILITY, isUnknownCapability, type ModelCapability } from '#/capability';
import {
UNKNOWN_CAPABILITY,
getModelInputTokenLimit,
isUnknownCapability,
type ModelCapability,
} from '#/capability';
import { describe, expect, it } from 'vitest';

describe('ModelCapability / UNKNOWN_CAPABILITY', () => {
const capability: ModelCapability = {
image_in: false,
video_in: false,
audio_in: false,
thinking: false,
tool_use: true,
max_context_tokens: 128_000,
};

it.each([
['the declared input cap', { ...capability, max_input_tokens: 64_000 }, 64_000],
['the total context window when no input cap is declared', capability, 128_000],
['zero when the capability is unavailable', undefined, 0],
])('resolves %s', (_label, value, expected) => {
expect(getModelInputTokenLimit(value)).toBe(expected);
});

it('UNKNOWN_CAPABILITY has all boolean fields false', () => {
expect(UNKNOWN_CAPABILITY.image_in).toBe(false);
expect(UNKNOWN_CAPABILITY.video_in).toBe(false);
Expand Down
3 changes: 2 additions & 1 deletion packages/node-sdk/src/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';

import {
ErrorCodes,
getModelInputTokenLimit,
makeErrorPayload,
type AgentContextData,
type ApprovalRequest,
Expand Down Expand Up @@ -579,7 +580,7 @@ export abstract class SDKRpcClientBase {
agentId,
});
const capability = config.modelCapabilities;
const maxContextTokens = capability?.max_input_tokens ?? capability?.max_context_tokens ?? 0;
const maxContextTokens = getModelInputTokenLimit(capability);
const contextTokens = context.tokenCount;
// Deliberately unclamped: >100% is the documented overflow signal on this
// path (see acp-adapter's formatContextUsage), unlike the schema-bounded
Expand Down