From 0dc8efb03fb195febe803fc8cbd3b971c51d35b2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 7 Apr 2026 02:09:26 +0000 Subject: [PATCH] nearly building --- packages/core/src/context/pipeline.ts | 3 - .../blobDegradationProcessor.test.ts | 9 +- .../processors/blobDegradationProcessor.ts | 1 - .../emergencyTruncationProcessor.test.ts | 17 +++- .../emergencyTruncationProcessor.ts | 1 - .../historySquashingProcessor.test.ts | 21 +++-- .../processors/historySquashingProcessor.ts | 2 +- .../semanticCompressionProcessor.test.ts | 18 +++- .../semanticCompressionProcessor.ts | 4 +- .../processors/stateSnapshotProcessor.test.ts | 13 ++- .../processors/toolMaskingProcessor.test.ts | 9 +- .../processors/toolMaskingProcessor.ts | 1 - .../src/context/sidecar/orchestrator.test.ts | 25 +++--- .../lifecycle.golden.test.ts.snap | 89 +++++++++++++++++++ 14 files changed, 170 insertions(+), 43 deletions(-) create mode 100644 packages/core/src/context/system-tests/__snapshots__/lifecycle.golden.test.ts.snap diff --git a/packages/core/src/context/pipeline.ts b/packages/core/src/context/pipeline.ts index b4ff400667..1a2b3981a2 100644 --- a/packages/core/src/context/pipeline.ts +++ b/packages/core/src/context/pipeline.ts @@ -3,9 +3,6 @@ * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ - -import type { Episode } from './ir/types.js'; - import type { EpisodeEditor } from './ir/episodeEditor.js'; /** diff --git a/packages/core/src/context/processors/blobDegradationProcessor.test.ts b/packages/core/src/context/processors/blobDegradationProcessor.test.ts index d6cedacec1..5ec8555587 100644 --- a/packages/core/src/context/processors/blobDegradationProcessor.test.ts +++ b/packages/core/src/context/processors/blobDegradationProcessor.test.ts @@ -6,6 +6,7 @@ import { createMockEnvironment, createDummyState, createDummyEpisode } from '../testing/contextTestUtils.js'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import { BlobDegradationProcessor } from './blobDegradationProcessor.js'; +import { EpisodeEditor } from '../ir/episodeEditor.js'; import type { UserPrompt } from '../ir/types.js'; import type { ContextEnvironment } from '../sidecar/environment.js'; import { InMemoryFileSystem } from '../system/InMemoryFileSystem.js'; @@ -35,7 +36,9 @@ describe('BlobDegradationProcessor', () => { ]); const state = createDummyState(false, 500); - const result = await processor.process([ep], state); + const editor = new EpisodeEditor([ep]); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); const parts = (result[0].trigger as UserPrompt).semanticParts; @@ -65,7 +68,9 @@ describe('BlobDegradationProcessor', () => { ]); const state = createDummyState(false, 500); - const result = await processor.process([ep], state); + const editor = new EpisodeEditor([ep]); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); const parts = (result[0].trigger as UserPrompt).semanticParts; expect(parts[0].presentation).toBeDefined(); diff --git a/packages/core/src/context/processors/blobDegradationProcessor.ts b/packages/core/src/context/processors/blobDegradationProcessor.ts index 36097362cb..77fd277d4a 100644 --- a/packages/core/src/context/processors/blobDegradationProcessor.ts +++ b/packages/core/src/context/processors/blobDegradationProcessor.ts @@ -3,7 +3,6 @@ * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ -import type { Episode } from '../ir/types.js'; import type { ContextAccountingState, ContextProcessor } from '../pipeline.js'; import type { ContextEnvironment } from '../sidecar/environment.js'; diff --git a/packages/core/src/context/processors/emergencyTruncationProcessor.test.ts b/packages/core/src/context/processors/emergencyTruncationProcessor.test.ts index 03d5a08983..effcb8574c 100644 --- a/packages/core/src/context/processors/emergencyTruncationProcessor.test.ts +++ b/packages/core/src/context/processors/emergencyTruncationProcessor.test.ts @@ -7,6 +7,7 @@ import { createMockEnvironment, createDummyState, createDummyEpisode } from '../testing/contextTestUtils.js'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import { EmergencyTruncationProcessor } from './emergencyTruncationProcessor.js'; +import { EpisodeEditor } from '../ir/episodeEditor.js'; import type { ContextEnvironment } from '../sidecar/environment.js'; describe('EmergencyTruncationProcessor', () => { @@ -32,7 +33,9 @@ describe('EmergencyTruncationProcessor', () => { // State says we are under budget (5000 < 10000) const state = createDummyState(true, 0, new Set(), 5000, 10000); - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); expect(result).toStrictEqual(episodes); expect(result.length).toBe(1); }); @@ -48,7 +51,9 @@ describe('EmergencyTruncationProcessor', () => { // We have 300 tokens, but max is 200. We need to drop 100 tokens. const state = createDummyState(false, 100, new Set(), 300, 200); - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); // It should drop the FIRST episode (ep-1) and keep the rest. expect(result.length).toBe(2); @@ -67,7 +72,9 @@ describe('EmergencyTruncationProcessor', () => { // However, ep-1 is protected! const state = createDummyState(false, 100, new Set(['ep-1']), 300, 200); - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); // It should SKIP dropping ep-1 (protected) and drop ep-2 instead. expect(result.length).toBe(2); @@ -85,7 +92,9 @@ describe('EmergencyTruncationProcessor', () => { // We have 300 tokens, max is 50. We need to drop 250 tokens! const state = createDummyState(false, 250, new Set(), 300, 50); - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); // It must drop ep1 (100t) and ep2 (100t). // Remaining is ep3 (100t). diff --git a/packages/core/src/context/processors/emergencyTruncationProcessor.ts b/packages/core/src/context/processors/emergencyTruncationProcessor.ts index 13546c59e6..adeec7fb25 100644 --- a/packages/core/src/context/processors/emergencyTruncationProcessor.ts +++ b/packages/core/src/context/processors/emergencyTruncationProcessor.ts @@ -5,7 +5,6 @@ */ import type { ContextProcessor, ContextAccountingState } from '../pipeline.js'; -import type { Episode } from '../ir/types.js'; import type { ContextEnvironment } from '../sidecar/environment.js'; diff --git a/packages/core/src/context/processors/historySquashingProcessor.test.ts b/packages/core/src/context/processors/historySquashingProcessor.test.ts index 8a41d0f741..228ecec564 100644 --- a/packages/core/src/context/processors/historySquashingProcessor.test.ts +++ b/packages/core/src/context/processors/historySquashingProcessor.test.ts @@ -6,6 +6,7 @@ import { createMockEnvironment, createDummyState, createDummyEpisode } from '../testing/contextTestUtils.js'; import { describe, it, expect, beforeEach } from 'vitest'; import { HistorySquashingProcessor } from './historySquashingProcessor.js'; +import { EpisodeEditor } from '../ir/episodeEditor.js'; import type { UserPrompt, AgentThought, @@ -44,7 +45,9 @@ describe('HistorySquashingProcessor', () => { const episodes = [createThoughtEpisode('1', 'short text', 'short thought')]; const state = createDummyState(true); - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); expect(result).toStrictEqual(episodes); expect( @@ -58,7 +61,9 @@ describe('HistorySquashingProcessor', () => { const episodes = [createThoughtEpisode('ep-1', longText, 'short thought')]; const state = createDummyState(false, 100, new Set(['ep-1'])); - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); expect( (result[0].trigger as UserPrompt).semanticParts[0].presentation, @@ -71,7 +76,9 @@ describe('HistorySquashingProcessor', () => { const episodes = [createThoughtEpisode('ep-2', longUser, longModel)]; const state = createDummyState(false, 500); // High deficit, force truncation - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); const userPart = (result[0].trigger as UserPrompt).semanticParts[0]; const thoughtPart = result[0].steps[0] as AgentThought; @@ -103,7 +110,9 @@ describe('HistorySquashingProcessor', () => { // Original = ~250 tokens. Limit = 100. Truncation saves ~150 tokens. const state = createDummyState(false, 150); - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); // First episode should be truncated const ep1Part = (result[0].trigger as UserPrompt).semanticParts[0]; @@ -129,7 +138,9 @@ describe('HistorySquashingProcessor', () => { }; const state = createDummyState(false, 500); - const result = await processor.process([ep], state); + const editor = new EpisodeEditor([ep]); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); const yieldPart = result[0].yield as AgentYield; const yieldPresentation = yieldPart.presentation as { text: string }; diff --git a/packages/core/src/context/processors/historySquashingProcessor.ts b/packages/core/src/context/processors/historySquashingProcessor.ts index 3435ff453a..762f599ab6 100644 --- a/packages/core/src/context/processors/historySquashingProcessor.ts +++ b/packages/core/src/context/processors/historySquashingProcessor.ts @@ -4,10 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { Episode } from '../ir/types.js'; import type { ContextAccountingState, ContextProcessor } from '../pipeline.js'; import type { ContextEnvironment } from '../sidecar/environment.js'; import { truncateProportionally } from '../truncation.js'; +import type { EpisodeEditor } from '../ir/episodeEditor.js'; export class HistorySquashingProcessor implements ContextProcessor { readonly name = 'HistorySquashing'; diff --git a/packages/core/src/context/processors/semanticCompressionProcessor.test.ts b/packages/core/src/context/processors/semanticCompressionProcessor.test.ts index 18adc2c976..2f04ed9572 100644 --- a/packages/core/src/context/processors/semanticCompressionProcessor.test.ts +++ b/packages/core/src/context/processors/semanticCompressionProcessor.test.ts @@ -7,6 +7,7 @@ import { createMockEnvironment, createDummyState, createDummyEpisode } from '../testing/contextTestUtils.js'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import { SemanticCompressionProcessor } from './semanticCompressionProcessor.js'; +import { EpisodeEditor } from '../ir/episodeEditor.js'; import type { UserPrompt, ToolExecution, @@ -75,7 +76,9 @@ describe('SemanticCompressionProcessor', () => { const episodes = [createEpisodeWithThoughtsAndTools('1', 'short', 'short', 'short')]; const state = createDummyState(true); - await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + expect(generateContentMock).not.toHaveBeenCalled(); }); @@ -86,7 +89,9 @@ describe('SemanticCompressionProcessor', () => { ]; const state = createDummyState(false, 1000, new Set(['ep-1'])); - await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + expect(generateContentMock).not.toHaveBeenCalled(); }); @@ -97,10 +102,13 @@ describe('SemanticCompressionProcessor', () => { ]; const state = createDummyState(false, 50000); // Massive deficit, forces all 3 to summarize - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + expect(generateContentMock).toHaveBeenCalledTimes(3); // Verify presentation layers were injected + const result = editor.getFinalEpisodes(); const userPart = (result[0].trigger as UserPrompt).semanticParts[0]; const thoughtPart = result[0].steps[0] as AgentThought; const toolPart = result[0].steps[1] as ToolExecution; @@ -126,7 +134,9 @@ describe('SemanticCompressionProcessor', () => { // Set deficit low enough that ONE summary solves the problem const state = createDummyState(false, 5); - await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + // It should only compress the UserPrompt and then stop expect(generateContentMock).toHaveBeenCalledTimes(1); }); diff --git a/packages/core/src/context/processors/semanticCompressionProcessor.ts b/packages/core/src/context/processors/semanticCompressionProcessor.ts index 17ea70abcb..093cd773a1 100644 --- a/packages/core/src/context/processors/semanticCompressionProcessor.ts +++ b/packages/core/src/context/processors/semanticCompressionProcessor.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { Episode } from '../ir/types.js'; +import type { IrMetadata } from '../ir/types.js'; import type { ContextAccountingState, ContextProcessor } from '../pipeline.js'; import type { ContextEnvironment } from '../sidecar/environment.js'; import { debugLogger } from '../../utils/debugLogger.js'; @@ -166,7 +166,7 @@ export class SemanticCompressionProcessor implements ContextProcessor { observation: newObsObject, tokens: { intent: intentTokens as number, observation: newObsTokens }, }; - if (!draftStep.metadata) { draftStep.metadata = { transformations: [] } }; + if (!draftStep.metadata) { draftStep.metadata = { transformations: [], currentTokens: 0, originalTokens: 0 } as unknown as IrMetadata }; if (!draftStep.metadata.transformations) { draftStep.metadata.transformations = [] }; draftStep.metadata.transformations.push({ processorName: this.name, diff --git a/packages/core/src/context/processors/stateSnapshotProcessor.test.ts b/packages/core/src/context/processors/stateSnapshotProcessor.test.ts index 752e971854..c508801be1 100644 --- a/packages/core/src/context/processors/stateSnapshotProcessor.test.ts +++ b/packages/core/src/context/processors/stateSnapshotProcessor.test.ts @@ -7,6 +7,7 @@ import { createMockEnvironment, createDummyState, createDummyEpisode } from '../testing/contextTestUtils.js'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import { StateSnapshotProcessor } from './stateSnapshotProcessor.js'; +import { EpisodeEditor } from '../ir/episodeEditor.js'; import type { ContextEnvironment } from '../sidecar/environment.js'; import type { BaseLlmClient } from '../../core/baseLlmClient.js'; @@ -37,7 +38,9 @@ describe('StateSnapshotProcessor', () => { // current: 100, max: 1000, retained: 200 (deficit 0) const state = createDummyState(false, 0, new Set(), 100, 1000, 200); - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); expect(result).toStrictEqual(episodes); expect(generateContentMock).not.toHaveBeenCalled(); }); @@ -51,7 +54,9 @@ describe('StateSnapshotProcessor', () => { // current: 1000, max: 10000, retained: 500. Target deficit = 500 const state = createDummyState(false, 500, new Set(), 1000, 10000, 500); - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); expect(result).toStrictEqual(episodes); expect(generateContentMock).not.toHaveBeenCalled(); }); @@ -67,7 +72,9 @@ describe('StateSnapshotProcessor', () => { // Target deficit = 200 const state = createDummyState(false, 200, new Set(), 1000, 10000, 800); - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); // We started with 4 episodes. // Episodes [1, 2] were synthesized into a single new Snapshot episode. diff --git a/packages/core/src/context/processors/toolMaskingProcessor.test.ts b/packages/core/src/context/processors/toolMaskingProcessor.test.ts index c63dbf40af..6a12b183ae 100644 --- a/packages/core/src/context/processors/toolMaskingProcessor.test.ts +++ b/packages/core/src/context/processors/toolMaskingProcessor.test.ts @@ -7,6 +7,7 @@ import { createMockEnvironment } from '../testing/contextTestUtils.js'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import { ToolMaskingProcessor } from './toolMaskingProcessor.js'; +import { EpisodeEditor } from '../ir/episodeEditor.js'; import type { Episode, ToolExecution } from '../ir/types.js'; import type { ContextAccountingState } from '../pipeline.js'; import { randomUUID } from 'node:crypto'; @@ -78,7 +79,9 @@ describe('ToolMaskingProcessor', () => { ]; const state = getDummyState(true); - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); expect(result).toStrictEqual(episodes); expect((result[0].steps[0] as ToolExecution).presentation).toBeUndefined(); @@ -95,7 +98,9 @@ describe('ToolMaskingProcessor', () => { const episodes = [createDummyEpisode('ep-1', intentPayload, obsPayload)]; const state = getDummyState(false, 1000, new Set()); // Huge deficit - const result = await processor.process(episodes, state); + const editor = new EpisodeEditor(episodes); + await processor.process(editor, state); + const result = editor.getFinalEpisodes(); const toolStep = result[0].steps[0] as ToolExecution; diff --git a/packages/core/src/context/processors/toolMaskingProcessor.ts b/packages/core/src/context/processors/toolMaskingProcessor.ts index 8a5196c0ca..13ff680bf7 100644 --- a/packages/core/src/context/processors/toolMaskingProcessor.ts +++ b/packages/core/src/context/processors/toolMaskingProcessor.ts @@ -15,7 +15,6 @@ import { ENTER_PLAN_MODE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, } from '../../tools/tool-names.js'; -import type { Episode } from '../ir/types.js'; const UNMASKABLE_TOOLS = new Set([ ACTIVATE_SKILL_TOOL_NAME, diff --git a/packages/core/src/context/sidecar/orchestrator.test.ts b/packages/core/src/context/sidecar/orchestrator.test.ts index 84ca081279..96599dc980 100644 --- a/packages/core/src/context/sidecar/orchestrator.test.ts +++ b/packages/core/src/context/sidecar/orchestrator.test.ts @@ -11,19 +11,17 @@ import { createMockEnvironment, createDummyState, createDummyEpisode } from '../ import type { ContextEnvironment } from './environment.js'; import type { ContextProcessor } from '../pipeline.js'; import type { SidecarConfig } from './types.js'; -import { ContextEventBus } from '../eventBus.js'; - -import type { Episode } from '../ir/types.js'; +import type { ContextEventBus } from '../eventBus.js'; // Create a Dummy Processor for testing Orchestration routing class DummySyncProcessor implements ContextProcessor { static create() { return new DummySyncProcessor(); } constructor() {} readonly name = 'DummySync'; - async process(episodes: any[], _state: any) { - const copy = [...episodes]; - copy[0] = { ...copy[0], dummyModified: true }; - return copy; + async process(editor: any, _state: any) { + editor.editEpisode(editor.episodes[0].id, 'DUMMY_EDIT', (draft: any) => { + draft.dummyModified = true; + }); } } @@ -31,19 +29,18 @@ class DummyAsyncProcessor implements ContextProcessor { static create() { return new DummyAsyncProcessor(); } constructor() {} readonly name = 'DummyAsync'; - async process(episodes: any[], _state: any) { - await new Promise(resolve => setTimeout(resolve, 50)); - const copy = [...episodes]; - copy[0] = { ...copy[0], asyncModified: true }; - return copy; + async process(editor: any, _state: any) { + editor.editEpisode(editor.episodes[0].id, 'DUMMY_EDIT', (draft: any) => { + draft.dummyAsyncModified = true; + }); } } class ThrowingProcessor implements ContextProcessor { static create() { return new ThrowingProcessor(); } constructor() {} - readonly name = 'Thrower'; - async process(): Promise { + readonly name = 'Throwing'; + async process(editor: any, state: any): Promise { throw new Error('Processor failed intentionally'); } } diff --git a/packages/core/src/context/system-tests/__snapshots__/lifecycle.golden.test.ts.snap b/packages/core/src/context/system-tests/__snapshots__/lifecycle.golden.test.ts.snap new file mode 100644 index 0000000000..cab629a597 --- /dev/null +++ b/packages/core/src/context/system-tests/__snapshots__/lifecycle.golden.test.ts.snap @@ -0,0 +1,89 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`System Lifecycle Golden Tests > Scenario 1: Organic Growth with Huge Tool Output & Images 1`] = ` +{ + "finalProjection": [ + { + "parts": [ + { + "text": "System Instructions", + }, + ], + "role": "user", + }, + { + "parts": [ + { + "text": "Ack.", + }, + ], + "role": "model", + }, + { + "parts": [ + { + "text": "Look at this architecture diagram:", + }, + { + "inlineData": { + "data": "fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_fake_base64_data_", + "mimeType": "image/png", + }, + }, + ], + "role": "user", + }, + { + "parts": [ + { + "text": "Nice diagram.", + }, + ], + "role": "model", + }, + { + "parts": [ + { + "text": "Can we refactor?", + }, + ], + "role": "user", + }, + { + "parts": [ + { + "text": "Yes we can.", + }, + ], + "role": "model", + }, + ], + "tokenTrajectory": [ + { + "tokensAfterBackground": 11, + "tokensBeforeBackground": 11, + "turnIndex": 0, + }, + { + "tokensAfterBackground": 23, + "tokensBeforeBackground": 23, + "turnIndex": 1, + }, + { + "tokensAfterBackground": 10067, + "tokensBeforeBackground": 10067, + "turnIndex": 2, + }, + { + "tokensAfterBackground": 13349, + "tokensBeforeBackground": 13349, + "turnIndex": 3, + }, + { + "tokensAfterBackground": 13362, + "tokensBeforeBackground": 13362, + "turnIndex": 4, + }, + ], +} +`;