mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
refactor(core): move session conversion logic to core (#19972)
This commit is contained in:
@@ -13,6 +13,7 @@ import type {
|
||||
import { isSlashCommand } from './ui/utils/commandUtils.js';
|
||||
import type { LoadedSettings } from './config/settings.js';
|
||||
import {
|
||||
convertSessionToClientHistory,
|
||||
GeminiEventType,
|
||||
FatalInputError,
|
||||
promptIdContext,
|
||||
@@ -35,7 +36,6 @@ import type { Content, Part } from '@google/genai';
|
||||
import readline from 'node:readline';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
|
||||
import { convertSessionToHistoryFormats } from './ui/hooks/useSessionBrowser.js';
|
||||
import { handleSlashCommand } from './nonInteractiveCliCommands.js';
|
||||
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
|
||||
import { handleAtCommand } from './ui/hooks/atCommandProcessor.js';
|
||||
@@ -220,9 +220,9 @@ export async function runNonInteractive({
|
||||
// Initialize chat. Resume if resume data is passed.
|
||||
if (resumedSessionData) {
|
||||
await geminiClient.resumeChat(
|
||||
convertSessionToHistoryFormats(
|
||||
convertSessionToClientHistory(
|
||||
resumedSessionData.conversation.messages,
|
||||
).clientHistory,
|
||||
),
|
||||
resumedSessionData,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
RewindEvent,
|
||||
type ChatRecordingService,
|
||||
type GeminiClient,
|
||||
convertSessionToClientHistory,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
/**
|
||||
@@ -54,9 +55,8 @@ async function rewindConversation(
|
||||
}
|
||||
|
||||
// Convert to UI and Client formats
|
||||
const { uiHistory, clientHistory } = convertSessionToHistoryFormats(
|
||||
conversation.messages,
|
||||
);
|
||||
const { uiHistory } = convertSessionToHistoryFormats(conversation.messages);
|
||||
const clientHistory = convertSessionToClientHistory(conversation.messages);
|
||||
|
||||
client.setHistory(clientHistory as Content[]);
|
||||
|
||||
|
||||
@@ -20,7 +20,10 @@ import {
|
||||
type MessageRecord,
|
||||
CoreToolCallStatus,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { coreEvents } from '@google/gemini-cli-core';
|
||||
import {
|
||||
coreEvents,
|
||||
convertSessionToClientHistory,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
// Mock modules
|
||||
vi.mock('fs/promises');
|
||||
@@ -157,7 +160,7 @@ describe('convertSessionToHistoryFormats', () => {
|
||||
it('should convert empty messages array', () => {
|
||||
const result = convertSessionToHistoryFormats([]);
|
||||
expect(result.uiHistory).toEqual([]);
|
||||
expect(result.clientHistory).toEqual([]);
|
||||
expect(convertSessionToClientHistory([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('should convert basic user and model messages', () => {
|
||||
@@ -175,12 +178,13 @@ describe('convertSessionToHistoryFormats', () => {
|
||||
text: 'Hi there',
|
||||
});
|
||||
|
||||
expect(result.clientHistory).toHaveLength(2);
|
||||
expect(result.clientHistory[0]).toEqual({
|
||||
const clientHistory = convertSessionToClientHistory(messages);
|
||||
expect(clientHistory).toHaveLength(2);
|
||||
expect(clientHistory[0]).toEqual({
|
||||
role: 'user',
|
||||
parts: [{ text: 'Hello' }],
|
||||
});
|
||||
expect(result.clientHistory[1]).toEqual({
|
||||
expect(clientHistory[1]).toEqual({
|
||||
role: 'model',
|
||||
parts: [{ text: 'Hi there' }],
|
||||
});
|
||||
@@ -203,8 +207,9 @@ describe('convertSessionToHistoryFormats', () => {
|
||||
text: 'User input',
|
||||
});
|
||||
|
||||
expect(result.clientHistory).toHaveLength(1);
|
||||
expect(result.clientHistory[0]).toEqual({
|
||||
const clientHistory = convertSessionToClientHistory(messages);
|
||||
expect(clientHistory).toHaveLength(1);
|
||||
expect(clientHistory[0]).toEqual({
|
||||
role: 'user',
|
||||
parts: [{ text: 'Expanded content' }],
|
||||
});
|
||||
@@ -225,7 +230,7 @@ describe('convertSessionToHistoryFormats', () => {
|
||||
text: 'Help text',
|
||||
});
|
||||
|
||||
expect(result.clientHistory).toHaveLength(0);
|
||||
expect(convertSessionToClientHistory(messages)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should handle tool calls and responses', () => {
|
||||
@@ -264,12 +269,13 @@ describe('convertSessionToHistoryFormats', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.clientHistory).toHaveLength(3); // User, Model (call), User (response)
|
||||
expect(result.clientHistory[0]).toEqual({
|
||||
const clientHistory = convertSessionToClientHistory(messages);
|
||||
expect(clientHistory).toHaveLength(3); // User, Model (call), User (response)
|
||||
expect(clientHistory[0]).toEqual({
|
||||
role: 'user',
|
||||
parts: [{ text: 'What time is it?' }],
|
||||
});
|
||||
expect(result.clientHistory[1]).toEqual({
|
||||
expect(clientHistory[1]).toEqual({
|
||||
role: 'model',
|
||||
parts: [
|
||||
{
|
||||
@@ -281,7 +287,7 @@ describe('convertSessionToHistoryFormats', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result.clientHistory[2]).toEqual({
|
||||
expect(clientHistory[2]).toEqual({
|
||||
role: 'user',
|
||||
parts: [
|
||||
{
|
||||
|
||||
@@ -13,7 +13,10 @@ import type {
|
||||
ConversationRecord,
|
||||
ResumedSessionData,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { coreEvents } from '@google/gemini-cli-core';
|
||||
import {
|
||||
coreEvents,
|
||||
convertSessionToClientHistory,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { SessionInfo } from '../../utils/sessionUtils.js';
|
||||
import { convertSessionToHistoryFormats } from '../../utils/sessionUtils.js';
|
||||
import type { Part } from '@google/genai';
|
||||
@@ -78,7 +81,7 @@ export const useSessionBrowser = (
|
||||
);
|
||||
await onLoadHistory(
|
||||
historyData.uiHistory,
|
||||
historyData.clientHistory,
|
||||
convertSessionToClientHistory(conversation.messages),
|
||||
resumedSessionData,
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
coreEvents,
|
||||
type Config,
|
||||
type ResumedSessionData,
|
||||
convertSessionToClientHistory,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Part } from '@google/genai';
|
||||
import type { HistoryItemWithoutId } from '../types.js';
|
||||
@@ -113,7 +114,7 @@ export function useSessionResume({
|
||||
);
|
||||
void loadHistoryForResume(
|
||||
historyData.uiHistory,
|
||||
historyData.clientHistory,
|
||||
convertSessionToClientHistory(resumedSessionData.conversation.messages),
|
||||
resumedSessionData,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
import * as fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { stripUnsafeCharacters } from '../ui/utils/textUtils.js';
|
||||
import type { Part } from '@google/genai';
|
||||
import { MessageType, type HistoryItemWithoutId } from '../ui/types.js';
|
||||
|
||||
/**
|
||||
@@ -526,13 +525,12 @@ export class SessionSelector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts session/conversation data into UI history and Gemini client history formats.
|
||||
* Converts session/conversation data into UI history format.
|
||||
*/
|
||||
export function convertSessionToHistoryFormats(
|
||||
messages: ConversationRecord['messages'],
|
||||
): {
|
||||
uiHistory: HistoryItemWithoutId[];
|
||||
clientHistory: Array<{ role: 'user' | 'model'; parts: Part[] }>;
|
||||
} {
|
||||
const uiHistory: HistoryItemWithoutId[] = [];
|
||||
|
||||
@@ -599,117 +597,7 @@ export function convertSessionToHistoryFormats(
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to Gemini client history format
|
||||
const clientHistory: Array<{ role: 'user' | 'model'; parts: Part[] }> = [];
|
||||
|
||||
for (const msg of messages) {
|
||||
// Skip system/error messages and user slash commands
|
||||
if (msg.type === 'info' || msg.type === 'error' || msg.type === 'warning') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msg.type === 'user') {
|
||||
// Skip user slash commands
|
||||
const contentString = partListUnionToString(msg.content);
|
||||
if (
|
||||
contentString.trim().startsWith('/') ||
|
||||
contentString.trim().startsWith('?')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add regular user message
|
||||
clientHistory.push({
|
||||
role: 'user',
|
||||
parts: Array.isArray(msg.content)
|
||||
? // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(msg.content as Part[])
|
||||
: [{ text: contentString }],
|
||||
});
|
||||
} else if (msg.type === 'gemini') {
|
||||
// Handle Gemini messages with potential tool calls
|
||||
const hasToolCalls = msg.toolCalls && msg.toolCalls.length > 0;
|
||||
|
||||
if (hasToolCalls) {
|
||||
// Create model message with function calls
|
||||
const modelParts: Part[] = [];
|
||||
|
||||
// Add text content if present
|
||||
const contentString = partListUnionToString(msg.content);
|
||||
if (msg.content && contentString.trim()) {
|
||||
modelParts.push({ text: contentString });
|
||||
}
|
||||
|
||||
// Add function calls
|
||||
for (const toolCall of msg.toolCalls!) {
|
||||
modelParts.push({
|
||||
functionCall: {
|
||||
name: toolCall.name,
|
||||
args: toolCall.args,
|
||||
...(toolCall.id && { id: toolCall.id }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
clientHistory.push({
|
||||
role: 'model',
|
||||
parts: modelParts,
|
||||
});
|
||||
|
||||
// Create single function response message with all tool call responses
|
||||
const functionResponseParts: Part[] = [];
|
||||
for (const toolCall of msg.toolCalls!) {
|
||||
if (toolCall.result) {
|
||||
// Convert PartListUnion result to function response format
|
||||
let responseData: Part;
|
||||
|
||||
if (typeof toolCall.result === 'string') {
|
||||
responseData = {
|
||||
functionResponse: {
|
||||
id: toolCall.id,
|
||||
name: toolCall.name,
|
||||
response: {
|
||||
output: toolCall.result,
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (Array.isArray(toolCall.result)) {
|
||||
// toolCall.result is an array containing properly formatted
|
||||
// function responses
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
functionResponseParts.push(...(toolCall.result as Part[]));
|
||||
continue;
|
||||
} else {
|
||||
// Fallback for non-array results
|
||||
responseData = toolCall.result;
|
||||
}
|
||||
|
||||
functionResponseParts.push(responseData);
|
||||
}
|
||||
}
|
||||
|
||||
// Only add user message if we have function responses
|
||||
if (functionResponseParts.length > 0) {
|
||||
clientHistory.push({
|
||||
role: 'user',
|
||||
parts: functionResponseParts,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Regular Gemini message without tool calls
|
||||
const contentString = partListUnionToString(msg.content);
|
||||
if (msg.content && contentString.trim()) {
|
||||
clientHistory.push({
|
||||
role: 'model',
|
||||
parts: [{ text: contentString }],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
uiHistory,
|
||||
clientHistory,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
SessionSelector,
|
||||
convertSessionToHistoryFormats,
|
||||
} from '../utils/sessionUtils.js';
|
||||
import { convertSessionToClientHistory } from '@google/gemini-cli-core';
|
||||
import type { LoadedSettings } from '../config/settings.js';
|
||||
|
||||
vi.mock('../config/config.js', () => ({
|
||||
@@ -42,6 +43,15 @@ vi.mock('../utils/sessionUtils.js', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
convertSessionToClientHistory: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('GeminiAgent Session Resume', () => {
|
||||
let mockConfig: Mocked<Config>;
|
||||
let mockSettings: Mocked<LoadedSettings>;
|
||||
@@ -142,9 +152,11 @@ describe('GeminiAgent Session Resume', () => {
|
||||
{ role: 'model', parts: [{ text: 'Hi there' }] },
|
||||
];
|
||||
(convertSessionToHistoryFormats as unknown as Mock).mockReturnValue({
|
||||
clientHistory: mockClientHistory,
|
||||
uiHistory: [],
|
||||
});
|
||||
(convertSessionToClientHistory as unknown as Mock).mockReturnValue(
|
||||
mockClientHistory,
|
||||
);
|
||||
|
||||
const response = await agent.loadSession({
|
||||
sessionId,
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
partListUnionToString,
|
||||
LlmRole,
|
||||
ApprovalMode,
|
||||
convertSessionToClientHistory,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as acp from '@agentclientprotocol/sdk';
|
||||
import { AcpFileSystemService } from './fileSystemService.js';
|
||||
@@ -53,10 +54,7 @@ import { randomUUID } from 'node:crypto';
|
||||
import type { CliArgs } from '../config/config.js';
|
||||
import { loadCliConfig } from '../config/config.js';
|
||||
import { runExitCleanup } from '../utils/cleanup.js';
|
||||
import {
|
||||
SessionSelector,
|
||||
convertSessionToHistoryFormats,
|
||||
} from '../utils/sessionUtils.js';
|
||||
import { SessionSelector } from '../utils/sessionUtils.js';
|
||||
|
||||
export async function runZedIntegration(
|
||||
config: Config,
|
||||
@@ -258,9 +256,7 @@ export class GeminiAgent {
|
||||
config.setFileSystemService(acpFileSystemService);
|
||||
}
|
||||
|
||||
const { clientHistory } = convertSessionToHistoryFormats(
|
||||
sessionData.messages,
|
||||
);
|
||||
const clientHistory = convertSessionToClientHistory(sessionData.messages);
|
||||
|
||||
const geminiClient = config.getGeminiClient();
|
||||
await geminiClient.initialize();
|
||||
|
||||
@@ -107,6 +107,7 @@ export * from './utils/secure-browser-launcher.js';
|
||||
export * from './utils/apiConversionUtils.js';
|
||||
export * from './utils/channel.js';
|
||||
export * from './utils/constants.js';
|
||||
export * from './utils/sessionUtils.js';
|
||||
|
||||
// Export services
|
||||
export * from './services/fileDiscoveryService.js';
|
||||
|
||||
148
packages/core/src/utils/sessionUtils.test.ts
Normal file
148
packages/core/src/utils/sessionUtils.test.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { convertSessionToClientHistory } from './sessionUtils.js';
|
||||
import { type ConversationRecord } from '../services/chatRecordingService.js';
|
||||
import { CoreToolCallStatus } from '../scheduler/types.js';
|
||||
|
||||
describe('convertSessionToClientHistory', () => {
|
||||
it('should convert a simple conversation without tool calls', () => {
|
||||
const messages: ConversationRecord['messages'] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'user',
|
||||
timestamp: '2024-01-01T10:00:00Z',
|
||||
content: 'Hello',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'gemini',
|
||||
timestamp: '2024-01-01T10:01:00Z',
|
||||
content: 'Hi there',
|
||||
},
|
||||
];
|
||||
|
||||
const history = convertSessionToClientHistory(messages);
|
||||
|
||||
expect(history).toEqual([
|
||||
{ role: 'user', parts: [{ text: 'Hello' }] },
|
||||
{ role: 'model', parts: [{ text: 'Hi there' }] },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore info, error, and slash commands', () => {
|
||||
const messages: ConversationRecord['messages'] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'info',
|
||||
timestamp: '2024-01-01T10:00:00Z',
|
||||
content: 'System info',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'user',
|
||||
timestamp: '2024-01-01T10:01:00Z',
|
||||
content: '/clear',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'user',
|
||||
timestamp: '2024-01-01T10:02:00Z',
|
||||
content: '?help',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'user',
|
||||
timestamp: '2024-01-01T10:03:00Z',
|
||||
content: 'Actual query',
|
||||
},
|
||||
];
|
||||
|
||||
const history = convertSessionToClientHistory(messages);
|
||||
|
||||
expect(history).toEqual([
|
||||
{ role: 'user', parts: [{ text: 'Actual query' }] },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should correctly map tool calls and their responses', () => {
|
||||
const messages: ConversationRecord['messages'] = [
|
||||
{
|
||||
id: 'msg1',
|
||||
type: 'user',
|
||||
timestamp: '2024-01-01T10:00:00Z',
|
||||
content: 'List files',
|
||||
},
|
||||
{
|
||||
id: 'msg2',
|
||||
type: 'gemini',
|
||||
timestamp: '2024-01-01T10:01:00Z',
|
||||
content: 'Let me check.',
|
||||
toolCalls: [
|
||||
{
|
||||
id: 'call123',
|
||||
name: 'ls',
|
||||
args: { dir: '.' },
|
||||
status: CoreToolCallStatus.Success,
|
||||
timestamp: '2024-01-01T10:01:05Z',
|
||||
result: 'file.txt',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const history = convertSessionToClientHistory(messages);
|
||||
|
||||
expect(history).toEqual([
|
||||
{ role: 'user', parts: [{ text: 'List files' }] },
|
||||
{
|
||||
role: 'model',
|
||||
parts: [
|
||||
{ text: 'Let me check.' },
|
||||
{ functionCall: { name: 'ls', args: { dir: '.' }, id: 'call123' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
parts: [
|
||||
{
|
||||
functionResponse: {
|
||||
id: 'call123',
|
||||
name: 'ls',
|
||||
response: { output: 'file.txt' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should preserve multi-modal parts (inlineData)', () => {
|
||||
const messages: ConversationRecord['messages'] = [
|
||||
{
|
||||
id: 'msg1',
|
||||
type: 'user',
|
||||
timestamp: '2024-01-01T10:00:00Z',
|
||||
content: [
|
||||
{ text: 'Look at this image' },
|
||||
{ inlineData: { mimeType: 'image/png', data: 'base64data' } },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const history = convertSessionToClientHistory(messages);
|
||||
|
||||
expect(history).toEqual([
|
||||
{
|
||||
role: 'user',
|
||||
parts: [
|
||||
{ text: 'Look at this image' },
|
||||
{ inlineData: { mimeType: 'image/png', data: 'base64data' } },
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
131
packages/core/src/utils/sessionUtils.ts
Normal file
131
packages/core/src/utils/sessionUtils.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { type Part, type PartListUnion } from '@google/genai';
|
||||
import { type ConversationRecord } from '../services/chatRecordingService.js';
|
||||
import { partListUnionToString } from '../core/geminiRequest.js';
|
||||
|
||||
/**
|
||||
* Converts a PartListUnion into a normalized array of Part objects.
|
||||
* This handles converting raw strings into { text: string } parts.
|
||||
*/
|
||||
function ensurePartArray(content: PartListUnion): Part[] {
|
||||
if (Array.isArray(content)) {
|
||||
return content.map((part) =>
|
||||
typeof part === 'string' ? { text: part } : part,
|
||||
);
|
||||
}
|
||||
if (typeof content === 'string') {
|
||||
return [{ text: content }];
|
||||
}
|
||||
return [content];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts session/conversation data into Gemini client history formats.
|
||||
*/
|
||||
export function convertSessionToClientHistory(
|
||||
messages: ConversationRecord['messages'],
|
||||
): Array<{ role: 'user' | 'model'; parts: Part[] }> {
|
||||
const clientHistory: Array<{ role: 'user' | 'model'; parts: Part[] }> = [];
|
||||
|
||||
for (const msg of messages) {
|
||||
if (msg.type === 'info' || msg.type === 'error' || msg.type === 'warning') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msg.type === 'user') {
|
||||
const contentString = partListUnionToString(msg.content);
|
||||
if (
|
||||
contentString.trim().startsWith('/') ||
|
||||
contentString.trim().startsWith('?')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
clientHistory.push({
|
||||
role: 'user',
|
||||
parts: ensurePartArray(msg.content),
|
||||
});
|
||||
} else if (msg.type === 'gemini') {
|
||||
const hasToolCalls = msg.toolCalls && msg.toolCalls.length > 0;
|
||||
|
||||
if (hasToolCalls) {
|
||||
const modelParts: Part[] = [];
|
||||
|
||||
// TODO: Revisit if we should preserve more than just Part metadata (e.g. thoughtSignatures)
|
||||
// currently those are only required within an active loop turn which resume clears
|
||||
// by forcing a new user text prompt.
|
||||
|
||||
// Preserve original parts to maintain multimodal integrity
|
||||
if (msg.content) {
|
||||
modelParts.push(...ensurePartArray(msg.content));
|
||||
}
|
||||
|
||||
for (const toolCall of msg.toolCalls!) {
|
||||
modelParts.push({
|
||||
functionCall: {
|
||||
name: toolCall.name,
|
||||
args: toolCall.args,
|
||||
...(toolCall.id && { id: toolCall.id }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
clientHistory.push({
|
||||
role: 'model',
|
||||
parts: modelParts,
|
||||
});
|
||||
|
||||
const functionResponseParts: Part[] = [];
|
||||
for (const toolCall of msg.toolCalls!) {
|
||||
if (toolCall.result) {
|
||||
let responseData: Part;
|
||||
|
||||
if (typeof toolCall.result === 'string') {
|
||||
responseData = {
|
||||
functionResponse: {
|
||||
id: toolCall.id,
|
||||
name: toolCall.name,
|
||||
response: {
|
||||
output: toolCall.result,
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (Array.isArray(toolCall.result)) {
|
||||
functionResponseParts.push(...ensurePartArray(toolCall.result));
|
||||
continue;
|
||||
} else {
|
||||
responseData = toolCall.result;
|
||||
}
|
||||
|
||||
functionResponseParts.push(responseData);
|
||||
}
|
||||
}
|
||||
|
||||
if (functionResponseParts.length > 0) {
|
||||
clientHistory.push({
|
||||
role: 'user',
|
||||
parts: functionResponseParts,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (msg.content) {
|
||||
const parts = ensurePartArray(msg.content);
|
||||
|
||||
if (parts.length > 0) {
|
||||
clientHistory.push({
|
||||
role: 'model',
|
||||
parts,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clientHistory;
|
||||
}
|
||||
Reference in New Issue
Block a user