@stigg/agent-sdk-js 0.0.0-stage → 1.0.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 +1147 -2
- package/dist/bindings/client/core/registry.d.ts +12 -0
- package/dist/bindings/client/core/registry.js +22 -0
- package/dist/bindings/client/core/types.d.ts +117 -0
- package/dist/bindings/client/core/types.js +13 -0
- package/dist/bindings/client/core/wrap-client.d.ts +6 -0
- package/dist/bindings/client/core/wrap-client.js +258 -0
- package/dist/bindings/client/gateways/litellm/litellm-recipe.d.ts +17 -0
- package/dist/bindings/client/gateways/litellm/litellm-recipe.js +171 -0
- package/dist/bindings/client/gateways/litellm/types.d.ts +30 -0
- package/dist/bindings/client/gateways/litellm/types.js +2 -0
- package/dist/bindings/client/gateways/openrouter/openrouter-recipe.d.ts +15 -0
- package/dist/bindings/client/gateways/openrouter/openrouter-recipe.js +141 -0
- package/dist/bindings/client/gateways/openrouter/types.d.ts +29 -0
- package/dist/bindings/client/gateways/openrouter/types.js +2 -0
- package/dist/bindings/client/providers/anthropic/anthropic-recipe.d.ts +34 -0
- package/dist/bindings/client/providers/anthropic/anthropic-recipe.js +91 -0
- package/dist/bindings/client/providers/gemini/gemini-recipe.d.ts +54 -0
- package/dist/bindings/client/providers/gemini/gemini-recipe.js +243 -0
- package/dist/bindings/client/providers/openai/openai-recipe.d.ts +43 -0
- package/dist/bindings/client/providers/openai/openai-recipe.js +80 -0
- package/dist/bindings/client/providers/typesafe/typesafe-recipe.d.ts +33 -0
- package/dist/bindings/client/providers/typesafe/typesafe-recipe.js +73 -0
- package/dist/bindings/proxy/headers.d.ts +6 -0
- package/dist/bindings/proxy/headers.js +12 -0
- package/dist/bindings/proxy/index.d.ts +21 -0
- package/dist/bindings/proxy/index.js +18 -0
- package/dist/bindings/proxy/types.d.ts +8 -0
- package/dist/bindings/proxy/types.js +2 -0
- package/dist/bindings/shared/estimate.d.ts +27 -0
- package/dist/bindings/shared/estimate.js +38 -0
- package/dist/bindings/shared/meter-stream.d.ts +18 -0
- package/dist/bindings/shared/meter-stream.js +151 -0
- package/dist/bindings/shared/token-usage.d.ts +148 -0
- package/dist/bindings/shared/token-usage.js +414 -0
- package/dist/bindings/shared/types.d.ts +79 -0
- package/dist/bindings/shared/types.js +2 -0
- package/dist/bindings/shared/weighted-metrics.d.ts +37 -0
- package/dist/bindings/shared/weighted-metrics.js +44 -0
- package/dist/config.d.ts +136 -0
- package/dist/config.js +97 -0
- package/dist/core/context.d.ts +151 -0
- package/dist/core/context.js +154 -0
- package/dist/core/dimensions.d.ts +22 -0
- package/dist/core/dimensions.js +47 -0
- package/dist/core/hooks.d.ts +21 -0
- package/dist/core/hooks.js +47 -0
- package/dist/core/session.d.ts +161 -0
- package/dist/core/session.js +403 -0
- package/dist/decision-text.d.ts +15 -0
- package/dist/decision-text.js +54 -0
- package/dist/engine/composite-engine.d.ts +72 -0
- package/dist/engine/composite-engine.js +135 -0
- package/dist/engine/create-engine.d.ts +16 -0
- package/dist/engine/create-engine.js +93 -0
- package/dist/engine/decision.d.ts +37 -0
- package/dist/engine/decision.js +128 -0
- package/dist/engine/engine.d.ts +166 -0
- package/dist/engine/engine.js +11 -0
- package/dist/engine/node-sdk-engine.d.ts +125 -0
- package/dist/engine/node-sdk-engine.js +262 -0
- package/dist/engine/report.d.ts +106 -0
- package/dist/engine/report.js +185 -0
- package/dist/engine/rest-governance-engine.d.ts +47 -0
- package/dist/engine/rest-governance-engine.js +220 -0
- package/dist/errors.d.ts +78 -0
- package/dist/errors.js +151 -0
- package/dist/headers.d.ts +27 -0
- package/dist/headers.js +63 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +85 -0
- package/dist/internal.d.ts +33 -0
- package/dist/internal.js +81 -0
- package/dist/logger.d.ts +15 -0
- package/dist/logger.js +20 -0
- package/dist/sdk-identity.d.ts +36 -0
- package/dist/sdk-identity.js +43 -0
- package/dist/stigg-agent-sdk.d.ts +242 -0
- package/dist/stigg-agent-sdk.js +594 -0
- package/dist/types.d.ts +531 -0
- package/dist/types.js +97 -0
- package/package.json +80 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The vendor's usage object, reduced to something an event can carry.
|
|
4
|
+
*
|
|
5
|
+
* Stigg dimensions are a flat map of scalars, so a nested object has to be flattened or dropped -
|
|
6
|
+
* there is no third option, and stringifying a subtree would put something no meter can aggregate
|
|
7
|
+
* into a field a meter is meant to read.
|
|
8
|
+
*
|
|
9
|
+
* Everything here is a limit rather than a translation, because the input is not ours: a vendor
|
|
10
|
+
* adds fields whenever it likes, and this runs on every metered call. Depth, count and type are
|
|
11
|
+
* all capped so a response that grows a large nested block cannot grow the event with it.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.RAW_PREFIX = void 0;
|
|
15
|
+
exports.flattenRaw = flattenRaw;
|
|
16
|
+
/** Prefix for everything read off the vendor's object, so it can never shadow a metric. */
|
|
17
|
+
exports.RAW_PREFIX = 'raw.';
|
|
18
|
+
const MAX_DEPTH = 2;
|
|
19
|
+
const MAX_KEYS = 32;
|
|
20
|
+
const isScalar = (value) => typeof value === 'string' || typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value));
|
|
21
|
+
/**
|
|
22
|
+
* The vendor's own usage block as event dimensions, `raw.`-prefixed and flattened with dotted keys.
|
|
23
|
+
*
|
|
24
|
+
* Fractions are kept: an event states what the vendor said and a meter is free to sum decimals,
|
|
25
|
+
* which is the one place a gateway's `cost` can be reported honestly. `null`, `undefined`, arrays
|
|
26
|
+
* and anything past the depth cap are dropped rather than coerced - a zero that was never reported
|
|
27
|
+
* is worse than a counter that is absent.
|
|
28
|
+
*/
|
|
29
|
+
function flattenRaw(raw) {
|
|
30
|
+
if (!raw)
|
|
31
|
+
return {};
|
|
32
|
+
const flat = {};
|
|
33
|
+
const walk = (node, path, depth) => {
|
|
34
|
+
for (const [key, value] of Object.entries(node)) {
|
|
35
|
+
if (Object.keys(flat).length >= MAX_KEYS)
|
|
36
|
+
return;
|
|
37
|
+
const name = path ? `${path}.${key}` : key;
|
|
38
|
+
if (isScalar(value))
|
|
39
|
+
flat[`${exports.RAW_PREFIX}${name}`] = value;
|
|
40
|
+
else if (depth < MAX_DEPTH && value && typeof value === 'object' && !Array.isArray(value)) {
|
|
41
|
+
walk(value, name, depth + 1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
walk(raw, '', 0);
|
|
46
|
+
return flat;
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running an application's own callbacks, where their failure must not become the caller's.
|
|
3
|
+
*
|
|
4
|
+
* Hooks are instrumentation. One that throws has told us something went wrong with the watching,
|
|
5
|
+
* not with the work - so it cannot be allowed to fail a decision, flip a delivered ingest, or
|
|
6
|
+
* escape a promise the contract says never rejects. It cannot be ignored either, which is what a
|
|
7
|
+
* bare `try {} catch {}` amounts to.
|
|
8
|
+
*
|
|
9
|
+
* Shared, because every path that calls one has to get this right the same way, and the ones that
|
|
10
|
+
* reimplemented it are exactly the ones that got it wrong.
|
|
11
|
+
*/
|
|
12
|
+
/** Whether a callback typed `=> void` actually handed back something to wait on. */
|
|
13
|
+
export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
|
|
14
|
+
/**
|
|
15
|
+
* Invokes a callback and routes its failure to `onFailure`, synchronous or not.
|
|
16
|
+
*
|
|
17
|
+
* The types say `void`, but nothing stops an application writing `async onIngest` - and a
|
|
18
|
+
* rejection there would otherwise surface as an unhandled one, which on Node's default takes the
|
|
19
|
+
* process down over instrumentation.
|
|
20
|
+
*/
|
|
21
|
+
export declare function safely(invoke: () => unknown, onFailure: (err: unknown) => void): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Running an application's own callbacks, where their failure must not become the caller's.
|
|
4
|
+
*
|
|
5
|
+
* Hooks are instrumentation. One that throws has told us something went wrong with the watching,
|
|
6
|
+
* not with the work - so it cannot be allowed to fail a decision, flip a delivered ingest, or
|
|
7
|
+
* escape a promise the contract says never rejects. It cannot be ignored either, which is what a
|
|
8
|
+
* bare `try {} catch {}` amounts to.
|
|
9
|
+
*
|
|
10
|
+
* Shared, because every path that calls one has to get this right the same way, and the ones that
|
|
11
|
+
* reimplemented it are exactly the ones that got it wrong.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.isThenable = isThenable;
|
|
15
|
+
exports.safely = safely;
|
|
16
|
+
/** Whether a callback typed `=> void` actually handed back something to wait on. */
|
|
17
|
+
function isThenable(value) {
|
|
18
|
+
return typeof value?.then === 'function';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Invokes a callback and routes its failure to `onFailure`, synchronous or not.
|
|
22
|
+
*
|
|
23
|
+
* The types say `void`, but nothing stops an application writing `async onIngest` - and a
|
|
24
|
+
* rejection there would otherwise surface as an unhandled one, which on Node's default takes the
|
|
25
|
+
* process down over instrumentation.
|
|
26
|
+
*/
|
|
27
|
+
function safely(invoke, onFailure) {
|
|
28
|
+
// The reporter is the application's code too - an `onError` that throws, a logger that throws
|
|
29
|
+
// while saying so. Losing the report is the last acceptable failure here; letting it escape
|
|
30
|
+
// would fail the work the callback was only watching.
|
|
31
|
+
const report = (err) => {
|
|
32
|
+
try {
|
|
33
|
+
onFailure(err);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Nowhere left to report to.
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
const returned = invoke();
|
|
41
|
+
if (isThenable(returned))
|
|
42
|
+
void Promise.resolve(returned).catch(report);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
report(err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { EngineError } from '../errors';
|
|
2
|
+
import { type GovernanceHeaders } from '../headers';
|
|
3
|
+
import { type ResolvedContext } from './context';
|
|
4
|
+
import type { GovernanceEngine } from '../engine/engine';
|
|
5
|
+
import type { ErrorContext } from '../config';
|
|
6
|
+
import type { Logger } from '../logger';
|
|
7
|
+
import { type GovernanceDecision, type IngestResult, type Measurement, type Metrics, type TokenUsage } from '../types';
|
|
8
|
+
/** Everything the session reports back while it works. Set once, at `wrap`. */
|
|
9
|
+
export type SessionHooks = {
|
|
10
|
+
/** Every check this client makes. The whole decision, already formatted as `.summary`. */
|
|
11
|
+
onDecision?: (decision: GovernanceDecision) => void;
|
|
12
|
+
/** Every ingest this client makes - including the ones that failed. */
|
|
13
|
+
onIngest?: (result: IngestResult) => void;
|
|
14
|
+
};
|
|
15
|
+
export type SessionDeps = {
|
|
16
|
+
/**
|
|
17
|
+
* Resolved at the point of use, not at construction.
|
|
18
|
+
*
|
|
19
|
+
* Building the engine is a network round trip - a probe that decides which transport serves
|
|
20
|
+
* which operation - and `wrap` is a setup call that reads as instant. So it starts that work
|
|
21
|
+
* and hands the session the promise: the first call that needs an engine awaits it, and every
|
|
22
|
+
* call after that gets one already resolved. The latency moves to the first request rather
|
|
23
|
+
* than disappearing.
|
|
24
|
+
*/
|
|
25
|
+
engine: () => Promise<GovernanceEngine>;
|
|
26
|
+
logger: Logger;
|
|
27
|
+
onError: (error: EngineError, context: ErrorContext) => void;
|
|
28
|
+
hooks: SessionHooks;
|
|
29
|
+
};
|
|
30
|
+
/** What a client did, for whoever wants to print it. Spans every context it served. */
|
|
31
|
+
export type GovernanceSummary = {
|
|
32
|
+
checks: number;
|
|
33
|
+
ingested: number;
|
|
34
|
+
/**
|
|
35
|
+
* Everything this client reported, summed per feature id - what it sent, not what it was billed.
|
|
36
|
+
*
|
|
37
|
+
* Under `'sync'` Stigg says whether a delivery was a replay, and a replay is left out: it
|
|
38
|
+
* consumed nothing. Under `'async'` it cannot say, so a retry Stigg deduplicated still counts
|
|
39
|
+
* here. Stigg remains the record of what was actually charged; this is the client's own tally.
|
|
40
|
+
*/
|
|
41
|
+
reported: Metrics;
|
|
42
|
+
ingestFailures: number;
|
|
43
|
+
lastDecision?: GovernanceDecision;
|
|
44
|
+
};
|
|
45
|
+
export type IngestOptions = {
|
|
46
|
+
/** Overrides the context's own name for this one report - the vendor method that was gated. */
|
|
47
|
+
event?: {
|
|
48
|
+
name: string;
|
|
49
|
+
};
|
|
50
|
+
/** The vendor's usage object, for the event to carry. Ignored under `'sync'`. */
|
|
51
|
+
raw?: Record<string, unknown>;
|
|
52
|
+
/**
|
|
53
|
+
* What deduplicates this report. Overrides the context's own, and is what a caller reaches for
|
|
54
|
+
* when one logical turn is reported twice - a retry, or a resumed stream.
|
|
55
|
+
*/
|
|
56
|
+
idempotencyKey?: string;
|
|
57
|
+
};
|
|
58
|
+
export declare class GovernanceSession {
|
|
59
|
+
private readonly deps;
|
|
60
|
+
private readonly pending;
|
|
61
|
+
private readonly reported;
|
|
62
|
+
private checks;
|
|
63
|
+
private ingested;
|
|
64
|
+
private ingestFailures;
|
|
65
|
+
private step;
|
|
66
|
+
private decision?;
|
|
67
|
+
/** The unmapped-feature warning is said once per client, not once per call. */
|
|
68
|
+
private warnedUnmapped;
|
|
69
|
+
constructor(deps: SessionDeps);
|
|
70
|
+
get lastDecision(): GovernanceDecision | undefined;
|
|
71
|
+
/** The attribution to send downstream when part of the work happens in another process. */
|
|
72
|
+
headers(context: ResolvedContext): GovernanceHeaders;
|
|
73
|
+
/**
|
|
74
|
+
* Primes the cache that actually serves these checks, so the calls that follow read local state.
|
|
75
|
+
*
|
|
76
|
+
* It primes the *engine's* cache, not one here - there isn't one. The engine loads the
|
|
77
|
+
* customer's whole entitlement state, so every entitlement is warm rather than one decision,
|
|
78
|
+
* and each check is still evaluated against the amount it actually asks for. An engine with no
|
|
79
|
+
* cache (the governance REST client) implements nothing and this is a no-op, which is the
|
|
80
|
+
* honest answer: there would be nothing for the round trip to fill.
|
|
81
|
+
*
|
|
82
|
+
* Failures are logged rather than thrown: the client works either way, and the first real check
|
|
83
|
+
* applies the error policy.
|
|
84
|
+
*/
|
|
85
|
+
warmUp(context: ResolvedContext): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* The enforcement path. Returns a decision, never throws - a refusal is a commercial event the
|
|
88
|
+
* application routes on, not an exception it recovers from.
|
|
89
|
+
*/
|
|
90
|
+
check(context: ResolvedContext, requestedUsage?: number): Promise<GovernanceDecision>;
|
|
91
|
+
/** Applies the context's mapper to a token measurement. The path every binding uses. */
|
|
92
|
+
ingestTokens(context: ResolvedContext, usage: TokenUsage, options?: IngestOptions): Promise<IngestResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Ingests whatever a binding measured, through the context's mapper.
|
|
95
|
+
*
|
|
96
|
+
* The mapper runs inside the boundary that never throws, because it is the application's own
|
|
97
|
+
* function over a vendor's object and either half is free to change shape - a throw there would
|
|
98
|
+
* put an exception where the contract promises a result.
|
|
99
|
+
*/
|
|
100
|
+
ingestMeasurement(context: ResolvedContext, measurement: Measurement | TokenUsage, options?: IngestOptions): Promise<IngestResult>;
|
|
101
|
+
/**
|
|
102
|
+
* Reports one value against the entitlement this context checks.
|
|
103
|
+
*
|
|
104
|
+
* The narrow case, and the only one where the SDK knows what a report costs the budget it gates
|
|
105
|
+
* on - which is why it is also the only one that can name the feature for you.
|
|
106
|
+
*/
|
|
107
|
+
ingest(context: ResolvedContext, value: number, options?: IngestOptions): Promise<IngestResult>;
|
|
108
|
+
/**
|
|
109
|
+
* Reports named metrics directly, skipping the mapper. The path a gateway and a hand-rolled
|
|
110
|
+
* binding use, where the numbers are already in Stigg's own vocabulary.
|
|
111
|
+
*/
|
|
112
|
+
ingestMetrics(context: ResolvedContext, metrics: Metrics, options?: IngestOptions): Promise<IngestResult>;
|
|
113
|
+
/**
|
|
114
|
+
* The instrumentation path: one report per metric, all in one call where the transport has one.
|
|
115
|
+
*
|
|
116
|
+
* Never throws and never applies the error policy - the spend already happened, so failing here
|
|
117
|
+
* must not fail the caller's call. Errors go to `onError`.
|
|
118
|
+
*/
|
|
119
|
+
private send;
|
|
120
|
+
/**
|
|
121
|
+
* A binding's own failure at measuring a call that already happened.
|
|
122
|
+
*
|
|
123
|
+
* A recipe reads usage off a vendor's response, and a vendor is free to change that response -
|
|
124
|
+
* so reading it can throw. The call succeeded and the money is spent either way, which makes
|
|
125
|
+
* this the same kind of event as a failed ingest: counted, reported, and never raised at the
|
|
126
|
+
* caller, who asked the vendor for something and got it.
|
|
127
|
+
*/
|
|
128
|
+
failedToMeasure(context: ResolvedContext, err: unknown): void;
|
|
129
|
+
/** Waits for in-flight ingests, so a caller knows what was actually delivered. */
|
|
130
|
+
drain(): Promise<GovernanceSummary>;
|
|
131
|
+
summary(): GovernanceSummary;
|
|
132
|
+
/** `correlationId:n` - distinct per call, for a vendor that offered no id of its own. */
|
|
133
|
+
private nextKey;
|
|
134
|
+
/**
|
|
135
|
+
* The failure that looks like success, said once.
|
|
136
|
+
*
|
|
137
|
+
* A call gates on one capability and reports to another. Stigg only moves the one to the other
|
|
138
|
+
* when the feature is credit-backed in the catalog - and when it is not, every call still
|
|
139
|
+
* succeeds, every ingest returns `ok`, and the budget never moves. Nothing else in the system
|
|
140
|
+
* can notice: the check keeps passing because nothing was ever spent.
|
|
141
|
+
*
|
|
142
|
+
* The two modes fail the same way and can be told about it to different degrees. A usage report
|
|
143
|
+
* answers with a credit balance, so its silence is proof. An event answers with nothing at all,
|
|
144
|
+
* so the most that can be said is that nothing reported names what this call checks, and whether
|
|
145
|
+
* a meter closes that gap is configuration this process cannot see.
|
|
146
|
+
*/
|
|
147
|
+
private warnIfNothingDrains;
|
|
148
|
+
private record;
|
|
149
|
+
/**
|
|
150
|
+
* Hooks are instrumentation, so a throwing one must not reach the caller: it cannot turn an
|
|
151
|
+
* allow into a denial, flip a delivered ingest to `ok: false`, or reject a promise the contract
|
|
152
|
+
* says never rejects. The failure is reported like any other.
|
|
153
|
+
*/
|
|
154
|
+
private hook;
|
|
155
|
+
/**
|
|
156
|
+
* The last stop for a failure. `onError` is the application's own code and can throw too, and
|
|
157
|
+
* there is nowhere left to report that to, so it falls back to the logger.
|
|
158
|
+
*/
|
|
159
|
+
private reportError;
|
|
160
|
+
private failedIngest;
|
|
161
|
+
}
|