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