@stigg/agent-sdk-js 0.0.0-stage → 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.
Files changed (98) hide show
  1. package/README.md +1296 -2
  2. package/dist/bindings/client/core/registry.d.ts +12 -0
  3. package/dist/bindings/client/core/registry.js +22 -0
  4. package/dist/bindings/client/core/types.d.ts +117 -0
  5. package/dist/bindings/client/core/types.js +13 -0
  6. package/dist/bindings/client/core/wrap-client.d.ts +6 -0
  7. package/dist/bindings/client/core/wrap-client.js +258 -0
  8. package/dist/bindings/client/gateways/bedrock/bedrock-recipe.d.ts +17 -0
  9. package/dist/bindings/client/gateways/bedrock/bedrock-recipe.js +134 -0
  10. package/dist/bindings/client/gateways/bedrock/types.d.ts +13 -0
  11. package/dist/bindings/client/gateways/bedrock/types.js +2 -0
  12. package/dist/bindings/client/gateways/litellm/litellm-recipe.d.ts +17 -0
  13. package/dist/bindings/client/gateways/litellm/litellm-recipe.js +171 -0
  14. package/dist/bindings/client/gateways/litellm/types.d.ts +30 -0
  15. package/dist/bindings/client/gateways/litellm/types.js +2 -0
  16. package/dist/bindings/client/gateways/openrouter/openrouter-recipe.d.ts +20 -0
  17. package/dist/bindings/client/gateways/openrouter/openrouter-recipe.js +244 -0
  18. package/dist/bindings/client/gateways/openrouter/sdk-usage.d.ts +37 -0
  19. package/dist/bindings/client/gateways/openrouter/sdk-usage.js +63 -0
  20. package/dist/bindings/client/gateways/openrouter/types.d.ts +78 -0
  21. package/dist/bindings/client/gateways/openrouter/types.js +2 -0
  22. package/dist/bindings/client/providers/anthropic/anthropic-recipe.d.ts +34 -0
  23. package/dist/bindings/client/providers/anthropic/anthropic-recipe.js +169 -0
  24. package/dist/bindings/client/providers/gemini/gemini-recipe.d.ts +54 -0
  25. package/dist/bindings/client/providers/gemini/gemini-recipe.js +243 -0
  26. package/dist/bindings/client/providers/groq/groq-recipe.d.ts +20 -0
  27. package/dist/bindings/client/providers/groq/groq-recipe.js +101 -0
  28. package/dist/bindings/client/providers/mistral/mistral-recipe.d.ts +22 -0
  29. package/dist/bindings/client/providers/mistral/mistral-recipe.js +114 -0
  30. package/dist/bindings/client/providers/mistral/types.d.ts +38 -0
  31. package/dist/bindings/client/providers/mistral/types.js +2 -0
  32. package/dist/bindings/client/providers/ollama/ollama-recipe.d.ts +31 -0
  33. package/dist/bindings/client/providers/ollama/ollama-recipe.js +107 -0
  34. package/dist/bindings/client/providers/ollama/types.d.ts +24 -0
  35. package/dist/bindings/client/providers/ollama/types.js +2 -0
  36. package/dist/bindings/client/providers/openai/openai-recipe.d.ts +71 -0
  37. package/dist/bindings/client/providers/openai/openai-recipe.js +122 -0
  38. package/dist/bindings/client/providers/typesafe/typesafe-recipe.d.ts +44 -0
  39. package/dist/bindings/client/providers/typesafe/typesafe-recipe.js +83 -0
  40. package/dist/bindings/proxy/headers.d.ts +6 -0
  41. package/dist/bindings/proxy/headers.js +12 -0
  42. package/dist/bindings/proxy/index.d.ts +21 -0
  43. package/dist/bindings/proxy/index.js +18 -0
  44. package/dist/bindings/proxy/types.d.ts +8 -0
  45. package/dist/bindings/proxy/types.js +2 -0
  46. package/dist/bindings/shared/estimate.d.ts +27 -0
  47. package/dist/bindings/shared/estimate.js +38 -0
  48. package/dist/bindings/shared/meter-stream.d.ts +18 -0
  49. package/dist/bindings/shared/meter-stream.js +151 -0
  50. package/dist/bindings/shared/token-usage.d.ts +240 -0
  51. package/dist/bindings/shared/token-usage.js +574 -0
  52. package/dist/bindings/shared/types.d.ts +129 -0
  53. package/dist/bindings/shared/types.js +2 -0
  54. package/dist/bindings/shared/weighted-metrics.d.ts +37 -0
  55. package/dist/bindings/shared/weighted-metrics.js +47 -0
  56. package/dist/config.d.ts +145 -0
  57. package/dist/config.js +99 -0
  58. package/dist/core/context.d.ts +160 -0
  59. package/dist/core/context.js +221 -0
  60. package/dist/core/dimensions.d.ts +22 -0
  61. package/dist/core/dimensions.js +47 -0
  62. package/dist/core/hooks.d.ts +21 -0
  63. package/dist/core/hooks.js +47 -0
  64. package/dist/core/session.d.ts +190 -0
  65. package/dist/core/session.js +448 -0
  66. package/dist/decision-text.d.ts +15 -0
  67. package/dist/decision-text.js +54 -0
  68. package/dist/engine/composite-engine.d.ts +72 -0
  69. package/dist/engine/composite-engine.js +135 -0
  70. package/dist/engine/create-engine.d.ts +16 -0
  71. package/dist/engine/create-engine.js +93 -0
  72. package/dist/engine/decision.d.ts +42 -0
  73. package/dist/engine/decision.js +135 -0
  74. package/dist/engine/engine.d.ts +166 -0
  75. package/dist/engine/engine.js +11 -0
  76. package/dist/engine/node-sdk-engine.d.ts +125 -0
  77. package/dist/engine/node-sdk-engine.js +262 -0
  78. package/dist/engine/report.d.ts +106 -0
  79. package/dist/engine/report.js +188 -0
  80. package/dist/engine/rest-governance-engine.d.ts +47 -0
  81. package/dist/engine/rest-governance-engine.js +220 -0
  82. package/dist/errors.d.ts +86 -0
  83. package/dist/errors.js +161 -0
  84. package/dist/headers.d.ts +40 -0
  85. package/dist/headers.js +82 -0
  86. package/dist/index.d.ts +57 -0
  87. package/dist/index.js +106 -0
  88. package/dist/internal.d.ts +33 -0
  89. package/dist/internal.js +87 -0
  90. package/dist/logger.d.ts +15 -0
  91. package/dist/logger.js +20 -0
  92. package/dist/sdk-identity.d.ts +36 -0
  93. package/dist/sdk-identity.js +43 -0
  94. package/dist/stigg-agent-sdk.d.ts +300 -0
  95. package/dist/stigg-agent-sdk.js +656 -0
  96. package/dist/types.d.ts +642 -0
  97. package/dist/types.js +147 -0
  98. package/package.json +89 -3
