gavio 0.2.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 (56) hide show
  1. package/dist/cjs/gateway.js +46 -0
  2. package/dist/cjs/index.js +4 -2
  3. package/dist/cjs/interceptors/audit/interceptor.js +4 -0
  4. package/dist/cjs/interceptors/audit/record.js +17 -3
  5. package/dist/cjs/interceptors/metrics/index.js +9 -0
  6. package/dist/cjs/interceptors/metrics/interceptor.js +37 -0
  7. package/dist/cjs/interceptors/metrics/registry.js +0 -0
  8. package/dist/cjs/interceptors/quality/index.js +7 -0
  9. package/dist/cjs/interceptors/quality/risk.js +49 -0
  10. package/dist/cjs/interceptors/reliability/index.js +3 -1
  11. package/dist/cjs/interceptors/reliability/stream-buffer.js +28 -0
  12. package/dist/cjs/providers/base.js +9 -0
  13. package/dist/cjs/request.js +3 -0
  14. package/dist/cjs/types.js +53 -1
  15. package/dist/esm/gateway.d.ts +13 -1
  16. package/dist/esm/gateway.js +46 -0
  17. package/dist/esm/index.d.ts +3 -3
  18. package/dist/esm/index.js +2 -2
  19. package/dist/esm/interceptors/audit/interceptor.js +4 -0
  20. package/dist/esm/interceptors/audit/record.d.ts +4 -2
  21. package/dist/esm/interceptors/audit/record.js +18 -4
  22. package/dist/esm/interceptors/metrics/index.d.ts +5 -0
  23. package/dist/esm/interceptors/metrics/index.js +3 -0
  24. package/dist/esm/interceptors/metrics/interceptor.d.ts +22 -0
  25. package/dist/esm/interceptors/metrics/interceptor.js +33 -0
  26. package/dist/esm/interceptors/metrics/registry.d.ts +31 -0
  27. package/dist/esm/interceptors/metrics/registry.js +0 -0
  28. package/dist/esm/interceptors/quality/index.d.ts +3 -0
  29. package/dist/esm/interceptors/quality/index.js +2 -0
  30. package/dist/esm/interceptors/quality/risk.d.ts +32 -0
  31. package/dist/esm/interceptors/quality/risk.js +44 -0
  32. package/dist/esm/interceptors/reliability/index.d.ts +1 -0
  33. package/dist/esm/interceptors/reliability/index.js +1 -0
  34. package/dist/esm/interceptors/reliability/stream-buffer.d.ts +18 -0
  35. package/dist/esm/interceptors/reliability/stream-buffer.js +24 -0
  36. package/dist/esm/providers/base.d.ts +7 -0
  37. package/dist/esm/providers/base.js +9 -1
  38. package/dist/esm/request.d.ts +4 -1
  39. package/dist/esm/request.js +4 -1
  40. package/dist/esm/types.d.ts +54 -0
  41. package/dist/esm/types.js +50 -0
  42. package/package.json +11 -1
  43. package/src/gateway.ts +52 -1
  44. package/src/index.ts +4 -2
  45. package/src/interceptors/audit/interceptor.ts +4 -0
  46. package/src/interceptors/audit/record.ts +18 -4
  47. package/src/interceptors/metrics/index.ts +6 -0
  48. package/src/interceptors/metrics/interceptor.ts +46 -0
  49. package/src/interceptors/metrics/registry.ts +0 -0
  50. package/src/interceptors/quality/index.ts +4 -0
  51. package/src/interceptors/quality/risk.ts +64 -0
  52. package/src/interceptors/reliability/index.ts +1 -0
  53. package/src/interceptors/reliability/stream-buffer.ts +27 -0
  54. package/src/providers/base.ts +21 -1
  55. package/src/request.ts +6 -2
  56. package/src/types.ts +77 -0
@@ -40,6 +40,7 @@ const errors_js_1 = require("./errors.js");
40
40
  const index_js_1 = require("./interceptors/audit/index.js");
41
41
  const base_js_1 = require("./interceptors/base.js");
42
42
  const chain_js_1 = require("./interceptors/chain.js");
43
+ const stream_buffer_js_1 = require("./interceptors/reliability/stream-buffer.js");
43
44
  const pricing_js_1 = require("./pricing.js");
