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 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, totalTokens, attempts, modelVersion, requestedModel, timestamp }
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-2.5-flash',
304
- thinkingConfig: { thinkingBudget: 1024 }
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 July 2026).
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.5-flash': { input: 1.50, output: 9.00 },
54
- 'gemini-3.1-flash-lite': { input: 0.25, output: 1.50 },
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': { input: 2.00, output: 12.00 }, // ≤200k tier
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: 2.50 },
64
- 'gemini-2.5-flash-lite': { input: 0.10, output: 0.40 },
65
- 'gemini-2.5-pro': { input: 1.25, output: 10.00 }, // ≤200k tier
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
- * Revisit when Google rotates what an alias points to.
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.5-flash',
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.1-flash-lite'
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 }|null} Pricing, or null if unknown.
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]) return 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
- * @returns {number|null} Cost in USD, or null when pricing is unknown.
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
- return (promptTokens / 1_000_000) * pricing.input
129
- + ((responseTokens + thoughtsTokens) / 1_000_000) * pricing.output;
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
- if (thinkingConfig.thinkingLevel !== undefined) {
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
  };
@@ -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.5-flash": { input: 1.5, output: 9 },
415
- "gemini-3.1-flash-lite": { input: 0.25, output: 1.5 },
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": { input: 2, output: 12 },
418
- // ≤200k tier
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: 2.5 },
429
- "gemini-2.5-flash-lite": { input: 0.1, output: 0.4 },
430
- "gemini-2.5-pro": { input: 1.25, output: 10 },
431
- // ≤200k tier
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.5-flash",
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.1-flash-lite"
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]) return 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
- return promptTokens / 1e6 * pricing.input + (responseTokens + thoughtsTokens) / 1e6 * pricing.output;
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 (thinkingConfig.thinkingLevel !== void 0) {
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.0",
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
- "CHANGELOG.md"
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
- /** Estimated USD cost from MODEL_PRICING (input+output). null when the model's pricing is unknown. */
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: { input: number; output: number } | null;
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, { input: number; output: number }>;
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
- /** Resolves pricing for a model id (follows -latest aliases). null when unknown. */
772
- export declare function resolvePricing(modelId: string | null | undefined): { input: number; output: number } | null;
773
- /** Estimated USD cost from token counts. null when the model's pricing is unknown. */
774
- export declare function computeCost(modelId: string | null | undefined, promptTokens: number, responseTokens: number): number | null;
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;