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/README.md +129 -0
- package/dist/chunk-Y4MC6YDW.mjs +135 -0
- package/dist/index.d.mts +511 -0
- package/dist/index.d.ts +511 -0
- package/dist/index.js +1696 -0
- package/dist/index.mjs +1480 -0
- package/dist/types-ADTG4FSI.mjs +46 -0
- package/package.json +60 -0
- package/src/auth.ts +293 -0
- package/src/backend.ts +615 -0
- package/src/client.ts +230 -0
- package/src/exceptions.ts +289 -0
- package/src/index.ts +148 -0
- package/src/session.ts +380 -0
- package/src/tools.ts +127 -0
- package/src/types.ts +352 -0
package/src/session.ts
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeminiSDK Session - Manages individual conversation sessions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { GeminiBackend } from './backend.js';
|
|
6
|
+
import { SessionClosedError } from './exceptions.js';
|
|
7
|
+
import {
|
|
8
|
+
EventType,
|
|
9
|
+
GenerationConfig,
|
|
10
|
+
Message,
|
|
11
|
+
MessageOptions,
|
|
12
|
+
Role,
|
|
13
|
+
SessionEvent,
|
|
14
|
+
SessionEventHandler,
|
|
15
|
+
ThinkingConfig,
|
|
16
|
+
Tool,
|
|
17
|
+
ToolCall,
|
|
18
|
+
ToolHandler,
|
|
19
|
+
ToolInvocation,
|
|
20
|
+
} from './types.js';
|
|
21
|
+
|
|
22
|
+
export class GeminiSession {
|
|
23
|
+
private readonly _sessionId: string;
|
|
24
|
+
private readonly _model: string;
|
|
25
|
+
private readonly _backend: GeminiBackend;
|
|
26
|
+
private _tools: Tool[];
|
|
27
|
+
private readonly _toolHandlers: Map<string, ToolHandler>;
|
|
28
|
+
private readonly _systemMessage?: string;
|
|
29
|
+
private readonly _generationConfig?: GenerationConfig;
|
|
30
|
+
private readonly _thinkingConfig?: ThinkingConfig;
|
|
31
|
+
private readonly _streaming: boolean;
|
|
32
|
+
|
|
33
|
+
private _messages: Message[] = [];
|
|
34
|
+
private readonly _eventHandlers: SessionEventHandler[] = [];
|
|
35
|
+
private _closed = false;
|
|
36
|
+
private readonly _startTime: Date;
|
|
37
|
+
private _modifiedTime: Date;
|
|
38
|
+
|
|
39
|
+
constructor(options: {
|
|
40
|
+
sessionId: string;
|
|
41
|
+
model: string;
|
|
42
|
+
backend: GeminiBackend;
|
|
43
|
+
tools?: Tool[];
|
|
44
|
+
systemMessage?: string;
|
|
45
|
+
generationConfig?: GenerationConfig;
|
|
46
|
+
thinkingConfig?: ThinkingConfig;
|
|
47
|
+
streaming?: boolean;
|
|
48
|
+
}) {
|
|
49
|
+
this._sessionId = options.sessionId;
|
|
50
|
+
this._model = options.model;
|
|
51
|
+
this._backend = options.backend;
|
|
52
|
+
this._tools = options.tools ?? [];
|
|
53
|
+
this._systemMessage = options.systemMessage;
|
|
54
|
+
this._generationConfig = options.generationConfig;
|
|
55
|
+
this._thinkingConfig = options.thinkingConfig;
|
|
56
|
+
this._streaming = options.streaming ?? true;
|
|
57
|
+
|
|
58
|
+
this._startTime = new Date();
|
|
59
|
+
this._modifiedTime = new Date();
|
|
60
|
+
|
|
61
|
+
this._toolHandlers = new Map();
|
|
62
|
+
for (const tool of this._tools) {
|
|
63
|
+
if (tool.handler) {
|
|
64
|
+
this._toolHandlers.set(tool.name, tool.handler);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (this._systemMessage) {
|
|
69
|
+
this._messages.push({
|
|
70
|
+
role: Role.SYSTEM,
|
|
71
|
+
content: this._systemMessage,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get sessionId(): string {
|
|
77
|
+
return this._sessionId;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get model(): string {
|
|
81
|
+
return this._model;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get startTime(): Date {
|
|
85
|
+
return this._startTime;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get modifiedTime(): Date {
|
|
89
|
+
return this._modifiedTime;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get messages(): Message[] {
|
|
93
|
+
return [...this._messages];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public on(handler: SessionEventHandler): () => void {
|
|
97
|
+
this._eventHandlers.push(handler);
|
|
98
|
+
return () => {
|
|
99
|
+
const index = this._eventHandlers.indexOf(handler);
|
|
100
|
+
if (index > -1) {
|
|
101
|
+
this._eventHandlers.splice(index, 1);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private emit(eventType: EventType, data: unknown): void {
|
|
107
|
+
const event: SessionEvent = {
|
|
108
|
+
type: eventType,
|
|
109
|
+
data,
|
|
110
|
+
sessionId: this._sessionId,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
for (const handler of this._eventHandlers) {
|
|
114
|
+
try {
|
|
115
|
+
handler(event);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
console.warn('Event handler error:', e);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public async send(options: MessageOptions): Promise<void> {
|
|
123
|
+
if (this._closed) {
|
|
124
|
+
throw new SessionClosedError(this._sessionId);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const prompt = options.prompt;
|
|
128
|
+
const context = options.context;
|
|
129
|
+
|
|
130
|
+
let content = prompt;
|
|
131
|
+
if (context) {
|
|
132
|
+
content = `${context}\n\n${prompt}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const userMessage: Message = {
|
|
136
|
+
role: Role.USER,
|
|
137
|
+
content,
|
|
138
|
+
};
|
|
139
|
+
this._messages.push(userMessage);
|
|
140
|
+
this._modifiedTime = new Date();
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
if (this._streaming) {
|
|
144
|
+
await this.streamResponse();
|
|
145
|
+
} else {
|
|
146
|
+
await this.getResponse();
|
|
147
|
+
}
|
|
148
|
+
} catch (e) {
|
|
149
|
+
this.emit(EventType.SESSION_ERROR, { error: String(e) });
|
|
150
|
+
throw e;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public async sendAndWait(options: MessageOptions): Promise<SessionEvent> {
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
let responseEvent: SessionEvent | null = null;
|
|
157
|
+
|
|
158
|
+
const unsubscribe = this.on((event) => {
|
|
159
|
+
if (event.type === EventType.ASSISTANT_MESSAGE) {
|
|
160
|
+
responseEvent = event;
|
|
161
|
+
} else if (
|
|
162
|
+
event.type === EventType.SESSION_IDLE ||
|
|
163
|
+
event.type === EventType.SESSION_ERROR
|
|
164
|
+
) {
|
|
165
|
+
unsubscribe();
|
|
166
|
+
if (responseEvent) {
|
|
167
|
+
resolve(responseEvent);
|
|
168
|
+
} else if (event.type === EventType.SESSION_ERROR) {
|
|
169
|
+
reject(new Error(String((event.data as Record<string, unknown>)?.['error'])));
|
|
170
|
+
} else {
|
|
171
|
+
reject(new Error('No response received'));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
this.send(options).catch((e) => {
|
|
177
|
+
unsubscribe();
|
|
178
|
+
reject(e);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private async streamResponse(): Promise<void> {
|
|
184
|
+
let fullContent = '';
|
|
185
|
+
let fullReasoning = '';
|
|
186
|
+
const allToolCalls: ToolCall[] = [];
|
|
187
|
+
let finalUsage: unknown;
|
|
188
|
+
|
|
189
|
+
for await (const chunk of this._backend.completeStreaming({
|
|
190
|
+
model: this._model,
|
|
191
|
+
messages: this._messages,
|
|
192
|
+
generationConfig: this._generationConfig,
|
|
193
|
+
thinkingConfig: this._thinkingConfig,
|
|
194
|
+
tools: this._tools.length > 0 ? this._tools : undefined,
|
|
195
|
+
})) {
|
|
196
|
+
if (chunk.content) {
|
|
197
|
+
fullContent += chunk.content;
|
|
198
|
+
this.emit(EventType.ASSISTANT_MESSAGE_DELTA, {
|
|
199
|
+
deltaContent: chunk.content,
|
|
200
|
+
content: fullContent,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (chunk.reasoningContent) {
|
|
205
|
+
fullReasoning += chunk.reasoningContent;
|
|
206
|
+
this.emit(EventType.ASSISTANT_REASONING_DELTA, {
|
|
207
|
+
deltaContent: chunk.reasoningContent,
|
|
208
|
+
content: fullReasoning,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (chunk.toolCalls) {
|
|
213
|
+
allToolCalls.push(...chunk.toolCalls);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (chunk.usage) {
|
|
217
|
+
finalUsage = chunk.usage;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (allToolCalls.length > 0) {
|
|
222
|
+
await this.handleToolCalls(allToolCalls);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const assistantMessage: Message = {
|
|
226
|
+
role: Role.ASSISTANT,
|
|
227
|
+
content: fullContent,
|
|
228
|
+
toolCalls: allToolCalls.length > 0 ? allToolCalls : undefined,
|
|
229
|
+
};
|
|
230
|
+
this._messages.push(assistantMessage);
|
|
231
|
+
|
|
232
|
+
if (fullReasoning) {
|
|
233
|
+
this.emit(EventType.ASSISTANT_REASONING, {
|
|
234
|
+
content: fullReasoning,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.emit(EventType.ASSISTANT_MESSAGE, {
|
|
239
|
+
content: fullContent,
|
|
240
|
+
toolCalls: allToolCalls.length > 0 ? allToolCalls : undefined,
|
|
241
|
+
usage: finalUsage,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
this.emit(EventType.SESSION_IDLE, {});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private async getResponse(): Promise<void> {
|
|
248
|
+
const chunk = await this._backend.complete({
|
|
249
|
+
model: this._model,
|
|
250
|
+
messages: this._messages,
|
|
251
|
+
generationConfig: this._generationConfig,
|
|
252
|
+
thinkingConfig: this._thinkingConfig,
|
|
253
|
+
tools: this._tools.length > 0 ? this._tools : undefined,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if (chunk.toolCalls) {
|
|
257
|
+
await this.handleToolCalls(chunk.toolCalls);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const assistantMessage: Message = {
|
|
261
|
+
role: Role.ASSISTANT,
|
|
262
|
+
content: chunk.content,
|
|
263
|
+
toolCalls: chunk.toolCalls,
|
|
264
|
+
};
|
|
265
|
+
this._messages.push(assistantMessage);
|
|
266
|
+
|
|
267
|
+
if (chunk.reasoningContent) {
|
|
268
|
+
this.emit(EventType.ASSISTANT_REASONING, {
|
|
269
|
+
content: chunk.reasoningContent,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
this.emit(EventType.ASSISTANT_MESSAGE, {
|
|
274
|
+
content: chunk.content,
|
|
275
|
+
toolCalls: chunk.toolCalls,
|
|
276
|
+
usage: chunk.usage,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
this.emit(EventType.SESSION_IDLE, {});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private async handleToolCalls(toolCalls: ToolCall[]): Promise<void> {
|
|
283
|
+
for (const toolCall of toolCalls) {
|
|
284
|
+
const toolName = toolCall.function.name;
|
|
285
|
+
|
|
286
|
+
this.emit(EventType.TOOL_CALL, {
|
|
287
|
+
name: toolName,
|
|
288
|
+
arguments: toolCall.function.arguments,
|
|
289
|
+
callId: toolCall.id,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const handler = this._toolHandlers.get(toolName);
|
|
293
|
+
if (!handler) {
|
|
294
|
+
console.warn(`No handler for tool: ${toolName}`);
|
|
295
|
+
this._messages.push({
|
|
296
|
+
role: Role.USER,
|
|
297
|
+
content: `Error: Tool '${toolName}' not found`,
|
|
298
|
+
toolCallId: toolCall.id,
|
|
299
|
+
name: toolName,
|
|
300
|
+
});
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const invocation: ToolInvocation = {
|
|
306
|
+
name: toolName,
|
|
307
|
+
arguments:
|
|
308
|
+
typeof toolCall.function.arguments === 'object'
|
|
309
|
+
? (toolCall.function.arguments as Record<string, unknown>)
|
|
310
|
+
: {},
|
|
311
|
+
callId: toolCall.id,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const result = await Promise.resolve(handler(invocation));
|
|
315
|
+
const resultText = result.textResultForLlm ?? JSON.stringify(result);
|
|
316
|
+
|
|
317
|
+
this.emit(EventType.TOOL_RESULT, {
|
|
318
|
+
name: toolName,
|
|
319
|
+
callId: toolCall.id,
|
|
320
|
+
result: resultText,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
this._messages.push({
|
|
324
|
+
role: Role.USER,
|
|
325
|
+
content: resultText,
|
|
326
|
+
toolCallId: toolCall.id,
|
|
327
|
+
name: toolName,
|
|
328
|
+
});
|
|
329
|
+
} catch (e) {
|
|
330
|
+
const errorMsg = `Error executing tool '${toolName}': ${e}`;
|
|
331
|
+
console.error(errorMsg);
|
|
332
|
+
|
|
333
|
+
this.emit(EventType.TOOL_RESULT, {
|
|
334
|
+
name: toolName,
|
|
335
|
+
callId: toolCall.id,
|
|
336
|
+
error: String(e),
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
this._messages.push({
|
|
340
|
+
role: Role.USER,
|
|
341
|
+
content: errorMsg,
|
|
342
|
+
toolCallId: toolCall.id,
|
|
343
|
+
name: toolName,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
public getMessages(): Message[] {
|
|
350
|
+
return [...this._messages];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
public addTool(tool: Tool): void {
|
|
354
|
+
this._tools.push(tool);
|
|
355
|
+
if (tool.handler) {
|
|
356
|
+
this._toolHandlers.set(tool.name, tool.handler);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
public removeTool(toolName: string): void {
|
|
361
|
+
this._tools = this._tools.filter((t) => t.name !== toolName);
|
|
362
|
+
this._toolHandlers.delete(toolName);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
public async clearHistory(): Promise<void> {
|
|
366
|
+
if (this._systemMessage) {
|
|
367
|
+
this._messages = [{ role: Role.SYSTEM, content: this._systemMessage }];
|
|
368
|
+
} else {
|
|
369
|
+
this._messages = [];
|
|
370
|
+
}
|
|
371
|
+
this._modifiedTime = new Date();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
public async destroy(): Promise<void> {
|
|
375
|
+
this._closed = true;
|
|
376
|
+
this._eventHandlers.length = 0;
|
|
377
|
+
this._toolHandlers.clear();
|
|
378
|
+
this._messages = [];
|
|
379
|
+
}
|
|
380
|
+
}
|
package/src/tools.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeminiSDK Tools - Tool definition utilities.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Tool,
|
|
7
|
+
ToolHandler,
|
|
8
|
+
ToolInvocation,
|
|
9
|
+
ToolResult,
|
|
10
|
+
} from './types.js';
|
|
11
|
+
|
|
12
|
+
const TYPE_MAPPING: Record<string, string> = {
|
|
13
|
+
string: 'string',
|
|
14
|
+
number: 'number',
|
|
15
|
+
boolean: 'boolean',
|
|
16
|
+
object: 'object',
|
|
17
|
+
array: 'array',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface ToolDefinitionOptions {
|
|
21
|
+
name?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
parameters?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Define a tool for use with Gemini models.
|
|
28
|
+
*/
|
|
29
|
+
export function defineTool<T extends Record<string, unknown>>(
|
|
30
|
+
options: ToolDefinitionOptions,
|
|
31
|
+
handler: (args: T) => unknown | Promise<unknown>
|
|
32
|
+
): Tool {
|
|
33
|
+
const toolName = options.name ?? handler.name ?? 'unnamed_tool';
|
|
34
|
+
const description = options.description ?? `Tool: ${toolName}`;
|
|
35
|
+
const parameters = options.parameters ?? { type: 'object', properties: {} };
|
|
36
|
+
|
|
37
|
+
const wrappedHandler: ToolHandler = async (invocation: ToolInvocation): Promise<ToolResult> => {
|
|
38
|
+
const result = await Promise.resolve(handler(invocation.arguments as T));
|
|
39
|
+
|
|
40
|
+
if (typeof result === 'object' && result !== null && 'textResultForLlm' in result) {
|
|
41
|
+
return result as ToolResult;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { textResultForLlm: String(result) };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
name: toolName,
|
|
49
|
+
description,
|
|
50
|
+
parameters,
|
|
51
|
+
handler: wrappedHandler,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create a tool programmatically.
|
|
57
|
+
*/
|
|
58
|
+
export function createTool(
|
|
59
|
+
name: string,
|
|
60
|
+
description: string,
|
|
61
|
+
parameters?: Record<string, unknown>,
|
|
62
|
+
handler?: ToolHandler
|
|
63
|
+
): Tool {
|
|
64
|
+
return {
|
|
65
|
+
name,
|
|
66
|
+
description,
|
|
67
|
+
parameters: parameters ?? { type: 'object', properties: {} },
|
|
68
|
+
handler,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Registry for managing tools.
|
|
74
|
+
*/
|
|
75
|
+
export class ToolRegistry {
|
|
76
|
+
private tools: Map<string, Tool> = new Map();
|
|
77
|
+
private categories: Map<string, Set<string>> = new Map();
|
|
78
|
+
|
|
79
|
+
public register(tool: Tool, category?: string): void {
|
|
80
|
+
this.tools.set(tool.name, tool);
|
|
81
|
+
|
|
82
|
+
if (category) {
|
|
83
|
+
if (!this.categories.has(category)) {
|
|
84
|
+
this.categories.set(category, new Set());
|
|
85
|
+
}
|
|
86
|
+
this.categories.get(category)!.add(tool.name);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public unregister(name: string): void {
|
|
91
|
+
this.tools.delete(name);
|
|
92
|
+
for (const categoryTools of this.categories.values()) {
|
|
93
|
+
categoryTools.delete(name);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public get(name: string): Tool | undefined {
|
|
98
|
+
return this.tools.get(name);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public getAll(): Tool[] {
|
|
102
|
+
return Array.from(this.tools.values());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public getByCategory(category: string): Tool[] {
|
|
106
|
+
const toolNames = this.categories.get(category);
|
|
107
|
+
if (!toolNames) return [];
|
|
108
|
+
return Array.from(toolNames)
|
|
109
|
+
.map((name) => this.tools.get(name))
|
|
110
|
+
.filter((tool): tool is Tool => tool !== undefined);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public listCategories(): string[] {
|
|
114
|
+
return Array.from(this.categories.keys());
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Global default registry
|
|
119
|
+
const defaultRegistry = new ToolRegistry();
|
|
120
|
+
|
|
121
|
+
export function getDefaultRegistry(): ToolRegistry {
|
|
122
|
+
return defaultRegistry;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function registerTool(tool: Tool, category?: string): void {
|
|
126
|
+
defaultRegistry.register(tool, category);
|
|
127
|
+
}
|