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/honest-timers-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@moonshot-ai/kimi-code": patch

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge List agent-core in the changeset

This changeset only selects the CLI even though the diff changes packages/agent-core/src/agent/compaction/full.ts. The current gen-changesets rules require source changes that affect a package's output to list that package, and internal package changes that enter the CLI bundle should list both the internal package and @moonshot-ai/kimi-code; otherwise the agent-core version/changelog will not record this fix when the changeset is consumed.

Useful? React with 👍 / 👎.

"@moonshot-ai/agent-core": patch
---

Respect model output-token limits during full-history compaction.
1 change: 1 addition & 0 deletions packages/agent-core/src/agent/compaction/full.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ export class FullCompaction {
const provider = applyCompletionBudget({
provider: this.agent.config.provider,
budget: resolveCompletionBudget({
maxOutputSize: this.agent.config.maxOutputSize,
reservedContextSize: this.agent.kimiConfig?.loopControl?.reservedContextSize,
}),
capability: this.agent.config.modelCapabilities,
Expand Down
51 changes: 46 additions & 5 deletions packages/agent-core/test/agent/compaction/full.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1528,7 +1528,7 @@ describe('FullCompaction', () => {

it('compacts provider overflow when model context size is unknown', async () => {
let callCount = 0;
const compactionMaxCompletionTokens: unknown[] = [];
const compactionMaxCompletionTokens: Array<number | undefined> = [];
const generate: GenerateFn = async (provider, _system, _tools, _history, callbacks) => {
callCount += 1;
if (callCount === 1) {
Expand Down Expand Up @@ -1594,7 +1594,7 @@ describe('FullCompaction', () => {
it('honors completion budget env hard caps during compaction', async () => {
vi.stubEnv('KIMI_MODEL_MAX_COMPLETION_TOKENS', '8192');
let callCount = 0;
const compactionMaxCompletionTokens: unknown[] = [];
const compactionMaxCompletionTokens: Array<number | undefined> = [];
const generate: GenerateFn = async (provider, _system, _tools, _history, callbacks) => {
callCount += 1;
if (callCount === 1) {
Expand Down Expand Up @@ -1625,10 +1625,50 @@ describe('FullCompaction', () => {
expect(compactionMaxCompletionTokens).toEqual([8192]);
});

it('honors model maxOutputSize during compaction', async () => {
let callCount = 0;
const compactionMaxCompletionTokens: Array<number | undefined> = [];
const generate: GenerateFn = async (provider, _system, _tools, _history, callbacks) => {
callCount += 1;
if (callCount === 1) {
throw new APIContextOverflowError(400, 'Context length exceeded', 'req-model-output-cap');
}
if (callCount === 2) {
compactionMaxCompletionTokens.push(providerMaxCompletionTokens(provider));
return textResult('Model output cap compacted summary.');
}
await callbacks?.onMessagePart?.({
type: 'text',
text: 'Recovered with model output cap.',
});
return textResult('Recovered with model output cap.');
};
const ctx = testAgent({ generate });
ctx.configure({
provider: CATALOGUED_PROVIDER,
modelCapabilities: CATALOGUED_MODEL_CAPABILITIES,
});
const providerManager = ctx.agent.modelProvider;
if (providerManager === undefined) throw new Error('Expected provider manager');
const resolveProviderConfig = providerManager.resolveProviderConfig.bind(providerManager);
vi.spyOn(providerManager, 'resolveProviderConfig').mockImplementation((model) => ({
...resolveProviderConfig(model),
maxOutputSize: 32768,
}));
ctx.appendExchange(1, 'old user one', 'old assistant one', 20);
ctx.newEvents();

await ctx.rpc.prompt({ input: [{ type: 'text', text: 'Retry with model output cap' }] });
await ctx.untilTurnEnd();

expect(callCount).toBe(3);
expect(compactionMaxCompletionTokens).toEqual([32768]);
});

it('honors completion budget env opt-out during compaction', async () => {
vi.stubEnv('KIMI_MODEL_MAX_COMPLETION_TOKENS', '0');
let callCount = 0;
const compactionMaxCompletionTokens: unknown[] = [];
const compactionMaxCompletionTokens: Array<number | undefined> = [];
const generate: GenerateFn = async (provider, _system, _tools, _history, callbacks) => {
callCount += 1;
if (callCount === 1) {
Expand Down Expand Up @@ -1884,12 +1924,13 @@ function oauthTestAgentOptions(
};
}

function providerMaxCompletionTokens(provider: Parameters<GenerateFn>[0]): unknown {
return (
function providerMaxCompletionTokens(provider: Parameters<GenerateFn>[0]): number | undefined {
const value = (
provider as {
readonly modelParameters?: Record<string, unknown>;
}
).modelParameters?.['max_completion_tokens'];
return typeof value === 'number' ? value : undefined;
}

function textResult(text: string): Awaited<ReturnType<GenerateFn>> {
Expand Down