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,66 @@
1
+ /**
2
+ * GavioTestKit — run interceptor chains in isolation for unit tests.
3
+ *
4
+ * This v0.1.0 kit drives a chain against a {@link mockProvider} and lets you
5
+ * assert on PII detection, the redacted request, and the resulting audit record.
6
+ */
7
+ import { InterceptorContext } from '../context.js';
8
+ import { isExecutorPolicy } from '../interceptors/base.js';
9
+ import { InterceptorChain } from '../interceptors/chain.js';
10
+ import { mockProvider } from '../providers/mock.js';
11
+ import { GavioRequest } from '../request.js';
12
+ import { coerceProvider } from '../types.js';
13
+ /** Records the request as it reaches the provider (post-redaction). */
14
+ class CaptureInterceptor {
15
+ name = '_capture';
16
+ captured = null;
17
+ before(request) {
18
+ this.captured = request;
19
+ return request;
20
+ }
21
+ }
22
+ export class GavioTestKit {
23
+ interceptors;
24
+ provider;
25
+ model;
26
+ constructor(options = {}) {
27
+ this.interceptors = [...(options.interceptors ?? [])];
28
+ this.provider = options.provider ?? mockProvider();
29
+ this.model = options.model ?? 'mock';
30
+ }
31
+ async run(input) {
32
+ const request = new GavioRequest({
33
+ messages: input.messages,
34
+ model: this.model,
35
+ provider: coerceProvider(this.provider.providerName),
36
+ options: input.options ?? {},
37
+ });
38
+ const ctx = new InterceptorContext({ traceId: request.traceId });
39
+ const capture = new CaptureInterceptor();
40
+ const all = [...this.interceptors, capture];
41
+ const policies = all.filter(isExecutorPolicy);
42
+ const regular = all.filter((i) => !isExecutorPolicy(i));
43
+ const chain = new InterceptorChain(regular);
44
+ let executor = (req) => this.provider.complete(req);
45
+ for (let i = policies.length - 1; i >= 0; i--) {
46
+ executor = wrap(policies[i], executor, ctx);
47
+ }
48
+ const response = await chain.execute(request, ctx, executor);
49
+ return {
50
+ response,
51
+ ctx,
52
+ preRequestText: () => capture.captured?.promptText() ?? '',
53
+ piiDetected: (entityType) => {
54
+ if (entityType === undefined)
55
+ return ctx.piiEntityTypes.length > 0;
56
+ return ctx.piiEntityTypes.includes(entityType);
57
+ },
58
+ get auditRecord() {
59
+ return response.audit;
60
+ },
61
+ };
62
+ }
63
+ }
64
+ function wrap(policy, inner, ctx) {
65
+ return (req) => policy.around(req, ctx, inner);
66
+ }
@@ -0,0 +1,5 @@
1
+ /** Gavio test utilities. */
2
+ export { GavioTestKit } from './harness.js';
3
+ export type { GavioTestKitOptions, RunResult } from './harness.js';
4
+ export { mockProvider } from '../providers/mock.js';
5
+ export type { MockProviderOptions } from '../providers/mock.js';
@@ -0,0 +1,3 @@
1
+ /** Gavio test utilities. */
2
+ export { GavioTestKit } from './harness.js';
3
+ export { mockProvider } from '../providers/mock.js';
@@ -0,0 +1,58 @@
1
+ /** Shared enums (as string unions / const objects) and utility types. */
2
+ /** A provider-agnostic chat message. */
3
+ export interface Message {
4
+ role: string;
5
+ content: string;
6
+ [key: string]: unknown;
7
+ }
8
+ /** Supported LLM providers. String-valued for easy config + logging. */
9
+ export declare const Provider: {
10
+ readonly OPENAI: "openai";
11
+ readonly ANTHROPIC: "anthropic";
12
+ readonly GEMINI: "gemini";
13
+ readonly AZURE_OPENAI: "azure_openai";
14
+ readonly OLLAMA: "ollama";
15
+ readonly BEDROCK: "bedrock";
16
+ readonly COHERE: "cohere";
17
+ readonly MOCK: "mock";
18
+ };
19
+ export type Provider = (typeof Provider)[keyof typeof Provider];
20
+ /** Accept either a known provider value or any string and normalise to lowercase. */
21
+ export declare function coerceProvider(value: Provider | string): Provider;
22
+ export declare const CacheType: {
23
+ readonly EXACT: "exact";
24
+ readonly SEMANTIC: "semantic";
25
+ };
26
+ export type CacheType = (typeof CacheType)[keyof typeof CacheType];
27
+ /** What PiiGuard does with a detected entity. */
28
+ export declare const PiiMode: {
29
+ readonly REDACT: "redact";
30
+ readonly MASK: "mask";
31
+ readonly TAG: "tag";
32
+ readonly BLOCK: "block";
33
+ };
34
+ export type PiiMode = (typeof PiiMode)[keyof typeof PiiMode];
35
+ export declare const Sensitivity: {
36
+ readonly STRICT: "strict";
37
+ readonly BALANCED: "balanced";
38
+ readonly PERMISSIVE: "permissive";
39
+ };
40
+ export type Sensitivity = (typeof Sensitivity)[keyof typeof Sensitivity];
41
+ export declare const GuardrailOutcome: {
42
+ readonly PASS: "PASS";
43
+ readonly FAIL: "FAIL";
44
+ readonly HITL: "HITL";
45
+ };
46
+ export type GuardrailOutcome = (typeof GuardrailOutcome)[keyof typeof GuardrailOutcome];
47
+ /** Token accounting for a single completion. */
48
+ export declare class TokenUsage {
49
+ readonly promptTokens: number;
50
+ readonly completionTokens: number;
51
+ constructor(promptTokens?: number, completionTokens?: number);
52
+ get totalTokens(): number;
53
+ toJSON(): {
54
+ promptTokens: number;
55
+ completionTokens: number;
56
+ totalTokens: number;
57
+ };
58
+ }
@@ -0,0 +1,56 @@
1
+ /** Shared enums (as string unions / const objects) and utility types. */
2
+ /** Supported LLM providers. String-valued for easy config + logging. */
3
+ export const Provider = {
4
+ OPENAI: 'openai',
5
+ ANTHROPIC: 'anthropic',
6
+ GEMINI: 'gemini',
7
+ AZURE_OPENAI: 'azure_openai',
8
+ OLLAMA: 'ollama',
9
+ BEDROCK: 'bedrock',
10
+ COHERE: 'cohere',
11
+ MOCK: 'mock',
12
+ };
13
+ /** Accept either a known provider value or any string and normalise to lowercase. */
14
+ export function coerceProvider(value) {
15
+ return value.toLowerCase();
16
+ }
17
+ export const CacheType = {
18
+ EXACT: 'exact',
19
+ SEMANTIC: 'semantic',
20
+ };
21
+ /** What PiiGuard does with a detected entity. */
22
+ export const PiiMode = {
23
+ REDACT: 'redact', // replace with a typed placeholder token
24
+ MASK: 'mask', // replace characters with asterisks
25
+ TAG: 'tag', // annotate inline but keep the value
26
+ BLOCK: 'block', // raise and refuse the request
27
+ };
28
+ export const Sensitivity = {
29
+ STRICT: 'strict',
30
+ BALANCED: 'balanced',
31
+ PERMISSIVE: 'permissive',
32
+ };
33
+ export const GuardrailOutcome = {
34
+ PASS: 'PASS',
35
+ FAIL: 'FAIL',
36
+ HITL: 'HITL',
37
+ };
38
+ /** Token accounting for a single completion. */
39
+ export class TokenUsage {
40
+ promptTokens;
41
+ completionTokens;
42
+ constructor(promptTokens = 0, completionTokens = 0) {
43
+ this.promptTokens = promptTokens;
44
+ this.completionTokens = completionTokens;
45
+ }
46
+ get totalTokens() {
47
+ return this.promptTokens + this.completionTokens;
48
+ }
49
+ toJSON() {
50
+ return {
51
+ promptTokens: this.promptTokens,
52
+ completionTokens: this.completionTokens,
53
+ totalTokens: this.totalTokens,
54
+ };
55
+ }
56
+ }
package/package.json ADDED
@@ -0,0 +1,115 @@
1
+ {
2
+ "name": "gavio",
3
+ "version": "0.1.0",
4
+ "description": "The open standard AI gateway for production systems — PII guarding, audit, reliability, and cost tracking.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/manojmallick/gavio.git",
9
+ "directory": "packages/gavio-js"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/manojmallick/gavio/issues"
13
+ },
14
+ "homepage": "https://github.com/manojmallick/gavio#readme",
15
+ "type": "module",
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "main": "./dist/cjs/index.js",
20
+ "module": "./dist/esm/index.js",
21
+ "types": "./dist/esm/index.d.ts",
22
+ "files": [
23
+ "dist",
24
+ "src",
25
+ "README.md"
26
+ ],
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/esm/index.d.ts",
30
+ "import": "./dist/esm/index.js",
31
+ "require": "./dist/cjs/index.js"
32
+ },
33
+ "./interceptors/pii": {
34
+ "types": "./dist/esm/interceptors/pii/index.d.ts",
35
+ "import": "./dist/esm/interceptors/pii/index.js",
36
+ "require": "./dist/cjs/interceptors/pii/index.js"
37
+ },
38
+ "./interceptors/pii/scanners": {
39
+ "types": "./dist/esm/interceptors/pii/scanners/index.d.ts",
40
+ "import": "./dist/esm/interceptors/pii/scanners/index.js",
41
+ "require": "./dist/cjs/interceptors/pii/scanners/index.js"
42
+ },
43
+ "./interceptors/audit": {
44
+ "types": "./dist/esm/interceptors/audit/index.d.ts",
45
+ "import": "./dist/esm/interceptors/audit/index.js",
46
+ "require": "./dist/cjs/interceptors/audit/index.js"
47
+ },
48
+ "./interceptors/audit/sinks": {
49
+ "types": "./dist/esm/interceptors/audit/sinks/index.d.ts",
50
+ "import": "./dist/esm/interceptors/audit/sinks/index.js",
51
+ "require": "./dist/cjs/interceptors/audit/sinks/index.js"
52
+ },
53
+ "./interceptors/cache": {
54
+ "types": "./dist/esm/interceptors/cache/index.d.ts",
55
+ "import": "./dist/esm/interceptors/cache/index.js",
56
+ "require": "./dist/cjs/interceptors/cache/index.js"
57
+ },
58
+ "./interceptors/cache/backends": {
59
+ "types": "./dist/esm/interceptors/cache/backends/index.d.ts",
60
+ "import": "./dist/esm/interceptors/cache/backends/index.js",
61
+ "require": "./dist/cjs/interceptors/cache/backends/index.js"
62
+ },
63
+ "./interceptors/reliability": {
64
+ "types": "./dist/esm/interceptors/reliability/index.d.ts",
65
+ "import": "./dist/esm/interceptors/reliability/index.js",
66
+ "require": "./dist/cjs/interceptors/reliability/index.js"
67
+ },
68
+ "./providers": {
69
+ "types": "./dist/esm/providers/index.d.ts",
70
+ "import": "./dist/esm/providers/index.js",
71
+ "require": "./dist/cjs/providers/index.js"
72
+ },
73
+ "./providers/openai": {
74
+ "types": "./dist/esm/providers/openai.d.ts",
75
+ "import": "./dist/esm/providers/openai.js",
76
+ "require": "./dist/cjs/providers/openai.js"
77
+ },
78
+ "./providers/anthropic": {
79
+ "types": "./dist/esm/providers/anthropic.d.ts",
80
+ "import": "./dist/esm/providers/anthropic.js",
81
+ "require": "./dist/cjs/providers/anthropic.js"
82
+ },
83
+ "./testing": {
84
+ "types": "./dist/esm/testing/index.d.ts",
85
+ "import": "./dist/esm/testing/index.js",
86
+ "require": "./dist/cjs/testing/index.js"
87
+ }
88
+ },
89
+ "scripts": {
90
+ "build": "npm run build:esm && npm run build:cjs && node scripts/finalize-build.mjs",
91
+ "build:esm": "tsc -p tsconfig.build.esm.json",
92
+ "build:cjs": "tsc -p tsconfig.build.cjs.json",
93
+ "clean": "rm -rf dist",
94
+ "typecheck": "tsc --noEmit",
95
+ "test": "vitest run",
96
+ "test:watch": "vitest",
97
+ "smoke": "npm run build && node scripts/smoke.mjs",
98
+ "prepublishOnly": "npm run clean && npm run build"
99
+ },
100
+ "keywords": [
101
+ "ai",
102
+ "gateway",
103
+ "llm",
104
+ "pii",
105
+ "audit",
106
+ "reliability",
107
+ "openai",
108
+ "anthropic"
109
+ ],
110
+ "devDependencies": {
111
+ "@types/node": "^22.10.0",
112
+ "typescript": "^5.7.0",
113
+ "vitest": "^2.1.0"
114
+ }
115
+ }
package/src/context.ts ADDED
@@ -0,0 +1,57 @@
1
+ /** Per-request context passed through the interceptor pipeline. */
2
+
3
+ export interface InterceptorContextInit {
4
+ traceId: string
5
+ agentId?: string | null
6
+ parentTraceId?: string | null
7
+ sessionId?: string | null
8
+ dryRun?: boolean
9
+ }
10
+
11
+ /**
12
+ * Mutable scratch space shared by all interceptors within one request. One
13
+ * instance per request — never shared across requests. Interceptors stash
14
+ * signals here (PII findings, cache decisions, risk scores) for the audit
15
+ * interceptor to collect at the end of the chain.
16
+ */
17
+ export class InterceptorContext {
18
+ traceId: string
19
+ agentId: string | null
20
+ parentTraceId: string | null
21
+ sessionId: string | null
22
+ dryRun: boolean
23
+
24
+ interceptorsFired: string[] = []
25
+ piiEntityTypes: string[] = []
26
+ piiEntityCounts: Record<string, number> = {}
27
+ cacheHit = false
28
+ cacheType: string | null = null
29
+ riskScore: number | null = null
30
+ guardrailOutcome: string | null = null
31
+
32
+ /** Arbitrary inter-interceptor state (e.g. PII replacement map for restore). */
33
+ state: Record<string, unknown> = {}
34
+
35
+ constructor(init: InterceptorContextInit) {
36
+ this.traceId = init.traceId
37
+ this.agentId = init.agentId ?? null
38
+ this.parentTraceId = init.parentTraceId ?? null
39
+ this.sessionId = init.sessionId ?? null
40
+ this.dryRun = init.dryRun ?? false
41
+ }
42
+
43
+ markFired(name: string): void {
44
+ if (!this.interceptorsFired.includes(name)) {
45
+ this.interceptorsFired.push(name)
46
+ }
47
+ }
48
+
49
+ recordPii(entityTypes: string[]): void {
50
+ for (const et of entityTypes) {
51
+ this.piiEntityCounts[et] = (this.piiEntityCounts[et] ?? 0) + 1
52
+ if (!this.piiEntityTypes.includes(et)) {
53
+ this.piiEntityTypes.push(et)
54
+ }
55
+ }
56
+ }
57
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Gavio error hierarchy. All Gavio errors derive from {@link GavioError} so
3
+ * callers can catch the whole family with a single check.
4
+ */
5
+
6
+ export class GavioError extends Error {
7
+ constructor(message?: string) {
8
+ super(message)
9
+ this.name = new.target.name
10
+ Object.setPrototypeOf(this, new.target.prototype)
11
+ }
12
+ }
13
+
14
+ /** Raised when the gateway is misconfigured (e.g. no provider set). */
15
+ export class ConfigurationError extends GavioError {}
16
+
17
+ /** Base class for provider-adapter failures. */
18
+ export class ProviderError extends GavioError {}
19
+
20
+ /** The provider could not be reached (network / health-check failure). */
21
+ export class ProviderUnavailableError extends ProviderError {}
22
+
23
+ /** The provider returned a rate-limit (HTTP 429) signal. */
24
+ export class RateLimitError extends ProviderError {}
25
+
26
+ /** The provider returned a 5xx server error. */
27
+ export class ServerError extends ProviderError {}
28
+
29
+ /** A request exceeded its configured timeout. */
30
+ export class TimeoutError extends ProviderError {}
31
+
32
+ /** PiiGuard is in BLOCK mode and detected PII in the request. */
33
+ export class PiiBlockedError extends GavioError {
34
+ readonly entityTypes: string[]
35
+
36
+ constructor(entityTypes: string[]) {
37
+ const sorted = Array.from(new Set(entityTypes)).sort()
38
+ super(`Request blocked: PII detected (${sorted.join(', ')})`)
39
+ this.entityTypes = entityTypes
40
+ }
41
+ }
42
+
43
+ /** A hard budget cap was exceeded. Never swallow this — surface to user. */
44
+ export class BudgetExceededError extends GavioError {}
45
+
46
+ /** Output failed a guardrail validator with onFailure='error'. */
47
+ export class GuardrailViolationError extends GavioError {}
package/src/gateway.ts ADDED
@@ -0,0 +1,174 @@
1
+ /** Gateway — the entry point. Wires interceptors around a provider adapter. */
2
+
3
+ import { InterceptorContext } from './context.js'
4
+ import { ConfigurationError } from './errors.js'
5
+ import { auditInterceptor, isAuditInterceptor } from './interceptors/audit/index.js'
6
+ import { isExecutorPolicy } from './interceptors/base.js'
7
+ import type { Executor, ExecutorPolicy, Interceptor } from './interceptors/base.js'
8
+ import { InterceptorChain } from './interceptors/chain.js'
9
+ import { PricingProvider } from './pricing.js'
10
+ import { buildAdapter } from './providers/index.js'
11
+ import type { ProviderAdapter } from './providers/base.js'
12
+ import { mockProvider } from './providers/mock.js'
13
+ import { GavioRequest } from './request.js'
14
+ import type { GavioResponse } from './response.js'
15
+ import { Provider, coerceProvider } from './types.js'
16
+ import type { Message } from './types.js'
17
+
18
+ export interface GatewayOptions {
19
+ provider?: Provider | string
20
+ model?: string
21
+ adapter?: ProviderAdapter
22
+ devMode?: boolean
23
+ dryRun?: boolean
24
+ pricing?: PricingProvider
25
+ }
26
+
27
+ export interface CompleteOptions {
28
+ messages: Message[]
29
+ model?: string
30
+ agentId?: string | null
31
+ parentTraceId?: string | null
32
+ sessionId?: string | null
33
+ metadata?: Record<string, unknown>
34
+ /** Provider sampling options (temperature, maxTokens, etc.). */
35
+ options?: Record<string, unknown>
36
+ }
37
+
38
+ const DEFAULT_MODELS: Record<string, string> = {
39
+ openai: 'gpt-4o',
40
+ anthropic: 'claude-sonnet-4-6',
41
+ mock: 'mock',
42
+ }
43
+
44
+ /**
45
+ * Routes a request through the interceptor pipeline to a provider.
46
+ *
47
+ * Construct with `new Gateway({ provider, model })` then chain `.use(...)` and
48
+ * `.withAdapter(...)`. A single instance is safe to reuse — per-request state
49
+ * lives in an {@link InterceptorContext} created fresh for every call.
50
+ */
51
+ export class Gateway {
52
+ private readonly providerHint: Provider | undefined
53
+ private modelHint: string | undefined
54
+ private adapterOverride: ProviderAdapter | undefined
55
+ private readonly devMode: boolean
56
+ private readonly dryRunMode: boolean
57
+ private readonly pricing: PricingProvider
58
+ private readonly interceptors: Interceptor[] = []
59
+
60
+ constructor(options: GatewayOptions = {}) {
61
+ this.providerHint = options.provider ? coerceProvider(options.provider) : undefined
62
+ this.modelHint = options.model
63
+ this.adapterOverride = options.adapter
64
+ this.devMode = options.devMode ?? false
65
+ this.dryRunMode = options.dryRun ?? false
66
+ this.pricing = options.pricing ?? new PricingProvider()
67
+ }
68
+
69
+ /** Register an interceptor or executor policy. First-registered = outermost. */
70
+ use(interceptor: Interceptor): this {
71
+ this.interceptors.push(interceptor)
72
+ return this
73
+ }
74
+
75
+ /** Supply a provider adapter explicitly (overrides `provider`). */
76
+ withAdapter(adapter: ProviderAdapter): this {
77
+ this.adapterOverride = adapter
78
+ return this
79
+ }
80
+
81
+ get model(): string {
82
+ return this.modelHint ?? this.resolveModel(this.resolveAdapter())
83
+ }
84
+
85
+ get providerName(): string {
86
+ return this.resolveAdapter().providerName
87
+ }
88
+
89
+ async complete(opts: CompleteOptions): Promise<GavioResponse> {
90
+ const adapter = this.resolveAdapter()
91
+ const model = opts.model ?? this.modelHint ?? this.resolveModel(adapter)
92
+
93
+ const request = new GavioRequest({
94
+ messages: opts.messages,
95
+ model,
96
+ provider: coerceProvider(adapter.providerName),
97
+ agentId: opts.agentId ?? null,
98
+ parentTraceId: opts.parentTraceId ?? null,
99
+ sessionId: opts.sessionId ?? null,
100
+ options: opts.options ?? {},
101
+ metadata: opts.metadata ?? {},
102
+ })
103
+ const ctx = new InterceptorContext({
104
+ traceId: request.traceId,
105
+ agentId: request.agentId,
106
+ parentTraceId: request.parentTraceId,
107
+ sessionId: request.sessionId,
108
+ dryRun: this.dryRunMode,
109
+ })
110
+
111
+ const { chain, executor } = this.buildPipeline(adapter, ctx)
112
+ return chain.execute(request, ctx, executor)
113
+ }
114
+
115
+ async healthCheck(): Promise<boolean> {
116
+ return this.resolveAdapter().healthCheck()
117
+ }
118
+
119
+ private buildPipeline(
120
+ adapter: ProviderAdapter,
121
+ ctx: InterceptorContext,
122
+ ): { chain: InterceptorChain; executor: Executor } {
123
+ let interceptors = [...this.interceptors]
124
+
125
+ // Dev mode auto-wires a stdout audit sink if none was added.
126
+ if (this.devMode && !interceptors.some(isAuditInterceptor)) {
127
+ interceptors = [auditInterceptor(), ...interceptors]
128
+ }
129
+
130
+ const policies = interceptors.filter(isExecutorPolicy)
131
+ const regular = interceptors.filter((i) => !isExecutorPolicy(i))
132
+ const chain = new InterceptorChain(regular)
133
+
134
+ let executor: Executor = (request) => adapter.complete(request)
135
+ // Wrap so the first-registered policy ends up outermost.
136
+ for (let i = policies.length - 1; i >= 0; i--) {
137
+ executor = this.wrapPolicy(policies[i]!, executor, ctx)
138
+ }
139
+ return { chain, executor }
140
+ }
141
+
142
+ private wrapPolicy(
143
+ policy: ExecutorPolicy,
144
+ inner: Executor,
145
+ ctx: InterceptorContext,
146
+ ): Executor {
147
+ return async (request: GavioRequest): Promise<GavioResponse> => {
148
+ if (ctx.dryRun && policy.dryRunSafe === false) {
149
+ return inner(request)
150
+ }
151
+ return policy.around(request, ctx, inner)
152
+ }
153
+ }
154
+
155
+ private resolveAdapter(): ProviderAdapter {
156
+ if (this.adapterOverride !== undefined) return this.adapterOverride
157
+ if (this.devMode) {
158
+ this.adapterOverride = mockProvider({ pricing: this.pricing })
159
+ return this.adapterOverride
160
+ }
161
+ if (this.providerHint === undefined) {
162
+ throw new ConfigurationError(
163
+ 'No provider configured. Pass { provider }, { adapter }, call .withAdapter(...), ' +
164
+ 'or set { devMode: true }.',
165
+ )
166
+ }
167
+ this.adapterOverride = buildAdapter(this.providerHint, this.pricing)
168
+ return this.adapterOverride
169
+ }
170
+
171
+ private resolveModel(adapter: ProviderAdapter): string {
172
+ return DEFAULT_MODELS[adapter.providerName] ?? 'mock'
173
+ }
174
+ }
package/src/ids.ts ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * UUID v7 generation — time-sortable, unique identifiers for traces.
3
+ *
4
+ * UUID v7 layout (RFC 9562): 48-bit Unix millisecond timestamp, 4-bit version,
5
+ * 12 bits used here as a per-millisecond monotonic sequence (rand_a), 2-bit
6
+ * variant, 62 bits of randomness. Ported directly from the Python `_ids.py`.
7
+ */
8
+
9
+ import { randomBytes } from 'node:crypto'
10
+
11
+ let lastMs = -1
12
+ let seq = 0 // 12-bit per-millisecond sequence in rand_a, for monotonicity
13
+
14
+ /**
15
+ * Return a [unix_ms, sequence] pair that is monotonically non-decreasing.
16
+ *
17
+ * Within a single millisecond the 12-bit sequence increments so IDs stay
18
+ * strictly ordered (RFC 9562 method 1). If the sequence overflows, the
19
+ * timestamp is nudged forward. JavaScript is single-threaded per event loop,
20
+ * so no lock is required.
21
+ */
22
+ function nextTimestampAndSeq(): [number, number] {
23
+ const nowMs = Date.now()
24
+ if (nowMs > lastMs) {
25
+ lastMs = nowMs
26
+ seq = randomBytes(2).readUInt16BE(0) & 0x0fff
27
+ } else {
28
+ seq += 1
29
+ if (seq > 0x0fff) {
30
+ lastMs += 1
31
+ seq = 0
32
+ }
33
+ }
34
+ return [lastMs, seq]
35
+ }
36
+
37
+ function toHex(value: bigint, width: number): string {
38
+ return value.toString(16).padStart(width, '0')
39
+ }
40
+
41
+ /** Return a new UUID version 7 (time-ordered, monotonic within a process). */
42
+ export function uuid7(): string {
43
+ const [unixMs, randA] = nextTimestampAndSeq()
44
+
45
+ // 48-bit millisecond timestamp occupies bits 80..127. Use BigInt for the
46
+ // shift — JS bitwise ops are 32-bit and would truncate Date.now() (~1.75e12).
47
+ const ms = BigInt(unixMs) & 0xffffffffffffn
48
+
49
+ const randBuf = randomBytes(8)
50
+ const randB = randBuf.readBigUInt64BE(0) & 0x3fffffffffffffffn // 62 bits
51
+
52
+ const value =
53
+ (ms << 80n) |
54
+ (0x7n << 76n) | // version 7
55
+ (BigInt(randA) << 64n) |
56
+ (0b10n << 62n) | // variant
57
+ randB
58
+
59
+ const hex = toHex(value, 32)
60
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(
61
+ 16,
62
+ 20,
63
+ )}-${hex.slice(20)}`
64
+ }
65
+
66
+ /** Return a fresh trace id as a string. */
67
+ export function newTraceId(): string {
68
+ return uuid7()
69
+ }