@stigg/agent-sdk-js 0.7.1 → 0.7.3

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,169 @@
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 at all is reported as a metering failure.
67
+ await governance.endModel(runId, (0, usage_1.measurementOf)(result?.usage, result?.response?.id));
68
+ return result;
69
+ },
70
+ async wrapStream({ doStream, params }) {
71
+ const runId = await start(params, 'vercel-ai.stream');
72
+ let result;
73
+ try {
74
+ result = await doStream();
75
+ }
76
+ catch (err) {
77
+ governance.abandon(runId);
78
+ throw err;
79
+ }
80
+ observeStream(result, params?.abortSignal, (measurement) => {
81
+ if (!measurement)
82
+ return governance.abandon(runId);
83
+ const call = callOf(params);
84
+ const pending = reporting.get(call) ?? new Set();
85
+ reporting.set(call, pending);
86
+ const report = governance.endModel(runId, measurement);
87
+ pending.add(report);
88
+ void report.finally(() => {
89
+ pending.delete(report);
90
+ if (pending.size === 0 && reporting.get(call) === pending)
91
+ reporting.delete(call);
92
+ });
93
+ });
94
+ return result;
95
+ },
96
+ };
97
+ }
98
+ /** What a stream has told us so far. */
99
+ function streamTally() {
100
+ let usage;
101
+ let responseId;
102
+ let chars = 0;
103
+ return {
104
+ /** Never throws: a malformed part is simply not counted. */
105
+ record(part) {
106
+ if (part?.type === 'finish')
107
+ usage = part.usage;
108
+ else if (part?.type === 'response-metadata' && typeof part.id === 'string')
109
+ responseId = part.id;
110
+ else if (typeof part?.delta === 'string')
111
+ chars += part.delta.length;
112
+ },
113
+ /** The usage the model stated; else an estimate; else nothing, for an attempt that produced nothing. */
114
+ measurement() {
115
+ const measured = (0, usage_1.measurementOf)(usage, responseId);
116
+ if (measured)
117
+ return measured;
118
+ if (chars === 0 && responseId === undefined)
119
+ return undefined;
120
+ return (0, usage_1.estimated)(chars);
121
+ },
122
+ };
123
+ }
124
+ /**
125
+ * Calls `done` once, when the stream ends: read to the end, failed, cancelled, or stopped by the
126
+ * call's `abortSignal` - which is how the AI SDK stops a stream, without cancelling it.
127
+ * ponytail: a reader that stops with no `abortSignal` is not reported; nothing tells this layer.
128
+ */
129
+ function observeStream(result, signal, done) {
130
+ const source = result?.stream;
131
+ if (typeof source?.getReader !== 'function') {
132
+ done(undefined);
133
+ return;
134
+ }
135
+ const tally = streamTally();
136
+ let reported = false;
137
+ const report = () => {
138
+ if (reported)
139
+ return;
140
+ reported = true;
141
+ // Every step of a loop shares the signal, which may live as long as the process.
142
+ signal?.removeEventListener('abort', report);
143
+ done(tally.measurement());
144
+ };
145
+ signal?.addEventListener('abort', report, { once: true });
146
+ if (signal?.aborted)
147
+ report();
148
+ // A source of its own rather than a TransformStream, whose flush never runs on a cancel.
149
+ const reader = source.getReader();
150
+ result.stream = new ReadableStream({
151
+ async pull(controller) {
152
+ const next = await reader.read().catch((err) => {
153
+ report();
154
+ throw err;
155
+ });
156
+ if (next.done) {
157
+ report();
158
+ controller.close();
159
+ return;
160
+ }
161
+ tally.record(next.value);
162
+ controller.enqueue(next.value);
163
+ },
164
+ async cancel(reason) {
165
+ report();
166
+ await reader.cancel(reason);
167
+ },
168
+ });
169
+ }
@@ -0,0 +1,29 @@
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
+ /** A stream stopped before stating usage: its output, estimated from the text. Its input is unknown. */
29
+ export declare function estimated(chars: number): Measurement;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERCEL_AI_METRICS = void 0;
4
+ exports.measurementOf = measurementOf;
5
+ exports.estimated = estimated;
6
+ exports.VERCEL_AI_METRICS = [
7
+ 'input_tokens',
8
+ 'output_tokens',
9
+ 'cache_read_tokens',
10
+ 'cache_write_tokens',
11
+ 'total_tokens',
12
+ ];
13
+ /** Roughly, for a stopped stream only - the Anthropic recipe's estimate too. */
14
+ const CHARS_PER_OUTPUT_TOKEN = 4;
15
+ /** One call's usage, or undefined when the model stated no counter - not a call that cost nothing. */
16
+ function measurementOf(usage, responseId) {
17
+ const input = usage?.inputTokens?.total;
18
+ const output = usage?.outputTokens?.total;
19
+ if (input === undefined && output === undefined)
20
+ return undefined;
21
+ const cacheRead = usage?.inputTokens?.cacheRead;
22
+ const cacheWrite = usage?.inputTokens?.cacheWrite;
23
+ const metrics = numbersOnly({
24
+ input_tokens: input,
25
+ output_tokens: output,
26
+ cache_read_tokens: cacheRead,
27
+ cache_write_tokens: cacheWrite,
28
+ total_tokens: (input ?? 0) + (output ?? 0),
29
+ });
30
+ const tokenUsage = {
31
+ input: usage?.inputTokens?.noCache ?? Math.max(0, (input ?? 0) - (cacheRead ?? 0) - (cacheWrite ?? 0)),
32
+ output: output ?? 0,
33
+ cacheRead: cacheRead ?? 0,
34
+ cacheCreate: cacheWrite ?? 0,
35
+ };
36
+ const raw = usage?.raw;
37
+ return {
38
+ metrics,
39
+ usage: tokenUsage,
40
+ ...(raw && typeof raw === 'object' && !Array.isArray(raw) ? { raw: raw } : {}),
41
+ ...(input === undefined || output === undefined ? { incomplete: 'the model stated only part of its usage' } : {}),
42
+ // The provider's own id, so a retried step that replays a reply is one report.
43
+ ...(responseId ? { idempotencyKey: responseId } : {}),
44
+ };
45
+ }
46
+ /** A stream stopped before stating usage: its output, estimated from the text. Its input is unknown. */
47
+ function estimated(chars) {
48
+ const output = chars > 0 ? Math.max(1, Math.ceil(chars / CHARS_PER_OUTPUT_TOKEN)) : 0;
49
+ return {
50
+ metrics: output ? { output_tokens: output, total_tokens: output } : {},
51
+ usage: { input: 0, output, cacheRead: 0, cacheCreate: 0 },
52
+ ...(output ? { estimated: true } : {}),
53
+ incomplete: 'the stream ended before the model stated its usage',
54
+ };
55
+ }
56
+ function numbersOnly(stated) {
57
+ const metrics = {};
58
+ for (const [name, value] of Object.entries(stated)) {
59
+ if (typeof value === 'number' && Number.isFinite(value))
60
+ metrics[name] = value;
61
+ }
62
+ return metrics;
63
+ }
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.1",
3
+ "version": "0.7.3",
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",
@@ -52,10 +52,12 @@
52
52
  "example-client-databricks": "tsx examples/client/databricks.ts",
