@stigg/agent-sdk-js 0.7.2 → 0.7.4

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 CHANGED
@@ -148,6 +148,8 @@ closed, and conflating them is how an outage takes an application down.
148
148
  | <img src="https://raw.githubusercontent.com/stiggio/stigg-agent-sdk-ts/main/docs/logos/gemini.svg" width="18" alt=""> | Gemini | <img src="https://raw.githubusercontent.com/stiggio/stigg-agent-sdk-ts/main/docs/logos/typesafe.png" width="18" alt=""> | TypeSafe |
149
149
  | <img src="https://raw.githubusercontent.com/stiggio/stigg-agent-sdk-ts/main/docs/logos/groq.svg" width="18" alt=""> | Groq | | |
150
150
 
151
+ **Frameworks** — [Vercel AI SDK](docs/vercel-ai.md): any model it runs, through `generateText`, `streamText` and agents. Model calls only, for now.
152
+
151
153
  **Gateways** — what was actually *charged*, and which model actually served after routing:
152
154
 
153
155
  | | Gateway | | Gateway |
@@ -181,6 +183,7 @@ rather than handing back a client that quietly meters nothing.
181
183
  | Meter a gateway you operate | `client.stigg.headers()` out, `stigg.ingestFromHeaders(...)` back |
182
184
  | Govern a LangChain / LangGraph agent graph ([guide](docs/langchain.md)) | `new StiggLangChainCallbackHandler({ stigg, agents, tools })` from `@stigg/agent-sdk-js/langchain` |
