gavio 0.1.0 → 0.2.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 (116) hide show
  1. package/dist/cjs/config.js +106 -0
  2. package/dist/cjs/errors.js +29 -1
  3. package/dist/cjs/gateway.js +42 -0
  4. package/dist/cjs/interceptors/audit/index.js +4 -1
  5. package/dist/cjs/interceptors/audit/interceptor.js +7 -0
  6. package/dist/cjs/interceptors/audit/trace.js +43 -0
  7. package/dist/cjs/interceptors/cache/embedding.js +53 -0
  8. package/dist/cjs/interceptors/cache/index.js +9 -5
  9. package/dist/cjs/interceptors/cache/interceptor.js +80 -0
  10. package/dist/cjs/interceptors/cache/vector.js +35 -0
  11. package/dist/cjs/interceptors/governance/budget.js +45 -0
  12. package/dist/cjs/interceptors/governance/index.js +10 -0
  13. package/dist/cjs/interceptors/governance/model-policy.js +18 -0
  14. package/dist/cjs/interceptors/governance/rate-limit.js +46 -0
  15. package/dist/cjs/interceptors/guardrails/index.js +11 -0
  16. package/dist/cjs/interceptors/guardrails/interceptor.js +40 -0
  17. package/dist/cjs/interceptors/guardrails/validator.js +8 -0
  18. package/dist/cjs/interceptors/guardrails/validators/regex.js +32 -0
  19. package/dist/cjs/interceptors/guardrails/validators/schema.js +63 -0
  20. package/dist/cjs/interceptors/injection.js +62 -0
  21. package/dist/cjs/interceptors/reliability/circuit-breaker.js +82 -0
  22. package/dist/cjs/interceptors/reliability/index.js +6 -1
  23. package/dist/cjs/interceptors/reliability/load-balancer.js +38 -0
  24. package/dist/cjs/pricing.js +5 -1
  25. package/dist/cjs/providers/azure-openai.js +56 -0
  26. package/dist/cjs/providers/gemini.js +73 -0
  27. package/dist/cjs/providers/index.js +22 -6
  28. package/dist/cjs/providers/ollama.js +41 -0
  29. package/dist/cjs/shim/openai.js +57 -0
  30. package/dist/esm/config.d.ts +12 -0
  31. package/dist/esm/config.js +102 -0
  32. package/dist/esm/errors.d.ts +17 -0
  33. package/dist/esm/errors.js +24 -0
  34. package/dist/esm/gateway.d.ts +5 -0
  35. package/dist/esm/gateway.js +9 -0
  36. package/dist/esm/interceptors/audit/index.d.ts +2 -0
  37. package/dist/esm/interceptors/audit/index.js +1 -0
  38. package/dist/esm/interceptors/audit/interceptor.d.ts +2 -0
  39. package/dist/esm/interceptors/audit/interceptor.js +7 -0
  40. package/dist/esm/interceptors/audit/trace.d.ts +19 -0
  41. package/dist/esm/interceptors/audit/trace.js +39 -0
  42. package/dist/esm/interceptors/cache/embedding.d.ts +14 -0
  43. package/dist/esm/interceptors/cache/embedding.js +49 -0
  44. package/dist/esm/interceptors/cache/index.d.ts +7 -4
  45. package/dist/esm/interceptors/cache/index.js +4 -4
  46. package/dist/esm/interceptors/cache/interceptor.d.ts +19 -0
  47. package/dist/esm/interceptors/cache/interceptor.js +77 -0
  48. package/dist/esm/interceptors/cache/vector.d.ts +9 -0
  49. package/dist/esm/interceptors/cache/vector.js +32 -0
  50. package/dist/esm/interceptors/governance/budget.d.ts +11 -0
  51. package/dist/esm/interceptors/governance/budget.js +42 -0
  52. package/dist/esm/interceptors/governance/index.d.ts +7 -0
  53. package/dist/esm/interceptors/governance/index.js +4 -0
  54. package/dist/esm/interceptors/governance/model-policy.d.ts +8 -0
  55. package/dist/esm/interceptors/governance/model-policy.js +15 -0
  56. package/dist/esm/interceptors/governance/rate-limit.d.ts +9 -0
  57. package/dist/esm/interceptors/governance/rate-limit.js +43 -0
  58. package/dist/esm/interceptors/guardrails/index.d.ts +6 -0
  59. package/dist/esm/interceptors/guardrails/index.js +4 -0
  60. package/dist/esm/interceptors/guardrails/interceptor.d.ts +15 -0
  61. package/dist/esm/interceptors/guardrails/interceptor.js +37 -0
  62. package/dist/esm/interceptors/guardrails/validator.d.ts +11 -0
  63. package/dist/esm/interceptors/guardrails/validator.js +3 -0
  64. package/dist/esm/interceptors/guardrails/validators/regex.d.ts +6 -0
  65. package/dist/esm/interceptors/guardrails/validators/regex.js +28 -0
  66. package/dist/esm/interceptors/guardrails/validators/schema.d.ts +5 -0
  67. package/dist/esm/interceptors/guardrails/validators/schema.js +60 -0
  68. package/dist/esm/interceptors/injection.d.ts +17 -0
  69. package/dist/esm/interceptors/injection.js +59 -0
  70. package/dist/esm/interceptors/reliability/circuit-breaker.d.ts +15 -0
  71. package/dist/esm/interceptors/reliability/circuit-breaker.js +78 -0
  72. package/dist/esm/interceptors/reliability/index.d.ts +4 -0
  73. package/dist/esm/interceptors/reliability/index.js +2 -0
  74. package/dist/esm/interceptors/reliability/load-balancer.d.ts +8 -0
  75. package/dist/esm/interceptors/reliability/load-balancer.js +35 -0
  76. package/dist/esm/pricing.js +5 -1
  77. package/dist/esm/providers/azure-openai.d.ts +28 -0
  78. package/dist/esm/providers/azure-openai.js +53 -0
  79. package/dist/esm/providers/gemini.d.ts +36 -0
  80. package/dist/esm/providers/gemini.js +69 -0
  81. package/dist/esm/providers/index.d.ts +7 -1
  82. package/dist/esm/providers/index.js +18 -5
  83. package/dist/esm/providers/ollama.d.ts +21 -0
  84. package/dist/esm/providers/ollama.js +38 -0
  85. package/dist/esm/shim/openai.d.ts +56 -0
  86. package/dist/esm/shim/openai.js +53 -0
  87. package/package.json +31 -2
  88. package/src/config.ts +125 -0
  89. package/src/errors.ts +28 -0
  90. package/src/gateway.ts +10 -0
  91. package/src/interceptors/audit/index.ts +2 -0
  92. package/src/interceptors/audit/interceptor.ts +9 -0
  93. package/src/interceptors/audit/trace.ts +47 -0
  94. package/src/interceptors/cache/embedding.ts +53 -0
  95. package/src/interceptors/cache/index.ts +7 -4
  96. package/src/interceptors/cache/interceptor.ts +111 -0
  97. package/src/interceptors/cache/vector.ts +45 -0
  98. package/src/interceptors/governance/budget.ts +59 -0
  99. package/src/interceptors/governance/index.ts +8 -0
  100. package/src/interceptors/governance/model-policy.ts +25 -0
  101. package/src/interceptors/governance/rate-limit.ts +63 -0
  102. package/src/interceptors/guardrails/index.ts +7 -0
  103. package/src/interceptors/guardrails/interceptor.ts +56 -0
  104. package/src/interceptors/guardrails/validator.ts +14 -0
  105. package/src/interceptors/guardrails/validators/regex.ts +29 -0
  106. package/src/interceptors/guardrails/validators/schema.ts +62 -0
  107. package/src/interceptors/injection.ts +72 -0
  108. package/src/interceptors/reliability/circuit-breaker.ts +102 -0
  109. package/src/interceptors/reliability/index.ts +4 -0
  110. package/src/interceptors/reliability/load-balancer.ts +56 -0
  111. package/src/pricing.ts +5 -1
  112. package/src/providers/azure-openai.ts +77 -0
  113. package/src/providers/gemini.ts +95 -0
  114. package/src/providers/index.ts +21 -5
  115. package/src/providers/ollama.ts +61 -0
  116. package/src/shim/openai.ts +76 -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");