53
53
  "example-client-typesafe": "tsx examples/client/typesafe.ts",
54
54
  "example-two-agents": "tsx examples/client/two-agents.ts",
55
- "example-orchestrator-langchain": "tsx examples/orchestrator/langchain.ts",
56
- "example-orchestrator-langgraph-handoff": "tsx examples/orchestrator/langgraph-handoff.ts",
57
- "example-orchestrator-langgraph-model-fallback": "tsx examples/orchestrator/langgraph-model-fallback.ts",
58
- "example-orchestrator-adk-handoff": "tsx examples/orchestrator/adk-handoff.ts",
55
+ "example-orchestrator-langchain": "tsx examples/orchestrator/langchain/langchain-handoff.ts",
56
+ "example-orchestrator-langgraph-handoff": "tsx examples/orchestrator/langchain/langgraph-handoff.ts",
57
+ "example-orchestrator-langgraph-model-fallback": "tsx examples/orchestrator/langchain/langgraph-model-fallback.ts",
58
+ "example-orchestrator-adk-handoff": "tsx examples/orchestrator/adk/adk-handoff.ts",
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",
59
61
  "example-gateway": "tsx examples/gateway/openrouter.ts",
60
62
  "example-gateway-openrouter": "tsx examples/gateway/openrouter.ts",
61
63
  "example-gateway-litellm": "tsx examples/gateway/litellm.ts",
@@ -98,6 +100,7 @@
98
100
  }
99
101
  },
100
102
  "devDependencies": {
103
+ "@ai-sdk/anthropic": "^4.0.91",
101
104
  "@anthropic-ai/sdk": "^0.124.0",
102
105
  "@aws-sdk/client-bedrock-runtime": "^3.1150.0",
103
106
  "@databricks/sdk-modelservingquery": "0.25.0",
@@ -119,6 +122,7 @@
119
122
  "@opentelemetry/sdk-trace-base": "^2.12.0",
120
123
  "@opentelemetry/sdk-trace-node": "^2.0.1",
121
124
  "@typesafe-ai/sdk": "^0.6.0",
125
+ "ai": "^7.0.153",
122
126
  "cloudflare": "^7.3.0",
123
127
  "dotenv": "^17.4.2",
124
128
  "groq-sdk": "^1.6.0",