gavio 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/README.md +95 -0
  2. package/dist/cjs/context.js +47 -0
  3. package/dist/cjs/errors.js +57 -0
  4. package/dist/cjs/gateway.js +127 -0
  5. package/dist/cjs/ids.js +60 -0
  6. package/dist/cjs/index.js +49 -0
  7. package/dist/cjs/interceptors/audit/index.js +12 -0
  8. package/dist/cjs/interceptors/audit/interceptor.js +77 -0
  9. package/dist/cjs/interceptors/audit/record.js +107 -0
  10. package/dist/cjs/interceptors/audit/sink.js +3 -0
  11. package/dist/cjs/interceptors/audit/sinks/index.js +5 -0
  12. package/dist/cjs/interceptors/audit/sinks/stdout.js +33 -0
  13. package/dist/cjs/interceptors/base.js +7 -0
  14. package/dist/cjs/interceptors/cache/backend.js +9 -0
  15. package/dist/cjs/interceptors/cache/backends/index.js +5 -0
  16. package/dist/cjs/interceptors/cache/backends/memory.js +53 -0
  17. package/dist/cjs/interceptors/cache/index.js +9 -0
  18. package/dist/cjs/interceptors/chain.js +57 -0
  19. package/dist/cjs/interceptors/index.js +18 -0
  20. package/dist/cjs/interceptors/pii/context.js +25 -0
  21. package/dist/cjs/interceptors/pii/guard.js +161 -0
  22. package/dist/cjs/interceptors/pii/index.js +28 -0
  23. package/dist/cjs/interceptors/pii/match.js +21 -0
  24. package/dist/cjs/interceptors/pii/scanner.js +31 -0
  25. package/dist/cjs/interceptors/pii/scanners/bsn.js +41 -0
  26. package/dist/cjs/interceptors/pii/scanners/credit-card.js +51 -0
  27. package/dist/cjs/interceptors/pii/scanners/email.js +26 -0
  28. package/dist/cjs/interceptors/pii/scanners/iban.js +58 -0
  29. package/dist/cjs/interceptors/pii/scanners/index.js +45 -0
  30. package/dist/cjs/interceptors/pii/scanners/ip-address.js +36 -0
  31. package/dist/cjs/interceptors/pii/scanners/phone.js +37 -0
  32. package/dist/cjs/interceptors/pii/scanners/secret.js +46 -0
  33. package/dist/cjs/interceptors/pii/scanners/ssn.js +28 -0
  34. package/dist/cjs/interceptors/reliability/fallback.js +53 -0
  35. package/dist/cjs/interceptors/reliability/index.js +11 -0
  36. package/dist/cjs/interceptors/reliability/retry.js +69 -0
  37. package/dist/cjs/interceptors/reliability/timeout.js +41 -0
  38. package/dist/cjs/package.json +3 -0
  39. package/dist/cjs/pricing.js +70 -0
  40. package/dist/cjs/providers/anthropic.js +80 -0
  41. package/dist/cjs/providers/base.js +30 -0
  42. package/dist/cjs/providers/http.js +42 -0
  43. package/dist/cjs/providers/index.js +34 -0
  44. package/dist/cjs/providers/mock.js +54 -0
  45. package/dist/cjs/providers/openai.js +63 -0
  46. package/dist/cjs/request.js +60 -0
  47. package/dist/cjs/response.js +55 -0
  48. package/dist/cjs/testing/harness.js +70 -0
  49. package/dist/cjs/testing/index.js +8 -0
  50. package/dist/cjs/types.js +61 -0
  51. package/dist/esm/context.d.ts +33 -0
  52. package/dist/esm/context.js +43 -0
  53. package/dist/esm/errors.d.ts +36 -0
  54. package/dist/esm/errors.js +44 -0
  55. package/dist/esm/gateway.d.ts +54 -0
  56. package/dist/esm/gateway.js +123 -0
  57. package/dist/esm/ids.d.ts +11 -0
  58. package/dist/esm/ids.js +56 -0
  59. package/dist/esm/index.d.ts +25 -0
  60. package/dist/esm/index.js +20 -0
  61. package/dist/esm/interceptors/audit/index.d.ts +7 -0
  62. package/dist/esm/interceptors/audit/index.js +3 -0
  63. package/dist/esm/interceptors/audit/interceptor.d.ts +11 -0
  64. package/dist/esm/interceptors/audit/interceptor.js +72 -0
  65. package/dist/esm/interceptors/audit/record.d.ts +66 -0
  66. package/dist/esm/interceptors/audit/record.js +103 -0
  67. package/dist/esm/interceptors/audit/sink.d.ts +8 -0
  68. package/dist/esm/interceptors/audit/sink.js +2 -0
  69. package/dist/esm/interceptors/audit/sinks/index.d.ts +2 -0
  70. package/dist/esm/interceptors/audit/sinks/index.js +1 -0
  71. package/dist/esm/interceptors/audit/sinks/stdout.d.ts +8 -0
  72. package/dist/esm/interceptors/audit/sinks/stdout.js +30 -0
  73. package/dist/esm/interceptors/base.d.ts +37 -0
  74. package/dist/esm/interceptors/base.js +4 -0
  75. package/dist/esm/interceptors/cache/backend.d.ts +14 -0
  76. package/dist/esm/interceptors/cache/backend.js +8 -0
  77. package/dist/esm/interceptors/cache/backends/index.d.ts +2 -0
  78. package/dist/esm/interceptors/cache/backends/index.js +1 -0
  79. package/dist/esm/interceptors/cache/backends/memory.d.ts +7 -0
  80. package/dist/esm/interceptors/cache/backends/memory.js +50 -0
  81. package/dist/esm/interceptors/cache/index.d.ts +7 -0
  82. package/dist/esm/interceptors/cache/index.js +5 -0
  83. package/dist/esm/interceptors/chain.d.ts +17 -0
  84. package/dist/esm/interceptors/chain.js +53 -0
  85. package/dist/esm/interceptors/index.d.ts +8 -0
  86. package/dist/esm/interceptors/index.js +7 -0
  87. package/dist/esm/interceptors/pii/context.d.ts +15 -0
  88. package/dist/esm/interceptors/pii/context.js +21 -0
  89. package/dist/esm/interceptors/pii/guard.d.ts +30 -0
  90. package/dist/esm/interceptors/pii/guard.js +157 -0
  91. package/dist/esm/interceptors/pii/index.d.ts +10 -0
  92. package/dist/esm/interceptors/pii/index.js +7 -0
  93. package/dist/esm/interceptors/pii/match.d.ts +26 -0
  94. package/dist/esm/interceptors/pii/match.js +17 -0
  95. package/dist/esm/interceptors/pii/scanner.d.ts +32 -0
  96. package/dist/esm/interceptors/pii/scanner.js +26 -0
  97. package/dist/esm/interceptors/pii/scanners/bsn.d.ts +5 -0
  98. package/dist/esm/interceptors/pii/scanners/bsn.js +37 -0
  99. package/dist/esm/interceptors/pii/scanners/credit-card.d.ts +4 -0
  100. package/dist/esm/interceptors/pii/scanners/credit-card.js +47 -0
  101. package/dist/esm/interceptors/pii/scanners/email.d.ts +3 -0
  102. package/dist/esm/interceptors/pii/scanners/email.js +23 -0
  103. package/dist/esm/interceptors/pii/scanners/iban.d.ts +5 -0
  104. package/dist/esm/interceptors/pii/scanners/iban.js +54 -0
  105. package/dist/esm/interceptors/pii/scanners/index.d.ts +13 -0
  106. package/dist/esm/interceptors/pii/scanners/index.js +30 -0
  107. package/dist/esm/interceptors/pii/scanners/ip-address.d.ts +3 -0
  108. package/dist/esm/interceptors/pii/scanners/ip-address.js +33 -0
  109. package/dist/esm/interceptors/pii/scanners/phone.d.ts +6 -0
  110. package/dist/esm/interceptors/pii/scanners/phone.js +34 -0
  111. package/dist/esm/interceptors/pii/scanners/secret.d.ts +9 -0
  112. package/dist/esm/interceptors/pii/scanners/secret.js +43 -0
  113. package/dist/esm/interceptors/pii/scanners/ssn.d.ts +3 -0
  114. package/dist/esm/interceptors/pii/scanners/ssn.js +25 -0
  115. package/dist/esm/interceptors/reliability/fallback.d.ts +9 -0
  116. package/dist/esm/interceptors/reliability/fallback.js +50 -0
  117. package/dist/esm/interceptors/reliability/index.d.ts +7 -0
  118. package/dist/esm/interceptors/reliability/index.js +4 -0
  119. package/dist/esm/interceptors/reliability/retry.d.ts +13 -0
  120. package/dist/esm/interceptors/reliability/retry.js +66 -0
  121. package/dist/esm/interceptors/reliability/timeout.d.ts +9 -0
  122. package/dist/esm/interceptors/reliability/timeout.js +37 -0
  123. package/dist/esm/package.json +3 -0
  124. package/dist/esm/pricing.d.ts +19 -0
  125. package/dist/esm/pricing.js +65 -0
  126. package/dist/esm/providers/anthropic.d.ts +30 -0
  127. package/dist/esm/providers/anthropic.js +77 -0
  128. package/dist/esm/providers/base.d.ts +23 -0
  129. package/dist/esm/providers/base.js +28 -0
  130. package/dist/esm/providers/http.d.ts +8 -0
  131. package/dist/esm/providers/http.js +39 -0
  132. package/dist/esm/providers/index.d.ts +15 -0
  133. package/dist/esm/providers/index.js +25 -0
  134. package/dist/esm/providers/mock.d.ts +31 -0
  135. package/dist/esm/providers/mock.js +51 -0
  136. package/dist/esm/providers/openai.d.ts +26 -0
  137. package/dist/esm/providers/openai.js +60 -0
  138. package/dist/esm/request.d.ts +36 -0
  139. package/dist/esm/request.js +56 -0
  140. package/dist/esm/response.d.ts +38 -0
  141. package/dist/esm/response.js +51 -0
  142. package/dist/esm/testing/harness.d.ts +37 -0
  143. package/dist/esm/testing/harness.js +66 -0
  144. package/dist/esm/testing/index.d.ts +5 -0
  145. package/dist/esm/testing/index.js +3 -0
  146. package/dist/esm/types.d.ts +58 -0
  147. package/dist/esm/types.js +56 -0
  148. package/package.json +115 -0
  149. package/src/context.ts +57 -0
  150. package/src/errors.ts +47 -0
  151. package/src/gateway.ts +174 -0
  152. package/src/ids.ts +69 -0
  153. package/src/index.ts +52 -0
  154. package/src/interceptors/audit/index.ts +7 -0
  155. package/src/interceptors/audit/interceptor.ts +93 -0
  156. package/src/interceptors/audit/record.ts +138 -0
  157. package/src/interceptors/audit/sink.ts +10 -0
  158. package/src/interceptors/audit/sinks/index.ts +2 -0
  159. package/src/interceptors/audit/sinks/stdout.ts +42 -0
  160. package/src/interceptors/base.ts +58 -0
  161. package/src/interceptors/cache/backend.ts +15 -0
  162. package/src/interceptors/cache/backends/index.ts +2 -0
  163. package/src/interceptors/cache/backends/memory.ts +68 -0
  164. package/src/interceptors/cache/index.ts +8 -0
  165. package/src/interceptors/chain.ts +65 -0
  166. package/src/interceptors/index.ts +9 -0
  167. package/src/interceptors/pii/context.ts +24 -0
  168. package/src/interceptors/pii/guard.ts +201 -0
  169. package/src/interceptors/pii/index.ts +21 -0
  170. package/src/interceptors/pii/match.ts +43 -0
  171. package/src/interceptors/pii/scanner.ts +54 -0
  172. package/src/interceptors/pii/scanners/bsn.ts +44 -0
  173. package/src/interceptors/pii/scanners/credit-card.ts +52 -0
  174. package/src/interceptors/pii/scanners/email.ts +31 -0
  175. package/src/interceptors/pii/scanners/iban.ts +60 -0
  176. package/src/interceptors/pii/scanners/index.ts +35 -0
  177. package/src/interceptors/pii/scanners/ip-address.ts +41 -0
  178. package/src/interceptors/pii/scanners/phone.ts +46 -0
  179. package/src/interceptors/pii/scanners/secret.ts +51 -0
  180. package/src/interceptors/pii/scanners/ssn.ts +33 -0
  181. package/src/interceptors/reliability/fallback.ts +66 -0
  182. package/src/interceptors/reliability/index.ts +8 -0
  183. package/src/interceptors/reliability/retry.ts +97 -0
  184. package/src/interceptors/reliability/timeout.ts +53 -0
  185. package/src/pricing.ts +72 -0
  186. package/src/providers/anthropic.ts +113 -0
  187. package/src/providers/base.ts +52 -0
  188. package/src/providers/http.ts +50 -0
  189. package/src/providers/index.ts +39 -0
  190. package/src/providers/mock.ts +73 -0
  191. package/src/providers/openai.ts +94 -0
  192. package/src/request.ts +76 -0
  193. package/src/response.ts +73 -0
  194. package/src/testing/harness.ts +98 -0
  195. package/src/testing/index.ts +6 -0
  196. package/src/types.ts +83 -0
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /** Built-in tier-1 (regex) PII scanners. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ssnScanner = exports.secretScanner = exports.phoneScanner = exports.ipAddressScanner = exports.validIban = exports.ibanScanner = exports.emailScanner = exports.luhnValid = exports.creditCardScanner = exports.validBsn = exports.bsnScanner = void 0;
5
+ exports.defaultScanners = defaultScanners;
6
+ const bsn_js_1 = require("./bsn.js");
7
+ const credit_card_js_1 = require("./credit-card.js");
8
+ const email_js_1 = require("./email.js");
9
+ const iban_js_1 = require("./iban.js");
10
+ const ip_address_js_1 = require("./ip-address.js");
11
+ const phone_js_1 = require("./phone.js");
12
+ const secret_js_1 = require("./secret.js");
13
+ const ssn_js_1 = require("./ssn.js");
14
+ var bsn_js_2 = require("./bsn.js");
15
+ Object.defineProperty(exports, "bsnScanner", { enumerable: true, get: function () { return bsn_js_2.bsnScanner; } });
16
+ Object.defineProperty(exports, "validBsn", { enumerable: true, get: function () { return bsn_js_2.validBsn; } });
17
+ var credit_card_js_2 = require("./credit-card.js");
18
+ Object.defineProperty(exports, "creditCardScanner", { enumerable: true, get: function () { return credit_card_js_2.creditCardScanner; } });
19
+ Object.defineProperty(exports, "luhnValid", { enumerable: true, get: function () { return credit_card_js_2.luhnValid; } });
20
+ var email_js_2 = require("./email.js");
21
+ Object.defineProperty(exports, "emailScanner", { enumerable: true, get: function () { return email_js_2.emailScanner; } });
22
+ var iban_js_2 = require("./iban.js");
23
+ Object.defineProperty(exports, "ibanScanner", { enumerable: true, get: function () { return iban_js_2.ibanScanner; } });
24
+ Object.defineProperty(exports, "validIban", { enumerable: true, get: function () { return iban_js_2.validIban; } });
25
+ var ip_address_js_2 = require("./ip-address.js");
26
+ Object.defineProperty(exports, "ipAddressScanner", { enumerable: true, get: function () { return ip_address_js_2.ipAddressScanner; } });
27
+ var phone_js_2 = require("./phone.js");
28
+ Object.defineProperty(exports, "phoneScanner", { enumerable: true, get: function () { return phone_js_2.phoneScanner; } });
29
+ var secret_js_2 = require("./secret.js");
30
+ Object.defineProperty(exports, "secretScanner", { enumerable: true, get: function () { return secret_js_2.secretScanner; } });
31
+ var ssn_js_2 = require("./ssn.js");
32
+ Object.defineProperty(exports, "ssnScanner", { enumerable: true, get: function () { return ssn_js_2.ssnScanner; } });
33
+ /** The default scanner set wired into PiiGuard when none is supplied. */
34
+ function defaultScanners() {
35
+ return [
36
+ (0, secret_js_1.secretScanner)(),
37
+ (0, email_js_1.emailScanner)(),
38
+ (0, iban_js_1.ibanScanner)(),
39
+ (0, bsn_js_1.bsnScanner)(),
40
+ (0, credit_card_js_1.creditCardScanner)(),
41
+ (0, ssn_js_1.ssnScanner)(),
42
+ (0, phone_js_1.phoneScanner)(),
43
+ (0, ip_address_js_1.ipAddressScanner)(),
44
+ ];
45
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /** IP address scanner — IPv4 and IPv6, validated via node:net isIP. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ipAddressScanner = ipAddressScanner;
5
+ const node_net_1 = require("node:net");
6
+ const match_js_1 = require("../match.js");
7
+ const IPV4 = String.raw `(?:\d{1,3}\.){3}\d{1,3}`;
8
+ // Permissive IPv6 candidate — allows empty groups for "::" compression. False
9
+ // positives are filtered by isIP validation below.
10
+ const IPV6 = String.raw `(?:[A-Fa-f0-9]{0,4}:){2,7}[A-Fa-f0-9]{0,4}`;
11
+ const IP = new RegExp(String.raw `(?<![\w.])(?:${IPV6}|${IPV4})(?![\w.])`, 'g');
12
+ function validIp(candidate) {
13
+ return (0, node_net_1.isIP)(candidate) !== 0;
14
+ }
15
+ function ipAddressScanner() {
16
+ return {
17
+ entityType: 'IP_ADDRESS',
18
+ tier: 1,
19
+ scan(text, ctx) {
20
+ const out = [];
21
+ for (const m of text.matchAll(IP)) {
22
+ if (!validIp(m[0]))
23
+ continue;
24
+ const idx = ctx.nextIndex('IP_ADDRESS');
25
+ out.push((0, match_js_1.makeMatch)({
26
+ entityType: 'IP_ADDRESS',
27
+ start: m.index,
28
+ end: m.index + m[0].length,
29
+ value: m[0],
30
+ replacement: `[IP_ADDRESS_${idx}]`,
31
+ }));
32
+ }
33
+ return out;
34
+ },
35
+ };
36
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /** Phone number scanner — E.164 and common national formats. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.phoneScanner = phoneScanner;
5
+ const match_js_1 = require("../match.js");
6
+ // E.164 (+CC...) or national groupings with separators. A digit-count filter
7
+ // (7–15) below avoids matching short numbers / years.
8
+ const PHONE = /(?<![\w.])(?:\+?\d{1,3}[ .-]?)?(?:\(\d{1,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){2,4}(?![\w])/g;
9
+ function phoneScanner(options = {}) {
10
+ const locales = options.locales ?? ['NL', 'DE', 'GB', 'US'];
11
+ return {
12
+ entityType: 'PHONE',
13
+ tier: 1,
14
+ confidence: 0.85,
15
+ scan(text, ctx) {
16
+ const out = [];
17
+ for (const m of text.matchAll(PHONE)) {
18
+ const digitCount = (m[0].match(/\d/g) ?? []).length;
19
+ if (digitCount < 7 || digitCount > 15)
20
+ continue;
21
+ const idx = ctx.nextIndex('PHONE');
22
+ out.push((0, match_js_1.makeMatch)({
23
+ entityType: 'PHONE',
24
+ start: m.index,
25
+ end: m.index + m[0].length,
26
+ value: m[0],
27
+ confidence: 0.85,
28
+ replacement: `[PHONE_${idx}]`,
29
+ }));
30
+ }
31
+ return out;
32
+ },
33
+ supportsLocale(locale) {
34
+ return locales.includes(locale.toUpperCase());
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ /**
3
+ * Secret / credential scanner (F-SEC-04).
4
+ *
5
+ * Detects API keys, tokens, JWTs, PEM private keys, and database connection
6
+ * strings. These must never leave the device, so SecretScanner is tier 1 and
7
+ * runs by default.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.secretScanner = secretScanner;
11
+ const match_js_1 = require("../match.js");
12
+ // [label, pattern] — ordered most-specific first. All patterns are global.
13
+ const PATTERNS = [
14
+ ['ANTHROPIC_KEY', /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g],
15
+ ['OPENAI_KEY', /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g],
16
+ ['AWS_ACCESS_KEY', /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g],
17
+ ['GITHUB_TOKEN', /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g],
18
+ ['JWT', /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g],
19
+ ['PRIVATE_KEY', /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/g],
20
+ [
21
+ 'DB_CONNECTION_STRING',
22
+ /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis):\/\/[^\s"']+/g,
23
+ ],
24
+ ];
25
+ function secretScanner() {
26
+ return {
27
+ entityType: 'SECRET',
28
+ tier: 1,
29
+ scan(text, ctx) {
30
+ const out = [];
31
+ for (const [, pattern] of PATTERNS) {
32
+ for (const m of text.matchAll(pattern)) {
33
+ const idx = ctx.nextIndex('SECRET');
34
+ out.push((0, match_js_1.makeMatch)({
35
+ entityType: 'SECRET',
36
+ start: m.index,
37
+ end: m.index + m[0].length,
38
+ value: m[0],
39
+ replacement: `[SECRET_${idx}]`,
40
+ }));
41
+ }
42
+ }
43
+ return out;
44
+ },
45
+ };
46
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /** US Social Security Number scanner. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ssnScanner = ssnScanner;
5
+ const match_js_1 = require("../match.js");
6
+ // AAA-GG-SSSS with hyphens or spaces. Requires a separator to avoid colliding
7
+ // with bare 9-digit numbers (handled by the BSN scanner / others).
8
+ const SSN = /\b(?!000|666|9\d\d)\d{3}[ -](?!00)\d{2}[ -](?!0000)\d{4}\b/g;
9
+ function ssnScanner() {
10
+ return {
11
+ entityType: 'SSN',
12
+ tier: 1,
13
+ scan(text, ctx) {
14
+ const out = [];
15
+ for (const m of text.matchAll(SSN)) {
16
+ const idx = ctx.nextIndex('SSN');
17
+ out.push((0, match_js_1.makeMatch)({
18
+ entityType: 'SSN',
19
+ start: m.index,
20
+ end: m.index + m[0].length,
21
+ value: m[0],
22
+ replacement: `[SSN_${idx}]`,
23
+ }));
24
+ }
25
+ return out;
26
+ },
27
+ };
28
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /** fallbackChain (F-REL-02) — route to a secondary provider on failure. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.fallbackChain = fallbackChain;
5
+ const errors_js_1 = require("../../errors.js");
6
+ const types_js_1 = require("../../types.js");
7
+ /**
8
+ * Try the primary executor; on a provider error, try fallback adapters.
9
+ *
10
+ * Each fallback is a provider adapter (anything with an async `complete`). The
11
+ * request's provider/model are rewritten per fallback so the audit record
12
+ * reflects which provider actually answered.
13
+ */
14
+ class FallbackChain {
15
+ name = 'fallback';
16
+ isExecutorPolicy = true;
17
+ dryRunSafe = true;
18
+ fallbacks;
19
+ constructor(options) {
20
+ if (!options.fallbacks || options.fallbacks.length === 0) {
21
+ throw new Error('fallbackChain requires at least one fallback adapter');
22
+ }
23
+ this.fallbacks = options.fallbacks;
24
+ }
25
+ async around(request, ctx, callNext) {
26
+ ctx.markFired(this.name);
27
+ try {
28
+ return await callNext(request);
29
+ }
30
+ catch (primaryError) {
31
+ if (!(primaryError instanceof errors_js_1.ProviderError))
32
+ throw primaryError;
33
+ let lastError = primaryError;
34
+ for (const adapter of this.fallbacks) {
35
+ try {
36
+ const rerouted = request.copyWithMessages(request.messages);
37
+ rerouted.provider = (0, types_js_1.coerceProvider)(adapter.providerName);
38
+ return await adapter.complete(rerouted);
39
+ }
40
+ catch (error) {
41
+ if (!(error instanceof errors_js_1.ProviderError))
42
+ throw error;
43
+ lastError = error;
44
+ }
45
+ }
46
+ throw lastError;
47
+ }
48
+ }
49
+ }
50
+ /** Factory: build a fallback policy. */
51
+ function fallbackChain(options) {
52
+ return new FallbackChain(options);
53
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /** Reliability policies (F-REL-01, F-REL-02, F-REL-07). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.fallbackChain = exports.timeout = exports.timeoutPolicy = exports.retryInterceptor = void 0;
5
+ var retry_js_1 = require("./retry.js");
6
+ Object.defineProperty(exports, "retryInterceptor", { enumerable: true, get: function () { return retry_js_1.retryInterceptor; } });
7
+ var timeout_js_1 = require("./timeout.js");
8
+ Object.defineProperty(exports, "timeoutPolicy", { enumerable: true, get: function () { return timeout_js_1.timeoutPolicy; } });
9
+ Object.defineProperty(exports, "timeout", { enumerable: true, get: function () { return timeout_js_1.timeout; } });
10
+ var fallback_js_1 = require("./fallback.js");
11
+ Object.defineProperty(exports, "fallbackChain", { enumerable: true, get: function () { return fallback_js_1.fallbackChain; } });
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ /** retryInterceptor (F-REL-01) — exponential backoff with jitter. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.retryInterceptor = retryInterceptor;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const errors_js_1 = require("../../errors.js");
7
+ /** Default predicate: retry transient provider errors. */
8
+ function defaultRetryable(error) {
9
+ return (error instanceof errors_js_1.RateLimitError ||
10
+ error instanceof errors_js_1.TimeoutError ||
11
+ error instanceof errors_js_1.ServerError ||
12
+ error instanceof errors_js_1.ProviderUnavailableError);
13
+ }
14
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
15
+ class RetryInterceptor {
16
+ name = 'retry';
17
+ isExecutorPolicy = true;
18
+ dryRunSafe = true;
19
+ maxAttempts;
20
+ baseDelayMs;
21
+ maxDelayMs;
22
+ jitter;
23
+ retryOn;
24
+ sleep;
25
+ constructor(options = {}) {
26
+ const maxAttempts = options.maxAttempts ?? 3;
27
+ if (maxAttempts < 1)
28
+ throw new Error('maxAttempts must be >= 1');
29
+ this.maxAttempts = maxAttempts;
30
+ this.baseDelayMs = options.baseDelayMs ?? 500;
31
+ this.maxDelayMs = options.maxDelayMs ?? 10_000;
32
+ this.jitter = options.jitter ?? true;
33
+ this.retryOn = options.retryOn ?? defaultRetryable;
34
+ this.sleep = options.sleep ?? defaultSleep;
35
+ }
36
+ async around(request, ctx, callNext) {
37
+ ctx.markFired(this.name);
38
+ let lastError;
39
+ for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
40
+ try {
41
+ return await callNext(request);
42
+ }
43
+ catch (error) {
44
+ if (!this.retryOn(error))
45
+ throw error;
46
+ lastError = error;
47
+ if (attempt >= this.maxAttempts)
48
+ break;
49
+ await this.sleep(this.delayMs(attempt));
50
+ }
51
+ }
52
+ throw lastError;
53
+ }
54
+ delayMs(attempt) {
55
+ // Exponential: base * 2^(attempt-1), capped, with optional full jitter.
56
+ const raw = this.baseDelayMs * 2 ** (attempt - 1);
57
+ let capped = Math.min(raw, this.maxDelayMs);
58
+ if (this.jitter) {
59
+ // full jitter in [0, capped] using crypto bytes (no global RNG state)
60
+ const frac = (0, node_crypto_1.randomBytes)(2).readUInt16BE(0) / 0xffff;
61
+ capped *= frac;
62
+ }
63
+ return capped;
64
+ }
65
+ }
66
+ /** Factory: build a retry policy. */
67
+ function retryInterceptor(options = {}) {
68
+ return new RetryInterceptor(options);
69
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /** timeoutPolicy (F-REL-07) — per-request timeout enforcement. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.timeout = void 0;
5
+ exports.timeoutPolicy = timeoutPolicy;
6
+ const errors_js_1 = require("../../errors.js");
7
+ class TimeoutPolicy {
8
+ name = 'timeout';
9
+ isExecutorPolicy = true;
10
+ dryRunSafe = true;
11
+ timeoutSeconds;
12
+ constructor(options = {}) {
13
+ const timeoutSeconds = options.timeoutSeconds ?? 30.0;
14
+ if (timeoutSeconds <= 0)
15
+ throw new Error('timeoutSeconds must be > 0');
16
+ this.timeoutSeconds = timeoutSeconds;
17
+ }
18
+ async around(request, ctx, callNext) {
19
+ ctx.markFired(this.name);
20
+ const ms = this.timeoutSeconds * 1000;
21
+ let timer;
22
+ const timeout = new Promise((_, reject) => {
23
+ timer = setTimeout(() => {
24
+ reject(new errors_js_1.TimeoutError(`Request exceeded ${this.timeoutSeconds}s timeout`));
25
+ }, ms);
26
+ });
27
+ try {
28
+ return await Promise.race([callNext(request), timeout]);
29
+ }
30
+ finally {
31
+ if (timer !== undefined)
32
+ clearTimeout(timer);
33
+ }
34
+ }
35
+ }
36
+ /** Factory: build a timeout policy. */
37
+ function timeoutPolicy(options = {}) {
38
+ return new TimeoutPolicy(options);
39
+ }
40
+ /** Alias matching the SDK plan naming (`timeout`). */
41
+ exports.timeout = timeoutPolicy;
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * Token cost tracking (F-GOV-01).
4
+ *
5
+ * Prices are USD per 1,000 tokens, sourced from public provider pricing and
6
+ * overridable. Unknown models price at zero (warned once) rather than guessing.
7
+ * Prices are intentionally data, not code — update the table, not the estimator.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.PricingProvider = void 0;
11
+ exports.estimateTokens = estimateTokens;
12
+ /** model -> [inputPer1kUsd, outputPer1kUsd] */
13
+ const DEFAULT_PRICES = {
14
+ // OpenAI
15
+ 'gpt-4o': [0.0025, 0.01],
16
+ 'gpt-4o-mini': [0.00015, 0.0006],
17
+ o1: [0.015, 0.06],
18
+ 'o1-mini': [0.0011, 0.0044],
19
+ // Anthropic
20
+ 'claude-sonnet-4-6': [0.003, 0.015],
21
+ 'claude-sonnet-4-20250514': [0.003, 0.015],
22
+ 'claude-haiku-4-5': [0.0008, 0.004],
23
+ 'claude-opus-4-1': [0.015, 0.075],
24
+ // Local / mock are free.
25
+ mock: [0.0, 0.0],
26
+ };
27
+ /** Estimates request cost from token usage and a model price table. */
28
+ class PricingProvider {
29
+ prices;
30
+ warned = new Set();
31
+ constructor(prices) {
32
+ this.prices = { ...DEFAULT_PRICES, ...(prices ?? {}) };
33
+ }
34
+ setPrice(model, inputPer1k, outputPer1k) {
35
+ this.prices[model] = [inputPer1k, outputPer1k];
36
+ }
37
+ rates(model) {
38
+ const rate = this.prices[model];
39
+ if (rate !== undefined)
40
+ return rate;
41
+ // try a prefix match (e.g. "gpt-4o-2024-..." -> "gpt-4o")
42
+ for (const [known, value] of Object.entries(this.prices)) {
43
+ if (model.startsWith(known))
44
+ return value;
45
+ }
46
+ if (!this.warned.has(model)) {
47
+ // eslint-disable-next-line no-console
48
+ console.warn(`[gavio:pricing] no pricing for model '${model}'; treating as free`);
49
+ this.warned.add(model);
50
+ }
51
+ return [0.0, 0.0];
52
+ }
53
+ estimate(model, usage) {
54
+ const [inRate, outRate] = this.rates(model);
55
+ let cost = (usage.promptTokens / 1000.0) * inRate;
56
+ cost += (usage.completionTokens / 1000.0) * outRate;
57
+ return roundTo(cost, 8);
58
+ }
59
+ }
60
+ exports.PricingProvider = PricingProvider;
61
+ function roundTo(value, decimals) {
62
+ const factor = 10 ** decimals;
63
+ return Math.round(value * factor) / factor;
64
+ }
65
+ /** Rough token estimate (~4 chars/token) for providers without a tokenizer. */
66
+ function estimateTokens(text) {
67
+ if (!text)
68
+ return 0;
69
+ return Math.max(1, Math.floor(text.length / 4));
70
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /** anthropicAdapter — Messages API (Claude Sonnet, Haiku, Opus). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.anthropicAdapter = anthropicAdapter;
5
+ const errors_js_1 = require("../errors.js");
6
+ const types_js_1 = require("../types.js");
7
+ const base_js_1 = require("./base.js");
8
+ const http_js_1 = require("./http.js");
9
+ const DEFAULT_BASE_URL = 'https://api.anthropic.com/v1';
10
+ const API_VERSION = '2023-06-01';
11
+ /**
12
+ * Talks to the Anthropic Messages endpoint. Anthropic splits the system prompt
13
+ * from the message list, so any `role === "system"` messages are extracted into
14
+ * the `system` field.
15
+ */
16
+ class AnthropicAdapter extends base_js_1.BaseProviderAdapter {
17
+ apiKey;
18
+ baseUrl;
19
+ timeoutSeconds;
20
+ constructor(options = {}) {
21
+ super(options.pricing);
22
+ this.apiKey = options.apiKey ?? process.env['ANTHROPIC_API_KEY'];
23
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
24
+ this.timeoutSeconds = (options.timeoutMs ?? 30_000) / 1000;
25
+ }
26
+ get providerName() {
27
+ return 'anthropic';
28
+ }
29
+ headers() {
30
+ if (!this.apiKey) {
31
+ throw new errors_js_1.ConfigurationError('ANTHROPIC_API_KEY not set (pass apiKey or set the env var)');
32
+ }
33
+ return {
34
+ 'x-api-key': this.apiKey,
35
+ 'anthropic-version': API_VERSION,
36
+ };
37
+ }
38
+ static splitSystem(messages) {
39
+ const systemParts = messages
40
+ .filter((m) => m.role === 'system')
41
+ .map((m) => m.content);
42
+ const chat = messages.filter((m) => m.role !== 'system');
43
+ const system = systemParts.length > 0 ? systemParts.join('\n') : null;
44
+ return [system, chat];
45
+ }
46
+ async complete(request) {
47
+ const started = performance.now();
48
+ const [system, chat] = AnthropicAdapter.splitSystem(request.messages);
49
+ const payload = {
50
+ model: request.model,
51
+ messages: chat,
52
+ max_tokens: request.maxTokens,
53
+ temperature: request.temperature,
54
+ };
55
+ if (system)
56
+ payload['system'] = system;
57
+ const data = await (0, http_js_1.postJson)(`${this.baseUrl}/messages`, payload, this.headers(), this.timeoutSeconds);
58
+ const blocks = data['content'] ?? [];
59
+ const content = blocks
60
+ .filter((b) => b['type'] === 'text')
61
+ .map((b) => b['text'] ?? '')
62
+ .join('');
63
+ const usageData = data['usage'] ?? {};
64
+ const usage = new types_js_1.TokenUsage(usageData['input_tokens'] ?? 0, usageData['output_tokens'] ?? 0);
65
+ return this.buildResponse(request, content, usage, data['model'] ?? request.model, started);
66
+ }
67
+ async healthCheck() {
68
+ try {
69
+ this.headers();
70
+ return true;
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
76
+ }
77
+ /** Factory: build an Anthropic provider adapter. */
78
+ function anthropicAdapter(options = {}) {
79
+ return new AnthropicAdapter(options);
80
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ /** ProviderAdapter interface and shared response-building helpers. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.BaseProviderAdapter = void 0;
5
+ const pricing_js_1 = require("../pricing.js");
6
+ const response_js_1 = require("../response.js");
7
+ /** Base class with a shared pricing provider and response builder. */
8
+ class BaseProviderAdapter {
9
+ pricing;
10
+ constructor(pricing) {
11
+ this.pricing = pricing ?? new pricing_js_1.PricingProvider();
12
+ }
13
+ get reportedModelVersion() {
14
+ return null;
15
+ }
16
+ buildResponse(request, content, usage, modelVersion, startedAt) {
17
+ const latencyMs = Math.floor(performance.now() - startedAt);
18
+ return new response_js_1.GavioResponse({
19
+ traceId: request.traceId,
20
+ content,
21
+ model: request.model,
22
+ provider: this.providerName,
23
+ modelVersion: modelVersion || request.model,
24
+ usage,
25
+ costUsd: this.pricing.estimate(request.model, usage),
26
+ latencyMs,
27
+ });
28
+ }
29
+ }
30
+ exports.BaseProviderAdapter = BaseProviderAdapter;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ /** Tiny JSON-over-HTTP helper built on native fetch (keeps core dependency-free). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.postJson = postJson;
5
+ const errors_js_1 = require("../errors.js");
6
+ /**
7
+ * POST `payload` as JSON and return the parsed response.
8
+ *
9
+ * Maps HTTP status families onto Gavio's transient error types so the
10
+ * retry/fallback policies can react.
11
+ */
12
+ async function postJson(url, payload, headers, timeoutSeconds = 30.0) {
13
+ const controller = new AbortController();
14
+ const timer = setTimeout(() => controller.abort(), timeoutSeconds * 1000);
15
+ let resp;
16
+ try {
17
+ resp = await fetch(url, {
18
+ method: 'POST',
19
+ headers: { 'Content-Type': 'application/json', ...headers },
20
+ body: JSON.stringify(payload),
21
+ signal: controller.signal,
22
+ });
23
+ }
24
+ catch (error) {
25
+ const reason = error instanceof Error ? error.message : String(error);
26
+ throw new errors_js_1.ProviderUnavailableError(`network error: ${reason}`);
27
+ }
28
+ finally {
29
+ clearTimeout(timer);
30
+ }
31
+ if (!resp.ok) {
32
+ const body = (await resp.text().catch(() => '')).slice(0, 200);
33
+ if (resp.status === 429) {
34
+ throw new errors_js_1.RateLimitError(`429 from provider: ${body}`);
35
+ }
36
+ if (resp.status >= 500) {
37
+ throw new errors_js_1.ServerError(`${resp.status} from provider: ${body}`);
38
+ }
39
+ throw new errors_js_1.ProviderUnavailableError(`${resp.status} from provider: ${body}`);
40
+ }
41
+ return (await resp.json());
42
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /** Provider adapters and the provider registry. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Provider = exports.anthropicAdapter = exports.openaiAdapter = exports.mockProvider = exports.BaseProviderAdapter = void 0;
5
+ exports.buildAdapter = buildAdapter;
6
+ const errors_js_1 = require("../errors.js");
7
+ const types_js_1 = require("../types.js");
8
+ const anthropic_js_1 = require("./anthropic.js");
9
+ const mock_js_1 = require("./mock.js");
10
+ const openai_js_1 = require("./openai.js");
11
+ var base_js_1 = require("./base.js");
12
+ Object.defineProperty(exports, "BaseProviderAdapter", { enumerable: true, get: function () { return base_js_1.BaseProviderAdapter; } });
13
+ var mock_js_2 = require("./mock.js");
14
+ Object.defineProperty(exports, "mockProvider", { enumerable: true, get: function () { return mock_js_2.mockProvider; } });
15
+ var openai_js_2 = require("./openai.js");
16
+ Object.defineProperty(exports, "openaiAdapter", { enumerable: true, get: function () { return openai_js_2.openaiAdapter; } });
17
+ var anthropic_js_2 = require("./anthropic.js");
18
+ Object.defineProperty(exports, "anthropicAdapter", { enumerable: true, get: function () { return anthropic_js_2.anthropicAdapter; } });
19
+ var types_js_2 = require("../types.js");
20
+ Object.defineProperty(exports, "Provider", { enumerable: true, get: function () { return types_js_2.Provider; } });
21
+ /** Instantiate the default adapter for a provider id. v0.1.0: OpenAI, Anthropic, Mock. */
22
+ function buildAdapter(provider, pricing) {
23
+ const p = (0, types_js_1.coerceProvider)(provider);
24
+ switch (p) {
25
+ case types_js_1.Provider.OPENAI:
26
+ return (0, openai_js_1.openaiAdapter)(pricing ? { pricing } : {});
27
+ case types_js_1.Provider.ANTHROPIC:
28
+ return (0, anthropic_js_1.anthropicAdapter)(pricing ? { pricing } : {});
29
+ case types_js_1.Provider.MOCK:
30
+ return (0, mock_js_1.mockProvider)(pricing ? { pricing } : {});
31
+ default:
32
+ throw new errors_js_1.ConfigurationError(`Provider '${p}' is not available in v0.1.0 (available: openai, anthropic, mock)`);
33
+ }
34
+ }