ak-gemini 2.5.0 → 2.6.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/CHANGELOG.md +65 -0
- package/README.md +49 -4
- package/UPGRADING.md +111 -0
- package/base.js +160 -31
- package/chat.js +2 -0
- package/code-agent.js +2 -0
- package/image-generator.js +1 -0
- package/index.cjs +119 -26
- package/index.js +1 -1
- package/message.js +1 -0
- package/package.json +3 -2
- package/rag-agent.js +2 -0
- package/tool-agent.js +2 -0
- package/transformer.js +6 -2
- package/types.d.ts +76 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.6.0
|
|
4
|
+
|
|
5
|
+
> **Upgrading from 2.5.0?** [UPGRADING.md](./UPGRADING.md) walks through only what you
|
|
6
|
+
> need to change, with before/after examples.
|
|
7
|
+
|
|
8
|
+
Feature request from a downstream consumer (`smarterchild/agent`, Vertex AI),
|
|
9
|
+
paired with ak-claude 0.2.0. **Minor** bump — cached-token billing changes
|
|
10
|
+
reported cost (see "Behavior changes" below).
|
|
11
|
+
|
|
12
|
+
### Behavior changes (read before upgrading)
|
|
13
|
+
- **Cached tokens are now billed at the discounted cached rate.**
|
|
14
|
+
`usageMetadata.cachedContentTokenCount` is surfaced as `usage.cachedTokens` and
|
|
15
|
+
factored into `estimatedCost`. **Gemini INCLUDES cached tokens in
|
|
16
|
+
`promptTokenCount`**, so they are *subtracted* from the prompt count and
|
|
17
|
+
re-billed at `cachedInput` — they are a subset, not an addition. (ak-claude is
|
|
18
|
+
the opposite: Anthropic excludes cache tokens from `input_tokens` and they are
|
|
19
|
+
added on top. The asymmetry is intentional; do not "unify" it.) Reported cost
|
|
20
|
+
for cache-heavy calls drops by up to ~10×. Models with no published cached rate
|
|
21
|
+
bill cached tokens at the full input rate — an overestimate, never an
|
|
22
|
+
underestimate.
|
|
23
|
+
- **Pro models switch to their >200k-context tier above 200,000 prompt tokens.**
|
|
24
|
+
`gemini-2.5-pro` and `gemini-3.1-pro-preview` previously billed every call at
|
|
25
|
+
the ≤200k rate. Long-context calls now report roughly 2× the input cost they
|
|
26
|
+
did before, which is what Google actually charges.
|
|
27
|
+
- **`gemini-2.5-flash` output pricing corrected from $2.50 to $3.00 per M.**
|
|
28
|
+
Reported cost for that model rises accordingly.
|
|
29
|
+
- **`gemini-flash-latest` now resolves to `gemini-3.6-flash`** (was
|
|
30
|
+
`gemini-3.5-flash`) and **`gemini-flash-lite-latest` to `gemini-3.5-flash-lite`**
|
|
31
|
+
for pricing. Alias resolution is logged at debug — prefer the model id the API
|
|
32
|
+
echoes in `modelVersion`, which `_estimatedCost` already does.
|
|
33
|
+
- **`getLastUsage()` now reports non-zero `thoughtsTokens` on `Chat`,
|
|
34
|
+
`ToolAgent`, `CodeAgent`, `RagAgent`, and `Transformer`.** These classes seeded
|
|
35
|
+
`_cumulativeUsage` without the thinking-token count, so `getLastUsage()`
|
|
36
|
+
reported `0` (and undercounted cost) even when the response carried thoughts.
|
|
37
|
+
`result.usage` was already correct.
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
- **`effort: 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'`** on every
|
|
41
|
+
class — the cross-package option name (identical to ak-claude). Maps to
|
|
42
|
+
`thinkingConfig.thinkingLevel` and wins over an explicit `thinkingLevel` /
|
|
43
|
+
`thinkingBudget` (the two are mutually exclusive on the wire). `xhigh` and
|
|
44
|
+
`max` are ak-claude-only levels and clamp to `high`. An unrecognized level
|
|
45
|
+
throws at construction.
|
|
46
|
+
- **`usage.cachedTokens`** on every `send()`/`chat()`/`generate()` result and on
|
|
47
|
+
`getLastUsage()`.
|
|
48
|
+
- **Pricing for new models:** `gemini-3.6-flash` ($1.50/$7.50, cached $0.15) and
|
|
49
|
+
`gemini-3.5-flash-lite` ($0.30/$2.50, cached $0.03).
|
|
50
|
+
- **`cachedInput` rates** on every model where Google publishes one, plus
|
|
51
|
+
`inputAbove200k` / `outputAbove200k` / `cachedInputAbove200k` on the tiered Pro
|
|
52
|
+
models.
|
|
53
|
+
- **New exports:** `MODEL_PRICING_AS_OF` (the date the table was last verified
|
|
54
|
+
against Google's pricing page) and `EFFORT_LEVELS`. `resolvePricing()` now
|
|
55
|
+
returns `asOf` and `tierThreshold`.
|
|
56
|
+
- `computeCost()` takes two new optional trailing args:
|
|
57
|
+
`computeCost(modelId, prompt, response, thoughtsTokens?, cachedTokens?)`.
|
|
58
|
+
|
|
59
|
+
### Notes
|
|
60
|
+
- `null` from `resolvePricing()` / `usage.estimatedCost` means the model's
|
|
61
|
+
pricing is **unknown** — it does not mean the call was free.
|
|
62
|
+
- Cache **storage** ($1.00/1M tokens/hour on Flash tiers, $4.50 on Pro) is
|
|
63
|
+
time-based and is **not** modelled by `estimatedCost` — only per-token charges
|
|
64
|
+
are.
|
|
65
|
+
- Image-output tokens on Nano Banana models ($60/M or $120/M) are still not
|
|
66
|
+
modelled.
|
|
67
|
+
|
|
3
68
|
## 2.5.0
|
|
4
69
|
|
|
5
70
|
Fixes from a downstream consumer's adversarial review. This is a **minor** bump —
|
package/README.md
CHANGED
|
@@ -285,9 +285,29 @@ const cost = await instance.estimateCost({ some: 'payload' });
|
|
|
285
285
|
|
|
286
286
|
```javascript
|
|
287
287
|
const usage = instance.getLastUsage();
|
|
288
|
-
// { promptTokens, responseTokens,
|
|
288
|
+
// { promptTokens, responseTokens, thoughtsTokens, cachedTokens, totalTokens,
|
|
289
|
+
// attempts, modelVersion, requestedModel, timestamp, estimatedCost }
|
|
289
290
|
```
|
|
290
291
|
|
|
292
|
+
`estimatedCost` is USD from `MODEL_PRICING`. **`null` means the model's pricing is
|
|
293
|
+
unknown — not that the call was free.**
|
|
294
|
+
|
|
295
|
+
**`cachedTokens` is a subset of `promptTokens`, not an addition.** Gemini includes
|
|
296
|
+
`cachedContentTokenCount` inside `promptTokenCount`, so `computeCost()` subtracts
|
|
297
|
+
them and re-bills at the discounted `cachedInput` rate. (`ak-claude` is the
|
|
298
|
+
opposite — Anthropic excludes cache tokens from `input_tokens` and they are added
|
|
299
|
+
on top.) Models with no published cached rate bill cached tokens at the full input
|
|
300
|
+
rate: an overestimate, never an underestimate.
|
|
301
|
+
|
|
302
|
+
Two things are deliberately **not** modelled: cache **storage** (time-based,
|
|
303
|
+
$1.00/1M tokens/hour on Flash tiers and $4.50 on Pro) and image-output tokens on
|
|
304
|
+
the Nano Banana models ($60/M or $120/M).
|
|
305
|
+
|
|
306
|
+
Pro models are tiered — `gemini-2.5-pro` and `gemini-3.1-pro-preview` switch to a
|
|
307
|
+
higher rate above 200,000 prompt tokens. `resolvePricing()` returns `tierThreshold`
|
|
308
|
+
and `asOf` (the date the table was last verified against Google's pricing page,
|
|
309
|
+
also exported as `MODEL_PRICING_AS_OF`).
|
|
310
|
+
|
|
291
311
|
### Few-Shot Seeding
|
|
292
312
|
|
|
293
313
|
```javascript
|
|
@@ -298,13 +318,30 @@ await instance.seed([
|
|
|
298
318
|
|
|
299
319
|
### Thinking Configuration
|
|
300
320
|
|
|
321
|
+
Use `effort` — it is the same option name in `ak-claude`, so one config object
|
|
322
|
+
works against both packages:
|
|
323
|
+
|
|
301
324
|
```javascript
|
|
302
325
|
new Chat({
|
|
303
|
-
modelName: 'gemini-
|
|
304
|
-
|
|
326
|
+
modelName: 'gemini-3.5-flash',
|
|
327
|
+
effort: 'high' // 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'
|
|
305
328
|
});
|
|
329
|
+
// wire: thinkingConfig: { thinkingLevel: 'high' }
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`xhigh` and `max` are ak-claude-only levels and clamp to `high` here. An
|
|
333
|
+
unrecognized level throws at construction.
|
|
334
|
+
|
|
335
|
+
The raw SDK shape still works:
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
new Chat({ modelName: 'gemini-2.5-flash', thinkingConfig: { thinkingBudget: 1024 } });
|
|
306
339
|
```
|
|
307
340
|
|
|
341
|
+
`thinkingLevel` and `thinkingBudget` are mutually exclusive on the wire — when
|
|
342
|
+
`effort` is set (or you pass `thinkingLevel` yourself), `thinkingBudget` is
|
|
343
|
+
dropped. `effort` wins over both.
|
|
344
|
+
|
|
308
345
|
### Google Search Grounding
|
|
309
346
|
|
|
310
347
|
Ground responses in real-time web search results. Available on all classes.
|
|
@@ -390,7 +427,8 @@ All classes accept `BaseGeminiOptions`:
|
|
|
390
427
|
| `project` | string | env var | GCP project ID |
|
|
391
428
|
| `location` | string | `'global'` | GCP region |
|
|
392
429
|
| `chatConfig` | object | — | Gemini chat config overrides |
|
|
393
|
-
| `thinkingConfig` | object | — | Thinking features config |
|
|
430
|
+
| `thinkingConfig` | object | — | Thinking features config (`thinkingBudget` or `thinkingLevel`) |
|
|
431
|
+
| `effort` | string | — | `'minimal'`\|`'low'`\|`'medium'`\|`'high'`\|`'xhigh'`\|`'max'`. Maps to `thinkingConfig.thinkingLevel`; wins over it |
|
|
394
432
|
| `maxOutputTokens` | number | `50000` | Max tokens in response (`null` removes limit) |
|
|
395
433
|
| `logLevel` | string | based on NODE_ENV | `'trace'`\|`'debug'`\|`'info'`\|`'warn'`\|`'error'`\|`'none'` |
|
|
396
434
|
| `labels` | object | — | Billing labels (Vertex AI) |
|
|
@@ -478,6 +516,13 @@ All tests use real Gemini API calls (no mocks). Rate limiting (429 errors) can c
|
|
|
478
516
|
|
|
479
517
|
---
|
|
480
518
|
|
|
519
|
+
## Upgrading
|
|
520
|
+
|
|
521
|
+
**Upgrading from 2.5.0 to 2.6.0?** See [UPGRADING.md](./UPGRADING.md). 2.6.0 adds the
|
|
522
|
+
`effort` option and corrects cost reporting — cached tokens now bill at the discounted
|
|
523
|
+
rate, Pro models are tiered above 200k, and `thoughtsTokens` is no longer dropped from
|
|
524
|
+
`getLastUsage()`. Full detail in [CHANGELOG.md](./CHANGELOG.md).
|
|
525
|
+
|
|
481
526
|
## Migration from v1.x
|
|
482
527
|
|
|
483
528
|
See [MIGRATION.md](./MIGRATION.md) for a detailed guide on upgrading from v1.x to v2.0.
|
package/UPGRADING.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Upgrading ak-gemini
|
|
2
|
+
|
|
3
|
+
Per-version upgrade notes. For the full list of changes see [CHANGELOG.md](./CHANGELOG.md). For the v1.x → v2.0 rewrite, see [MIGRATION.md](./MIGRATION.md).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 2.5.0 → 2.6.0
|
|
8
|
+
|
|
9
|
+
**Minor bump. No API surface is removed and no call signature breaks.** What changes is what `estimatedCost` reports — in most cases it goes *down*, because cached tokens and context tiers are now modelled correctly.
|
|
10
|
+
|
|
11
|
+
Companion release to **ak-claude 0.2.0**; the two packages keep matching option names.
|
|
12
|
+
|
|
13
|
+
### At a glance
|
|
14
|
+
|
|
15
|
+
| If you… | Then… |
|
|
16
|
+
|---|---|
|
|
17
|
+
| don't use context caching and stay under 200k tokens | numbers are unchanged, except `gemini-2.5-flash` — see #3 |
|
|
18
|
+
| use context caching (`cachedContent` / `useCache()`) | your reported cost drops — cached tokens now bill at the discounted rate, see #1 |
|
|
19
|
+
| send >200k-token prompts to a Pro model | your reported cost rises to the correct tier rate — see #2 |
|
|
20
|
+
| use `gemini-2.5-flash` | output rate corrected $2.50 → $3.00 per M — see #3 |
|
|
21
|
+
| use `gemini-flash-latest` or `gemini-flash-lite-latest` | these aliases now resolve to newer models — see #4 |
|
|
22
|
+
| read `getLastUsage()` on Chat / ToolAgent / CodeAgent / RagAgent / Transformer | `thoughtsTokens` is now non-zero when thinking ran — see #5 |
|
|
23
|
+
| want to control reasoning depth | new `effort` option — see #6 |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
### 1. Cached tokens are billed at the cached rate
|
|
28
|
+
|
|
29
|
+
**This is the change most likely to move your numbers.**
|
|
30
|
+
|
|
31
|
+
Gemini's `promptTokenCount` **includes** `cachedContentTokenCount`. Before 2.6.0, ak-gemini billed the whole prompt count at the full input rate, so cached tokens were charged as if they were fresh — a large overestimate on cache-heavy workloads.
|
|
32
|
+
|
|
33
|
+
`computeCost()` now subtracts cached tokens from the prompt count and re-bills them at `cachedInput` (roughly a 10× discount):
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
// promptTokens: 10_000, of which cachedTokens: 8_000, on gemini-3.5-flash
|
|
37
|
+
// 2.5.0: 10_000 × $1.50/M = $0.0150
|
|
38
|
+
// 2.6.0: 2_000 × $1.50/M + 8_000 × $0.15/M = $0.0042
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`usage.cachedTokens` is now exposed on both `getLastUsage()` and per-call `result.usage`.
|
|
42
|
+
|
|
43
|
+
> **`cachedTokens` is a SUBSET of `promptTokens`, not an addition.** Do not sum them. This is the opposite of **ak-claude**, where Anthropic *excludes* cache tokens from `input_tokens` and they are added on top. If you have code that normalizes usage across both packages, this asymmetry is the thing to get right.
|
|
44
|
+
|
|
45
|
+
Models with no published `cachedInput` rate fall back to the full input rate — an overestimate, never an underestimate.
|
|
46
|
+
|
|
47
|
+
**Still not modelled:** cache *storage*, which is time-based ($1.00 per 1M tokens per hour on Flash tiers, $4.50 on Pro). `estimatedCost` covers per-token charges only.
|
|
48
|
+
|
|
49
|
+
### 2. Context-tier pricing above 200k tokens
|
|
50
|
+
|
|
51
|
+
Pro models switch to a higher rate above 200,000 prompt tokens. Previously ak-gemini always billed the ≤200k tier, understating cost on long-context calls.
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
resolvePricing('gemini-2.5-pro')
|
|
55
|
+
// { input: 1.25, output: 10.00, cachedInput: 0.125,
|
|
56
|
+
// inputAbove200k: 2.50, outputAbove200k: 15.00, cachedInputAbove200k: 0.25,
|
|
57
|
+
// tierThreshold: 200000, asOf: '2026-07-28' }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`computeCost()` picks the tier from `promptTokens` automatically. Populated for `gemini-2.5-pro` and `gemini-3.1-pro-preview`.
|
|
61
|
+
|
|
62
|
+
### 3. `gemini-2.5-flash` output rate corrected
|
|
63
|
+
|
|
64
|
+
$2.50 → **$3.00** per M output tokens. The old value was wrong against Google's published pricing. If you track cost over time, expect a step change here.
|
|
65
|
+
|
|
66
|
+
### 4. Alias repointing
|
|
67
|
+
|
|
68
|
+
| Alias | 2.5.0 resolved to | 2.6.0 resolves to |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `gemini-flash-latest` | `gemini-3.5-flash` | `gemini-3.6-flash` |
|
|
71
|
+
| `gemini-flash-lite-latest` | `gemini-3.1-flash-lite` | `gemini-3.5-flash-lite` |
|
|
72
|
+
|
|
73
|
+
Aliases are a pricing-table convenience and go stale whenever Google rotates what they point at. ak-gemini already prefers the model id the API **echoes** (`response.modelVersion`) over the alias you requested, and now logs each alias resolution at debug level. Pin a concrete model id if you need cost reporting you can audit.
|
|
74
|
+
|
|
75
|
+
New pricing entries: `gemini-3.6-flash` ($1.50 / $7.50) and `gemini-3.5-flash-lite` ($0.30 / $2.50).
|
|
76
|
+
|
|
77
|
+
### 5. `thoughtsTokens` now appears in `getLastUsage()`
|
|
78
|
+
|
|
79
|
+
`Chat`, `ToolAgent`, `CodeAgent`, `RagAgent`, and `Transformer` seeded their cumulative usage without `thoughtsTokens`, so `getLastUsage()` reported `0` and **omitted thinking tokens from `estimatedCost`** even when the response carried them. Per-call `result.usage` was already correct.
|
|
80
|
+
|
|
81
|
+
If you read cost from `getLastUsage()` on a thinking-enabled model, your reported cost will rise — it was under-reporting before. Thinking tokens bill at the output rate.
|
|
82
|
+
|
|
83
|
+
### 6. New `effort` option
|
|
84
|
+
|
|
85
|
+
The cross-package reasoning-depth knob — same option name in ak-claude.
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
new Chat({ modelName: 'gemini-3.5-flash', effort: 'high' })
|
|
89
|
+
// → thinkingConfig: { thinkingLevel: 'high' }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Accepts `'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'`. Gemini's wire field only goes up to `high`, so **`xhigh` and `max` clamp to `high`** — they exist so the same config object works against both packages. An unrecognized value **throws at construction**.
|
|
93
|
+
|
|
94
|
+
`effort` wins over an explicit `thinkingLevel` or `thinkingBudget`; `thinkingLevel` and `thinkingBudget` are mutually exclusive on the wire, so setting `effort` deletes any budget. `thinkingConfig: null` still disables thinking outright and takes precedence over `effort`.
|
|
95
|
+
|
|
96
|
+
`thinkingConfig: { thinkingBudget: N }` continues to work unchanged.
|
|
97
|
+
|
|
98
|
+
### New exports
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
import {
|
|
102
|
+
MODEL_PRICING_AS_OF, // date the pricing table was last verified
|
|
103
|
+
EFFORT_LEVELS, // ['minimal','low','medium','high','xhigh','max']
|
|
104
|
+
resolvePricing, // now returns `tierThreshold` and `asOf`
|
|
105
|
+
computeCost // now takes (model, prompt, response, thoughts?, cached?)
|
|
106
|
+
} from 'ak-gemini';
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`computeCost()`'s new `cachedTokens` argument is optional and trailing — existing 3- and 4-argument calls behave as before, except for the tier selection in #2.
|
|
110
|
+
|
|
111
|
+
> **A `null` from `resolvePricing()` or `estimatedCost` means UNKNOWN, not free.** Unpriced models (Gemma, anything not in the table) return `null`. Don't coerce it to `0` in a cost rollup.
|
package/base.js
CHANGED
|
@@ -25,6 +25,22 @@ const DEFAULT_THINKING_CONFIG = {
|
|
|
25
25
|
|
|
26
26
|
const DEFAULT_MAX_OUTPUT_TOKENS = 50_000;
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Effort levels accepted by the cross-package `effort` option.
|
|
30
|
+
* Gemini's wire field is `thinkingConfig.thinkingLevel`, which accepts
|
|
31
|
+
* minimal/low/medium/high. `xhigh` and `max` exist on ak-claude only and are
|
|
32
|
+
* clamped to `high` so the same config object works against both packages.
|
|
33
|
+
*/
|
|
34
|
+
const EFFORT_LEVELS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
35
|
+
const EFFORT_TO_THINKING_LEVEL = {
|
|
36
|
+
minimal: 'minimal',
|
|
37
|
+
low: 'low',
|
|
38
|
+
medium: 'medium',
|
|
39
|
+
high: 'high',
|
|
40
|
+
xhigh: 'high',
|
|
41
|
+
max: 'high'
|
|
42
|
+
};
|
|
43
|
+
|
|
28
44
|
/** Models that support thinking features. Image / live / tts variants intentionally excluded. */
|
|
29
45
|
const THINKING_SUPPORTED_MODELS = [
|
|
30
46
|
/^gemini-3(\.\d+)?-pro(-preview)?$/,
|
|
@@ -36,33 +52,56 @@ const THINKING_SUPPORTED_MODELS = [
|
|
|
36
52
|
/^gemini-2\.0-flash$/
|
|
37
53
|
];
|
|
38
54
|
|
|
55
|
+
/** Date the pricing table below was last verified against Google's pricing page. */
|
|
56
|
+
const MODEL_PRICING_AS_OF = '2026-07-28';
|
|
57
|
+
|
|
58
|
+
/** Context-window size at which tiered models switch to their higher rate. */
|
|
59
|
+
const TIER_THRESHOLD = 200_000;
|
|
60
|
+
|
|
39
61
|
/**
|
|
40
|
-
* Model pricing per million tokens (Paid Tier Standard, base rate, as of
|
|
62
|
+
* Model pricing per million tokens (Paid Tier Standard, base rate, as of MODEL_PRICING_AS_OF).
|
|
41
63
|
* Source: https://ai.google.dev/gemini-api/docs/pricing
|
|
42
64
|
*
|
|
65
|
+
* Fields:
|
|
66
|
+
* - `input` / `output` — standard per-1M rates (≤200k tier where a model is tiered).
|
|
67
|
+
* - `cachedInput` — per-1M rate for tokens served from a context cache. Omitted when
|
|
68
|
+
* Google does not publish one; computeCost() then falls back to the full `input`
|
|
69
|
+
* rate, which OVERSTATES rather than understates cost.
|
|
70
|
+
* - `inputAbove200k` / `outputAbove200k` / `cachedInputAbove200k` — >200k context tier.
|
|
71
|
+
*
|
|
43
72
|
* NOTES:
|
|
44
|
-
* - Pro models use tiered pricing (≤200k vs >200k context). Listed rate is ≤200k base tier.
|
|
45
73
|
* - Gemma models (e.g. Gemma 4) are intentionally excluded — they are open models with
|
|
46
74
|
* no paid per-token tier on the Gemini API (Vertex deployments bill by compute).
|
|
47
75
|
* - Image-output tokens on Nano Banana models bill at $60/M (1.5 Flash Image) or $120/M (3 Pro Image).
|
|
48
76
|
* Only text-input/text-output rates are modelled here; image-output cost is NOT included in estimateCost().
|
|
49
77
|
* - Audio input is more expensive on most models — listed rate covers text/image/video input.
|
|
78
|
+
* - Cache STORAGE ($1.00/1M tokens/hour on Flash tiers, $4.50 on Pro) is time-based and
|
|
79
|
+
* NOT modelled here — estimatedCost only covers per-token charges.
|
|
80
|
+
* - A null result from resolvePricing()/computeCost() means UNKNOWN, not free.
|
|
50
81
|
*/
|
|
51
82
|
const MODEL_PRICING = {
|
|
52
83
|
// Gemini 3.x stable
|
|
53
|
-
'gemini-3.
|
|
54
|
-
'gemini-3.
|
|
84
|
+
'gemini-3.6-flash': { input: 1.50, output: 7.50, cachedInput: 0.15 },
|
|
85
|
+
'gemini-3.5-flash': { input: 1.50, output: 9.00, cachedInput: 0.15 },
|
|
86
|
+
'gemini-3.5-flash-lite': { input: 0.30, output: 2.50, cachedInput: 0.03 },
|
|
87
|
+
'gemini-3.1-flash-lite': { input: 0.25, output: 1.50, cachedInput: 0.025 },
|
|
55
88
|
// Gemini 3.x preview
|
|
56
|
-
'gemini-3.1-pro-preview': {
|
|
89
|
+
'gemini-3.1-pro-preview': {
|
|
90
|
+
input: 2.00, output: 12.00, cachedInput: 0.20,
|
|
91
|
+
inputAbove200k: 4.00, outputAbove200k: 18.00, cachedInputAbove200k: 0.40
|
|
92
|
+
},
|
|
57
93
|
'gemini-3-pro-preview': { input: 2.00, output: 12.00 }, // ≤200k tier; launch rate (superseded by 3.1, off the pricing page)
|
|
58
94
|
'gemini-3-flash-preview': { input: 0.50, output: 3.00 },
|
|
59
|
-
'gemini-3.1-flash-lite-preview': { input: 0.25, output: 1.50 },
|
|
95
|
+
'gemini-3.1-flash-lite-preview': { input: 0.25, output: 1.50, cachedInput: 0.025 },
|
|
60
96
|
'gemini-3.1-flash-image-preview': { input: 0.50, output: 3.00 }, // text-only; image-output is $60/M
|
|
61
97
|
'gemini-3-pro-image-preview': { input: 2.00, output: 12.00 }, // text-only; image-output is $120/M
|
|
62
98
|
// Gemini 2.5 stable
|
|
63
|
-
'gemini-2.5-flash': { input: 0.30, output:
|
|
64
|
-
'gemini-2.5-flash-lite': { input: 0.10, output: 0.40 },
|
|
65
|
-
'gemini-2.5-pro': {
|
|
99
|
+
'gemini-2.5-flash': { input: 0.30, output: 3.00, cachedInput: 0.05 },
|
|
100
|
+
'gemini-2.5-flash-lite': { input: 0.10, output: 0.40, cachedInput: 0.01 },
|
|
101
|
+
'gemini-2.5-pro': {
|
|
102
|
+
input: 1.25, output: 10.00, cachedInput: 0.125,
|
|
103
|
+
inputAbove200k: 2.50, outputAbove200k: 15.00, cachedInputAbove200k: 0.25
|
|
104
|
+
},
|
|
66
105
|
'gemini-2.5-flash-image': { input: 0.30, output: 0 }, // image-output is ~$0.039/image (1290 tokens)
|
|
67
106
|
// Deprecated but kept for back-compat (shut down June 2026)
|
|
68
107
|
'gemini-2.0-flash': { input: 0.10, output: 0.40 },
|
|
@@ -76,25 +115,44 @@ const MODEL_PRICING = {
|
|
|
76
115
|
* Google publishes floating `-latest` aliases that resolve server-side to a
|
|
77
116
|
* concrete model; they never appear in MODEL_PRICING directly. Map them to the
|
|
78
117
|
* canonical id whose rate they currently bill at so estimatedCost can resolve.
|
|
79
|
-
*
|
|
118
|
+
*
|
|
119
|
+
* These go stale whenever Google rotates an alias. Prefer the model id the API
|
|
120
|
+
* ECHOES (`response.modelVersion`) over the alias you requested — `_estimatedCost`
|
|
121
|
+
* already does this, and alias resolution is logged at debug level.
|
|
80
122
|
*/
|
|
81
123
|
const MODEL_ALIASES = {
|
|
82
|
-
'gemini-flash-latest': 'gemini-3.
|
|
124
|
+
'gemini-flash-latest': 'gemini-3.6-flash',
|
|
83
125
|
'gemini-pro-latest': 'gemini-3.1-pro-preview',
|
|
84
|
-
'gemini-flash-lite-latest': 'gemini-3.
|
|
126
|
+
'gemini-flash-lite-latest': 'gemini-3.5-flash-lite'
|
|
85
127
|
};
|
|
86
128
|
|
|
87
129
|
/**
|
|
88
130
|
* Resolves pricing for a model id, following `-latest` aliases.
|
|
131
|
+
* A null result means pricing is UNKNOWN, not free.
|
|
89
132
|
* @param {string|null|undefined} modelId
|
|
90
|
-
* @returns {{ input: number, output: number
|
|
133
|
+
* @returns {{ input: number, output: number, cachedInput?: number, inputAbove200k?: number, outputAbove200k?: number, cachedInputAbove200k?: number, tierThreshold: number, asOf: string }|null}
|
|
91
134
|
*/
|
|
92
135
|
function resolvePricing(modelId) {
|
|
136
|
+
const entry = _lookupPricing(modelId);
|
|
137
|
+
if (!entry) return null;
|
|
138
|
+
return { ...entry, tierThreshold: TIER_THRESHOLD, asOf: MODEL_PRICING_AS_OF };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Raw table lookup with alias + pinned-build fallback. No asOf/tier decoration.
|
|
143
|
+
* @param {string|null|undefined} modelId
|
|
144
|
+
* @returns {any|null}
|
|
145
|
+
* @private
|
|
146
|
+
*/
|
|
147
|
+
function _lookupPricing(modelId) {
|
|
93
148
|
if (!modelId) return null;
|
|
94
149
|
const tryKey = (id) => {
|
|
95
150
|
if (MODEL_PRICING[id]) return MODEL_PRICING[id];
|
|
96
151
|
const alias = MODEL_ALIASES[id];
|
|
97
|
-
if (alias && MODEL_PRICING[alias])
|
|
152
|
+
if (alias && MODEL_PRICING[alias]) {
|
|
153
|
+
log.debug(`Pricing: alias "${id}" resolved to "${alias}". If Google has rotated this alias the rate may be wrong — prefer the echoed modelVersion.`);
|
|
154
|
+
return MODEL_PRICING[alias];
|
|
155
|
+
}
|
|
98
156
|
return null;
|
|
99
157
|
};
|
|
100
158
|
let hit = tryKey(modelId);
|
|
@@ -116,20 +174,44 @@ function resolvePricing(modelId) {
|
|
|
116
174
|
/**
|
|
117
175
|
* Computes estimated USD cost from token counts using MODEL_PRICING.
|
|
118
176
|
* Thinking ("thoughts") tokens are billed at the output rate.
|
|
177
|
+
*
|
|
178
|
+
* IMPORTANT: Gemini's `promptTokenCount` INCLUDES `cachedContentTokenCount`, so
|
|
179
|
+
* cached tokens are SUBTRACTED from promptTokens and re-billed at the discounted
|
|
180
|
+
* cached rate. (ak-claude is the opposite — Anthropic's `input_tokens` excludes
|
|
181
|
+
* cache tokens and they are added on top. Do not "unify" these.)
|
|
182
|
+
*
|
|
183
|
+
* When a model has no published `cachedInput` rate, cached tokens bill at the full
|
|
184
|
+
* input rate — an overestimate, never an underestimate.
|
|
185
|
+
*
|
|
186
|
+
* Cache STORAGE (per token-hour) is not modelled.
|
|
187
|
+
*
|
|
119
188
|
* @param {string|null|undefined} modelId
|
|
120
|
-
* @param {number} promptTokens
|
|
189
|
+
* @param {number} promptTokens - Total prompt tokens as reported by the API (INCLUDES cached tokens)
|
|
121
190
|
* @param {number} responseTokens
|
|
122
191
|
* @param {number} [thoughtsTokens=0] - Thinking tokens (billed at output rate)
|
|
123
|
-
* @
|
|
192
|
+
* @param {number} [cachedTokens=0] - Tokens served from a context cache (subset of promptTokens)
|
|
193
|
+
* @returns {number|null} Cost in USD, or null when pricing is UNKNOWN (not free).
|
|
124
194
|
*/
|
|
125
|
-
function computeCost(modelId, promptTokens, responseTokens, thoughtsTokens = 0) {
|
|
195
|
+
function computeCost(modelId, promptTokens, responseTokens, thoughtsTokens = 0, cachedTokens = 0) {
|
|
126
196
|
const pricing = resolvePricing(modelId);
|
|
127
197
|
if (!pricing) return null;
|
|
128
|
-
|
|
129
|
-
|
|
198
|
+
|
|
199
|
+
const above = (promptTokens || 0) > TIER_THRESHOLD;
|
|
200
|
+
const inputRate = (above && pricing.inputAbove200k) || pricing.input;
|
|
201
|
+
const outputRate = (above && pricing.outputAbove200k) || pricing.output;
|
|
202
|
+
const cachedRate = above
|
|
203
|
+
? (pricing.cachedInputAbove200k ?? pricing.cachedInput ?? inputRate)
|
|
204
|
+
: (pricing.cachedInput ?? inputRate);
|
|
205
|
+
|
|
206
|
+
const cached = Math.max(0, Math.min(cachedTokens || 0, promptTokens || 0));
|
|
207
|
+
const billablePrompt = Math.max(0, (promptTokens || 0) - cached);
|
|
208
|
+
|
|
209
|
+
return (billablePrompt / 1_000_000) * inputRate
|
|
210
|
+
+ (cached / 1_000_000) * cachedRate
|
|
211
|
+
+ (((responseTokens || 0) + (thoughtsTokens || 0)) / 1_000_000) * outputRate;
|
|
130
212
|
}
|
|
131
213
|
|
|
132
|
-
export { DEFAULT_SAFETY_SETTINGS, DEFAULT_THINKING_CONFIG, THINKING_SUPPORTED_MODELS, MODEL_PRICING, MODEL_ALIASES, DEFAULT_MAX_OUTPUT_TOKENS, resolvePricing, computeCost };
|
|
214
|
+
export { DEFAULT_SAFETY_SETTINGS, DEFAULT_THINKING_CONFIG, THINKING_SUPPORTED_MODELS, MODEL_PRICING, MODEL_PRICING_AS_OF, MODEL_ALIASES, DEFAULT_MAX_OUTPUT_TOKENS, EFFORT_LEVELS, resolvePricing, computeCost };
|
|
133
215
|
|
|
134
216
|
// ── BaseGemini Class ─────────────────────────────────────────────────────────
|
|
135
217
|
|
|
@@ -251,6 +333,9 @@ class BaseGemini {
|
|
|
251
333
|
}
|
|
252
334
|
|
|
253
335
|
// ── Thinking Config ──
|
|
336
|
+
// `effort` is the cross-package option (same name in ak-claude); it maps to
|
|
337
|
+
// Gemini's `thinkingConfig.thinkingLevel`.
|
|
338
|
+
this.effort = this._normalizeEffort(options.effort);
|
|
254
339
|
this._configureThinking(options.thinkingConfig);
|
|
255
340
|
|
|
256
341
|
// ── GenAI Client ──
|
|
@@ -272,6 +357,8 @@ class BaseGemini {
|
|
|
272
357
|
this._cumulativeUsage = {
|
|
273
358
|
promptTokens: 0,
|
|
274
359
|
responseTokens: 0,
|
|
360
|
+
thoughtsTokens: 0,
|
|
361
|
+
cachedTokens: 0,
|
|
275
362
|
totalTokens: 0,
|
|
276
363
|
attempts: 0
|
|
277
364
|
};
|
|
@@ -384,7 +471,7 @@ class BaseGemini {
|
|
|
384
471
|
}
|
|
385
472
|
this.chatSession = this._createChatSession([]);
|
|
386
473
|
this.lastResponseMetadata = null;
|
|
387
|
-
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, totalTokens: 0, attempts: 0 };
|
|
474
|
+
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, thoughtsTokens: 0, cachedTokens: 0, totalTokens: 0, attempts: 0 };
|
|
388
475
|
log.debug(`${this.constructor.name}: Conversation history cleared.`);
|
|
389
476
|
}
|
|
390
477
|
|
|
@@ -487,12 +574,15 @@ class BaseGemini {
|
|
|
487
574
|
const promptTokens = response.usageMetadata?.promptTokenCount || 0;
|
|
488
575
|
const responseTokens = response.usageMetadata?.candidatesTokenCount || 0;
|
|
489
576
|
const thoughtsTokens = response.usageMetadata?.thoughtsTokenCount || 0;
|
|
577
|
+
// NB: promptTokenCount INCLUDES cachedContentTokenCount — see computeCost().
|
|
578
|
+
const cachedTokens = response.usageMetadata?.cachedContentTokenCount || 0;
|
|
490
579
|
this.lastResponseMetadata = {
|
|
491
580
|
modelVersion: response.modelVersion || null,
|
|
492
581
|
requestedModel: this.modelName,
|
|
493
582
|
promptTokens,
|
|
494
583
|
responseTokens,
|
|
495
584
|
thoughtsTokens,
|
|
585
|
+
cachedTokens,
|
|
496
586
|
// totalTokenCount includes thoughts; fall back to summing the parts.
|
|
497
587
|
totalTokens: response.usageMetadata?.totalTokenCount || (promptTokens + responseTokens + thoughtsTokens),
|
|
498
588
|
timestamp: Date.now(),
|
|
@@ -514,18 +604,24 @@ class BaseGemini {
|
|
|
514
604
|
if (!this.lastResponseMetadata) return null;
|
|
515
605
|
|
|
516
606
|
const meta = this.lastResponseMetadata;
|
|
517
|
-
const cumulative = this._cumulativeUsage || { promptTokens: 0, responseTokens: 0, totalTokens: 0, thoughtsTokens: 0, attempts: 1 };
|
|
607
|
+
const cumulative = this._cumulativeUsage || { promptTokens: 0, responseTokens: 0, totalTokens: 0, thoughtsTokens: 0, cachedTokens: 0, attempts: 1 };
|
|
518
608
|
const useCumulative = cumulative.attempts > 0;
|
|
519
609
|
|
|
520
610
|
const promptTokens = useCumulative ? cumulative.promptTokens : meta.promptTokens;
|
|
521
611
|
const responseTokens = useCumulative ? cumulative.responseTokens : meta.responseTokens;
|
|
522
612
|
const thoughtsTokens = useCumulative ? (cumulative.thoughtsTokens || 0) : (meta.thoughtsTokens || 0);
|
|
523
613
|
const totalTokens = useCumulative ? cumulative.totalTokens : meta.totalTokens;
|
|
614
|
+
// Cached tokens accumulate across retries when tracked; otherwise fall back
|
|
615
|
+
// to the last response's value (single-call classes).
|
|
616
|
+
const cachedTokens = useCumulative && cumulative.cachedTokens !== undefined
|
|
617
|
+
? cumulative.cachedTokens
|
|
618
|
+
: (meta.cachedTokens || 0);
|
|
524
619
|
|
|
525
620
|
return {
|
|
526
621
|
promptTokens,
|
|
527
622
|
responseTokens,
|
|
528
623
|
thoughtsTokens,
|
|
624
|
+
cachedTokens,
|
|
529
625
|
totalTokens,
|
|
530
626
|
attempts: useCumulative ? cumulative.attempts : 1,
|
|
531
627
|
modelVersion: meta.modelVersion,
|
|
@@ -533,7 +629,7 @@ class BaseGemini {
|
|
|
533
629
|
timestamp: meta.timestamp,
|
|
534
630
|
groundingMetadata: meta.groundingMetadata || null,
|
|
535
631
|
modelStatus: meta.modelStatus || null,
|
|
536
|
-
estimatedCost: this._estimatedCost(meta.modelVersion, promptTokens, responseTokens, thoughtsTokens)
|
|
632
|
+
estimatedCost: this._estimatedCost(meta.modelVersion, promptTokens, responseTokens, thoughtsTokens, cachedTokens)
|
|
537
633
|
};
|
|
538
634
|
}
|
|
539
635
|
|
|
@@ -544,12 +640,13 @@ class BaseGemini {
|
|
|
544
640
|
* @param {number} promptTokens
|
|
545
641
|
* @param {number} responseTokens
|
|
546
642
|
* @param {number} [thoughtsTokens=0]
|
|
643
|
+
* @param {number} [cachedTokens=0] - Cached-content tokens (a SUBSET of promptTokens)
|
|
547
644
|
* @returns {number|null}
|
|
548
645
|
* @protected
|
|
549
646
|
*/
|
|
550
|
-
_estimatedCost(modelVersion, promptTokens, responseTokens, thoughtsTokens = 0) {
|
|
551
|
-
return computeCost(modelVersion, promptTokens, responseTokens, thoughtsTokens)
|
|
552
|
-
?? computeCost(this.modelName, promptTokens, responseTokens, thoughtsTokens);
|
|
647
|
+
_estimatedCost(modelVersion, promptTokens, responseTokens, thoughtsTokens = 0, cachedTokens = 0) {
|
|
648
|
+
return computeCost(modelVersion, promptTokens, responseTokens, thoughtsTokens, cachedTokens)
|
|
649
|
+
?? computeCost(this.modelName, promptTokens, responseTokens, thoughtsTokens, cachedTokens);
|
|
553
650
|
}
|
|
554
651
|
|
|
555
652
|
/**
|
|
@@ -567,12 +664,14 @@ class BaseGemini {
|
|
|
567
664
|
const promptTokens = response?.usageMetadata?.promptTokenCount || 0;
|
|
568
665
|
const responseTokens = response?.usageMetadata?.candidatesTokenCount || 0;
|
|
569
666
|
const thoughtsTokens = response?.usageMetadata?.thoughtsTokenCount || 0;
|
|
667
|
+
const cachedTokens = response?.usageMetadata?.cachedContentTokenCount || 0;
|
|
570
668
|
const totalTokens = response?.usageMetadata?.totalTokenCount || (promptTokens + responseTokens + thoughtsTokens);
|
|
571
669
|
const modelVersion = response?.modelVersion || null;
|
|
572
670
|
return {
|
|
573
671
|
promptTokens,
|
|
574
672
|
responseTokens,
|
|
575
673
|
thoughtsTokens,
|
|
674
|
+
cachedTokens,
|
|
576
675
|
totalTokens,
|
|
577
676
|
attempts,
|
|
578
677
|
modelVersion,
|
|
@@ -580,7 +679,7 @@ class BaseGemini {
|
|
|
580
679
|
timestamp: Date.now(),
|
|
581
680
|
groundingMetadata: response?.candidates?.[0]?.groundingMetadata || null,
|
|
582
681
|
modelStatus: response?.modelStatus || null,
|
|
583
|
-
estimatedCost: this._estimatedCost(modelVersion, promptTokens, responseTokens, thoughtsTokens)
|
|
682
|
+
estimatedCost: this._estimatedCost(modelVersion, promptTokens, responseTokens, thoughtsTokens, cachedTokens)
|
|
584
683
|
};
|
|
585
684
|
}
|
|
586
685
|
|
|
@@ -819,7 +918,7 @@ class BaseGemini {
|
|
|
819
918
|
_configureThinking(thinkingConfig) {
|
|
820
919
|
const modelSupportsThinking = THINKING_SUPPORTED_MODELS.some(p => p.test(this.modelName));
|
|
821
920
|
|
|
822
|
-
if (thinkingConfig === undefined) return;
|
|
921
|
+
if (thinkingConfig === undefined && this.effort === null) return;
|
|
823
922
|
|
|
824
923
|
if (thinkingConfig === null) {
|
|
825
924
|
delete this.chatConfig.thinkingConfig;
|
|
@@ -828,17 +927,47 @@ class BaseGemini {
|
|
|
828
927
|
}
|
|
829
928
|
|
|
830
929
|
if (!modelSupportsThinking) {
|
|
831
|
-
log.warn(`Model ${this.modelName} does not support thinking features. Ignoring thinkingConfig.`);
|
|
930
|
+
log.warn(`Model ${this.modelName} does not support thinking features. Ignoring thinkingConfig/effort.`);
|
|
832
931
|
return;
|
|
833
932
|
}
|
|
834
933
|
|
|
835
|
-
const config = { ...DEFAULT_THINKING_CONFIG, ...thinkingConfig };
|
|
836
|
-
|
|
934
|
+
const config = { ...DEFAULT_THINKING_CONFIG, ...(thinkingConfig || {}) };
|
|
935
|
+
|
|
936
|
+
// `effort` wins over an explicit thinkingLevel/thinkingBudget — it is the
|
|
937
|
+
// cross-package knob and is set deliberately.
|
|
938
|
+
if (this.effort !== null) {
|
|
939
|
+
const level = EFFORT_TO_THINKING_LEVEL[this.effort];
|
|
940
|
+
if (level !== this.effort) {
|
|
941
|
+
log.debug(`Effort '${this.effort}' has no Gemini equivalent; using thinkingLevel '${level}'.`);
|
|
942
|
+
}
|
|
943
|
+
config.thinkingLevel = level;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// thinkingLevel and thinkingBudget are mutually exclusive on the wire.
|
|
947
|
+
if (config.thinkingLevel !== undefined) {
|
|
837
948
|
delete config.thinkingBudget;
|
|
838
949
|
}
|
|
950
|
+
|
|
839
951
|
this.chatConfig.thinkingConfig = config;
|
|
840
952
|
log.debug(`Thinking config applied: ${JSON.stringify(config)}`);
|
|
841
953
|
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Validates the cross-package `effort` option.
|
|
957
|
+
* Throws rather than warns: this runs in the constructor before log levels are
|
|
958
|
+
* finalized, so a warning could be swallowed.
|
|
959
|
+
* @param {string|null|undefined} effort
|
|
960
|
+
* @returns {'minimal'|'low'|'medium'|'high'|'xhigh'|'max'|null}
|
|
961
|
+
* @protected
|
|
962
|
+
*/
|
|
963
|
+
_normalizeEffort(effort) {
|
|
964
|
+
if (effort === undefined || effort === null) return null;
|
|
965
|
+
const level = String(effort).toLowerCase();
|
|
966
|
+
if (!EFFORT_LEVELS.includes(level)) {
|
|
967
|
+
throw new Error(`Invalid effort "${effort}". Expected one of: ${EFFORT_LEVELS.join(', ')}.`);
|
|
968
|
+
}
|
|
969
|
+
return /** @type {any} */ (level);
|
|
970
|
+
}
|
|
842
971
|
}
|
|
843
972
|
|
|
844
973
|
export default BaseGemini;
|
package/chat.js
CHANGED
|
@@ -87,6 +87,8 @@ class Chat extends BaseGemini {
|
|
|
87
87
|
this._cumulativeUsage = {
|
|
88
88
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
89
89
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
90
|
+
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
91
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
90
92
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
91
93
|
attempts: 1
|
|
92
94
|
};
|
package/code-agent.js
CHANGED
|
@@ -922,6 +922,8 @@ ${lang.codeRules}
|
|
|
922
922
|
this._cumulativeUsage = {
|
|
923
923
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
924
924
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
925
|
+
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
926
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
925
927
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
926
928
|
attempts: 1
|
|
927
929
|
};
|
package/image-generator.js
CHANGED
|
@@ -114,6 +114,7 @@ export default class ImageGenerator extends BaseGemini {
|
|
|
114
114
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
115
115
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
116
116
|
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
117
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
117
118
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
118
119
|
attempts: 1
|
|
119
120
|
};
|
package/index.cjs
CHANGED
|
@@ -32,12 +32,14 @@ __export(index_exports, {
|
|
|
32
32
|
BaseGemini: () => base_default,
|
|
33
33
|
Chat: () => chat_default,
|
|
34
34
|
CodeAgent: () => code_agent_default,
|
|
35
|
+
EFFORT_LEVELS: () => EFFORT_LEVELS,
|
|
35
36
|
Embedding: () => Embedding,
|
|
36
37
|
HarmBlockThreshold: () => import_genai2.HarmBlockThreshold,
|
|
37
38
|
HarmCategory: () => import_genai2.HarmCategory,
|
|
38
39
|
ImageGenerator: () => ImageGenerator,
|
|
39
40
|
MODEL_ALIASES: () => MODEL_ALIASES,
|
|
40
41
|
MODEL_PRICING: () => MODEL_PRICING,
|
|
42
|
+
MODEL_PRICING_AS_OF: () => MODEL_PRICING_AS_OF,
|
|
41
43
|
Message: () => message_default,
|
|
42
44
|
RagAgent: () => rag_agent_default,
|
|
43
45
|
ThinkingLevel: () => import_genai2.ThinkingLevel,
|
|
@@ -400,6 +402,15 @@ var DEFAULT_THINKING_CONFIG = {
|
|
|
400
402
|
thinkingBudget: 0
|
|
401
403
|
};
|
|
402
404
|
var DEFAULT_MAX_OUTPUT_TOKENS = 5e4;
|
|
405
|
+
var EFFORT_LEVELS = ["minimal", "low", "medium", "high", "xhigh", "max"];
|
|
406
|
+
var EFFORT_TO_THINKING_LEVEL = {
|
|
407
|
+
minimal: "minimal",
|
|
408
|
+
low: "low",
|
|
409
|
+
medium: "medium",
|
|
410
|
+
high: "high",
|
|
411
|
+
xhigh: "high",
|
|
412
|
+
max: "high"
|
|
413
|
+
};
|
|
403
414
|
var THINKING_SUPPORTED_MODELS = [
|
|
404
415
|
/^gemini-3(\.\d+)?-pro(-preview)?$/,
|
|
405
416
|
/^gemini-3(\.\d+)?-flash(-preview)?$/,
|
|
@@ -409,26 +420,42 @@ var THINKING_SUPPORTED_MODELS = [
|
|
|
409
420
|
/^gemini-2\.5-flash-lite(-preview)?$/,
|
|
410
421
|
/^gemini-2\.0-flash$/
|
|
411
422
|
];
|
|
423
|
+
var MODEL_PRICING_AS_OF = "2026-07-28";
|
|
424
|
+
var TIER_THRESHOLD = 2e5;
|
|
412
425
|
var MODEL_PRICING = {
|
|
413
426
|
// Gemini 3.x stable
|
|
414
|
-
"gemini-3.
|
|
415
|
-
"gemini-3.
|
|
427
|
+
"gemini-3.6-flash": { input: 1.5, output: 7.5, cachedInput: 0.15 },
|
|
428
|
+
"gemini-3.5-flash": { input: 1.5, output: 9, cachedInput: 0.15 },
|
|
429
|
+
"gemini-3.5-flash-lite": { input: 0.3, output: 2.5, cachedInput: 0.03 },
|
|
430
|
+
"gemini-3.1-flash-lite": { input: 0.25, output: 1.5, cachedInput: 0.025 },
|
|
416
431
|
// Gemini 3.x preview
|
|
417
|
-
"gemini-3.1-pro-preview": {
|
|
418
|
-
|
|
432
|
+
"gemini-3.1-pro-preview": {
|
|
433
|
+
input: 2,
|
|
434
|
+
output: 12,
|
|
435
|
+
cachedInput: 0.2,
|
|
436
|
+
inputAbove200k: 4,
|
|
437
|
+
outputAbove200k: 18,
|
|
438
|
+
cachedInputAbove200k: 0.4
|
|
439
|
+
},
|
|
419
440
|
"gemini-3-pro-preview": { input: 2, output: 12 },
|
|
420
441
|
// ≤200k tier; launch rate (superseded by 3.1, off the pricing page)
|
|
421
442
|
"gemini-3-flash-preview": { input: 0.5, output: 3 },
|
|
422
|
-
"gemini-3.1-flash-lite-preview": { input: 0.25, output: 1.5 },
|
|
443
|
+
"gemini-3.1-flash-lite-preview": { input: 0.25, output: 1.5, cachedInput: 0.025 },
|
|
423
444
|
"gemini-3.1-flash-image-preview": { input: 0.5, output: 3 },
|
|
424
445
|
// text-only; image-output is $60/M
|
|
425
446
|
"gemini-3-pro-image-preview": { input: 2, output: 12 },
|
|
426
447
|
// text-only; image-output is $120/M
|
|
427
448
|
// Gemini 2.5 stable
|
|
428
|
-
"gemini-2.5-flash": { input: 0.3, output:
|
|
429
|
-
"gemini-2.5-flash-lite": { input: 0.1, output: 0.4 },
|
|
430
|
-
"gemini-2.5-pro": {
|
|
431
|
-
|
|
449
|
+
"gemini-2.5-flash": { input: 0.3, output: 3, cachedInput: 0.05 },
|
|
450
|
+
"gemini-2.5-flash-lite": { input: 0.1, output: 0.4, cachedInput: 0.01 },
|
|
451
|
+
"gemini-2.5-pro": {
|
|
452
|
+
input: 1.25,
|
|
453
|
+
output: 10,
|
|
454
|
+
cachedInput: 0.125,
|
|
455
|
+
inputAbove200k: 2.5,
|
|
456
|
+
outputAbove200k: 15,
|
|
457
|
+
cachedInputAbove200k: 0.25
|
|
458
|
+
},
|
|
432
459
|
"gemini-2.5-flash-image": { input: 0.3, output: 0 },
|
|
433
460
|
// image-output is ~$0.039/image (1290 tokens)
|
|
434
461
|
// Deprecated but kept for back-compat (shut down June 2026)
|
|
@@ -438,16 +465,24 @@ var MODEL_PRICING = {
|
|
|
438
465
|
"gemini-embedding-001": { input: 0.15, output: 0 }
|
|
439
466
|
};
|
|
440
467
|
var MODEL_ALIASES = {
|
|
441
|
-
"gemini-flash-latest": "gemini-3.
|
|
468
|
+
"gemini-flash-latest": "gemini-3.6-flash",
|
|
442
469
|
"gemini-pro-latest": "gemini-3.1-pro-preview",
|
|
443
|
-
"gemini-flash-lite-latest": "gemini-3.
|
|
470
|
+
"gemini-flash-lite-latest": "gemini-3.5-flash-lite"
|
|
444
471
|
};
|
|
445
472
|
function resolvePricing(modelId) {
|
|
473
|
+
const entry = _lookupPricing(modelId);
|
|
474
|
+
if (!entry) return null;
|
|
475
|
+
return { ...entry, tierThreshold: TIER_THRESHOLD, asOf: MODEL_PRICING_AS_OF };
|
|
476
|
+
}
|
|
477
|
+
function _lookupPricing(modelId) {
|
|
446
478
|
if (!modelId) return null;
|
|
447
479
|
const tryKey = (id) => {
|
|
448
480
|
if (MODEL_PRICING[id]) return MODEL_PRICING[id];
|
|
449
481
|
const alias = MODEL_ALIASES[id];
|
|
450
|
-
if (alias && MODEL_PRICING[alias])
|
|
482
|
+
if (alias && MODEL_PRICING[alias]) {
|
|
483
|
+
logger_default.debug(`Pricing: alias "${id}" resolved to "${alias}". If Google has rotated this alias the rate may be wrong \u2014 prefer the echoed modelVersion.`);
|
|
484
|
+
return MODEL_PRICING[alias];
|
|
485
|
+
}
|
|
451
486
|
return null;
|
|
452
487
|
};
|
|
453
488
|
let hit = tryKey(modelId);
|
|
@@ -462,10 +497,16 @@ function resolvePricing(modelId) {
|
|
|
462
497
|
}
|
|
463
498
|
return null;
|
|
464
499
|
}
|
|
465
|
-
function computeCost(modelId, promptTokens, responseTokens, thoughtsTokens = 0) {
|
|
500
|
+
function computeCost(modelId, promptTokens, responseTokens, thoughtsTokens = 0, cachedTokens = 0) {
|
|
466
501
|
const pricing = resolvePricing(modelId);
|
|
467
502
|
if (!pricing) return null;
|
|
468
|
-
|
|
503
|
+
const above = (promptTokens || 0) > TIER_THRESHOLD;
|
|
504
|
+
const inputRate = above && pricing.inputAbove200k || pricing.input;
|
|
505
|
+
const outputRate = above && pricing.outputAbove200k || pricing.output;
|
|
506
|
+
const cachedRate = above ? pricing.cachedInputAbove200k ?? pricing.cachedInput ?? inputRate : pricing.cachedInput ?? inputRate;
|
|
507
|
+
const cached = Math.max(0, Math.min(cachedTokens || 0, promptTokens || 0));
|
|
508
|
+
const billablePrompt = Math.max(0, (promptTokens || 0) - cached);
|
|
509
|
+
return billablePrompt / 1e6 * inputRate + cached / 1e6 * cachedRate + ((responseTokens || 0) + (thoughtsTokens || 0)) / 1e6 * outputRate;
|
|
469
510
|
}
|
|
470
511
|
var BaseGemini = class {
|
|
471
512
|
/**
|
|
@@ -530,6 +571,7 @@ var BaseGemini = class {
|
|
|
530
571
|
} else {
|
|
531
572
|
this.chatConfig.maxOutputTokens = DEFAULT_MAX_OUTPUT_TOKENS;
|
|
532
573
|
}
|
|
574
|
+
this.effort = this._normalizeEffort(options.effort);
|
|
533
575
|
this._configureThinking(options.thinkingConfig);
|
|
534
576
|
const clientOptions = this.vertexai ? {
|
|
535
577
|
vertexai: true,
|
|
@@ -544,6 +586,8 @@ var BaseGemini = class {
|
|
|
544
586
|
this._cumulativeUsage = {
|
|
545
587
|
promptTokens: 0,
|
|
546
588
|
responseTokens: 0,
|
|
589
|
+
thoughtsTokens: 0,
|
|
590
|
+
cachedTokens: 0,
|
|
547
591
|
totalTokens: 0,
|
|
548
592
|
attempts: 0
|
|
549
593
|
};
|
|
@@ -640,7 +684,7 @@ var BaseGemini = class {
|
|
|
640
684
|
}
|
|
641
685
|
this.chatSession = this._createChatSession([]);
|
|
642
686
|
this.lastResponseMetadata = null;
|
|
643
|
-
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, totalTokens: 0, attempts: 0 };
|
|
687
|
+
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, thoughtsTokens: 0, cachedTokens: 0, totalTokens: 0, attempts: 0 };
|
|
644
688
|
logger_default.debug(`${this.constructor.name}: Conversation history cleared.`);
|
|
645
689
|
}
|
|
646
690
|
// ── Few-Shot Seeding ─────────────────────────────────────────────────────
|
|
@@ -729,12 +773,14 @@ ${contextText}
|
|
|
729
773
|
const promptTokens = response.usageMetadata?.promptTokenCount || 0;
|
|
730
774
|
const responseTokens = response.usageMetadata?.candidatesTokenCount || 0;
|
|
731
775
|
const thoughtsTokens = response.usageMetadata?.thoughtsTokenCount || 0;
|
|
776
|
+
const cachedTokens = response.usageMetadata?.cachedContentTokenCount || 0;
|
|
732
777
|
this.lastResponseMetadata = {
|
|
733
778
|
modelVersion: response.modelVersion || null,
|
|
734
779
|
requestedModel: this.modelName,
|
|
735
780
|
promptTokens,
|
|
736
781
|
responseTokens,
|
|
737
782
|
thoughtsTokens,
|
|
783
|
+
cachedTokens,
|
|
738
784
|
// totalTokenCount includes thoughts; fall back to summing the parts.
|
|
739
785
|
totalTokens: response.usageMetadata?.totalTokenCount || promptTokens + responseTokens + thoughtsTokens,
|
|
740
786
|
timestamp: Date.now(),
|
|
@@ -754,16 +800,18 @@ ${contextText}
|
|
|
754
800
|
getLastUsage() {
|
|
755
801
|
if (!this.lastResponseMetadata) return null;
|
|
756
802
|
const meta = this.lastResponseMetadata;
|
|
757
|
-
const cumulative = this._cumulativeUsage || { promptTokens: 0, responseTokens: 0, totalTokens: 0, thoughtsTokens: 0, attempts: 1 };
|
|
803
|
+
const cumulative = this._cumulativeUsage || { promptTokens: 0, responseTokens: 0, totalTokens: 0, thoughtsTokens: 0, cachedTokens: 0, attempts: 1 };
|
|
758
804
|
const useCumulative = cumulative.attempts > 0;
|
|
759
805
|
const promptTokens = useCumulative ? cumulative.promptTokens : meta.promptTokens;
|
|
760
806
|
const responseTokens = useCumulative ? cumulative.responseTokens : meta.responseTokens;
|
|
761
807
|
const thoughtsTokens = useCumulative ? cumulative.thoughtsTokens || 0 : meta.thoughtsTokens || 0;
|
|
762
808
|
const totalTokens = useCumulative ? cumulative.totalTokens : meta.totalTokens;
|
|
809
|
+
const cachedTokens = useCumulative && cumulative.cachedTokens !== void 0 ? cumulative.cachedTokens : meta.cachedTokens || 0;
|
|
763
810
|
return {
|
|
764
811
|
promptTokens,
|
|
765
812
|
responseTokens,
|
|
766
813
|
thoughtsTokens,
|
|
814
|
+
cachedTokens,
|
|
767
815
|
totalTokens,
|
|
768
816
|
attempts: useCumulative ? cumulative.attempts : 1,
|
|
769
817
|
modelVersion: meta.modelVersion,
|
|
@@ -771,7 +819,7 @@ ${contextText}
|
|
|
771
819
|
timestamp: meta.timestamp,
|
|
772
820
|
groundingMetadata: meta.groundingMetadata || null,
|
|
773
821
|
modelStatus: meta.modelStatus || null,
|
|
774
|
-
estimatedCost: this._estimatedCost(meta.modelVersion, promptTokens, responseTokens, thoughtsTokens)
|
|
822
|
+
estimatedCost: this._estimatedCost(meta.modelVersion, promptTokens, responseTokens, thoughtsTokens, cachedTokens)
|
|
775
823
|
};
|
|
776
824
|
}
|
|
777
825
|
/**
|
|
@@ -781,11 +829,12 @@ ${contextText}
|
|
|
781
829
|
* @param {number} promptTokens
|
|
782
830
|
* @param {number} responseTokens
|
|
783
831
|
* @param {number} [thoughtsTokens=0]
|
|
832
|
+
* @param {number} [cachedTokens=0] - Cached-content tokens (a SUBSET of promptTokens)
|
|
784
833
|
* @returns {number|null}
|
|
785
834
|
* @protected
|
|
786
835
|
*/
|
|
787
|
-
_estimatedCost(modelVersion, promptTokens, responseTokens, thoughtsTokens = 0) {
|
|
788
|
-
return computeCost(modelVersion, promptTokens, responseTokens, thoughtsTokens) ?? computeCost(this.modelName, promptTokens, responseTokens, thoughtsTokens);
|
|
836
|
+
_estimatedCost(modelVersion, promptTokens, responseTokens, thoughtsTokens = 0, cachedTokens = 0) {
|
|
837
|
+
return computeCost(modelVersion, promptTokens, responseTokens, thoughtsTokens, cachedTokens) ?? computeCost(this.modelName, promptTokens, responseTokens, thoughtsTokens, cachedTokens);
|
|
789
838
|
}
|
|
790
839
|
/**
|
|
791
840
|
* Builds a usage object directly from a single API response, WITHOUT reading
|
|
@@ -802,12 +851,14 @@ ${contextText}
|
|
|
802
851
|
const promptTokens = response?.usageMetadata?.promptTokenCount || 0;
|
|
803
852
|
const responseTokens = response?.usageMetadata?.candidatesTokenCount || 0;
|
|
804
853
|
const thoughtsTokens = response?.usageMetadata?.thoughtsTokenCount || 0;
|
|
854
|
+
const cachedTokens = response?.usageMetadata?.cachedContentTokenCount || 0;
|
|
805
855
|
const totalTokens = response?.usageMetadata?.totalTokenCount || promptTokens + responseTokens + thoughtsTokens;
|
|
806
856
|
const modelVersion = response?.modelVersion || null;
|
|
807
857
|
return {
|
|
808
858
|
promptTokens,
|
|
809
859
|
responseTokens,
|
|
810
860
|
thoughtsTokens,
|
|
861
|
+
cachedTokens,
|
|
811
862
|
totalTokens,
|
|
812
863
|
attempts,
|
|
813
864
|
modelVersion,
|
|
@@ -815,7 +866,7 @@ ${contextText}
|
|
|
815
866
|
timestamp: Date.now(),
|
|
816
867
|
groundingMetadata: response?.candidates?.[0]?.groundingMetadata || null,
|
|
817
868
|
modelStatus: response?.modelStatus || null,
|
|
818
|
-
estimatedCost: this._estimatedCost(modelVersion, promptTokens, responseTokens, thoughtsTokens)
|
|
869
|
+
estimatedCost: this._estimatedCost(modelVersion, promptTokens, responseTokens, thoughtsTokens, cachedTokens)
|
|
819
870
|
};
|
|
820
871
|
}
|
|
821
872
|
// ── Token Estimation ─────────────────────────────────────────────────────
|
|
@@ -1020,23 +1071,49 @@ ${contextText}
|
|
|
1020
1071
|
*/
|
|
1021
1072
|
_configureThinking(thinkingConfig) {
|
|
1022
1073
|
const modelSupportsThinking = THINKING_SUPPORTED_MODELS.some((p) => p.test(this.modelName));
|
|
1023
|
-
if (thinkingConfig === void 0) return;
|
|
1074
|
+
if (thinkingConfig === void 0 && this.effort === null) return;
|
|
1024
1075
|
if (thinkingConfig === null) {
|
|
1025
1076
|
delete this.chatConfig.thinkingConfig;
|
|
1026
1077
|
logger_default.debug(`thinkingConfig set to null - removed from configuration`);
|
|
1027
1078
|
return;
|
|
1028
1079
|
}
|
|
1029
1080
|
if (!modelSupportsThinking) {
|
|
1030
|
-
logger_default.warn(`Model ${this.modelName} does not support thinking features. Ignoring thinkingConfig.`);
|
|
1081
|
+
logger_default.warn(`Model ${this.modelName} does not support thinking features. Ignoring thinkingConfig/effort.`);
|
|
1031
1082
|
return;
|
|
1032
1083
|
}
|
|
1033
|
-
const config = { ...DEFAULT_THINKING_CONFIG, ...thinkingConfig };
|
|
1034
|
-
if (
|
|
1084
|
+
const config = { ...DEFAULT_THINKING_CONFIG, ...thinkingConfig || {} };
|
|
1085
|
+
if (this.effort !== null) {
|
|
1086
|
+
const level = EFFORT_TO_THINKING_LEVEL[this.effort];
|
|
1087
|
+
if (level !== this.effort) {
|
|
1088
|
+
logger_default.debug(`Effort '${this.effort}' has no Gemini equivalent; using thinkingLevel '${level}'.`);
|
|
1089
|
+
}
|
|
1090
|
+
config.thinkingLevel = level;
|
|
1091
|
+
}
|
|
1092
|
+
if (config.thinkingLevel !== void 0) {
|
|
1035
1093
|
delete config.thinkingBudget;
|
|
1036
1094
|
}
|
|
1037
1095
|
this.chatConfig.thinkingConfig = config;
|
|
1038
1096
|
logger_default.debug(`Thinking config applied: ${JSON.stringify(config)}`);
|
|
1039
1097
|
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Validates the cross-package `effort` option.
|
|
1100
|
+
* Throws rather than warns: this runs in the constructor before log levels are
|
|
1101
|
+
* finalized, so a warning could be swallowed.
|
|
1102
|
+
* @param {string|null|undefined} effort
|
|
1103
|
+
* @returns {'minimal'|'low'|'medium'|'high'|'xhigh'|'max'|null}
|
|
1104
|
+
* @protected
|
|
1105
|
+
*/
|
|
1106
|
+
_normalizeEffort(effort) {
|
|
1107
|
+
if (effort === void 0 || effort === null) return null;
|
|
1108
|
+
const level = String(effort).toLowerCase();
|
|
1109
|
+
if (!EFFORT_LEVELS.includes(level)) {
|
|
1110
|
+
throw new Error(`Invalid effort "${effort}". Expected one of: ${EFFORT_LEVELS.join(", ")}.`);
|
|
1111
|
+
}
|
|
1112
|
+
return (
|
|
1113
|
+
/** @type {any} */
|
|
1114
|
+
level
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1040
1117
|
};
|
|
1041
1118
|
var base_default = BaseGemini;
|
|
1042
1119
|
|
|
@@ -1168,7 +1245,7 @@ var Transformer = class extends base_default {
|
|
|
1168
1245
|
let lastPayload = this._preparePayload(payload);
|
|
1169
1246
|
const messageOptions = {};
|
|
1170
1247
|
if (opts.labels) messageOptions.labels = opts.labels;
|
|
1171
|
-
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, totalTokens: 0, attempts: 0 };
|
|
1248
|
+
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, thoughtsTokens: 0, cachedTokens: 0, totalTokens: 0, attempts: 0 };
|
|
1172
1249
|
let lastError = null;
|
|
1173
1250
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1174
1251
|
try {
|
|
@@ -1176,6 +1253,8 @@ var Transformer = class extends base_default {
|
|
|
1176
1253
|
if (this.lastResponseMetadata) {
|
|
1177
1254
|
this._cumulativeUsage.promptTokens += this.lastResponseMetadata.promptTokens || 0;
|
|
1178
1255
|
this._cumulativeUsage.responseTokens += this.lastResponseMetadata.responseTokens || 0;
|
|
1256
|
+
this._cumulativeUsage.thoughtsTokens += this.lastResponseMetadata.thoughtsTokens || 0;
|
|
1257
|
+
this._cumulativeUsage.cachedTokens += this.lastResponseMetadata.cachedTokens || 0;
|
|
1179
1258
|
this._cumulativeUsage.totalTokens += this.lastResponseMetadata.totalTokens || 0;
|
|
1180
1259
|
this._cumulativeUsage.attempts = attempt + 1;
|
|
1181
1260
|
}
|
|
@@ -1311,6 +1390,8 @@ Respond with JSON only \u2013 no comments or explanations.
|
|
|
1311
1390
|
this._cumulativeUsage = {
|
|
1312
1391
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
1313
1392
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
1393
|
+
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
1394
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
1314
1395
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
1315
1396
|
attempts: 1
|
|
1316
1397
|
};
|
|
@@ -1336,7 +1417,7 @@ Respond with JSON only \u2013 no comments or explanations.
|
|
|
1336
1417
|
const exampleHistory = history.slice(0, this.exampleCount || 0);
|
|
1337
1418
|
this.chatSession = this._createChatSession(exampleHistory);
|
|
1338
1419
|
this.lastResponseMetadata = null;
|
|
1339
|
-
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, totalTokens: 0, attempts: 0 };
|
|
1420
|
+
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, thoughtsTokens: 0, cachedTokens: 0, totalTokens: 0, attempts: 0 };
|
|
1340
1421
|
logger_default.debug(`Conversation cleared. Preserved ${exampleHistory.length} example items.`);
|
|
1341
1422
|
}
|
|
1342
1423
|
/**
|
|
@@ -1435,6 +1516,8 @@ var Chat = class extends base_default {
|
|
|
1435
1516
|
this._cumulativeUsage = {
|
|
1436
1517
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
1437
1518
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
1519
|
+
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
1520
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
1438
1521
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
1439
1522
|
attempts: 1
|
|
1440
1523
|
};
|
|
@@ -1530,6 +1613,7 @@ var Message = class extends base_default {
|
|
|
1530
1613
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
1531
1614
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
1532
1615
|
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
1616
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
1533
1617
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
1534
1618
|
attempts: 1
|
|
1535
1619
|
};
|
|
@@ -1687,6 +1771,8 @@ var ToolAgent = class extends base_default {
|
|
|
1687
1771
|
this._cumulativeUsage = {
|
|
1688
1772
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
1689
1773
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
1774
|
+
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
1775
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
1690
1776
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
1691
1777
|
attempts: 1
|
|
1692
1778
|
};
|
|
@@ -2698,6 +2784,8 @@ ${this.envOverview}`;
|
|
|
2698
2784
|
this._cumulativeUsage = {
|
|
2699
2785
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
2700
2786
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
2787
|
+
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
2788
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
2701
2789
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
2702
2790
|
attempts: 1
|
|
2703
2791
|
};
|
|
@@ -3016,6 +3104,8 @@ ${serialized}` });
|
|
|
3016
3104
|
this._cumulativeUsage = {
|
|
3017
3105
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
3018
3106
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
3107
|
+
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
3108
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
3019
3109
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
3020
3110
|
attempts: 1
|
|
3021
3111
|
};
|
|
@@ -3333,6 +3423,7 @@ var ImageGenerator = class extends base_default {
|
|
|
3333
3423
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
3334
3424
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
3335
3425
|
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
3426
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
3336
3427
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
3337
3428
|
attempts: 1
|
|
3338
3429
|
};
|
|
@@ -3408,12 +3499,14 @@ var index_default = { Transformer: transformer_default, Chat: chat_default, Mess
|
|
|
3408
3499
|
BaseGemini,
|
|
3409
3500
|
Chat,
|
|
3410
3501
|
CodeAgent,
|
|
3502
|
+
EFFORT_LEVELS,
|
|
3411
3503
|
Embedding,
|
|
3412
3504
|
HarmBlockThreshold,
|
|
3413
3505
|
HarmCategory,
|
|
3414
3506
|
ImageGenerator,
|
|
3415
3507
|
MODEL_ALIASES,
|
|
3416
3508
|
MODEL_PRICING,
|
|
3509
|
+
MODEL_PRICING_AS_OF,
|
|
3417
3510
|
Message,
|
|
3418
3511
|
RagAgent,
|
|
3419
3512
|
ThinkingLevel,
|
package/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { default as RagAgent } from './rag-agent.js';
|
|
|
29
29
|
export { default as Embedding } from './embedding.js';
|
|
30
30
|
export { default as ImageGenerator } from './image-generator.js';
|
|
31
31
|
export { default as BaseGemini } from './base.js';
|
|
32
|
-
export { MODEL_PRICING, MODEL_ALIASES, resolvePricing, computeCost } from './base.js';
|
|
32
|
+
export { MODEL_PRICING, MODEL_PRICING_AS_OF, MODEL_ALIASES, EFFORT_LEVELS, resolvePricing, computeCost } from './base.js';
|
|
33
33
|
export { default as log } from './logger.js';
|
|
34
34
|
export { ThinkingLevel, HarmCategory, HarmBlockThreshold } from '@google/genai';
|
|
35
35
|
export { extractJSON, attemptJSONRecovery, validateSchema } from './json-helpers.js';
|
package/message.js
CHANGED
|
@@ -128,6 +128,7 @@ class Message extends BaseGemini {
|
|
|
128
128
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
129
129
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
130
130
|
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
131
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
131
132
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
132
133
|
attempts: 1
|
|
133
134
|
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "ak-gemini",
|
|
3
3
|
"author": "ak@mixpanel.com",
|
|
4
4
|
"description": "AK's Generative AI Helper for doing... everything",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.6.0",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
8
8
|
"index.js",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"types.d.ts",
|
|
21
21
|
"logger.js",
|
|
22
22
|
"GUIDE.md",
|
|
23
|
-
"
|
|
23
|
+
"UPGRADING.md",
|
|
24
|
+
"CHANGELOG.md"
|
|
24
25
|
],
|
|
25
26
|
"types": "types.d.ts",
|
|
26
27
|
"exports": {
|
package/rag-agent.js
CHANGED
|
@@ -213,6 +213,8 @@ class RagAgent extends BaseGemini {
|
|
|
213
213
|
this._cumulativeUsage = {
|
|
214
214
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
215
215
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
216
|
+
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
217
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
216
218
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
217
219
|
attempts: 1
|
|
218
220
|
};
|
package/tool-agent.js
CHANGED
|
@@ -200,6 +200,8 @@ class ToolAgent extends BaseGemini {
|
|
|
200
200
|
this._cumulativeUsage = {
|
|
201
201
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
202
202
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
203
|
+
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
204
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
203
205
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
204
206
|
attempts: 1
|
|
205
207
|
};
|
package/transformer.js
CHANGED
|
@@ -201,7 +201,7 @@ class Transformer extends BaseGemini {
|
|
|
201
201
|
if (opts.labels) messageOptions.labels = opts.labels;
|
|
202
202
|
|
|
203
203
|
// Reset cumulative usage tracking
|
|
204
|
-
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, totalTokens: 0, attempts: 0 };
|
|
204
|
+
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, thoughtsTokens: 0, cachedTokens: 0, totalTokens: 0, attempts: 0 };
|
|
205
205
|
|
|
206
206
|
let lastError = null;
|
|
207
207
|
|
|
@@ -215,6 +215,8 @@ class Transformer extends BaseGemini {
|
|
|
215
215
|
if (this.lastResponseMetadata) {
|
|
216
216
|
this._cumulativeUsage.promptTokens += this.lastResponseMetadata.promptTokens || 0;
|
|
217
217
|
this._cumulativeUsage.responseTokens += this.lastResponseMetadata.responseTokens || 0;
|
|
218
|
+
this._cumulativeUsage.thoughtsTokens += this.lastResponseMetadata.thoughtsTokens || 0;
|
|
219
|
+
this._cumulativeUsage.cachedTokens += this.lastResponseMetadata.cachedTokens || 0;
|
|
218
220
|
this._cumulativeUsage.totalTokens += this.lastResponseMetadata.totalTokens || 0;
|
|
219
221
|
this._cumulativeUsage.attempts = attempt + 1;
|
|
220
222
|
}
|
|
@@ -389,6 +391,8 @@ Respond with JSON only – no comments or explanations.
|
|
|
389
391
|
this._cumulativeUsage = {
|
|
390
392
|
promptTokens: this.lastResponseMetadata.promptTokens,
|
|
391
393
|
responseTokens: this.lastResponseMetadata.responseTokens,
|
|
394
|
+
thoughtsTokens: this.lastResponseMetadata.thoughtsTokens,
|
|
395
|
+
cachedTokens: this.lastResponseMetadata.cachedTokens,
|
|
392
396
|
totalTokens: this.lastResponseMetadata.totalTokens,
|
|
393
397
|
attempts: 1
|
|
394
398
|
};
|
|
@@ -422,7 +426,7 @@ Respond with JSON only – no comments or explanations.
|
|
|
422
426
|
this.chatSession = this._createChatSession(exampleHistory);
|
|
423
427
|
|
|
424
428
|
this.lastResponseMetadata = null;
|
|
425
|
-
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, totalTokens: 0, attempts: 0 };
|
|
429
|
+
this._cumulativeUsage = { promptTokens: 0, responseTokens: 0, thoughtsTokens: 0, cachedTokens: 0, totalTokens: 0, attempts: 0 };
|
|
426
430
|
|
|
427
431
|
log.debug(`Conversation cleared. Preserved ${exampleHistory.length} example items.`);
|
|
428
432
|
}
|
package/types.d.ts
CHANGED
|
@@ -6,11 +6,38 @@ export { ThinkingLevel, HarmCategory, HarmBlockThreshold };
|
|
|
6
6
|
|
|
7
7
|
export interface ThinkingConfig {
|
|
8
8
|
includeThoughts?: boolean;
|
|
9
|
-
/** Token budget for thinking. 0 = disabled, -1 = automatic. */
|
|
9
|
+
/** Token budget for thinking. 0 = disabled, -1 = automatic. Mutually exclusive with thinkingLevel. */
|
|
10
10
|
thinkingBudget?: number;
|
|
11
11
|
thinkingLevel?: ThinkingLevel;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Cross-package reasoning-effort levels (same option name in ak-claude).
|
|
16
|
+
* Gemini's wire field is `thinkingConfig.thinkingLevel`; `xhigh` and `max` are
|
|
17
|
+
* ak-claude-only levels and clamp to `high` here.
|
|
18
|
+
*/
|
|
19
|
+
export type EffortLevel = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
20
|
+
|
|
21
|
+
/** Per-million-token rates for a single model. */
|
|
22
|
+
export interface ModelPricing {
|
|
23
|
+
input: number;
|
|
24
|
+
output: number;
|
|
25
|
+
/** Rate for tokens served from a context cache. Absent when Google publishes none — cost then falls back to `input` (overestimate). */
|
|
26
|
+
cachedInput?: number;
|
|
27
|
+
/** >200k-context tier rates, present only on tiered (Pro) models. */
|
|
28
|
+
inputAbove200k?: number;
|
|
29
|
+
outputAbove200k?: number;
|
|
30
|
+
cachedInputAbove200k?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** ModelPricing decorated with the tier threshold and the table's verification date. */
|
|
34
|
+
export interface ResolvedPricing extends ModelPricing {
|
|
35
|
+
/** Prompt-token count above which the *Above200k rates apply. */
|
|
36
|
+
tierThreshold: number;
|
|
37
|
+
/** ISO date the pricing table was last verified against Google's pricing page. */
|
|
38
|
+
asOf: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
export interface SafetySetting {
|
|
15
42
|
category: HarmCategory;
|
|
16
43
|
threshold: HarmBlockThreshold;
|
|
@@ -54,6 +81,9 @@ export interface ResponseMetadata {
|
|
|
54
81
|
requestedModel: string;
|
|
55
82
|
promptTokens: number;
|
|
56
83
|
responseTokens: number;
|
|
84
|
+
thoughtsTokens?: number;
|
|
85
|
+
/** Cached-content tokens. A SUBSET of promptTokens, not an addition to it. */
|
|
86
|
+
cachedTokens?: number;
|
|
57
87
|
totalTokens: number;
|
|
58
88
|
timestamp: number;
|
|
59
89
|
groundingMetadata?: GroundingMetadata | null;
|
|
@@ -66,6 +96,14 @@ export interface UsageData {
|
|
|
66
96
|
responseTokens: number;
|
|
67
97
|
/** CUMULATIVE thinking ("thoughts") tokens; billed at the output rate. Included in totalTokens and estimatedCost. */
|
|
68
98
|
thoughtsTokens?: number;
|
|
99
|
+
/**
|
|
100
|
+
* CUMULATIVE cached-content tokens (`usageMetadata.cachedContentTokenCount`).
|
|
101
|
+
* NOTE: Gemini INCLUDES these in promptTokens — they are a subset, not an
|
|
102
|
+
* addition. `estimatedCost` subtracts them from promptTokens and re-bills them
|
|
103
|
+
* at the discounted cached rate. (ak-claude is the opposite: Anthropic's cache
|
|
104
|
+
* tokens are excluded from input_tokens and added on top.)
|
|
105
|
+
*/
|
|
106
|
+
cachedTokens?: number;
|
|
69
107
|
/** CUMULATIVE total tokens across all retry attempts (includes thoughtsTokens) */
|
|
70
108
|
totalTokens: number;
|
|
71
109
|
/** Number of attempts (1 = first try success, 2+ = retries needed) */
|
|
@@ -78,7 +116,11 @@ export interface UsageData {
|
|
|
78
116
|
groundingMetadata?: GroundingMetadata | null;
|
|
79
117
|
/** Model lifecycle status from Google (e.g., 'DEPRECATED'). Surfaced from @google/genai 1.47+. */
|
|
80
118
|
modelStatus?: string | null;
|
|
81
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* Estimated USD cost from MODEL_PRICING (input + cached input + output + thoughts).
|
|
121
|
+
* `null` means the model's pricing is UNKNOWN — it does NOT mean the call was free.
|
|
122
|
+
* Cache STORAGE (per token-hour) and image-output tokens are not modelled.
|
|
123
|
+
*/
|
|
82
124
|
estimatedCost?: number | null;
|
|
83
125
|
}
|
|
84
126
|
|
|
@@ -149,6 +191,12 @@ export interface BaseGeminiOptions {
|
|
|
149
191
|
chatConfig?: Partial<ChatConfig>;
|
|
150
192
|
/** Thinking features configuration */
|
|
151
193
|
thinkingConfig?: ThinkingConfig | null;
|
|
194
|
+
/**
|
|
195
|
+
* Reasoning effort (cross-package option; same name in ak-claude). Maps to
|
|
196
|
+
* `thinkingConfig.thinkingLevel` and wins over an explicit thinkingLevel /
|
|
197
|
+
* thinkingBudget. Throws on an unrecognized level.
|
|
198
|
+
*/
|
|
199
|
+
effort?: EffortLevel | null;
|
|
152
200
|
/** Maximum output tokens (default: 50000, null removes limit) */
|
|
153
201
|
maxOutputTokens?: number | null;
|
|
154
202
|
/** Sampling temperature (shorthand for chatConfig.temperature; wins over chatConfig) */
|
|
@@ -596,6 +644,8 @@ export declare class BaseGemini {
|
|
|
596
644
|
cachedContent: string | null;
|
|
597
645
|
serviceTier: ServiceTier | null;
|
|
598
646
|
includeServerSideToolInvocations: boolean;
|
|
647
|
+
/** Normalized `effort` option; null when unset. */
|
|
648
|
+
effort: EffortLevel | null;
|
|
599
649
|
|
|
600
650
|
init(force?: boolean): Promise<void>;
|
|
601
651
|
seed(examples?: TransformationExample[], opts?: SeedOptions): Promise<any[]>;
|
|
@@ -606,11 +656,14 @@ export declare class BaseGemini {
|
|
|
606
656
|
estimateCost(nextPayload: Record<string, unknown> | string): Promise<{
|
|
607
657
|
inputTokens: number;
|
|
608
658
|
model: string;
|
|
609
|
-
pricing:
|
|
659
|
+
pricing: ResolvedPricing | null;
|
|
610
660
|
estimatedInputCost: number | null;
|
|
611
661
|
note: string;
|
|
612
662
|
}>;
|
|
613
663
|
|
|
664
|
+
/** @internal Validates the `effort` option; throws on an unknown level. */
|
|
665
|
+
protected _normalizeEffort(effort?: string | null): EffortLevel | null;
|
|
666
|
+
|
|
614
667
|
// Context Caching
|
|
615
668
|
createCache(config?: CacheConfig): Promise<CachedContentInfo>;
|
|
616
669
|
getCache(cacheName: string): Promise<CachedContentInfo>;
|
|
@@ -765,13 +818,28 @@ export declare function attemptJSONRecovery(text: string, maxAttempts?: number):
|
|
|
765
818
|
export declare function validateSchema(data: any, schema: Record<string, any>, path?: string): string[];
|
|
766
819
|
|
|
767
820
|
/** Per-million-token pricing keyed by model id. */
|
|
768
|
-
export declare const MODEL_PRICING: Record<string,
|
|
821
|
+
export declare const MODEL_PRICING: Record<string, ModelPricing>;
|
|
822
|
+
/** ISO date the pricing table was last verified against Google's published rates. */
|
|
823
|
+
export declare const MODEL_PRICING_AS_OF: string;
|
|
769
824
|
/** Floating `-latest` alias → canonical model id, for pricing resolution. */
|
|
770
825
|
export declare const MODEL_ALIASES: Record<string, string>;
|
|
771
|
-
/**
|
|
772
|
-
export declare
|
|
773
|
-
/**
|
|
774
|
-
export declare function
|
|
826
|
+
/** Valid values for the `effort` option. */
|
|
827
|
+
export declare const EFFORT_LEVELS: readonly EffortLevel[];
|
|
828
|
+
/** Resolves pricing for a model id (follows -latest aliases). `null` means UNKNOWN, not free. */
|
|
829
|
+
export declare function resolvePricing(modelId: string | null | undefined): ResolvedPricing | null;
|
|
830
|
+
/**
|
|
831
|
+
* Estimated USD cost from token counts. `null` means UNKNOWN, not free.
|
|
832
|
+
* `cachedTokens` is a SUBSET of `promptTokens` (Gemini includes them) and is
|
|
833
|
+
* subtracted before billing at the cached rate. Thinking tokens bill at the
|
|
834
|
+
* output rate. Tier is selected from `promptTokens` vs 200k.
|
|
835
|
+
*/
|
|
836
|
+
export declare function computeCost(
|
|
837
|
+
modelId: string | null | undefined,
|
|
838
|
+
promptTokens: number,
|
|
839
|
+
responseTokens: number,
|
|
840
|
+
thoughtsTokens?: number,
|
|
841
|
+
cachedTokens?: number
|
|
842
|
+
): number | null;
|
|
775
843
|
|
|
776
844
|
declare const _default: {
|
|
777
845
|
Transformer: typeof Transformer;
|