gavio 0.1.0 → 0.3.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 (160) hide show
  1. package/dist/cjs/config.js +106 -0
  2. package/dist/cjs/errors.js +29 -1
  3. package/dist/cjs/gateway.js +88 -0
  4. package/dist/cjs/index.js +4 -2
  5. package/dist/cjs/interceptors/audit/index.js +4 -1
  6. package/dist/cjs/interceptors/audit/interceptor.js +11 -0
  7. package/dist/cjs/interceptors/audit/record.js +17 -3
  8. package/dist/cjs/interceptors/audit/trace.js +43 -0
  9. package/dist/cjs/interceptors/cache/embedding.js +53 -0
  10. package/dist/cjs/interceptors/cache/index.js +9 -5
  11. package/dist/cjs/interceptors/cache/interceptor.js +80 -0
  12. package/dist/cjs/interceptors/cache/vector.js +35 -0
  13. package/dist/cjs/interceptors/governance/budget.js +45 -0
  14. package/dist/cjs/interceptors/governance/index.js +10 -0
  15. package/dist/cjs/interceptors/governance/model-policy.js +18 -0
  16. package/dist/cjs/interceptors/governance/rate-limit.js +46 -0
  17. package/dist/cjs/interceptors/guardrails/index.js +11 -0
  18. package/dist/cjs/interceptors/guardrails/interceptor.js +40 -0
  19. package/dist/cjs/interceptors/guardrails/validator.js +8 -0
  20. package/dist/cjs/interceptors/guardrails/validators/regex.js +32 -0
  21. package/dist/cjs/interceptors/guardrails/validators/schema.js +63 -0
  22. package/dist/cjs/interceptors/injection.js +62 -0
  23. package/dist/cjs/interceptors/metrics/index.js +9 -0
  24. package/dist/cjs/interceptors/metrics/interceptor.js +37 -0
  25. package/dist/cjs/interceptors/metrics/registry.js +0 -0
  26. package/dist/cjs/interceptors/quality/index.js +7 -0
  27. package/dist/cjs/interceptors/quality/risk.js +49 -0
  28. package/dist/cjs/interceptors/reliability/circuit-breaker.js +82 -0
  29. package/dist/cjs/interceptors/reliability/index.js +8 -1
  30. package/dist/cjs/interceptors/reliability/load-balancer.js +38 -0
  31. package/dist/cjs/interceptors/reliability/stream-buffer.js +28 -0
  32. package/dist/cjs/pricing.js +5 -1
  33. package/dist/cjs/providers/azure-openai.js +56 -0
  34. package/dist/cjs/providers/base.js +9 -0
  35. package/dist/cjs/providers/gemini.js +73 -0
  36. package/dist/cjs/providers/index.js +22 -6
  37. package/dist/cjs/providers/ollama.js +41 -0
  38. package/dist/cjs/request.js +3 -0
  39. package/dist/cjs/shim/openai.js +57 -0
  40. package/dist/cjs/types.js +53 -1
  41. package/dist/esm/config.d.ts +12 -0
  42. package/dist/esm/config.js +102 -0
  43. package/dist/esm/errors.d.ts +17 -0
  44. package/dist/esm/errors.js +24 -0
  45. package/dist/esm/gateway.d.ts +18 -1
  46. package/dist/esm/gateway.js +55 -0
  47. package/dist/esm/index.d.ts +3 -3
  48. package/dist/esm/index.js +2 -2
  49. package/dist/esm/interceptors/audit/index.d.ts +2 -0
  50. package/dist/esm/interceptors/audit/index.js +1 -0
  51. package/dist/esm/interceptors/audit/interceptor.d.ts +2 -0
  52. package/dist/esm/interceptors/audit/interceptor.js +11 -0
  53. package/dist/esm/interceptors/audit/record.d.ts +4 -2
  54. package/dist/esm/interceptors/audit/record.js +18 -4
  55. package/dist/esm/interceptors/audit/trace.d.ts +19 -0
  56. package/dist/esm/interceptors/audit/trace.js +39 -0
  57. package/dist/esm/interceptors/cache/embedding.d.ts +14 -0
  58. package/dist/esm/interceptors/cache/embedding.js +49 -0
  59. package/dist/esm/interceptors/cache/index.d.ts +7 -4
  60. package/dist/esm/interceptors/cache/index.js +4 -4
  61. package/dist/esm/interceptors/cache/interceptor.d.ts +19 -0
  62. package/dist/esm/interceptors/cache/interceptor.js +77 -0
  63. package/dist/esm/interceptors/cache/vector.d.ts +9 -0
  64. package/dist/esm/interceptors/cache/vector.js +32 -0
  65. package/dist/esm/interceptors/governance/budget.d.ts +11 -0
  66. package/dist/esm/interceptors/governance/budget.js +42 -0
  67. package/dist/esm/interceptors/governance/index.d.ts +7 -0
  68. package/dist/esm/interceptors/governance/index.js +4 -0
  69. package/dist/esm/interceptors/governance/model-policy.d.ts +8 -0
  70. package/dist/esm/interceptors/governance/model-policy.js +15 -0
  71. package/dist/esm/interceptors/governance/rate-limit.d.ts +9 -0
  72. package/dist/esm/interceptors/governance/rate-limit.js +43 -0
  73. package/dist/esm/interceptors/guardrails/index.d.ts +6 -0
  74. package/dist/esm/interceptors/guardrails/index.js +4 -0
  75. package/dist/esm/interceptors/guardrails/interceptor.d.ts +15 -0
  76. package/dist/esm/interceptors/guardrails/interceptor.js +37 -0
  77. package/dist/esm/interceptors/guardrails/validator.d.ts +11 -0
  78. package/dist/esm/interceptors/guardrails/validator.js +3 -0
  79. package/dist/esm/interceptors/guardrails/validators/regex.d.ts +6 -0
  80. package/dist/esm/interceptors/guardrails/validators/regex.js +28 -0
  81. package/dist/esm/interceptors/guardrails/validators/schema.d.ts +5 -0
  82. package/dist/esm/interceptors/guardrails/validators/schema.js +60 -0
  83. package/dist/esm/interceptors/injection.d.ts +17 -0
  84. package/dist/esm/interceptors/injection.js +59 -0
  85. package/dist/esm/interceptors/metrics/index.d.ts +5 -0
  86. package/dist/esm/interceptors/metrics/index.js +3 -0
  87. package/dist/esm/interceptors/metrics/interceptor.d.ts +22 -0
  88. package/dist/esm/interceptors/metrics/interceptor.js +33 -0
  89. package/dist/esm/interceptors/metrics/registry.d.ts +31 -0
  90. package/dist/esm/interceptors/metrics/registry.js +0 -0
  91. package/dist/esm/interceptors/quality/index.d.ts +3 -0
  92. package/dist/esm/interceptors/quality/index.js +2 -0
  93. package/dist/esm/interceptors/quality/risk.d.ts +32 -0
  94. package/dist/esm/interceptors/quality/risk.js +44 -0
  95. package/dist/esm/interceptors/reliability/circuit-breaker.d.ts +15 -0
  96. package/dist/esm/interceptors/reliability/circuit-breaker.js +78 -0
  97. package/dist/esm/interceptors/reliability/index.d.ts +5 -0
  98. package/dist/esm/interceptors/reliability/index.js +3 -0
  99. package/dist/esm/interceptors/reliability/load-balancer.d.ts +8 -0
  100. package/dist/esm/interceptors/reliability/load-balancer.js +35 -0
  101. package/dist/esm/interceptors/reliability/stream-buffer.d.ts +18 -0
  102. package/dist/esm/interceptors/reliability/stream-buffer.js +24 -0
  103. package/dist/esm/pricing.js +5 -1
  104. package/dist/esm/providers/azure-openai.d.ts +28 -0
  105. package/dist/esm/providers/azure-openai.js +53 -0
  106. package/dist/esm/providers/base.d.ts +7 -0
  107. package/dist/esm/providers/base.js +9 -1
  108. package/dist/esm/providers/gemini.d.ts +36 -0
  109. package/dist/esm/providers/gemini.js +69 -0
  110. package/dist/esm/providers/index.d.ts +7 -1
  111. package/dist/esm/providers/index.js +18 -5
  112. package/dist/esm/providers/ollama.d.ts +21 -0
  113. package/dist/esm/providers/ollama.js +38 -0
  114. package/dist/esm/request.d.ts +4 -1
  115. package/dist/esm/request.js +4 -1
  116. package/dist/esm/shim/openai.d.ts +56 -0
  117. package/dist/esm/shim/openai.js +53 -0
  118. package/dist/esm/types.d.ts +54 -0
  119. package/dist/esm/types.js +50 -0
  120. package/package.json +41 -2
  121. package/src/config.ts +125 -0
  122. package/src/errors.ts +28 -0
  123. package/src/gateway.ts +62 -1
  124. package/src/index.ts +4 -2
  125. package/src/interceptors/audit/index.ts +2 -0
  126. package/src/interceptors/audit/interceptor.ts +13 -0
  127. package/src/interceptors/audit/record.ts +18 -4
  128. package/src/interceptors/audit/trace.ts +47 -0
  129. package/src/interceptors/cache/embedding.ts +53 -0
  130. package/src/interceptors/cache/index.ts +7 -4
  131. package/src/interceptors/cache/interceptor.ts +111 -0
  132. package/src/interceptors/cache/vector.ts +45 -0
  133. package/src/interceptors/governance/budget.ts +59 -0
  134. package/src/interceptors/governance/index.ts +8 -0
  135. package/src/interceptors/governance/model-policy.ts +25 -0
  136. package/src/interceptors/governance/rate-limit.ts +63 -0
  137. package/src/interceptors/guardrails/index.ts +7 -0
  138. package/src/interceptors/guardrails/interceptor.ts +56 -0
  139. package/src/interceptors/guardrails/validator.ts +14 -0
  140. package/src/interceptors/guardrails/validators/regex.ts +29 -0
  141. package/src/interceptors/guardrails/validators/schema.ts +62 -0
  142. package/src/interceptors/injection.ts +72 -0
  143. package/src/interceptors/metrics/index.ts +6 -0
  144. package/src/interceptors/metrics/interceptor.ts +46 -0
  145. package/src/interceptors/metrics/registry.ts +0 -0
  146. package/src/interceptors/quality/index.ts +4 -0
  147. package/src/interceptors/quality/risk.ts +64 -0
  148. package/src/interceptors/reliability/circuit-breaker.ts +102 -0
  149. package/src/interceptors/reliability/index.ts +5 -0
  150. package/src/interceptors/reliability/load-balancer.ts +56 -0
  151. package/src/interceptors/reliability/stream-buffer.ts +27 -0
  152. package/src/pricing.ts +5 -1
  153. package/src/providers/azure-openai.ts +77 -0
  154. package/src/providers/base.ts +21 -1
  155. package/src/providers/gemini.ts +95 -0
  156. package/src/providers/index.ts +21 -5
  157. package/src/providers/ollama.ts +61 -0
  158. package/src/request.ts +6 -2
  159. package/src/shim/openai.ts +76 -0
  160. package/src/types.ts +77 -0
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ /**
3
+ * Config loader (F-DX-05) — build a Gateway from an object or a JSON file.
4
+ *
5
+ * const gw = await Gateway.fromConfig('gateway.json')
6
+ *
7
+ * JSON is supported out of the box; string values expand ${ENV_VAR}.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.loadConfig = loadConfig;
11
+ exports.buildFromConfig = buildFromConfig;
12
+ const node_fs_1 = require("node:fs");
13
+ const errors_js_1 = require("./errors.js");
14
+ const gateway_js_1 = require("./gateway.js");
15
+ const index_js_1 = require("./interceptors/audit/index.js");
16
+ const index_js_2 = require("./interceptors/cache/index.js");
17
+ const index_js_3 = require("./interceptors/governance/index.js");
18
+ const injection_js_1 = require("./interceptors/injection.js");
19
+ const index_js_4 = require("./interceptors/pii/index.js");
20
+ const index_js_5 = require("./interceptors/reliability/index.js");
21
+ function loadConfig(path) {
22
+ const text = (0, node_fs_1.readFileSync)(path, 'utf8');
23
+ if (!path.endsWith('.json')) {
24
+ throw new errors_js_1.ConfigurationError('JS config loader supports JSON only (use .json)');
25
+ }
26
+ return expand(JSON.parse(text));
27
+ }
28
+ function expand(obj) {
29
+ if (Array.isArray(obj))
30
+ return obj.map(expand);
31
+ if (obj && typeof obj === 'object') {
32
+ return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, expand(v)]));
33
+ }
34
+ if (typeof obj === 'string') {
35
+ return obj.replace(/\$\{(\w+)\}/g, (_, v) => process.env[v] ?? '');
36
+ }
37
+ return obj;
38
+ }
39
+ function buildFromConfig(config) {
40
+ const gatewayOptions = {};
41
+ if (config['provider'])
42
+ gatewayOptions['provider'] = config['provider'];
43
+ if (config['model'])
44
+ gatewayOptions['model'] = config['model'];
45
+ if (config['devMode'] ?? config['dev_mode'])
46
+ gatewayOptions['devMode'] = true;
47
+ if (config['dryRun'] ?? config['dry_run'])
48
+ gatewayOptions['dryRun'] = true;
49
+ let gw = new gateway_js_1.Gateway(gatewayOptions);
50
+ const ic = config['interceptors'] ?? {};
51
+ const cfg = (name) => {
52
+ const entry = ic[name];
53
+ return entry && entry['enabled'] !== false ? entry : null;
54
+ };
55
+ let c;
56
+ if ((c = cfg('audit'))) {
57
+ gw = gw.use((0, index_js_1.auditInterceptor)({
58
+ sink: c['sink'] ?? 'stdout',
59
+ hashChain: Boolean(c['hashChain'] ?? c['hash_chain']),
60
+ }));
61
+ }
62
+ if ((c = cfg('prompt_injection'))) {
63
+ gw = gw.use((0, injection_js_1.promptInjectionGuard)({ action: c['action'] ?? 'block' }));
64
+ }
65
+ if ((c = cfg('pii_guard'))) {
66
+ gw = gw.use((0, index_js_4.piiGuard)({
67
+ sensitivity: c['sensitivity'] ?? 'strict',
68
+ mode: c['mode'] ?? 'redact',
69
+ }));
70
+ }
71
+ if ((c = cfg('cost_control'))) {
72
+ gw = gw.use((0, index_js_3.costControl)({
73
+ hardCapUsd: Number(c['hardCapUsd'] ?? c['hard_cap_usd']),
74
+ softCapUsd: (c['softCapUsd'] ?? c['soft_cap_usd']),
75
+ scope: c['scope'] ?? 'global',
76
+ window: c['window'] ?? 'day',
77
+ }));
78
+ }
79
+ if ((c = cfg('rate_limiter'))) {
80
+ gw = gw.use((0, index_js_3.rateLimiter)({
81
+ maxRequestsPerMinute: (c['maxRequestsPerMinute'] ?? c['max_requests_per_minute']),
82
+ maxTokensPerMinute: (c['maxTokensPerMinute'] ?? c['max_tokens_per_minute']),
83
+ scope: c['scope'] ?? 'global',
84
+ }));
85
+ }
86
+ if ((c = cfg('model_policy'))) {
87
+ gw = gw.use((0, index_js_3.modelPolicy)({ roles: c['roles'] ?? {} }));
88
+ }
89
+ if ((c = cfg('semantic_cache'))) {
90
+ const embedder = (c['enableSemantic'] ?? c['enable_semantic']) ? (0, index_js_2.hashingEmbedder)() : undefined;
91
+ gw = gw.use((0, index_js_2.semanticCache)({
92
+ embedder,
93
+ similarityThreshold: Number(c['similarityThreshold'] ?? c['similarity_threshold'] ?? 0.95),
94
+ }));
95
+ }
96
+ if ((c = cfg('timeout'))) {
97
+ gw = gw.use((0, index_js_5.timeoutPolicy)({ timeoutSeconds: Number(c['timeoutSeconds'] ?? c['timeout_seconds'] ?? 30) }));
98
+ }
99
+ if ((c = cfg('retry'))) {
100
+ gw = gw.use((0, index_js_5.retryInterceptor)({
101
+ maxAttempts: Number(c['maxAttempts'] ?? c['max_attempts'] ?? 3),
102
+ baseDelayMs: Number(c['baseDelayMs'] ?? c['base_delay_ms'] ?? 500),
103
+ }));
104
+ }
105
+ return gw;
106
+ }
@@ -4,7 +4,7 @@
4
4
  * callers can catch the whole family with a single check.
5
5
  */
