@agentier/google 0.1.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/index.d.ts +7 -0
- package/dist/index.js +220 -0
- package/dist/mapper.d.ts +79 -0
- package/dist/provider.d.ts +24 -0
- package/dist/types.d.ts +23 -0
- package/package.json +29 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// src/mapper.ts
|
|
2
|
+
function toGeminiContents(messages) {
|
|
3
|
+
let systemInstruction;
|
|
4
|
+
const contents = [];
|
|
5
|
+
for (const msg of messages) {
|
|
6
|
+
if (msg.role === "system") {
|
|
7
|
+
systemInstruction = { parts: [{ text: msg.content ?? "" }] };
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
if (msg.role === "user") {
|
|
11
|
+
contents.push({ role: "user", parts: [{ text: msg.content ?? "" }] });
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (msg.role === "assistant") {
|
|
15
|
+
const parts = [];
|
|
16
|
+
if (msg.content) {
|
|
17
|
+
parts.push({ text: msg.content });
|
|
18
|
+
}
|
|
19
|
+
if (msg.toolCalls) {
|
|
20
|
+
for (const tc of msg.toolCalls) {
|
|
21
|
+
parts.push({
|
|
22
|
+
functionCall: { name: tc.name, args: tc.arguments }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (parts.length > 0) {
|
|
27
|
+
contents.push({ role: "model", parts });
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (msg.role === "tool") {
|
|
32
|
+
const part = {
|
|
33
|
+
functionResponse: {
|
|
34
|
+
name: msg.name ?? "",
|
|
35
|
+
response: { content: msg.content ?? "" }
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const last = contents[contents.length - 1];
|
|
39
|
+
if (last && last.role === "user" && last.parts.some((p) => ("functionResponse" in p))) {
|
|
40
|
+
last.parts.push(part);
|
|
41
|
+
} else {
|
|
42
|
+
contents.push({ role: "user", parts: [part] });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { systemInstruction, contents };
|
|
47
|
+
}
|
|
48
|
+
function toGeminiTools(tools) {
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
functionDeclarations: tools.map((t) => ({
|
|
52
|
+
name: t.name,
|
|
53
|
+
description: t.description,
|
|
54
|
+
parameters: t.parameters
|
|
55
|
+
}))
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
function fromGeminiResponse(candidates) {
|
|
60
|
+
let text = "";
|
|
61
|
+
const toolCalls = [];
|
|
62
|
+
let callIndex = 0;
|
|
63
|
+
const parts = candidates[0]?.content?.parts ?? [];
|
|
64
|
+
for (const part of parts) {
|
|
65
|
+
if ("text" in part) {
|
|
66
|
+
text += part.text;
|
|
67
|
+
} else if ("functionCall" in part) {
|
|
68
|
+
toolCalls.push({
|
|
69
|
+
id: `call_${callIndex++}`,
|
|
70
|
+
name: part.functionCall.name,
|
|
71
|
+
arguments: part.functionCall.args
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { text: text || null, toolCalls };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/provider.ts
|
|
79
|
+
function google(config) {
|
|
80
|
+
const apiVersion = config.apiVersion ?? "v1beta";
|
|
81
|
+
const baseUrl = (config.baseUrl ?? `https://generativelanguage.googleapis.com/${apiVersion}`).replace(/\/$/, "");
|
|
82
|
+
const fetchFn = config.fetch ?? globalThis.fetch;
|
|
83
|
+
function buildHeaders() {
|
|
84
|
+
return {
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
...config.defaultHeaders
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function buildBody(params) {
|
|
90
|
+
const { systemInstruction, contents } = toGeminiContents(params.messages);
|
|
91
|
+
const body = {
|
|
92
|
+
contents
|
|
93
|
+
};
|
|
94
|
+
if (systemInstruction)
|
|
95
|
+
body.systemInstruction = systemInstruction;
|
|
96
|
+
if (params.tools && params.tools.length > 0) {
|
|
97
|
+
body.tools = toGeminiTools(params.tools);
|
|
98
|
+
}
|
|
99
|
+
const generationConfig = {};
|
|
100
|
+
if (params.temperature !== undefined)
|
|
101
|
+
generationConfig.temperature = params.temperature;
|
|
102
|
+
if (params.topP !== undefined)
|
|
103
|
+
generationConfig.topP = params.topP;
|
|
104
|
+
if (params.maxOutputTokens !== undefined)
|
|
105
|
+
generationConfig.maxOutputTokens = params.maxOutputTokens;
|
|
106
|
+
if (Object.keys(generationConfig).length > 0) {
|
|
107
|
+
body.generationConfig = generationConfig;
|
|
108
|
+
}
|
|
109
|
+
return body;
|
|
110
|
+
}
|
|
111
|
+
const provider = {
|
|
112
|
+
name: "google",
|
|
113
|
+
async chat(params) {
|
|
114
|
+
const url = `${baseUrl}/models/${params.model}:generateContent?key=${config.apiKey}`;
|
|
115
|
+
const response = await fetchFn(url, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: buildHeaders(),
|
|
118
|
+
body: JSON.stringify(buildBody(params)),
|
|
119
|
+
signal: params.signal
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
const errorBody = await response.text();
|
|
123
|
+
throw new Error(`Google API error (${response.status}): ${errorBody}`);
|
|
124
|
+
}
|
|
125
|
+
const data = await response.json();
|
|
126
|
+
const { text, toolCalls } = fromGeminiResponse(data.candidates ?? []);
|
|
127
|
+
return {
|
|
128
|
+
content: text,
|
|
129
|
+
toolCalls,
|
|
130
|
+
usage: {
|
|
131
|
+
inputTokens: data.usageMetadata?.promptTokenCount ?? 0,
|
|
132
|
+
outputTokens: data.usageMetadata?.candidatesTokenCount ?? 0
|
|
133
|
+
},
|
|
134
|
+
raw: data
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
async* stream(params) {
|
|
138
|
+
const url = `${baseUrl}/models/${params.model}:streamGenerateContent?alt=sse&key=${config.apiKey}`;
|
|
139
|
+
const response = await fetchFn(url, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: buildHeaders(),
|
|
142
|
+
body: JSON.stringify(buildBody(params)),
|
|
143
|
+
signal: params.signal
|
|
144
|
+
});
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
const errorBody = await response.text();
|
|
147
|
+
throw new Error(`Google API error (${response.status}): ${errorBody}`);
|
|
148
|
+
}
|
|
149
|
+
const reader = response.body?.getReader();
|
|
150
|
+
if (!reader)
|
|
151
|
+
throw new Error("No response body");
|
|
152
|
+
const decoder = new TextDecoder;
|
|
153
|
+
let buffer = "";
|
|
154
|
+
let fullContent = "";
|
|
155
|
+
const toolCalls = [];
|
|
156
|
+
let callIndex = 0;
|
|
157
|
+
let inputTokens = 0;
|
|
158
|
+
let outputTokens = 0;
|
|
159
|
+
try {
|
|
160
|
+
while (true) {
|
|
161
|
+
const { done, value } = await reader.read();
|
|
162
|
+
if (done)
|
|
163
|
+
break;
|
|
164
|
+
buffer += decoder.decode(value, { stream: true });
|
|
165
|
+
const lines = buffer.split(`
|
|
166
|
+
`);
|
|
167
|
+
buffer = lines.pop() ?? "";
|
|
168
|
+
for (const line of lines) {
|
|
169
|
+
const trimmed = line.trim();
|
|
170
|
+
if (!trimmed.startsWith("data: "))
|
|
171
|
+
continue;
|
|
172
|
+
const data = trimmed.slice(6);
|
|
173
|
+
try {
|
|
174
|
+
const parsed = JSON.parse(data);
|
|
175
|
+
const parts = parsed.candidates?.[0]?.content?.parts ?? [];
|
|
176
|
+
for (const part of parts) {
|
|
177
|
+
if ("text" in part) {
|
|
178
|
+
fullContent += part.text;
|
|
179
|
+
yield { type: "token", text: part.text };
|
|
180
|
+
} else if ("functionCall" in part) {
|
|
181
|
+
const id = `call_${callIndex++}`;
|
|
182
|
+
const call = {
|
|
183
|
+
id,
|
|
184
|
+
name: part.functionCall.name,
|
|
185
|
+
arguments: part.functionCall.args
|
|
186
|
+
};
|
|
187
|
+
toolCalls.push(call);
|
|
188
|
+
yield {
|
|
189
|
+
type: "tool_call_start",
|
|
190
|
+
id,
|
|
191
|
+
name: part.functionCall.name
|
|
192
|
+
};
|
|
193
|
+
yield { type: "tool_call_end", id, call };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (parsed.usageMetadata) {
|
|
197
|
+
inputTokens = parsed.usageMetadata.promptTokenCount ?? inputTokens;
|
|
198
|
+
outputTokens = parsed.usageMetadata.candidatesTokenCount ?? outputTokens;
|
|
199
|
+
}
|
|
200
|
+
} catch {}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} finally {
|
|
204
|
+
reader.releaseLock();
|
|
205
|
+
}
|
|
206
|
+
yield {
|
|
207
|
+
type: "done",
|
|
208
|
+
response: {
|
|
209
|
+
content: fullContent || null,
|
|
210
|
+
toolCalls,
|
|
211
|
+
usage: { inputTokens, outputTokens }
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
return provider;
|
|
217
|
+
}
|
|
218
|
+
export {
|
|
219
|
+
google
|
|
220
|
+
};
|
package/dist/mapper.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Message, ToolCall, ToolJsonSchema } from '@agentier/core';
|
|
2
|
+
/** Wire format for a Gemini conversation turn. */
|
|
3
|
+
export interface GeminiContent {
|
|
4
|
+
role: 'user' | 'model';
|
|
5
|
+
parts: GeminiPart[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A single part within a Gemini message.
|
|
9
|
+
*
|
|
10
|
+
* Can be plain text, a function call from the model, or a function response
|
|
11
|
+
* from the caller.
|
|
12
|
+
*/
|
|
13
|
+
export type GeminiPart = {
|
|
14
|
+
text: string;
|
|
15
|
+
} | {
|
|
16
|
+
functionCall: {
|
|
17
|
+
name: string;
|
|
18
|
+
args: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
} | {
|
|
21
|
+
functionResponse: {
|
|
22
|
+
name: string;
|
|
23
|
+
response: {
|
|
24
|
+
content: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/** Wire format for a Gemini function (tool) declaration. */
|
|
29
|
+
export interface GeminiFunctionDeclaration {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
parameters: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Converts agenti `Message` objects into Gemini `contents` and an optional `systemInstruction`.
|
|
36
|
+
*
|
|
37
|
+
* System messages become a top-level `systemInstruction`. Tool result messages
|
|
38
|
+
* are placed inside `user` turns as `functionResponse` parts, and consecutive
|
|
39
|
+
* tool results are merged into a single turn, matching the Gemini protocol.
|
|
40
|
+
*
|
|
41
|
+
* @param messages - Array of provider-agnostic messages.
|
|
42
|
+
* @returns An object with optional `systemInstruction` and the `contents` array.
|
|
43
|
+
*/
|
|
44
|
+
export declare function toGeminiContents(messages: Message[]): {
|
|
45
|
+
systemInstruction?: {
|
|
46
|
+
parts: [{
|
|
47
|
+
text: string;
|
|
48
|
+
}];
|
|
49
|
+
};
|
|
50
|
+
contents: GeminiContent[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Converts agenti tool schemas into the Gemini function declarations format.
|
|
54
|
+
*
|
|
55
|
+
* Wraps all declarations in a single tool group, as required by the Gemini API.
|
|
56
|
+
*
|
|
57
|
+
* @param tools - Array of provider-agnostic tool JSON schemas.
|
|
58
|
+
* @returns Array containing one tool group with all function declarations.
|
|
59
|
+
*/
|
|
60
|
+
export declare function toGeminiTools(tools: ToolJsonSchema[]): {
|
|
61
|
+
functionDeclarations: GeminiFunctionDeclaration[];
|
|
62
|
+
}[];
|
|
63
|
+
/**
|
|
64
|
+
* Extracts text and tool calls from a Gemini response candidates array.
|
|
65
|
+
*
|
|
66
|
+
* Since Gemini does not return tool call IDs, synthetic IDs are generated
|
|
67
|
+
* using an incrementing counter (`call_0`, `call_1`, etc.).
|
|
68
|
+
*
|
|
69
|
+
* @param candidates - The `candidates` array from the Gemini API response.
|
|
70
|
+
* @returns Parsed text (or `null` if empty) and an array of agenti `ToolCall` objects.
|
|
71
|
+
*/
|
|
72
|
+
export declare function fromGeminiResponse(candidates: Array<{
|
|
73
|
+
content: {
|
|
74
|
+
parts: GeminiPart[];
|
|
75
|
+
};
|
|
76
|
+
}>): {
|
|
77
|
+
text: string | null;
|
|
78
|
+
toolCalls: ToolCall[];
|
|
79
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ModelProvider } from '@agentier/core';
|
|
2
|
+
import type { GoogleProviderConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a Google Gemini {@link ModelProvider}.
|
|
5
|
+
*
|
|
6
|
+
* Supports both blocking `chat` (via `generateContent`) and streaming `stream`
|
|
7
|
+
* (via `streamGenerateContent`) endpoints. The API key is passed as a query
|
|
8
|
+
* parameter rather than a header, matching the Gemini REST convention.
|
|
9
|
+
*
|
|
10
|
+
* @param config - Provider configuration including API key and optional overrides.
|
|
11
|
+
* @returns A `ModelProvider` wired to the Google Gemini API.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { google } from '@agentier/provider-google'
|
|
16
|
+
*
|
|
17
|
+
* const provider = google({ apiKey: process.env.GOOGLE_API_KEY! })
|
|
18
|
+
* const response = await provider.chat({
|
|
19
|
+
* model: 'gemini-2.0-flash',
|
|
20
|
+
* messages: [{ role: 'user', content: 'Hello!' }],
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function google(config: GoogleProviderConfig): ModelProvider;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the Google (Gemini) provider.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* const config: GoogleProviderConfig = {
|
|
7
|
+
* apiKey: process.env.GOOGLE_API_KEY!,
|
|
8
|
+
* apiVersion: 'v1beta',
|
|
9
|
+
* }
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export interface GoogleProviderConfig {
|
|
13
|
+
/** Google AI API key appended as a query parameter to each request. */
|
|
14
|
+
apiKey: string;
|
|
15
|
+
/** Base URL for API requests. Defaults to `https://generativelanguage.googleapis.com/{apiVersion}`. */
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
/** Gemini API version used in the default base URL. Defaults to `'v1beta'`. */
|
|
18
|
+
apiVersion?: string;
|
|
19
|
+
/** Additional headers merged into every request. */
|
|
20
|
+
defaultHeaders?: Record<string, string>;
|
|
21
|
+
/** Custom `fetch` implementation, useful for proxies or test doubles. */
|
|
22
|
+
fetch?: typeof globalThis.fetch;
|
|
23
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentier/google",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target node",
|
|
18
|
+
"test": "bun test",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@agentier/core": "^0.1.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@agentier/core": "workspace:*",
|
|
26
|
+
"typescript": "^5.5.0",
|
|
27
|
+
"@types/bun": "latest"
|
|
28
|
+
}
|
|
29
|
+
}
|