mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-18 15:52:53 -07:00
mapper tidy
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Content, Part } from '@google/genai';
|
||||
import type { Episode, EpisodeStep, UserPrompt, AgentYield } from './types.js';
|
||||
|
||||
export function fromIr(episodes: Episode[]): Content[] {
|
||||
const history: Content[] = [];
|
||||
|
||||
for (const ep of episodes) {
|
||||
if (ep.trigger.type === 'USER_PROMPT') {
|
||||
const triggerContent = serializeTrigger(ep.trigger);
|
||||
if (triggerContent) history.push(triggerContent);
|
||||
}
|
||||
|
||||
const stepContents = serializeSteps(ep.steps);
|
||||
history.push(...stepContents);
|
||||
|
||||
if (ep.yield) {
|
||||
history.push(serializeYield(ep.yield));
|
||||
}
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
function serializeTrigger(trigger: UserPrompt): Content | null {
|
||||
const parts: Part[] = [];
|
||||
for (const sp of trigger.semanticParts) {
|
||||
if (sp.presentation) {
|
||||
parts.push({ text: sp.presentation.text });
|
||||
} else if (sp.type === 'text') {
|
||||
parts.push({ text: sp.text });
|
||||
} else if (sp.type === 'inline_data') {
|
||||
parts.push({
|
||||
inlineData: { mimeType: sp.mimeType, data: sp.data },
|
||||
});
|
||||
} else if (sp.type === 'file_data') {
|
||||
parts.push({
|
||||
fileData: { mimeType: sp.mimeType, fileUri: sp.fileUri },
|
||||
});
|
||||
} else if (sp.type === 'raw_part') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-unsafe-type-assertion
|
||||
parts.push(sp.part as unknown as Part);
|
||||
}
|
||||
}
|
||||
return parts.length > 0 ? { role: 'user', parts } : null;
|
||||
}
|
||||
|
||||
function serializeSteps(steps: EpisodeStep[]): Content[] {
|
||||
const history: Content[] = [];
|
||||
let pendingModelParts: Part[] = [];
|
||||
let pendingUserParts: Part[] = [];
|
||||
|
||||
const flushPending = () => {
|
||||
if (pendingModelParts.length > 0) {
|
||||
history.push({ role: 'model', parts: [...pendingModelParts] });
|
||||
pendingModelParts = [];
|
||||
}
|
||||
if (pendingUserParts.length > 0) {
|
||||
history.push({ role: 'user', parts: [...pendingUserParts] });
|
||||
pendingUserParts = [];
|
||||
}
|
||||
};
|
||||
|
||||
for (const step of steps) {
|
||||
if (step.type === 'AGENT_THOUGHT') {
|
||||
if (pendingUserParts.length > 0) flushPending();
|
||||
pendingModelParts.push({
|
||||
text: step.presentation?.text ?? step.text,
|
||||
});
|
||||
} else if (step.type === 'TOOL_EXECUTION') {
|
||||
pendingModelParts.push({
|
||||
functionCall: {
|
||||
name: step.toolName,
|
||||
args: step.intent as unknown as Record<string, unknown>, // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
|
||||
id: step.id,
|
||||
},
|
||||
});
|
||||
const observation = step.presentation
|
||||
? step.presentation.observation
|
||||
: step.observation;
|
||||
pendingUserParts.push({
|
||||
functionResponse: {
|
||||
name: step.toolName,
|
||||
response: observation as unknown as Record<string, unknown>, // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
|
||||
id: step.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
flushPending();
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
function serializeYield(yieldNode: AgentYield): Content {
|
||||
return {
|
||||
role: 'model',
|
||||
parts: [{ text: yieldNode.presentation?.text ?? yieldNode.text }],
|
||||
};
|
||||
}
|
||||
@@ -4,304 +4,28 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Content, Part } from '@google/genai';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type {
|
||||
Episode,
|
||||
IrMetadata,
|
||||
SemanticPart,
|
||||
ToolExecution,
|
||||
AgentThought,
|
||||
AgentYield,
|
||||
UserPrompt,
|
||||
} from './types.js';
|
||||
import { estimateContextTokenCountSync as estimateTokenCountSync } from '../utils/contextTokenCalculator.js';
|
||||
|
||||
// WeakMap to provide stable, deterministic identity across parses for the exact same Content/Part references
|
||||
const nodeIdentityMap = new WeakMap<object, string>();
|
||||
|
||||
function getStableId(obj: object): string {
|
||||
let id = nodeIdentityMap.get(obj);
|
||||
if (!id) {
|
||||
id = randomUUID();
|
||||
nodeIdentityMap.set(obj, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
import type { Content } from '@google/genai';
|
||||
import type { Episode } from './types.js';
|
||||
import { toIr, setMapperConfig } from './toIr.js';
|
||||
import { fromIr } from './fromIr.js';
|
||||
|
||||
export class IrMapper {
|
||||
static setConfig(cfg: { charsPerToken?: number }) {
|
||||
this.config = cfg;
|
||||
setMapperConfig(cfg);
|
||||
}
|
||||
private static config: { charsPerToken?: number } | undefined;
|
||||
|
||||
/**
|
||||
* Translates a flat Gemini Content[] array into our rich Episodic Intermediate Representation.
|
||||
* Groups adjacent function calls and responses into unified ToolExecution nodes.
|
||||
*/
|
||||
static toIr(history: readonly Content[]): Episode[] {
|
||||
const episodes: Episode[] = [];
|
||||
let currentEpisode: Partial<Episode> | null = null;
|
||||
const pendingCallParts: Map<string, Part> = new Map();
|
||||
|
||||
const createMetadata = (parts: Part[]): IrMetadata => {
|
||||
const tokens = estimateTokenCountSync(parts, 0, IrMapper.config);
|
||||
return {
|
||||
originalTokens: tokens,
|
||||
currentTokens: tokens,
|
||||
transformations: [],
|
||||
};
|
||||
};
|
||||
|
||||
const finalizeEpisode = () => {
|
||||
if (currentEpisode && currentEpisode.trigger) {
|
||||
episodes.push(currentEpisode as unknown as Episode); // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
|
||||
}
|
||||
currentEpisode = null;
|
||||
};
|
||||
|
||||
for (const msg of history) {
|
||||
if (!msg.parts) continue;
|
||||
|
||||
if (msg.role === 'user') {
|
||||
const hasToolResponses = msg.parts.some((p) => !!p.functionResponse);
|
||||
const hasUserParts = msg.parts.some(
|
||||
(p) => !!p.text || !!p.inlineData || !!p.fileData,
|
||||
);
|
||||
|
||||
if (hasToolResponses) {
|
||||
if (!currentEpisode) {
|
||||
currentEpisode = {
|
||||
id: getStableId(msg),
|
||||
timestamp: Date.now(),
|
||||
trigger: {
|
||||
id: getStableId(msg.parts[0] || msg),
|
||||
type: 'SYSTEM_EVENT',
|
||||
name: 'history_resume',
|
||||
payload: {},
|
||||
metadata: createMetadata([]),
|
||||
},
|
||||
steps: [],
|
||||
};
|
||||
}
|
||||
|
||||
for (const part of msg.parts) {
|
||||
if (part.functionResponse) {
|
||||
const callId = part.functionResponse.id || '';
|
||||
const matchingCall = pendingCallParts.get(callId);
|
||||
|
||||
const intentTokens = matchingCall
|
||||
? estimateTokenCountSync([matchingCall])
|
||||
: 0;
|
||||
const obsTokens = estimateTokenCountSync([part]);
|
||||
|
||||
const step: ToolExecution = {
|
||||
id: getStableId(part),
|
||||
type: 'TOOL_EXECUTION',
|
||||
toolName: part.functionResponse.name || 'unknown',
|
||||
intent:
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(matchingCall?.functionCall?.args as unknown as Record<
|
||||
string,
|
||||
unknown
|
||||
>) || {},
|
||||
observation:
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(part.functionResponse.response as unknown as Record<
|
||||
string,
|
||||
unknown
|
||||
>) || {},
|
||||
tokens: {
|
||||
intent: intentTokens,
|
||||
observation: obsTokens,
|
||||
},
|
||||
metadata: {
|
||||
originalTokens: intentTokens + obsTokens,
|
||||
currentTokens: intentTokens + obsTokens,
|
||||
transformations: [],
|
||||
},
|
||||
};
|
||||
currentEpisode.steps!.push(step);
|
||||
if (callId) pendingCallParts.delete(callId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUserParts) {
|
||||
finalizeEpisode();
|
||||
|
||||
const semanticParts: SemanticPart[] = [];
|
||||
for (const p of msg.parts) {
|
||||
if (p.text !== undefined)
|
||||
semanticParts.push({ type: 'text', text: p.text });
|
||||
else if (p.inlineData)
|
||||
semanticParts.push({
|
||||
type: 'inline_data',
|
||||
mimeType: p.inlineData.mimeType || '',
|
||||
data: p.inlineData.data || '',
|
||||
});
|
||||
else if (p.fileData)
|
||||
semanticParts.push({
|
||||
type: 'file_data',
|
||||
mimeType: p.fileData.mimeType || '',
|
||||
fileUri: p.fileData.fileUri || '',
|
||||
});
|
||||
else if (!p.functionResponse)
|
||||
semanticParts.push({ type: 'raw_part', part: p }); // Preserve unknowns
|
||||
}
|
||||
|
||||
const trigger: UserPrompt = {
|
||||
id: getStableId(msg.parts[0] || msg),
|
||||
type: 'USER_PROMPT',
|
||||
semanticParts,
|
||||
metadata: createMetadata(
|
||||
msg.parts.filter((p) => !p.functionResponse),
|
||||
),
|
||||
};
|
||||
|
||||
currentEpisode = {
|
||||
id: getStableId(msg),
|
||||
timestamp: Date.now(),
|
||||
trigger,
|
||||
steps: [],
|
||||
};
|
||||
}
|
||||
} else if (msg.role === 'model') {
|
||||
if (!currentEpisode) {
|
||||
currentEpisode = {
|
||||
id: getStableId(msg),
|
||||
timestamp: Date.now(),
|
||||
trigger: {
|
||||
id: getStableId(msg.parts[0] || msg),
|
||||
type: 'SYSTEM_EVENT',
|
||||
name: 'model_init',
|
||||
payload: {},
|
||||
metadata: createMetadata([]),
|
||||
},
|
||||
steps: [],
|
||||
};
|
||||
}
|
||||
|
||||
for (const part of msg.parts) {
|
||||
if (part.functionCall) {
|
||||
const callId = part.functionCall.id || '';
|
||||
if (callId) pendingCallParts.set(callId, part);
|
||||
} else if (part.text) {
|
||||
const thought: AgentThought = {
|
||||
id: getStableId(part),
|
||||
type: 'AGENT_THOUGHT',
|
||||
text: part.text,
|
||||
metadata: createMetadata([part]),
|
||||
};
|
||||
currentEpisode.steps!.push(thought);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentEpisode) {
|
||||
if (currentEpisode.steps && currentEpisode.steps.length > 0) {
|
||||
const lastStep = currentEpisode.steps[currentEpisode.steps.length - 1];
|
||||
if (lastStep.type === 'AGENT_THOUGHT') {
|
||||
const yieldNode: AgentYield = {
|
||||
id: lastStep.id,
|
||||
type: 'AGENT_YIELD',
|
||||
text: lastStep.text,
|
||||
metadata: lastStep.metadata,
|
||||
};
|
||||
currentEpisode.steps.pop();
|
||||
currentEpisode.yield = yieldNode;
|
||||
}
|
||||
}
|
||||
finalizeEpisode();
|
||||
}
|
||||
|
||||
return episodes;
|
||||
return toIr(history);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-serializes the Episodic IR back into a flat Gemini Content[] array.
|
||||
*/
|
||||
static fromIr(episodes: Episode[]): Content[] {
|
||||
const history: Content[] = [];
|
||||
|
||||
for (const ep of episodes) {
|
||||
// 1. Serialize Trigger
|
||||
if (ep.trigger.type === 'USER_PROMPT') {
|
||||
const parts: Part[] = [];
|
||||
for (const sp of ep.trigger.semanticParts) {
|
||||
if (sp.presentation) {
|
||||
parts.push({ text: sp.presentation.text });
|
||||
} else if (sp.type === 'text') {
|
||||
parts.push({ text: sp.text });
|
||||
} else if (sp.type === 'inline_data') {
|
||||
parts.push({
|
||||
inlineData: { mimeType: sp.mimeType, data: sp.data },
|
||||
});
|
||||
} else if (sp.type === 'file_data') {
|
||||
parts.push({
|
||||
fileData: { mimeType: sp.mimeType, fileUri: sp.fileUri },
|
||||
});
|
||||
} else if (sp.type === 'raw_part') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-unsafe-type-assertion
|
||||
parts.push(sp.part as unknown as Part);
|
||||
}
|
||||
}
|
||||
if (parts.length > 0) history.push({ role: 'user', parts });
|
||||
}
|
||||
|
||||
// 2. Serialize Steps
|
||||
let pendingModelParts: Part[] = [];
|
||||
let pendingUserParts: Part[] = [];
|
||||
|
||||
const flushPending = () => {
|
||||
if (pendingModelParts.length > 0) {
|
||||
history.push({ role: 'model', parts: [...pendingModelParts] });
|
||||
pendingModelParts = [];
|
||||
}
|
||||
if (pendingUserParts.length > 0) {
|
||||
history.push({ role: 'user', parts: [...pendingUserParts] });
|
||||
pendingUserParts = [];
|
||||
}
|
||||
};
|
||||
|
||||
for (const step of ep.steps) {
|
||||
if (step.type === 'AGENT_THOUGHT') {
|
||||
if (pendingUserParts.length > 0) flushPending();
|
||||
pendingModelParts.push({
|
||||
text: step.presentation?.text ?? step.text,
|
||||
});
|
||||
} else if (step.type === 'TOOL_EXECUTION') {
|
||||
pendingModelParts.push({
|
||||
functionCall: {
|
||||
name: step.toolName,
|
||||
args: step.intent as unknown as Record<string, unknown>, // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
|
||||
id: step.id,
|
||||
},
|
||||
});
|
||||
const observation = step.presentation
|
||||
? step.presentation.observation
|
||||
: step.observation;
|
||||
pendingUserParts.push({
|
||||
functionResponse: {
|
||||
name: step.toolName,
|
||||
response: observation as unknown as Record<string, unknown>, // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
|
||||
id: step.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
flushPending();
|
||||
|
||||
// 3. Serialize Yield
|
||||
if (ep.yield) {
|
||||
history.push({
|
||||
role: 'model',
|
||||
parts: [{ text: ep.yield.presentation?.text ?? ep.yield.text }],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return history;
|
||||
return fromIr(episodes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type { Content, Part } from '@google/genai';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type {
|
||||
Episode,
|
||||
IrMetadata,
|
||||
SemanticPart,
|
||||
ToolExecution,
|
||||
AgentThought,
|
||||
AgentYield,
|
||||
UserPrompt,
|
||||
SystemEvent,
|
||||
} from './types.js';
|
||||
import { estimateContextTokenCountSync } from '../utils/contextTokenCalculator.js';
|
||||
|
||||
// WeakMap to provide stable, deterministic identity across parses for the exact same Content/Part references
|
||||
const nodeIdentityMap = new WeakMap<object, string>();
|
||||
|
||||
export function getStableId(obj: object): string {
|
||||
let id = nodeIdentityMap.get(obj);
|
||||
if (!id) {
|
||||
id = randomUUID();
|
||||
nodeIdentityMap.set(obj, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export let charsPerTokenConfig: { charsPerToken?: number } | undefined;
|
||||
|
||||
export function setMapperConfig(cfg: { charsPerToken?: number }) {
|
||||
charsPerTokenConfig = cfg;
|
||||
}
|
||||
|
||||
export function createMetadata(parts: Part[]): IrMetadata {
|
||||
const tokens = estimateContextTokenCountSync(parts, 0, charsPerTokenConfig);
|
||||
return {
|
||||
originalTokens: tokens,
|
||||
currentTokens: tokens,
|
||||
transformations: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function toIr(history: readonly Content[]): Episode[] {
|
||||
const episodes: Episode[] = [];
|
||||
let currentEpisode: Partial<Episode> | null = null;
|
||||
const pendingCallParts: Map<string, Part> = new Map();
|
||||
|
||||
const finalizeEpisode = () => {
|
||||
if (currentEpisode && currentEpisode.trigger) {
|
||||
episodes.push(currentEpisode as unknown as Episode); // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
|
||||
}
|
||||
currentEpisode = null;
|
||||
};
|
||||
|
||||
for (const msg of history) {
|
||||
if (!msg.parts) continue;
|
||||
|
||||
if (msg.role === 'user') {
|
||||
const hasToolResponses = msg.parts.some((p) => !!p.functionResponse);
|
||||
const hasUserParts = msg.parts.some(
|
||||
(p) => !!p.text || !!p.inlineData || !!p.fileData,
|
||||
);
|
||||
|
||||
if (hasToolResponses) {
|
||||
currentEpisode = parseToolResponses(msg, currentEpisode, pendingCallParts);
|
||||
}
|
||||
|
||||
if (hasUserParts) {
|
||||
finalizeEpisode();
|
||||
currentEpisode = parseUserParts(msg);
|
||||
}
|
||||
} else if (msg.role === 'model') {
|
||||
currentEpisode = parseModelParts(msg, currentEpisode, pendingCallParts);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentEpisode) {
|
||||
finalizeYield(currentEpisode);
|
||||
finalizeEpisode();
|
||||
}
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
function parseToolResponses(
|
||||
msg: Content,
|
||||
currentEpisode: Partial<Episode> | null,
|
||||
pendingCallParts: Map<string, Part>,
|
||||
): Partial<Episode> {
|
||||
if (!currentEpisode) {
|
||||
currentEpisode = {
|
||||
id: getStableId(msg),
|
||||
timestamp: Date.now(),
|
||||
trigger: {
|
||||
id: getStableId(msg.parts![0] || msg),
|
||||
type: 'SYSTEM_EVENT',
|
||||
name: 'history_resume',
|
||||
payload: {},
|
||||
metadata: createMetadata([]),
|
||||
} as SystemEvent,
|
||||
steps: [],
|
||||
};
|
||||
}
|
||||
|
||||
for (const part of msg.parts!) {
|
||||
if (part.functionResponse) {
|
||||
const callId = part.functionResponse.id || '';
|
||||
const matchingCall = pendingCallParts.get(callId);
|
||||
|
||||
const intentTokens = matchingCall
|
||||
? estimateContextTokenCountSync([matchingCall])
|
||||
: 0;
|
||||
const obsTokens = estimateContextTokenCountSync([part]);
|
||||
|
||||
const step: ToolExecution = {
|
||||
id: getStableId(part),
|
||||
type: 'TOOL_EXECUTION',
|
||||
toolName: part.functionResponse.name || 'unknown',
|
||||
intent:
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(matchingCall?.functionCall?.args as unknown as Record<
|
||||
string,
|
||||
unknown
|
||||
>) || {},
|
||||
observation:
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(part.functionResponse.response as unknown as Record<
|
||||
string,
|
||||
unknown
|
||||
>) || {},
|
||||
tokens: {
|
||||
intent: intentTokens,
|
||||
observation: obsTokens,
|
||||
},
|
||||
metadata: {
|
||||
originalTokens: intentTokens + obsTokens,
|
||||
currentTokens: intentTokens + obsTokens,
|
||||
transformations: [],
|
||||
},
|
||||
};
|
||||
currentEpisode.steps!.push(step);
|
||||
if (callId) pendingCallParts.delete(callId);
|
||||
}
|
||||
}
|
||||
return currentEpisode;
|
||||
}
|
||||
|
||||
function parseUserParts(msg: Content): Partial<Episode> {
|
||||
const semanticParts: SemanticPart[] = [];
|
||||
for (const p of msg.parts!) {
|
||||
if (p.text !== undefined)
|
||||
semanticParts.push({ type: 'text', text: p.text });
|
||||
else if (p.inlineData)
|
||||
semanticParts.push({
|
||||
type: 'inline_data',
|
||||
mimeType: p.inlineData.mimeType || '',
|
||||
data: p.inlineData.data || '',
|
||||
});
|
||||
else if (p.fileData)
|
||||
semanticParts.push({
|
||||
type: 'file_data',
|
||||
mimeType: p.fileData.mimeType || '',
|
||||
fileUri: p.fileData.fileUri || '',
|
||||
});
|
||||
else if (!p.functionResponse)
|
||||
semanticParts.push({ type: 'raw_part', part: p }); // Preserve unknowns
|
||||
}
|
||||
|
||||
const trigger: UserPrompt = {
|
||||
id: getStableId(msg.parts![0] || msg),
|
||||
type: 'USER_PROMPT',
|
||||
semanticParts,
|
||||
metadata: createMetadata(
|
||||
msg.parts!.filter((p) => !p.functionResponse),
|
||||
),
|
||||
};
|
||||
|
||||
return {
|
||||
id: getStableId(msg),
|
||||
timestamp: Date.now(),
|
||||
trigger,
|
||||
steps: [],
|
||||
};
|
||||
}
|
||||
|
||||
function parseModelParts(
|
||||
msg: Content,
|
||||
currentEpisode: Partial<Episode> | null,
|
||||
pendingCallParts: Map<string, Part>,
|
||||
): Partial<Episode> {
|
||||
if (!currentEpisode) {
|
||||
currentEpisode = {
|
||||
id: getStableId(msg),
|
||||
timestamp: Date.now(),
|
||||
trigger: {
|
||||
id: getStableId(msg.parts![0] || msg),
|
||||
type: 'SYSTEM_EVENT',
|
||||
name: 'model_init',
|
||||
payload: {},
|
||||
metadata: createMetadata([]),
|
||||
} as SystemEvent,
|
||||
steps: [],
|
||||
};
|
||||
}
|
||||
|
||||
for (const part of msg.parts!) {
|
||||
if (part.functionCall) {
|
||||
const callId = part.functionCall.id || '';
|
||||
if (callId) pendingCallParts.set(callId, part);
|
||||
} else if (part.text) {
|
||||
const thought: AgentThought = {
|
||||
id: getStableId(part),
|
||||
type: 'AGENT_THOUGHT',
|
||||
text: part.text,
|
||||
metadata: createMetadata([part]),
|
||||
};
|
||||
currentEpisode.steps!.push(thought);
|
||||
}
|
||||
}
|
||||
return currentEpisode;
|
||||
}
|
||||
|
||||
function finalizeYield(currentEpisode: Partial<Episode>) {
|
||||
if (currentEpisode.steps && currentEpisode.steps.length > 0) {
|
||||
const lastStep = currentEpisode.steps[currentEpisode.steps.length - 1];
|
||||
if (lastStep.type === 'AGENT_THOUGHT') {
|
||||
const yieldNode: AgentYield = {
|
||||
id: lastStep.id,
|
||||
type: 'AGENT_YIELD',
|
||||
text: lastStep.text,
|
||||
metadata: lastStep.metadata,
|
||||
};
|
||||
currentEpisode.steps.pop();
|
||||
currentEpisode.yield = yieldNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user