Skip to content

Commit db2c5a5

Browse files
Merge main into release
2 parents 89051ca + 2306920 commit db2c5a5

File tree

19 files changed

+764
-13
lines changed

19 files changed

+764
-13
lines changed

‎.changeset/many-mails-marry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/storage': patch
3+
---
4+
5+
Fixed issue where Firebase Studio wasn't populating cookies for Storage users

‎.changeset/wicked-scissors-yell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@firebase/auth": patch
3+
---
4+
5+
Fixed issue where Firebase Auth cookie refresh attempts issues in Firebase Studio resulted in CORS errors.

‎.github/ISSUE_TEMPLATE/bug_report_v2.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ body:
5858
description: Select the Firebase product(s) relevant to your issue. You can select multiple options in the dropdown.
5959
multiple: true
6060
options:
61+
- AI
6162
- Analytics
6263
- AppCheck
6364
- Auth
@@ -72,7 +73,6 @@ body:
7273
- Performance
7374
- Remote-Config
7475
- Storage
75-
- VertexAI
7676
validations:
7777
required: true
7878
- type: textarea

‎.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,4 @@ vertexai-sdk-test-data
103103
mocks-lookup.ts
104104

105105
# temp changeset output
106-
changeset-temp.json
106+
changeset-temp.json

‎.vscode/launch.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"type": "node",
1010
"request": "launch",
1111
"program": "${workspaceFolder}/node_modules/.bin/_mocha",
12-
"cwd": "${workspaceRoot}/packages/vertexai",
12+
"cwd": "${workspaceRoot}/packages/ai",
1313
"args": [
1414
"--require",
1515
"ts-node/register",
@@ -24,6 +24,26 @@
2424
},
2525
"sourceMaps": true
2626
},
27+
{
28+
"name": "AI Integration Tests (node)",
29+
"type": "node",
30+
"request": "launch",
31+
"program": "${workspaceFolder}/node_modules/.bin/_mocha",
32+
"cwd": "${workspaceRoot}/packages/ai",
33+
"args": [
34+
"--require",
35+
"ts-node/register",
36+
"--require",
37+
"src/index.node.ts",
38+
"--timeout",
39+
"5000",
40+
"integration/**/*.test.ts"
41+
],
42+
"env": {
43+
"TS_NODE_COMPILER_OPTIONS": "{\"module\":\"commonjs\"}"
44+
},
45+
"sourceMaps": true
46+
},
2747
{
2848
"type": "node",
2949
"request": "launch",

‎README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ and follow the instructions to login.
143143

144144
For more information, visit https://firebase.google.com/docs/storage/web/download-files#cors_configuration
145145

146+
Then, make sure you have anonymous sign-in provider enabled:
147+
146148
#### Authentication Support
147149

148150
Visit the authentication config in your project and enable the `Anonymous`

‎packages/ai/integration/chat.test.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { expect } from 'chai';
19+
import {
20+
Content,
21+
GenerationConfig,
22+
HarmBlockThreshold,
23+
HarmCategory,
24+
SafetySetting,
25+
getGenerativeModel
26+
} from '../src';
27+
import { testConfigs, TOKEN_COUNT_DELTA } from './constants';
28+
29+
describe('Chat Session', () => {
30+
testConfigs.forEach(testConfig => {
31+
describe(`${testConfig.toString()}`, () => {
32+
const commonGenerationConfig: GenerationConfig = {
33+
temperature: 0,
34+
topP: 0,
35+
responseMimeType: 'text/plain'
36+
};
37+
38+
const commonSafetySettings: SafetySetting[] = [
39+
{
40+
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
41+
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
42+
},
43+
{
44+
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
45+
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
46+
},
47+
{
48+
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
49+
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
50+
},
51+
{
52+
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
53+
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
54+
}
55+
];
56+
57+
const commonSystemInstruction: Content = {
58+
role: 'system',
59+
parts: [
60+
{
61+
text: 'You are a friendly and helpful assistant.'
62+
}
63+
]
64+
};
65+
66+
it('startChat and sendMessage: text input, text output', async () => {
67+
const model = getGenerativeModel(testConfig.ai, {
68+
model: testConfig.model,
69+
generationConfig: commonGenerationConfig,
70+
safetySettings: commonSafetySettings,
71+
systemInstruction: commonSystemInstruction
72+
});
73+
74+
const chat = model.startChat();
75+
const result1 = await chat.sendMessage(
76+
'What is the capital of France?'
77+
);
78+
const response1 = result1.response;
79+
expect(response1.text().trim().toLowerCase()).to.include('paris');
80+
81+
let history = await chat.getHistory();
82+
expect(history.length).to.equal(2);
83+
expect(history[0].role).to.equal('user');
84+
expect(history[0].parts[0].text).to.equal(
85+
'What is the capital of France?'
86+
);
87+
expect(history[1].role).to.equal('model');
88+
expect(history[1].parts[0].text?.toLowerCase()).to.include('paris');
89+
90+
expect(response1.usageMetadata).to.not.be.null;
91+
// Token counts can vary slightly in chat context
92+
expect(response1.usageMetadata!.promptTokenCount).to.be.closeTo(
93+
15, // "What is the capital of France?" + system instruction
94+
TOKEN_COUNT_DELTA + 2 // More variance for chat context
95+
);
96+
expect(response1.usageMetadata!.candidatesTokenCount).to.be.closeTo(
97+
8, // "Paris"
98+
TOKEN_COUNT_DELTA
99+
);
100+
expect(response1.usageMetadata!.totalTokenCount).to.be.closeTo(
101+
23, // "What is the capital of France?" + system instruction + "Paris"
102+
TOKEN_COUNT_DELTA + 3 // More variance for chat context
103+
);
104+
105+
const result2 = await chat.sendMessage('And what about Italy?');
106+
const response2 = result2.response;
107+
expect(response2.text().trim().toLowerCase()).to.include('rome');
108+
109+
history = await chat.getHistory();
110+
expect(history.length).to.equal(4);
111+
expect(history[2].role).to.equal('user');
112+
expect(history[2].parts[0].text).to.equal('And what about Italy?');
113+
expect(history[3].role).to.equal('model');
114+
expect(history[3].parts[0].text?.toLowerCase()).to.include('rome');
115+
116+
expect(response2.usageMetadata).to.not.be.null;
117+
expect(response2.usageMetadata!.promptTokenCount).to.be.closeTo(
118+
28, // History + "And what about Italy?" + system instruction
119+
TOKEN_COUNT_DELTA + 5 // More variance for chat context with history
120+
);
121+
expect(response2.usageMetadata!.candidatesTokenCount).to.be.closeTo(
122+
8,
123+
TOKEN_COUNT_DELTA
124+
);
125+
expect(response2.usageMetadata!.totalTokenCount).to.be.closeTo(
126+
36,
127+
TOKEN_COUNT_DELTA
128+
);
129+
});
130+
});
131+
});
132+
});

‎packages/ai/integration/constants.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { initializeApp } from '@firebase/app';
19+
import {
20+
AI,
21+
Backend,
22+
BackendType,
23+
GoogleAIBackend,
24+
VertexAIBackend,
25+
getAI
26+
} from '../src';
27+
import { FIREBASE_CONFIG } from './firebase-config';
28+
29+
const app = initializeApp(FIREBASE_CONFIG);
30+
31+
/**
32+
* Test config that all tests will be ran against.
33+
*/
34+
export type TestConfig = Readonly<{
35+
ai: AI;
36+
model: string;
37+
/** This will be used to output the test config at runtime */
38+
toString: () => string;
39+
}>;
40+
41+
function formatConfigAsString(config: { ai: AI; model: string }): string {
42+
return `${backendNames.get(config.ai.backend.backendType)} ${config.model}`;
43+
}
44+
45+
const backends: readonly Backend[] = [
46+
new GoogleAIBackend(),
47+
new VertexAIBackend()
48+
];
49+
50+
const backendNames: Map<BackendType, string> = new Map([
51+
[BackendType.GOOGLE_AI, 'Google AI'],
52+
[BackendType.VERTEX_AI, 'Vertex AI']
53+
]);
54+
55+
const modelNames: readonly string[] = ['gemini-2.0-flash'];
56+
57+
/**
58+
* Array of test configurations that is iterated over to get full coverage
59+
* of backends and models. Contains all combinations of backends and models.
60+
*/
61+
export const testConfigs: readonly TestConfig[] = backends.flatMap(backend => {
62+
return modelNames.map(modelName => {
63+
const ai = getAI(app, { backend });
64+
return {
65+
ai: getAI(app, { backend }),
66+
model: modelName,
67+
toString: () => formatConfigAsString({ ai, model: modelName })
68+
};
69+
});
70+
});
71+
72+
export const TINY_IMG_BASE64 =
73+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=';
74+
export const IMAGE_MIME_TYPE = 'image/png';
75+
export const TINY_MP3_BASE64 =
76+
'SUQzBAAAAAAAIlRTU0UAAAAOAAADTGF2ZjYxLjcuMTAwAAAAAAAAAAAAAAD/+0DAAAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAAAUAAAK+AGhoaGhoaGhoaGhoaGhoaGhoaGiOjo6Ojo6Ojo6Ojo6Ojo6Ojo6OjrS0tLS0tLS0tLS0tLS0tLS0tLS02tra2tra2tra2tra2tra2tra2tr//////////////////////////wAAAABMYXZjNjEuMTkAAAAAAAAAAAAAAAAkAwYAAAAAAAACvhC6DYoAAAAAAP/7EMQAA8AAAaQAAAAgAAA0gAAABExBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sQxCmDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+xDEUwPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7EMR8g8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sQxKYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU=';
77+
export const AUDIO_MIME_TYPE = 'audio/mpeg';
78+
79+
// Token counts are only expected to differ by at most this number of tokens.
80+
// Set to 1 for whitespace that is not always present.
81+
export const TOKEN_COUNT_DELTA = 1;

0 commit comments

Comments
 (0)