geminisdk 0.1.1

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/src/client.ts ADDED
@@ -0,0 +1,230 @@
1
+ /**
2
+ * GeminiSDK Client - Main entry point for the Gemini SDK.
3
+ */
4
+
5
+ import { GeminiOAuthManager } from './auth.js';
6
+ import { GeminiBackend } from './backend.js';
7
+ import { SessionNotFoundError } from './exceptions.js';
8
+ import { GeminiSession } from './session.js';
9
+ import {
10
+ ConnectionState,
11
+ GEMINI_CLI_MODELS,
12
+ GeminiClientOptions,
13
+ ModelInfo,
14
+ SessionConfig,
15
+ SessionMetadata,
16
+ } from './types.js';
17
+
18
+ // Simple UUID generator
19
+ function generateUUID(): string {
20
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
21
+ const r = (Math.random() * 16) | 0;
22
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
23
+ return v.toString(16);
24
+ });
25
+ }
26
+
27
+ export class GeminiClient {
28
+ private _options: GeminiClientOptions;
29
+ private _state: ConnectionState = 'disconnected';
30
+ private _backend: GeminiBackend | null = null;
31
+ private _oauthManager: GeminiOAuthManager | null = null;
32
+ private _sessions: Map<string, GeminiSession> = new Map();
33
+ private _started = false;
34
+ private _autoRefreshInterval: ReturnType<typeof setInterval> | null = null;
35
+
36
+ constructor(options: GeminiClientOptions = {}) {
37
+ this._options = options;
38
+ }
39
+
40
+ get options(): GeminiClientOptions {
41
+ return this._options;
42
+ }
43
+
44
+ get state(): ConnectionState {
45
+ return this._state;
46
+ }
47
+
48
+ public async start(): Promise<void> {
49
+ if (this._started) return;
50
+
51
+ this._state = 'connecting';
52
+
53
+ try {
54
+ this._oauthManager = new GeminiOAuthManager(
55
+ this._options.oauthPath,
56
+ this._options.clientId,
57
+ this._options.clientSecret
58
+ );
59
+
60
+ this._backend = new GeminiBackend({
61
+ timeout: this._options.timeout ?? 720000,
62
+ oauthPath: this._options.oauthPath,
63
+ clientId: this._options.clientId,
64
+ clientSecret: this._options.clientSecret,
65
+ });
66
+
67
+ await this._oauthManager.ensureAuthenticated();
68
+
69
+ this._state = 'connected';
70
+ this._started = true;
71
+
72
+ if (this._options.autoRefresh !== false) {
73
+ this.startAutoRefresh();
74
+ }
75
+ } catch (e) {
76
+ this._state = 'error';
77
+ throw e;
78
+ }
79
+ }
80
+
81
+ private startAutoRefresh(): void {
82
+ if (this._autoRefreshInterval) return;
83
+
84
+ this._autoRefreshInterval = setInterval(async () => {
85
+ try {
86
+ if (this._oauthManager) {
87
+ await this._oauthManager.ensureAuthenticated();
88
+ }
89
+ } catch (e) {
90
+ // Ignore background refresh errors
91
+ }
92
+ }, 30000);
93
+ }
94
+
95
+ public async stop(): Promise<void> {
96
+ if (this._autoRefreshInterval) {
97
+ clearInterval(this._autoRefreshInterval);
98
+ this._autoRefreshInterval = null;
99
+ }
100
+
101
+ for (const session of this._sessions.values()) {
102
+ try {
103
+ await session.destroy();
104
+ } catch (e) {
105
+ console.warn('Error destroying session:', e);
106
+ }
107
+ }
108
+ this._sessions.clear();
109
+
110
+ if (this._backend) {
111
+ await this._backend.close();
112
+ this._backend = null;
113
+ }
114
+
115
+ this._oauthManager = null;
116
+ this._state = 'disconnected';
117
+ this._started = false;
118
+ }
119
+
120
+ public async close(): Promise<void> {
121
+ await this.stop();
122
+ }
123
+
124
+ public async createSession(config: SessionConfig = {}): Promise<GeminiSession> {
125
+ if (!this._started) {
126
+ await this.start();
127
+ }
128
+
129
+ if (!this._backend) {
130
+ throw new Error('Client not connected. Call start() first.');
131
+ }
132
+
133
+ const sessionId = config.sessionId ?? generateUUID();
134
+ const model = config.model ?? 'gemini-2.5-pro';
135
+
136
+ const session = new GeminiSession({
137
+ sessionId,
138
+ model,
139
+ backend: this._backend,
140
+ tools: config.tools,
141
+ systemMessage: config.systemMessage,
142
+ generationConfig: config.generationConfig,
143
+ thinkingConfig: config.thinkingConfig,
144
+ streaming: config.streaming ?? true,
145
+ });
146
+
147
+ this._sessions.set(sessionId, session);
148
+ return session;
149
+ }
150
+
151
+ public async getSession(sessionId: string): Promise<GeminiSession> {
152
+ const session = this._sessions.get(sessionId);
153
+ if (!session) {
154
+ throw new SessionNotFoundError(sessionId);
155
+ }
156
+ return session;
157
+ }
158
+
159
+ public async listSessions(): Promise<SessionMetadata[]> {
160
+ const result: SessionMetadata[] = [];
161
+ for (const session of this._sessions.values()) {
162
+ result.push({
163
+ sessionId: session.sessionId,
164
+ startTime: session.startTime.toISOString(),
165
+ modifiedTime: session.modifiedTime.toISOString(),
166
+ model: session.model,
167
+ });
168
+ }
169
+ return result;
170
+ }
171
+
172
+ public async deleteSession(sessionId: string): Promise<void> {
173
+ const session = this._sessions.get(sessionId);
174
+ if (session) {
175
+ await session.destroy();
176
+ this._sessions.delete(sessionId);
177
+ }
178
+ }
179
+
180
+ public getState(): ConnectionState {
181
+ return this._state;
182
+ }
183
+
184
+ public async getAuthStatus(): Promise<{ authenticated: boolean; tokenType?: string; expiresAt?: number }> {
185
+ if (!this._oauthManager) {
186
+ return { authenticated: false };
187
+ }
188
+
189
+ try {
190
+ const credentials = await this._oauthManager.getCredentials();
191
+ return {
192
+ authenticated: true,
193
+ tokenType: credentials.tokenType,
194
+ expiresAt: credentials.expiryDate,
195
+ };
196
+ } catch {
197
+ return { authenticated: false };
198
+ }
199
+ }
200
+
201
+ public async listModels(): Promise<ModelInfo[]> {
202
+ const models: ModelInfo[] = [];
203
+
204
+ for (const [modelId, info] of Object.entries(GEMINI_CLI_MODELS)) {
205
+ models.push({
206
+ id: modelId,
207
+ name: info.name,
208
+ capabilities: {
209
+ supports: {
210
+ vision: false,
211
+ tools: info.supportsNativeTools,
212
+ thinking: info.supportsThinking,
213
+ },
214
+ limits: {
215
+ maxContextWindowTokens: info.contextWindow,
216
+ maxPromptTokens: info.contextWindow,
217
+ },
218
+ },
219
+ });
220
+ }
221
+
222
+ return models;
223
+ }
224
+
225
+ public async refreshAuth(): Promise<void> {
226
+ if (this._oauthManager) {
227
+ await this._oauthManager.ensureAuthenticated(true);
228
+ }
229
+ }
230
+ }
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Custom exceptions for GeminiSDK TypeScript
3
+ */
4
+
5
+ export interface ErrorDetails {
6
+ [key: string]: unknown;
7
+ }
8
+
9
+ export class GeminiSDKError extends Error {
10
+ public readonly details: ErrorDetails;
11
+
12
+ constructor(message: string, details: ErrorDetails = {}) {
13
+ super(message);
14
+ this.name = 'GeminiSDKError';
15
+ this.details = details;
16
+ Object.setPrototypeOf(this, new.target.prototype);
17
+ }
18
+ }
19
+
20
+ export class AuthenticationError extends GeminiSDKError {
21
+ constructor(message = 'Authentication failed', details: ErrorDetails = {}) {
22
+ super(message, details);
23
+ this.name = 'AuthenticationError';
24
+ }
25
+ }
26
+
27
+ export class CredentialsNotFoundError extends AuthenticationError {
28
+ public readonly credentialPath: string;
29
+
30
+ constructor(credentialPath: string, message?: string) {
31
+ const msg =
32
+ message ??
33
+ `Gemini OAuth credentials not found at ${credentialPath}. ` +
34
+ 'Please login using the Gemini CLI first: gemini auth login';
35
+ super(msg, { credentialPath });
36
+ this.name = 'CredentialsNotFoundError';
37
+ this.credentialPath = credentialPath;
38
+ }
39
+ }
40
+
41
+ export class TokenRefreshError extends AuthenticationError {
42
+ public readonly statusCode?: number;
43
+ public readonly responseBody?: string;
44
+
45
+ constructor(
46
+ message = 'Failed to refresh access token',
47
+ statusCode?: number,
48
+ responseBody?: string
49
+ ) {
50
+ const details: ErrorDetails = {};
51
+ if (statusCode !== undefined) details['statusCode'] = statusCode;
52
+ if (responseBody !== undefined) details['responseBody'] = responseBody;
53
+ super(message, details);
54
+ this.name = 'TokenRefreshError';
55
+ this.statusCode = statusCode;
56
+ this.responseBody = responseBody;
57
+ }
58
+ }
59
+
60
+ export class TokenExpiredError extends AuthenticationError {
61
+ constructor(message = 'Access token has expired') {
62
+ super(message);
63
+ this.name = 'TokenExpiredError';
64
+ }
65
+ }
66
+
67
+ export class ConnectionError extends GeminiSDKError {
68
+ public readonly endpoint?: string;
69
+
70
+ constructor(
71
+ message = 'Failed to connect to Gemini API',
72
+ endpoint?: string,
73
+ details: ErrorDetails = {}
74
+ ) {
75
+ if (endpoint) details['endpoint'] = endpoint;
76
+ super(message, details);
77
+ this.name = 'ConnectionError';
78
+ this.endpoint = endpoint;
79
+ }
80
+ }
81
+
82
+ export class APIError extends GeminiSDKError {
83
+ public readonly statusCode: number;
84
+ public readonly responseBody?: string;
85
+ public readonly endpoint?: string;
86
+
87
+ constructor(
88
+ message: string,
89
+ statusCode: number,
90
+ responseBody?: string,
91
+ endpoint?: string
92
+ ) {
93
+ const details: ErrorDetails = { statusCode };
94
+ if (responseBody !== undefined) details['responseBody'] = responseBody;
95
+ if (endpoint !== undefined) details['endpoint'] = endpoint;
96
+ super(message, details);
97
+ this.name = 'APIError';
98
+ this.statusCode = statusCode;
99
+ this.responseBody = responseBody;
100
+ this.endpoint = endpoint;
101
+ }
102
+ }
103
+
104
+ export class RateLimitError extends APIError {
105
+ public readonly retryAfter?: number;
106
+
107
+ constructor(
108
+ message = 'Rate limit exceeded',
109
+ statusCode = 429,
110
+ retryAfter?: number,
111
+ responseBody?: string
112
+ ) {
113
+ super(message, statusCode, responseBody);
114
+ this.name = 'RateLimitError';
115
+ this.retryAfter = retryAfter;
116
+ if (retryAfter !== undefined) this.details['retryAfter'] = retryAfter;
117
+ }
118
+ }
119
+
120
+ export class QuotaExceededError extends APIError {
121
+ public readonly resetTime?: string;
122
+
123
+ constructor(
124
+ message = 'Quota exceeded',
125
+ statusCode = 429,
126
+ resetTime?: string,
127
+ responseBody?: string
128
+ ) {
129
+ super(message, statusCode, responseBody);
130
+ this.name = 'QuotaExceededError';
131
+ this.resetTime = resetTime;
132
+ if (resetTime !== undefined) this.details['resetTime'] = resetTime;
133
+ }
134
+ }
135
+
136
+ export class PermissionDeniedError extends APIError {
137
+ constructor(
138
+ message = 'Permission denied',
139
+ statusCode = 403,
140
+ responseBody?: string
141
+ ) {
142
+ super(message, statusCode, responseBody);
143
+ this.name = 'PermissionDeniedError';
144
+ }
145
+ }
146
+
147
+ export class NotFoundError extends APIError {
148
+ public readonly resource?: string;
149
+
150
+ constructor(
151
+ message = 'Resource not found',
152
+ statusCode = 404,
153
+ resource?: string,
154
+ responseBody?: string
155
+ ) {
156
+ super(message, statusCode, responseBody);
157
+ this.name = 'NotFoundError';
158
+ this.resource = resource;
159
+ if (resource !== undefined) this.details['resource'] = resource;
160
+ }
161
+ }
162
+
163
+ export class SessionError extends GeminiSDKError {
164
+ public readonly sessionId?: string;
165
+
166
+ constructor(message: string, sessionId?: string, details: ErrorDetails = {}) {
167
+ if (sessionId) details['sessionId'] = sessionId;
168
+ super(message, details);
169
+ this.name = 'SessionError';
170
+ this.sessionId = sessionId;
171
+ }
172
+ }
173
+
174
+ export class SessionNotFoundError extends SessionError {
175
+ constructor(sessionId: string) {
176
+ super(`Session not found: ${sessionId}`, sessionId);
177
+ this.name = 'SessionNotFoundError';
178
+ }
179
+ }
180
+
181
+ export class SessionClosedError extends SessionError {
182
+ constructor(sessionId?: string) {
183
+ super('Session is closed', sessionId);
184
+ this.name = 'SessionClosedError';
185
+ }
186
+ }
187
+
188
+ export class ToolError extends GeminiSDKError {
189
+ public readonly toolName?: string;
190
+
191
+ constructor(message: string, toolName?: string, details: ErrorDetails = {}) {
192
+ if (toolName) details['toolName'] = toolName;
193
+ super(message, details);
194
+ this.name = 'ToolError';
195
+ this.toolName = toolName;
196
+ }
197
+ }
198
+
199
+ export class ToolNotFoundError extends ToolError {
200
+ constructor(toolName: string) {
201
+ super(`Tool not found: ${toolName}`, toolName);
202
+ this.name = 'ToolNotFoundError';
203
+ }
204
+ }
205
+
206
+ export class ToolExecutionError extends ToolError {
207
+ public readonly originalError?: Error;
208
+
209
+ constructor(message: string, toolName: string, originalError?: Error) {
210
+ const details: ErrorDetails = {};
211
+ if (originalError) details['originalError'] = originalError.message;
212
+ super(message, toolName, details);
213
+ this.name = 'ToolExecutionError';
214
+ this.originalError = originalError;
215
+ }
216
+ }
217
+
218
+ export class ValidationError extends GeminiSDKError {
219
+ public readonly field?: string;
220
+ public readonly value?: unknown;
221
+
222
+ constructor(message: string, field?: string, value?: unknown) {
223
+ const details: ErrorDetails = {};
224
+ if (field !== undefined) details['field'] = field;
225
+ if (value !== undefined) details['value'] = String(value);
226
+ super(message, details);
227
+ this.name = 'ValidationError';
228
+ this.field = field;
229
+ this.value = value;
230
+ }
231
+ }
232
+
233
+ export class ConfigurationError extends GeminiSDKError {
234
+ public readonly configKey?: string;
235
+
236
+ constructor(message: string, configKey?: string) {
237
+ const details: ErrorDetails = {};
238
+ if (configKey) details['configKey'] = configKey;
239
+ super(message, details);
240
+ this.name = 'ConfigurationError';
241
+ this.configKey = configKey;
242
+ }
243
+ }
244
+
245
+ export class StreamError extends GeminiSDKError {
246
+ public readonly partialContent?: string;
247
+
248
+ constructor(message: string, partialContent?: string) {
249
+ const details: ErrorDetails = {};
250
+ if (partialContent) details['partialContent'] = partialContent.slice(0, 500);
251
+ super(message, details);
252
+ this.name = 'StreamError';
253
+ this.partialContent = partialContent;
254
+ }
255
+ }
256
+
257
+ export class CancellationError extends GeminiSDKError {
258
+ constructor(message = 'Operation was cancelled') {
259
+ super(message);
260
+ this.name = 'CancellationError';
261
+ }
262
+ }
263
+
264
+ export class TimeoutError extends GeminiSDKError {
265
+ public readonly timeout?: number;
266
+
267
+ constructor(message = 'Operation timed out', timeout?: number) {
268
+ const details: ErrorDetails = {};
269
+ if (timeout !== undefined) details['timeout'] = timeout;
270
+ super(message, details);
271
+ this.name = 'TimeoutError';
272
+ this.timeout = timeout;
273
+ }
274
+ }
275
+
276
+ export class OnboardingError extends GeminiSDKError {
277
+ public readonly tierId?: string;
278
+
279
+ constructor(
280
+ message = 'Failed to complete Gemini Code Assist onboarding',
281
+ tierId?: string
282
+ ) {
283
+ const details: ErrorDetails = {};
284
+ if (tierId) details['tierId'] = tierId;
285
+ super(message, details);
286
+ this.name = 'OnboardingError';
287
+ this.tierId = tierId;
288
+ }
289
+ }
package/src/index.ts ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * GeminiSDK TypeScript - A TypeScript SDK for Google Gemini Code Assist API.
3
+ *
4
+ * This SDK provides a high-level interface for interacting with the Gemini
5
+ * Code Assist API, supporting OAuth authentication, streaming responses,
6
+ * tool calling, and session management.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { GeminiClient, EventType } from 'geminisdk';
11
+ *
12
+ * async function main() {
13
+ * const client = new GeminiClient();
14
+ * await client.start();
15
+ *
16
+ * const session = await client.createSession({
17
+ * model: 'gemini-2.5-pro',
18
+ * streaming: true,
19
+ * });
20
+ *
21
+ * session.on((event) => {
22
+ * if (event.type === EventType.ASSISTANT_MESSAGE_DELTA) {
23
+ * process.stdout.write((event.data as any).deltaContent);
24
+ * }
25
+ * });
26
+ *
27
+ * await session.send({ prompt: 'What is TypeScript?' });
28
+ * await client.close();
29
+ * }
30
+ *
31
+ * main();
32
+ * ```
33
+ */
34
+
35
+ export const VERSION = '0.1.1';
36
+
37
+ // Client
38
+ export { GeminiClient } from './client.js';
39
+
40
+ // Session
41
+ export { GeminiSession } from './session.js';
42
+
43
+ // Backend
44
+ export { GeminiBackend } from './backend.js';
45
+
46
+ // Authentication
47
+ export { GeminiOAuthManager } from './auth.js';
48
+
49
+ // Tools
50
+ export {
51
+ ToolRegistry,
52
+ createTool,
53
+ defineTool,
54
+ getDefaultRegistry,
55
+ registerTool,
56
+ } from './tools.js';
57
+
58
+ // Types
59
+ export {
60
+ // Connection
61
+ ConnectionState,
62
+ LogLevel,
63
+ Role,
64
+ // OAuth
65
+ GeminiOAuthCredentials,
66
+ // Models
67
+ GeminiModelInfo,
68
+ ModelVisionLimits,
69
+ ModelLimits,
70
+ ModelSupports,
71
+ ModelCapabilities,
72
+ ModelInfo,
73
+ // Messages
74
+ ContentPart,
75
+ Message,
76
+ Attachment,
77
+ // Tools
78
+ FunctionCall,
79
+ ToolCall,
80
+ ToolInvocation,
81
+ ToolResultType,
82
+ ToolResult,
83
+ ToolHandler,
84
+ Tool,
85
+ // Generation
86
+ GenerationConfig,
87
+ ThinkingConfig,
88
+ // Request/Response
89
+ MessageOptions,
90
+ LLMUsage,
91
+ LLMChunk,
92
+ // Session
93
+ SessionConfig,
94
+ SessionMetadata,
95
+ // Client
96
+ GeminiClientOptions,
97
+ // Events
98
+ EventType,
99
+ SessionEvent,
100
+ SessionEventHandler,
101
+ // Constants
102
+ GEMINI_OAUTH_REDIRECT_URI,
103
+ GEMINI_OAUTH_BASE_URL,
104
+ GEMINI_OAUTH_TOKEN_ENDPOINT,
105
+ GEMINI_OAUTH_AUTH_ENDPOINT,
106
+ GEMINI_OAUTH_CLIENT_ID,
107
+ GEMINI_OAUTH_CLIENT_SECRET,
108
+ GEMINI_OAUTH_SCOPES,
109
+ GEMINI_CODE_ASSIST_ENDPOINT,
110
+ GEMINI_CODE_ASSIST_API_VERSION,
111
+ GEMINI_DIR,
112
+ GEMINI_CREDENTIAL_FILENAME,
113
+ GEMINI_ENV_FILENAME,
114
+ TOKEN_REFRESH_BUFFER_MS,
115
+ HTTP_OK,
116
+ HTTP_UNAUTHORIZED,
117
+ HTTP_FORBIDDEN,
118
+ GEMINI_CLI_MODELS,
119
+ getGeminiCliCredentialPath,
120
+ getGeminiCliEnvPath,
121
+ } from './types.js';
122
+
123
+ // Exceptions
124
+ export {
125
+ GeminiSDKError,
126
+ AuthenticationError,
127
+ CredentialsNotFoundError,
128
+ TokenRefreshError,
129
+ TokenExpiredError,
130
+ ConnectionError,
131
+ APIError,
132
+ RateLimitError,
133
+ QuotaExceededError,
134
+ PermissionDeniedError,
135
+ NotFoundError,
136
+ SessionError,
137
+ SessionNotFoundError,
138
+ SessionClosedError,
139
+ ToolError,
140
+ ToolNotFoundError,
141
+ ToolExecutionError,
142
+ ValidationError,
143
+ ConfigurationError,
144
+ StreamError,
145
+ CancellationError,
146
+ TimeoutError,
147
+ OnboardingError,
148
+ } from './exceptions.js';