@@ -0,0 +1,642 @@
1
+ /**
2
+ * The vocabulary shared by the core, the engines and every binding, and the argument list of
3
+ * every call on the public surface.
4
+ *
5
+ * Terms follow Stigg governance rather than the draft design notes: provision/archive (not
6
+ * register/de-register), ingest (not report), assignment (not budget), dimensions (not tags).
7
+ *
8
+ * The imports below are all `import type`, so they are erased at compile time: this file pulls in
9
+ * no module at runtime and stays the leaf everything else can depend on.
10
+ */
11
+ import { EngineError } from './errors';
12
+ import type { StiggContext } from './core/context';
13
+ import type { SessionSummary, SessionHooks } from './core/session';
14
+ import type { GovernanceHeaders } from './headers';
15
+ import type { ClientRecipe } from './bindings/client/core/types';
16
+ import type { ObservedVendor } from './bindings/shared/token-usage';
17
+ import type { EventIngest } from './engine/engine';
18
+ /**
19
+ * What a budget is measured against. Exactly one of the two - the same mutual exclusion the
20
+ * Stigg check endpoint enforces, expressed so the compiler catches it instead of the API.
21
+ */
22
+ export type Entitlement = {
23
+ currencyId: string;
24
+ featureId?: never;
25
+ } | {
26
+ featureId: string;
27
+ currencyId?: never;
28
+ };
29
+ /**
30
+ * Which of Stigg's two ingest APIs a run reports through.
31
+ *
32
+ * `'sync'` is `usage.report`/`reportUsageBulk`: a fixed DTO of `featureId` and a whole-number
33
+ * `value` per metric, aggregated the moment it lands, so the next check sees it. It costs more
34
+ * latency and has the lower rate limit.
35
+ *
36
+ * `'async'` is `events.report`/`reportEvent`: the call is reported as one event and Stigg's own
37
+ * meters turn it into usage, seconds later. Low latency, a far higher rate limit, and nothing
38
+ * meters at all until a **raw-events meter** is configured for the event name on Stigg's side.
39
+ */
40
+ export type IngestMode = 'sync' | 'async';
41
+ /** The id to send as `featureId`/`currencyId`, whichever kind the entitlement is. */
42
+ export declare const entitlementId: (entitlement: Entitlement) => string;
43
+ /** 'credit' targets read the customer's credit pool; 'feature' targets need an entitling subscription. */
44
+ export declare const entitlementKind: (entitlement: Entitlement) => "credit" | "feature";
45
+ /**
46
+ * The entitlement as every Stigg endpoint takes it: one of two mutually exclusive id fields,
47
+ * ready to spread.
48
+ *
49
+ * ```ts
50
+ * await client.v1Beta.customers.entitlements.check(customerId, { ...entitlementRef(entitlement), requestedUsage });
51
+ * ```
52
+ *
53
+ * Narrowing on the property rather than through {@link entitlementKind} is the point: a function
54
+ * call cannot narrow a union, so every hand-written version of this needed a `!` on each branch -
55
+ * an assertion that the union already guarantees and that would survive the union changing.
56
+ */
57
+ export declare const entitlementRef: (entitlement: Entitlement) => {
58
+ currencyId: string;
59
+ } | {
60
+ featureId: string;
61
+ };
62
+ /**
63
+ * Why an entitlement cannot be billed against, if one was named at all.
64
+ *
65
+ * Naming none is legal: it is how a caller asks for metering without enforcement, so undefined is
66
+ * not an error here. The one place an entitlement is genuinely required is an assignment, which is
67
+ * written over `(entity x entitlement)` - and `register` refuses that case itself, because only it
68
+ * knows the agent to name in the message.
69
+ *
70
+ * What stays refused is an entitlement that is named and names nothing. The union permits
71
+ * `{ currencyId: '' }`, and an empty currency is falsy - so it classifies as a feature and every
72
+ * branch downstream then asks for a `featureId` that is not there. An empty id is a typo rather
73
+ * than an intention, and letting it through meters a run against nothing while it still looks
74
+ * enforced.
75
+ */
76
+ export declare function entitlementError(entitlement: Entitlement | undefined): Error | undefined;
77
+ /**
78
+ * A provider-neutral token measurement. Every client/orchestrator binding normalizes into this
79
+ * before anything touches Stigg, so the mapper below is written once for all of them.
80
+ */
81
+ export type TokenUsage = {
82
+ input: number;
83
+ output: number;
84
+ cacheRead: number;
85
+ cacheCreate: number;
86
+ /**
87
+ * What the vendor says the call cost in total, when it says anything.
88
+ *
89
+ * Apart from the four above, and deliberately so: those are disjoint and add up to the call,
90
+ * while this one *overlaps all of them*. It is here because a mapper that wants one number
91
+ * should be able to use the vendor's own rather than re-adding the parts and hoping the
92
+ * arithmetic matches - the two can differ, and when they do the vendor's is the one that gets
93
+ * invoiced.
94
+ *
95
+ * Read from the response where the vendor publishes a total, and summed from the four where it
96
+ * publishes none and they are disjoint. Undefined where neither is true, which is the honest
97
+ * answer rather than a guess: see `COMPUTED_TOTALS`.
98
+ */
99
+ total?: number;
100
+ };
101
+ export declare const emptyTokenUsage: () => TokenUsage;
102
+ /**
103
+ * What one completed call reports: feature id -> whole-number value. One report each.
104
+ *
105
+ * A record rather than a list of `{featureId, value}` pairs, so the same feature cannot appear
106
+ * twice - two reports of one feature in one call would collide on idempotency keys and silently
107
+ * lose one of them.
108
+ */
109
+ export type Metrics = Record<string, number>;
110
+ /**
111
+ * Turns what a binding observed into the metrics to report.
112
+ *
113
+ * Defaults to the vendor's own: whatever names and values came back on the response. Supply your
114
+ * own to report something else - see `unitsPerTokens` for the one-number case, and each client
115
+ * recipe for what that vendor meters by default.
116
+ */
117
+ export type MetricsMapper = (measurement: Measurement) => Metrics;
118
+ /**
119
+ * What one completed call is worth, as the binding that observed it saw it.
120
+ *
121
+ * Two views of one call, and both are useful. `metrics` is what the vendor said, in the vendor's
122
+ * own words - reported as-is, so what lands in Stigg reconciles against what the vendor bills.
123
+ * `usage` is the same call flattened into four comparable counters, which is what a mapper writes
124
+ * against when it wants one number or one vocabulary across vendors.
125
+ */
126
+ export type Measurement = {
127
+ /** The vendor's own field names and values, verbatim. What gets reported by default. */
128
+ metrics?: Metrics;
129
+ /** The same call, normalized and non-overlapping. Context for writing your own mapper. */
130
+ usage?: TokenUsage;
131
+ /**
132
+ * The vendor's usage object as it arrived, before any counter was picked out of it.
133
+ *
134
+ * Carried only so an event can state what the vendor said in full - it rides the event's
135
+ * dimensions under a `raw.` prefix, flattened to scalars, and a meter can then aggregate a
136
+ * counter this SDK does not know about yet. Never reported as a metric: a feature id has to be
137
+ * something the customer's catalog knows, and a vendor's changelog must not be able to invent
138
+ * one. The usage block only - never a response body, so no prompt or completion text leaves
139
+ * the process.
140
+ */
141
+ raw?: Record<string, unknown>;
142
+ /** Per-call attribution, merged over the run's own dimensions. e.g. the model that served. */
143
+ dimensions?: Record<string, string>;
144
+ /**
145
+ * Set when a counter here is this SDK's estimate rather than something the vendor stated.
146
+ *
147
+ * Only one thing sets it today: a stream the consumer abandoned. Anthropic states the output
148
+ * count on the event that ends the stream, so a reply stopped halfway never states one - and
149
+ * reporting nothing for text the customer read, and the vendor billed, is a hole rather than a
150
+ * conservative answer.
151
+ *
152
+ * Carried on the measurement rather than folded into a dimension so it reaches the run's
153
+ * `usageMapper`, which is what makes it a decision rather than a surprise: a mapper that would
154
+ * rather bill nothing than bill an estimate can branch on this and return `{}`.
155
+ */
156
+ estimated?: true;
157
+ /** The vendor's own request id, when it has one that is stable across its retries. */
158
+ idempotencyKey?: string;
159
+ /**
160
+ * Why this measurement does not cover everything the vendor billed, when it does not.
161
+ *
162
+ * For the case where usage exists that the response never carried - not a disagreement between
163
+ * two numbers the vendor gave, which is data the caller can judge for themselves, but work
164
+ * that was charged for and never reported. What is here is still ingested: a short measurement
165
+ * beats none. The reason is then raised through the same channel a failed read uses, so
166
+ * `onError` fires and `summary().ingestFailures` counts it, rather than the shortfall being
167
+ * something a reader of the docs was supposed to have known about.
168
+ */
169
+ incomplete?: string;
170
+ };
171
+ /** What a run reports when nobody says otherwise: exactly what the vendor stated. */
172
+ export declare const vendorMetrics: MetricsMapper;
173
+ /** A binding may report either shape; `TokenUsage` is the common case and stays terse. */
174
+ export declare const toMeasurement: (value: TokenUsage | Measurement) => Measurement;
175
+ /**
176
+ * The amount to report when this run is attributed to a governance entity.
177
+ *
178
+ * Governance counts in whole units and cannot be made to count otherwise from here: its ingest
179
+ * schema is `z.number().int()`, and the counter behind it is a Redis `HINCRBY`, which takes an
180
+ * integer delta. A fractional amount is rejected at that boundary - the wallet still moves, the
181
+ * per-entity counter does not, and nothing in the response says so. An agent's budget silently
182
+ * stops advancing while its spend carries on.
183
+ *
184
+ * So an attributed run rounds up, and only an attributed run: a run that names no entity reports
185
+ * what its mapper produced, because the wallet takes fractions and nothing downstream of it
186
+ * counts in whole units.
187
+ *
188
+ * Rounding *up* rather than to nearest, because a call that happened must cost something: at any
189
+ * sane denomination most calls are worth less than one unit, and rounding to nearest would bill
190
+ * nothing for nearly all of them.
191
+ *
192
+ * TODO: delete this, and the call sites that use it, once governance counts fractional usage.
193
+ * The platform fix is to scale internally - store micro-units and accept fractions at the API
194
+ * boundary - which keeps `HINCRBY` working and is the only way an agent budget can be
195
+ * denominated finely enough to price a call worth a tenth of a cent. Until then this is a
196
+ * deliberate overcharge on small calls, which is why it is announced rather than silent.
197
+ */
198
+ export declare const wholeUnitsForGovernance: (value: number) => number;
199
+ /**
200
+ * Why a measured value cannot be reported, if it cannot.
201
+ *
202
+ * Zero is not an error - a cached refusal or an empty stream is a normal turn - but a mapper
203
+ * dividing by an unset config returns NaN or Infinity, and reading those as "nothing was used"
204
+ * meters the run at zero while every call still reports success. Those are refused everywhere.
205
+ *
206
+ * A fraction is refused on one path only, and the rule is the endpoint's rather than this SDK's:
207
+ *
208
+ * - **The wallet** takes them. `credits.consumption.consume` and its async twin accept a
209
+ * fractional `amount`, and balances are fractional too - which they have to be, or a currency
210
+ * could never be denominated finely enough to price a call worth a tenth of a cent.
211
+ * - **An event** takes them. The number rides as a dimension and a meter sums what it finds,
212
+ * which is the only way a gateway's `cost` reports honestly.
213
+ * - **The usage report** does not: `POST /api/v1/usage` answers
214
+ * `400 Expected integer, received float`. That is the one case refused here, where the caller
215
+ * can still see which metric it was - metering failures are swallowed into `onError`, so a 400
216
+ * three frames down is a silent gap rather than an error anyone reads.
217
+ *
218
+ * Shared, because every path that reports usage has to answer this the same way.
219
+ */
220
+ export declare function usageValueError(value: number, mode?: IngestMode, drawsWallet?: boolean): EngineError | undefined;
221
+ /**
222
+ * One metric, summed from every token class and divided by the unit size.
223
+ *
224
+ * The mapper for an application that prices tokens itself. Named for what it produces - units
225
+ * under an id - rather than for credits, because it does not deduct any: it reports a number, and
226
+ * that number draws the wallet only when the id happens to name a credit currency. Which it is
227
+ * decides the whole ingest path, so a mapper that claimed to know was the wrong place to look.
228
+ *
229
+ * **Prefer reporting the raw counters and converting in Stigg.** A consumption mapping on the plan
230
+ * does the same arithmetic where a pricing change does not need a deploy, and it is the only place
231
+ * that can weight cache reads cheaper. Reach for this when the application deliberately prices
232
+ * itself.
233
+ *
234
+ * The division is reported as it comes out. A credit currency takes fractions, so a call worth
235
+ * four fifths of a unit costs four fifths of one - rounding it up here would be this SDK's
236
+ * arithmetic on someone else's price. A **feature** is the exception: `POST /api/v1/usage` takes
237
+ * integers, so pick a `tokensPerUnit` that divides, or round in your own mapper.
238
+ *
239
+ * @param id The Stigg feature id or credit currency id to report under.
240
+ * @param tokensPerUnit How many tokens make one unit.
241
+ */
242
+ export declare const unitsPerTokens: (id: string, tokensPerUnit?: number) => MetricsMapper;
243
+ /** What the caller should do about a denial. A refusal is a commercial event, not an error. */
244
+ export type Remedy = 'top-up' | 'raise-budget' | 'subscribe' | 'retry';
245
+ /**
246
+ * Which client answered, or - for 'cache', 'policy' and 'unenforced' - that nothing was asked.
247
+ *
248
+ * `'unenforced'` is the one that could not have been asked: the run named no entitlement, so there
249
+ * is no budget to consult and every decision it hands back is an allow.
250
+ */
251
+ export type DecisionSource = 'rest' | 'node-sdk' | 'cache' | 'policy' | 'unenforced';
252
+ /**
253
+ * The structured refusal. Returned, never thrown, everywhere the caller has a return path:
254
+ * the denial path is a revenue surface (top-up, upgrade), so it must be inspectable.
255
+ */
256
+ export type AccessDecision = {
257
+ hasAccess: boolean;
258
+ /** Which entity blocked - a governance node, or the customer when governance is not in play. */
259
+ deniedBy?: string;
260
+ /**
261
+ * Why it was refused. Stigg's `accessDeniedReason` verbatim, except where `locallyDerived` says
262
+ * the SDK reached the verdict itself. `InsufficientCredits` and `BudgetExceeded` differ.
263
+ */
264
+ reason?: string;
265
+ remedy?: Remedy;
266
+ /** Tightest remaining budget across the pool and the chain. Undefined when nothing is bounded. */
267
+ remaining?: number;
268
+ usageLimit?: number | null;
269
+ currentUsage?: number;
270
+ source: DecisionSource;
271
+ checkedAt: number;
272
+ /** True when the engine served this from its own fallback rather than live state. */
273
+ isFallback?: boolean;
274
+ /**
275
+ * True when the SDK decided this rather than Stigg. A credit check reads the balance and
276
+ * compares it against the requested amount here, because the credit read takes no requested
277
+ * usage to adjudicate - so the balance is Stigg's, and the refusal is the SDK's.
278
+ *
279
+ * Distinct from `isFallback`: that one says the state was stale, this one says the state was
280
+ * live and the arithmetic was ours. Both can be true, and `strictCheck: false` on the engine's
281
+ * capabilities is the same admission at the level above.
282
+ */
283
+ locallyDerived?: boolean;
284
+ /** One line for a log: every field that matters, already formatted. */
285
+ summary: string;
286
+ /** What to tell the user. A refusal is a commercial event, so it carries its own remedy. */
287
+ message: string;
288
+ };
289
+ /**
290
+ * The outcome of one ingest - which is now one report per metric, so the result is per metric too.
291
+ * Never a rejection: metering must not break the caller's run.
292
+ */
293
+ export type IngestResult = {
294
+ /** True only when every metric landed. One failure among four is not a success. */
295
+ ok: boolean;
296
+ /** What was reported, by feature id. Empty when the call measured nothing at all. */
297
+ reported: Metrics;
298
+ /**
299
+ * The key the report deduped on. Under `'sync'` each metric is keyed
300
+ * `${idempotencyKey}:${featureId}`, so they dedupe independently; under `'async'` the whole
301
+ * turn is one event under this key alone.
302
+ */
303
+ idempotencyKey: string;
304
+ /** Which API this went through, because the two answer with different amounts of truth. */
305
+ mode: IngestMode;
306
+ /**
307
+ * Stigg reported nothing consumed for every metric: these keys were already ingested.
308
+ * **Undefined under `'async'`** - the event API answers with an empty body, so a replay is
309
+ * something Stigg does and the SDK cannot observe. Saying `false` would be a claim, not a fact.
310
+ */
311
+ replayed?: boolean;
312
+ /**
313
+ * Credits this report actually drew, when Stigg said so. Absent when the features reported are
314
+ * not credit-backed - which is also when the run's budget will never drain - and always absent
315
+ * under `'async'`, which returns no balance at all.
316
+ */
317
+ creditsConsumed?: number;
318
+ error?: unknown;
319
+ };
320
+ /**
321
+ * What a check resolves to when the engine could not answer it. A policy, not a notification:
322
+ * hooks are told what happened and cannot change it, while this decides the outcome. Ingest has
323
+ * no equivalent - a failed ingest never affects a run, it only reaches `hooks.onError`.
324
+ */
325
+ export type CheckErrorAction = 'deny' | 'allow';
326
+ /** What binding a vendor client needs: how to recognise it, what to watch, and what governs it. */
327
+ export type ClientOptions = {
328
+ /**
329
+ * Use this adapter instead of matching the client by its shape and base URL. The built-in ones
330
+ * cover Anthropic, OpenAI, OpenRouter and LiteLLM; reach for this when a vendor none of them
331
+ * identifies needs one of its own.
332
+ */
333
+ recipe?: ClientRecipe;
334
+ /** Everything this client reports back while it works. See {@link SessionHooks}. */
335
+ hooks?: SessionHooks;
336
+ /** The default context for every call through this client. Any call may override it. */
337
+ stiggContext?: StiggContext;
338
+ };
339
+ /**
340
+ * `register` - giving an agent an identity, a budget, and a place in the tree.
341
+ *
342
+ * A write, which is why it is its own call rather than something a gated call carries: a limit is
343
+ * not a field on the entity, so this is two idempotent upserts underneath - the entity, then an
344
+ * assignment over `(entity x entitlement)`. That is also why `parentId` belongs here and not on
345
+ * the entity: the hierarchy is per-entitlement.
346
+ *
347
+ * Optional throughout. Skip it and the agent has no entity - calls still work, and checks and
348
+ * usage apply to the customer instead.
349
+ */
350
+ export type AgentOptions = {
351
+ /** The durable agent. This is the entity budgets attach to. */
352
+ agentId: string;
353
+ /**
354
+ * The governance entity type to create the agent as. Required, and not defaulted: a guessed
355
+ * type pairs the agent with an attribution key nothing resolves, and the spend then lands on
356
+ * the customer looking exactly like a call that was never governed.
357
+ */
358
+ entityTypeId: string;
359
+ displayName?: string;
360
+ metadata?: Record<string, string>;
361
+ /**
362
+ * What this agent may spend, and where it sits in the tree. Omit it and `register` writes an
363
+ * identity and nothing else.
364
+ *
365
+ * Grouped rather than flat because these five are one write underneath - an assignment over
366
+ * `(entity x entitlement)` - while the fields above are the entity. See {@link AgentBudget}.
367
+ */
368
+ budget?: AgentBudget;
369
+ };
370
+ /**
371
+ * What an agent may spend, as the one write it actually is.
372
+ *
373
+ * Grouped because these are one write underneath - an assignment over `(entity x entitlement)` -
374
+ * while the fields beside them describe the entity. The grouping is what makes two illegal
375
+ * combinations unrepresentable: `apply` is one field, so there is no way to ask for both budget
376
+ * modes, and `entitlement` is required, so there is no way to write a limit against nothing.
377
+ *
378
+ * `parentId` lives here rather than on the entity because the hierarchy is **per-entitlement**:
379
+ * an agent can sit under one parent for one budget and elsewhere for another.
380
+ */
381
+ export type AgentBudget = {
382
+ /**
383
+ * Which entitlement the budget is written against - a credit currency or a metered feature.
384
+ *
385
+ * Required, because an assignment is written over `(entity x entitlement)`: a limit with
386
+ * nothing to write it against has nowhere to land.
387
+ */
388
+ entitlement: Entitlement;
389
+ /** ISO-8601 single-unit reset window, e.g. 'P1M'. Governance requires it when creating a budget. */
390
+ cadence?: string;
391
+ /** Places the agent under a parent entity. Tri-state: omit to leave it, null to make it a root. */
392
+ parentId?: string | null;
393
+ } & ({
394
+ /** The limit, written on every call. `null` clears whatever is assigned. */
395
+ limit?: number | null;
396
+ /** Writes the limit every time. The default. */
397
+ apply?: 'always';
398
+ } | {
399
+ /** The limit to seed with. Not nullable: there is nothing to clear on an agent being created. */
400
+ limit: number;
401
+ /**
402
+ * Writes the limit only when this agent has no assignment yet.
403
+ *
404
+ * Reach for it when an admin may change the budget out of band - `'always'` would overwrite
405
+ * their change on every process start.
406
+ */
407
+ apply: 'on-create';
408
+ });
409
+ export type AgentScope = {
410
+ /** The tenant. Every governance call is scoped to one customer. */
411
+ customerId: string;
412
+ };
413
+ /** `register` - the standalone call, which has no run to take the scope from. */
414
+ export type RegisterInput = AgentOptions & AgentScope;
415
+ /** `deregister` - archiving the entity. No entitlement: an entity is not archived per-entitlement. */
416
+ export type DeregisterInput = Pick<AgentScope, 'customerId'> & Pick<AgentOptions, 'agentId'>;
417
+ /**
418
+ * Every property path the shipped recipes gate, written as the overlay walks it.
419
+ *
420
+ * Paths, not leaf names. The extra field has to appear in the vendor's own parameter type or an
421
+ * object literal carrying it is an excess-property error - but matching on the last segment alone
422
+ * also typed `stiggContext` onto every *other* method that happened to share the name. `create` is
423
+ * the common one: `openai.files.create` and `openai.batches.create` are not gated by any recipe,
424
+ * so a caller who passed a context there type-checked, was never checked, and handed the vendor an
425
+ * unknown field it would forward rather than strip - the exact harm the narrow list existed to
426
+ * prevent.
427
+ *
428
+ * `messages.stream` is here without being in any recipe's `methods`: it reaches the wire through
429
+ * the gated `messages.create`, so a context given to it does arrive.
430
+ */
431
+ export type GatedPath = ['messages', 'create'] | ['messages', 'stream'] | ['chat', 'completions', 'create'] | ['responses', 'create'] | ['systemOne'] | ['models', 'generateContent'] | ['models', 'generateContentStream'] | ['chat', 'complete'] | ['chat', 'stream'] | ['chat', 'parse'] | ['chat', 'parseStream'] | ['fim', 'complete'] | ['fim', 'stream'] | ['agents', 'complete'] | ['agents', 'stream'];
432
+ /**
433
+ * Gemini's conversational surface, which is wrapped as an object in its own right: a `Chat` holds
434
+ * the models module privately, so wrapping the client never reaches these. The paths are one
435
+ * segment long because the `Chat` *is* the client there.
436
+ *
437
+ * Kept OUT of {@link GatedPath} and reached only through {@link ChatOf}, because one segment is
438
+ * the whole path here and `sendMessage` is far too common a verb to claim globally. In the
439
+ * fallback list it typed `stiggContext` onto any client that happened to have one - a Slack
440
+ * client, a queue client - and on a client that some *other* recipe matched, that was the silent
441
+ * case the narrow list exists to prevent: `wrap` succeeded, the method was never gated, and the
442
+ * context object was forwarded to the vendor as an unknown field.
443
+ */
444
+ export type ChatGatedPath = ['sendMessage'] | ['sendMessageStream'];
445
+ /**
446
+ * Ollama's two, kept out of {@link GatedPath} for exactly the reason `sendMessage` is.
447
+ *
448
+ * The client is flat - `chat` and `generate` are methods on it, with no namespace to make them
449
+ * distinctive - and those are among the most common verbs a client object can have. In the
450
+ * fallback list they would type `stiggContext` onto any object with a `chat(...)` method at all,
451
+ * on clients no recipe matches, which is the silent case that list exists to prevent: `wrap`
452
+ * succeeds, the method is never gated, and the context object is forwarded to the vendor as an
453
+ * unknown field. Reached only through {@link ChatOf}, keyed on all three markers
454
+ * `ollamaRecipe.matches` requires.
455
+ */
456
+ export type OllamaGatedPath = ['chat'] | ['generate'];
457
+ /**
458
+ * The paths to overlay when no recipe was named, given what the client actually looks like.
459
+ *
460
+ * `getHistory` is the discriminator, and it is exactly what `geminiChatRecipe.matches` requires -
461
+ * so the types promise a context on precisely the methods the runtime will gate, rather than on
462
+ * every object with a method of that name.
463
+ */
464
+ /**
465
+ * The shapes the registry matches *before* it ever reaches the chat recipe.
466
+ *
467
+ * `geminiChatRecipe` is last in the shipped list, so any of these wins - which means a client
468
+ * carrying one of them has its chat methods left unpatched however chat-shaped it also looks.
469
+ */
470
+ type GatedVendorShape = {
471
+ messages: {
472
+ create: unknown;
473
+ };
474
+ } | {
475
+ chat: {
476
+ completions: {
477
+ create: unknown;
478
+ };
479
+ };
480
+ } | {
481
+ responses: {
482
+ create: unknown;
483
+ };
484
+ } | {
485
+ models: {
486
+ generateContent: unknown;
487
+ };
488
+ } | {
489
+ models: {
490
+ generateContentStream: unknown;
491
+ };
492
+ } | {
493
+ chat: {
494
+ complete: unknown;
495
+ };
496
+ } | {
497
+ systemOne: unknown;
498
+ };
499
+ /**
500
+ * Which paths to overlay when no recipe was named, given what the client looks like.
501
+ *
502
+ * `getHistory` is the discriminator, and it is what `geminiChatRecipe.matches` requires - so the
503
+ * types promise a context on the methods the runtime will gate rather than on every object with
504
+ * a method of that name.
505
+ *
506
+ * It is not enough on its own, though. A client carrying *both* a gated vendor's shape and a
507
+ * chat's is matched as that vendor, leaving `sendMessage` unpatched - and an overlay keyed on
508
+ * `getHistory` alone still invited a context onto it, which is never checked and is handed to
509
+ * the vendor as an unknown field. So anything the registry would claim first is excluded here,
510
+ * mirroring that precedence.
511
+ */
512
+ export type ChatOf<T> = T extends GatedVendorShape ? GatedPath : T extends OllamaShape ? GatedPath | OllamaGatedPath : T extends {
513
+ getHistory: (...args: any[]) => any;
514
+ } ? GatedPath | ChatGatedPath : GatedPath;
515
+ /** What `ollamaRecipe.matches` looks for, as a type. Kept in step with it by hand. */
516
+ type OllamaShape = {
517
+ chat: (...args: any[]) => any;
518
+ generate: (...args: any[]) => any;
519
+ ps: (...args: any[]) => any;
520
+ };
521
+ /**
522
+ * The paths still live after stepping through `K`, or `never` when that key leads nowhere gated.
523
+ *
524
+ * This is what bounds the walk: recursion follows the paths themselves, so there is no depth
525
+ * budget to keep in step with the deepest vendor surface, and no mapping of branches - a client's
526
+ * whole `files` or `batches` subtree - that could never hold a gated method.
527
+ */
528
+ type Below<Paths extends readonly string[], K> = Paths extends readonly [infer Head, ...infer Rest] ? Head extends K ? Rest extends readonly string[] ? Rest : never : never : never;
529
+ /** One signature, with the context threaded into whatever parameter object it already takes. */
530
+ type GatedSignature<P, R extends unknown[], Ret, Ctx> = P extends object ? (params: P & {
531
+ stiggContext?: Ctx;
532
+ }, ...rest: R) => Ret : (params: P, ...rest: R) => Ret;
533
+ /**
534
+ * A gated method, overloads and all.
535
+ *
536
+ * The arms are tried widest first because a single `infer` arm would silently collapse an
537
+ * overloaded method to its **last** signature - and `chat.completions.create` is overloaded
538
+ * precisely on `stream`, so collapsing it would hand a streaming caller the non-streaming return
539
+ * type. Three arms covers every vendor method the recipes gate; a fourth overload would fall
540
+ * through to the last arm and keep working, minus the earlier signatures.
541
+ */
542
+ type GatedMethod<F, Ctx> = F extends {
543
+ (params: infer P1, ...rest: infer R1): infer T1;
544
+ (params: infer P2, ...rest: infer R2): infer T2;
545
+ (params: infer P3, ...rest: infer R3): infer T3;
546
+ } ? GatedSignature<P1, R1, T1, Ctx> & GatedSignature<P2, R2, T2, Ctx> & GatedSignature<P3, R3, T3, Ctx> : F extends {
547
+ (params: infer P1, ...rest: infer R1): infer T1;
548
+ (params: infer P2, ...rest: infer R2): infer T2;
549
+ } ? GatedSignature<P1, R1, T1, Ctx> & GatedSignature<P2, R2, T2, Ctx> : F extends (params: infer P, ...rest: infer R) => infer Ret ? GatedSignature<P, R, Ret, Ctx> : F;
550
+ /**
551
+ * The client's own surface, with every gated method taught to accept a `stiggContext`.
552
+ *
553
+ * Rebuilt structurally, which on its own would lose the vendor's class identity - so
554
+ * {@link WrappedClient} intersects it back with `T` rather than replacing it. The overlay comes
555
+ * first in that intersection on purpose: an intersection of two call signatures is an overload
556
+ * set resolved in order, and the vendor's own signature would reject `stiggContext` as an excess
557
+ * property if it were tried first.
558
+ */
559
+ export type GatedSurface<T, Ctx, Paths extends readonly string[] = GatedPath> = {
560
+ [K in keyof T]: [Below<Paths, K>] extends [never] ? T[K] : [
561
+ ] extends Below<Paths, K> ? GatedMethod<T[K], Ctx> : GatedSurface<T[K], Ctx, Below<Paths, K>>;
562
+ };
563
+ /**
564
+ * Which paths a wrapped client accepts a context on: the recipe's own, or the shipped ones.
565
+ *
566
+ * `wrap` infers the options object it was handed, so a recipe passed inline keeps whatever types
567
+ * it was written with - and a recipe that declares its paths as literals (`path: [...] as const`)
568
+ * gets the overlay on exactly the methods it gates, rather than on the shipped vendors' paths.
569
+ *
570
+ * Anything else falls back to {@link GatedPath}: no recipe at all, which is the detection case, and
571
+ * a recipe whose paths widened to `string[]`, where there is nothing to narrow to and guessing
572
+ * would be worse than the default. So this only ever adds precision; it cannot take it away.
573
+ */
574
+ export type PathsOf<R, T = unknown> = R extends {
575
+ methods: readonly {
576
+ path: infer P;
577
+ }[];
578
+ } ? P extends readonly [string, ...string[]] ? P : ChatOf<T> : ChatOf<T>;
579
+ /**
580
+ * What a call still owes, given what `wrap` was already told.
581
+ *
582
+ * `customerId` is the one field that has to come from somewhere, so a client wrapped without one
583
+ * types it as required on every call and a client wrapped with one types it as optional. The
584
+ * runtime checks it either way, for callers the types never reached.
585
+ */
586
+ export type CallContext<Wrapped> = Wrapped extends {
587
+ customerId: string;
588
+ } ? StiggContext : StiggContext & {
589
+ customerId: string;
590
+ };
591
+ /**
592
+ * A vendor client bound to this environment. Every call its recipe covers is checked before it
593
+ * leaves and metered after, against the context `wrap` was given and whatever the call overrides.
594
+ */
595
+ export type WrappedClient<T, Wrapped = StiggContext, Paths extends readonly string[] = GatedPath> = GatedSurface<T, CallContext<Wrapped>, Paths> & T & {
596
+ /** What this client did, and what is still in flight. */
597
+ readonly stigg: ClientGovernance;
598
+ };
599
+ /** The governance handle hung off a wrapped client. */
600
+ export type ClientGovernance = {
601
+ /** Waits for in-flight ingests, then reports what this client did. */
602
+ flush(): Promise<SessionSummary>;
603
+ /** Drains, then releases what this client holds. */
604
+ close(): Promise<void>;
605
+ /** What this client has done so far, without waiting. */
606
+ summary(): SessionSummary;
607
+ /** The last decision this client made, or undefined if it has made none. */
608
+ readonly lastDecision?: AccessDecision;
609
+ /** The `x-stigg-*` attribution for work continuing in another process. */
610
+ headers(override?: StiggContext): GovernanceHeaders;
611
+ /** The gate, for a call this client does not wrap. */
612
+ check(override?: StiggContext, requestedUsage?: number): Promise<AccessDecision>;
613
+ /** The meter, for usage this client did not observe. */
614
+ report(metrics: Metrics, override?: StiggContext): Promise<IngestResult>;
615
+ };
616
+ /** What an out-of-process ingest needs, since it has no run to carry it. */
617
+ export type IngestContext = {
618
+ /** What the run this usage belongs to checks against. Defaults to the `credits` currency. */
619
+ entitlement?: Entitlement;
620
+ /**
621
+ * What to report. Defaults to the vendor's own names, read off the usage object the proxy
622
+ * observed - the same default a wrapped client gets.
623
+ */
624
+ metrics?: MetricsMapper;
625
+ /**
626
+ * Which shape the observed usage is. Named rather than sniffed: Anthropic and the Responses API
627
+ * both count `input_tokens`, and a wrong guess would report to another customer's meter.
628
+ * Omitted, only the normalized counters are available - so supply a `metrics` mapper too.
629
+ */
630
+ vendor?: ObservedVendor;
631
+ /** Which of Stigg's ingest APIs to report through. Defaults to the client's setting. */
632
+ ingest?: IngestMode;
633
+ /**
634
+ * What the event is called in `'async'` mode. There is no wrapped call out here to name it
635
+ * after, so a proxy either says, or is reported under the vendor it declared.
636
+ *
637
+ * Singular, and named to match `IngestOptions.event`: one report is one event, whatever the
638
+ * turn measured.
639
+ */
640
+ event?: EventIngest;
641
+ };
642
+ export {};