@kindgi/adapter-model-gemini 0.0.0-bootstrap.0 → 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.
@@ -0,0 +1,168 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ import { GoogleGenAI, } from '@google/genai';
4
+ import { computeCostUsd, toFrameworkUsage } from './cost.js';
5
+ import { fromGeminiResponse, toGeminiFunctions, toGeminiRequest } from './translate.js';
6
+ /** The adapter id servers register `geminiAdapterFactory` under. */
7
+ export const GEMINI_ADAPTER_ID = '@kindgi/adapter-model-gemini';
8
+ const CLOUD_PLATFORM_SCOPE = 'https://www.googleapis.com/auth/cloud-platform';
9
+ /**
10
+ * A Gemini `ModelProvider` on Vertex AI. Non-streaming, tool-use enabled:
11
+ * one `generateContent` call per `invoke`.
12
+ */
13
+ export function createGeminiProvider(options) {
14
+ const { metadata } = options;
15
+ checkVertexTarget(metadata.id, options.vertex);
16
+ const modelsByName = new Map(metadata.models.map((m) => [m.name, m]));
17
+ // One client per credential: reused while the resolved key stays the
18
+ // same, rebuilt when it rotates. `''` stands for ADC.
19
+ let cached;
20
+ async function resolveClient() {
21
+ if (options.client !== undefined)
22
+ return options.client;
23
+ const key = options.credentials !== undefined ? await options.credentials() : '';
24
+ if (cached?.key === key)
25
+ return cached.client;
26
+ const client = new GoogleGenAI({
27
+ vertexai: true,
28
+ project: options.vertex.project,
29
+ location: options.vertex.location,
30
+ ...(key !== '' && {
31
+ googleAuthOptions: {
32
+ credentials: parseServiceAccountKey(key),
33
+ scopes: [CLOUD_PLATFORM_SCOPE],
34
+ },
35
+ }),
36
+ });
37
+ cached = { key, client };
38
+ return client;
39
+ }
40
+ return {
41
+ metadata,
42
+ async invoke(input) {
43
+ const model = modelsByName.get(input.model);
44
+ if (model === undefined) {
45
+ throw new Error(`${GEMINI_ADAPTER_ID}: provider "${metadata.id}" does not expose model "${input.model}". ` +
46
+ `Available: ${[...modelsByName.keys()].join(', ') || '<none>'}.`);
47
+ }
48
+ const startedAt = Date.now();
49
+ const { systemInstruction, contents } = toGeminiRequest(input.messages);
50
+ const maxOutputTokens = input.maxOutputTokens ?? model.maxOutputTokens;
51
+ const config = {
52
+ ...(systemInstruction !== undefined && { systemInstruction }),
53
+ ...(input.tools !== undefined &&
54
+ input.tools.length > 0 && {
55
+ tools: [{ functionDeclarations: [...toGeminiFunctions(input.tools)] }],
56
+ }),
57
+ ...(input.structuredOutput !== undefined && {
58
+ responseMimeType: 'application/json',
59
+ responseJsonSchema: input.structuredOutput.schema,
60
+ }),
61
+ ...(input.temperature !== undefined && { temperature: input.temperature }),
62
+ ...(maxOutputTokens !== undefined && { maxOutputTokens }),
63
+ ...(input.abortSignal !== undefined && { abortSignal: input.abortSignal }),
64
+ };
65
+ const client = await resolveClient();
66
+ const response = await client.models.generateContent({
67
+ model: input.model,
68
+ contents: [...contents],
69
+ config,
70
+ });
71
+ const { message, finishReason } = fromGeminiResponse(response);
72
+ const usage = toFrameworkUsage(response.usageMetadata);
73
+ return {
74
+ message,
75
+ finishReason,
76
+ usage,
77
+ costUsd: computeCostUsd(usage, model.cost),
78
+ durationMs: Date.now() - startedAt,
79
+ provider: { id: metadata.id, model: input.model },
80
+ };
81
+ },
82
+ };
83
+ }
84
+ /**
85
+ * A service-account key's JSON, reduced to the fields a service-account
86
+ * key has. google-auth-library acts on a credential's `type`: an
87
+ * `external_account` fetches URLs it names (with its headers) and reads
88
+ * files it names, `token_uri` and `universe_domain` move the token
89
+ * exchange. A tenant writes this secret, so only `service_account` is
90
+ * accepted, and Google's own token endpoint is always used.
91
+ */
92
+ export function parseServiceAccountKey(key) {
93
+ let parsed;
94
+ try {
95
+ parsed = JSON.parse(key);
96
+ }
97
+ catch {
98
+ throw new Error(`${GEMINI_ADAPTER_ID}: the credential isn't a service-account key (not JSON).`);
99
+ }
100
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
101
+ throw new Error(`${GEMINI_ADAPTER_ID}: the credential isn't a service-account key (not an object).`);
102
+ }
103
+ const fields = parsed;
104
+ if (fields.type !== 'service_account') {
105
+ throw new Error(`${GEMINI_ADAPTER_ID}: the credential must be a service-account key ("type": "service_account"); other credential types aren't accepted.`);
106
+ }
107
+ if (typeof fields.client_email !== 'string' || typeof fields.private_key !== 'string') {
108
+ throw new Error(`${GEMINI_ADAPTER_ID}: the credential isn't a service-account key (no client_email / private_key).`);
109
+ }
110
+ const reduced = {
111
+ type: 'service_account',
112
+ client_email: fields.client_email,
113
+ private_key: fields.private_key,
114
+ };
115
+ for (const optional of ['project_id', 'private_key_id', 'client_id']) {
116
+ const value = fields[optional];
117
+ if (typeof value === 'string')
118
+ reduced[optional] = value;
119
+ }
120
+ return reduced;
121
+ }
122
+ /**
123
+ * The adapter factory servers register under `GEMINI_ADAPTER_ID`. A
124
+ * provider registered with it carries:
125
+ *
126
+ * - `adapter_config.project` — the Vertex AI project (required);
127
+ * - `metadata.region` — the Vertex location (`global`, `us-central1`,
128
+ * `northamerica-northeast1`, …); `'unspecified'` means `global`;
129
+ * - `secret_ref` (optional) — a service-account key. Without one the
130
+ * adapter uses Application Default Credentials.
131
+ */
132
+ export const geminiAdapterFactory = (input) => createGeminiProvider({
133
+ metadata: input.metadata,
134
+ vertex: vertexTarget(input),
135
+ ...(input.resolveApiKey !== undefined && { credentials: input.resolveApiKey }),
136
+ });
137
+ /** The Vertex project and location a registration names (see `geminiAdapterFactory`). */
138
+ export function vertexTarget(input) {
139
+ const project = input.config?.project;
140
+ if (typeof project !== 'string' || project.length === 0) {
141
+ throw new Error(`${GEMINI_ADAPTER_ID}: provider "${input.metadata.id}" needs adapter_config.project (the Vertex AI project).`);
142
+ }
143
+ const target = {
144
+ project,
145
+ location: input.metadata.region === 'unspecified' ? 'global' : input.metadata.region,
146
+ };
147
+ checkVertexTarget(input.metadata.id, target);
148
+ return target;
149
+ }
150
+ /**
151
+ * The SDK puts the project in the request path and builds the hostname
152
+ * from the location (`https://<location>-aiplatform.googleapis.com/`):
153
+ * anything but a project id and one DNS label could move the request,
154
+ * with the server's credentials, to another host.
155
+ */
156
+ function checkVertexTarget(providerId, vertex) {
157
+ if (!VERTEX_PROJECT.test(vertex.project)) {
158
+ throw new Error(`${GEMINI_ADAPTER_ID}: provider "${providerId}" adapter_config.project must be a Google Cloud project id or number.`);
159
+ }
160
+ if (!VERTEX_LOCATION.test(vertex.location)) {
161
+ throw new Error(`${GEMINI_ADAPTER_ID}: provider "${providerId}" metadata.region must be a Vertex AI location (one DNS label, e.g. us-central1).`);
162
+ }
163
+ }
164
+ /** A Google Cloud project id (6–30: lowercase, digits, hyphens) or project number. */
165
+ const VERTEX_PROJECT = /^(?:[a-z][a-z0-9-]{4,28}[a-z0-9]|[0-9]{1,20})$/;
166
+ /** A Vertex AI location: one DNS label. */
167
+ const VERTEX_LOCATION = /^[a-z][a-z0-9-]{0,62}$/;
168
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { Content, FunctionDeclaration, GenerateContentResponse } from '@google/genai';
2
+ import type { ModelCallResult, ModelMessage, ModelToolDefinition } from '@kindgi/capabilities';
3
+ export declare function checkFunctionName(name: string): string;
4
+ /**
5
+ * The framework's message trail as a Gemini request:
6
+ *
7
+ * - `system` messages lift into `systemInstruction` (several join with
8
+ * a blank line, in order);
9
+ * - `user` messages become `user` turns; `assistant` messages become
10
+ * `model` turns — text, then one `functionCall` part per tool call,
11
+ * carrying the call's thought signature back;
12
+ * - `tool` messages become `functionResponse` parts in a `user` turn.
13
+ * Gemini names the function it answers, so the name comes from the
14
+ * assistant tool call with the same id. The tool's output goes under
15
+ * `response.output` (parsed when it is JSON).
16
+ *
17
+ * Consecutive turns of the same role merge into one `Content`.
18
+ */
19
+ export declare function toGeminiRequest(messages: readonly ModelMessage[]): {
20
+ readonly systemInstruction: string | undefined;
21
+ readonly contents: readonly Content[];
22
+ };
23
+ /**
24
+ * Tool definitions as Gemini function declarations. The input schema is
25
+ * full JSON Schema, which `parametersJsonSchema` takes as it is.
26
+ */
27
+ export declare function toGeminiFunctions(tools: readonly ModelToolDefinition[]): readonly FunctionDeclaration[];
28
+ /**
29
+ * A Gemini response as the framework's assistant message: the text of
30
+ * the first candidate (thinking parts left out), and a tool call per
31
+ * `functionCall` part, with its thought signature. Gemini may omit the
32
+ * call id; one is generated then, so the tool result can refer to it.
33
+ */
34
+ export declare function fromGeminiResponse(response: GenerateContentResponse): {
35
+ readonly message: ModelMessage;
36
+ readonly finishReason: ModelCallResult['finishReason'];
37
+ };
38
+ /**
39
+ * Gemini's `finishReason` as the framework's:
40
+ * - `STOP`, unspecified or absent → `stop` (a response with function
41
+ * calls is `tool-use` before this is consulted);
42
+ * - `MAX_TOKENS` → `length`;
43
+ * - the safety, recitation, blocklist and sensitive-data stops →
44
+ * `content-filter`;
45
+ * - anything else (a malformed or unexpected function call, too many
46
+ * tool calls, an unsupported language, `OTHER`) → `error`.
47
+ */
48
+ export declare function mapFinishReason(reason: string | undefined): ModelCallResult['finishReason'];
49
+ //# sourceMappingURL=translate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,OAAO,EAEP,mBAAmB,EACnB,uBAAuB,EAExB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEZ,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAS9B,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG;IAClE,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;CACvC,CAuBA;AAiDD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,mBAAmB,EAAE,GACpC,SAAS,mBAAmB,EAAE,CAMhC;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,uBAAuB,GAAG;IACrE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,eAAe,CAAC,cAAc,CAAC,CAAC;CACxD,CA2BA;AAsBD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,CAAC,cAAc,CAAC,CAO3F"}
@@ -0,0 +1,179 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ import { randomUUID } from 'node:crypto';
4
+ /**
5
+ * Gemini function names: a letter or `_` first, then letters, digits,
6
+ * `_ . : -`, at most 64 characters. The framework's `<pack>.<tool>` ids
7
+ * fit as they are, so names go over the wire unchanged.
8
+ */
9
+ const FUNCTION_NAME = /^[A-Za-z_][A-Za-z0-9_.:-]{0,63}$/;
10
+ export function checkFunctionName(name) {
11
+ if (!FUNCTION_NAME.test(name)) {
12
+ throw new Error(`@kindgi/adapter-model-gemini: tool "${name}" isn't a valid Gemini function name (a letter or _ first, then letters, digits, _ . : -, at most 64 characters).`);
13
+ }
14
+ return name;
15
+ }
16
+ /**
17
+ * The framework's message trail as a Gemini request:
18
+ *
19
+ * - `system` messages lift into `systemInstruction` (several join with
20
+ * a blank line, in order);
21
+ * - `user` messages become `user` turns; `assistant` messages become
22
+ * `model` turns — text, then one `functionCall` part per tool call,
23
+ * carrying the call's thought signature back;
24
+ * - `tool` messages become `functionResponse` parts in a `user` turn.
25
+ * Gemini names the function it answers, so the name comes from the
26
+ * assistant tool call with the same id. The tool's output goes under
27
+ * `response.output` (parsed when it is JSON).
28
+ *
29
+ * Consecutive turns of the same role merge into one `Content`.
30
+ */
31
+ export function toGeminiRequest(messages) {
32
+ const systemParts = [];
33
+ const contents = [];
34
+ const callNames = new Map();
35
+ for (const msg of messages) {
36
+ if (msg.role === 'system') {
37
+ if (msg.content.length > 0)
38
+ systemParts.push(msg.content);
39
+ }
40
+ else if (msg.role === 'user') {
41
+ append(contents, 'user', [{ text: msg.content }]);
42
+ }
43
+ else if (msg.role === 'assistant') {
44
+ const parts = modelParts(msg, callNames);
45
+ // An empty model turn is invalid; the agent loop never produces one.
46
+ if (parts.length > 0)
47
+ append(contents, 'model', parts);
48
+ }
49
+ else {
50
+ append(contents, 'user', [functionResponsePart(msg, callNames)]);
51
+ }
52
+ }
53
+ return {
54
+ systemInstruction: systemParts.length > 0 ? systemParts.join('\n\n') : undefined,
55
+ contents,
56
+ };
57
+ }
58
+ /** An assistant message's parts: its text, then a `functionCall` per tool call. */
59
+ function modelParts(msg, callNames) {
60
+ const parts = msg.content.length > 0 ? [{ text: msg.content }] : [];
61
+ for (const call of msg.toolCalls ?? []) {
62
+ callNames.set(call.id, call.name);
63
+ parts.push({
64
+ functionCall: {
65
+ id: call.id,
66
+ name: checkFunctionName(call.name),
67
+ args: { ...call.arguments },
68
+ },
69
+ ...(call.signature !== undefined && { thoughtSignature: call.signature }),
70
+ });
71
+ }
72
+ return parts;
73
+ }
74
+ /** A tool result, named after the call it answers. */
75
+ function functionResponsePart(msg, callNames) {
76
+ const id = msg.toolCallId ?? '';
77
+ const name = callNames.get(id);
78
+ if (name === undefined) {
79
+ throw new Error(`@kindgi/adapter-model-gemini: a tool result answers call "${id}", which no earlier assistant turn made.`);
80
+ }
81
+ return { functionResponse: { id, name, response: { output: parseOutput(msg.content) } } };
82
+ }
83
+ function append(contents, role, parts) {
84
+ const last = contents[contents.length - 1];
85
+ if (last !== undefined && last.role === role) {
86
+ last.parts = [...(last.parts ?? []), ...parts];
87
+ return;
88
+ }
89
+ contents.push({ role, parts: [...parts] });
90
+ }
91
+ /** A tool's output: its JSON value when it is JSON, else the text. */
92
+ function parseOutput(content) {
93
+ try {
94
+ return JSON.parse(content);
95
+ }
96
+ catch {
97
+ return content;
98
+ }
99
+ }
100
+ /**
101
+ * Tool definitions as Gemini function declarations. The input schema is
102
+ * full JSON Schema, which `parametersJsonSchema` takes as it is.
103
+ */
104
+ export function toGeminiFunctions(tools) {
105
+ return tools.map((t) => ({
106
+ name: checkFunctionName(t.name),
107
+ description: t.description,
108
+ parametersJsonSchema: t.inputSchema,
109
+ }));
110
+ }
111
+ /**
112
+ * A Gemini response as the framework's assistant message: the text of
113
+ * the first candidate (thinking parts left out), and a tool call per
114
+ * `functionCall` part, with its thought signature. Gemini may omit the
115
+ * call id; one is generated then, so the tool result can refer to it.
116
+ */
117
+ export function fromGeminiResponse(response) {
118
+ const candidate = response.candidates?.[0];
119
+ if (candidate === undefined) {
120
+ // No candidate: the prompt itself was blocked.
121
+ const blocked = response.promptFeedback?.blockReason !== undefined;
122
+ return {
123
+ message: { role: 'assistant', content: '' },
124
+ finishReason: blocked ? 'content-filter' : 'error',
125
+ };
126
+ }
127
+ const parts = candidate.content?.parts ?? [];
128
+ const text = parts
129
+ .filter((part) => part.functionCall === undefined && part.thought !== true)
130
+ .map((part) => part.text ?? '');
131
+ const toolCalls = parts.flatMap((part) => part.functionCall !== undefined ? [toToolCall(part.functionCall, part.thoughtSignature)] : []);
132
+ return {
133
+ message: {
134
+ role: 'assistant',
135
+ content: text.join(''),
136
+ ...(toolCalls.length > 0 && { toolCalls }),
137
+ },
138
+ finishReason: toolCalls.length > 0 ? 'tool-use' : mapFinishReason(candidate.finishReason),
139
+ };
140
+ }
141
+ function toToolCall(call, signature) {
142
+ return {
143
+ id: call.id ?? `gemini-call-${randomUUID()}`,
144
+ name: call.name ?? '',
145
+ arguments: (call.args ?? {}),
146
+ ...(signature !== undefined && { signature }),
147
+ };
148
+ }
149
+ const FILTERED = new Set([
150
+ 'SAFETY',
151
+ 'RECITATION',
152
+ 'BLOCKLIST',
153
+ 'PROHIBITED_CONTENT',
154
+ 'SPII',
155
+ 'IMAGE_SAFETY',
156
+ 'IMAGE_PROHIBITED_CONTENT',
157
+ 'IMAGE_RECITATION',
158
+ ]);
159
+ /**
160
+ * Gemini's `finishReason` as the framework's:
161
+ * - `STOP`, unspecified or absent → `stop` (a response with function
162
+ * calls is `tool-use` before this is consulted);
163
+ * - `MAX_TOKENS` → `length`;
164
+ * - the safety, recitation, blocklist and sensitive-data stops →
165
+ * `content-filter`;
166
+ * - anything else (a malformed or unexpected function call, too many
167
+ * tool calls, an unsupported language, `OTHER`) → `error`.
168
+ */
169
+ export function mapFinishReason(reason) {
170
+ if (reason === undefined || reason === 'STOP' || reason === 'FINISH_REASON_UNSPECIFIED') {
171
+ return 'stop';
172
+ }
173
+ if (reason === 'MAX_TOKENS')
174
+ return 'length';
175
+ if (FILTERED.has(reason))
176
+ return 'content-filter';
177
+ return 'error';
178
+ }
179
+ //# sourceMappingURL=translate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.js","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,51 @@
1
1
  {
2
2
  "name": "@kindgi/adapter-model-gemini",
3
- "version": "0.0.0-bootstrap.0",
4
- "description": "Placeholder so a trusted publisher can be attached. Releases are published from https://github.com/kindgi/kindgi-sdk with provenance; use 0.1.0 or later.",
3
+ "version": "0.1.0",
4
+ "description": "Gemini ModelProvider for @kindgi/capabilities, on Vertex AI. Wraps @google/genai with the framework's provider-neutral interface: message-trail ↔ generateContent translation, function calling with thought signatures, token and cost accounting including thinking tokens, and Google Application Default Credentials (or a service-account key from the secret store). Non-streaming, tool-use enabled.",
5
5
  "license": "Apache-2.0",
6
- "repository": { "type": "git", "url": "git+https://github.com/kindgi/kindgi-sdk.git" }
7
- }
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/kindgi/kindgi-sdk.git",
9
+ "directory": "packages/adapters/model-gemini"
10
+ },
11
+ "homepage": "https://github.com/kindgi/kindgi-sdk/tree/main/packages/adapters/model-gemini#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/kindgi/kindgi-sdk/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "src",
27
+ "README.md"
28
+ ],
29
+ "dependencies": {
30
+ "@kindgi/capabilities": "0.1.0",
31
+ "@google/genai": "^2.24.0"
32
+ },
33
+ "engines": {
34
+ "node": ">=22.0.0"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "provenance": true
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.10.5",
42
+ "typescript": "^5.7.3",
43
+ "vitest": "^2.1.8"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc -p tsconfig.build.json",
47
+ "typecheck": "tsc --noEmit",
48
+ "test": "vitest run",
49
+ "clean": "rm -rf dist *.tsbuildinfo"
50
+ }
51
+ }
package/src/cost.ts ADDED
@@ -0,0 +1,73 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import type { GenerateContentResponseUsageMetadata } from '@google/genai';
5
+ import type { ModelInfo, UsageCounters } from '@kindgi/capabilities';
6
+
7
+ /**
8
+ * Cached prompt tokens bill at this fraction of the prompt rate unless a
9
+ * model says otherwise — Gemini 2.5's implicit-cache discount (75% off).
10
+ */
11
+ export const DEFAULT_CACHED_PROMPT_MULTIPLIER = 0.25;
12
+
13
+ /** A model's rates, per 1K tokens, with Gemini's two pricing extras. */
14
+ export interface GeminiCostRates {
15
+ readonly promptUsdPer1kTokens: number;
16
+ readonly completionUsdPer1kTokens: number;
17
+ /** Cached prompt tokens' share of the prompt rate. Default 0.25. */
18
+ readonly cachedPromptMultiplier?: number;
19
+ /**
20
+ * Long-context pricing: when a call's prompt exceeds `thresholdTokens`,
21
+ * the whole call bills at these rates (Gemini 2.5 Pro doubles above
22
+ * 200K prompt tokens, for example).
23
+ */
24
+ readonly longContext?: {
25
+ readonly thresholdTokens: number;
26
+ readonly promptUsdPer1kTokens: number;
27
+ readonly completionUsdPer1kTokens: number;
28
+ };
29
+ }
30
+
31
+ /** `ModelInfo` whose `cost` takes Gemini's cached-token and long-context rates. */
32
+ export interface GeminiModelInfo extends ModelInfo {
33
+ readonly cost: ModelInfo['cost'] & Omit<GeminiCostRates, keyof ModelInfo['cost']>;
34
+ }
35
+
36
+ /**
37
+ * Gemini's usage as the framework's counters. Thinking tokens bill as
38
+ * output, so they count as completion tokens; the prompt count includes
39
+ * cached tokens and the tokens of built-in tool prompts.
40
+ */
41
+ export function toFrameworkUsage(
42
+ usage: GenerateContentResponseUsageMetadata | undefined,
43
+ ): UsageCounters {
44
+ const cached = usage?.cachedContentTokenCount ?? 0;
45
+ return {
46
+ promptTokens: (usage?.promptTokenCount ?? 0) + (usage?.toolUsePromptTokenCount ?? 0),
47
+ completionTokens: (usage?.candidatesTokenCount ?? 0) + (usage?.thoughtsTokenCount ?? 0),
48
+ ...(cached > 0 && { cachedTokens: cached }),
49
+ };
50
+ }
51
+
52
+ /**
53
+ * A call's cost in USD: uncached prompt tokens at the prompt rate, cached
54
+ * ones at the cached share of it, completion (thinking included) at the
55
+ * completion rate — the long-context rates when the prompt is past the
56
+ * model's threshold.
57
+ */
58
+ export function computeCostUsd(usage: UsageCounters, rates: GeminiCostRates): number {
59
+ const long =
60
+ rates.longContext !== undefined && usage.promptTokens > rates.longContext.thresholdTokens
61
+ ? rates.longContext
62
+ : undefined;
63
+ const promptRate = long?.promptUsdPer1kTokens ?? rates.promptUsdPer1kTokens;
64
+ const completionRate = long?.completionUsdPer1kTokens ?? rates.completionUsdPer1kTokens;
65
+ const cached = usage.cachedTokens ?? 0;
66
+ const multiplier = rates.cachedPromptMultiplier ?? DEFAULT_CACHED_PROMPT_MULTIPLIER;
67
+ return (
68
+ ((usage.promptTokens - cached) * promptRate +
69
+ cached * promptRate * multiplier +
70
+ usage.completionTokens * completionRate) /
71
+ 1000
72
+ );
73
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ export {
5
+ GEMINI_ADAPTER_ID,
6
+ createGeminiProvider,
7
+ geminiAdapterFactory,
8
+ vertexTarget,
9
+ } from './provider.js';
10
+ export type { GeminiClient, GeminiProviderOptions } from './provider.js';
11
+ export { DEFAULT_CACHED_PROMPT_MULTIPLIER, computeCostUsd, toFrameworkUsage } from './cost.js';
12
+ export type { GeminiCostRates, GeminiModelInfo } from './cost.js';
13
+ export {
14
+ checkFunctionName,
15
+ fromGeminiResponse,
16
+ mapFinishReason,
17
+ toGeminiFunctions,
18
+ toGeminiRequest,
19
+ } from './translate.js';
20
+ export type { ModelProvider } from '@kindgi/capabilities';