@anvailabs/sandhi 0.5.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/Cargo.lock +1625 -0
- package/Cargo.toml +38 -0
- package/README.md +89 -0
- package/__test__/gateway.test.mjs +860 -0
- package/__test__/typed_error.test.mjs +48 -0
- package/artifacts/sandhi.darwin-arm64.node +0 -0
- package/artifacts/sandhi.linux-x64-gnu.node +0 -0
- package/build.rs +3 -0
- package/contracts.d.ts +63 -0
- package/index.d.ts +143 -0
- package/index.js +326 -0
- package/npm/darwin-arm64/package.json +19 -0
- package/npm/darwin-arm64/sandhi.darwin-arm64.node +0 -0
- package/npm/linux-x64-gnu/package.json +22 -0
- package/npm/linux-x64-gnu/sandhi.linux-x64-gnu.node +0 -0
- package/package.json +45 -0
- package/sandhi.d.ts +22 -0
- package/sandhi.js +113 -0
- package/sandhi.linux-x64-gnu.node +0 -0
- package/src/lib.rs +834 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// SandhiProviderError rewrap contract (TD-0008 P2/C): typed payloads become the
|
|
2
|
+
// class; binding-internal failures pass through untouched.
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { test } from "node:test";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const sandhi = require("../sandhi.js");
|
|
9
|
+
|
|
10
|
+
test("SandhiProviderError exposes typed fields from a ProviderErrorV1 payload", () => {
|
|
11
|
+
const payload = {
|
|
12
|
+
code: "upstream_error",
|
|
13
|
+
message: "upstream status 400: bad tool pairing",
|
|
14
|
+
retryable: false,
|
|
15
|
+
http_status: 400,
|
|
16
|
+
provider: "moonshot",
|
|
17
|
+
request_id: "req_abc",
|
|
18
|
+
details: { upstream_body: "{\"error\":\"bad tool pairing\"}" },
|
|
19
|
+
};
|
|
20
|
+
const err = new sandhi.SandhiProviderError(payload, JSON.stringify(payload));
|
|
21
|
+
assert.equal(err.name, "SandhiProviderError");
|
|
22
|
+
assert.equal(err.code, "upstream_error");
|
|
23
|
+
assert.equal(err.httpStatus, 400);
|
|
24
|
+
assert.equal(err.retryable, false);
|
|
25
|
+
assert.equal(err.requestId, "req_abc");
|
|
26
|
+
assert.ok(err instanceof Error);
|
|
27
|
+
assert.equal(err.details.upstream_body, "{\"error\":\"bad tool pairing\"}");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("async iterator rewraps typed error messages and passes others through", async () => {
|
|
31
|
+
const typedJson = JSON.stringify({ code: "rate_limited", message: "rate limited (429)", retryable: true, http_status: 429 });
|
|
32
|
+
const makeStream = (error) => {
|
|
33
|
+
const stream = Object.create(sandhi.TypedEventStream.prototype);
|
|
34
|
+
stream.read = async () => { throw error; };
|
|
35
|
+
return stream;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
await assert.rejects(
|
|
39
|
+
async () => { for await (const _ of makeStream(new Error(typedJson))) { /* drain */ } },
|
|
40
|
+
(err) => err instanceof sandhi.SandhiProviderError && err.code === "rate_limited" && err.retryable === true
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const plain = new Error("segfault in binding");
|
|
44
|
+
await assert.rejects(
|
|
45
|
+
async () => { for await (const _ of makeStream(plain)) { /* drain */ } },
|
|
46
|
+
(err) => err === plain && !(err instanceof sandhi.SandhiProviderError)
|
|
47
|
+
);
|
|
48
|
+
});
|
|
Binary file
|
|
Binary file
|
package/build.rs
ADDED
package/contracts.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Generated by scripts/gen-binding-contract-facades.py; DO NOT EDIT.
|
|
2
|
+
// schema-sha256: 0eff7885fe2a40495027f8ae846d4acef93c0b76cf712b6c16f72fa4c97bebe7
|
|
3
|
+
export type JsonValue = unknown
|
|
4
|
+
export type Role = "developer" | "system" | "user" | "assistant" | "tool" | "function"
|
|
5
|
+
export type FinishReasonV1 = "stop" | "length" | "tool_calls" | "content_filter" | "function_call" | "unknown"
|
|
6
|
+
export type UsageCompleteness = "final" | "partial" | "unavailable"
|
|
7
|
+
export type UsageBasis = "provider_reported" | "estimated"
|
|
8
|
+
export type ContentPart =
|
|
9
|
+
| { type: "text"; text: string }
|
|
10
|
+
| { type: "image_url"; image_url: string; detail?: string }
|
|
11
|
+
| { type: "input_audio"; data: string; format: string }
|
|
12
|
+
| { type: "file"; file_id?: string; file_data?: string; filename?: string }
|
|
13
|
+
export type MessageContent = string | ContentPart[]
|
|
14
|
+
export interface ToolCallV1 { id: string; name: string; arguments: string; extensions?: Record<string, JsonValue> }
|
|
15
|
+
export type ChatMessageV1 =
|
|
16
|
+
| { role: "developer" | "system" | "user"; content: MessageContent; name?: string }
|
|
17
|
+
| { role: "assistant"; content?: MessageContent; name?: string; tool_calls?: ToolCallV1[]; refusal?: string }
|
|
18
|
+
| { role: "tool"; content: MessageContent; tool_call_id: string }
|
|
19
|
+
| { role: "function"; content: MessageContent; name: string }
|
|
20
|
+
export interface ToolDefinitionV1 { name: string; parameters: JsonValue; description?: string; strict?: boolean }
|
|
21
|
+
export interface RequestMetadataV1 { session_id?: string; virtual_key_id?: string; subject_id?: string; group_id?: string; route?: string }
|
|
22
|
+
export interface ThinkingV1 { enabled: boolean; budget_tokens?: number }
|
|
23
|
+
export interface ChatRequestV1 {
|
|
24
|
+
schema_version?: "1"; model: string; messages: ChatMessageV1[]; tools?: ToolDefinitionV1[]
|
|
25
|
+
tool_choice?: "none" | "auto" | "required" | { name: string }; temperature?: number
|
|
26
|
+
max_output_tokens?: number; stop?: string[]; response_format?: JsonValue; seed?: number
|
|
27
|
+
metadata?: RequestMetadataV1; reasoning_effort?: string; thinking?: ThinkingV1
|
|
28
|
+
include_native_response?: boolean
|
|
29
|
+
extensions?: Record<string, JsonValue>
|
|
30
|
+
}
|
|
31
|
+
export interface UsageV2 {
|
|
32
|
+
tokens_in: number; tokens_out: number; cache_creation_tokens: number; cache_read_tokens: number
|
|
33
|
+
completeness?: UsageCompleteness; basis?: UsageBasis; attempts?: number; outcome?: string
|
|
34
|
+
upstream_request_id?: string
|
|
35
|
+
duration_ms?: number; time_to_first_token_ms?: number
|
|
36
|
+
audio_input_tokens?: number; audio_output_tokens?: number; reasoning_tokens?: number
|
|
37
|
+
accepted_prediction_tokens?: number; rejected_prediction_tokens?: number
|
|
38
|
+
}
|
|
39
|
+
export interface LatencySummary { samples: number; p50_ms: number; p95_ms: number; ttft_p50_ms?: number }
|
|
40
|
+
export interface UsageAggregateV1 {
|
|
41
|
+
key: string; calls: number; tokens_in: number; tokens_out: number;
|
|
42
|
+
cache_creation_tokens: number; cache_read_tokens: number; reasoning_tokens: number;
|
|
43
|
+
billable_tokens: number; latency?: LatencySummary
|
|
44
|
+
}
|
|
45
|
+
export interface AssistantOutputV1 { content?: MessageContent; tool_calls?: ToolCallV1[]; refusal?: string }
|
|
46
|
+
export interface ChatResponseV1 {
|
|
47
|
+
schema_version: "1"; id?: string; model: string; output: AssistantOutputV1
|
|
48
|
+
finish_reason?: FinishReasonV1; usage: UsageV2; extensions?: Record<string, JsonValue>
|
|
49
|
+
}
|
|
50
|
+
export interface ProviderErrorV1 { code: string; message: string; retryable: boolean; http_status?: number; provider?: string; request_id?: string; details?: Record<string, JsonValue> }
|
|
51
|
+
export type ChatStreamEventV1 =
|
|
52
|
+
| { event: "response_start"; id?: string; model: string }
|
|
53
|
+
| { event: "text_delta" | "reasoning_delta" | "refusal_delta"; delta: string }
|
|
54
|
+
| { event: "tool_call_start"; index: number; id: string; name: string }
|
|
55
|
+
| { event: "tool_call_arguments_delta"; index: number; delta: string }
|
|
56
|
+
| { event: "tool_call_end"; index: number }
|
|
57
|
+
| { event: "usage"; usage: UsageV2 }
|
|
58
|
+
| { event: "finish"; reason: FinishReasonV1 }
|
|
59
|
+
| { event: "error"; error: ProviderErrorV1 }
|
|
60
|
+
export interface ProviderDescriptorV1 {
|
|
61
|
+
schema_version: "1"; slug: string; aliases?: string[]; endpoint_family: string; base_url: string
|
|
62
|
+
capabilities: Record<string, boolean>; models?: JsonValue[]; extensions?: Record<string, JsonValue>
|
|
63
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/* auto-generated by NAPI-RS */
|
|
5
|
+
|
|
6
|
+
/** The neutral token breakdown parsed from a provider response. */
|
|
7
|
+
export interface UsageBreakdown {
|
|
8
|
+
tokensIn: number
|
|
9
|
+
tokensOut: number
|
|
10
|
+
cacheCreationTokens: number
|
|
11
|
+
cacheReadTokens: number
|
|
12
|
+
}
|
|
13
|
+
/** A neutral usage event (mirrors `usage-event.v1.schema.json`). */
|
|
14
|
+
export interface Event {
|
|
15
|
+
schemaVersion: string
|
|
16
|
+
requestId: string
|
|
17
|
+
occurredAt: string
|
|
18
|
+
provider: string
|
|
19
|
+
model: string
|
|
20
|
+
backend: string
|
|
21
|
+
virtualKeyId?: string
|
|
22
|
+
subjectId?: string
|
|
23
|
+
groupId?: string
|
|
24
|
+
route?: string
|
|
25
|
+
sessionId?: string
|
|
26
|
+
tokensIn: number
|
|
27
|
+
tokensOut: number
|
|
28
|
+
cacheCreationTokens: number
|
|
29
|
+
cacheReadTokens: number
|
|
30
|
+
usageCompleteness: string
|
|
31
|
+
attempts: number
|
|
32
|
+
outcome?: string
|
|
33
|
+
upstreamRequestId?: string
|
|
34
|
+
gpuSeconds?: number
|
|
35
|
+
}
|
|
36
|
+
/** The usage-event wire-contract major version this build targets. */
|
|
37
|
+
export declare function wireContractVersion(): string
|
|
38
|
+
/**
|
|
39
|
+
* The neutral chat-contract (ChatRequestV1/ChatStreamEventV1) major version this
|
|
40
|
+
* build targets. Today equal to `wire_contract_version()` (pinned by a
|
|
41
|
+
* sandhi-core test); exported separately so a future split cannot silently
|
|
42
|
+
* invalidate consumer handshakes that validate the chat contract.
|
|
43
|
+
*/
|
|
44
|
+
export declare function chatContractVersion(): string
|
|
45
|
+
/**
|
|
46
|
+
* Additive growth counter within the v1 chat contract (W3c). Consumers
|
|
47
|
+
* feature-detect this export — an older binding without it reads as minor 0
|
|
48
|
+
* — and gate trust in newer additive fields on a minimum minor.
|
|
49
|
+
*/
|
|
50
|
+
export declare function chatContractMinor(): number
|
|
51
|
+
/**
|
|
52
|
+
* Resolve an OpenAI-compatible provider spec (slug, aliases, base_url) as JSON.
|
|
53
|
+
* Parity with the Python binding's `provider_spec` (TD-0008 P4); JSON here
|
|
54
|
+
* because napi objects would otherwise diverge from the schema'd facades.
|
|
55
|
+
*/
|
|
56
|
+
export declare function providerSpecJson(provider: string, model?: string | undefined | null): string
|
|
57
|
+
/**
|
|
58
|
+
* Return a checked chat-contract JSON Schema document by name
|
|
59
|
+
* (e.g. "chat-stream-event.v1"). Parity with the Python binding.
|
|
60
|
+
*/
|
|
61
|
+
export declare function chatContractSchemaJson(name: string): string
|
|
62
|
+
/** Return the versioned typed descriptor for a known provider as JSON. */
|
|
63
|
+
export declare function providerDescriptorJson(provider: string): string
|
|
64
|
+
/**
|
|
65
|
+
* Return the curated model descriptors for a known provider as a JSON list (TD-0004 catalog DATA).
|
|
66
|
+
* Carries facts only (id, context window, max output, capabilities); no pricing.
|
|
67
|
+
*/
|
|
68
|
+
export declare function providerModelsJson(provider: string): string
|
|
69
|
+
/**
|
|
70
|
+
* Parse a provider response body (JSON string) into the neutral token breakdown. `provider`
|
|
71
|
+
* selects the parser: `anthropic` → the Anthropic Messages shape; anything else → OpenAI-compat.
|
|
72
|
+
*/
|
|
73
|
+
export declare function parseUsage(provider: string, responseJson: string): UsageBreakdown
|
|
74
|
+
export type JsProviderRuntime = ProviderRuntime
|
|
75
|
+
/**
|
|
76
|
+
* Persistent factory for typed provider handles. The HTTP pool, retry policy, and circuit
|
|
77
|
+
* breaker belong to each returned handle rather than being rebuilt for every request.
|
|
78
|
+
*/
|
|
79
|
+
export declare class ProviderRuntime {
|
|
80
|
+
constructor()
|
|
81
|
+
openaiCompat(provider: string, baseUrl: string, apiKey: string, headersJson?: string | undefined | null, maxRetries?: number | undefined | null, timeoutSecs?: number | undefined | null, streamIdleTimeoutSecs?: number | undefined | null): TypedProvider
|
|
82
|
+
openaiResponses(provider: string, baseUrl: string, bearerToken: string, headersJson?: string | undefined | null, maxRetries?: number | undefined | null, timeoutSecs?: number | undefined | null, streamIdleTimeoutSecs?: number | undefined | null): TypedProvider
|
|
83
|
+
provider(provider: string, model: string, apiKey: string, baseUrl?: string | undefined | null, headersJson?: string | undefined | null, maxRetries?: number | undefined | null, timeoutSecs?: number | undefined | null, streamIdleTimeoutSecs?: number | undefined | null, authScheme?: string | undefined | null, protocol?: string | undefined | null): TypedProvider
|
|
84
|
+
}
|
|
85
|
+
/** A persistent provider handle accepting Sandhi chat-contract v1 JSON documents. */
|
|
86
|
+
export declare class TypedProvider {
|
|
87
|
+
get provider(): string
|
|
88
|
+
/**
|
|
89
|
+
* `wireHeadersJson` (optional, TD-0022 D1): per-call wire headers — a string map sent
|
|
90
|
+
* with THIS call only, for gateway-path metadata that changes per turn (e.g.
|
|
91
|
+
* `x-sandhi-step-id`). Transport-owned names are stripped and can never override the
|
|
92
|
+
* credential.
|
|
93
|
+
*/
|
|
94
|
+
completeJson(requestJson: string, wireHeadersJson?: string | undefined | null): Promise<string>
|
|
95
|
+
/** `wireHeadersJson` (optional, TD-0022 D1): per-call wire headers, as `completeJson`. */
|
|
96
|
+
streamJson(requestJson: string, wireHeadersJson?: string | undefined | null): TypedEventStream
|
|
97
|
+
}
|
|
98
|
+
/** Pull-based stream of serialized `ChatStreamEventV1` documents. */
|
|
99
|
+
export declare class TypedEventStream {
|
|
100
|
+
read(): Promise<string | null>
|
|
101
|
+
}
|
|
102
|
+
/** In-process metering middleware: virtual keys, budgets, and usage-event emission. */
|
|
103
|
+
export declare class Gateway {
|
|
104
|
+
/** `sinkPath` — append emitted events as JSONL to this file (plus an in-memory buffer). */
|
|
105
|
+
constructor(sinkPath?: string | undefined | null)
|
|
106
|
+
/** Register a virtual key: `id` → subject/group attribution + an opaque `upstream` ref. */
|
|
107
|
+
addVirtualKey(id: string, subject?: string | undefined | null, group?: string | undefined | null, upstream?: string | undefined | null): void
|
|
108
|
+
/**
|
|
109
|
+
* Set a token budget on a scope (e.g. `group:platform`). Exposed as a 64-bit `bigint` (napi
|
|
110
|
+
* has no bare `u64`); negatives clamp to 0 — a budget is a non-negative token count.
|
|
111
|
+
*/
|
|
112
|
+
setBudget(scope: string, tokens: number): void
|
|
113
|
+
/** Would `add` more tokens be within the scope's budget? */
|
|
114
|
+
checkBudget(scope: string, add: number): boolean
|
|
115
|
+
/** Tokens spent so far on a scope. */
|
|
116
|
+
spent(scope: string): number
|
|
117
|
+
/**
|
|
118
|
+
* Meter one completed call: parse usage from `responseJson` (built-in parser for `provider`),
|
|
119
|
+
* attribute it to `virtualKey`, emit the neutral event + record the budget, and return the
|
|
120
|
+
* event. Throws on an unknown virtual key or bad JSON.
|
|
121
|
+
*/
|
|
122
|
+
meter(virtualKey: string, provider: string, model: string, responseJson: string, sessionId?: string | undefined | null, route?: string | undefined | null): Event
|
|
123
|
+
/**
|
|
124
|
+
* Meter from token counts you supply directly (bypass parsing) — the escape hatch for any
|
|
125
|
+
* provider Sandhi doesn't natively parse: do your own parsing and pass the counts. Same
|
|
126
|
+
* attribution + budget + emit as `meter()`.
|
|
127
|
+
*/
|
|
128
|
+
meterTokens(virtualKey: string, provider: string, model: string, tokensIn: number, tokensOut: number, cacheCreationTokens?: number | undefined | null, cacheReadTokens?: number | undefined | null, sessionId?: string | undefined | null, route?: string | undefined | null): Event
|
|
129
|
+
/** All events emitted so far (in-memory). */
|
|
130
|
+
events(): Array<Event>
|
|
131
|
+
/**
|
|
132
|
+
* Fold the events recorded so far into `UsageAggregateV1` rows for one attribution
|
|
133
|
+
* `dimension`, serialized as a JSON array (TD-0009 P2). `dimension` is one of `subject`
|
|
134
|
+
* (`user`), `group`, `provider`, `model`, `key` (`virtual_key`), `session`, or `total`.
|
|
135
|
+
*
|
|
136
|
+
* The fold is `sandhi_core::UsageAggregator` — the same definition the proxy, the CLI, and
|
|
137
|
+
* the dashboard read, so the in-process path cannot disagree with them. `cap` bounds the
|
|
138
|
+
* number of distinct keys before the rest fold into `"(overflow)"` (default 1024): a
|
|
139
|
+
* long-lived process metering unbounded `sessionId`s loses per-key detail, never the sum —
|
|
140
|
+
* the rows always add up to the exact total. Throws on an unknown dimension.
|
|
141
|
+
*/
|
|
142
|
+
usageSnapshotJson(dimension: string, cap?: number | undefined | null): string
|
|
143
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/* prettier-ignore */
|
|
4
|
+
|
|
5
|
+
/* auto-generated by NAPI-RS */
|
|
6
|
+
|
|
7
|
+
const { existsSync, readFileSync } = require('fs')
|
|
8
|
+
const { join } = require('path')
|
|
9
|
+
|
|
10
|
+
const { platform, arch } = process
|
|
11
|
+
|
|
12
|
+
let nativeBinding = null
|
|
13
|
+
let localFileExisted = false
|
|
14
|
+
let loadError = null
|
|
15
|
+
|
|
16
|
+
function isMusl() {
|
|
17
|
+
// For Node 10
|
|
18
|
+
if (!process.report || typeof process.report.getReport !== 'function') {
|
|
19
|
+
try {
|
|
20
|
+
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
|
21
|
+
return readFileSync(lddPath, 'utf8').includes('musl')
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
const { glibcVersionRuntime } = process.report.getReport().header
|
|
27
|
+
return !glibcVersionRuntime
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
switch (platform) {
|
|
32
|
+
case 'android':
|
|
33
|
+
switch (arch) {
|
|
34
|
+
case 'arm64':
|
|
35
|
+
localFileExisted = existsSync(join(__dirname, 'sandhi.android-arm64.node'))
|
|
36
|
+
try {
|
|
37
|
+
if (localFileExisted) {
|
|
38
|
+
nativeBinding = require('./sandhi.android-arm64.node')
|
|
39
|
+
} else {
|
|
40
|
+
nativeBinding = require('@anvailabs/sandhi-android-arm64')
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
loadError = e
|
|
44
|
+
}
|
|
45
|
+
break
|
|
46
|
+
case 'arm':
|
|
47
|
+
localFileExisted = existsSync(join(__dirname, 'sandhi.android-arm-eabi.node'))
|
|
48
|
+
try {
|
|
49
|
+
if (localFileExisted) {
|
|
50
|
+
nativeBinding = require('./sandhi.android-arm-eabi.node')
|
|
51
|
+
} else {
|
|
52
|
+
nativeBinding = require('@anvailabs/sandhi-android-arm-eabi')
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
loadError = e
|
|
56
|
+
}
|
|
57
|
+
break
|
|
58
|
+
default:
|
|
59
|
+
throw new Error(`Unsupported architecture on Android ${arch}`)
|
|
60
|
+
}
|
|
61
|
+
break
|
|
62
|
+
case 'win32':
|
|
63
|
+
switch (arch) {
|
|
64
|
+
case 'x64':
|
|
65
|
+
localFileExisted = existsSync(
|
|
66
|
+
join(__dirname, 'sandhi.win32-x64-msvc.node')
|
|
67
|
+
)
|
|
68
|
+
try {
|
|
69
|
+
if (localFileExisted) {
|
|
70
|
+
nativeBinding = require('./sandhi.win32-x64-msvc.node')
|
|
71
|
+
} else {
|
|
72
|
+
nativeBinding = require('@anvailabs/sandhi-win32-x64-msvc')
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
loadError = e
|
|
76
|
+
}
|
|
77
|
+
break
|
|
78
|
+
case 'ia32':
|
|
79
|
+
localFileExisted = existsSync(
|
|
80
|
+
join(__dirname, 'sandhi.win32-ia32-msvc.node')
|
|
81
|
+
)
|
|
82
|
+
try {
|
|
83
|
+
if (localFileExisted) {
|
|
84
|
+
nativeBinding = require('./sandhi.win32-ia32-msvc.node')
|
|
85
|
+
} else {
|
|
86
|
+
nativeBinding = require('@anvailabs/sandhi-win32-ia32-msvc')
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
loadError = e
|
|
90
|
+
}
|
|
91
|
+
break
|
|
92
|
+
case 'arm64':
|
|
93
|
+
localFileExisted = existsSync(
|
|
94
|
+
join(__dirname, 'sandhi.win32-arm64-msvc.node')
|
|
95
|
+
)
|
|
96
|
+
try {
|
|
97
|
+
if (localFileExisted) {
|
|
98
|
+
nativeBinding = require('./sandhi.win32-arm64-msvc.node')
|
|
99
|
+
} else {
|
|
100
|
+
nativeBinding = require('@anvailabs/sandhi-win32-arm64-msvc')
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
loadError = e
|
|
104
|
+
}
|
|
105
|
+
break
|
|
106
|
+
default:
|
|
107
|
+
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
|
108
|
+
}
|
|
109
|
+
break
|
|
110
|
+
case 'darwin':
|
|
111
|
+
localFileExisted = existsSync(join(__dirname, 'sandhi.darwin-universal.node'))
|
|
112
|
+
try {
|
|
113
|
+
if (localFileExisted) {
|
|
114
|
+
nativeBinding = require('./sandhi.darwin-universal.node')
|
|
115
|
+
} else {
|
|
116
|
+
nativeBinding = require('@anvailabs/sandhi-darwin-universal')
|
|
117
|
+
}
|
|
118
|
+
break
|
|
119
|
+
} catch {}
|
|
120
|
+
switch (arch) {
|
|
121
|
+
case 'x64':
|
|
122
|
+
localFileExisted = existsSync(join(__dirname, 'sandhi.darwin-x64.node'))
|
|
123
|
+
try {
|
|
124
|
+
if (localFileExisted) {
|
|
125
|
+
nativeBinding = require('./sandhi.darwin-x64.node')
|
|
126
|
+
} else {
|
|
127
|
+
nativeBinding = require('@anvailabs/sandhi-darwin-x64')
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
loadError = e
|
|
131
|
+
}
|
|
132
|
+
break
|
|
133
|
+
case 'arm64':
|
|
134
|
+
localFileExisted = existsSync(
|
|
135
|
+
join(__dirname, 'sandhi.darwin-arm64.node')
|
|
136
|
+
)
|
|
137
|
+
try {
|
|
138
|
+
if (localFileExisted) {
|
|
139
|
+
nativeBinding = require('./sandhi.darwin-arm64.node')
|
|
140
|
+
} else {
|
|
141
|
+
nativeBinding = require('@anvailabs/sandhi-darwin-arm64')
|
|
142
|
+
}
|
|
143
|
+
} catch (e) {
|
|
144
|
+
loadError = e
|
|
145
|
+
}
|
|
146
|
+
break
|
|
147
|
+
default:
|
|
148
|
+
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
|
149
|
+
}
|
|
150
|
+
break
|
|
151
|
+
case 'freebsd':
|
|
152
|
+
if (arch !== 'x64') {
|
|
153
|
+
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
|
|
154
|
+
}
|
|
155
|
+
localFileExisted = existsSync(join(__dirname, 'sandhi.freebsd-x64.node'))
|
|
156
|
+
try {
|
|
157
|
+
if (localFileExisted) {
|
|
158
|
+
nativeBinding = require('./sandhi.freebsd-x64.node')
|
|
159
|
+
} else {
|
|
160
|
+
nativeBinding = require('@anvailabs/sandhi-freebsd-x64')
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
loadError = e
|
|
164
|
+
}
|
|
165
|
+
break
|
|
166
|
+
case 'linux':
|
|
167
|
+
switch (arch) {
|
|
168
|
+
case 'x64':
|
|
169
|
+
if (isMusl()) {
|
|
170
|
+
localFileExisted = existsSync(
|
|
171
|
+
join(__dirname, 'sandhi.linux-x64-musl.node')
|
|
172
|
+
)
|
|
173
|
+
try {
|
|
174
|
+
if (localFileExisted) {
|
|
175
|
+
nativeBinding = require('./sandhi.linux-x64-musl.node')
|
|
176
|
+
} else {
|
|
177
|
+
nativeBinding = require('@anvailabs/sandhi-linux-x64-musl')
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
loadError = e
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
localFileExisted = existsSync(
|
|
184
|
+
join(__dirname, 'sandhi.linux-x64-gnu.node')
|
|
185
|
+
)
|
|
186
|
+
try {
|
|
187
|
+
if (localFileExisted) {
|
|
188
|
+
nativeBinding = require('./sandhi.linux-x64-gnu.node')
|
|
189
|
+
} else {
|
|
190
|
+
nativeBinding = require('@anvailabs/sandhi-linux-x64-gnu')
|
|
191
|
+
}
|
|
192
|
+
} catch (e) {
|
|
193
|
+
loadError = e
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
break
|
|
197
|
+
case 'arm64':
|
|
198
|
+
if (isMusl()) {
|
|
199
|
+
localFileExisted = existsSync(
|
|
200
|
+
join(__dirname, 'sandhi.linux-arm64-musl.node')
|
|
201
|
+
)
|
|
202
|
+
try {
|
|
203
|
+
if (localFileExisted) {
|
|
204
|
+
nativeBinding = require('./sandhi.linux-arm64-musl.node')
|
|
205
|
+
} else {
|
|
206
|
+
nativeBinding = require('@anvailabs/sandhi-linux-arm64-musl')
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {
|
|
209
|
+
loadError = e
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
localFileExisted = existsSync(
|
|
213
|
+
join(__dirname, 'sandhi.linux-arm64-gnu.node')
|
|
214
|
+
)
|
|
215
|
+
try {
|
|
216
|
+
if (localFileExisted) {
|
|
217
|
+
nativeBinding = require('./sandhi.linux-arm64-gnu.node')
|
|
218
|
+
} else {
|
|
219
|
+
nativeBinding = require('@anvailabs/sandhi-linux-arm64-gnu')
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
loadError = e
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
break
|
|
226
|
+
case 'arm':
|
|
227
|
+
if (isMusl()) {
|
|
228
|
+
localFileExisted = existsSync(
|
|
229
|
+
join(__dirname, 'sandhi.linux-arm-musleabihf.node')
|
|
230
|
+
)
|
|
231
|
+
try {
|
|
232
|
+
if (localFileExisted) {
|
|
233
|
+
nativeBinding = require('./sandhi.linux-arm-musleabihf.node')
|
|
234
|
+
} else {
|
|
235
|
+
nativeBinding = require('@anvailabs/sandhi-linux-arm-musleabihf')
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
loadError = e
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
localFileExisted = existsSync(
|
|
242
|
+
join(__dirname, 'sandhi.linux-arm-gnueabihf.node')
|
|
243
|
+
)
|
|
244
|
+
try {
|
|
245
|
+
if (localFileExisted) {
|
|
246
|
+
nativeBinding = require('./sandhi.linux-arm-gnueabihf.node')
|
|
247
|
+
} else {
|
|
248
|
+
nativeBinding = require('@anvailabs/sandhi-linux-arm-gnueabihf')
|
|
249
|
+
}
|
|
250
|
+
} catch (e) {
|
|
251
|
+
loadError = e
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
break
|
|
255
|
+
case 'riscv64':
|
|
256
|
+
if (isMusl()) {
|
|
257
|
+
localFileExisted = existsSync(
|
|
258
|
+
join(__dirname, 'sandhi.linux-riscv64-musl.node')
|
|
259
|
+
)
|
|
260
|
+
try {
|
|
261
|
+
if (localFileExisted) {
|
|
262
|
+
nativeBinding = require('./sandhi.linux-riscv64-musl.node')
|
|
263
|
+
} else {
|
|
264
|
+
nativeBinding = require('@anvailabs/sandhi-linux-riscv64-musl')
|
|
265
|
+
}
|
|
266
|
+
} catch (e) {
|
|
267
|
+
loadError = e
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
localFileExisted = existsSync(
|
|
271
|
+
join(__dirname, 'sandhi.linux-riscv64-gnu.node')
|
|
272
|
+
)
|
|
273
|
+
try {
|
|
274
|
+
if (localFileExisted) {
|
|
275
|
+
nativeBinding = require('./sandhi.linux-riscv64-gnu.node')
|
|
276
|
+
} else {
|
|
277
|
+
nativeBinding = require('@anvailabs/sandhi-linux-riscv64-gnu')
|
|
278
|
+
}
|
|
279
|
+
} catch (e) {
|
|
280
|
+
loadError = e
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
break
|
|
284
|
+
case 's390x':
|
|
285
|
+
localFileExisted = existsSync(
|
|
286
|
+
join(__dirname, 'sandhi.linux-s390x-gnu.node')
|
|
287
|
+
)
|
|
288
|
+
try {
|
|
289
|
+
if (localFileExisted) {
|
|
290
|
+
nativeBinding = require('./sandhi.linux-s390x-gnu.node')
|
|
291
|
+
} else {
|
|
292
|
+
nativeBinding = require('@anvailabs/sandhi-linux-s390x-gnu')
|
|
293
|
+
}
|
|
294
|
+
} catch (e) {
|
|
295
|
+
loadError = e
|
|
296
|
+
}
|
|
297
|
+
break
|
|
298
|
+
default:
|
|
299
|
+
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
|
300
|
+
}
|
|
301
|
+
break
|
|
302
|
+
default:
|
|
303
|
+
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!nativeBinding) {
|
|
307
|
+
if (loadError) {
|
|
308
|
+
throw loadError
|
|
309
|
+
}
|
|
310
|
+
throw new Error(`Failed to load native binding`)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const { ProviderRuntime, TypedProvider, TypedEventStream, wireContractVersion, chatContractVersion, chatContractMinor, providerSpecJson, chatContractSchemaJson, providerDescriptorJson, providerModelsJson, parseUsage, Gateway } = nativeBinding
|
|
314
|
+
|
|
315
|
+
module.exports.ProviderRuntime = ProviderRuntime
|
|
316
|
+
module.exports.TypedProvider = TypedProvider
|
|
317
|
+
module.exports.TypedEventStream = TypedEventStream
|
|
318
|
+
module.exports.wireContractVersion = wireContractVersion
|
|
319
|
+
module.exports.chatContractVersion = chatContractVersion
|
|
320
|
+
module.exports.chatContractMinor = chatContractMinor
|
|
321
|
+
module.exports.providerSpecJson = providerSpecJson
|
|
322
|
+
module.exports.chatContractSchemaJson = chatContractSchemaJson
|
|
323
|
+
module.exports.providerDescriptorJson = providerDescriptorJson
|
|
324
|
+
module.exports.providerModelsJson = providerModelsJson
|
|
325
|
+
module.exports.parseUsage = parseUsage
|
|
326
|
+
module.exports.Gateway = Gateway
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@anvailabs/sandhi-darwin-arm64",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"main": "sandhi.darwin-arm64.node",
|
|
5
|
+
"files": [
|
|
6
|
+
"sandhi.darwin-arm64.node"
|
|
7
|
+
],
|
|
8
|
+
"description": "Platform binary for @anvailabs/sandhi (darwin-arm64)",
|
|
9
|
+
"license": "Apache-2.0",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">= 10"
|
|
12
|
+
},
|
|
13
|
+
"cpu": [
|
|
14
|
+
"arm64"
|
|
15
|
+
],
|
|
16
|
+
"os": [
|
|
17
|
+
"darwin"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@anvailabs/sandhi-linux-x64-gnu",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"main": "sandhi.linux-x64-gnu.node",
|
|
5
|
+
"files": [
|
|
6
|
+
"sandhi.linux-x64-gnu.node"
|
|
7
|
+
],
|
|
8
|
+
"description": "Platform binary for @anvailabs/sandhi (linux-x64-gnu)",
|
|
9
|
+
"license": "Apache-2.0",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">= 10"
|
|
12
|
+
},
|
|
13
|
+
"cpu": [
|
|
14
|
+
"x64"
|
|
15
|
+
],
|
|
16
|
+
"os": [
|
|
17
|
+
"linux"
|
|
18
|
+
],
|
|
19
|
+
"libc": [
|
|
20
|
+
"glibc"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@anvailabs/sandhi",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Sandhi — the metering layer for AI agents (Node binding): in-process virtual keys, budgets, and neutral usage-event metering.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": "https://github.com/anvai-labs/sandhi",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"llm",
|
|
9
|
+
"gateway",
|
|
10
|
+
"usage",
|
|
11
|
+
"metering",
|
|
12
|
+
"attribution",
|
|
13
|
+
"ai"
|
|
14
|
+
],
|
|
15
|
+
"main": "sandhi.js",
|
|
16
|
+
"types": "sandhi.d.ts",
|
|
17
|
+
"napi": {
|
|
18
|
+
"name": "sandhi",
|
|
19
|
+
"triples": {
|
|
20
|
+
"defaults": true,
|
|
21
|
+
"additional": [
|
|
22
|
+
"aarch64-apple-darwin",
|
|
23
|
+
"aarch64-unknown-linux-gnu"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">= 16"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "napi build --platform --release",
|
|
32
|
+
"build:debug": "napi build --platform",
|
|
33
|
+
"test": "node --test __test__/gateway.test.mjs"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@napi-rs/cli": "^2.18.4"
|
|
37
|
+
},
|
|
38
|
+
"optionalDependencies": {
|
|
39
|
+
"@anvailabs/sandhi-win32-x64-msvc": "0.5.0",
|
|
40
|
+
"@anvailabs/sandhi-darwin-x64": "0.5.0",
|
|
41
|
+
"@anvailabs/sandhi-linux-x64-gnu": "0.5.0",
|
|
42
|
+
"@anvailabs/sandhi-darwin-arm64": "0.5.0",
|
|
43
|
+
"@anvailabs/sandhi-linux-arm64-gnu": "0.5.0"
|
|
44
|
+
}
|
|
45
|
+
}
|