6
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;
7
+ exports.PromptInjectionError = exports.GuardrailViolationError = exports.ModelNotAllowedError = exports.RateLimitExceededError = exports.CircuitOpenError = exports.BudgetExceededError = exports.PiiBlockedError = exports.TimeoutError = exports.ServerError = exports.RateLimitError = exports.ProviderUnavailableError = exports.ProviderError = exports.ConfigurationError = exports.GavioError = void 0;
8
8
  class GavioError extends Error {
9
9
  constructor(message) {
10
10
  super(message);
@@ -51,7 +51,35 @@ exports.PiiBlockedError = PiiBlockedError;
51
51
  class BudgetExceededError extends GavioError {
52
52
  }
53
53
  exports.BudgetExceededError = BudgetExceededError;
54
+ /** The circuit breaker is open; the call was rejected without hitting the provider. */
55
+ class CircuitOpenError extends ProviderUnavailableError {
56
+ }
57
+ exports.CircuitOpenError = CircuitOpenError;
58
+ /** A local rate limit (requests/tokens per minute) was exceeded. */
59
+ class RateLimitExceededError extends GavioError {
60
+ }
61
+ exports.RateLimitExceededError = RateLimitExceededError;
62
+ /** The caller's role is not permitted to use the requested model (RBAC). */
63
+ class ModelNotAllowedError extends GavioError {
64
+ role;
65
+ model;
66
+ constructor(role, model) {
67
+ super(`role ${JSON.stringify(role)} may not use model ${JSON.stringify(model)}`);
68
+ this.role = role;
69
+ this.model = model;
70
+ }
71
+ }
72
+ exports.ModelNotAllowedError = ModelNotAllowedError;
54
73
  /** Output failed a guardrail validator with onFailure='error'. */
55
74
  class GuardrailViolationError extends GavioError {
56
75
  }
57
76
  exports.GuardrailViolationError = GuardrailViolationError;
77
+ /** A prompt-injection attempt was detected and the guard is in block mode. */
78
+ class PromptInjectionError extends GavioError {
79
+ patterns;
80
+ constructor(patterns) {
81
+ super(`prompt injection detected: ${patterns.join(', ')}`);
82
+ this.patterns = patterns;
83
+ }
84
+ }
85
+ exports.PromptInjectionError = PromptInjectionError;
@@ -1,5 +1,38 @@
1
1
  "use strict";
2
2
  /** Gateway — the entry point. Wires interceptors around a provider adapter. */
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  Object.defineProperty(exports, "__esModule", { value: true });
4
37
  exports.Gateway = void 0;
5
38
  const context_js_1 = require("./context.js");
@@ -7,6 +40,7 @@ const errors_js_1 = require("./errors.js");
7
40
  const index_js_1 = require("./interceptors/audit/index.js");
8
41
  const base_js_1 = require("./interceptors/base.js");
9
42
  const chain_js_1 = require("./interceptors/chain.js");
43
+ const stream_buffer_js_1 = require("./interceptors/reliability/stream-buffer.js");
10
44
  const pricing_js_1 = require("./pricing.js");
11
45
  const index_js_2 = require("./providers/index.js");
12
46
  const mock_js_1 = require("./providers/mock.js");
@@ -40,6 +74,15 @@ class Gateway {
40
74
  this.dryRunMode = options.dryRun ?? false;
41
75
  this.pricing = options.pricing ?? new pricing_js_1.PricingProvider();
42
76
  }
77
+ /**
78
+ * Build a Gateway from a config object or a JSON file path (F-DX-05).
79
+ * Async so the config module loads lazily (avoids a circular import).
80
+ */
81
+ static async fromConfig(config) {
82
+ const mod = await Promise.resolve().then(() => __importStar(require('./config.js')));
83
+ const data = typeof config === 'string' ? mod.loadConfig(config) : config;
84
+ return mod.buildFromConfig(data);
85
+ }
43
86
  /** Register an interceptor or executor policy. First-registered = outermost. */
44
87
  use(interceptor) {
45
88
  this.interceptors.push(interceptor);
@@ -68,6 +111,7 @@ class Gateway {
68
111
  sessionId: opts.sessionId ?? null,
69
112
  options: opts.options ?? {},
70
113
  metadata: opts.metadata ?? {},
114
+ lineage: opts.lineage ?? null,
71
115
  });
72
116
  const ctx = new context_js_1.InterceptorContext({
73
117
  traceId: request.traceId,
@@ -79,6 +123,50 @@ class Gateway {
79
123
  const { chain, executor } = this.buildPipeline(adapter, ctx);
80
124
  return chain.execute(request, ctx, executor);
81
125
  }
126
+ /**
127
+ * Stream a completion, buffering the provider stream (F-REL-06).
128
+ *
129
+ * The provider stream is buffered in full so the post-interceptor pipeline
130
+ * (guardrails, PII restore, audit) runs on the complete response before any
131
+ * chunk reaches the caller. Pre/post interceptors run via the chain; executor
132
+ * policies (retry, circuit breaker, cache) are not applied to the streaming
133
+ * path.
134
+ */
135
+ async *stream(opts) {
136
+ const adapter = this.resolveAdapter();
137
+ if (adapter.stream === undefined || adapter.buildStreamResponse === undefined) {
138
+ throw new errors_js_1.ConfigurationError(`${adapter.providerName} does not support streaming`);
139
+ }
140
+ const model = opts.model ?? this.modelHint ?? this.resolveModel(adapter);
141
+ const request = new request_js_1.GavioRequest({
142
+ messages: opts.messages,
143
+ model,
144
+ provider: (0, types_js_1.coerceProvider)(adapter.providerName),
145
+ agentId: opts.agentId ?? null,
146
+ parentTraceId: opts.parentTraceId ?? null,
147
+ sessionId: opts.sessionId ?? null,
148
+ options: opts.options ?? {},
149
+ metadata: opts.metadata ?? {},
150
+ });
151
+ const ctx = new context_js_1.InterceptorContext({
152
+ traceId: request.traceId,
153
+ agentId: request.agentId,
154
+ parentTraceId: request.parentTraceId,
155
+ sessionId: request.sessionId,
156
+ dryRun: this.dryRunMode,
157
+ });
158
+ const startedAt = performance.now();
159
+ const buffer = new stream_buffer_js_1.StreamBuffer();
160
+ const { chain } = this.buildPipeline(adapter, ctx);
161
+ const bufferingExecutor = async (req) => {
162
+ for await (const chunk of adapter.stream(req))
163
+ buffer.append(chunk);
164
+ return adapter.buildStreamResponse(req, buffer.text(), startedAt);
165
+ };
166
+ const response = await chain.execute(request, ctx, bufferingExecutor);
167
+ // Post-interceptors have run on the fully buffered response; emit it now.
168
+ yield response.content;
169
+ }
82
170
  async healthCheck() {
83
171
  return this.resolveAdapter().healthCheck();
84
172
  }
package/dist/cjs/index.js CHANGED
@@ -9,8 +9,8 @@
9
9
  * See https://gavio.io for documentation. MIT licensed.
10
10
  */
11
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';
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.RagChunk = exports.PromptLineage = 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.3.0';
14
14
  var gateway_js_1 = require("./gateway.js");
15
15
  Object.defineProperty(exports, "Gateway", { enumerable: true, get: function () { return gateway_js_1.Gateway; } });
16
16
  var request_js_1 = require("./request.js");
@@ -32,6 +32,8 @@ Object.defineProperty(exports, "PiiMode", { enumerable: true, get: function () {
32
32
  Object.defineProperty(exports, "Sensitivity", { enumerable: true, get: function () { return types_js_1.Sensitivity; } });
33
33
  Object.defineProperty(exports, "GuardrailOutcome", { enumerable: true, get: function () { return types_js_1.GuardrailOutcome; } });
34
34
  Object.defineProperty(exports, "TokenUsage", { enumerable: true, get: function () { return types_js_1.TokenUsage; } });
35
+ Object.defineProperty(exports, "PromptLineage", { enumerable: true, get: function () { return types_js_1.PromptLineage; } });
36
+ Object.defineProperty(exports, "RagChunk", { enumerable: true, get: function () { return types_js_1.RagChunk; } });
35
37
  Object.defineProperty(exports, "coerceProvider", { enumerable: true, get: function () { return types_js_1.coerceProvider; } });
36
38
  var chain_js_1 = require("./interceptors/chain.js");
37
39
  Object.defineProperty(exports, "InterceptorChain", { enumerable: true, get: function () { return chain_js_1.InterceptorChain; } });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.stdoutSink = exports.SCHEMA_VERSION = exports.AuditRecord = exports.AUDIT_NAME = exports.isAuditInterceptor = exports.auditInterceptor = void 0;
3
+ exports.buildCallGraph = exports.verifyChain = exports.stdoutSink = exports.SCHEMA_VERSION = exports.AuditRecord = exports.AUDIT_NAME = exports.isAuditInterceptor = exports.auditInterceptor = void 0;
4
4
  var interceptor_js_1 = require("./interceptor.js");
5
5
  Object.defineProperty(exports, "auditInterceptor", { enumerable: true, get: function () { return interceptor_js_1.auditInterceptor; } });
6
6
  Object.defineProperty(exports, "isAuditInterceptor", { enumerable: true, get: function () { return interceptor_js_1.isAuditInterceptor; } });
@@ -10,3 +10,6 @@ Object.defineProperty(exports, "AuditRecord", { enumerable: true, get: function
10
10
  Object.defineProperty(exports, "SCHEMA_VERSION", { enumerable: true, get: function () { return record_js_1.SCHEMA_VERSION; } });
11
11
  var stdout_js_1 = require("./sinks/stdout.js");
12
12
  Object.defineProperty(exports, "stdoutSink", { enumerable: true, get: function () { return stdout_js_1.stdoutSink; } });
13
+ var trace_js_1 = require("./trace.js");
14
+ Object.defineProperty(exports, "verifyChain", { enumerable: true, get: function () { return trace_js_1.verifyChain; } });
15
+ Object.defineProperty(exports, "buildCallGraph", { enumerable: true, get: function () { return trace_js_1.buildCallGraph; } });
@@ -7,6 +7,7 @@ exports.isAuditInterceptor = isAuditInterceptor;
7
7
  const record_js_1 = require("./record.js");
8
8
  const stdout_js_1 = require("./sinks/stdout.js");
9
9
  const PROMPT_HASH_KEY = 'audit_prompt_hash';
10
+ const LINEAGE_KEY = 'audit_lineage';
10
11
  exports.AUDIT_NAME = 'audit';
11
12
  /**
12
13
  * Build an AuditRecord per request and write it to a sink.
@@ -20,11 +21,16 @@ class AuditInterceptor {
20
21
  name = exports.AUDIT_NAME;
21
22
  dryRunSafe = true; // auditing is observation-only, so it always runs
22
23
  sink;
24
+ hashChain;
25
+ lastHash = '';
23
26
  constructor(options = {}) {
24
27
  this.sink = resolveSink(options.sink);
28
+ this.hashChain = options.hashChain ?? false;
25
29
  }
26
30
  async before(request, ctx) {
27
31
  ctx.state[PROMPT_HASH_KEY] = record_js_1.AuditRecord.hashText(request.promptText());
32
+ if (request.lineage != null)
33
+ ctx.state[LINEAGE_KEY] = request.lineage;
28
34
  return request;
29
35
  }
30
36
  async after(response, ctx) {
@@ -49,7 +55,12 @@ class AuditInterceptor {
49
55
  cacheType: response.cacheType,
50
56
  guardrailOutcome: ctx.guardrailOutcome,
51
57
  riskScore: ctx.riskScore,
58
+ lineage: ctx.state[LINEAGE_KEY] ?? null,
52
59
  });
60
+ if (this.hashChain) {
61
+ record.previousHash = this.lastHash;
62
+ this.lastHash = record.contentHash();
63
+ }
53
64
  response.audit = record;
54
65
  try {
55
66
  await this.sink.write(record);
@@ -8,6 +8,18 @@ exports.SCHEMA_VERSION = '1.0';
8
8
  function sha256(text) {
9
9
  return (0, node_crypto_1.createHash)('sha256').update(text, 'utf-8').digest('hex');
10
10
  }
11
+ /** Deterministic JSON with keys sorted at every nesting level. */
12
+ function stableStringify(value) {
13
+ if (value === null || typeof value !== 'object')
14
+ return JSON.stringify(value) ?? 'null';
15
+ if (Array.isArray(value))
16
+ return `[${value.map(stableStringify).join(',')}]`;
17
+ const obj = value;
18
+ const parts = Object.keys(obj)
19
+ .sort()
20
+ .map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`);
21
+ return `{${parts.join(',')}}`;
22
+ }
11
23
  /**
12
24
  * One append-only audit entry. Carries metadata only — never raw content.
13
25
  *
@@ -36,6 +48,7 @@ class AuditRecord {
36
48
  cacheType;
37
49
  guardrailOutcome;
38
50
  riskScore;
51
+ lineage;
39
52
  previousHash;
40
53
  schemaVersion;
41
54
  constructor(init) {
@@ -59,6 +72,7 @@ class AuditRecord {
59
72
  this.cacheType = init.cacheType ?? null;
60
73
  this.guardrailOutcome = init.guardrailOutcome ?? null;
61
74
  this.riskScore = init.riskScore ?? null;
75
+ this.lineage = init.lineage ?? null;
62
76
  this.previousHash = init.previousHash ?? '';
63
77
  this.schemaVersion = init.schemaVersion ?? exports.SCHEMA_VERSION;
64
78
  }
@@ -90,14 +104,14 @@ class AuditRecord {
90
104
  cacheType: this.cacheType,
91
105
  guardrailOutcome: this.guardrailOutcome,
92
106
  riskScore: this.riskScore,
107
+ lineage: this.lineage ? this.lineage.toJSON() : null,
93
108
  previousHash: this.previousHash,
94
109
  schemaVersion: this.schemaVersion,
95
110
  };
96
111
  }
97
- /** Stable JSON with sorted keys — used for the v0.2.0 hash chain. */
112
+ /** Stable JSON with recursively sorted keys — used for the v0.2.0 hash chain. */
98
113
  toCanonicalJson() {
99
- const data = this.toJSON();
100
- return JSON.stringify(data, Object.keys(data).sort());
114
+ return stableStringify(this.toJSON());
101
115
  }
102
116
  /** Hash of this record's content — used to build the v0.2.0 chain. */
103
117
  contentHash() {
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ /** Audit-chain verification (F-OBS-02) and multi-agent DAG trace (F-OBS-03). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.verifyChain = verifyChain;
5
+ exports.buildCallGraph = buildCallGraph;
6
+ /**
7
+ * Return true if the records form an intact hash chain. Each record's
8
+ * previousHash must equal the content hash of the record before it; the first
9
+ * must be empty. Any edit, reorder, or deletion breaks the chain.
10
+ */
11
+ function verifyChain(records) {
12
+ let prevHash = '';
13
+ for (const rec of records) {
14
+ if (rec.previousHash !== prevHash)
15
+ return false;
16
+ prevHash = rec.contentHash();
17
+ }
18
+ return true;
19
+ }
20
+ /**
21
+ * Reconstruct the multi-agent DAG from audit records using parentTraceId +
22
+ * traceId. Returns the root nodes (those with no known parent).
23
+ */
24
+ function buildCallGraph(records) {
25
+ const nodes = new Map();
26
+ for (const rec of records) {
27
+ nodes.set(rec.traceId, {
28
+ traceId: rec.traceId,
29
+ agentId: rec.agentId,
30
+ parentTraceId: rec.parentTraceId,
31
+ children: [],
32
+ });
33
+ }
34
+ const roots = [];
35
+ for (const node of nodes.values()) {
36
+ const parent = node.parentTraceId ? nodes.get(node.parentTraceId) : undefined;
37
+ if (parent)
38
+ parent.children.push(node);
39
+ else
40
+ roots.push(node);
41
+ }
42
+ return roots;
43
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /**
3
+ * Embeddings for the semantic cache (F-CACHE-02).
4
+ *
5
+ * Zero-dependency hashed bag-of-words embedder (L2-normalised) — good enough to
6
+ * dedup near-identical prompts. Plug in a real embedder implementing `Embedder`
7
+ * for production semantic matching.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.hashingEmbedder = hashingEmbedder;
11
+ exports.cosineSimilarity = cosineSimilarity;
12
+ const node_crypto_1 = require("node:crypto");
13
+ const TOKEN = /[a-z0-9]+/g;
14
+ /** Deterministic hashed bag-of-words embedder. */
15
+ function hashingEmbedder(dim = 256) {
16
+ return {
17
+ embed(text) {
18
+ const vec = new Array(dim).fill(0);
19
+ const tokens = text.toLowerCase().match(TOKEN) ?? [];
20
+ for (const token of tokens) {
21
+ // Parity note: Python uses blake2b(digest_size=8); here we take the
22
+ // first 8 bytes of blake2b512. Both are deterministic; the JS cache is
23
+ // per-process so cross-language byte-parity is not required.
24
+ const digest = (0, node_crypto_1.createHash)('blake2b512').update(token).digest();
25
+ let n = 0n;
26
+ for (let i = 0; i < 8; i++)
27
+ n = (n << 8n) | BigInt(digest[i]);
28
+ const bucket = Number(n % BigInt(dim));
29
+ vec[bucket] += 1;
30
+ }
31
+ const norm = Math.sqrt(vec.reduce((s, x) => s + x * x, 0));
32
+ if (norm === 0)
33
+ return vec;
34
+ return vec.map((x) => x / norm);
35
+ },
36
+ };
37
+ }
38
+ /** Cosine similarity; safe for zero vectors. */
39
+ function cosineSimilarity(a, b) {
40
+ if (a.length !== b.length)
41
+ throw new Error('vectors must have equal length');
42
+ let dot = 0;
43
+ let na = 0;
44
+ let nb = 0;
45
+ for (let i = 0; i < a.length; i++) {
46
+ dot += a[i] * b[i];
47
+ na += a[i] * a[i];
48
+ nb += b[i] * b[i];
49
+ }
50
+ if (na === 0 || nb === 0)
51
+ return 0;
52
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
53
+ }
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
- /**
3
- * Caching substrate. The SemanticCache interceptor ships in v0.2.0; v0.1.0
4
- * exposes the CacheBackend interface and the in-memory backend only.
5
- */
2
+ /** Caching (F-CACHE-01 exact, F-CACHE-02 semantic, F-CACHE-03 in-memory). */
6
3
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.memoryCacheBackend = void 0;
4
+ exports.inMemoryVectorBackend = exports.cosineSimilarity = exports.hashingEmbedder = exports.semanticCache = exports.memoryCacheBackend = void 0;
8
5
  var memory_js_1 = require("./backends/memory.js");
9
6
  Object.defineProperty(exports, "memoryCacheBackend", { enumerable: true, get: function () { return memory_js_1.memoryCacheBackend; } });
7
+ var interceptor_js_1 = require("./interceptor.js");
8
+ Object.defineProperty(exports, "semanticCache", { enumerable: true, get: function () { return interceptor_js_1.semanticCache; } });
9
+ var embedding_js_1 = require("./embedding.js");
10
+ Object.defineProperty(exports, "hashingEmbedder", { enumerable: true, get: function () { return embedding_js_1.hashingEmbedder; } });
11
+ Object.defineProperty(exports, "cosineSimilarity", { enumerable: true, get: function () { return embedding_js_1.cosineSimilarity; } });
12
+ var vector_js_1 = require("./vector.js");
13
+ Object.defineProperty(exports, "inMemoryVectorBackend", { enumerable: true, get: function () { return vector_js_1.inMemoryVectorBackend; } });
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /**
3
+ * semanticCache (F-CACHE-01, F-CACHE-02) — two-level cache as an ExecutorPolicy.
4
+ *
5
+ * Exact SHA-256 cache, then optional semantic cosine cache; a hit returns the
6
+ * cached response and skips the provider. Register outermost.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.semanticCache = semanticCache;
10
+ const node_crypto_1 = require("node:crypto");
11
+ const response_js_1 = require("../../response.js");
12
+ const types_js_1 = require("../../types.js");
13
+ const memory_js_1 = require("./backends/memory.js");
14
+ const vector_js_1 = require("./vector.js");
15
+ function semanticCache(options = {}) {
16
+ const backend = options.backend ?? (0, memory_js_1.memoryCacheBackend)();
17
+ const embedder = options.embedder;
18
+ const semantic = embedder != null;
19
+ const vector = options.vectorBackend ?? (semantic ? (0, vector_js_1.inMemoryVectorBackend)() : null);
20
+ const exactTtl = options.exactTtlSeconds ?? 3600;
21
+ const semanticTtl = options.semanticTtlSeconds ?? 86400;
22
+ const threshold = options.similarityThreshold ?? 0.95;
23
+ function exactKey(request) {
24
+ const opts = request.options ?? {};
25
+ const sorted = {};
26
+ for (const k of Object.keys(opts).sort())
27
+ sorted[k] = opts[k];
28
+ const payload = JSON.stringify({
29
+ provider: String(request.provider),
30
+ model: request.model,
31
+ messages: request.messages,
32
+ options: sorted,
33
+ });
34
+ return 'gavio:exact:' + (0, node_crypto_1.createHash)('sha256').update(payload).digest('hex');
35
+ }
36
+ function hit(request, ctx, entry, type) {
37
+ ctx.cacheHit = true;
38
+ ctx.cacheType = type;
39
+ return new response_js_1.GavioResponse({
40
+ traceId: request.traceId,
41
+ content: entry.content,
42
+ model: request.model,
43
+ provider: String(request.provider),
44
+ modelVersion: entry.modelVersion,
45
+ usage: new types_js_1.TokenUsage(entry.promptTokens, entry.completionTokens),
46
+ costUsd: 0,
47
+ cacheHit: true,
48
+ cacheType: type,
49
+ });
50
+ }
51
+ return {
52
+ name: 'semantic_cache',
53
+ isExecutorPolicy: true,
54
+ async around(request, ctx, callNext) {
55
+ ctx.markFired('semantic_cache');
56
+ const key = exactKey(request);
57
+ const cached = (await backend.get(key));
58
+ if (cached)
59
+ return hit(request, ctx, cached, types_js_1.CacheType.EXACT);
60
+ let embedding = null;
61
+ if (semantic && vector && embedder) {
62
+ embedding = embedder.embed(request.promptText());
63
+ const semHit = (await vector.query(embedding, threshold));
64
+ if (semHit)
65
+ return hit(request, ctx, semHit, types_js_1.CacheType.SEMANTIC);
66
+ }
67
+ const response = await callNext(request);
68
+ const entry = {
69
+ content: response.content,
70
+ modelVersion: response.modelVersion,
71
+ promptTokens: response.usage.promptTokens,
72
+ completionTokens: response.usage.completionTokens,
73
+ };
74
+ await backend.set(key, entry, exactTtl);
75
+ if (embedding && vector)
76
+ await vector.add(embedding, entry, semanticTtl);
77
+ return response;
78
+ },
79
+ };
80
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /** VectorBackend — nearest-neighbour store for the semantic cache (F-CACHE-02). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.inMemoryVectorBackend = inMemoryVectorBackend;
5
+ const embedding_js_1 = require("./embedding.js");
6
+ /** Bounded, brute-force in-memory vector store (default dev backend). */
7
+ function inMemoryVectorBackend(maxSize = 1000) {
8
+ const items = [];
9
+ return {
10
+ async add(vector, value, ttlSeconds) {
11
+ const expiresAt = ttlSeconds ? Date.now() + ttlSeconds * 1000 : null;
12
+ items.push({ vector, value, expiresAt });
13
+ if (items.length > maxSize)
14
+ items.shift();
15
+ },
16
+ async query(vector, threshold) {
17
+ const now = Date.now();
18
+ let best = null;
19
+ let bestSim = threshold;
20
+ for (const item of items) {
21
+ if (item.expiresAt !== null && now > item.expiresAt)
22
+ continue;
23
+ const sim = (0, embedding_js_1.cosineSimilarity)(vector, item.vector);
24
+ if (sim >= bestSim) {
25
+ bestSim = sim;
26
+ best = item.value;
27
+ }
28
+ }
29
+ return best;
30
+ },
31
+ async clear() {
32
+ items.length = 0;
33
+ },
34
+ };
35
+ }