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,94 @@
|
|
|
1
|
+
/** openaiAdapter — Chat Completions API (GPT-4o, o1, ...). */
|
|
2
|
+
|
|
3
|
+
import { ConfigurationError } from '../errors.js'
|
|
4
|
+
import type { PricingProvider } from '../pricing.js'
|
|
5
|
+
import type { GavioRequest } from '../request.js'
|
|
6
|
+
import type { GavioResponse } from '../response.js'
|
|
7
|
+
import { TokenUsage } from '../types.js'
|
|
8
|
+
import { BaseProviderAdapter } from './base.js'
|
|
9
|
+
import { postJson } from './http.js'
|
|
10
|
+
|
|
11
|
+
const DEFAULT_BASE_URL = 'https://api.openai.com/v1'
|
|
12
|
+
|
|
13
|
+
export interface OpenAIAdapterOptions {
|
|
14
|
+
apiKey?: string
|
|
15
|
+
baseUrl?: string
|
|
16
|
+
timeoutMs?: number
|
|
17
|
+
organization?: string
|
|
18
|
+
pricing?: PricingProvider
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class OpenAIAdapter extends BaseProviderAdapter {
|
|
22
|
+
private readonly apiKey: string | undefined
|
|
23
|
+
private readonly baseUrl: string
|
|
24
|
+
private readonly timeoutSeconds: number
|
|
25
|
+
private readonly organization: string | undefined
|
|
26
|
+
|
|
27
|
+
constructor(options: OpenAIAdapterOptions = {}) {
|
|
28
|
+
super(options.pricing)
|
|
29
|
+
this.apiKey = options.apiKey ?? process.env['OPENAI_API_KEY']
|
|
30
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '')
|
|
31
|
+
this.timeoutSeconds = (options.timeoutMs ?? 30_000) / 1000
|
|
32
|
+
this.organization = options.organization
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get providerName(): string {
|
|
36
|
+
return 'openai'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private headers(): Record<string, string> {
|
|
40
|
+
if (!this.apiKey) {
|
|
41
|
+
throw new ConfigurationError(
|
|
42
|
+
'OPENAI_API_KEY not set (pass apiKey or set the env var)',
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
const headers: Record<string, string> = { Authorization: `Bearer ${this.apiKey}` }
|
|
46
|
+
if (this.organization) headers['OpenAI-Organization'] = this.organization
|
|
47
|
+
return headers
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async complete(request: GavioRequest): Promise<GavioResponse> {
|
|
51
|
+
const started = performance.now()
|
|
52
|
+
const payload = {
|
|
53
|
+
model: request.model,
|
|
54
|
+
messages: request.messages,
|
|
55
|
+
temperature: request.temperature,
|
|
56
|
+
max_tokens: request.maxTokens,
|
|
57
|
+
}
|
|
58
|
+
const data = await postJson(
|
|
59
|
+
`${this.baseUrl}/chat/completions`,
|
|
60
|
+
payload,
|
|
61
|
+
this.headers(),
|
|
62
|
+
this.timeoutSeconds,
|
|
63
|
+
)
|
|
64
|
+
const choices = (data['choices'] as Array<Record<string, unknown>>) ?? []
|
|
65
|
+
const message = (choices[0]?.['message'] as Record<string, unknown>) ?? {}
|
|
66
|
+
const content = (message['content'] as string) ?? ''
|
|
67
|
+
const usageData = (data['usage'] as Record<string, number>) ?? {}
|
|
68
|
+
const usage = new TokenUsage(
|
|
69
|
+
usageData['prompt_tokens'] ?? 0,
|
|
70
|
+
usageData['completion_tokens'] ?? 0,
|
|
71
|
+
)
|
|
72
|
+
return this.buildResponse(
|
|
73
|
+
request,
|
|
74
|
+
content,
|
|
75
|
+
usage,
|
|
76
|
+
(data['model'] as string) ?? request.model,
|
|
77
|
+
started,
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async healthCheck(): Promise<boolean> {
|
|
82
|
+
try {
|
|
83
|
+
this.headers()
|
|
84
|
+
return true
|
|
85
|
+
} catch {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Factory: build an OpenAI provider adapter. */
|
|
92
|
+
export function openaiAdapter(options: OpenAIAdapterOptions = {}): OpenAIAdapter {
|
|
93
|
+
return new OpenAIAdapter(options)
|
|
94
|
+
}
|
package/src/request.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** GavioRequest — the canonical, provider-agnostic request model. */
|
|
2
|
+
|
|
3
|
+
import { newTraceId } from './ids.js'
|
|
4
|
+
import { coerceProvider } from './types.js'
|
|
5
|
+
import type { Message, Provider } from './types.js'
|
|
6
|
+
|
|
7
|
+
export interface GavioRequestInit {
|
|
8
|
+
messages: Message[]
|
|
9
|
+
model: string
|
|
10
|
+
provider: Provider | string
|
|
11
|
+
traceId?: string
|
|
12
|
+
agentId?: string | null
|
|
13
|
+
parentTraceId?: string | null
|
|
14
|
+
sessionId?: string | null
|
|
15
|
+
options?: Record<string, unknown>
|
|
16
|
+
metadata?: Record<string, unknown>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A single gateway call. A `traceId` (UUID v7, time-sortable) is assigned
|
|
21
|
+
* automatically if not supplied. `parentTraceId` links calls into a
|
|
22
|
+
* multi-agent DAG.
|
|
23
|
+
*/
|
|
24
|
+
export class GavioRequest {
|
|
25
|
+
messages: Message[]
|
|
26
|
+
model: string
|
|
27
|
+
provider: Provider
|
|
28
|
+
traceId: string
|
|
29
|
+
agentId: string | null
|
|
30
|
+
parentTraceId: string | null
|
|
31
|
+
sessionId: string | null
|
|
32
|
+
options: Record<string, unknown>
|
|
33
|
+
metadata: Record<string, unknown>
|
|
34
|
+
|
|
35
|
+
constructor(init: GavioRequestInit) {
|
|
36
|
+
this.messages = init.messages
|
|
37
|
+
this.model = init.model
|
|
38
|
+
this.provider = coerceProvider(init.provider)
|
|
39
|
+
this.traceId = init.traceId ?? newTraceId()
|
|
40
|
+
this.agentId = init.agentId ?? null
|
|
41
|
+
this.parentTraceId = init.parentTraceId ?? null
|
|
42
|
+
this.sessionId = init.sessionId ?? null
|
|
43
|
+
this.options = init.options ?? {}
|
|
44
|
+
this.metadata = init.metadata ?? {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get temperature(): number {
|
|
48
|
+
const t = this.options['temperature']
|
|
49
|
+
return typeof t === 'number' ? t : 0.7
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get maxTokens(): number {
|
|
53
|
+
const m = this.options['maxTokens'] ?? this.options['max_tokens']
|
|
54
|
+
return typeof m === 'number' ? m : 1024
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Concatenate message contents — used for hashing and token estimation. */
|
|
58
|
+
promptText(): string {
|
|
59
|
+
return this.messages.map((m) => m.content ?? '').join('\n')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Return a shallow copy with replaced messages (interceptors mutate via this). */
|
|
63
|
+
copyWithMessages(messages: Message[]): GavioRequest {
|
|
64
|
+
return new GavioRequest({
|
|
65
|
+
messages,
|
|
66
|
+
model: this.model,
|
|
67
|
+
provider: this.provider,
|
|
68
|
+
traceId: this.traceId,
|
|
69
|
+
agentId: this.agentId,
|
|
70
|
+
parentTraceId: this.parentTraceId,
|
|
71
|
+
sessionId: this.sessionId,
|
|
72
|
+
options: { ...this.options },
|
|
73
|
+
metadata: { ...this.metadata },
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/response.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** GavioResponse — the canonical response returned to the caller. */
|
|
2
|
+
|
|
3
|
+
import { TokenUsage } from './types.js'
|
|
4
|
+
import type { CacheType } from './types.js'
|
|
5
|
+
import type { AuditRecord } from './interceptors/audit/record.js'
|
|
6
|
+
|
|
7
|
+
export interface GavioResponseInit {
|
|
8
|
+
traceId: string
|
|
9
|
+
content: string
|
|
10
|
+
model: string
|
|
11
|
+
provider: string
|
|
12
|
+
modelVersion?: string
|
|
13
|
+
usage?: TokenUsage
|
|
14
|
+
costUsd?: number
|
|
15
|
+
latencyMs?: number
|
|
16
|
+
cacheHit?: boolean
|
|
17
|
+
cacheType?: CacheType | null
|
|
18
|
+
interceptorsFired?: string[]
|
|
19
|
+
audit?: AuditRecord | null
|
|
20
|
+
metadata?: Record<string, unknown>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Result of a gateway call, enriched by the post-interceptor pipeline. */
|
|
24
|
+
export class GavioResponse {
|
|
25
|
+
traceId: string
|
|
26
|
+
content: string
|
|
27
|
+
model: string
|
|
28
|
+
provider: string
|
|
29
|
+
modelVersion: string
|
|
30
|
+
usage: TokenUsage
|
|
31
|
+
costUsd: number
|
|
32
|
+
latencyMs: number
|
|
33
|
+
cacheHit: boolean
|
|
34
|
+
cacheType: CacheType | null
|
|
35
|
+
interceptorsFired: string[]
|
|
36
|
+
audit: AuditRecord | null
|
|
37
|
+
metadata: Record<string, unknown>
|
|
38
|
+
|
|
39
|
+
constructor(init: GavioResponseInit) {
|
|
40
|
+
this.traceId = init.traceId
|
|
41
|
+
this.content = init.content
|
|
42
|
+
this.model = init.model
|
|
43
|
+
this.provider = init.provider
|
|
44
|
+
this.modelVersion = init.modelVersion ?? ''
|
|
45
|
+
this.usage = init.usage ?? new TokenUsage()
|
|
46
|
+
this.costUsd = init.costUsd ?? 0.0
|
|
47
|
+
this.latencyMs = init.latencyMs ?? 0
|
|
48
|
+
this.cacheHit = init.cacheHit ?? false
|
|
49
|
+
this.cacheType = init.cacheType ?? null
|
|
50
|
+
this.interceptorsFired = init.interceptorsFired ?? []
|
|
51
|
+
this.audit = init.audit ?? null
|
|
52
|
+
this.metadata = init.metadata ?? {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Return a copy with replaced content (used by PII restore, guardrails). */
|
|
56
|
+
copyWithContent(content: string): GavioResponse {
|
|
57
|
+
return new GavioResponse({
|
|
58
|
+
traceId: this.traceId,
|
|
59
|
+
content,
|
|
60
|
+
model: this.model,
|
|
61
|
+
provider: this.provider,
|
|
62
|
+
modelVersion: this.modelVersion,
|
|
63
|
+
usage: this.usage,
|
|
64
|
+
costUsd: this.costUsd,
|
|
65
|
+
latencyMs: this.latencyMs,
|
|
66
|
+
cacheHit: this.cacheHit,
|
|
67
|
+
cacheType: this.cacheType,
|
|
68
|
+
interceptorsFired: [...this.interceptorsFired],
|
|
69
|
+
audit: this.audit,
|
|
70
|
+
metadata: { ...this.metadata },
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
|
|
8
|
+
import { InterceptorContext } from '../context.js'
|
|
9
|
+
import { isExecutorPolicy } from '../interceptors/base.js'
|
|
10
|
+
import type { Executor, ExecutorPolicy, Interceptor } from '../interceptors/base.js'
|
|
11
|
+
import { InterceptorChain } from '../interceptors/chain.js'
|
|
12
|
+
import type { AuditRecord } from '../interceptors/audit/record.js'
|
|
13
|
+
import { mockProvider } from '../providers/mock.js'
|
|
14
|
+
import type { ProviderAdapter } from '../providers/base.js'
|
|
15
|
+
import { GavioRequest } from '../request.js'
|
|
16
|
+
import type { GavioResponse } from '../response.js'
|
|
17
|
+
import { coerceProvider } from '../types.js'
|
|
18
|
+
import type { Message } from '../types.js'
|
|
19
|
+
|
|
20
|
+
/** Records the request as it reaches the provider (post-redaction). */
|
|
21
|
+
class CaptureInterceptor implements Interceptor {
|
|
22
|
+
readonly name = '_capture'
|
|
23
|
+
captured: GavioRequest | null = null
|
|
24
|
+
|
|
25
|
+
before(request: GavioRequest): GavioRequest {
|
|
26
|
+
this.captured = request
|
|
27
|
+
return request
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface GavioTestKitOptions {
|
|
32
|
+
interceptors?: Interceptor[]
|
|
33
|
+
provider?: ProviderAdapter
|
|
34
|
+
model?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RunResult {
|
|
38
|
+
response: GavioResponse
|
|
39
|
+
ctx: InterceptorContext
|
|
40
|
+
/** The request text as it reached the provider (post-redaction). */
|
|
41
|
+
preRequestText(): string
|
|
42
|
+
/** True if any (or a specific) PII entity type was detected. */
|
|
43
|
+
piiDetected(entityType?: string): boolean
|
|
44
|
+
/** The audit record produced for this call, if an audit interceptor ran. */
|
|
45
|
+
readonly auditRecord: AuditRecord | null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class GavioTestKit {
|
|
49
|
+
private readonly interceptors: Interceptor[]
|
|
50
|
+
private readonly provider: ProviderAdapter
|
|
51
|
+
private readonly model: string
|
|
52
|
+
|
|
53
|
+
constructor(options: GavioTestKitOptions = {}) {
|
|
54
|
+
this.interceptors = [...(options.interceptors ?? [])]
|
|
55
|
+
this.provider = options.provider ?? mockProvider()
|
|
56
|
+
this.model = options.model ?? 'mock'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async run(input: { messages: Message[]; options?: Record<string, unknown> }): Promise<RunResult> {
|
|
60
|
+
const request = new GavioRequest({
|
|
61
|
+
messages: input.messages,
|
|
62
|
+
model: this.model,
|
|
63
|
+
provider: coerceProvider(this.provider.providerName),
|
|
64
|
+
options: input.options ?? {},
|
|
65
|
+
})
|
|
66
|
+
const ctx = new InterceptorContext({ traceId: request.traceId })
|
|
67
|
+
|
|
68
|
+
const capture = new CaptureInterceptor()
|
|
69
|
+
const all = [...this.interceptors, capture]
|
|
70
|
+
const policies = all.filter(isExecutorPolicy)
|
|
71
|
+
const regular = all.filter((i) => !isExecutorPolicy(i))
|
|
72
|
+
const chain = new InterceptorChain(regular)
|
|
73
|
+
|
|
74
|
+
let executor: Executor = (req) => this.provider.complete(req)
|
|
75
|
+
for (let i = policies.length - 1; i >= 0; i--) {
|
|
76
|
+
executor = wrap(policies[i]!, executor, ctx)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const response = await chain.execute(request, ctx, executor)
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
response,
|
|
83
|
+
ctx,
|
|
84
|
+
preRequestText: () => capture.captured?.promptText() ?? '',
|
|
85
|
+
piiDetected: (entityType?: string): boolean => {
|
|
86
|
+
if (entityType === undefined) return ctx.piiEntityTypes.length > 0
|
|
87
|
+
return ctx.piiEntityTypes.includes(entityType)
|
|
88
|
+
},
|
|
89
|
+
get auditRecord(): AuditRecord | null {
|
|
90
|
+
return response.audit
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function wrap(policy: ExecutorPolicy, inner: Executor, ctx: InterceptorContext): Executor {
|
|
97
|
+
return (req: GavioRequest) => policy.around(req, ctx, inner)
|
|
98
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** Shared enums (as string unions / const objects) and utility types. */
|
|
2
|
+
|
|
3
|
+
/** A provider-agnostic chat message. */
|
|
4
|
+
export interface Message {
|
|
5
|
+
role: string
|
|
6
|
+
content: string
|
|
7
|
+
[key: string]: unknown
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Supported LLM providers. String-valued for easy config + logging. */
|
|
11
|
+
export const Provider = {
|
|
12
|
+
OPENAI: 'openai',
|
|
13
|
+
ANTHROPIC: 'anthropic',
|
|
14
|
+
GEMINI: 'gemini',
|
|
15
|
+
AZURE_OPENAI: 'azure_openai',
|
|
16
|
+
OLLAMA: 'ollama',
|
|
17
|
+
BEDROCK: 'bedrock',
|
|
18
|
+
COHERE: 'cohere',
|
|
19
|
+
MOCK: 'mock',
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
export type Provider = (typeof Provider)[keyof typeof Provider]
|
|
23
|
+
|
|
24
|
+
/** Accept either a known provider value or any string and normalise to lowercase. */
|
|
25
|
+
export function coerceProvider(value: Provider | string): Provider {
|
|
26
|
+
return value.toLowerCase() as Provider
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const CacheType = {
|
|
30
|
+
EXACT: 'exact',
|
|
31
|
+
SEMANTIC: 'semantic',
|
|
32
|
+
} as const
|
|
33
|
+
|
|
34
|
+
export type CacheType = (typeof CacheType)[keyof typeof CacheType]
|
|
35
|
+
|
|
36
|
+
/** What PiiGuard does with a detected entity. */
|
|
37
|
+
export const PiiMode = {
|
|
38
|
+
REDACT: 'redact', // replace with a typed placeholder token
|
|
39
|
+
MASK: 'mask', // replace characters with asterisks
|
|
40
|
+
TAG: 'tag', // annotate inline but keep the value
|
|
41
|
+
BLOCK: 'block', // raise and refuse the request
|
|
42
|
+
} as const
|
|
43
|
+
|
|
44
|
+
export type PiiMode = (typeof PiiMode)[keyof typeof PiiMode]
|
|
45
|
+
|
|
46
|
+
export const Sensitivity = {
|
|
47
|
+
STRICT: 'strict',
|
|
48
|
+
BALANCED: 'balanced',
|
|
49
|
+
PERMISSIVE: 'permissive',
|
|
50
|
+
} as const
|
|
51
|
+
|
|
52
|
+
export type Sensitivity = (typeof Sensitivity)[keyof typeof Sensitivity]
|
|
53
|
+
|
|
54
|
+
export const GuardrailOutcome = {
|
|
55
|
+
PASS: 'PASS',
|
|
56
|
+
FAIL: 'FAIL',
|
|
57
|
+
HITL: 'HITL',
|
|
58
|
+
} as const
|
|
59
|
+
|
|
60
|
+
export type GuardrailOutcome = (typeof GuardrailOutcome)[keyof typeof GuardrailOutcome]
|
|
61
|
+
|
|
62
|
+
/** Token accounting for a single completion. */
|
|
63
|
+
export class TokenUsage {
|
|
64
|
+
readonly promptTokens: number
|
|
65
|
+
readonly completionTokens: number
|
|
66
|
+
|
|
67
|
+
constructor(promptTokens = 0, completionTokens = 0) {
|
|
68
|
+
this.promptTokens = promptTokens
|
|
69
|
+
this.completionTokens = completionTokens
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get totalTokens(): number {
|
|
73
|
+
return this.promptTokens + this.completionTokens
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
toJSON(): { promptTokens: number; completionTokens: number; totalTokens: number } {
|
|
77
|
+
return {
|
|
78
|
+
promptTokens: this.promptTokens,
|
|
79
|
+
completionTokens: this.completionTokens,
|
|
80
|
+
totalTokens: this.totalTokens,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|