183
185
  | Govern a Google ADK agent graph ([guide](docs/guide.md#google-adk-one-plugin-per-runner)) | `new StiggAdkPlugin({ stigg, agents, tools, stepContext })` from `@stigg/agent-sdk-js/adk`, in the Runner's `plugins` |
186
+ | Govern a Vercel AI SDK model or agent ([guide](docs/vercel-ai.md)) | `wrapLanguageModel({ model, middleware: stiggVercelMiddleware({ stigg, stiggContext }) })`, a call's own in `providerOptions.stigg` |
184
187
  | Govern a graph from your own harness | `stigg.middleware({ stiggContext, agents })` |
185
188
 
186
189
  One wrapped client serves every customer and every agent: the context is an argument, given once at
@@ -270,6 +270,9 @@ function createAgentMiddleware(session, config) {
270
270
  return;
271
271
  }
272
272
  await session.ingestMeasurement(step.context, measurement);
273
+ // Reported what it could, and said what it could not - as a gated client call does.
274
+ if (measurement.incomplete)
275
+ session.failedToMeasure(step.context, new Error(measurement.incomplete));
273
276
  },
274
277
  async startTool(runId, { name, callId, kind, input }, attribution) {
275
278
  // Never taken, only looked up - the cap drops old ones: a tools node that runs again, before
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The Vercel AI SDK binding: a language model middleware, for `wrapLanguageModel`.
3
+ *
4
+ * const model = wrapLanguageModel({ model: anthropic(MODEL), middleware: stiggVercelMiddleware({ stigg, stiggContext }) });
5
+ * await generateText({ model, prompt, providerOptions: { stigg: { customerId } } });
6
+ *
7
+ * Every step of `generateText`, `streamText` and an agent's loop is one model call, so each one is
8
+ * checked before it runs and reported after, through the same core as the LangChain and ADK
9
+ * bindings. A call's own `stiggContext` rides `providerOptions.stigg`, and is taken out before the
10
+ * provider sees the call. Nothing here imports `ai`: a middleware is a plain object. See
11
+ * docs/vercel-ai.md.
12
+ */
13
+ import type { StiggAgentSDK } from '../../../stigg-agent-sdk';
14
+ import type { SessionHooks } from '../../../core/session';
15
+ import type { ClientContext, GovernedAgentMiddleware, Measurement } from '../../../types';
16
+ export { VERCEL_AI_METRICS, measurementOf } from './usage';
17
+ export type { VercelAIMetric, VercelAIMetrics } from './usage';
18
+ export type StiggVercelMiddlewareOptions = {
19
+ stigg: StiggAgentSDK;
20
+ /** Every call's context - typically the agent's `agentId` and what it gates on. A call's `providerOptions.stigg` is laid over it. */
21
+ stiggContext?: ClientContext;
22
+ hooks?: SessionHooks;
23
+ };
24
+ /**
25
+ * What `wrapLanguageModel` takes, typed here so this package does not import `ai`. `v3` is what
26
+ * `ai` 6 requires; `ai` 7 accepts any version and runs it the same way.
27
+ */
28
+ export type StiggVercelMiddleware = {
29
+ readonly specificationVersion: 'v3';
30
+ transformParams(options: {
31
+ params: any;
32
+ }): Promise<any>;
33
+ wrapGenerate(options: {
34
+ doGenerate: () => PromiseLike<any>;
35
+ params: any;
36
+ }): Promise<any>;
37
+ wrapStream(options: {
38
+ doStream: () => PromiseLike<any>;
39
+ params: any;
40
+ }): Promise<any>;
41
+ /** What this middleware did, and what is still in flight. */
42
+ readonly stigg: GovernedAgentMiddleware['stigg'];
43
+ };
44
+ export declare function stiggVercelMiddleware({ stigg, stiggContext, hooks, }: StiggVercelMiddlewareOptions): StiggVercelMiddleware;
45
+ /**
46
+ * Calls `done` once, when the stream ends: read to the end, failed, cancelled, or stopped by the
47
+ * call's `abortSignal` - which is how the AI SDK stops a stream, without cancelling it.
48
+ * ponytail: a reader that stops with no `abortSignal` is not reported; nothing tells this layer.
49
+ */
50
+ export declare function observeStream(result: any, signal: AbortSignal | undefined, done: (measurement: Measurement | undefined) => void): void;
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.measurementOf = exports.VERCEL_AI_METRICS = void 0;
4
+ exports.stiggVercelMiddleware = stiggVercelMiddleware;
5
+ exports.observeStream = observeStream;
6
+ const usage_1 = require("./usage");
7
+ var usage_2 = require("./usage");
8
+ Object.defineProperty(exports, "VERCEL_AI_METRICS", { enumerable: true, get: function () { return usage_2.VERCEL_AI_METRICS; } });
9
+ Object.defineProperty(exports, "measurementOf", { enumerable: true, get: function () { return usage_2.measurementOf; } });
10
+ const MISSING_CUSTOMER = 'Name it on every call as `providerOptions: { stigg: { customerId } }`, ' +
11
+ 'or give the middleware a default in its `stiggContext: { customerId }`.';
12
+ function stiggVercelMiddleware({ stigg, stiggContext, hooks, }) {
13
+ if (!stigg || typeof stigg.middleware !== 'function') {
14
+ throw new Error('stiggVercelMiddleware needs `stigg`: the StiggAgentSDK instance to govern through.');
15
+ }
16
+ const governance = stigg.middleware({ stiggContext, hooks, missingCustomer: MISSING_CUSTOMER });
17
+ // `doGenerate()` takes no arguments: the model is called with what `transformParams` returned.
18
+ // So the context comes out there, and is found again by that params object.
19
+ const contexts = new WeakMap();
20
+ // A step's id only has to be unique among this middleware's own steps in flight.
21
+ let steps = 0;
22
+ // A stream reports when it ends, after `wrapStream` has returned - so the call's next step waits
23
+ // for those reports here, and is checked against their spend. Keyed by the call: the AI SDK hands
24
+ // every step of one call the same `providerOptions.stigg` object, so other calls - other users
25
+ // of the same customer included - never queue behind it. A call with no context of its own falls
26
+ // back to its customer and agent. `endModel` never rejects.
27
+ const reporting = new Map();
28
+ const callOf = (params) => contexts.get(params) ?? JSON.stringify([stiggContext?.customerId, stiggContext?.agentId]);
29
+ /** Checks the step, as the agent and customer this call names. Refused: `AccessDeniedError`. */
30
+ const start = async (params, event) => {
31
+ // Once: a call's steps run one after another, so nothing of its own lands while this waits.
32
+ await Promise.all(reporting.get(callOf(params)) ?? []);
33
+ const runId = String(++steps);
34
+ const context = contexts.get(params);
35
+ await governance.startModel(runId, { stiggContexts: context ? [context] : [] }, event);
36
+ return runId;
37
+ };
38
+ return {
39
+ specificationVersion: 'v3',
40
+ stigg: governance.stigg,
41
+ async transformParams({ params }) {
42
+ const options = params?.providerOptions;
43
+ if (!options || typeof options !== 'object' || !('stigg' in options))
44
+ return params;
45
+ // The AI Gateway posts call options whole, and the customer and agent ids are not Vercel's to receive.
46
+ const { stigg: context, ...rest } = options;
47
+ if (context !== null && (typeof context !== 'object' || Array.isArray(context))) {
48
+ throw new TypeError(`\`providerOptions.stigg\` must be a stiggContext object, or null for none - got ${typeof context}.`);
49
+ }
50
+ const stripped = { ...params, providerOptions: rest };
51
+ if (context)
52
+ contexts.set(stripped, context);
53
+ return stripped;
54
+ },
55
+ async wrapGenerate({ doGenerate, params }) {
56
+ const runId = await start(params, 'vercel-ai.generate');
57
+ let result;
58
+ try {
59
+ result = await doGenerate();
60
+ }
61
+ catch (err) {
62
+ governance.abandon(runId);
63
+ throw err;
64
+ }
65
+ // Awaited, so the agent's next step is checked against this one's spend. Never throws. No
66
+ // usage stated: estimated from the reply, as a stream is, and counted as a metering failure.
67
+ const responseId = result?.response?.id;
68
+ const chars = (0, usage_1.contentChars)(result?.content);
69
+ await governance.endModel(runId, (0, usage_1.measurementOf)(result?.usage, responseId) ?? (chars ? (0, usage_1.estimated)(chars, usage_1.NO_USAGE, responseId) : undefined));
70
+ return result;
71
+ },
72
+ async wrapStream({ doStream, params }) {
73
+ const runId = await start(params, 'vercel-ai.stream');
74
+ let result;
75
+ try {
76
+ result = await doStream();
77
+ }
78
+ catch (err) {
79
+ governance.abandon(runId);
80
+ throw err;
81
+ }
82
+ observeStream(result, params?.abortSignal, (measurement) => {
83
+ if (!measurement)
84
+ return governance.abandon(runId);
85
+ const call = callOf(params);
86
+ const pending = reporting.get(call) ?? new Set();
87
+ reporting.set(call, pending);
88
+ const report = governance.endModel(runId, measurement);
89
+ pending.add(report);
90
+ void report.finally(() => {
91
+ pending.delete(report);
92
+ if (pending.size === 0 && reporting.get(call) === pending)
93
+ reporting.delete(call);
94
+ });
95
+ });
96
+ return result;
97
+ },
98
+ };
99
+ }
100
+ /** What `incomplete` says for a stream that ended without its usage, by how it ended. */
101
+ const STREAM_GAPS = {
102
+ ended: usage_1.NO_USAGE,
103
+ failed: 'the stream failed before the model stated its usage, so its output was estimated',
104
+ timedOut: 'the call timed out before the model stated its usage, so its output was estimated',
105
+ stopped: 'the call was stopped before any output, so its input was not measured',
106
+ };
107
+ /** A timeout's abort is not the caller stopping. */
108
+ const isTimeout = (reason) => reason?.name === 'TimeoutError';
109
+ /**
110
+ * Why a reader cancelled: the AI SDK cancels an attempt it retries with the provider's error, which
111
+ * is a failure; any other reason is the caller stopping.
112
+ */
113
+ function cancelEnd(reason) {
114
+ if (isTimeout(reason))
115
+ return 'timedOut';
116
+ return reason instanceof Error && reason.name !== 'AbortError' ? 'failed' : 'stopped';
117
+ }
118
+ /** What a stream has told us so far. */
119
+ function streamTally() {
120
+ let usage;
121
+ let responseId;
122
+ let chars = 0;
123
+ return {
124
+ /** Never throws: a malformed part is simply not counted. */
125
+ record(part) {
126
+ if (part?.type === 'finish')
127
+ usage = part.usage;
128
+ else if (part?.type === 'response-metadata' && typeof part.id === 'string')
129
+ responseId = part.id;
130
+ else if (typeof part?.delta === 'string')
131
+ chars += part.delta.length;
132
+ },
133
+ /**
134
+ * The usage the model stated; else an estimate; else nothing, for an attempt that produced
135
+ * nothing. A stream the caller stopped is estimated without counting as a metering failure -
136
+ * unless there was nothing to estimate, while the provider still counted its input.
137
+ */
138
+ measurement(end) {
139
+ const measured = (0, usage_1.measurementOf)(usage, responseId);
140
+ if (measured)
141
+ return measured;
142
+ if (chars === 0 && responseId === undefined)
143
+ return undefined;
144
+ const gap = end === 'stopped' && chars > 0 ? undefined : STREAM_GAPS[end];
145
+ return (0, usage_1.estimated)(chars, gap, responseId);
146
+ },
147
+ };
148
+ }
149
+ /**
150
+ * Calls `done` once, when the stream ends: read to the end, failed, cancelled, or stopped by the
151
+ * call's `abortSignal` - which is how the AI SDK stops a stream, without cancelling it.
152
+ * ponytail: a reader that stops with no `abortSignal` is not reported; nothing tells this layer.
153
+ */
154
+ function observeStream(result, signal, done) {
155
+ const source = result?.stream;
156
+ if (typeof source?.getReader !== 'function') {
157
+ done(undefined);
158
+ return;
159
+ }
160
+ const tally = streamTally();
161
+ let reported = false;
162
+ const report = (end) => {
163
+ if (reported)
164
+ return;
165
+ reported = true;
166
+ // Every step of a loop shares the signal, which may live as long as the process.
167
+ signal?.removeEventListener('abort', onAbort);
168
+ done(tally.measurement(end));
169
+ };
170
+ const onAbort = () => report(isTimeout(signal?.reason) ? 'timedOut' : 'stopped');
171
+ signal?.addEventListener('abort', onAbort, { once: true });
172
+ if (signal?.aborted)
173
+ onAbort();
174
+ // A source of its own rather than a TransformStream, whose flush never runs on a cancel.
175
+ const reader = source.getReader();
176
+ result.stream = new ReadableStream({
177
+ async pull(controller) {
178
+ const next = await reader.read().catch((err) => {
179
+ report('failed');
180
+ throw err;
181
+ });
182
+ if (next.done) {
183
+ report('ended');
184
+ controller.close();
185
+ return;
186
+ }
187
+ tally.record(next.value);
188
+ controller.enqueue(next.value);
189
+ },
190
+ async cancel(reason) {
191
+ report(cancelEnd(reason));
192
+ await reader.cancel(reason);
193
+ },
194
+ });
195
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A Vercel AI SDK model's usage, as this SDK reports it.
3
+ *
4
+ * The counters overlap the way OpenAI's do - `input_tokens` already counts the cache - so a meter can
5
+ * price cached input at its own rate. The normalized `usage` a mapper reads is disjoint.
6
+ */
7
+ import type { Measurement } from '../../../types';
8
+ export declare const VERCEL_AI_METRICS: readonly ["input_tokens", "output_tokens", "cache_read_tokens", "cache_write_tokens", "total_tokens"];
9
+ /** One of the metric names a Vercel AI SDK model reports. */
10
+ export type VercelAIMetric = (typeof VERCEL_AI_METRICS)[number];
11
+ /** What a mapper reads off a Vercel AI SDK call: those names, each possibly absent. */
12
+ export type VercelAIMetrics = Partial<Record<VercelAIMetric, number>>;
13
+ /** `LanguageModelV3Usage` and `V4` alike: `inputTokens.total` includes the cache. */
14
+ export type ModelUsage = {
15
+ inputTokens?: {
16
+ total?: number;
17
+ noCache?: number;
18
+ cacheRead?: number;
19
+ cacheWrite?: number;
20
+ };
21
+ outputTokens?: {
22
+ total?: number;
23
+ };
24
+ raw?: unknown;
25
+ };
26
+ /** One call's usage, or undefined when the model stated no counter - not a call that cost nothing. */
27
+ export declare function measurementOf(usage: ModelUsage | undefined, responseId?: string): Measurement | undefined;
28
+ /** What `incomplete` says when the model never stated its usage: a gap in metering, not a stop. */
29
+ export declare const NO_USAGE = "the model stated no usage, so its output was estimated";
30
+ /**
31
+ * A call with no usage stated: its output, estimated from the text. Its input is unknown.
32
+ * `incomplete` is set only for a metering gap - a call the caller stopped is not one.
33
+ */
34
+ export declare function estimated(chars: number, incomplete?: string, responseId?: string): Measurement;
35
+ /** The characters a generated reply holds: its text, its reasoning and its tool calls' input. */
36
+ export declare function contentChars(content: unknown): number;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NO_USAGE = exports.VERCEL_AI_METRICS = void 0;
4
+ exports.measurementOf = measurementOf;
5
+ exports.estimated = estimated;
6
+ exports.contentChars = contentChars;
7
+ exports.VERCEL_AI_METRICS = [
8
+ 'input_tokens',
9
+ 'output_tokens',
10
+ 'cache_read_tokens',
11
+ 'cache_write_tokens',
12
+ 'total_tokens',
13
+ ];
14
+ /** Roughly, for a stopped stream only - the Anthropic recipe's estimate too. */
15
+ const CHARS_PER_OUTPUT_TOKEN = 4;
16
+ /** One call's usage, or undefined when the model stated no counter - not a call that cost nothing. */
17
+ function measurementOf(usage, responseId) {
18
+ const input = usage?.inputTokens?.total;
19
+ const output = usage?.outputTokens?.total;
20
+ if (input === undefined && output === undefined)
21
+ return undefined;
22
+ const cacheRead = usage?.inputTokens?.cacheRead;
23
+ const cacheWrite = usage?.inputTokens?.cacheWrite;
24
+ const metrics = numbersOnly({
25
+ input_tokens: input,
26
+ output_tokens: output,
27
+ cache_read_tokens: cacheRead,
28
+ cache_write_tokens: cacheWrite,
29
+ total_tokens: (input ?? 0) + (output ?? 0),
30
+ });
31
+ const tokenUsage = {
32
+ input: usage?.inputTokens?.noCache ?? Math.max(0, (input ?? 0) - (cacheRead ?? 0) - (cacheWrite ?? 0)),
33
+ output: output ?? 0,
34
+ cacheRead: cacheRead ?? 0,
35
+ cacheCreate: cacheWrite ?? 0,
36
+ };
37
+ const raw = usage?.raw;
38
+ return {
39
+ metrics,
40
+ usage: tokenUsage,
41
+ ...(raw && typeof raw === 'object' && !Array.isArray(raw) ? { raw: raw } : {}),
42
+ ...(input === undefined || output === undefined ? { incomplete: 'the model stated only part of its usage' } : {}),
43
+ // The provider's own id, so a retried step that replays a reply is one report.
44
+ ...(responseId ? { idempotencyKey: responseId } : {}),
45
+ };
46
+ }
47
+ /** What `incomplete` says when the model never stated its usage: a gap in metering, not a stop. */
48
+ exports.NO_USAGE = 'the model stated no usage, so its output was estimated';
49
+ /**
50
+ * A call with no usage stated: its output, estimated from the text. Its input is unknown.
51
+ * `incomplete` is set only for a metering gap - a call the caller stopped is not one.
52
+ */
53
+ function estimated(chars, incomplete, responseId) {
54
+ const output = chars > 0 ? Math.max(1, Math.ceil(chars / CHARS_PER_OUTPUT_TOKEN)) : 0;
55
+ return {
56
+ metrics: output ? { output_tokens: output, total_tokens: output } : {},
57
+ usage: { input: 0, output, cacheRead: 0, cacheCreate: 0 },
58
+ ...(output ? { estimated: true } : {}),
59
+ ...(incomplete ? { incomplete } : {}),
60
+ // Keyed by the response, as a stated usage is: a replayed reply is still one report.
61
+ ...(responseId ? { idempotencyKey: responseId } : {}),
62
+ };
63
+ }
64
+ /** The characters a generated reply holds: its text, its reasoning and its tool calls' input. */
65
+ function contentChars(content) {
66
+ if (!Array.isArray(content))
67
+ return 0;
68
+ let chars = 0;
69
+ for (const part of content) {
70
+ const text = part?.type === 'tool-call' ? part.input : part?.text;
71
+ if (typeof text === 'string')
72
+ chars += text.length;
73
+ }
74
+ return chars;
75
+ }
76
+ function numbersOnly(stated) {
77
+ const metrics = {};
78
+ for (const [name, value] of Object.entries(stated)) {
79
+ if (typeof value === 'number' && Number.isFinite(value))
80
+ metrics[name] = value;
81
+ }
82
+ return metrics;
83
+ }
package/dist/index.d.ts CHANGED
@@ -62,4 +62,6 @@ export { weightedMetrics } from './bindings/shared/weighted-metrics';
62
62
  export type { WeightedMetricsOptions } from './bindings/shared/weighted-metrics';
63
63
  export type { ProxyIngestInput } from './bindings/proxy';
64
64
  export type { AgentMiddleware, IssuedToolCall, StepAttribution, ToolStep, } from './bindings/orchestrator/core/agent-middleware';
65
+ export { stiggVercelMiddleware } from './bindings/orchestrator/vercel-ai/middleware';
66
+ export type { StiggVercelMiddleware, StiggVercelMiddlewareOptions, VercelAIMetric, VercelAIMetrics, } from './bindings/orchestrator/vercel-ai/middleware';
65
67
  export { normalizedMetrics, normalizeUsage } from './bindings/shared/token-usage';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.normalizeUsage = exports.normalizedMetrics = exports.weightedMetrics = exports.estimateFromState = exports.typesafeRecipe = exports.cloudflareRecipe = exports.bedrockRecipe = exports.databricksExternalRecipe = exports.databricksRecipe = exports.ollamaRecipe = exports.mistralRecipe = exports.groqRecipe = exports.litellmRecipe = exports.openrouterRecipe = exports.estimateFromMaxOutputTokens = exports.geminiChatRecipe = exports.geminiRecipe = exports.openAIRecipe = exports.estimateFromMaxTokens = exports.anthropicRecipe = exports.hasMethod = exports.createClientRegistry = exports.wrapClient = exports.toMeasurement = exports.entitlementRef = exports.entitlementKind = exports.entitlementId = exports.emptyTokenUsage = exports.vendorMetrics = exports.unitsPerTokens = exports.looksLikeGovernanceNotEnabled = exports.isGovernanceNotEnabled = exports.isAccessDenied = exports.isEngineError = exports.GovernanceNotEnabledError = exports.AccessDeniedError = exports.EngineError = exports.STIGG_TRACER_NAME = exports.noopLogger = exports.consoleLogger = exports.parseGovernanceHeaders = exports.GOVERNANCE_HEADERS = exports.remedyMessage = exports.describeDecision = exports.flattenRaw = exports.RAW_PREFIX = exports.StiggAgentSDK = void 0;
3
+ exports.normalizeUsage = exports.normalizedMetrics = exports.stiggVercelMiddleware = exports.weightedMetrics = exports.estimateFromState = exports.typesafeRecipe = exports.cloudflareRecipe = exports.bedrockRecipe = exports.databricksExternalRecipe = exports.databricksRecipe = exports.ollamaRecipe = exports.mistralRecipe = exports.groqRecipe = exports.litellmRecipe = exports.openrouterRecipe = exports.estimateFromMaxOutputTokens = exports.geminiChatRecipe = exports.geminiRecipe = exports.openAIRecipe = exports.estimateFromMaxTokens = exports.anthropicRecipe = exports.hasMethod = exports.createClientRegistry = exports.wrapClient = exports.toMeasurement = exports.entitlementRef = exports.entitlementKind = exports.entitlementId = exports.emptyTokenUsage = exports.vendorMetrics = exports.unitsPerTokens = exports.looksLikeGovernanceNotEnabled = exports.isGovernanceNotEnabled = exports.isAccessDenied = exports.isEngineError = exports.GovernanceNotEnabledError = exports.AccessDeniedError = exports.EngineError = exports.STIGG_TRACER_NAME = exports.noopLogger = exports.consoleLogger = exports.parseGovernanceHeaders = exports.GOVERNANCE_HEADERS = exports.remedyMessage = exports.describeDecision = exports.flattenRaw = exports.RAW_PREFIX = exports.StiggAgentSDK = void 0;
4
4
  /**
5
5
  * Public surface.
6
6
  *
@@ -123,6 +123,10 @@ Object.defineProperty(exports, "typesafeRecipe", { enumerable: true, get: functi
123
123
  Object.defineProperty(exports, "estimateFromState", { enumerable: true, get: function () { return typesafe_recipe_1.estimateFromState; } });
124
124
  var weighted_metrics_1 = require("./bindings/shared/weighted-metrics");
125
125
  Object.defineProperty(exports, "weightedMetrics", { enumerable: true, get: function () { return weighted_metrics_1.weightedMetrics; } });
126
+ // The Vercel AI SDK binding (`ai` 6 and 7): a middleware for `wrapLanguageModel`. On the main entry
127
+ // because it imports nothing from `ai` - a middleware is a plain object.
128
+ var middleware_1 = require("./bindings/orchestrator/vercel-ai/middleware");
129
+ Object.defineProperty(exports, "stiggVercelMiddleware", { enumerable: true, get: function () { return middleware_1.stiggVercelMiddleware; } });
126
130
  var token_usage_1 = require("./bindings/shared/token-usage");
127
131
  Object.defineProperty(exports, "normalizedMetrics", { enumerable: true, get: function () { return token_usage_1.normalizedMetrics; } });
128
132
  Object.defineProperty(exports, "normalizeUsage", { enumerable: true, get: function () { return token_usage_1.normalizeUsage; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigg/agent-sdk-js",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "Stigg agent SDK (JS) - run-lifecycle governance for agentic stacks, with examples per layer",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -57,6 +57,7 @@
57
57
  "example-orchestrator-langgraph-model-fallback": "tsx examples/orchestrator/langchain/langgraph-model-fallback.ts",
58
58
  "example-orchestrator-adk-handoff": "tsx examples/orchestrator/adk/adk-handoff.ts",
59
59
  "example-orchestrator-adk-model-fallback": "tsx examples/orchestrator/adk/adk-model-fallback.ts",
60
+ "example-orchestrator-vercel-ai-handoff": "tsx examples/orchestrator/vercel-ai/vercel-ai-handoff.ts",
60
61
  "example-gateway": "tsx examples/gateway/openrouter.ts",
61
62
  "example-gateway-openrouter": "tsx examples/gateway/openrouter.ts",
62
63
  "example-gateway-litellm": "tsx examples/gateway/litellm.ts",
@@ -99,6 +100,7 @@
99
100
  }
100
101
  },
101
102
  "devDependencies": {
103
+ "@ai-sdk/anthropic": "^4.0.91",
102
104
  "@anthropic-ai/sdk": "^0.124.0",
103
105
  "@aws-sdk/client-bedrock-runtime": "^3.1150.0",
104
106
  "@databricks/sdk-modelservingquery": "0.25.0",
@@ -120,6 +122,8 @@
120
122
  "@opentelemetry/sdk-trace-base": "^2.12.0",
121
123
  "@opentelemetry/sdk-trace-node": "^2.0.1",
122
124
  "@typesafe-ai/sdk": "^0.6.0",
125
+ "ai": "^7.0.153",
126
+ "ai-v6": "npm:ai@^6.0.320",
123
127
  "cloudflare": "^7.3.0",
124
128
  "dotenv": "^17.4.2",
125
129
  "groq-sdk": "^1.6.0",