tolvyn 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,11 @@
1
- # tolvyn · npm
1
+ # tolvyn
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/tolvyn.svg)](https://www.npmjs.com/package/tolvyn)
3
+ Drop-in replacement for `openai` and `@anthropic-ai/sdk`. One line change. Every AI call metered, attributed, and governed.
4
4
 
5
- Drop-in replacement for `openai` and `@anthropic-ai/sdk`.
6
- One line change. Every AI call metered, attributed, and governed.
5
+ [![npm](https://img.shields.io/npm/v/tolvyn.svg)](https://www.npmjs.com/package/tolvyn)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ **10,000 free requests forever. No credit card.**
7
9
 
8
10
  ## Install
9
11
 
@@ -11,6 +13,14 @@ One line change. Every AI call metered, attributed, and governed.
11
13
  npm install tolvyn
12
14
  ```
13
15
 
16
+ Node 18 or later required. Ships dual ESM/CJS builds. TypeScript types included.
17
+
18
+ Google support requires the optional peer dependency:
19
+
20
+ ```bash
21
+ npm install tolvyn @google/generative-ai
22
+ ```
23
+
14
24
  ## Quick start
15
25
 
16
26
  ```typescript
@@ -33,13 +43,6 @@ const response = await client.chat.completions.create({
33
43
  });
34
44
  ```
35
45
 
36
- Works the same way for Anthropic:
37
-
38
- ```typescript
39
- import { Anthropic } from "tolvyn";
40
- const client = new Anthropic({ tolvynApiKey: "tlv_live_...", team: "ml", service: "classifier" });
41
- ```
42
-
43
46
  CommonJS:
44
47
 
45
48
  ```javascript
@@ -47,17 +50,99 @@ const { OpenAI } = require("tolvyn");
47
50
  const client = new OpenAI({ tolvynApiKey: process.env.TOLVYN_API_KEY, team: "backend" });
48
51
  ```
49
52
 
50
- ## What you get
53
+ ## All three providers
54
+
55
+ ```typescript
56
+ import { OpenAI, Anthropic, Google } from "tolvyn";
57
+
58
+ // OpenAI
59
+ const oai = new OpenAI({
60
+ tolvynApiKey: "tlv_live_...",
61
+ openAIApiKey: "sk-...", // optional — enables fail-open fallback
62
+ });
63
+
64
+ // Anthropic
65
+ const anth = new Anthropic({
66
+ tolvynApiKey: "tlv_live_...",
67
+ anthropicApiKey: "sk-ant-...", // optional — enables fail-open fallback
68
+ });
69
+
70
+ // Google (requires @google/generative-ai peer dep)
71
+ const goog = new Google({ tolvynApiKey: "tlv_live_..." });
72
+ const model = goog.getGenerativeModel({ model: "gemini-1.5-flash" });
73
+ ```
74
+
75
+ ## Attribution headers
76
+
77
+ Set any combination of these on construction; the SDK sends them as `X-Tolvyn-*` headers automatically:
78
+
79
+ ```typescript
80
+ const client = new OpenAI({
81
+ tolvynApiKey: "tlv_live_...",
82
+ team: "backend",
83
+ service: "invoice-summarizer",
84
+ feature: "summarize",
85
+ agent: "claude-code",
86
+ user: "alice@company.com",
87
+ endCustomer: "acme-corp",
88
+ });
89
+ ```
90
+
91
+ The TOLVYN proxy strips all six headers before forwarding the request upstream — they never reach OpenAI/Anthropic/Google.
92
+
93
+ ## Fail-open behavior
94
+
95
+ If TOLVYN's proxy is unreachable, the SDK automatically retries the request directly against the provider (requires `openAIApiKey` / `anthropicApiKey` / `googleApiKey` to be set). Disable with `failOpen: false`.
96
+
97
+ Triggers on: connection refused, timeout, DNS failure, HTTP 503.
98
+ Does NOT trigger on: 4xx errors (auth failures, rate limits, bad requests).
99
+
100
+ Requests that fail open bypass the proxy and are not metered for that call.
101
+
102
+ ## Environment variables
103
+
104
+ | Variable | Required | Description |
105
+ |---|---|---|
106
+ | `TOLVYN_API_KEY` | Yes (unless `tolvynApiKey` option is passed) | Your TOLVYN API key (`tlv_live_...`) |
107
+ | `OPENAI_API_KEY` | For fail-open | Fallback OpenAI key if proxy unreachable |
108
+ | `ANTHROPIC_API_KEY` | For fail-open | Fallback Anthropic key if proxy unreachable |
109
+ | `GOOGLE_API_KEY` | For fail-open | Reserved; Google fail-open is implemented in v1.0.6+ |
110
+ | `TOLVYN_PROXY_URL` | No | Override proxy URL |
111
+
112
+ ## API keys
113
+
114
+ - Production keys start with `tlv_live_`
115
+ - Test keys start with `tlv_test_` (use these in CI / staging)
116
+ - Get your key at [app.tolvyn.io](https://app.tolvyn.io) → API Keys
117
+ - **Provider keys** (OpenAI / Anthropic / Google) go in the dashboard under **Account → Provider Keys** — never in code. They are stored encrypted server-side.
118
+
119
+ ## TypeScript
120
+
121
+ Fully typed. Import the option interfaces directly when you need them:
122
+
123
+ ```typescript
124
+ import type {
125
+ TolvynOpenAIOptions,
126
+ TolvynAnthropicOptions,
127
+ TolvynGoogleOptions,
128
+ } from "tolvyn";
129
+ ```
130
+
131
+ ## Changelog
132
+
133
+ [github.com/tolvyn/tolvyn-cli/releases](https://github.com/tolvyn/tolvyn-cli/releases)
134
+
135
+ ## Links
136
+
137
+ - Docs: [docs.tolvyn.io/sdks/nodejs](https://docs.tolvyn.io/sdks/nodejs)
138
+ - Quickstart: [docs.tolvyn.io/getting-started/quickstart](https://docs.tolvyn.io/getting-started/quickstart)
139
+ - Dashboard: [app.tolvyn.io](https://app.tolvyn.io)
140
+ - Issues: [github.com/tolvyn/tolvyn-nodejs/issues](https://github.com/tolvyn/tolvyn-nodejs/issues)
51
141
 
52
- - **Cost metering** — every request logged with exact token counts and cost in microdollars
53
- - **Team attribution** — see spend by team and service, not just a total invoice number
54
- - **Budget enforcement** — set hard limits that block requests before they hit your provider
55
- - **Immutable ledger** — hash-chained audit trail, verifiable at any time
56
- - **Drop-in** — no changes to your existing API calls, models, or response handling
142
+ ## Feedback
57
143
 
58
- Full docs: [docs.tolvyn.io/nodejs-sdk](https://docs.tolvyn.io/nodejs-sdk)
59
- Free trial: [tolvyn.io](https://tolvyn.io)
144
+ [founder@tolvyn.io](mailto:founder@tolvyn.io) — we read every message.
60
145
 
61
146
  ---
62
147
 
63
- © 2026 TOLVYN. All rights reserved.
148
+ © 2026 TOLVYN. MIT licensed.
@@ -14,7 +14,7 @@ export interface TolvynGoogleOptions {
14
14
  failOpen?: boolean;
15
15
  googleApiKey?: string;
16
16
  }
17
- export declare class TolvynGoogle extends GoogleGenerativeAI {
17
+ export declare class Google extends GoogleGenerativeAI {
18
18
  readonly _tolvynFailOpen: boolean;
19
19
  readonly _tolvynFallbackKey: string | undefined;
20
20
  private readonly _tolvynProxyUrl;
@@ -1 +1 @@
1
- {"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/google.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,cAAc,EACf,MAAM,uBAAuB,CAAC;AAM/B,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,YAAa,SAAQ,kBAAkB;IAClD,SAAgB,eAAe,EAAE,OAAO,CAAC;IACzC,SAAgB,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IACvD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;gBAE5C,OAAO,GAAE,mBAAwB;IA8BpC,kBAAkB,CACzB,WAAW,EAAE,WAAW,EACxB,cAAc,CAAC,EAAE,cAAc;CASlC"}
1
+ {"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/google.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,cAAc,EACf,MAAM,uBAAuB,CAAC;AAO/B,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,qBAAa,MAAO,SAAQ,kBAAkB;IAC5C,SAAgB,eAAe,EAAE,OAAO,CAAC;IACzC,SAAgB,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IACvD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;gBAE5C,OAAO,GAAE,mBAAwB;IAgCpC,kBAAkB,CACzB,WAAW,EAAE,WAAW,EACxB,cAAc,CAAC,EAAE,cAAc;CAkClC"}
@@ -1,21 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TolvynGoogle = void 0;
3
+ exports.Google = void 0;
4
4
  /**
5
5
  * TOLVYN Google wrapper — thin drop-in over @google/generative-ai.
6
6
  */
7
7
  const generative_ai_1 = require("@google/generative-ai");
8
+ const failopen_1 = require("./failopen");
8
9
  // The proxy base URL is prepended to Google API paths (/v1beta/models/...).
9
10
  // The TOLVYN proxy strips /v1/proxy/google and forwards the remainder to Google.
10
11
  const GOOGLE_DEFAULT_PROXY_URL = 'https://proxy.tolvyn.io/v1/proxy/google/';
11
- class TolvynGoogle extends generative_ai_1.GoogleGenerativeAI {
12
+ // ND-05: renamed from TolvynGoogle Google so stack traces and constructor.name
13
+ // match the public API. Old name remains re-exportable via index.ts alias.
14
+ class Google extends generative_ai_1.GoogleGenerativeAI {
12
15
  constructor(options = {}) {
13
16
  const tolvynApiKey = options.tolvynApiKey ?? process.env['TOLVYN_API_KEY'];
14
17
  if (!tolvynApiKey) {
15
18
  throw new Error('tolvynApiKey required. Set TOLVYN_API_KEY env var or pass tolvynApiKey.');
16
19
  }
17
- // GoogleGenerativeAI sends this value as x-goog-api-key.
18
- // The TOLVYN proxy's extractBearer reads x-goog-api-key as a fallback.
20
+ // ND-06: The TOLVYN API key is passed as the GoogleGenerativeAI api key.
21
+ // Google's SDK sends this as `x-goog-api-key`; the TOLVYN proxy reads
22
+ // `x-goog-api-key` as a Bearer-auth fallback for Google requests.
23
+ // This is intentional — keep this comment if you refactor.
19
24
  super(tolvynApiKey);
20
25
  this._tolvynProxyUrl =
21
26
  options.proxyUrl ??
@@ -44,8 +49,32 @@ class TolvynGoogle extends generative_ai_1.GoogleGenerativeAI {
44
49
  customHeaders: this._tolvynHeaders,
45
50
  ...requestOptions,
46
51
  };
47
- return super.getGenerativeModel(modelParams, mergedOptions);
52
+ const model = super.getGenerativeModel(modelParams, mergedOptions);
53
+ // ND-01: wrap generateContent so a proxy-unreachable error retries against
54
+ // the real Google API using the fallback key.
55
+ if (!this._tolvynFailOpen || !this._tolvynFallbackKey) {
56
+ return model;
57
+ }
58
+ const fallbackKey = this._tolvynFallbackKey;
59
+ const originalGenerate = model.generateContent.bind(model);
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ model.generateContent = async (...args) => {
62
+ try {
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ return await originalGenerate(...args);
65
+ }
66
+ catch (e) {
67
+ if (!(0, failopen_1.isProxyError)(e))
68
+ throw e;
69
+ console.error('TOLVYN proxy unreachable — routing direct to Google (fail-open)');
70
+ const fallbackAI = new generative_ai_1.GoogleGenerativeAI(fallbackKey);
71
+ const fallbackModel = fallbackAI.getGenerativeModel(modelParams, requestOptions);
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
+ return fallbackModel.generateContent(...args);
74
+ }
75
+ };
76
+ return model;
48
77
  }
49
78
  }
50
- exports.TolvynGoogle = TolvynGoogle;
79
+ exports.Google = Google;
51
80
  //# sourceMappingURL=google.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/google.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,yDAI+B;AAE/B,4EAA4E;AAC5E,iFAAiF;AACjF,MAAM,wBAAwB,GAAG,0CAA0C,CAAC;AAe5E,MAAa,YAAa,SAAQ,kCAAkB;IAMlD,YAAY,UAA+B,EAAE;QAC3C,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC3E,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;QACJ,CAAC;QAED,yDAAyD;QACzD,uEAAuE;QACvE,KAAK,CAAC,YAAY,CAAC,CAAC;QAEpB,IAAI,CAAC,eAAe;YAClB,OAAO,CAAC,QAAQ;gBAChB,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;gBAC/B,wBAAwB,CAAC;QAE3B,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,IAAI;YAAS,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,GAAW,OAAO,CAAC,IAAI,CAAC;QACrF,IAAI,OAAO,CAAC,OAAO;YAAM,IAAI,CAAC,cAAc,CAAC,kBAAkB,CAAC,GAAQ,OAAO,CAAC,OAAO,CAAC;QACxF,IAAI,OAAO,CAAC,OAAO;YAAM,IAAI,CAAC,cAAc,CAAC,kBAAkB,CAAC,GAAQ,OAAO,CAAC,OAAO,CAAC;QACxF,IAAI,OAAO,CAAC,KAAK;YAAQ,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,GAAU,OAAO,CAAC,KAAK,CAAC;QACtF,IAAI,OAAO,CAAC,IAAI;YAAS,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,GAAW,OAAO,CAAC,IAAI,CAAC;QACrF,IAAI,OAAO,CAAC,WAAW;YAAE,IAAI,CAAC,cAAc,CAAC,uBAAuB,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;QAE5F,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC;QAChD,IAAI,CAAC,kBAAkB;YACrB,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC1D,CAAC;IAEQ,kBAAkB,CACzB,WAAwB,EACxB,cAA+B;QAE/B,MAAM,aAAa,GAAmB;YACpC,OAAO,EAAE,IAAI,CAAC,eAAe;YAC7B,aAAa,EAAE,IAAI,CAAC,cAAc;YAClC,GAAG,cAAc;SAClB,CAAC;QACF,OAAO,KAAK,CAAC,kBAAkB,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAC9D,CAAC;CACF;AA/CD,oCA+CC"}
1
+ {"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/google.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,6 @@ export { OpenAI } from './client';
2
2
  export type { TolvynOpenAIOptions } from './client';
3
3
  export { Anthropic } from './anthropic';
4
4
  export type { TolvynAnthropicOptions } from './anthropic';
5
- export { TolvynGoogle as Google } from './google';
5
+ export { Google } from './google';
6
6
  export type { TolvynGoogleOptions } from './google';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,YAAY,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC"}
package/dist/cjs/index.js CHANGED
@@ -6,5 +6,5 @@ Object.defineProperty(exports, "OpenAI", { enumerable: true, get: function () {
6
6
  var anthropic_1 = require("./anthropic");
7
7
  Object.defineProperty(exports, "Anthropic", { enumerable: true, get: function () { return anthropic_1.Anthropic; } });
8
8
  var google_1 = require("./google");
9
- Object.defineProperty(exports, "Google", { enumerable: true, get: function () { return google_1.TolvynGoogle; } });
9
+ Object.defineProperty(exports, "Google", { enumerable: true, get: function () { return google_1.Google; } });
10
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAAkC;AAAzB,gGAAA,MAAM,OAAA;AAEf,yCAAwC;AAA/B,sGAAA,SAAS,OAAA;AAElB,mCAAkD;AAAzC,gGAAA,YAAY,OAAU"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAAkC;AAAzB,gGAAA,MAAM,OAAA;AAEf,yCAAwC;AAA/B,sGAAA,SAAS,OAAA;AAElB,mCAAkC;AAAzB,gGAAA,MAAM,OAAA"}
@@ -14,7 +14,7 @@ export interface TolvynGoogleOptions {
14
14
  failOpen?: boolean;
15
15
  googleApiKey?: string;
16
16
  }
17
- export declare class TolvynGoogle extends GoogleGenerativeAI {
17
+ export declare class Google extends GoogleGenerativeAI {
18
18
  readonly _tolvynFailOpen: boolean;
19
19
  readonly _tolvynFallbackKey: string | undefined;
20
20
  private readonly _tolvynProxyUrl;
@@ -1 +1 @@
1
- {"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/google.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,cAAc,EACf,MAAM,uBAAuB,CAAC;AAM/B,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,YAAa,SAAQ,kBAAkB;IAClD,SAAgB,eAAe,EAAE,OAAO,CAAC;IACzC,SAAgB,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IACvD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;gBAE5C,OAAO,GAAE,mBAAwB;IA8BpC,kBAAkB,CACzB,WAAW,EAAE,WAAW,EACxB,cAAc,CAAC,EAAE,cAAc;CASlC"}
1
+ {"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/google.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,cAAc,EACf,MAAM,uBAAuB,CAAC;AAO/B,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,qBAAa,MAAO,SAAQ,kBAAkB;IAC5C,SAAgB,eAAe,EAAE,OAAO,CAAC;IACzC,SAAgB,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IACvD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;gBAE5C,OAAO,GAAE,mBAAwB;IAgCpC,kBAAkB,CACzB,WAAW,EAAE,WAAW,EACxB,cAAc,CAAC,EAAE,cAAc;CAkClC"}
@@ -2,17 +2,22 @@
2
2
  * TOLVYN Google wrapper — thin drop-in over @google/generative-ai.
3
3
  */
4
4
  import { GoogleGenerativeAI, } from '@google/generative-ai';
5
+ import { isProxyError } from './failopen';
5
6
  // The proxy base URL is prepended to Google API paths (/v1beta/models/...).
6
7
  // The TOLVYN proxy strips /v1/proxy/google and forwards the remainder to Google.
7
8
  const GOOGLE_DEFAULT_PROXY_URL = 'https://proxy.tolvyn.io/v1/proxy/google/';
8
- export class TolvynGoogle extends GoogleGenerativeAI {
9
+ // ND-05: renamed from TolvynGoogle Google so stack traces and constructor.name
10
+ // match the public API. Old name remains re-exportable via index.ts alias.
11
+ export class Google extends GoogleGenerativeAI {
9
12
  constructor(options = {}) {
10
13
  const tolvynApiKey = options.tolvynApiKey ?? process.env['TOLVYN_API_KEY'];
11
14
  if (!tolvynApiKey) {
12
15
  throw new Error('tolvynApiKey required. Set TOLVYN_API_KEY env var or pass tolvynApiKey.');
13
16
  }
14
- // GoogleGenerativeAI sends this value as x-goog-api-key.
15
- // The TOLVYN proxy's extractBearer reads x-goog-api-key as a fallback.
17
+ // ND-06: The TOLVYN API key is passed as the GoogleGenerativeAI api key.
18
+ // Google's SDK sends this as `x-goog-api-key`; the TOLVYN proxy reads
19
+ // `x-goog-api-key` as a Bearer-auth fallback for Google requests.
20
+ // This is intentional — keep this comment if you refactor.
16
21
  super(tolvynApiKey);
17
22
  this._tolvynProxyUrl =
18
23
  options.proxyUrl ??
@@ -41,7 +46,31 @@ export class TolvynGoogle extends GoogleGenerativeAI {
41
46
  customHeaders: this._tolvynHeaders,
42
47
  ...requestOptions,
43
48
  };
44
- return super.getGenerativeModel(modelParams, mergedOptions);
49
+ const model = super.getGenerativeModel(modelParams, mergedOptions);
50
+ // ND-01: wrap generateContent so a proxy-unreachable error retries against
51
+ // the real Google API using the fallback key.
52
+ if (!this._tolvynFailOpen || !this._tolvynFallbackKey) {
53
+ return model;
54
+ }
55
+ const fallbackKey = this._tolvynFallbackKey;
56
+ const originalGenerate = model.generateContent.bind(model);
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ model.generateContent = async (...args) => {
59
+ try {
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ return await originalGenerate(...args);
62
+ }
63
+ catch (e) {
64
+ if (!isProxyError(e))
65
+ throw e;
66
+ console.error('TOLVYN proxy unreachable — routing direct to Google (fail-open)');
67
+ const fallbackAI = new GoogleGenerativeAI(fallbackKey);
68
+ const fallbackModel = fallbackAI.getGenerativeModel(modelParams, requestOptions);
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ return fallbackModel.generateContent(...args);
71
+ }
72
+ };
73
+ return model;
45
74
  }
46
75
  }
47
76
  //# sourceMappingURL=google.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/google.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EACL,kBAAkB,GAGnB,MAAM,uBAAuB,CAAC;AAE/B,4EAA4E;AAC5E,iFAAiF;AACjF,MAAM,wBAAwB,GAAG,0CAA0C,CAAC;AAe5E,MAAM,OAAO,YAAa,SAAQ,kBAAkB;IAMlD,YAAY,UAA+B,EAAE;QAC3C,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC3E,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;QACJ,CAAC;QAED,yDAAyD;QACzD,uEAAuE;QACvE,KAAK,CAAC,YAAY,CAAC,CAAC;QAEpB,IAAI,CAAC,eAAe;YAClB,OAAO,CAAC,QAAQ;gBAChB,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;gBAC/B,wBAAwB,CAAC;QAE3B,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,IAAI;YAAS,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,GAAW,OAAO,CAAC,IAAI,CAAC;QACrF,IAAI,OAAO,CAAC,OAAO;YAAM,IAAI,CAAC,cAAc,CAAC,kBAAkB,CAAC,GAAQ,OAAO,CAAC,OAAO,CAAC;QACxF,IAAI,OAAO,CAAC,OAAO;YAAM,IAAI,CAAC,cAAc,CAAC,kBAAkB,CAAC,GAAQ,OAAO,CAAC,OAAO,CAAC;QACxF,IAAI,OAAO,CAAC,KAAK;YAAQ,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,GAAU,OAAO,CAAC,KAAK,CAAC;QACtF,IAAI,OAAO,CAAC,IAAI;YAAS,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,GAAW,OAAO,CAAC,IAAI,CAAC;QACrF,IAAI,OAAO,CAAC,WAAW;YAAE,IAAI,CAAC,cAAc,CAAC,uBAAuB,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;QAE5F,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC;QAChD,IAAI,CAAC,kBAAkB;YACrB,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC1D,CAAC;IAEQ,kBAAkB,CACzB,WAAwB,EACxB,cAA+B;QAE/B,MAAM,aAAa,GAAmB;YACpC,OAAO,EAAE,IAAI,CAAC,eAAe;YAC7B,aAAa,EAAE,IAAI,CAAC,cAAc;YAClC,GAAG,cAAc;SAClB,CAAC;QACF,OAAO,KAAK,CAAC,kBAAkB,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAC9D,CAAC;CACF"}
1
+ {"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/google.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,6 @@ export { OpenAI } from './client';
2
2
  export type { TolvynOpenAIOptions } from './client';
3
3
  export { Anthropic } from './anthropic';
4
4
  export type { TolvynAnthropicOptions } from './anthropic';
5
- export { TolvynGoogle as Google } from './google';
5
+ export { Google } from './google';
6
6
  export type { TolvynGoogleOptions } from './google';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,YAAY,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC"}
package/dist/esm/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { OpenAI } from './client';
2
2
  export { Anthropic } from './anthropic';
3
- export { TolvynGoogle as Google } from './google';
3
+ export { Google } from './google';
4
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,YAAY,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tolvyn",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Drop-in replacement for the OpenAI/Anthropic SDK — routes through TOLVYN for cost attribution, budget enforcement, and audit logging.",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -23,9 +23,16 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@anthropic-ai/sdk": "^0.20.0",
26
- "@google/generative-ai": "^0.21.0",
27
26
  "openai": "^4.0.0"
28
27
  },
28
+ "peerDependencies": {
29
+ "@google/generative-ai": "^0.21.0"
30
+ },
31
+ "peerDependenciesMeta": {
32
+ "@google/generative-ai": {
33
+ "optional": true
34
+ }
35
+ },
29
36
  "devDependencies": {
30
37
  "@types/jest": "^29.5.0",
31
38
  "@types/node": "^20.0.0",
package/src/google.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  ModelParams,
7
7
  RequestOptions,
8
8
  } from '@google/generative-ai';
9
+ import { isProxyError } from './failopen';
9
10
 
10
11
  // The proxy base URL is prepended to Google API paths (/v1beta/models/...).
11
12
  // The TOLVYN proxy strips /v1/proxy/google and forwards the remainder to Google.
@@ -24,7 +25,9 @@ export interface TolvynGoogleOptions {
24
25
  googleApiKey?: string;
25
26
  }
26
27
 
27
- export class TolvynGoogle extends GoogleGenerativeAI {
28
+ // ND-05: renamed from TolvynGoogle Google so stack traces and constructor.name
29
+ // match the public API. Old name remains re-exportable via index.ts alias.
30
+ export class Google extends GoogleGenerativeAI {
28
31
  public readonly _tolvynFailOpen: boolean;
29
32
  public readonly _tolvynFallbackKey: string | undefined;
30
33
  private readonly _tolvynProxyUrl: string;
@@ -38,8 +41,10 @@ export class TolvynGoogle extends GoogleGenerativeAI {
38
41
  );
39
42
  }
40
43
 
41
- // GoogleGenerativeAI sends this value as x-goog-api-key.
42
- // The TOLVYN proxy's extractBearer reads x-goog-api-key as a fallback.
44
+ // ND-06: The TOLVYN API key is passed as the GoogleGenerativeAI api key.
45
+ // Google's SDK sends this as `x-goog-api-key`; the TOLVYN proxy reads
46
+ // `x-goog-api-key` as a Bearer-auth fallback for Google requests.
47
+ // This is intentional — keep this comment if you refactor.
43
48
  super(tolvynApiKey);
44
49
 
45
50
  this._tolvynProxyUrl =
@@ -69,6 +74,31 @@ export class TolvynGoogle extends GoogleGenerativeAI {
69
74
  customHeaders: this._tolvynHeaders,
70
75
  ...requestOptions,
71
76
  };
72
- return super.getGenerativeModel(modelParams, mergedOptions);
77
+ const model = super.getGenerativeModel(modelParams, mergedOptions);
78
+
79
+ // ND-01: wrap generateContent so a proxy-unreachable error retries against
80
+ // the real Google API using the fallback key.
81
+ if (!this._tolvynFailOpen || !this._tolvynFallbackKey) {
82
+ return model;
83
+ }
84
+ const fallbackKey = this._tolvynFallbackKey;
85
+ const originalGenerate = model.generateContent.bind(model);
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ (model as any).generateContent = async (...args: unknown[]) => {
88
+ try {
89
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
+ return await (originalGenerate as any)(...args);
91
+ } catch (e) {
92
+ if (!isProxyError(e)) throw e;
93
+ console.error(
94
+ 'TOLVYN proxy unreachable — routing direct to Google (fail-open)',
95
+ );
96
+ const fallbackAI = new GoogleGenerativeAI(fallbackKey);
97
+ const fallbackModel = fallbackAI.getGenerativeModel(modelParams, requestOptions);
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ return (fallbackModel.generateContent as any)(...args);
100
+ }
101
+ };
102
+ return model;
73
103
  }
74
104
  }
package/src/index.ts CHANGED
@@ -2,5 +2,5 @@ export { OpenAI } from './client';
2
2
  export type { TolvynOpenAIOptions } from './client';
3
3
  export { Anthropic } from './anthropic';
4
4
  export type { TolvynAnthropicOptions } from './anthropic';
5
- export { TolvynGoogle as Google } from './google';
5
+ export { Google } from './google';
6
6
  export type { TolvynGoogleOptions } from './google';