@google/gemini-cli-core 0.56.0 → 0.57.0-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/changelogs/latest.md +224 -3
- package/dist/docs/get-started/authentication.mdx +1 -1
- package/dist/docs/reference/commands.md +7 -5
- package/dist/src/agents/agentLoader.d.ts +18 -18
- package/dist/src/agents/cli-help-agent.js +13 -1
- package/dist/src/agents/cli-help-agent.js.map +1 -1
- package/dist/src/agents/cli-help-agent.test.js +4 -4
- package/dist/src/agents/cli-help-agent.test.js.map +1 -1
- package/dist/src/agents/registry.js +4 -1
- package/dist/src/agents/registry.js.map +1 -1
- package/dist/src/agents/registry.test.js +8 -0
- package/dist/src/agents/registry.test.js.map +1 -1
- package/dist/src/availability/modelAvailabilityService.d.ts +2 -1
- package/dist/src/availability/modelAvailabilityService.js +20 -5
- package/dist/src/availability/modelAvailabilityService.js.map +1 -1
- package/dist/src/availability/modelAvailabilityService.test.js +50 -1
- package/dist/src/availability/modelAvailabilityService.test.js.map +1 -1
- package/dist/src/config/config.js +5 -2
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/context/contextManager.js +5 -6
- package/dist/src/context/contextManager.js.map +1 -1
- package/dist/src/context/contextManager.test.js +17 -0
- package/dist/src/context/contextManager.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +3 -0
- package/dist/src/core/geminiChat.js +65 -12
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +233 -22
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/ide/ide-connection-utils.js +47 -10
- package/dist/src/ide/ide-connection-utils.js.map +1 -1
- package/dist/src/ide/ide-connection-utils.test.js +85 -0
- package/dist/src/ide/ide-connection-utils.test.js.map +1 -1
- package/dist/src/ide/process-utils.js +5 -2
- package/dist/src/ide/process-utils.js.map +1 -1
- package/dist/src/ide/process-utils.test.js +13 -1
- package/dist/src/ide/process-utils.test.js.map +1 -1
- package/dist/src/mcp/oauth-provider.js +2 -2
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-provider.test.js +70 -0
- package/dist/src/mcp/oauth-provider.test.js.map +1 -1
- package/dist/src/policy/policy-engine.d.ts +2 -0
- package/dist/src/policy/policy-engine.js +56 -0
- package/dist/src/policy/policy-engine.js.map +1 -1
- package/dist/src/policy/policy-engine.test.js +79 -0
- package/dist/src/policy/policy-engine.test.js.map +1 -1
- package/dist/src/policy/types.d.ts +4 -0
- package/dist/src/policy/types.js.map +1 -1
- package/dist/src/services/gitService.js +4 -3
- package/dist/src/services/gitService.js.map +1 -1
- package/dist/src/services/gitService.test.js +16 -9
- package/dist/src/services/gitService.test.js.map +1 -1
- package/dist/src/services/shellExecutionService.d.ts +1 -1
- package/dist/src/services/shellExecutionService.js +40 -27
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +29 -9
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/services/worktreeService.js +9 -0
- package/dist/src/services/worktreeService.js.map +1 -1
- package/dist/src/services/worktreeService.test.js +8 -6
- package/dist/src/services/worktreeService.test.js.map +1 -1
- package/dist/src/skills/builtin/antigravity-support/SKILL.md +55 -18
- package/dist/src/skills/builtin/skill-creator/SKILL.md +221 -84
- package/dist/src/tools/grep.js +2 -1
- package/dist/src/tools/grep.js.map +1 -1
- package/dist/src/utils/gitUtils.d.ts +1 -0
- package/dist/src/utils/gitUtils.js +34 -0
- package/dist/src/utils/gitUtils.js.map +1 -1
- package/dist/src/utils/oauth-flow.d.ts +6 -0
- package/dist/src/utils/oauth-flow.js +33 -2
- package/dist/src/utils/oauth-flow.js.map +1 -1
- package/dist/src/utils/oauth-flow.test.js +81 -0
- package/dist/src/utils/oauth-flow.test.js.map +1 -1
- package/dist/src/utils/retry.js +55 -14
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/retry.test.js +88 -0
- package/dist/src/utils/retry.test.js.map +1 -1
- package/dist/src/utils/trust.js +3 -0
- package/dist/src/utils/trust.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
import { ApiError, ThinkingLevel, } from '@google/genai';
|
|
7
|
+
import { ApiError, ThinkingLevel, Language, } from '@google/genai';
|
|
8
8
|
import { GeminiChat, InvalidStreamError, StreamEventType, SYNTHETIC_THOUGHT_SIGNATURE, stripToolCallIdPrefixes, coalesceConsecutiveRoles, stripThoughts, } from './geminiChat.js';
|
|
9
9
|
import { CoreToolCallStatus, } from '../scheduler/types.js';
|
|
10
10
|
import { MockTool } from '../test-utils/mock-tool.js';
|
|
@@ -603,7 +603,7 @@ describe('GeminiChat', () => {
|
|
|
603
603
|
expect(modelTurn?.parts?.length).toBe(1);
|
|
604
604
|
expect(modelTurn?.parts[0].text).toBe('This is the visible text that should not be lost.');
|
|
605
605
|
});
|
|
606
|
-
it('should
|
|
606
|
+
it('should succeed when a tool call is followed by an empty stream response', async () => {
|
|
607
607
|
// 1. Setup: A history where the model has just made a function call.
|
|
608
608
|
const initialHistory = [
|
|
609
609
|
{
|
|
@@ -648,12 +648,16 @@ describe('GeminiChat', () => {
|
|
|
648
648
|
response: { name: 'Vesuvio' },
|
|
649
649
|
},
|
|
650
650
|
}, 'prompt-id-stream-1', new AbortController().signal, LlmRole.MAIN);
|
|
651
|
-
// 4. Assert: The stream processing should
|
|
651
|
+
// 4. Assert: The stream processing should succeed.
|
|
652
652
|
await expect((async () => {
|
|
653
653
|
for await (const _ of stream) {
|
|
654
654
|
// This loop consumes the stream to trigger the internal logic.
|
|
655
655
|
}
|
|
656
|
-
})()).
|
|
656
|
+
})()).resolves.not.toThrow();
|
|
657
|
+
// Verify history now ends with a successful model turn (with empty parts)
|
|
658
|
+
const lastTurn = chat.agentHistory.get()[chat.agentHistory.length - 1];
|
|
659
|
+
expect(lastTurn.content.role).toBe('model');
|
|
660
|
+
expect(lastTurn.content.parts).toEqual([]);
|
|
657
661
|
});
|
|
658
662
|
it('should succeed when there is a tool call without finish reason', async () => {
|
|
659
663
|
// Setup: Stream with tool call but no finish reason
|
|
@@ -715,7 +719,7 @@ describe('GeminiChat', () => {
|
|
|
715
719
|
// Verify history has been rolled back to its initial state
|
|
716
720
|
expect(chat.agentHistory.length).toBe(initialHistoryLength);
|
|
717
721
|
});
|
|
718
|
-
it('should preserve function responses
|
|
722
|
+
it('should preserve function responses and matching model turn when empty stream response is received', async () => {
|
|
719
723
|
// 1. Setup history ending with a model turn containing functionCall
|
|
720
724
|
chat.agentHistory.push({
|
|
721
725
|
id: 'model-turn-1',
|
|
@@ -732,7 +736,7 @@ describe('GeminiChat', () => {
|
|
|
732
736
|
},
|
|
733
737
|
});
|
|
734
738
|
const initialHistoryLength = chat.agentHistory.length;
|
|
735
|
-
// Setup: Stream
|
|
739
|
+
// Setup: Stream with empty parts
|
|
736
740
|
const streamWithNoResponseText = (async function* () {
|
|
737
741
|
yield {
|
|
738
742
|
candidates: [
|
|
@@ -758,11 +762,13 @@ describe('GeminiChat', () => {
|
|
|
758
762
|
for await (const _ of stream) {
|
|
759
763
|
// consume
|
|
760
764
|
}
|
|
761
|
-
})()).
|
|
762
|
-
// Verify that history
|
|
763
|
-
expect(chat.agentHistory.length).toBe(initialHistoryLength +
|
|
764
|
-
const
|
|
765
|
-
expect(
|
|
765
|
+
})()).resolves.not.toThrow();
|
|
766
|
+
// Verify that history contains both the function response and the matched empty model response turn
|
|
767
|
+
expect(chat.agentHistory.length).toBe(initialHistoryLength + 2);
|
|
768
|
+
const turns = chat.agentHistory.get();
|
|
769
|
+
expect(turns[turns.length - 2].content.parts?.[0]?.functionResponse).toBeDefined();
|
|
770
|
+
expect(turns[turns.length - 1].content.role).toBe('model');
|
|
771
|
+
expect(turns[turns.length - 1].content.parts).toEqual([]);
|
|
766
772
|
});
|
|
767
773
|
it('should not fuse the next user message into a preserved tool-response turn', async () => {
|
|
768
774
|
// Regression: when a stream fails mid tool-loop the tool response is
|
|
@@ -778,7 +784,7 @@ describe('GeminiChat', () => {
|
|
|
778
784
|
parts: [{ functionCall: { name: 'test_tool', args: {} } }],
|
|
779
785
|
},
|
|
780
786
|
});
|
|
781
|
-
// 1. Tool response goes back, model returns nothing ->
|
|
787
|
+
// 1. Tool response goes back, model returns nothing -> succeeds under Option 2.
|
|
782
788
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue((async function* () {
|
|
783
789
|
yield {
|
|
784
790
|
candidates: [
|
|
@@ -786,7 +792,7 @@ describe('GeminiChat', () => {
|
|
|
786
792
|
],
|
|
787
793
|
};
|
|
788
794
|
})());
|
|
789
|
-
const
|
|
795
|
+
const successfulStream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, [
|
|
790
796
|
{
|
|
791
797
|
functionResponse: {
|
|
792
798
|
name: 'test_tool',
|
|
@@ -795,10 +801,10 @@ describe('GeminiChat', () => {
|
|
|
795
801
|
},
|
|
796
802
|
], 'prompt-id-fusion-setup', new AbortController().signal, LlmRole.MAIN);
|
|
797
803
|
await expect((async () => {
|
|
798
|
-
for await (const _ of
|
|
804
|
+
for await (const _ of successfulStream) {
|
|
799
805
|
// consume
|
|
800
806
|
}
|
|
801
|
-
})()).
|
|
807
|
+
})()).resolves.not.toThrow();
|
|
802
808
|
// 2. The user types a brand new instruction.
|
|
803
809
|
let capturedContents = [];
|
|
804
810
|
vi.mocked(mockContentGenerator.generateContentStream).mockImplementation(async (req) => {
|
|
@@ -937,7 +943,7 @@ describe('GeminiChat', () => {
|
|
|
937
943
|
},
|
|
938
944
|
});
|
|
939
945
|
const initialHistoryLength = chat.agentHistory.length;
|
|
940
|
-
// Setup: Stream
|
|
946
|
+
// Setup: Stream with empty parts
|
|
941
947
|
const streamWithNoResponseText = (async function* () {
|
|
942
948
|
yield {
|
|
943
949
|
candidates: [
|
|
@@ -969,12 +975,14 @@ describe('GeminiChat', () => {
|
|
|
969
975
|
for await (const _ of stream) {
|
|
970
976
|
// consume
|
|
971
977
|
}
|
|
972
|
-
})()).
|
|
973
|
-
// Verify that history
|
|
974
|
-
expect(chat.agentHistory.length).toBe(initialHistoryLength +
|
|
975
|
-
const
|
|
976
|
-
expect(
|
|
977
|
-
expect(
|
|
978
|
+
})()).resolves.not.toThrow();
|
|
979
|
+
// Verify that history contains both the function response and the matched empty model response turn
|
|
980
|
+
expect(chat.agentHistory.length).toBe(initialHistoryLength + 2);
|
|
981
|
+
const turns = chat.agentHistory.get();
|
|
982
|
+
expect(turns[turns.length - 2].content.parts?.[0]?.functionResponse).toBeDefined();
|
|
983
|
+
expect(turns[turns.length - 2].content.parts?.[1]?.fileData).toBeDefined();
|
|
984
|
+
expect(turns[turns.length - 1].content.role).toBe('model');
|
|
985
|
+
expect(turns[turns.length - 1].content.parts).toEqual([]);
|
|
978
986
|
});
|
|
979
987
|
it('should restore the lastPromptTokenCount baseline on history rollback when InvalidStreamError is thrown', async () => {
|
|
980
988
|
// Establish an initial token count baseline
|
|
@@ -1201,6 +1209,101 @@ describe('GeminiChat', () => {
|
|
|
1201
1209
|
// Verify history has been rolled back to its initial state
|
|
1202
1210
|
expect(chat.agentHistory.length).toBe(initialHistoryLength);
|
|
1203
1211
|
});
|
|
1212
|
+
it('should roll back the un-responded user turn from history when stream consumption is broken out of early', async () => {
|
|
1213
|
+
const initialHistoryLength = chat.agentHistory.length;
|
|
1214
|
+
// Setup: A multi-chunk stream that would succeed if fully consumed
|
|
1215
|
+
const activeStream = (async function* () {
|
|
1216
|
+
yield {
|
|
1217
|
+
candidates: [
|
|
1218
|
+
{
|
|
1219
|
+
content: {
|
|
1220
|
+
role: 'model',
|
|
1221
|
+
parts: [{ text: 'chunk 1' }],
|
|
1222
|
+
},
|
|
1223
|
+
},
|
|
1224
|
+
],
|
|
1225
|
+
};
|
|
1226
|
+
yield {
|
|
1227
|
+
candidates: [
|
|
1228
|
+
{
|
|
1229
|
+
content: {
|
|
1230
|
+
role: 'model',
|
|
1231
|
+
parts: [{ text: 'chunk 2' }],
|
|
1232
|
+
},
|
|
1233
|
+
finishReason: 'STOP',
|
|
1234
|
+
},
|
|
1235
|
+
],
|
|
1236
|
+
};
|
|
1237
|
+
})();
|
|
1238
|
+
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(activeStream);
|
|
1239
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test early exit message', 'prompt-id-early-exit', new AbortController().signal, LlmRole.MAIN);
|
|
1240
|
+
// Verify the user turn WAS added during sendMessageStream
|
|
1241
|
+
expect(chat.agentHistory.length).toBe(initialHistoryLength + 1);
|
|
1242
|
+
// Consume only the first chunk and break out of the loop early!
|
|
1243
|
+
for await (const chunk of stream) {
|
|
1244
|
+
expect(chunk.type).toBe(StreamEventType.CHUNK);
|
|
1245
|
+
break; // Trigger early exit, calling generator.return() under the hood
|
|
1246
|
+
}
|
|
1247
|
+
// Verify history has been rolled back to its initial state because of the early exit
|
|
1248
|
+
expect(chat.agentHistory.length).toBe(initialHistoryLength);
|
|
1249
|
+
});
|
|
1250
|
+
it('should roll back the entire multi-turn request including function responses when a continuation stream is aborted/cancelled', async () => {
|
|
1251
|
+
const initialHistoryLength = chat.agentHistory.length;
|
|
1252
|
+
const abortController = new AbortController();
|
|
1253
|
+
// 1. Send the first message of the prompt. This will succeed and register prompt-id-multi-turn-abort.
|
|
1254
|
+
const streamFirst = (async function* () {
|
|
1255
|
+
yield {
|
|
1256
|
+
candidates: [
|
|
1257
|
+
{
|
|
1258
|
+
content: {
|
|
1259
|
+
role: 'model',
|
|
1260
|
+
parts: [{ text: 'model first response' }],
|
|
1261
|
+
},
|
|
1262
|
+
finishReason: 'STOP',
|
|
1263
|
+
},
|
|
1264
|
+
],
|
|
1265
|
+
};
|
|
1266
|
+
})();
|
|
1267
|
+
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamFirst);
|
|
1268
|
+
const s1 = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'user original prompt', 'prompt-id-multi-turn-abort', new AbortController().signal, LlmRole.MAIN);
|
|
1269
|
+
for await (const _ of s1) {
|
|
1270
|
+
// consume the stream
|
|
1271
|
+
}
|
|
1272
|
+
// Expect history to contain: user, model
|
|
1273
|
+
expect(chat.agentHistory.length).toBe(initialHistoryLength + 2);
|
|
1274
|
+
// 2. Send a continuation (functionResponse), which is cancelled mid-stream.
|
|
1275
|
+
const streamSecond = (async function* () {
|
|
1276
|
+
yield {
|
|
1277
|
+
candidates: [
|
|
1278
|
+
{
|
|
1279
|
+
content: {
|
|
1280
|
+
role: 'model',
|
|
1281
|
+
parts: [{ text: 'partial model response before abort' }],
|
|
1282
|
+
},
|
|
1283
|
+
},
|
|
1284
|
+
],
|
|
1285
|
+
};
|
|
1286
|
+
abortController.abort();
|
|
1287
|
+
throw new Error('User aborted a continuation stream.');
|
|
1288
|
+
})();
|
|
1289
|
+
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamSecond);
|
|
1290
|
+
const s2 = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, [
|
|
1291
|
+
{
|
|
1292
|
+
functionResponse: {
|
|
1293
|
+
id: 'call_id_1',
|
|
1294
|
+
name: 'my_tool',
|
|
1295
|
+
response: { result: 'success' },
|
|
1296
|
+
},
|
|
1297
|
+
},
|
|
1298
|
+
], 'prompt-id-multi-turn-abort', abortController.signal, LlmRole.MAIN);
|
|
1299
|
+
await expect((async () => {
|
|
1300
|
+
for await (const _ of s2) {
|
|
1301
|
+
// consume the stream to trigger abort
|
|
1302
|
+
}
|
|
1303
|
+
})()).rejects.toThrow();
|
|
1304
|
+
// Verify history has been rolled back entirely to initialHistoryLength (before the original prompt started)!
|
|
1305
|
+
expect(chat.agentHistory.length).toBe(initialHistoryLength);
|
|
1306
|
+
});
|
|
1204
1307
|
it('should roll back the un-responded user turn from history when an ApiError is thrown', async () => {
|
|
1205
1308
|
const initialHistoryLength = chat.agentHistory.length;
|
|
1206
1309
|
// Setup: Stream that throws a standard API error
|
|
@@ -1646,6 +1749,43 @@ describe('GeminiChat', () => {
|
|
|
1646
1749
|
});
|
|
1647
1750
|
expect(geminiWrite).toBeDefined();
|
|
1648
1751
|
});
|
|
1752
|
+
it('should accept a completely empty response without throwing NO_RESPONSE_TEXT if the input is a tool response (isOriginalFunctionResponse is true)', async () => {
|
|
1753
|
+
// Mock an empty stream response with no text/thoughts
|
|
1754
|
+
const responseStream = (async function* () {
|
|
1755
|
+
yield {
|
|
1756
|
+
candidates: [
|
|
1757
|
+
{
|
|
1758
|
+
content: {
|
|
1759
|
+
role: 'model',
|
|
1760
|
+
parts: [],
|
|
1761
|
+
},
|
|
1762
|
+
finishReason: 'STOP',
|
|
1763
|
+
},
|
|
1764
|
+
],
|
|
1765
|
+
};
|
|
1766
|
+
})();
|
|
1767
|
+
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(responseStream);
|
|
1768
|
+
// We send a functionResponse message so that isOriginalFunctionResponse is true
|
|
1769
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, [
|
|
1770
|
+
{
|
|
1771
|
+
functionResponse: {
|
|
1772
|
+
name: 'edit_file',
|
|
1773
|
+
response: { success: true },
|
|
1774
|
+
},
|
|
1775
|
+
},
|
|
1776
|
+
], 'prompt-id-empty-tool-response', new AbortController().signal, LlmRole.MAIN);
|
|
1777
|
+
let error = undefined;
|
|
1778
|
+
try {
|
|
1779
|
+
for await (const _ of stream) {
|
|
1780
|
+
// consume
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
catch (err) {
|
|
1784
|
+
error = err;
|
|
1785
|
+
}
|
|
1786
|
+
// It should NOT throw InvalidStreamError NO_RESPONSE_TEXT
|
|
1787
|
+
expect(error).toBeUndefined();
|
|
1788
|
+
});
|
|
1649
1789
|
});
|
|
1650
1790
|
describe('addHistory', () => {
|
|
1651
1791
|
it('should add a new content item to the history', () => {
|
|
@@ -3346,5 +3486,76 @@ describe('GeminiChat', () => {
|
|
|
3346
3486
|
expect(stripped[0]).toHaveProperty('metadata', { some: 'value' });
|
|
3347
3487
|
});
|
|
3348
3488
|
});
|
|
3489
|
+
describe('isValidContent via curated history', () => {
|
|
3490
|
+
it('should strip turns with truly empty text parts and no other keys', () => {
|
|
3491
|
+
const chat = new GeminiChat(mockConfig, '', [], []);
|
|
3492
|
+
chat.setHistory([
|
|
3493
|
+
{ role: 'user', parts: [{ text: 'User prompt' }] },
|
|
3494
|
+
{ role: 'model', parts: [{ text: '' }] },
|
|
3495
|
+
]);
|
|
3496
|
+
// Curated history should strip the invalid model turn
|
|
3497
|
+
const history = chat.getHistory(true);
|
|
3498
|
+
expect(history).toHaveLength(1);
|
|
3499
|
+
expect(history[0].role).toBe('user');
|
|
3500
|
+
});
|
|
3501
|
+
it('should preserve turns with empty text parts that contain functionCall', () => {
|
|
3502
|
+
const chat = new GeminiChat(mockConfig, '', [], []);
|
|
3503
|
+
chat.setHistory([
|
|
3504
|
+
{ role: 'user', parts: [{ text: 'User prompt' }] },
|
|
3505
|
+
{
|
|
3506
|
+
role: 'model',
|
|
3507
|
+
parts: [
|
|
3508
|
+
{
|
|
3509
|
+
text: '',
|
|
3510
|
+
functionCall: { name: 'test_tool', args: {} },
|
|
3511
|
+
},
|
|
3512
|
+
],
|
|
3513
|
+
},
|
|
3514
|
+
]);
|
|
3515
|
+
// Curated history should preserve the valid model turn because of functionCall
|
|
3516
|
+
const history = chat.getHistory(true);
|
|
3517
|
+
expect(history).toHaveLength(2);
|
|
3518
|
+
expect(history[1].role).toBe('model');
|
|
3519
|
+
});
|
|
3520
|
+
it('should preserve turns with empty text parts that contain functionResponse, inlineData, or fileData', () => {
|
|
3521
|
+
const chat = new GeminiChat(mockConfig, '', [], []);
|
|
3522
|
+
chat.setHistory([
|
|
3523
|
+
{ role: 'user', parts: [{ text: 'User prompt' }] },
|
|
3524
|
+
{
|
|
3525
|
+
role: 'model',
|
|
3526
|
+
parts: [
|
|
3527
|
+
{
|
|
3528
|
+
text: '',
|
|
3529
|
+
functionResponse: { name: 'test_tool', response: {} },
|
|
3530
|
+
},
|
|
3531
|
+
],
|
|
3532
|
+
},
|
|
3533
|
+
]);
|
|
3534
|
+
const history = chat.getHistory(true);
|
|
3535
|
+
expect(history).toHaveLength(2);
|
|
3536
|
+
expect(history[1].role).toBe('model');
|
|
3537
|
+
});
|
|
3538
|
+
it('should preserve turns with empty text parts that contain executableCode or codeExecutionResult', () => {
|
|
3539
|
+
const chat = new GeminiChat(mockConfig, '', [], []);
|
|
3540
|
+
chat.setHistory([
|
|
3541
|
+
{ role: 'user', parts: [{ text: 'User prompt' }] },
|
|
3542
|
+
{
|
|
3543
|
+
role: 'model',
|
|
3544
|
+
parts: [
|
|
3545
|
+
{
|
|
3546
|
+
text: '',
|
|
3547
|
+
executableCode: {
|
|
3548
|
+
language: Language.PYTHON,
|
|
3549
|
+
code: 'print("test")',
|
|
3550
|
+
},
|
|
3551
|
+
},
|
|
3552
|
+
],
|
|
3553
|
+
},
|
|
3554
|
+
]);
|
|
3555
|
+
const history = chat.getHistory(true);
|
|
3556
|
+
expect(history).toHaveLength(2);
|
|
3557
|
+
expect(history[1].role).toBe('model');
|
|
3558
|
+
});
|
|
3559
|
+
});
|
|
3349
3560
|
});
|
|
3350
3561
|
//# sourceMappingURL=geminiChat.test.js.map
|