44
45
  const index_js_2 = require("./providers/index.js");
45
46
  const mock_js_1 = require("./providers/mock.js");
@@ -110,6 +111,7 @@ class Gateway {
110
111
  sessionId: opts.sessionId ?? null,
111
112
  options: opts.options ?? {},
112
113
  metadata: opts.metadata ?? {},
114
+ lineage: opts.lineage ?? null,
113
115
  });
114
116
  const ctx = new context_js_1.InterceptorContext({
115
117
  traceId: request.traceId,
@@ -121,6 +123,50 @@ class Gateway {
121
123
  const { chain, executor } = this.buildPipeline(adapter, ctx);
122
124
  return chain.execute(request, ctx, executor);
123
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
+ }
124
170
  async healthCheck() {
125
171
  return this.resolveAdapter().healthCheck();
126
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; } });
@@ -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.
@@ -28,6 +29,8 @@ class AuditInterceptor {
28
29
  }
29
30
  async before(request, ctx) {
30
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;
31
34
  return request;
32
35
  }
33
36
  async after(response, ctx) {
@@ -52,6 +55,7 @@ class AuditInterceptor {
52
55
  cacheType: response.cacheType,
53
56
  guardrailOutcome: ctx.guardrailOutcome,
54
57
  riskScore: ctx.riskScore,
58
+ lineage: ctx.state[LINEAGE_KEY] ?? null,
55
59
  });