@@ -40,6 +73,15 @@ class Gateway {
40
73
  this.dryRunMode = options.dryRun ?? false;
41
74
  this.pricing = options.pricing ?? new pricing_js_1.PricingProvider();
42
75
  }
76
+ /**
77
+ * Build a Gateway from a config object or a JSON file path (F-DX-05).
78
+ * Async so the config module loads lazily (avoids a circular import).
79
+ */
80
+ static async fromConfig(config) {
81
+ const mod = await Promise.resolve().then(() => __importStar(require('./config.js')));
82
+ const data = typeof config === 'string' ? mod.loadConfig(config) : config;
83
+ return mod.buildFromConfig(data);
84
+ }
43
85
  /** Register an interceptor or executor policy. First-registered = outermost. */
44
86
  use(interceptor) {
45
87
  this.interceptors.push(interceptor);
@@ -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; } });
@@ -20,8 +20,11 @@ class AuditInterceptor {
20
20
  name = exports.AUDIT_NAME;
21
21
  dryRunSafe = true; // auditing is observation-only, so it always runs
22
22
  sink;
23
+ hashChain;
24
+ lastHash = '';
23
25
  constructor(options = {}) {
24
26
  this.sink = resolveSink(options.sink);
27
+ this.hashChain = options.hashChain ?? false;
25
28
  }
26
29
  async before(request, ctx) {
27
30
  ctx.state[PROMPT_HASH_KEY] = record_js_1.AuditRecord.hashText(request.promptText());
@@ -50,6 +53,10 @@ class AuditInterceptor {
50
53
  guardrailOutcome: ctx.guardrailOutcome,
51
54
  riskScore: ctx.riskScore,
52
55
  });
56
+ if (this.hashChain) {
57
+ record.previousHash = this.lastHash;
58
+ this.lastHash = record.contentHash();
59
+ }
53
60
  response.audit = record;
54
61
  try {
55
62
  await this.sink.write(record);
@@ -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
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /** costControl (F-GOV-02) — soft/hard budget caps per scope and window. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.costControl = costControl;
5
+ const errors_js_1 = require("../../errors.js");
6
+ function scopeKey(scope, ctx) {
7
+ if (scope === 'agent')
8
+ return `agent:${ctx.agentId ?? 'unknown'}`;
9
+ if (scope === 'session')
10
+ return `session:${ctx.sessionId ?? 'unknown'}`;
11
+ return 'global';
12
+ }
13
+ function windowBucket(window) {
14
+ const now = new Date().toISOString();
15
+ if (window === 'day')
16
+ return now.slice(0, 10);
17
+ if (window === 'month')
18
+ return now.slice(0, 7);
19
+ return 'total';
20
+ }
21
+ function costControl(options) {
22
+ const { hardCapUsd, softCapUsd, scope = 'global', window = 'day' } = options;
23
+ const spend = new Map();
24
+ const key = (ctx) => `${scopeKey(scope, ctx)}|${windowBucket(window)}`;
25
+ return {
26
+ name: 'cost_control',
27
+ before(request, ctx) {
28
+ const spent = spend.get(key(ctx)) ?? 0;
29
+ if (spent >= hardCapUsd) {
30
+ throw new errors_js_1.BudgetExceededError(`budget hard cap $${hardCapUsd.toFixed(2)} reached (spent $${spent.toFixed(4)})`);
31
+ }
32
+ return request;
33
+ },
34
+ after(response, ctx) {
35
+ const k = key(ctx);
36
+ const total = (spend.get(k) ?? 0) + response.costUsd;
37
+ spend.set(k, total);
38
+ if (softCapUsd !== undefined && total >= softCapUsd) {
39
+ // eslint-disable-next-line no-console
40
+ console.warn(`[gavio:budget] soft cap: $${total.toFixed(4)} of $${softCapUsd} for ${k}`);
41
+ }
42
+ return response;
43
+ },
44
+ };
45
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /** Cost & governance (F-GOV-02 budget, F-GOV-03 rate limit, F-GOV-04 RBAC). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.modelPolicy = exports.rateLimiter = exports.costControl = void 0;
5
+ var budget_js_1 = require("./budget.js");
6
+ Object.defineProperty(exports, "costControl", { enumerable: true, get: function () { return budget_js_1.costControl; } });
7
+ var rate_limit_js_1 = require("./rate-limit.js");
8
+ Object.defineProperty(exports, "rateLimiter", { enumerable: true, get: function () { return rate_limit_js_1.rateLimiter; } });
9
+ var model_policy_js_1 = require("./model-policy.js");
10
+ Object.defineProperty(exports, "modelPolicy", { enumerable: true, get: function () { return model_policy_js_1.modelPolicy; } });
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /** modelPolicy (F-GOV-04) — per-role model allowlists (RBAC). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.modelPolicy = modelPolicy;
5
+ const errors_js_1 = require("../../errors.js");
6
+ function modelPolicy(options) {
7
+ const { roles, defaultRole = 'default', roleKey = 'role' } = options;
8
+ return {
9
+ name: 'model_policy',
10
+ before(request, _ctx) {
11
+ const role = String(request.metadata?.[roleKey] ?? defaultRole);
12
+ const allowed = roles[role] ?? [];
13
+ if (allowed.includes('*') || allowed.includes(request.model))
14
+ return request;
15
+ throw new errors_js_1.ModelNotAllowedError(role, request.model);
16
+ },
17
+ };
18
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ /** rateLimiter (F-GOV-03) — fixed-window requests/tokens per minute per scope. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.rateLimiter = rateLimiter;
5
+ const errors_js_1 = require("../../errors.js");
6
+ function scopeKey(scope, ctx) {
7
+ if (scope === 'agent')
8
+ return `agent:${ctx.agentId ?? 'unknown'}`;
9
+ if (scope === 'session')
10
+ return `session:${ctx.sessionId ?? 'unknown'}`;
11
+ return 'global';
12
+ }
13
+ function rateLimiter(options = {}) {
14
+ const { maxRequestsPerMinute, maxTokensPerMinute, scope = 'global' } = options;
15
+ const windows = new Map();
16
+ function windowFor(ctx) {
17
+ const minute = Math.floor(Date.now() / 60000);
18
+ const key = scopeKey(scope, ctx);
19
+ let w = windows.get(key);
20
+ if (!w || w.minute !== minute) {
21
+ w = { minute, requests: 0, tokens: 0 };
22
+ windows.set(key, w);
23
+ }
24
+ return w;
25
+ }
26
+ return {
27
+ name: 'rate_limiter',
28
+ before(request, ctx) {
29
+ const w = windowFor(ctx);
30
+ if (maxRequestsPerMinute !== undefined && w.requests >= maxRequestsPerMinute) {
31
+ throw new errors_js_1.RateLimitExceededError(`rate limit: ${maxRequestsPerMinute} requests/min exceeded`);
32
+ }
33
+ if (maxTokensPerMinute !== undefined && w.tokens >= maxTokensPerMinute) {
34
+ throw new errors_js_1.RateLimitExceededError(`rate limit: ${maxTokensPerMinute} tokens/min exceeded`);
35
+ }
36
+ w.requests += 1;
37
+ return request;
38
+ },
39
+ after(response, ctx) {
40
+ if (maxTokensPerMinute !== undefined) {
41
+ windowFor(ctx).tokens += response.usage.totalTokens;
42
+ }
43
+ return response;
44
+ },
45
+ };
46
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /** Guardrails & output validation (F-QUA-01 schema, F-QUA-02 regex). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.regexAllowlist = exports.regexDenylist = exports.jsonSchemaValidator = exports.guardrails = void 0;
5
+ var interceptor_js_1 = require("./interceptor.js");
6
+ Object.defineProperty(exports, "guardrails", { enumerable: true, get: function () { return interceptor_js_1.guardrails; } });
7
+ var schema_js_1 = require("./validators/schema.js");
8
+ Object.defineProperty(exports, "jsonSchemaValidator", { enumerable: true, get: function () { return schema_js_1.jsonSchemaValidator; } });
9
+ var regex_js_1 = require("./validators/regex.js");
10
+ Object.defineProperty(exports, "regexDenylist", { enumerable: true, get: function () { return regex_js_1.regexDenylist; } });
11
+ Object.defineProperty(exports, "regexAllowlist", { enumerable: true, get: function () { return regex_js_1.regexAllowlist; } });
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /**
3
+ * guardrails (F-QUA-01, F-QUA-02) — validate responses, act on failure.
4
+ *
5
+ * An ExecutorPolicy so it can re-run the provider on failure. Records the
6
+ * outcome in ctx.guardrailOutcome for the audit trail.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.guardrails = guardrails;
10
+ const errors_js_1 = require("../../errors.js");
11
+ function guardrails(options) {
12
+ const { validators, onFailure = 'error', maxRetries = 2 } = options;
13
+ return {
14
+ name: 'guardrails',
15
+ isExecutorPolicy: true,
16
+ async around(request, ctx, callNext) {
17
+ ctx.markFired('guardrails');
18
+ const attempts = onFailure === 'retry' ? maxRetries + 1 : 1;
19
+ let response;
20
+ let failures = [];
21
+ for (let attempt = 0; attempt < attempts; attempt++) {
22
+ response = await callNext(request);
23
+ failures = [];
24
+ for (const v of validators) {
25
+ const result = v.validate(response.content);
26
+ if (!result.ok)
27
+ failures.push(`${v.name}: ${result.reason ?? ''}`);
28
+ }
29
+ if (failures.length === 0) {
30
+ ctx.guardrailOutcome = 'PASS';
31
+ return response;
32
+ }
33
+ }
34
+ ctx.guardrailOutcome = 'FAIL';
35
+ if (onFailure === 'warn')
36
+ return response;
37
+ throw new errors_js_1.GuardrailViolationError(failures.join('; '));
38
+ },
39
+ };
40
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /** OutputValidator interface for guardrails (F-QUA-01, F-QUA-02). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.failed = exports.passed = void 0;
5
+ const passed = () => ({ ok: true });
6
+ exports.passed = passed;
7
+ const failed = (reason) => ({ ok: false, reason });
8
+ exports.failed = failed;