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,35 @@
1
+ /** loadBalancer (F-REL-04) — weighted round-robin across provider adapters. */
2
+ import { coerceProvider } from '../../types.js';
3
+ class LoadBalancer {
4
+ name = 'load_balancer';
5
+ isExecutorPolicy = true;
6
+ pool;
7
+ index = 0;
8
+ constructor(adapters, options = {}) {
9
+ if (adapters.length === 0) {
10
+ throw new Error('loadBalancer requires at least one adapter');
11
+ }
12
+ const weights = options.weights ?? adapters.map(() => 1);
13
+ if (weights.length !== adapters.length) {
14
+ throw new Error('weights must match adapters length');
15
+ }
16
+ // Expand by weight, then cycle for round-robin.
17
+ this.pool = [];
18
+ adapters.forEach((adapter, i) => {
19
+ for (let k = 0; k < Math.max(1, weights[i]); k++)
20
+ this.pool.push(adapter);
21
+ });
22
+ }
23
+ async around(request, ctx, _callNext) {
24
+ ctx.markFired(this.name);
25
+ const adapter = this.pool[this.index % this.pool.length];
26
+ this.index += 1;
27
+ const rerouted = request.copyWithMessages(request.messages);
28
+ rerouted.provider = coerceProvider(adapter.providerName);
29
+ return adapter.complete(rerouted);
30
+ }
31
+ }
32
+ /** Factory: build a load balancer over a pool of adapters. */
33
+ export function loadBalancer(adapters, options = {}) {
34
+ return new LoadBalancer(adapters, options);
35
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * StreamBuffer (F-REL-06) — accumulate a provider stream for post-interceptors.
3
+ *
4
+ * Post-interceptors (guardrails, PII restore, audit) need the *complete*
5
+ * response, so a streamed reply is buffered in full before the post pipeline
6
+ * runs and before any chunk reaches the caller. This trades first-token latency
7
+ * for the guarantee that every interceptor sees — and can rewrite or block — the
8
+ * whole response.
9
+ */
10
+ export declare class StreamBuffer {
11
+ private readonly parts;
12
+ /** Add one streamed chunk. */
13
+ append(chunk: string): void;
14
+ /** The full buffered response so far. */
15
+ text(): string;
16
+ /** Total buffered length in characters. */
17
+ get length(): number;
18
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * StreamBuffer (F-REL-06) — accumulate a provider stream for post-interceptors.
3
+ *
4
+ * Post-interceptors (guardrails, PII restore, audit) need the *complete*
5
+ * response, so a streamed reply is buffered in full before the post pipeline
6
+ * runs and before any chunk reaches the caller. This trades first-token latency
7
+ * for the guarantee that every interceptor sees — and can rewrite or block — the
8
+ * whole response.
9
+ */
10
+ export class StreamBuffer {
11
+ parts = [];
12
+ /** Add one streamed chunk. */
13
+ append(chunk) {
14
+ this.parts.push(chunk);
15
+ }
16
+ /** The full buffered response so far. */
17
+ text() {
18
+ return this.parts.join('');
19
+ }
20
+ /** Total buffered length in characters. */
21
+ get length() {
22
+ return this.parts.reduce((n, p) => n + p.length, 0);
23
+ }
24
+ }
@@ -17,7 +17,11 @@ const DEFAULT_PRICES = {
17
17
  'claude-sonnet-4-20250514': [0.003, 0.015],
18
18
  'claude-haiku-4-5': [0.0008, 0.004],
19
19
  'claude-opus-4-1': [0.015, 0.075],
20
- // Local / mock are free.
20
+ // Gemini (approximate public pricing; override via config)
21
+ 'gemini-2.0-flash': [0.0001, 0.0004],
22
+ 'gemini-1.5-flash': [0.000075, 0.0003],
23
+ 'gemini-1.5-pro': [0.00125, 0.005],
24
+ // Local (Ollama) / mock are free.
21
25
  mock: [0.0, 0.0],
22
26
  };
23
27
  /** Estimates request cost from token usage and a model price table. */
@@ -0,0 +1,28 @@
1
+ /** azureOpenaiAdapter — Azure OpenAI deployment-based chat completions. */
2
+ import type { PricingProvider } from '../pricing.js';
3
+ import type { GavioRequest } from '../request.js';
4
+ import type { GavioResponse } from '../response.js';
5
+ import { BaseProviderAdapter } from './base.js';
6
+ export interface AzureOpenAIAdapterOptions {
7
+ apiKey?: string;
8
+ endpoint?: string;
9
+ deployment?: string;
10
+ apiVersion?: string;
11
+ timeoutMs?: number;
12
+ pricing?: PricingProvider;
13
+ }
14
+ declare class AzureOpenAIAdapter extends BaseProviderAdapter {
15
+ private readonly apiKey;
16
+ readonly endpoint: string;
17
+ private readonly deployment;
18
+ private readonly apiVersion;
19
+ private readonly timeoutSeconds;
20
+ constructor(options?: AzureOpenAIAdapterOptions);
21
+ get providerName(): string;
22
+ url(request: GavioRequest): string;
23
+ complete(request: GavioRequest): Promise<GavioResponse>;
24
+ healthCheck(): Promise<boolean>;
25
+ }
26
+ /** Factory: build an Azure OpenAI provider adapter. */
27
+ export declare function azureOpenaiAdapter(options?: AzureOpenAIAdapterOptions): AzureOpenAIAdapter;
28
+ export {};
@@ -0,0 +1,53 @@
1
+ /** azureOpenaiAdapter — Azure OpenAI deployment-based chat completions. */
2
+ import { ConfigurationError } from '../errors.js';
3
+ import { TokenUsage } from '../types.js';
4
+ import { BaseProviderAdapter } from './base.js';
5
+ import { postJson } from './http.js';
6
+ const DEFAULT_API_VERSION = '2024-06-01';
7
+ class AzureOpenAIAdapter extends BaseProviderAdapter {
8
+ apiKey;
9
+ endpoint;
10
+ deployment;
11
+ apiVersion;
12
+ timeoutSeconds;
13
+ constructor(options = {}) {
14
+ super(options.pricing);
15
+ this.apiKey = options.apiKey ?? process.env['AZURE_OPENAI_API_KEY'];
16
+ this.endpoint = (options.endpoint ?? process.env['AZURE_OPENAI_ENDPOINT'] ?? '').replace(/\/+$/, '');
17
+ this.deployment = options.deployment ?? process.env['AZURE_OPENAI_DEPLOYMENT'];
18
+ this.apiVersion = options.apiVersion ?? DEFAULT_API_VERSION;
19
+ this.timeoutSeconds = (options.timeoutMs ?? 30_000) / 1000;
20
+ }
21
+ get providerName() {
22
+ return 'azure_openai';
23
+ }
24
+ url(request) {
25
+ const deployment = this.deployment ?? request.model;
26
+ return `${this.endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${this.apiVersion}`;
27
+ }
28
+ async complete(request) {
29
+ if (!this.apiKey || !this.endpoint) {
30
+ throw new ConfigurationError('AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT must be set');
31
+ }
32
+ const started = performance.now();
33
+ const payload = {
34
+ messages: request.messages,
35
+ temperature: request.temperature,
36
+ max_tokens: request.maxTokens,
37
+ };
38
+ const data = await postJson(this.url(request), payload, { 'api-key': this.apiKey }, this.timeoutSeconds);
39
+ const choices = data['choices'] ?? [];
40
+ const message = choices[0]?.['message'] ?? {};
41
+ const content = message['content'] ?? '';
42
+ const usageData = data['usage'] ?? {};
43
+ const usage = new TokenUsage(usageData['prompt_tokens'] ?? 0, usageData['completion_tokens'] ?? 0);
44
+ return this.buildResponse(request, content, usage, data['model'] ?? request.model, started);
45
+ }
46
+ async healthCheck() {
47
+ return !!(this.apiKey && this.endpoint);
48
+ }
49
+ }
50
+ /** Factory: build an Azure OpenAI provider adapter. */
51
+ export function azureOpenaiAdapter(options = {}) {
52
+ return new AzureOpenAIAdapter(options);
53
+ }
@@ -8,6 +8,8 @@ export interface ProviderAdapter {
8
8
  readonly providerName: string;
9
9
  complete(request: GavioRequest): Promise<GavioResponse>;
10
10
  stream?(request: GavioRequest): AsyncIterable<string>;
11
+ /** Build a response from a fully buffered stream (F-REL-06). */
12
+ buildStreamResponse?(request: GavioRequest, content: string, startedAt: number): GavioResponse;
11
13
  healthCheck(): Promise<boolean>;
12
14
  readonly reportedModelVersion?: string | null;
13
15
  }
@@ -19,5 +21,10 @@ export declare abstract class BaseProviderAdapter implements ProviderAdapter {
19
21
  abstract complete(request: GavioRequest): Promise<GavioResponse>;
20
22
  abstract healthCheck(): Promise<boolean>;
21
23
  get reportedModelVersion(): string | null;
24
+ /**
25
+ * Build a response from a fully buffered stream (F-REL-06). Streamed chunks
26
+ * carry text only, so token usage is estimated from prompt + content.
27
+ */
28
+ buildStreamResponse(request: GavioRequest, content: string, startedAt: number): GavioResponse;
22
29
  protected buildResponse(request: GavioRequest, content: string, usage: TokenUsage, modelVersion: string, startedAt: number): GavioResponse;
23
30
  }
@@ -1,5 +1,5 @@
1
1
  /** ProviderAdapter interface and shared response-building helpers. */
2
- import { PricingProvider } from '../pricing.js';
2
+ import { PricingProvider, estimateTokens } from '../pricing.js';
3
3
  import { GavioRequest } from '../request.js';
4
4
  import { GavioResponse } from '../response.js';
5
5
  import { TokenUsage } from '../types.js';
@@ -12,6 +12,14 @@ export class BaseProviderAdapter {
12
12
  get reportedModelVersion() {
13
13
  return null;
14
14
  }
15
+ /**
16
+ * Build a response from a fully buffered stream (F-REL-06). Streamed chunks
17
+ * carry text only, so token usage is estimated from prompt + content.
18
+ */
19
+ buildStreamResponse(request, content, startedAt) {
20
+ const usage = new TokenUsage(estimateTokens(request.promptText()), estimateTokens(content));
21
+ return this.buildResponse(request, content, usage, this.reportedModelVersion ?? request.model, startedAt);
22
+ }
15
23
  buildResponse(request, content, usage, modelVersion, startedAt) {
16
24
  const latencyMs = Math.floor(performance.now() - startedAt);
17
25
  return new GavioResponse({
@@ -0,0 +1,36 @@
1
+ /** geminiAdapter — Google Generative Language API (generateContent). */
2
+ import type { PricingProvider } from '../pricing.js';
3
+ import type { GavioRequest } from '../request.js';
4
+ import type { GavioResponse } from '../response.js';
5
+ import { type Message } from '../types.js';
6
+ import { BaseProviderAdapter } from './base.js';
7
+ export interface GeminiAdapterOptions {
8
+ apiKey?: string;
9
+ baseUrl?: string;
10
+ timeoutMs?: number;
11
+ pricing?: PricingProvider;
12
+ }
13
+ interface GeminiContent {
14
+ role: string;
15
+ parts: {
16
+ text: string;
17
+ }[];
18
+ }
19
+ /** Map Gavio messages to Gemini contents + a system instruction. */
20
+ export declare function geminiToContents(messages: Message[]): {
21
+ system: string | null;
22
+ contents: GeminiContent[];
23
+ };
24
+ declare class GeminiAdapter extends BaseProviderAdapter {
25
+ private readonly apiKey;
26
+ private readonly baseUrl;
27
+ private readonly timeoutSeconds;
28
+ constructor(options?: GeminiAdapterOptions);
29
+ get providerName(): string;
30
+ private payload;
31
+ complete(request: GavioRequest): Promise<GavioResponse>;
32
+ healthCheck(): Promise<boolean>;
33
+ }
34
+ /** Factory: build a Gemini provider adapter. */
35
+ export declare function geminiAdapter(options?: GeminiAdapterOptions): GeminiAdapter;
36
+ export {};
@@ -0,0 +1,69 @@
1
+ /** geminiAdapter — Google Generative Language API (generateContent). */
2
+ import { ConfigurationError } from '../errors.js';
3
+ import { TokenUsage } from '../types.js';
4
+ import { BaseProviderAdapter } from './base.js';
5
+ import { postJson } from './http.js';
6
+ const DEFAULT_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';
7
+ /** Map Gavio messages to Gemini contents + a system instruction. */
8
+ export function geminiToContents(messages) {
9
+ let system = null;
10
+ const contents = [];
11
+ for (const m of messages) {
12
+ const text = m.content;
13
+ if (m.role === 'system') {
14
+ system = system ? `${system}\n${text}` : text;
15
+ continue;
16
+ }
17
+ contents.push({ role: m.role === 'assistant' ? 'model' : 'user', parts: [{ text }] });
18
+ }
19
+ return { system, contents };
20
+ }
21
+ class GeminiAdapter extends BaseProviderAdapter {
22
+ apiKey;
23
+ baseUrl;
24
+ timeoutSeconds;
25
+ constructor(options = {}) {
26
+ super(options.pricing);
27
+ this.apiKey =
28
+ options.apiKey ?? process.env['GEMINI_API_KEY'] ?? process.env['GOOGLE_API_KEY'];
29
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
30
+ this.timeoutSeconds = (options.timeoutMs ?? 30_000) / 1000;
31
+ }
32
+ get providerName() {
33
+ return 'gemini';
34
+ }
35
+ payload(request) {
36
+ const { system, contents } = geminiToContents(request.messages);
37
+ const payload = {
38
+ contents,
39
+ generationConfig: {
40
+ temperature: request.temperature,
41
+ maxOutputTokens: request.maxTokens,
42
+ },
43
+ };
44
+ if (system)
45
+ payload['systemInstruction'] = { parts: [{ text: system }] };
46
+ return payload;
47
+ }
48
+ async complete(request) {
49
+ if (!this.apiKey)
50
+ throw new ConfigurationError('GEMINI_API_KEY not set');
51
+ const started = performance.now();
52
+ const url = `${this.baseUrl}/models/${request.model}:generateContent?key=${this.apiKey}`;
53
+ const data = await postJson(url, this.payload(request), {}, this.timeoutSeconds);
54
+ const candidates = data['candidates'] ?? [{}];
55
+ const contentObj = candidates[0]?.['content'] ?? {};
56
+ const parts = contentObj['parts'] ?? [];
57
+ const content = parts.map((p) => p.text ?? '').join('');
58
+ const um = data['usageMetadata'] ?? {};
59
+ const usage = new TokenUsage(um['promptTokenCount'] ?? 0, um['candidatesTokenCount'] ?? 0);
60
+ return this.buildResponse(request, content, usage, request.model, started);
61
+ }
62
+ async healthCheck() {
63
+ return !!this.apiKey;
64
+ }
65
+ }
66
+ /** Factory: build a Gemini provider adapter. */
67
+ export function geminiAdapter(options = {}) {
68
+ return new GeminiAdapter(options);
69
+ }
@@ -10,6 +10,12 @@ export { openaiAdapter } from './openai.js';
10
10
  export type { OpenAIAdapterOptions } from './openai.js';
11
11
  export { anthropicAdapter } from './anthropic.js';
12
12
  export type { AnthropicAdapterOptions } from './anthropic.js';
13
+ export { geminiAdapter } from './gemini.js';
14
+ export type { GeminiAdapterOptions } from './gemini.js';
15
+ export { azureOpenaiAdapter } from './azure-openai.js';
16
+ export type { AzureOpenAIAdapterOptions } from './azure-openai.js';
17
+ export { ollamaAdapter } from './ollama.js';
18
+ export type { OllamaAdapterOptions } from './ollama.js';
13
19
  export { Provider } from '../types.js';
14
- /** Instantiate the default adapter for a provider id. v0.1.0: OpenAI, Anthropic, Mock. */
20
+ /** Instantiate the default adapter for a provider id. */
15
21
  export declare function buildAdapter(provider: Provider | string, pricing?: PricingProvider): ProviderAdapter;
@@ -2,24 +2,37 @@
2
2
  import { ConfigurationError } from '../errors.js';
3
3
  import { Provider, coerceProvider } from '../types.js';
4
4
  import { anthropicAdapter } from './anthropic.js';
5
+ import { azureOpenaiAdapter } from './azure-openai.js';
6
+ import { geminiAdapter } from './gemini.js';
5
7
  import { mockProvider } from './mock.js';
8
+ import { ollamaAdapter } from './ollama.js';
6
9
  import { openaiAdapter } from './openai.js';
7
10
  export { BaseProviderAdapter } from './base.js';
8
11
  export { mockProvider } from './mock.js';
9
12
  export { openaiAdapter } from './openai.js';
10
13
  export { anthropicAdapter } from './anthropic.js';
14
+ export { geminiAdapter } from './gemini.js';
15
+ export { azureOpenaiAdapter } from './azure-openai.js';
16
+ export { ollamaAdapter } from './ollama.js';
11
17
  export { Provider } from '../types.js';
12
- /** Instantiate the default adapter for a provider id. v0.1.0: OpenAI, Anthropic, Mock. */
18
+ /** Instantiate the default adapter for a provider id. */
13
19
  export function buildAdapter(provider, pricing) {
14
20
  const p = coerceProvider(provider);
21
+ const opts = pricing ? { pricing } : {};
15
22
  switch (p) {
16
23
  case Provider.OPENAI:
17
- return openaiAdapter(pricing ? { pricing } : {});
24
+ return openaiAdapter(opts);
18
25
  case Provider.ANTHROPIC:
19
- return anthropicAdapter(pricing ? { pricing } : {});
26
+ return anthropicAdapter(opts);
27
+ case Provider.GEMINI:
28
+ return geminiAdapter(opts);
29
+ case Provider.AZURE_OPENAI:
30
+ return azureOpenaiAdapter(opts);
31
+ case Provider.OLLAMA:
32
+ return ollamaAdapter(opts);
20
33
  case Provider.MOCK:
21
- return mockProvider(pricing ? { pricing } : {});
34
+ return mockProvider(opts);
22
35
  default:
23
- throw new ConfigurationError(`Provider '${p}' is not available in v0.1.0 (available: openai, anthropic, mock)`);
36
+ throw new ConfigurationError(`Provider '${p}' is not available (v0.3.0 adds bedrock, cohere)`);
24
37
  }
25
38
  }
@@ -0,0 +1,21 @@
1
+ /** ollamaAdapter — local models via the Ollama chat API. */
2
+ import type { PricingProvider } from '../pricing.js';
3
+ import type { GavioRequest } from '../request.js';
4
+ import type { GavioResponse } from '../response.js';
5
+ import { BaseProviderAdapter } from './base.js';
6
+ export interface OllamaAdapterOptions {
7
+ baseUrl?: string;
8
+ timeoutMs?: number;
9
+ pricing?: PricingProvider;
10
+ }
11
+ declare class OllamaAdapter extends BaseProviderAdapter {
12
+ private readonly baseUrl;
13
+ private readonly timeoutSeconds;
14
+ constructor(options?: OllamaAdapterOptions);
15
+ get providerName(): string;
16
+ complete(request: GavioRequest): Promise<GavioResponse>;
17
+ healthCheck(): Promise<boolean>;
18
+ }
19
+ /** Factory: build an Ollama provider adapter. */
20
+ export declare function ollamaAdapter(options?: OllamaAdapterOptions): OllamaAdapter;
21
+ export {};
@@ -0,0 +1,38 @@
1
+ /** ollamaAdapter — local models via the Ollama chat API. */
2
+ import { TokenUsage } from '../types.js';
3
+ import { BaseProviderAdapter } from './base.js';
4
+ import { postJson } from './http.js';
5
+ const DEFAULT_BASE_URL = 'http://localhost:11434';
6
+ class OllamaAdapter extends BaseProviderAdapter {
7
+ baseUrl;
8
+ timeoutSeconds;
9
+ constructor(options = {}) {
10
+ super(options.pricing);
11
+ this.baseUrl = (options.baseUrl ?? process.env['OLLAMA_HOST'] ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
12
+ this.timeoutSeconds = (options.timeoutMs ?? 60_000) / 1000;
13
+ }
14
+ get providerName() {
15
+ return 'ollama';
16
+ }
17
+ async complete(request) {
18
+ const started = performance.now();
19
+ const payload = {
20
+ model: request.model,
21
+ messages: request.messages,
22
+ stream: false,
23
+ options: { temperature: request.temperature },
24
+ };
25
+ const data = await postJson(`${this.baseUrl}/api/chat`, payload, {}, this.timeoutSeconds);
26
+ const message = data['message'] ?? {};
27
+ const content = message['content'] ?? '';
28
+ const usage = new TokenUsage(data['prompt_eval_count'] ?? 0, data['eval_count'] ?? 0);
29
+ return this.buildResponse(request, content, usage, data['model'] ?? request.model, started);
30
+ }
31
+ async healthCheck() {
32
+ return true;
33
+ }
34
+ }
35
+ /** Factory: build an Ollama provider adapter. */
36
+ export function ollamaAdapter(options = {}) {
37
+ return new OllamaAdapter(options);
38
+ }
@@ -1,5 +1,6 @@
1
1
  /** GavioRequest — the canonical, provider-agnostic request model. */
2
- import type { Message, Provider } from './types.js';
2
+ import { PromptLineage } from './types.js';
3
+ import type { Message, PromptLineageInit, Provider } from './types.js';
3
4
  export interface GavioRequestInit {
4
5
  messages: Message[];
5
6
  model: string;
@@ -10,6 +11,7 @@ export interface GavioRequestInit {
10
11
  sessionId?: string | null;
11
12
  options?: Record<string, unknown>;
12
13
  metadata?: Record<string, unknown>;
14
+ lineage?: PromptLineage | PromptLineageInit | null;
13
15
  }
14
16
  /**
15
17
  * A single gateway call. A `traceId` (UUID v7, time-sortable) is assigned
@@ -26,6 +28,7 @@ export declare class GavioRequest {
26
28
  sessionId: string | null;
27
29
  options: Record<string, unknown>;
28
30
  metadata: Record<string, unknown>;
31
+ lineage: PromptLineage | null;
29
32
  constructor(init: GavioRequestInit);
30
33
  get temperature(): number;
31
34
  get maxTokens(): number;
@@ -1,6 +1,6 @@
1
1
  /** GavioRequest — the canonical, provider-agnostic request model. */
2
2
  import { newTraceId } from './ids.js';
3
- import { coerceProvider } from './types.js';
3
+ import { coerceProvider, PromptLineage } from './types.js';
4
4
  /**
5
5
  * A single gateway call. A `traceId` (UUID v7, time-sortable) is assigned
6
6
  * automatically if not supplied. `parentTraceId` links calls into a
@@ -16,6 +16,7 @@ export class GavioRequest {
16
16
  sessionId;
17
17
  options;
18
18
  metadata;
19
+ lineage;
19
20
  constructor(init) {
20
21
  this.messages = init.messages;
21
22
  this.model = init.model;
@@ -26,6 +27,7 @@ export class GavioRequest {
26
27
  this.sessionId = init.sessionId ?? null;
27
28
  this.options = init.options ?? {};
28
29
  this.metadata = init.metadata ?? {};
30
+ this.lineage = init.lineage != null ? PromptLineage.from(init.lineage) : null;
29
31
  }
30
32
  get temperature() {
31
33
  const t = this.options['temperature'];
@@ -51,6 +53,7 @@ export class GavioRequest {
51
53
  sessionId: this.sessionId,
52
54
  options: { ...this.options },
53
55
  metadata: { ...this.metadata },
56
+ lineage: this.lineage,
54
57
  });
55
58
  }
56
59
  }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * OpenAI drop-in shim (F-DX-04) — point existing OpenAI SDK code at Gavio.
3
+ *
4
+ * import { Gateway } from 'gavio'
5
+ * import { GavioOpenAI } from 'gavio/shim/openai'
6
+ *
7
+ * const client = new GavioOpenAI(new Gateway({ provider: 'openai', model: 'gpt-4o' }))
8
+ * const resp = await client.chat.completions.create({
9
+ * model: 'gpt-4o', messages: [{ role: 'user', content: 'hi' }],
10
+ * })
11
+ * console.log(resp.choices[0].message.content)
12
+ */
13
+ import type { Gateway } from '../gateway.js';
14
+ import type { Message } from '../types.js';
15
+ export interface ChatCompletion {
16
+ id: string;
17
+ object: 'chat.completion';
18
+ model: string;
19
+ choices: {
20
+ index: number;
21
+ message: {
22
+ role: string;
23
+ content: string;
24
+ };
25
+ finish_reason: string;
26
+ }[];
27
+ usage: {
28
+ prompt_tokens: number;
29
+ completion_tokens: number;
30
+ total_tokens: number;
31
+ };
32
+ gavio: {
33
+ costUsd: number;
34
+ cacheHit: boolean;
35
+ interceptorsFired: string[];
36
+ };
37
+ }
38
+ export interface CreateParams {
39
+ messages: Message[];
40
+ model?: string;
41
+ temperature?: number;
42
+ max_tokens?: number;
43
+ }
44
+ declare class Completions {
45
+ private readonly gw;
46
+ constructor(gw: Gateway);
47
+ create(params: CreateParams): Promise<ChatCompletion>;
48
+ }
49
+ /** OpenAI-client-shaped facade over a Gavio Gateway. */
50
+ export declare class GavioOpenAI {
51
+ readonly chat: {
52
+ completions: Completions;
53
+ };
54
+ constructor(gateway: Gateway);
55
+ }
56
+ export {};
@@ -0,0 +1,53 @@
1
+ /**
2
+ * OpenAI drop-in shim (F-DX-04) — point existing OpenAI SDK code at Gavio.
3
+ *
4
+ * import { Gateway } from 'gavio'
5
+ * import { GavioOpenAI } from 'gavio/shim/openai'
6
+ *
7
+ * const client = new GavioOpenAI(new Gateway({ provider: 'openai', model: 'gpt-4o' }))
8
+ * const resp = await client.chat.completions.create({
9
+ * model: 'gpt-4o', messages: [{ role: 'user', content: 'hi' }],
10
+ * })
11
+ * console.log(resp.choices[0].message.content)
12
+ */
13
+ function toCompletion(resp) {
14
+ return {
15
+ id: resp.traceId,
16
+ object: 'chat.completion',
17
+ model: resp.modelVersion || resp.model,
18
+ choices: [
19
+ { index: 0, message: { role: 'assistant', content: resp.content }, finish_reason: 'stop' },
20
+ ],
21
+ usage: {
22
+ prompt_tokens: resp.usage.promptTokens,
23
+ completion_tokens: resp.usage.completionTokens,
24
+ total_tokens: resp.usage.totalTokens,
25
+ },
26
+ gavio: {
27
+ costUsd: resp.costUsd,
28
+ cacheHit: resp.cacheHit,
29
+ interceptorsFired: resp.interceptorsFired,
30
+ },
31
+ };
32
+ }
33
+ class Completions {
34
+ gw;
35
+ constructor(gw) {
36
+ this.gw = gw;
37
+ }
38
+ async create(params) {
39
+ const resp = await this.gw.complete({
40
+ messages: params.messages,
41
+ model: params.model,
42
+ options: { temperature: params.temperature ?? 0.7, maxTokens: params.max_tokens ?? 1024 },
43
+ });
44
+ return toCompletion(resp);
45
+ }
46
+ }
47
+ /** OpenAI-client-shaped facade over a Gavio Gateway. */
48
+ export class GavioOpenAI {
49
+ chat;
50
+ constructor(gateway) {
51
+ this.chat = { completions: new Completions(gateway) };
52
+ }
53
+ }
@@ -56,3 +56,57 @@ export declare class TokenUsage {
56
56
  totalTokens: number;
57
57
  };
58
58
  }
59
+ export interface RagChunkInit {
60
+ source: string;
61
+ chunkId?: string | null;
62
+ score?: number | null;
63
+ }
64
+ /**
65
+ * A single retrieved source that contributed to a prompt. Carries a *reference*
66
+ * to the source — never the retrieved text — so prompt lineage stays within the
67
+ * audit record's metadata-only contract.
68
+ */
69
+ export declare class RagChunk {
70
+ readonly source: string;
71
+ readonly chunkId: string | null;
72
+ readonly score: number | null;
73
+ constructor(init: RagChunkInit);
74
+ toJSON(): {
75
+ source: string;
76
+ chunkId: string | null;
77
+ score: number | null;
78
+ };
79
+ }
80
+ export interface PromptLineageInit {
81
+ templateId?: string | null;
82
+ templateVersion?: string | null;
83
+ variables?: Record<string, unknown>;
84
+ ragChunks?: Array<RagChunk | RagChunkInit>;
85
+ }
86
+ /**
87
+ * Provenance for a rendered prompt (F-OBS-04): the template, the variable
88
+ * bindings interpolated into it, and the RAG chunk sources retrieved for it.
89
+ *
90
+ * Attached to a GavioRequest by the caller and copied into the AuditRecord so
91
+ * any prompt can be reconstructed and debugged. RAG chunk text is never stored
92
+ * — only source references (see {@link RagChunk}).
93
+ */
94
+ export declare class PromptLineage {
95
+ readonly templateId: string | null;
96
+ readonly templateVersion: string | null;
97
+ readonly variables: Record<string, unknown>;
98
+ readonly ragChunks: RagChunk[];
99
+ constructor(init?: PromptLineageInit);
100
+ /** Coerce a PromptLineage instance or plain init object into a PromptLineage. */
101
+ static from(value: PromptLineage | PromptLineageInit): PromptLineage;
102
+ toJSON(): {
103
+ templateId: string | null;
104
+ templateVersion: string | null;
105
+ variables: Record<string, unknown>;
106
+ ragChunks: Array<{
107
+ source: string;
108
+ chunkId: string | null;
109
+ score: number | null;
110
+ }>;
111
+ };
112
+ }