Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/thinking-signature-recovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Fix conversations breaking after switching between models from different providers mid-session.
7 changes: 6 additions & 1 deletion packages/agent-core-v2/docs/state-manifest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
// references become '(circular)', and class instances collapse to a '(ClassName)'
// marker — the wire shape of an entry is the JSON projection of the type here.
//
// Index (App: 0 keys · Workspace: 6 keys · Session: 9 keys · Agent: 80 keys)
// Index (App: 0 keys · Workspace: 6 keys · Session: 9 keys · Agent: 81 keys)
// App
// Workspace
// workspaceDirs.ephemeralDirs src/workspace/workspaceDirs/workspaceDirsService.ts
Expand Down Expand Up @@ -71,6 +71,7 @@
// llmRequester.lastConfigLogSignature src/agent/llmRequester/llmRequesterService.ts
// llmRequester.mediaDegradedTurns src/agent/llmRequester/llmRequesterService.ts
// llmRequester.mediaStrippedTurns src/agent/llmRequester/llmRequesterService.ts
// llmRequester.thinkingStripped src/agent/llmRequester/llmRequesterService.ts
// llmRequester.turnConfigs src/agent/llmRequester/llmRequesterService.ts
// loop.disposing src/agent/loop/loopService.ts
// loop.lastRequestTraceId src/agent/loop/loopService.ts
Expand Down Expand Up @@ -1051,6 +1052,7 @@ export interface AgentStateSnapshot {
type: 'think';
think: string;
encrypted?: string;
encryptedProtocol?: 'anthropic' | 'openai' | 'openai_responses' | 'google-genai';
} | /* ImageURLPart — packages/agent-core-v2/src/kosong/contract/message.ts */ {
type: 'image_url';
imageUrl: {
Expand Down Expand Up @@ -1197,6 +1199,8 @@ export interface AgentStateSnapshot {
'llmRequester.mediaDegradedTurns': Set<number>;
// replayable · durable — folds: MediaStripped
'llmRequester.mediaStrippedTurns': readonly string[];
// replayable · durable — folds: ThinkingStripped
'llmRequester.thinkingStripped': boolean;
'llmRequester.turnConfigs': Map<number, /* TurnRequestConfig — packages/agent-core-v2/src/agent/llmRequester/llmRequesterService.ts */ {
readonly resolved: /* ProfileModelContext — packages/agent-core-v2/src/agent/profile/profile.ts */ {
readonly modelAlias: string;
Expand Down Expand Up @@ -1265,6 +1269,7 @@ export interface AgentStateSnapshot {
type: 'think';
think: string;
encrypted?: string;
encryptedProtocol?: 'anthropic' | 'openai' | 'openai_responses' | 'google-genai';
} | /* ImageURLPart — packages/agent-core-v2/src/kosong/contract/message.ts */ {
type: 'image_url';
imageUrl: {
Expand Down
15 changes: 13 additions & 2 deletions packages/agent-core-v2/docs/wire-manifest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
// cross-reducers), blobs (the folding states whose blob codec offloads inline
// media to blob storage), owner (the source file declaring the class).

// Index (58 record types)
// Index (59 record types)
// config.update profile src/agent/profile/profileOps.ts
// context.append_loop_event contextMemory, turn src/agent/contextMemory/contextEvents.ts
// context.append_message contextMemory, plan, task.notificationDelivery src/agent/contextMemory/contextEvents.ts
Expand All @@ -46,6 +46,7 @@
// interruptionReminder.recorded interruptionReminder src/agent/interruptionReminder/interruptionReminderOps.ts
// llm.media_stripped llmRequester.mediaStrippedTurns src/agent/llmRequester/llmRequestOps.ts
// llm.request llm.requestTrace src/agent/llmRequester/llmRequestOps.ts
// llm.thinking_stripped llmRequester.thinkingStripped src/agent/llmRequester/llmRequestOps.ts
// llm.tools_snapshot llm.requestTrace src/agent/llmRequester/llmRequestOps.ts
// mcp.tools_discovered mcp.discovery src/agent/mcp/mcpDiscoveryOps.ts
// permission.record_approval_result permissionRules src/agent/permissionRules/permissionRulesOps.ts
Expand Down Expand Up @@ -375,10 +376,19 @@ interface LlmRequestPayload {
messageCount: number;
turnStep?: string;
attempt?: string;
projection?: 'strict' | 'media-degraded' | 'media-stripped' | 'strict-media-degraded' | 'strict-media-stripped';
projection?: 'strict' | 'media-degraded' | 'media-stripped' | 'strict-media-degraded' | 'strict-media-stripped' | 'thinking-stripped' | 'strict-thinking-stripped' | 'media-degraded-thinking-stripped' | 'media-stripped-thinking-stripped' | 'strict-media-degraded-thinking-stripped' | 'strict-media-stripped-thinking-stripped';
droppedCount?: number;
}

/**
* states: llmRequester.thinkingStripped
* owner: src/agent/llmRequester/llmRequestOps.ts
*/
interface LlmThinkingStrippedPayload {
_name: 'llm.thinking_stripped';
agentId: string;
}

/**
* states: llm.requestTrace
* owner: src/agent/llmRequester/llmRequestOps.ts
Expand Down Expand Up @@ -880,6 +890,7 @@ interface WirePayloadMap {
"interruptionReminder.recorded": InterruptionReminderRecordedPayload;
"llm.media_stripped": LlmMediaStrippedPayload;
"llm.request": LlmRequestPayload;
"llm.thinking_stripped": LlmThinkingStrippedPayload;
"llm.tools_snapshot": LlmToolsSnapshotPayload;
"mcp.tools_discovered": McpToolsDiscoveredPayload;
"permission.record_approval_result": PermissionRecordApprovalResultPayload;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface MediaStripSnapshot {
export interface ProjectionPolicy {
readonly structure?: 'strict';
readonly media?: 'degraded' | { readonly strip: MediaStripSnapshot };
readonly thinking?: 'strip';
}

export interface IAgentContextProjectorService {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import {
project,
projectStrict,
stripThinkingParts,
summarizeProjectionRepairs,
type OnAnomaly,
type ProjectionAnomaly,
Expand Down Expand Up @@ -57,10 +58,11 @@ export class AgentContextProjectorService implements IAgentContextProjectorServi
messages,
policy.structure === 'strict' ? projectStrict : project,
);
const thought = policy.thinking === 'strip' ? stripThinkingParts(projected) : projected;
const media = policy.media;
if (media === undefined) return projected;
if (media === 'degraded') return degradeOlderMediaParts(projected, MEDIA_DEGRADE_KEEP_RECENT);
return stripMediaPartsBySnapshot(projected, media.strip);
if (media === undefined) return thought;
if (media === 'degraded') return degradeOlderMediaParts(thought, MEDIA_DEGRADE_KEEP_RECENT);
return stripMediaPartsBySnapshot(thought, media.strip);
}

captureMediaStripSnapshot(messages: readonly ContextMessage[]): MediaStripSnapshot {
Expand Down
30 changes: 29 additions & 1 deletion packages/agent-core-v2/src/agent/contextProjector/projection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ export function projectStrict(
);
}

export function stripThinkingParts(messages: readonly Message[]): readonly Message[] {
if (!messages.some(hasThinkPart)) return messages;
const out: Message[] = [];
for (const message of messages) {
if (!hasThinkPart(message)) {
out.push(message);
continue;
}
const stripped: Message = {
...message,
content: message.content.filter((part) => part.type !== 'think'),
};
if (isWireSendableMessage(stripped)) out.push(stripped);
}
return out;
}

interface SliceLayout {
readonly sizing: boolean;
readonly lastNonToolIndex: number;
Expand Down Expand Up @@ -416,11 +433,22 @@ function wireSendableContent(content: readonly ContentPart[]): ContentPart[] {
return content.filter((part) => part.type !== 'think' || part.encrypted !== undefined);
}

function hasThinkPart(message: Message): boolean {
return message.content.some((part) => part.type === 'think');
}

function isWireSendableMessage(message: Message): boolean {
if (message.role === 'tool') return true;
if (message.toolCalls.length > 0) return true;
if (hasDeclaredTools(message)) return true;
return !message.content.every(isVacuousContentPart);
}

function canMergeUserMessage(message: ContextMessage): boolean {
return message.role === 'user' && message.origin?.kind === 'user';
}

function hasDeclaredTools(message: ContextMessage): boolean {
function hasDeclaredTools(message: Message): boolean {
return message.tools !== undefined && message.tools.length > 0;
}

Expand Down
37 changes: 35 additions & 2 deletions packages/agent-core-v2/src/agent/llmRequester/llmRequestOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ export interface LlmToolsSnapshot {
readonly tools: readonly LlmRequestToolSchema[];
}

export const llmRequestProjectionSchema = z.enum([
'strict',
'media-degraded',
'media-stripped',
'strict-media-degraded',
'strict-media-stripped',
'thinking-stripped',
'strict-thinking-stripped',
'media-degraded-thinking-stripped',
'media-stripped-thinking-stripped',
'strict-media-degraded-thinking-stripped',
'strict-media-stripped-thinking-stripped',
]);

const llmRequestSchema = z.object({
agentId: z.string(),
kind: z.enum(['loop', 'compaction']),
Expand All @@ -57,7 +71,7 @@ const llmRequestSchema = z.object({
messageCount: z.number(),
turnStep: z.string().optional(),
attempt: z.string().optional(),
projection: z.enum(['strict', 'media-degraded', 'media-stripped', 'strict-media-degraded', 'strict-media-stripped']).optional(),
projection: llmRequestProjectionSchema.optional(),
droppedCount: z.number().optional(),
});

Expand Down Expand Up @@ -92,7 +106,13 @@ export interface LlmRequest {
| 'media-degraded'
| 'media-stripped'
| 'strict-media-degraded'
| 'strict-media-stripped';
| 'strict-media-stripped'
| 'thinking-stripped'
| 'strict-thinking-stripped'
| 'media-degraded-thinking-stripped'
| 'media-stripped-thinking-stripped'
| 'strict-media-degraded-thinking-stripped'
| 'strict-media-stripped-thinking-stripped';
readonly droppedCount?: number;
}

Expand All @@ -111,6 +131,19 @@ export interface MediaStripped {
readonly keys: readonly string[];
}

const thinkingStrippedSchema = z.object({
agentId: z.string(),
});

export class ThinkingStripped extends AgentEvent2<z.infer<typeof thinkingStrippedSchema>> {
static override readonly type = 'llm.thinking_stripped';
static override readonly durable = true;
static override readonly schema = thinkingStrippedSchema;
}
export interface ThinkingStripped {
readonly agentId: string;
}

export const llmRequestTraceKey = defineState(
'llm.requestTrace',
(): LlmRequestTraceState => ({ seenToolsHashes: [] }),
Expand Down
Loading
Loading