Skip to content

Commit e280f33

Browse files
authored
fix: recover from model token limit errors (#207)
1 parent f3269ea commit e280f33

5 files changed

Lines changed: 67 additions & 8 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@moonshot-ai/agent-core": patch
3+
"@moonshot-ai/kosong": patch
4+
"@moonshot-ai/kimi-code": patch
5+
---
6+
7+
Recover from provider model token limit errors during long conversations.

‎packages/agent-core/src/agent/compaction/full.ts‎

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export class FullCompaction {
4646
startedAt: number;
4747
telemetryTrigger: CompactionTelemetryTrigger;
4848
promise: Promise<void>;
49+
blockedByTurn: boolean;
4950
} | null = null;
5051
protected _compactedHistory: CompactedHistory[] = [];
5152
protected readonly strategy: CompactionStrategy;
@@ -112,6 +113,7 @@ export class FullCompaction {
112113
startedAt: Date.now(),
113114
telemetryTrigger: compactionTelemetryTrigger(data.source, data.instruction),
114115
promise: Promise.resolve(),
116+
blockedByTurn: false,
115117
};
116118
this.compacting = active;
117119
active.promise = this.compactionWorker(abortController.signal, data, compactedCount);
@@ -195,6 +197,7 @@ export class FullCompaction {
195197
private async block(signal: AbortSignal): Promise<void> {
196198
const active = this.compacting;
197199
if (active) {
200+
active.blockedByTurn = true;
198201
signal.addEventListener('abort', () => {
199202
if (this.compacting === active) {
200203
this.cancel();
@@ -312,26 +315,34 @@ export class FullCompaction {
312315
this.triggerPostCompactHook(data, result);
313316
} catch (error) {
314317
if (!isAbortError(error)) {
318+
const active = this.compacting;
319+
const blockedByTurn = active?.blockedByTurn === true;
315320
this.agent.log.error('compaction failed', {
316321
code: isKimiError(error) ? error.code : undefined,
317322
error,
318323
});
319324
this.markCanceled();
320-
const payload =
321-
isKimiError(error) && error.code === ErrorCodes.AUTH_LOGIN_REQUIRED
322-
? toKimiErrorPayload(error)
323-
: makeErrorPayload(ErrorCodes.COMPACTION_FAILED, String(error));
324-
this.agent.emitEvent({
325-
type: 'error',
326-
...payload,
327-
});
325+
if (!blockedByTurn) {
326+
const payload =
327+
isKimiError(error) && error.code === ErrorCodes.AUTH_LOGIN_REQUIRED
328+
? toKimiErrorPayload(error)
329+
: makeErrorPayload(ErrorCodes.COMPACTION_FAILED, String(error));
330+
this.agent.emitEvent({
331+
type: 'error',
332+
...payload,
333+
});
334+
}
328335
this.agent.telemetry.track('compaction_failed', {
329336
trigger_type: compactionTelemetryTrigger(data.source, data.instruction),
330337
before_tokens: tokensBefore,
331338
duration_ms: Date.now() - startedAt,
332339
retry_count: retryCount,
333340
error_type: error instanceof Error ? error.name : 'Unknown',
334341
});
342+
if (blockedByTurn) {
343+
if (isKimiError(error) && error.code === ErrorCodes.AUTH_LOGIN_REQUIRED) throw error;
344+
throw new KimiError(ErrorCodes.COMPACTION_FAILED, String(error), { cause: error });
345+
}
335346
}
336347
}
337348
}

‎packages/agent-core/test/agent/compaction.test.ts‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,45 @@ describe('Agent compaction', () => {
636636
await ctx.expectResumeMatches();
637637
});
638638

639+
it('fails a blocked turn when auto compaction generation fails', async () => {
640+
let attempts = 0;
641+
const generate: GenerateFn = async () => {
642+
attempts += 1;
643+
throw new APIStatusError(400, 'Bad request');
644+
};
645+
const ctx = testAgent({ generate, compactionStrategy: alwaysCompactOnce });
646+
ctx.configure();
647+
648+
await ctx.rpc.prompt({ input: [{ type: 'text', text: 'Trigger failed auto compaction' }] });
649+
const events = await ctx.untilTurnEnd();
650+
651+
expect(attempts).toBe(1);
652+
expect(events).not.toContainEqual(expect.objectContaining({ event: 'error' }));
653+
expect(events).toContainEqual(
654+
expect.objectContaining({
655+
event: 'turn.ended',
656+
args: {
657+
turnId: 0,
658+
reason: 'failed',
659+
error: expect.objectContaining({
660+
code: 'compaction.failed',
661+
message: 'APIStatusError: Bad request',
662+
}),
663+
},
664+
}),
665+
);
666+
const errorEvents = ctx.newEvents();
667+
expect(errorEvents).toHaveLength(1);
668+
expect(errorEvents[0]).toMatchObject({
669+
event: 'error',
670+
args: expect.objectContaining({
671+
code: 'compaction.failed',
672+
message: 'APIStatusError: Bad request',
673+
}),
674+
});
675+
await ctx.expectResumeMatches();
676+
});
677+
639678
it('reports compaction retry_count when retryable generation failures are exhausted', async () => {
640679
vi.useFakeTimers();
641680
const records: TelemetryRecord[] = [];

‎packages/kosong/src/errors.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const CONTEXT_OVERFLOW_MESSAGE_PATTERNS = [
8282
/(?:too many tokens.*(?:prompt|input|context)|(?:prompt|input|context).*too many tokens)/,
8383
/prompt is too long.*maximum/,
8484
/input token count.*exceeds?.*maximum number of tokens/,
85+
/request.*exceed(?:ed|s|ing)?.*model token limit/,
8586
] as const;
8687

8788
export function normalizeAPIStatusError(

‎packages/kosong/test/errors.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ describe('normalizeAPIStatusError', () => {
154154
[422, 'Too many tokens in prompt'],
155155
[400, 'prompt is too long: 210000 tokens exceeds the maximum'],
156156
[400, 'input token count 131072 exceeds the maximum number of tokens allowed'],
157+
[400, 'Invalid request: Your request exceeded model token limit: 262144 (requested: 274613)'],
157158
])('normalizes %i "%s" to APIContextOverflowError', (statusCode, message) => {
158159
const error = normalizeAPIStatusError(statusCode, message, 'req-context');
159160
expect(error).toBeInstanceOf(APIContextOverflowError);

0 commit comments

Comments
 (0)