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
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Gavio — JavaScript / TypeScript SDK
2
+
3
+ > The open standard AI gateway for production systems. PII protection, audit
4
+ > trails, reliability, and cost control as composable interceptors.
5
+
6
+ `gavio` sits between your application and any LLM provider. The same request
7
+ passes through a pre/post interceptor chain — PII redaction, retries, cost
8
+ tracking, audit logging — before and after the provider call.
9
+
10
+ Part of the [Gavio](https://gavio.io) project. MIT licensed. Written in
11
+ TypeScript, ships full type definitions, ESM. Node.js 18+ (native `fetch`,
12
+ `node:crypto`).
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install gavio # zero runtime dependencies
18
+ ```
19
+
20
+ ## Quick start (dev mode — no API key, no network)
21
+
22
+ ```typescript
23
+ import { Gateway } from 'gavio'
24
+ import { piiGuard } from 'gavio/interceptors/pii'
25
+
26
+ const gw = new Gateway({ devMode: true }).use(piiGuard())
27
+
28
+ const r = await gw.complete({
29
+ messages: [{ role: 'user', content: 'Email jan@example.com re NL91ABNA0417164300' }],
30
+ agentId: 'demo',
31
+ })
32
+
33
+ console.log(r.content) // PII restored in the reply
34
+ console.log(`cost=$${r.costUsd.toFixed(6)} latency=${r.latencyMs}ms`)
35
+ console.log('pii types:', r.audit.piiEntityTypes)
36
+ ```
37
+
38
+ ## Real providers
39
+
40
+ ```typescript
41
+ import { Gateway } from 'gavio'
42
+ import { piiGuard } from 'gavio/interceptors/pii'
43
+ import { auditInterceptor } from 'gavio/interceptors/audit'
44
+ import { retryInterceptor, timeoutPolicy } from 'gavio/interceptors/reliability'
45
+
46
+ const gw = new Gateway({ provider: 'anthropic', model: 'claude-sonnet-4-6' }) // reads ANTHROPIC_API_KEY
47
+ .use(piiGuard({ sensitivity: 'strict' }))
48
+ .use(auditInterceptor({ sink: 'stdout' }))
49
+ .use(timeoutPolicy({ timeoutMs: 30_000 }))
50
+ .use(retryInterceptor({ maxAttempts: 3 }))
51
+
52
+ const r = await gw.complete({ messages: [{ role: 'user', content: 'Hi' }] })
53
+ ```
54
+
55
+ `OPENAI_API_KEY` / `provider: 'openai'` work the same way.
56
+
57
+ ## Sub-path imports (tree-shaking)
58
+
59
+ ```typescript
60
+ import { Gateway } from 'gavio'
61
+ import { piiGuard } from 'gavio/interceptors/pii'
62
+ import { auditInterceptor } from 'gavio/interceptors/audit'
63
+ import { retryInterceptor } from 'gavio/interceptors/reliability'
64
+ import { anthropicAdapter } from 'gavio/providers/anthropic'
65
+ import { GavioTestKit } from 'gavio/testing'
66
+ ```
67
+
68
+ ## What ships in v0.1.0
69
+
70
+ - **Core** — `Gateway` (object config + `.use()` / `.withAdapter()`),
71
+ `InterceptorChain` (onion pre/post model), `GavioRequest` / `GavioResponse`
72
+ (camelCase), monotonic UUID v7 `traceId`, `agentId` / `parentTraceId`.
73
+ - **PII Guard (F-SEC-01)** — Email, IBAN (mod-97), BSN (11-proef),
74
+ CreditCard (Luhn), Phone, IP (v4/v6), SSN scanners; redact / mask / tag /
75
+ block; restore-on-response; overlap resolution.
76
+ - **Secret Scanner (F-SEC-04)** — API keys, JWTs, PEM keys, DB URLs.
77
+ - **Reliability** — `retryInterceptor` (F-REL-01), `fallbackChain` (F-REL-02),
78
+ `timeoutPolicy` (F-REL-07).
79
+ - **Cost tracking (F-GOV-01)** — `costUsd` on every response.
80
+ - **Audit (F-OBS-01)** — `AuditRecord` (SHA-256 hashes, metadata only) +
81
+ `stdoutSink` (F-OBS-05).
82
+ - **Dev mode (F-DX-01)** and **dry-run mode (F-DX-02)**.
83
+ - **Providers** — OpenAI, Anthropic (native `fetch`), Mock.
84
+ - **Testing** — `GavioTestKit`, `mockProvider`.
85
+
86
+ See the [JavaScript guide](../../docs/packages/javascript.md) and [CHANGELOG.md](../../CHANGELOG.md).
87
+
88
+ ## Scripts
89
+
90
+ ```bash
91
+ npm run typecheck # tsc --noEmit (strict)
92
+ npm test # vitest run (59 unit tests)
93
+ npm run smoke # build + dev-mode end-to-end check
94
+ npm run build # compile to dist/
95
+ ```
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ /** Per-request context passed through the interceptor pipeline. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.InterceptorContext = void 0;
5
+ /**
6
+ * Mutable scratch space shared by all interceptors within one request. One
7
+ * instance per request — never shared across requests. Interceptors stash
8
+ * signals here (PII findings, cache decisions, risk scores) for the audit
9
+ * interceptor to collect at the end of the chain.
10
+ */
11
+ class InterceptorContext {
12
+ traceId;
13
+ agentId;
14
+ parentTraceId;
15
+ sessionId;
16
+ dryRun;
17
+ interceptorsFired = [];
18
+ piiEntityTypes = [];
19
+ piiEntityCounts = {};
20
+ cacheHit = false;
21
+ cacheType = null;
22
+ riskScore = null;
23
+ guardrailOutcome = null;
24
+ /** Arbitrary inter-interceptor state (e.g. PII replacement map for restore). */
25
+ state = {};
26
+ constructor(init) {
27
+ this.traceId = init.traceId;
28
+ this.agentId = init.agentId ?? null;
29
+ this.parentTraceId = init.parentTraceId ?? null;
30
+ this.sessionId = init.sessionId ?? null;
31
+ this.dryRun = init.dryRun ?? false;
32
+ }
33
+ markFired(name) {
34
+ if (!this.interceptorsFired.includes(name)) {
35
+ this.interceptorsFired.push(name);
36
+ }
37
+ }
38
+ recordPii(entityTypes) {
39
+ for (const et of entityTypes) {
40
+ this.piiEntityCounts[et] = (this.piiEntityCounts[et] ?? 0) + 1;
41
+ if (!this.piiEntityTypes.includes(et)) {
42
+ this.piiEntityTypes.push(et);
43
+ }
44
+ }
45
+ }
46
+ }
47
+ exports.InterceptorContext = InterceptorContext;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ /**
3
+ * Gavio error hierarchy. All Gavio errors derive from {@link GavioError} so
4
+ * callers can catch the whole family with a single check.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.GuardrailViolationError = exports.BudgetExceededError = exports.PiiBlockedError = exports.TimeoutError = exports.ServerError = exports.RateLimitError = exports.ProviderUnavailableError = exports.ProviderError = exports.ConfigurationError = exports.GavioError = void 0;
8
+ class GavioError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = new.target.name;
12
+ Object.setPrototypeOf(this, new.target.prototype);
13
+ }
14
+ }
15
+ exports.GavioError = GavioError;
16
+ /** Raised when the gateway is misconfigured (e.g. no provider set). */
17
+ class ConfigurationError extends GavioError {
18
+ }
19
+ exports.ConfigurationError = ConfigurationError;
20
+ /** Base class for provider-adapter failures. */
21
+ class ProviderError extends GavioError {
22
+ }
23
+ exports.ProviderError = ProviderError;
24
+ /** The provider could not be reached (network / health-check failure). */
25
+ class ProviderUnavailableError extends ProviderError {
26
+ }
27
+ exports.ProviderUnavailableError = ProviderUnavailableError;
28
+ /** The provider returned a rate-limit (HTTP 429) signal. */
29
+ class RateLimitError extends ProviderError {
30
+ }
31
+ exports.RateLimitError = RateLimitError;
32
+ /** The provider returned a 5xx server error. */
33
+ class ServerError extends ProviderError {
34
+ }
35
+ exports.ServerError = ServerError;
36
+ /** A request exceeded its configured timeout. */
37
+ class TimeoutError extends ProviderError {
38
+ }
39
+ exports.TimeoutError = TimeoutError;
40
+ /** PiiGuard is in BLOCK mode and detected PII in the request. */
41
+ class PiiBlockedError extends GavioError {
42
+ entityTypes;
43
+ constructor(entityTypes) {
44
+ const sorted = Array.from(new Set(entityTypes)).sort();
45
+ super(`Request blocked: PII detected (${sorted.join(', ')})`);
46
+ this.entityTypes = entityTypes;
47
+ }
48
+ }
49
+ exports.PiiBlockedError = PiiBlockedError;
50
+ /** A hard budget cap was exceeded. Never swallow this — surface to user. */
51
+ class BudgetExceededError extends GavioError {
52
+ }
53
+ exports.BudgetExceededError = BudgetExceededError;
54
+ /** Output failed a guardrail validator with onFailure='error'. */
55
+ class GuardrailViolationError extends GavioError {
56
+ }
57
+ exports.GuardrailViolationError = GuardrailViolationError;
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ /** Gateway — the entry point. Wires interceptors around a provider adapter. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Gateway = void 0;
5
+ const context_js_1 = require("./context.js");
6
+ const errors_js_1 = require("./errors.js");
7
+ const index_js_1 = require("./interceptors/audit/index.js");
8
+ const base_js_1 = require("./interceptors/base.js");
9
+ const chain_js_1 = require("./interceptors/chain.js");
10
+ const pricing_js_1 = require("./pricing.js");
11
+ const index_js_2 = require("./providers/index.js");
12
+ const mock_js_1 = require("./providers/mock.js");
13
+ const request_js_1 = require("./request.js");
14
+ const types_js_1 = require("./types.js");
15
+ const DEFAULT_MODELS = {
16
+ openai: 'gpt-4o',
17
+ anthropic: 'claude-sonnet-4-6',
18
+ mock: 'mock',
19
+ };
20
+ /**
21
+ * Routes a request through the interceptor pipeline to a provider.
22
+ *
23
+ * Construct with `new Gateway({ provider, model })` then chain `.use(...)` and
24
+ * `.withAdapter(...)`. A single instance is safe to reuse — per-request state
25
+ * lives in an {@link InterceptorContext} created fresh for every call.
26
+ */
27
+ class Gateway {
28
+ providerHint;
29
+ modelHint;
30
+ adapterOverride;
31
+ devMode;
32
+ dryRunMode;
33
+ pricing;
34
+ interceptors = [];
35
+ constructor(options = {}) {
36
+ this.providerHint = options.provider ? (0, types_js_1.coerceProvider)(options.provider) : undefined;
37
+ this.modelHint = options.model;
38
+ this.adapterOverride = options.adapter;
39
+ this.devMode = options.devMode ?? false;
40
+ this.dryRunMode = options.dryRun ?? false;
41
+ this.pricing = options.pricing ?? new pricing_js_1.PricingProvider();
42
+ }
43
+ /** Register an interceptor or executor policy. First-registered = outermost. */
44
+ use(interceptor) {
45
+ this.interceptors.push(interceptor);
46
+ return this;
47
+ }
48
+ /** Supply a provider adapter explicitly (overrides `provider`). */
49
+ withAdapter(adapter) {
50
+ this.adapterOverride = adapter;
51
+ return this;
52
+ }
53
+ get model() {
54
+ return this.modelHint ?? this.resolveModel(this.resolveAdapter());
55
+ }
56
+ get providerName() {
57
+ return this.resolveAdapter().providerName;
58
+ }
59
+ async complete(opts) {
60
+ const adapter = this.resolveAdapter();
61
+ const model = opts.model ?? this.modelHint ?? this.resolveModel(adapter);
62
+ const request = new request_js_1.GavioRequest({
63
+ messages: opts.messages,
64
+ model,
65
+ provider: (0, types_js_1.coerceProvider)(adapter.providerName),
66
+ agentId: opts.agentId ?? null,
67
+ parentTraceId: opts.parentTraceId ?? null,
68
+ sessionId: opts.sessionId ?? null,
69
+ options: opts.options ?? {},
70
+ metadata: opts.metadata ?? {},
71
+ });
72
+ const ctx = new context_js_1.InterceptorContext({
73
+ traceId: request.traceId,
74
+ agentId: request.agentId,
75
+ parentTraceId: request.parentTraceId,
76
+ sessionId: request.sessionId,
77
+ dryRun: this.dryRunMode,
78
+ });
79
+ const { chain, executor } = this.buildPipeline(adapter, ctx);
80
+ return chain.execute(request, ctx, executor);
81
+ }
82
+ async healthCheck() {
83
+ return this.resolveAdapter().healthCheck();
84
+ }
85
+ buildPipeline(adapter, ctx) {
86
+ let interceptors = [...this.interceptors];
87
+ // Dev mode auto-wires a stdout audit sink if none was added.
88
+ if (this.devMode && !interceptors.some(index_js_1.isAuditInterceptor)) {
89
+ interceptors = [(0, index_js_1.auditInterceptor)(), ...interceptors];
90
+ }
91
+ const policies = interceptors.filter(base_js_1.isExecutorPolicy);
92
+ const regular = interceptors.filter((i) => !(0, base_js_1.isExecutorPolicy)(i));
93
+ const chain = new chain_js_1.InterceptorChain(regular);
94
+ let executor = (request) => adapter.complete(request);
95
+ // Wrap so the first-registered policy ends up outermost.
96
+ for (let i = policies.length - 1; i >= 0; i--) {
97
+ executor = this.wrapPolicy(policies[i], executor, ctx);
98
+ }
99
+ return { chain, executor };
100
+ }
101
+ wrapPolicy(policy, inner, ctx) {
102
+ return async (request) => {
103
+ if (ctx.dryRun && policy.dryRunSafe === false) {
104
+ return inner(request);
105
+ }
106
+ return policy.around(request, ctx, inner);
107
+ };
108
+ }
109
+ resolveAdapter() {
110
+ if (this.adapterOverride !== undefined)
111
+ return this.adapterOverride;
112
+ if (this.devMode) {
113
+ this.adapterOverride = (0, mock_js_1.mockProvider)({ pricing: this.pricing });
114
+ return this.adapterOverride;
115
+ }
116
+ if (this.providerHint === undefined) {
117
+ throw new errors_js_1.ConfigurationError('No provider configured. Pass { provider }, { adapter }, call .withAdapter(...), ' +
118
+ 'or set { devMode: true }.');
119
+ }
120
+ this.adapterOverride = (0, index_js_2.buildAdapter)(this.providerHint, this.pricing);
121
+ return this.adapterOverride;
122
+ }
123
+ resolveModel(adapter) {
124
+ return DEFAULT_MODELS[adapter.providerName] ?? 'mock';
125
+ }
126
+ }
127
+ exports.Gateway = Gateway;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * UUID v7 generation — time-sortable, unique identifiers for traces.
4
+ *
5
+ * UUID v7 layout (RFC 9562): 48-bit Unix millisecond timestamp, 4-bit version,
6
+ * 12 bits used here as a per-millisecond monotonic sequence (rand_a), 2-bit
7
+ * variant, 62 bits of randomness. Ported directly from the Python `_ids.py`.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.uuid7 = uuid7;
11
+ exports.newTraceId = newTraceId;
12
+ const node_crypto_1 = require("node:crypto");
13
+ let lastMs = -1;
14
+ let seq = 0; // 12-bit per-millisecond sequence in rand_a, for monotonicity
15
+ /**
16
+ * Return a [unix_ms, sequence] pair that is monotonically non-decreasing.
17
+ *
18
+ * Within a single millisecond the 12-bit sequence increments so IDs stay
19
+ * strictly ordered (RFC 9562 method 1). If the sequence overflows, the
20
+ * timestamp is nudged forward. JavaScript is single-threaded per event loop,
21
+ * so no lock is required.
22
+ */
23
+ function nextTimestampAndSeq() {
24
+ const nowMs = Date.now();
25
+ if (nowMs > lastMs) {
26
+ lastMs = nowMs;
27
+ seq = (0, node_crypto_1.randomBytes)(2).readUInt16BE(0) & 0x0fff;
28
+ }
29
+ else {
30
+ seq += 1;
31
+ if (seq > 0x0fff) {
32
+ lastMs += 1;
33
+ seq = 0;
34
+ }
35
+ }
36
+ return [lastMs, seq];
37
+ }
38
+ function toHex(value, width) {
39
+ return value.toString(16).padStart(width, '0');
40
+ }
41
+ /** Return a new UUID version 7 (time-ordered, monotonic within a process). */
42
+ function uuid7() {
43
+ const [unixMs, randA] = nextTimestampAndSeq();
44
+ // 48-bit millisecond timestamp occupies bits 80..127. Use BigInt for the
45
+ // shift — JS bitwise ops are 32-bit and would truncate Date.now() (~1.75e12).
46
+ const ms = BigInt(unixMs) & 0xffffffffffffn;
47
+ const randBuf = (0, node_crypto_1.randomBytes)(8);
48
+ const randB = randBuf.readBigUInt64BE(0) & 0x3fffffffffffffffn; // 62 bits
49
+ const value = (ms << 80n) |
50
+ (0x7n << 76n) | // version 7
51
+ (BigInt(randA) << 64n) |
52
+ (2n << 62n) | // variant
53
+ randB;
54
+ const hex = toHex(value, 32);
55
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
56
+ }
57
+ /** Return a fresh trace id as a string. */
58
+ function newTraceId() {
59
+ return uuid7();
60
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ /**
3
+ * Gavio — the open standard AI gateway for production systems.
4
+ *
5
+ * Public API surface (v0.1.0):
6
+ *
7
+ * import { Gateway, GavioRequest, GavioResponse, Provider } from 'gavio'
8
+ *
9
+ * See https://gavio.io for documentation. MIT licensed.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.GuardrailViolationError = exports.BudgetExceededError = exports.PiiBlockedError = exports.TimeoutError = exports.ServerError = exports.RateLimitError = exports.ProviderUnavailableError = exports.ProviderError = exports.ConfigurationError = exports.GavioError = exports.InterceptorChain = exports.coerceProvider = exports.TokenUsage = exports.GuardrailOutcome = exports.Sensitivity = exports.PiiMode = exports.CacheType = exports.Provider = exports.estimateTokens = exports.PricingProvider = exports.newTraceId = exports.uuid7 = exports.InterceptorContext = exports.GavioResponse = exports.GavioRequest = exports.Gateway = exports.VERSION = void 0;
13
+ exports.VERSION = '0.1.0';
14
+ var gateway_js_1 = require("./gateway.js");
15
+ Object.defineProperty(exports, "Gateway", { enumerable: true, get: function () { return gateway_js_1.Gateway; } });
16
+ var request_js_1 = require("./request.js");
17
+ Object.defineProperty(exports, "GavioRequest", { enumerable: true, get: function () { return request_js_1.GavioRequest; } });
18
+ var response_js_1 = require("./response.js");
19
+ Object.defineProperty(exports, "GavioResponse", { enumerable: true, get: function () { return response_js_1.GavioResponse; } });
20
+ var context_js_1 = require("./context.js");
21
+ Object.defineProperty(exports, "InterceptorContext", { enumerable: true, get: function () { return context_js_1.InterceptorContext; } });
22
+ var ids_js_1 = require("./ids.js");
23
+ Object.defineProperty(exports, "uuid7", { enumerable: true, get: function () { return ids_js_1.uuid7; } });
24
+ Object.defineProperty(exports, "newTraceId", { enumerable: true, get: function () { return ids_js_1.newTraceId; } });
25
+ var pricing_js_1 = require("./pricing.js");
26
+ Object.defineProperty(exports, "PricingProvider", { enumerable: true, get: function () { return pricing_js_1.PricingProvider; } });
27
+ Object.defineProperty(exports, "estimateTokens", { enumerable: true, get: function () { return pricing_js_1.estimateTokens; } });
28
+ var types_js_1 = require("./types.js");
29
+ Object.defineProperty(exports, "Provider", { enumerable: true, get: function () { return types_js_1.Provider; } });
30
+ Object.defineProperty(exports, "CacheType", { enumerable: true, get: function () { return types_js_1.CacheType; } });
31
+ Object.defineProperty(exports, "PiiMode", { enumerable: true, get: function () { return types_js_1.PiiMode; } });
32
+ Object.defineProperty(exports, "Sensitivity", { enumerable: true, get: function () { return types_js_1.Sensitivity; } });
33
+ Object.defineProperty(exports, "GuardrailOutcome", { enumerable: true, get: function () { return types_js_1.GuardrailOutcome; } });
34
+ Object.defineProperty(exports, "TokenUsage", { enumerable: true, get: function () { return types_js_1.TokenUsage; } });
35
+ Object.defineProperty(exports, "coerceProvider", { enumerable: true, get: function () { return types_js_1.coerceProvider; } });
36
+ var chain_js_1 = require("./interceptors/chain.js");
37
+ Object.defineProperty(exports, "InterceptorChain", { enumerable: true, get: function () { return chain_js_1.InterceptorChain; } });
38
+ // errors
39
+ var errors_js_1 = require("./errors.js");
40
+ Object.defineProperty(exports, "GavioError", { enumerable: true, get: function () { return errors_js_1.GavioError; } });
41
+ Object.defineProperty(exports, "ConfigurationError", { enumerable: true, get: function () { return errors_js_1.ConfigurationError; } });
42
+ Object.defineProperty(exports, "ProviderError", { enumerable: true, get: function () { return errors_js_1.ProviderError; } });
43
+ Object.defineProperty(exports, "ProviderUnavailableError", { enumerable: true, get: function () { return errors_js_1.ProviderUnavailableError; } });
44
+ Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_js_1.RateLimitError; } });
45
+ Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return errors_js_1.ServerError; } });
46
+ Object.defineProperty(exports, "TimeoutError", { enumerable: true, get: function () { return errors_js_1.TimeoutError; } });
47
+ Object.defineProperty(exports, "PiiBlockedError", { enumerable: true, get: function () { return errors_js_1.PiiBlockedError; } });
48
+ Object.defineProperty(exports, "BudgetExceededError", { enumerable: true, get: function () { return errors_js_1.BudgetExceededError; } });
49
+ Object.defineProperty(exports, "GuardrailViolationError", { enumerable: true, get: function () { return errors_js_1.GuardrailViolationError; } });
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stdoutSink = exports.SCHEMA_VERSION = exports.AuditRecord = exports.AUDIT_NAME = exports.isAuditInterceptor = exports.auditInterceptor = void 0;
4
+ var interceptor_js_1 = require("./interceptor.js");
5
+ Object.defineProperty(exports, "auditInterceptor", { enumerable: true, get: function () { return interceptor_js_1.auditInterceptor; } });
6
+ Object.defineProperty(exports, "isAuditInterceptor", { enumerable: true, get: function () { return interceptor_js_1.isAuditInterceptor; } });
7
+ Object.defineProperty(exports, "AUDIT_NAME", { enumerable: true, get: function () { return interceptor_js_1.AUDIT_NAME; } });
8
+ var record_js_1 = require("./record.js");
9
+ Object.defineProperty(exports, "AuditRecord", { enumerable: true, get: function () { return record_js_1.AuditRecord; } });
10
+ Object.defineProperty(exports, "SCHEMA_VERSION", { enumerable: true, get: function () { return record_js_1.SCHEMA_VERSION; } });
11
+ var stdout_js_1 = require("./sinks/stdout.js");
12
+ Object.defineProperty(exports, "stdoutSink", { enumerable: true, get: function () { return stdout_js_1.stdoutSink; } });
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /** auditInterceptor (F-OBS-01) — captures a full record of every call. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.AUDIT_NAME = void 0;
5
+ exports.auditInterceptor = auditInterceptor;
6
+ exports.isAuditInterceptor = isAuditInterceptor;
7
+ const record_js_1 = require("./record.js");
8
+ const stdout_js_1 = require("./sinks/stdout.js");
9
+ const PROMPT_HASH_KEY = 'audit_prompt_hash';
10
+ exports.AUDIT_NAME = 'audit';
11
+ /**
12
+ * Build an AuditRecord per request and write it to a sink.
13
+ *
14
+ * Register this as the outermost interceptor so its `after` runs last and sees
15
+ * the final, fully-processed response. It hashes the (already PII-redacted)
16
+ * prompt in `before` and the response in `after` — content is never stored,
17
+ * only digests and metadata.
18
+ */
19
+ class AuditInterceptor {
20
+ name = exports.AUDIT_NAME;
21
+ dryRunSafe = true; // auditing is observation-only, so it always runs
22
+ sink;
23
+ constructor(options = {}) {
24
+ this.sink = resolveSink(options.sink);
25
+ }
26
+ async before(request, ctx) {
27
+ ctx.state[PROMPT_HASH_KEY] = record_js_1.AuditRecord.hashText(request.promptText());
28
+ return request;
29
+ }
30
+ async after(response, ctx) {
31
+ const record = new record_js_1.AuditRecord({
32
+ traceId: response.traceId,
33
+ parentTraceId: ctx.parentTraceId,
34
+ agentId: ctx.agentId,
35
+ sessionId: ctx.sessionId,
36
+ timestampUtc: record_js_1.AuditRecord.nowUtc(),
37
+ provider: response.provider,
38
+ model: response.model,
39
+ modelVersion: response.modelVersion,
40
+ promptHash: ctx.state[PROMPT_HASH_KEY] ?? '',
41
+ responseHash: record_js_1.AuditRecord.hashText(response.content),
42
+ tokenUsage: response.usage,
43
+ costUsd: response.costUsd,
44
+ latencyMs: response.latencyMs,
45
+ piiEntityTypes: [...ctx.piiEntityTypes],
46
+ piiEntityCounts: { ...ctx.piiEntityCounts },
47
+ interceptorsFired: [...ctx.interceptorsFired],
48
+ cacheHit: response.cacheHit,
49
+ cacheType: response.cacheType,
50
+ guardrailOutcome: ctx.guardrailOutcome,
51
+ riskScore: ctx.riskScore,
52
+ });
53
+ response.audit = record;
54
+ try {
55
+ await this.sink.write(record);
56
+ }
57
+ catch {
58
+ // Auditing must never break the call.
59
+ // eslint-disable-next-line no-console
60
+ console.error(`[gavio:audit] sink write failed for trace ${record.traceId}`);
61
+ }
62
+ return response;
63
+ }
64
+ }
65
+ function resolveSink(sink) {
66
+ if (sink === undefined || sink === 'stdout')
67
+ return (0, stdout_js_1.stdoutSink)();
68
+ return sink;
69
+ }
70
+ /** Factory: build an audit interceptor. */
71
+ function auditInterceptor(options = {}) {
72
+ return new AuditInterceptor(options);
73
+ }
74
+ /** True if an interceptor is the audit interceptor (used by dev-mode auto-wiring). */
75
+ function isAuditInterceptor(i) {
76
+ return i.name === exports.AUDIT_NAME;
77
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ /** AuditRecord — the immutable, per-request audit entry. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.AuditRecord = exports.SCHEMA_VERSION = void 0;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const types_js_1 = require("../../types.js");
7
+ exports.SCHEMA_VERSION = '1.0';
8
+ function sha256(text) {
9
+ return (0, node_crypto_1.createHash)('sha256').update(text, 'utf-8').digest('hex');
10
+ }
11
+ /**
12
+ * One append-only audit entry. Carries metadata only — never raw content.
13
+ *
14
+ * `promptHash` / `responseHash` are SHA-256 digests so the entry is verifiable
15
+ * without storing sensitive text. `previousHash` is reserved for the v0.2.0
16
+ * hash-chain (F-OBS-02); empty in v0.1.0.
17
+ */
18
+ class AuditRecord {
19
+ traceId;
20
+ provider;
21
+ model;
22
+ timestampUtc;
23
+ parentTraceId;
24
+ agentId;
25
+ sessionId;
26
+ modelVersion;
27
+ promptHash;
28
+ responseHash;
29
+ tokenUsage;
30
+ costUsd;
31
+ latencyMs;
32
+ piiEntityTypes;
33
+ piiEntityCounts;
34
+ interceptorsFired;
35
+ cacheHit;
36
+ cacheType;
37
+ guardrailOutcome;
38
+ riskScore;
39
+ previousHash;
40
+ schemaVersion;
41
+ constructor(init) {
42
+ this.traceId = init.traceId;
43
+ this.provider = init.provider;
44
+ this.model = init.model;
45
+ this.timestampUtc = init.timestampUtc;
46
+ this.parentTraceId = init.parentTraceId ?? null;
47
+ this.agentId = init.agentId ?? null;
48
+ this.sessionId = init.sessionId ?? null;
49
+ this.modelVersion = init.modelVersion ?? '';
50
+ this.promptHash = init.promptHash ?? '';
51
+ this.responseHash = init.responseHash ?? '';
52
+ this.tokenUsage = init.tokenUsage ?? new types_js_1.TokenUsage();
53
+ this.costUsd = init.costUsd ?? 0.0;
54
+ this.latencyMs = init.latencyMs ?? 0;
55
+ this.piiEntityTypes = init.piiEntityTypes ?? [];
56
+ this.piiEntityCounts = init.piiEntityCounts ?? {};
57
+ this.interceptorsFired = init.interceptorsFired ?? [];
58
+ this.cacheHit = init.cacheHit ?? false;
59
+ this.cacheType = init.cacheType ?? null;
60
+ this.guardrailOutcome = init.guardrailOutcome ?? null;
61
+ this.riskScore = init.riskScore ?? null;
62
+ this.previousHash = init.previousHash ?? '';
63
+ this.schemaVersion = init.schemaVersion ?? exports.SCHEMA_VERSION;
64
+ }
65
+ static nowUtc() {
66
+ return new Date().toISOString();
67
+ }
68
+ static hashText(text) {
69
+ return sha256(text);
70
+ }
71
+ toJSON() {
72
+ return {
73
+ traceId: this.traceId,
74
+ parentTraceId: this.parentTraceId,
75
+ agentId: this.agentId,
76
+ sessionId: this.sessionId,
77
+ provider: this.provider,
78
+ model: this.model,
79
+ modelVersion: this.modelVersion,
80
+ timestampUtc: this.timestampUtc,
81
+ promptHash: this.promptHash,
82
+ responseHash: this.responseHash,
83
+ tokenUsage: this.tokenUsage.toJSON(),
84
+ costUsd: this.costUsd,
85
+ latencyMs: this.latencyMs,
86
+ piiEntityTypes: this.piiEntityTypes,
87
+ piiEntityCounts: this.piiEntityCounts,
88
+ interceptorsFired: this.interceptorsFired,
89
+ cacheHit: this.cacheHit,
90
+ cacheType: this.cacheType,
91
+ guardrailOutcome: this.guardrailOutcome,
92
+ riskScore: this.riskScore,
93
+ previousHash: this.previousHash,
94
+ schemaVersion: this.schemaVersion,
95
+ };
96
+ }
97
+ /** Stable JSON with sorted keys — used for the v0.2.0 hash chain. */
98
+ toCanonicalJson() {
99
+ const data = this.toJSON();
100
+ return JSON.stringify(data, Object.keys(data).sort());
101
+ }
102
+ /** Hash of this record's content — used to build the v0.2.0 chain. */
103
+ contentHash() {
104
+ return sha256(this.toCanonicalJson());
105
+ }
106
+ }
107
+ exports.AuditRecord = AuditRecord;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ /** AuditSink — the extensible destination for audit records. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stdoutSink = void 0;
4
+ var stdout_js_1 = require("./stdout.js");
5
+ Object.defineProperty(exports, "stdoutSink", { enumerable: true, get: function () { return stdout_js_1.stdoutSink; } });