Skip to content

Commit cee1fae

Browse files
committed
Encode/decode instance identifiers directly to/from backends
1 parent 48fc75c commit cee1fae

File tree

9 files changed

+72
-121
lines changed

9 files changed

+72
-121
lines changed

common/api-review/vertexai.api.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { FirebaseError } from '@firebase/util';
1212
// @public
1313
export interface AI {
1414
app: FirebaseApp;
15-
// Warning: (ae-forgotten-export) The symbol "Backend" needs to be exported by the entry point index.d.ts
1615
backend: Backend;
1716
// @deprecated
1817
location: string;
@@ -75,6 +74,12 @@ export class ArraySchema extends Schema {
7574
toJSON(): SchemaRequest;
7675
}
7776

77+
// @public
78+
export abstract class Backend {
79+
protected constructor(type: BackendType);
80+
readonly backendType: BackendType;
81+
}
82+
7883
// @public
7984
export const BackendType: {
8085
readonly VERTEX_AI: "VERTEX_AI";
@@ -418,6 +423,11 @@ export function getImagenModel(ai: AI, modelParams: ImagenModelParams, requestOp
418423
// @public
419424
export function getVertexAI(app?: FirebaseApp, options?: VertexAIOptions): VertexAI;
420425

426+
// @public
427+
export class GoogleAIBackend extends Backend {
428+
constructor();
429+
}
430+
421431
// Warning: (ae-internal-missing-underscore) The name "GoogleAICitationMetadata" should be prefixed with an underscore because the declaration is marked as @internal
422432
//
423433
// @internal (undocumented)
@@ -897,6 +907,12 @@ export interface UsageMetadata {
897907
// @public
898908
export type VertexAI = AI;
899909

910+
// @public
911+
export class VertexAIBackend extends Backend {
912+
constructor(location?: string);
913+
readonly location: string;
914+
}
915+
900916
// @public
901917
export const VertexAIError: typeof AIError;
902918

packages/vertexai/src/api.ts

+6-28
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,9 @@
1818
import { FirebaseApp, getApp, _getProvider } from '@firebase/app';
1919
import { Provider } from '@firebase/component';
2020
import { getModularInstance } from '@firebase/util';
21-
import { DEFAULT_LOCATION, AI_TYPE } from './constants';
21+
import { AI_TYPE } from './constants';
2222
import { AIService } from './service';
23-
import {
24-
BackendType,
25-
AI,
26-
AIOptions,
27-
VertexAI,
28-
VertexAIOptions
29-
} from './public-types';
23+
import { AI, AIOptions, VertexAI, VertexAIOptions } from './public-types';
3024
import {
3125
ImagenModelParams,
3226
ModelParams,
@@ -42,6 +36,7 @@ export { ChatSession } from './methods/chat-session';
4236
export * from './requests/schema-builder';
4337
export { ImagenImageFormat } from './requests/imagen-image-format';
4438
export { AIModel, GenerativeModel, ImagenModel, AIError };
39+
export { Backend, VertexAIBackend, GoogleAIBackend } from './backend';
4540

4641
export { AIErrorCode as VertexAIErrorCode };
4742

@@ -86,10 +81,8 @@ export function getVertexAI(
8681
// Dependencies
8782
const AIProvider: Provider<'AI'> = _getProvider(app, AI_TYPE);
8883

89-
const identifier = encodeInstanceIdentifier({
90-
backendType: BackendType.VERTEX_AI,
91-
location: options?.location ?? DEFAULT_LOCATION
92-
});
84+
const backend = new VertexAIBackend(options?.location);
85+
const identifier = encodeInstanceIdentifier(backend);
9386
return AIProvider.getImmediate({
9487
identifier
9588
});
@@ -131,22 +124,7 @@ export function getAI(
131124
// Dependencies
132125
const AIProvider: Provider<'AI'> = _getProvider(app, AI_TYPE);
133126

134-
let identifier: string;
135-
if (options.backend instanceof GoogleAIBackend) {
136-
identifier = encodeInstanceIdentifier({
137-
backendType: BackendType.GOOGLE_AI
138-
});
139-
} else if (options.backend instanceof VertexAIBackend) {
140-
identifier = encodeInstanceIdentifier({
141-
backendType: BackendType.VERTEX_AI,
142-
location: options.backend.location ?? DEFAULT_LOCATION
143-
});
144-
} else {
145-
throw new AIError(
146-
AIErrorCode.ERROR,
147-
`Invalid backend type: ${options.backend.backendType}`
148-
);
149-
}
127+
const identifier = encodeInstanceIdentifier(options.backend);
150128
return AIProvider.getImmediate({
151129
identifier
152130
});

packages/vertexai/src/constants.ts

-6
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,12 @@
1616
*/
1717

1818
import { version } from '../package.json';
19-
import { BackendType } from './public-types';
20-
import { InstanceIdentifier } from './types/internal';
2119

2220
// TODO (v12): Remove this
2321
export const VERTEX_TYPE = 'vertexAI';
2422

2523
export const AI_TYPE = 'AI';
2624

27-
export const DEFAULT_INSTANCE_IDENTIFIER: InstanceIdentifier = {
28-
backendType: BackendType.GOOGLE_AI
29-
};
30-
3125
export const DEFAULT_LOCATION = 'us-central1';
3226

3327
export const DEFAULT_BASE_URL = 'https://firebasevertexai.googleapis.com';

packages/vertexai/src/helpers.test.ts

+14-32
Original file line numberDiff line numberDiff line change
@@ -18,52 +18,39 @@ import { expect } from 'chai';
1818
import { AI_TYPE } from './constants';
1919
import { encodeInstanceIdentifier, decodeInstanceIdentifier } from './helpers';
2020
import { AIError } from './errors';
21-
import { BackendType } from './public-types';
22-
import { InstanceIdentifier } from './types/internal';
2321
import { AIErrorCode } from './types';
22+
import { GoogleAIBackend, VertexAIBackend } from './backend';
2423

2524
describe('Identifier Encoding/Decoding', () => {
2625
describe('encodeInstanceIdentifier', () => {
2726
it('should encode Vertex AI identifier with a specific location', () => {
28-
const identifier: InstanceIdentifier = {
29-
backendType: BackendType.VERTEX_AI,
30-
location: 'us-central1'
31-
};
27+
const backend = new VertexAIBackend('us-central1');
3228
const expected = `${AI_TYPE}/vertexai/us-central1`;
33-
expect(encodeInstanceIdentifier(identifier)).to.equal(expected);
29+
expect(encodeInstanceIdentifier(backend)).to.equal(expected);
3430
});
3531

3632
it('should encode Vertex AI identifier using empty location', () => {
37-
const identifier: InstanceIdentifier = {
38-
backendType: BackendType.VERTEX_AI,
39-
location: ''
40-
};
33+
const backend = new VertexAIBackend('');
4134
const expected = `${AI_TYPE}/vertexai/`;
42-
expect(encodeInstanceIdentifier(identifier)).to.equal(expected);
35+
expect(encodeInstanceIdentifier(backend)).to.equal(expected);
4336
});
4437

4538
it('should encode Google AI identifier', () => {
46-
const identifier: InstanceIdentifier = {
47-
backendType: BackendType.GOOGLE_AI
48-
};
39+
const backend = new GoogleAIBackend();
4940
const expected = `${AI_TYPE}/googleai`;
50-
expect(encodeInstanceIdentifier(identifier)).to.equal(expected);
41+
expect(encodeInstanceIdentifier(backend)).to.equal(expected);
5142
});
5243

5344
it('should throw AIError for unknown backend type', () => {
54-
const identifier = {
55-
backendType: 'some-future-backend'
56-
} as any; // bypass type checking for the test
57-
58-
expect(() => encodeInstanceIdentifier(identifier)).to.throw(AIError);
45+
expect(() => encodeInstanceIdentifier({} as any)).to.throw(AIError);
5946

6047
try {
61-
encodeInstanceIdentifier(identifier);
48+
encodeInstanceIdentifier({} as any);
6249
expect.fail('Expected encodeInstanceIdentifier to throw');
6350
} catch (e) {
6451
expect(e).to.be.instanceOf(AIError);
6552
const error = e as AIError;
66-
expect(error.message).to.contain(`Unknown backend`);
53+
expect(error.message).to.contain('Invalid backend');
6754
expect(error.code).to.equal(AIErrorCode.ERROR);
6855
}
6956
});
@@ -72,11 +59,8 @@ describe('Identifier Encoding/Decoding', () => {
7259
describe('decodeInstanceIdentifier', () => {
7360
it('should decode Vertex AI identifier with location', () => {
7461
const encoded = `${AI_TYPE}/vertexai/europe-west1`;
75-
const expected: InstanceIdentifier = {
76-
backendType: BackendType.VERTEX_AI,
77-
location: 'europe-west1'
78-
};
79-
expect(decodeInstanceIdentifier(encoded)).to.deep.equal(expected);
62+
const backend = new VertexAIBackend('europe-west1');
63+
expect(decodeInstanceIdentifier(encoded)).to.deep.equal(backend);
8064
});
8165

8266
it('should throw an error if Vertex AI identifier string without explicit location part', () => {
@@ -98,10 +82,8 @@ describe('Identifier Encoding/Decoding', () => {
9882

9983
it('should decode Google AI identifier', () => {
10084
const encoded = `${AI_TYPE}/googleai`;
101-
const expected: InstanceIdentifier = {
102-
backendType: BackendType.GOOGLE_AI
103-
};
104-
expect(decodeInstanceIdentifier(encoded)).to.deep.equal(expected);
85+
const backend = new GoogleAIBackend();
86+
expect(decodeInstanceIdentifier(encoded)).to.deep.equal(backend);
10587
});
10688

10789
it('should throw AIError for invalid backend string', () => {

packages/vertexai/src/helpers.ts

+19-31
Original file line numberDiff line numberDiff line change
@@ -17,50 +17,43 @@
1717

1818
import { AI_TYPE } from './constants';
1919
import { AIError } from './errors';
20-
import { BackendType } from './public-types';
21-
import { InstanceIdentifier } from './types/internal';
2220
import { AIErrorCode } from './types';
21+
import { Backend, GoogleAIBackend, VertexAIBackend } from './backend';
2322

2423
/**
25-
* Encodes an {@link InstanceIdentifier} into a string.
26-
*
27-
* This string is used to identify unique {@link AI} instances by backend type.
24+
* Encodes a {@link Backend} into a string that will be used to uniquely identify {@link AI}
25+
* instances by backend type.
2826
*
2927
* @internal
3028
*/
31-
export function encodeInstanceIdentifier(
32-
instanceIdentifier: InstanceIdentifier
33-
): string {
34-
switch (instanceIdentifier.backendType) {
35-
case BackendType.VERTEX_AI:
36-
return `${AI_TYPE}/vertexai/${instanceIdentifier.location}`;
37-
case BackendType.GOOGLE_AI:
38-
return `${AI_TYPE}/googleai`;
39-
default:
40-
throw new AIError(
41-
AIErrorCode.ERROR,
42-
`Unknown backend '${instanceIdentifier}'`
43-
);
29+
export function encodeInstanceIdentifier(backend: Backend): string {
30+
if (backend instanceof GoogleAIBackend) {
31+
return `${AI_TYPE}/googleai`;
32+
} else if (backend instanceof VertexAIBackend) {
33+
return `${AI_TYPE}/vertexai/${backend.location}`;
34+
} else {
35+
throw new AIError(
36+
AIErrorCode.ERROR,
37+
`Invalid backend: ${JSON.stringify(backend.backendType)}`
38+
);
4439
}
4540
}
4641

4742
/**
48-
* Decodes an instance identifier string into an {@link InstanceIdentifier}.
43+
* Decodes an instance identifier string into a {@link Backend}.
4944
*
5045
* @internal
5146
*/
52-
export function decodeInstanceIdentifier(
53-
instanceIdentifier: string
54-
): InstanceIdentifier {
47+
export function decodeInstanceIdentifier(instanceIdentifier: string): Backend {
5548
const identifierParts = instanceIdentifier.split('/');
5649
if (identifierParts[0] !== AI_TYPE) {
5750
throw new AIError(
5851
AIErrorCode.ERROR,
5952
`Invalid instance identifier, unknown prefix '${identifierParts[0]}'`
6053
);
6154
}
62-
const backend = identifierParts[1];
63-
switch (backend) {
55+
const backendType = identifierParts[1];
56+
switch (backendType) {
6457
case 'vertexai':
6558
const location: string | undefined = identifierParts[2];
6659
if (!location) {
@@ -69,14 +62,9 @@ export function decodeInstanceIdentifier(
6962
`Invalid instance identifier, unknown location '${instanceIdentifier}'`
7063
);
7164
}
72-
return {
73-
backendType: BackendType.VERTEX_AI,
74-
location
75-
};
65+
return new VertexAIBackend(location);
7666
case 'googleai':
77-
return {
78-
backendType: BackendType.GOOGLE_AI
79-
};
67+
return new GoogleAIBackend();
8068
default:
8169
throw new AIError(
8270
AIErrorCode.ERROR,

packages/vertexai/src/index.node.ts

+13-15
Original file line numberDiff line numberDiff line change
@@ -23,33 +23,31 @@
2323

2424
import { registerVersion, _registerComponent } from '@firebase/app';
2525
import { AIService } from './service';
26-
import { DEFAULT_INSTANCE_IDENTIFIER, AI_TYPE } from './constants';
26+
import { AI_TYPE } from './constants';
2727
import { Component, ComponentType } from '@firebase/component';
2828
import { name, version } from '../package.json';
29-
import { InstanceIdentifier } from './types/internal';
3029
import { decodeInstanceIdentifier } from './helpers';
30+
import { AIError } from './errors';
31+
import { AIErrorCode } from './public-types';
3132

3233
function registerAI(): void {
3334
_registerComponent(
3435
new Component(
3536
AI_TYPE,
36-
(container, options) => {
37-
// getImmediate for FirebaseApp will always succeed
38-
const app = container.getProvider('app').getImmediate();
39-
const auth = container.getProvider('auth-internal');
40-
const appCheckProvider = container.getProvider('app-check-internal');
41-
42-
let instanceIdentifier: InstanceIdentifier;
43-
if (options.instanceIdentifier) {
44-
instanceIdentifier = decodeInstanceIdentifier(
45-
options.instanceIdentifier
37+
(container, { instanceIdentifier }) => {
38+
if (!instanceIdentifier) {
39+
throw new AIError(
40+
AIErrorCode.ERROR,
41+
'AIService instance identifier is undefined.'
4642
);
47-
} else {
48-
instanceIdentifier = DEFAULT_INSTANCE_IDENTIFIER;
4943
}
5044

51-
const backend = instanceIdentifier;
45+
const backend = decodeInstanceIdentifier(instanceIdentifier);
5246

47+
// getImmediate for FirebaseApp will always succeed
48+
const app = container.getProvider('app').getImmediate();
49+
const auth = container.getProvider('auth-internal');
50+
const appCheckProvider = container.getProvider('app-check-internal');
5351
return new AIService(app, backend, auth, appCheckProvider);
5452
},
5553
ComponentType.PUBLIC

packages/vertexai/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function registerAI(): void {
4949
}
5050

5151
const backend = decodeInstanceIdentifier(instanceIdentifier);
52+
5253
// getImmediate for FirebaseApp will always succeed
5354
const app = container.getProvider('app').getImmediate();
5455
const auth = container.getProvider('auth-internal');

packages/vertexai/src/requests/request.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class RequestUrl {
6666
} else {
6767
throw new AIError(
6868
AIErrorCode.ERROR,
69-
`Invalid backend: ${this.apiSettings.backend}`
69+
`Invalid backend: ${JSON.stringify(this.apiSettings.backend)}`
7070
);
7171
}
7272
}

packages/vertexai/src/types/internal.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import { AppCheckTokenResult } from '@firebase/app-check-interop-types';
1919
import { FirebaseAuthTokenData } from '@firebase/auth-interop-types';
2020
import { Backend } from '../backend';
21-
import { BackendType } from '../public-types';
2221

2322
export * from './imagen/internal';
2423

@@ -28,15 +27,10 @@ export interface ApiSettings {
2827
appId: string;
2928
automaticDataCollectionEnabled?: boolean;
3029
/**
31-
* @deprecated
30+
* @deprecated Use `backend.location` instead.
3231
*/
3332
location: string;
3433
backend: Backend;
3534
getAuthToken?: () => Promise<FirebaseAuthTokenData | null>;
3635
getAppCheckToken?: () => Promise<AppCheckTokenResult>;
3736
}
38-
39-
export interface InstanceIdentifier {
40-
backendType: BackendType;
41-
location?: string;
42-
}

0 commit comments

Comments
 (0)