56
60
  if (this.hashChain) {
57
61
  record.previousHash = this.lastHash;
@@ -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,9 @@
1
+ "use strict";
2
+ /** Prometheus metrics (F-OBS-08). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.METRICS_NAME = exports.metricsInterceptor = exports.PrometheusMetrics = void 0;
5
+ var registry_js_1 = require("./registry.js");
6
+ Object.defineProperty(exports, "PrometheusMetrics", { enumerable: true, get: function () { return registry_js_1.PrometheusMetrics; } });
7
+ var interceptor_js_1 = require("./interceptor.js");
8
+ Object.defineProperty(exports, "metricsInterceptor", { enumerable: true, get: function () { return interceptor_js_1.metricsInterceptor; } });
9
+ Object.defineProperty(exports, "METRICS_NAME", { enumerable: true, get: function () { return interceptor_js_1.METRICS_NAME; } });
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /** metricsInterceptor (F-OBS-08) — records Prometheus metrics per request. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.METRICS_NAME = void 0;
5
+ exports.metricsInterceptor = metricsInterceptor;
6
+ const registry_js_1 = require("./registry.js");
7
+ exports.METRICS_NAME = 'metrics';
8
+ /**
9
+ * Build a metrics interceptor. Pass a shared {@link PrometheusMetrics} registry
10
+ * (or let it create one) and scrape it via `.metrics.render()`:
11
+ *
12
+ * ```ts
13
+ * const m = metricsInterceptor()
14
+ * const gw = new Gateway({ devMode: true }).use(m)
15
+ * // ...
16
+ * console.log(m.metrics.render())
17
+ * ```
18
+ *
19
+ * Observation-only, so it always runs (including in dry-run).
20
+ */
21
+ function metricsInterceptor(metrics = new registry_js_1.PrometheusMetrics()) {
22
+ return {
23
+ name: exports.METRICS_NAME,
24
+ dryRunSafe: true,
25
+ metrics,
26
+ async after(response, _ctx) {
27
+ metrics.record(response.provider, response.model, {
28
+ promptTokens: response.usage.promptTokens,
29
+ completionTokens: response.usage.completionTokens,
30
+ costUsd: response.costUsd,
31
+ latencyMs: response.latencyMs,
32
+ cacheHit: response.cacheHit,
33
+ });
34
+ return response;
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /** Quality & compliance interceptors (F-QUA-06 risk scoring; F-QUA-03/04 to come). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.riskScorer = exports.RiskScorer = void 0;
5
+ var risk_js_1 = require("./risk.js");
6
+ Object.defineProperty(exports, "RiskScorer", { enumerable: true, get: function () { return risk_js_1.RiskScorer; } });
7
+ Object.defineProperty(exports, "riskScorer", { enumerable: true, get: function () { return risk_js_1.riskScorer; } });
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ /**
3
+ * RiskScorer (F-QUA-06) — a composite risk score from per-request signals.
4
+ *
5
+ * Folds the signals other interceptors leave on the {@link InterceptorContext}
6
+ * — PII entities found, guardrail outcome, and the prompt-injection risk — into
7
+ * a single score in `[0, 1]` written to `ctx.riskScore` (and thus the audit
8
+ * record). Register it *inside* the audit interceptor so audit sees the composite.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.RiskScorer = void 0;
12
+ exports.riskScorer = riskScorer;
13
+ // Guardrail outcome → its contribution before weighting.
14
+ const GUARDRAIL_SIGNAL = { FAIL: 1.0, HITL: 0.6 };
15
+ class RiskScorer {
16
+ name = 'risk_scorer';
17
+ dryRunSafe = true;
18
+ pii;
19
+ guardrail;
20
+ injection;
21
+ piiSaturation;
22
+ constructor(weights = {}) {
23
+ this.pii = weights.pii ?? 0.3;
24
+ this.guardrail = weights.guardrail ?? 0.4;
25
+ this.injection = weights.injection ?? 0.3;
26
+ this.piiSaturation = weights.piiSaturation ?? 4;
27
+ }
28
+ /** Compute the composite risk score from the three raw signals. */
29
+ score(piiCount, guardrailOutcome, injectionScore) {
30
+ let piiSignal = 0;
31
+ if (piiCount > 0) {
32
+ piiSignal = this.piiSaturation <= 0 ? 1 : Math.min(1, piiCount / this.piiSaturation);
33
+ }
34
+ const guardrailSignal = GUARDRAIL_SIGNAL[guardrailOutcome ?? ''] ?? 0;
35
+ const injectionSignal = injectionScore ?? 0;
36
+ const composite = this.pii * piiSignal + this.guardrail * guardrailSignal + this.injection * injectionSignal;
37
+ return Math.max(0, Math.min(1, composite));
38
+ }
39
+ async after(response, ctx) {
40
+ const piiCount = Object.values(ctx.piiEntityCounts).reduce((a, b) => a + b, 0);
41
+ ctx.riskScore = this.score(piiCount, ctx.guardrailOutcome, ctx.riskScore);
42
+ return response;
43
+ }
44
+ }
45
+ exports.RiskScorer = RiskScorer;
46
+ /** Build a risk scorer. */
47
+ function riskScorer(weights = {}) {
48
+ return new RiskScorer(weights);
49
+ }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /** Reliability policies (F-REL-01, F-REL-02, F-REL-07). */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.loadBalancer = exports.CircuitState = exports.circuitBreaker = exports.fallbackChain = exports.timeout = exports.timeoutPolicy = exports.retryInterceptor = void 0;
4
+ exports.StreamBuffer = exports.loadBalancer = exports.CircuitState = exports.circuitBreaker = exports.fallbackChain = exports.timeout = exports.timeoutPolicy = exports.retryInterceptor = void 0;
5
5
  var retry_js_1 = require("./retry.js");
6
6
  Object.defineProperty(exports, "retryInterceptor", { enumerable: true, get: function () { return retry_js_1.retryInterceptor; } });
7
7
  var timeout_js_1 = require("./timeout.js");
@@ -14,3 +14,5 @@ Object.defineProperty(exports, "circuitBreaker", { enumerable: true, get: functi
14
14
  Object.defineProperty(exports, "CircuitState", { enumerable: true, get: function () { return circuit_breaker_js_1.CircuitState; } });
15
15
  var load_balancer_js_1 = require("./load-balancer.js");
16
16
  Object.defineProperty(exports, "loadBalancer", { enumerable: true, get: function () { return load_balancer_js_1.loadBalancer; } });
17
+ var stream_buffer_js_1 = require("./stream-buffer.js");
18
+ Object.defineProperty(exports, "StreamBuffer", { enumerable: true, get: function () { return stream_buffer_js_1.StreamBuffer; } });
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StreamBuffer = void 0;
4
+ /**
5
+ * StreamBuffer (F-REL-06) — accumulate a provider stream for post-interceptors.
6
+ *
7
+ * Post-interceptors (guardrails, PII restore, audit) need the *complete*
8
+ * response, so a streamed reply is buffered in full before the post pipeline
9
+ * runs and before any chunk reaches the caller. This trades first-token latency
10
+ * for the guarantee that every interceptor sees — and can rewrite or block — the
11
+ * whole response.
12
+ */
13
+ class StreamBuffer {
14
+ parts = [];
15
+ /** Add one streamed chunk. */
16
+ append(chunk) {
17
+ this.parts.push(chunk);
18
+ }
19
+ /** The full buffered response so far. */
20
+ text() {
21
+ return this.parts.join('');
22
+ }
23
+ /** Total buffered length in characters. */
24
+ get length() {
25
+ return this.parts.reduce((n, p) => n + p.length, 0);
26
+ }
27
+ }
28
+ exports.StreamBuffer = StreamBuffer;
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.BaseProviderAdapter = void 0;
5
5
  const pricing_js_1 = require("../pricing.js");
6
6
  const response_js_1 = require("../response.js");
7
+ const types_js_1 = require("../types.js");
7
8
  /** Base class with a shared pricing provider and response builder. */
8
9
  class BaseProviderAdapter {
9
10
  pricing;
@@ -13,6 +14,14 @@ class BaseProviderAdapter {
13
14
  get reportedModelVersion() {
14
15
  return null;
15
16
  }
17
+ /**
18
+ * Build a response from a fully buffered stream (F-REL-06). Streamed chunks
19
+ * carry text only, so token usage is estimated from prompt + content.
20
+ */
21
+ buildStreamResponse(request, content, startedAt) {
22
+ const usage = new types_js_1.TokenUsage((0, pricing_js_1.estimateTokens)(request.promptText()), (0, pricing_js_1.estimateTokens)(content));
23
+ return this.buildResponse(request, content, usage, this.reportedModelVersion ?? request.model, startedAt);
24
+ }
16
25
  buildResponse(request, content, usage, modelVersion, startedAt) {
17
26
  const latencyMs = Math.floor(performance.now() - startedAt);
18
27
  return new response_js_1.GavioResponse({
@@ -19,6 +19,7 @@ class GavioRequest {
19
19
  sessionId;
20
20
  options;
21
21
  metadata;
22
+ lineage;
22
23
  constructor(init) {
23
24
  this.messages = init.messages;
24
25
  this.model = init.model;
@@ -29,6 +30,7 @@ class GavioRequest {
29
30
  this.sessionId = init.sessionId ?? null;
30
31
  this.options = init.options ?? {};
31
32
  this.metadata = init.metadata ?? {};
33
+ this.lineage = init.lineage != null ? types_js_1.PromptLineage.from(init.lineage) : null;
32
34
  }
33
35
  get temperature() {
34
36
  const t = this.options['temperature'];
@@ -54,6 +56,7 @@ class GavioRequest {
54
56
  sessionId: this.sessionId,
55
57
  options: { ...this.options },
56
58
  metadata: { ...this.metadata },
59
+ lineage: this.lineage,
57
60
  });
58
61
  }
59
62
  }
package/dist/cjs/types.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /** Shared enums (as string unions / const objects) and utility types. */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.TokenUsage = exports.GuardrailOutcome = exports.Sensitivity = exports.PiiMode = exports.CacheType = exports.Provider = void 0;
4
+ exports.PromptLineage = exports.RagChunk = exports.TokenUsage = exports.GuardrailOutcome = exports.Sensitivity = exports.PiiMode = exports.CacheType = exports.Provider = void 0;
5
5
  exports.coerceProvider = coerceProvider;
6
6
  /** Supported LLM providers. String-valued for easy config + logging. */
7
7
  exports.Provider = {
@@ -59,3 +59,55 @@ class TokenUsage {
59
59
  }
60
60
  }
61
61
  exports.TokenUsage = TokenUsage;
62
+ /**
63
+ * A single retrieved source that contributed to a prompt. Carries a *reference*
64
+ * to the source — never the retrieved text — so prompt lineage stays within the
65
+ * audit record's metadata-only contract.
66
+ */
67
+ class RagChunk {
68
+ source;
69
+ chunkId;
70
+ score;
71
+ constructor(init) {
72
+ this.source = init.source;
73
+ this.chunkId = init.chunkId ?? null;
74
+ this.score = init.score ?? null;
75
+ }
76
+ toJSON() {
77
+ return { source: this.source, chunkId: this.chunkId, score: this.score };
78
+ }
79
+ }
80
+ exports.RagChunk = RagChunk;
81
+ /**
82
+ * Provenance for a rendered prompt (F-OBS-04): the template, the variable
83
+ * bindings interpolated into it, and the RAG chunk sources retrieved for it.
84
+ *
85
+ * Attached to a GavioRequest by the caller and copied into the AuditRecord so
86
+ * any prompt can be reconstructed and debugged. RAG chunk text is never stored
87
+ * — only source references (see {@link RagChunk}).
88
+ */
89
+ class PromptLineage {
90
+ templateId;
91
+ templateVersion;
92
+ variables;
93
+ ragChunks;
94
+ constructor(init = {}) {
95
+ this.templateId = init.templateId ?? null;
96
+ this.templateVersion = init.templateVersion ?? null;
97
+ this.variables = init.variables ?? {};
98
+ this.ragChunks = (init.ragChunks ?? []).map((c) => c instanceof RagChunk ? c : new RagChunk(c));
99
+ }
100
+ /** Coerce a PromptLineage instance or plain init object into a PromptLineage. */
101
+ static from(value) {
102
+ return value instanceof PromptLineage ? value : new PromptLineage(value);
103
+ }
104
+ toJSON() {
105
+ return {
106
+ templateId: this.templateId,
107
+ templateVersion: this.templateVersion,
108
+ variables: this.variables,
109
+ ragChunks: this.ragChunks.map((c) => c.toJSON()),
110
+ };
111
+ }
112
+ }
113
+ exports.PromptLineage = PromptLineage;
@@ -4,7 +4,7 @@ import { PricingProvider } from './pricing.js';
4
4
  import type { ProviderAdapter } from './providers/base.js';
5
5
  import type { GavioResponse } from './response.js';
6
6
  import { Provider } from './types.js';
7
- import type { Message } from './types.js';
7
+ import type { Message, PromptLineage, PromptLineageInit } from './types.js';
8
8
  export interface GatewayOptions {
9
9
  provider?: Provider | string;
10
10
  model?: string;
@@ -22,6 +22,8 @@ export interface CompleteOptions {
22
22
  metadata?: Record<string, unknown>;
23
23
  /** Provider sampling options (temperature, maxTokens, etc.). */
24
24
  options?: Record<string, unknown>;
25
+ /** Prompt provenance (F-OBS-04): template, variables, and RAG chunk sources. */
26
+ lineage?: PromptLineage | PromptLineageInit | null;
25
27
  }
26
28
  /**
27
29
  * Routes a request through the interceptor pipeline to a provider.
@@ -51,6 +53,16 @@ export declare class Gateway {
51
53
  get model(): string;
52
54
  get providerName(): string;
53
55
  complete(opts: CompleteOptions): Promise<GavioResponse>;
56
+ /**
57
+ * Stream a completion, buffering the provider stream (F-REL-06).
58
+ *
59
+ * The provider stream is buffered in full so the post-interceptor pipeline
60
+ * (guardrails, PII restore, audit) runs on the complete response before any
61
+ * chunk reaches the caller. Pre/post interceptors run via the chain; executor
62
+ * policies (retry, circuit breaker, cache) are not applied to the streaming
63
+ * path.
64
+ */
65
+ stream(opts: CompleteOptions): AsyncGenerator<string>;
54
66
  healthCheck(): Promise<boolean>;
55
67
  private buildPipeline;
56
68
  private wrapPolicy;
@@ -4,6 +4,7 @@ import { ConfigurationError } from './errors.js';
4
4
  import { auditInterceptor, isAuditInterceptor } from './interceptors/audit/index.js';
5
5
  import { isExecutorPolicy } from './interceptors/base.js';
6
6
  import { InterceptorChain } from './interceptors/chain.js';
7
+ import { StreamBuffer } from './interceptors/reliability/stream-buffer.js';
7
8
  import { PricingProvider } from './pricing.js';
8
9
  import { buildAdapter } from './providers/index.js';
9
10
  import { mockProvider } from './providers/mock.js';
@@ -74,6 +75,7 @@ export class Gateway {
74
75
  sessionId: opts.sessionId ?? null,
75
76
  options: opts.options ?? {},
76
77
  metadata: opts.metadata ?? {},
78
+ lineage: opts.lineage ?? null,
77
79
  });
78
80
  const ctx = new InterceptorContext({
79
81
  traceId: request.traceId,
@@ -85,6 +87,50 @@ export class Gateway {
85
87
  const { chain, executor } = this.buildPipeline(adapter, ctx);
86
88
  return chain.execute(request, ctx, executor);
87
89
  }
90
+ /**
91
+ * Stream a completion, buffering the provider stream (F-REL-06).
92
+ *
93
+ * The provider stream is buffered in full so the post-interceptor pipeline
94
+ * (guardrails, PII restore, audit) runs on the complete response before any
95
+ * chunk reaches the caller. Pre/post interceptors run via the chain; executor
96
+ * policies (retry, circuit breaker, cache) are not applied to the streaming
97
+ * path.
98
+ */
99
+ async *stream(opts) {
100
+ const adapter = this.resolveAdapter();
101
+ if (adapter.stream === undefined || adapter.buildStreamResponse === undefined) {
102
+ throw new ConfigurationError(`${adapter.providerName} does not support streaming`);
103
+ }
104
+ const model = opts.model ?? this.modelHint ?? this.resolveModel(adapter);
105
+ const request = new GavioRequest({
106
+ messages: opts.messages,
107
+ model,
108
+ provider: coerceProvider(adapter.providerName),
109
+ agentId: opts.agentId ?? null,
110
+ parentTraceId: opts.parentTraceId ?? null,
111
+ sessionId: opts.sessionId ?? null,
112
+ options: opts.options ?? {},
113
+ metadata: opts.metadata ?? {},
114
+ });
115
+ const ctx = new InterceptorContext({
116
+ traceId: request.traceId,
117
+ agentId: request.agentId,
118
+ parentTraceId: request.parentTraceId,
119
+ sessionId: request.sessionId,
120
+ dryRun: this.dryRunMode,
121
+ });
122
+ const startedAt = performance.now();
123
+ const buffer = new StreamBuffer();
124
+ const { chain } = this.buildPipeline(adapter, ctx);
125
+ const bufferingExecutor = async (req) => {
126
+ for await (const chunk of adapter.stream(req))
127
+ buffer.append(chunk);
128
+ return adapter.buildStreamResponse(req, buffer.text(), startedAt);
129
+ };
130
+ const response = await chain.execute(request, ctx, bufferingExecutor);
131
+ // Post-interceptors have run on the fully buffered response; emit it now.
132
+ yield response.content;
133
+ }
88
134
  async healthCheck() {
89
135
  return this.resolveAdapter().healthCheck();
90
136
  }
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * See https://gavio.io for documentation. MIT licensed.
9
9
  */
10
- export declare const VERSION = "0.1.0";
10
+ export declare const VERSION = "0.3.0";
11
11
  export { Gateway } from './gateway.js';
12
12
  export type { GatewayOptions, CompleteOptions } from './gateway.js';
13
13
  export { GavioRequest } from './request.js';
@@ -18,8 +18,8 @@ export { InterceptorContext } from './context.js';
18
18
  export type { InterceptorContextInit } from './context.js';
19
19
  export { uuid7, newTraceId } from './ids.js';
20
20
  export { PricingProvider, estimateTokens } from './pricing.js';
21
- export { Provider, CacheType, PiiMode, Sensitivity, GuardrailOutcome, TokenUsage, coerceProvider, } from './types.js';
22
- export type { Message } from './types.js';
21
+ export { Provider, CacheType, PiiMode, Sensitivity, GuardrailOutcome, TokenUsage, PromptLineage, RagChunk, coerceProvider, } from './types.js';
22
+ export type { Message, PromptLineageInit, RagChunkInit } from './types.js';
23
23
  export type { Interceptor, Executor, ExecutorPolicy } from './interceptors/base.js';
24
24
  export { InterceptorChain } from './interceptors/chain.js';
25
25
  export { GavioError, ConfigurationError, ProviderError, ProviderUnavailableError, RateLimitError, ServerError, TimeoutError, PiiBlockedError, BudgetExceededError, GuardrailViolationError, } from './errors.js';
package/dist/esm/index.js CHANGED
@@ -7,14 +7,14 @@
7
7
  *
8
8
  * See https://gavio.io for documentation. MIT licensed.
9
9
  */
10
- export const VERSION = '0.1.0';
10
+ export const VERSION = '0.3.0';
11
11
  export { Gateway } from './gateway.js';
12
12
  export { GavioRequest } from './request.js';
13
13
  export { GavioResponse } from './response.js';
14
14
  export { InterceptorContext } from './context.js';
15
15
  export { uuid7, newTraceId } from './ids.js';
16
16
  export { PricingProvider, estimateTokens } from './pricing.js';
17
- export { Provider, CacheType, PiiMode, Sensitivity, GuardrailOutcome, TokenUsage, coerceProvider, } from './types.js';
17
+ export { Provider, CacheType, PiiMode, Sensitivity, GuardrailOutcome, TokenUsage, PromptLineage, RagChunk, coerceProvider, } from './types.js';
18
18
  export { InterceptorChain } from './interceptors/chain.js';
19
19
  // errors
20
20
  export { GavioError, ConfigurationError, ProviderError, ProviderUnavailableError, RateLimitError, ServerError, TimeoutError, PiiBlockedError, BudgetExceededError, GuardrailViolationError, } from './errors.js';
@@ -2,6 +2,7 @@
2
2
  import { AuditRecord } from './record.js';
3
3
  import { stdoutSink } from './sinks/stdout.js';
4
4
  const PROMPT_HASH_KEY = 'audit_prompt_hash';
5
+ const LINEAGE_KEY = 'audit_lineage';
5
6
  export const AUDIT_NAME = 'audit';
6
7
  /**
7
8
  * Build an AuditRecord per request and write it to a sink.
@@ -23,6 +24,8 @@ class AuditInterceptor {
23
24
  }
24
25
  async before(request, ctx) {
25
26
  ctx.state[PROMPT_HASH_KEY] = AuditRecord.hashText(request.promptText());
27
+ if (request.lineage != null)
28
+ ctx.state[LINEAGE_KEY] = request.lineage;
26
29
  return request;
27
30
  }
28
31
  async after(response, ctx) {
@@ -47,6 +50,7 @@ class AuditInterceptor {
47
50
  cacheType: response.cacheType,
48
51
  guardrailOutcome: ctx.guardrailOutcome,
49
52
  riskScore: ctx.riskScore,
53
+ lineage: ctx.state[LINEAGE_KEY] ?? null,
50
54
  });
51
55
  if (this.hashChain) {
52
56
  record.previousHash = this.lastHash;
@@ -1,5 +1,5 @@
1
1
  /** AuditRecord — the immutable, per-request audit entry. */
2
- import { TokenUsage } from '../../types.js';
2
+ import { PromptLineage, TokenUsage } from '../../types.js';
3
3
  export declare const SCHEMA_VERSION = "1.0";
4
4
  export interface AuditRecordInit {
5
5
  traceId: string;
@@ -22,6 +22,7 @@ export interface AuditRecordInit {
22
22
  cacheType?: string | null;
23
23
  guardrailOutcome?: string | null;
24
24
  riskScore?: number | null;
25
+ lineage?: PromptLineage | null;
25
26
  previousHash?: string;
26
27
  schemaVersion?: string;
27
28
  }
@@ -53,13 +54,14 @@ export declare class AuditRecord {
53
54
  cacheType: string | null;
54
55
  guardrailOutcome: string | null;
55
56
  riskScore: number | null;
57
+ lineage: PromptLineage | null;
56
58
  previousHash: string;
57
59
  schemaVersion: string;
58
60
  constructor(init: AuditRecordInit);
59
61
  static nowUtc(): string;
60
62
  static hashText(text: string): string;
61
63
  toJSON(): Record<string, unknown>;
62
- /** Stable JSON with sorted keys — used for the v0.2.0 hash chain. */
64
+ /** Stable JSON with recursively sorted keys — used for the v0.2.0 hash chain. */
63
65
  toCanonicalJson(): string;
64
66
  /** Hash of this record's content — used to build the v0.2.0 chain. */
65
67
  contentHash(): string;