@agentguard-run/spend 0.13.1 → 0.13.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/frameworks/claude-code.d.ts +32 -0
  3. package/dist/frameworks/claude-code.d.ts.map +1 -0
  4. package/dist/frameworks/claude-code.js +75 -0
  5. package/dist/frameworks/claude-code.js.map +1 -0
  6. package/dist/frameworks/common.d.ts +51 -0
  7. package/dist/frameworks/common.d.ts.map +1 -0
  8. package/dist/frameworks/common.js +168 -0
  9. package/dist/frameworks/common.js.map +1 -0
  10. package/dist/frameworks/hermes.d.ts +34 -0
  11. package/dist/frameworks/hermes.d.ts.map +1 -0
  12. package/dist/frameworks/hermes.js +50 -0
  13. package/dist/frameworks/hermes.js.map +1 -0
  14. package/dist/frameworks/index.d.ts +6 -0
  15. package/dist/frameworks/index.d.ts.map +1 -0
  16. package/dist/frameworks/index.js +22 -0
  17. package/dist/frameworks/index.js.map +1 -0
  18. package/dist/frameworks/openrouter.d.ts +19 -0
  19. package/dist/frameworks/openrouter.d.ts.map +1 -0
  20. package/dist/frameworks/openrouter.js +85 -0
  21. package/dist/frameworks/openrouter.js.map +1 -0
  22. package/dist/frameworks/vercel-ai.d.ts +27 -0
  23. package/dist/frameworks/vercel-ai.d.ts.map +1 -0
  24. package/dist/frameworks/vercel-ai.js +96 -0
  25. package/dist/frameworks/vercel-ai.js.map +1 -0
  26. package/dist/index.d.ts +2 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/receipts/dag.d.ts.map +1 -1
  31. package/dist/receipts/dag.js +2 -0
  32. package/dist/receipts/dag.js.map +1 -1
  33. package/package.json +33 -3
  34. package/src/frameworks/README.md +44 -0
  35. package/src/frameworks/claude-code.ts +108 -0
  36. package/src/frameworks/common.ts +208 -0
  37. package/src/frameworks/hermes.ts +83 -0
  38. package/src/frameworks/index.ts +5 -0
  39. package/src/frameworks/openrouter.ts +90 -0
  40. package/src/frameworks/vercel-ai.ts +118 -0
  41. package/src/receipts/dag.ts +2 -0
@@ -0,0 +1,208 @@
1
+ import type { CapabilityTier, CallContext, Provider, SignedDecisionLogEntry, SpendDecision, SpendPolicy, SpendScope } from '../types';
2
+ import { inferProvider } from '../cost-table';
3
+ import { sanitizeMetadata } from '../governance';
4
+ import { AgentGuardBlockedError, SpendGuard, type SpendGuardConfig } from '../spend-guard';
5
+
6
+ export interface FrameworkAdapterOptions {
7
+ policy: SpendPolicy;
8
+ scope: SpendScope;
9
+ capabilityClaim?: CapabilityTier;
10
+ config?: Omit<SpendGuardConfig, 'policy'>;
11
+ licenseKey?: string;
12
+ defaultModel?: string;
13
+ defaultOutputTokens?: number;
14
+ framework?: string;
15
+ }
16
+
17
+ export interface FrameworkPreflight {
18
+ guard: SpendGuard;
19
+ call: CallContext;
20
+ decision: SpendDecision;
21
+ signed: SignedDecisionLogEntry | null;
22
+ }
23
+
24
+ export interface FrameworkCallInput {
25
+ framework: string;
26
+ model?: string;
27
+ provider?: Provider;
28
+ params?: unknown;
29
+ requestShape?: Record<string, unknown>;
30
+ metadata?: Record<string, unknown>;
31
+ workflowId?: string;
32
+ subagentId?: string;
33
+ toolName?: string;
34
+ outputTokens?: number;
35
+ }
36
+
37
+ const DATA_PLANE_KEYS = /^(prompt|completion|content|messages|input|output|text|raw)$/i;
38
+
39
+ export function createFrameworkGuard(opts: FrameworkAdapterOptions): SpendGuard {
40
+ return new SpendGuard({
41
+ policy: opts.policy,
42
+ ...(opts.config ?? {}),
43
+ licenseKey: opts.licenseKey ?? opts.config?.licenseKey,
44
+ });
45
+ }
46
+
47
+ export function safeRequestShape(value: unknown): Record<string, unknown> {
48
+ const redacted = redactDataPlane(value);
49
+ if (redacted && typeof redacted === 'object' && !Array.isArray(redacted)) {
50
+ return sanitizeMetadata(redacted as Record<string, unknown>);
51
+ }
52
+ return { valueType: typeof value };
53
+ }
54
+
55
+ export function redactDataPlane(value: unknown): unknown {
56
+ if (value == null) return value;
57
+ if (Array.isArray(value)) return value.map((child) => redactDataPlane(child));
58
+ if (typeof value !== 'object') return value;
59
+ const out: Record<string, unknown> = {};
60
+ for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
61
+ if (DATA_PLANE_KEYS.test(key)) {
62
+ out[key + '_present'] = child != null;
63
+ out[key + '_type'] = Array.isArray(child) ? 'array' : typeof child;
64
+ if (Array.isArray(child)) out[key + '_items'] = child.length;
65
+ continue;
66
+ }
67
+ out[key] = redactDataPlane(child);
68
+ }
69
+ return out;
70
+ }
71
+
72
+ export function estimateInputTokens(guard: SpendGuard, value: unknown): number {
73
+ const serialized = typeof value === 'string' ? value : stableJson(value ?? {});
74
+ return Math.max(1, guard.estimateTokens(serialized));
75
+ }
76
+
77
+ export function outputTokensFromParams(params: unknown, fallback = 512): number {
78
+ const record = objectRecord(params);
79
+ const value =
80
+ record?.max_tokens ??
81
+ record?.maxTokens ??
82
+ record?.maxOutputTokens ??
83
+ record?.max_completion_tokens ??
84
+ record?.maxCompletionTokens;
85
+ return Number.isSafeInteger(value) && (value as number) >= 0 ? value as number : fallback;
86
+ }
87
+
88
+ export function modelFromUnknown(model: unknown, fallback = 'unknown'): string {
89
+ if (typeof model === 'string' && model.trim()) return model.trim();
90
+ const record = objectRecord(model);
91
+ for (const key of ['modelId', 'model_id', 'id', 'name']) {
92
+ const value = record?.[key];
93
+ if (typeof value === 'string' && value.trim()) return value.trim();
94
+ }
95
+ return fallback;
96
+ }
97
+
98
+ export function providerFromModel(model: string, provider?: Provider): Provider {
99
+ return provider ?? inferProvider(model);
100
+ }
101
+
102
+ export function buildFrameworkCall(
103
+ guard: SpendGuard,
104
+ opts: FrameworkAdapterOptions,
105
+ input: FrameworkCallInput,
106
+ ): CallContext {
107
+ const model = modelFromUnknown(input.model, opts.defaultModel ?? 'unknown');
108
+ const metadata = sanitizeMetadata(redactDataPlane(input.metadata ?? {}) as Record<string, unknown>);
109
+ const requestShape = sanitizeMetadata({
110
+ framework: input.framework,
111
+ ...(input.requestShape ?? safeRequestShape(input.params)),
112
+ });
113
+ return {
114
+ provider: providerFromModel(model, input.provider),
115
+ model,
116
+ inputTokens: estimateInputTokens(guard, input.params),
117
+ outputTokens: input.outputTokens ?? outputTokensFromParams(input.params, opts.defaultOutputTokens ?? 512),
118
+ scope: opts.scope,
119
+ capabilityClaim: opts.capabilityClaim,
120
+ workflowId: input.workflowId ?? stringValue(metadata.workflowId) ?? stringValue(metadata.workflow_id),
121
+ subagentId: input.subagentId ?? stringValue(metadata.subagentId) ?? stringValue(metadata.subagent_id),
122
+ label: stringValue(metadata.label) ?? input.framework,
123
+ toolName: input.toolName,
124
+ requestShape,
125
+ };
126
+ }
127
+
128
+ export async function preflightFrameworkCall(
129
+ guard: SpendGuard,
130
+ opts: FrameworkAdapterOptions,
131
+ input: FrameworkCallInput,
132
+ ): Promise<FrameworkPreflight> {
133
+ const call = buildFrameworkCall(guard, opts, input);
134
+ const { decision, signed } = await guard.decide(call);
135
+ if (decision.action === 'block') {
136
+ throw new AgentGuardBlockedError(decision, opts.scope, opts.config?.locale);
137
+ }
138
+ return { guard, call, decision, signed };
139
+ }
140
+
141
+ export async function settleFrameworkCall(preflight: FrameworkPreflight, result: unknown, partial = false): Promise<void> {
142
+ const usage = usageFromResult(result);
143
+ if (!usage) return;
144
+ await preflight.guard.settleStreamUsage(
145
+ preflight.decision.decisionId,
146
+ usage.inputTokens,
147
+ usage.outputTokens,
148
+ { partial },
149
+ );
150
+ }
151
+
152
+ export async function recordFrameworkReceipt(
153
+ guard: SpendGuard,
154
+ framework: string,
155
+ receipt: Record<string, unknown>,
156
+ ): Promise<{ decision: SpendDecision; signed: SignedDecisionLogEntry | null }> {
157
+ return guard.recordOutcomeReceipt({
158
+ type: 'framework_adapter',
159
+ framework,
160
+ totalCostCents: safeInteger(receipt.totalCostCents),
161
+ ...sanitizeMetadata(redactDataPlane(receipt) as Record<string, unknown>),
162
+ });
163
+ }
164
+
165
+ export function usageFromResult(result: unknown): { inputTokens: number; outputTokens: number } | null {
166
+ const candidates = [
167
+ objectRecord(result)?.usage,
168
+ objectRecord(objectRecord(result)?.response)?.usage,
169
+ objectRecord(objectRecord(result)?.rawResponse)?.usage,
170
+ ];
171
+ for (const candidate of candidates) {
172
+ const usage = objectRecord(candidate);
173
+ if (!usage) continue;
174
+ const input = firstInteger(usage.inputTokens, usage.promptTokens, usage.prompt_tokens, usage.input_tokens);
175
+ const output = firstInteger(usage.outputTokens, usage.completionTokens, usage.completion_tokens, usage.output_tokens);
176
+ if (input !== null && output !== null) return { inputTokens: input, outputTokens: output };
177
+ }
178
+ return null;
179
+ }
180
+
181
+ export function objectRecord(value: unknown): Record<string, unknown> | null {
182
+ return value && typeof value === 'object' && !Array.isArray(value)
183
+ ? value as Record<string, unknown>
184
+ : null;
185
+ }
186
+
187
+ export function stringValue(value: unknown): string | undefined {
188
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
189
+ }
190
+
191
+ function firstInteger(...values: unknown[]): number | null {
192
+ for (const value of values) {
193
+ if (Number.isSafeInteger(value) && (value as number) >= 0) return value as number;
194
+ }
195
+ return null;
196
+ }
197
+
198
+ function safeInteger(value: unknown): number {
199
+ return Number.isSafeInteger(value) && (value as number) >= 0 ? value as number : 0;
200
+ }
201
+
202
+ function stableJson(value: unknown): string {
203
+ try {
204
+ return JSON.stringify(value);
205
+ } catch {
206
+ return String(value);
207
+ }
208
+ }
@@ -0,0 +1,83 @@
1
+ import type { SpendGuard } from '../spend-guard';
2
+ import { createFrameworkGuard, recordFrameworkReceipt, redactDataPlane, safeRequestShape, stringValue, type FrameworkAdapterOptions } from './common';
3
+
4
+ export interface HermesToolEvent {
5
+ toolName?: string;
6
+ name?: string;
7
+ args?: Record<string, unknown>;
8
+ toolArgs?: Record<string, unknown>;
9
+ workflowId?: string;
10
+ sessionId?: string;
11
+ metadata?: Record<string, unknown>;
12
+ result?: Record<string, unknown>;
13
+ }
14
+
15
+ export interface HermesHookResult {
16
+ allow: boolean;
17
+ approved?: boolean;
18
+ reason?: string;
19
+ receiptId?: string;
20
+ editedArgs?: Record<string, unknown>;
21
+ }
22
+
23
+ export interface HermesPlugin {
24
+ name: 'agentguard-spend';
25
+ guard: SpendGuard;
26
+ hooks: {
27
+ 'pre-tool-call': (event: HermesToolEvent) => Promise<HermesHookResult>;
28
+ approval: (event: HermesToolEvent) => Promise<HermesHookResult>;
29
+ 'post-tool-call': (event: HermesToolEvent) => Promise<HermesHookResult>;
30
+ };
31
+ preToolCall(event: HermesToolEvent): Promise<HermesHookResult>;
32
+ approval(event: HermesToolEvent): Promise<HermesHookResult>;
33
+ postToolCall(event: HermesToolEvent): Promise<HermesHookResult>;
34
+ }
35
+
36
+ export function createHermesPlugin(opts: FrameworkAdapterOptions): HermesPlugin {
37
+ const guard = createFrameworkGuard({ ...opts, framework: 'hermes' });
38
+ const plugin: HermesPlugin = {
39
+ name: 'agentguard-spend',
40
+ guard,
41
+ hooks: {
42
+ 'pre-tool-call': (event) => preToolCall(guard, opts, event),
43
+ approval: (event) => approval(guard, opts, event),
44
+ 'post-tool-call': (event) => postToolCall(guard, event),
45
+ },
46
+ preToolCall: (event) => preToolCall(guard, opts, event),
47
+ approval: (event) => approval(guard, opts, event),
48
+ postToolCall: (event) => postToolCall(guard, event),
49
+ };
50
+ return plugin;
51
+ }
52
+
53
+ export const agentguardHermesPlugin = createHermesPlugin;
54
+
55
+ async function preToolCall(guard: SpendGuard, opts: FrameworkAdapterOptions, event: HermesToolEvent): Promise<HermesHookResult> {
56
+ const gate = await guard.guardToolCall({
57
+ toolName: toolName(event),
58
+ toolArgs: safeRequestShape(redactDataPlane(event.toolArgs ?? event.args ?? {})),
59
+ workflowId: event.workflowId ?? event.sessionId,
60
+ scope: opts.scope,
61
+ });
62
+ if (!gate.approved) return { allow: false, approved: false, reason: gate.decision.reasons.join('; ') };
63
+ return { allow: true, approved: true, editedArgs: gate.editedArgs };
64
+ }
65
+
66
+ async function approval(guard: SpendGuard, opts: FrameworkAdapterOptions, event: HermesToolEvent): Promise<HermesHookResult> {
67
+ return preToolCall(guard, opts, event);
68
+ }
69
+
70
+ async function postToolCall(guard: SpendGuard, event: HermesToolEvent): Promise<HermesHookResult> {
71
+ const { decision } = await recordFrameworkReceipt(guard, 'hermes', {
72
+ event: 'post-tool-call',
73
+ toolName: toolName(event),
74
+ workflowId: event.workflowId ?? event.sessionId ?? null,
75
+ metadata: redactDataPlane(event.metadata ?? {}),
76
+ result: redactDataPlane(event.result ?? {}),
77
+ });
78
+ return { allow: true, approved: true, receiptId: decision.decisionId };
79
+ }
80
+
81
+ function toolName(event: HermesToolEvent): string {
82
+ return stringValue(event.toolName) ?? stringValue(event.name) ?? 'unknown_tool';
83
+ }
@@ -0,0 +1,5 @@
1
+ export * from './common';
2
+ export * from './openrouter';
3
+ export * from './vercel-ai';
4
+ export * from './claude-code';
5
+ export * from './hermes';
@@ -0,0 +1,90 @@
1
+ import { withSpendGuard, type OpenAIBindingOptions, type SpendGuardConfig } from '../spend-guard';
2
+ import type { CapabilityTier, SpendPolicy, SpendScope } from '../types';
3
+ import { createFrameworkGuard, preflightFrameworkCall, settleFrameworkCall, type FrameworkAdapterOptions } from './common';
4
+
5
+ export interface OpenRouterBindingOptions {
6
+ policy: SpendPolicy;
7
+ scope: SpendScope;
8
+ capabilityClaim?: CapabilityTier;
9
+ config?: Omit<SpendGuardConfig, 'policy'>;
10
+ licenseKey?: string;
11
+ openRouterBaseUrl?: string;
12
+ }
13
+
14
+ export function withSpendGuardOpenRouter<TClient>(client: TClient, opts: OpenRouterBindingOptions): TClient {
15
+ const config = {
16
+ ...(opts.config ?? {}),
17
+ openRouterBaseUrl: opts.openRouterBaseUrl ?? opts.config?.openRouterBaseUrl ?? 'https://openrouter.ai/api/v1',
18
+ providerRoute: opts.config?.providerRoute ?? 'openrouter',
19
+ } as Omit<SpendGuardConfig, 'policy'>;
20
+ return withSpendGuard(client, {
21
+ policy: opts.policy,
22
+ scope: opts.scope,
23
+ capabilityClaim: opts.capabilityClaim,
24
+ licenseKey: opts.licenseKey,
25
+ config,
26
+ } satisfies OpenAIBindingOptions) as TClient;
27
+ }
28
+
29
+ export function withOpenRouterSpendGuard<TClient>(client: TClient, opts: OpenRouterBindingOptions): TClient {
30
+ return withSpendGuardOpenRouter(client, opts);
31
+ }
32
+
33
+ export interface OpenRouterFetchOptions extends FrameworkAdapterOptions {
34
+ fetchImpl?: (url: string, init?: Record<string, unknown>) => Promise<unknown>;
35
+ endpoint?: string;
36
+ }
37
+
38
+ export function createOpenRouterFetch(opts: OpenRouterFetchOptions): (url: string, init?: Record<string, unknown>) => Promise<unknown> {
39
+ const guard = createFrameworkGuard({ ...opts, defaultModel: opts.defaultModel ?? 'openrouter/auto', framework: 'openrouter' });
40
+ const fetchImpl = opts.fetchImpl ?? (globalThis as unknown as { fetch?: (url: string, init?: Record<string, unknown>) => Promise<unknown> }).fetch;
41
+ if (!fetchImpl) throw new Error('createOpenRouterFetch: fetch is not available');
42
+ const endpoint = opts.endpoint ?? 'https://openrouter.ai/api/v1/chat/completions';
43
+
44
+ return async (url: string, init: Record<string, unknown> = {}) => {
45
+ const target = String(url);
46
+ if (!target.startsWith(endpoint)) return fetchImpl(target, init);
47
+ const body = parseJsonBody(init.body);
48
+ const preflight = await preflightFrameworkCall(guard, opts, {
49
+ framework: 'openrouter',
50
+ model: typeof body.model === 'string' ? body.model : opts.defaultModel ?? 'openrouter/auto',
51
+ params: body,
52
+ metadata: metadataRecord(body.metadata),
53
+ requestShape: {
54
+ endpoint: '/api/v1/chat/completions',
55
+ stream: body.stream === true,
56
+ maxTokens: typeof body.max_tokens === 'number' ? body.max_tokens : undefined,
57
+ toolCount: Array.isArray(body.tools) ? body.tools.length : 0,
58
+ },
59
+ });
60
+ const nextBody = preflight.decision.action === 'downgrade' && preflight.decision.modelResolved !== preflight.decision.modelRequested
61
+ ? { ...body, model: preflight.decision.modelResolved }
62
+ : body;
63
+ const response = await fetchImpl(target, { ...init, body: JSON.stringify(nextBody) });
64
+ await settleFrameworkCall(preflight, await responseJson(response));
65
+ return response;
66
+ };
67
+ }
68
+
69
+ function parseJsonBody(body: unknown): Record<string, unknown> {
70
+ if (typeof body === 'string') {
71
+ try { return JSON.parse(body) as Record<string, unknown>; } catch { return {}; }
72
+ }
73
+ if (body && typeof body === 'object' && !Array.isArray(body)) return body as Record<string, unknown>;
74
+ return {};
75
+ }
76
+
77
+ function metadataRecord(value: unknown): Record<string, unknown> {
78
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
79
+ }
80
+
81
+ async function responseJson(response: unknown): Promise<unknown> {
82
+ const maybe = response as { clone?: () => { json?: () => Promise<unknown> }; json?: () => Promise<unknown> };
83
+ try {
84
+ if (typeof maybe.clone === 'function') return await maybe.clone().json?.();
85
+ if (typeof maybe.json === 'function') return await maybe.json();
86
+ } catch {
87
+ return null;
88
+ }
89
+ return null;
90
+ }
@@ -0,0 +1,118 @@
1
+ import type { SpendGuard } from '../spend-guard';
2
+ import { createFrameworkGuard, modelFromUnknown, objectRecord, preflightFrameworkCall, recordFrameworkReceipt, redactDataPlane, settleFrameworkCall, type FrameworkAdapterOptions, type FrameworkPreflight } from './common';
3
+
4
+ export type AgentGuardAiSdkMiddleware = {
5
+ specificationVersion: 'v3';
6
+ transformParams?: (args: { type: 'generate' | 'stream'; params: unknown; model: unknown }) => Promise<unknown> | unknown;
7
+ wrapGenerate?: (args: { doGenerate: () => Promise<unknown>; doStream?: () => Promise<unknown>; params: unknown; model: unknown }) => Promise<unknown>;
8
+ wrapStream?: (args: { doGenerate?: () => Promise<unknown>; doStream: () => Promise<unknown>; params: unknown; model: unknown }) => Promise<unknown>;
9
+ };
10
+
11
+ export interface AgentGuardAiSdkOptions extends FrameworkAdapterOptions {
12
+ framework?: 'vercel-ai-sdk';
13
+ }
14
+
15
+ export function agentguardAiSdkMiddleware(opts: AgentGuardAiSdkOptions): AgentGuardAiSdkMiddleware {
16
+ const guard = createFrameworkGuard({ ...opts, framework: 'vercel-ai-sdk' });
17
+ return {
18
+ specificationVersion: 'v3',
19
+ transformParams: ({ params }) => params,
20
+ wrapGenerate: async ({ doGenerate, params, model }) => {
21
+ const preflight = await aiSdkPreflight(guard, opts, params, model, 'generate');
22
+ const result = await doGenerate();
23
+ await settleFrameworkCall(preflight, result);
24
+ await recordFrameworkReceipt(guard, 'vercel-ai-sdk', {
25
+ event: 'post_generate',
26
+ decisionId: preflight.decision.decisionId,
27
+ model: preflight.decision.modelResolved,
28
+ requestShape: requestShape(params, 'generate'),
29
+ hasUsage: Boolean(objectRecord(result)?.usage),
30
+ });
31
+ return result;
32
+ },
33
+ wrapStream: async ({ doStream, params, model }) => {
34
+ const preflight = await aiSdkPreflight(guard, opts, params, model, 'stream');
35
+ const result = await doStream();
36
+ return wrapAiSdkStreamResult(guard, preflight, result, params);
37
+ },
38
+ };
39
+ }
40
+
41
+ export const agentguard = agentguardAiSdkMiddleware;
42
+
43
+ async function aiSdkPreflight(
44
+ guard: SpendGuard,
45
+ opts: AgentGuardAiSdkOptions,
46
+ params: unknown,
47
+ model: unknown,
48
+ type: 'generate' | 'stream',
49
+ ): Promise<FrameworkPreflight> {
50
+ return preflightFrameworkCall(guard, opts, {
51
+ framework: 'vercel-ai-sdk',
52
+ model: modelFromUnknown(model, opts.defaultModel ?? 'unknown'),
53
+ params,
54
+ metadata: agentguardMetadata(params),
55
+ requestShape: requestShape(params, type),
56
+ });
57
+ }
58
+
59
+ function requestShape(params: unknown, type: 'generate' | 'stream'): Record<string, unknown> {
60
+ const record = objectRecord(params) ?? {};
61
+ const tools = objectRecord(record.tools) ?? {};
62
+ return {
63
+ type,
64
+ maxOutputTokens: record.maxOutputTokens ?? record.maxTokens,
65
+ toolNames: Object.keys(tools).sort(),
66
+ providerOptionsPresent: Boolean(record.providerOptions),
67
+ redacted: redactDataPlane({ prompt: record.prompt, messages: record.messages }),
68
+ };
69
+ }
70
+
71
+ function agentguardMetadata(params: unknown): Record<string, unknown> {
72
+ const record = objectRecord(params) ?? {};
73
+ const providerOptions = objectRecord(record.providerOptions) ?? objectRecord(record.providerMetadata) ?? {};
74
+ const metadata = objectRecord(providerOptions.agentguard) ?? {};
75
+ return metadata;
76
+ }
77
+
78
+ async function wrapAiSdkStreamResult(
79
+ guard: SpendGuard,
80
+ preflight: FrameworkPreflight,
81
+ result: unknown,
82
+ params: unknown,
83
+ ): Promise<unknown> {
84
+ const record = objectRecord(result);
85
+ const stream = record?.stream;
86
+ if (!stream || typeof (stream as { pipeThrough?: unknown }).pipeThrough !== 'function') {
87
+ await settleFrameworkCall(preflight, result, true);
88
+ await recordFrameworkReceipt(guard, 'vercel-ai-sdk', {
89
+ event: 'post_stream',
90
+ decisionId: preflight.decision.decisionId,
91
+ model: preflight.decision.modelResolved,
92
+ requestShape: requestShape(params, 'stream'),
93
+ partial: true,
94
+ });
95
+ return result;
96
+ }
97
+ const TransformStreamCtor = (globalThis as unknown as { TransformStream?: new (handlers: Record<string, unknown>) => unknown }).TransformStream;
98
+ if (!TransformStreamCtor) return result;
99
+ let finalUsage: unknown = null;
100
+ const transform = new TransformStreamCtor({
101
+ transform(chunk: unknown, controller: { enqueue: (chunk: unknown) => void }) {
102
+ const usage = objectRecord(chunk)?.usage;
103
+ if (usage) finalUsage = { usage };
104
+ controller.enqueue(chunk);
105
+ },
106
+ async flush() {
107
+ await settleFrameworkCall(preflight, finalUsage ?? result);
108
+ await recordFrameworkReceipt(guard, 'vercel-ai-sdk', {
109
+ event: 'post_stream',
110
+ decisionId: preflight.decision.decisionId,
111
+ model: preflight.decision.modelResolved,
112
+ requestShape: requestShape(params, 'stream'),
113
+ partial: false,
114
+ });
115
+ },
116
+ });
117
+ return { ...record, stream: (stream as { pipeThrough: (transform: unknown) => unknown }).pipeThrough(transform) };
118
+ }
@@ -192,6 +192,7 @@ export async function verifyReceiptDag(
192
192
  entryHash: node.entryHash,
193
193
  signature: node.signature,
194
194
  signerFingerprint: node.signerFingerprint,
195
+ builderCode: node.builderCode ?? undefined,
195
196
  },
196
197
  publicKey,
197
198
  );
@@ -348,6 +349,7 @@ function normalizeDagNode(input: ReceiptDagInput): NormalizedDagNode {
348
349
  entryHash: candidate.entryHash,
349
350
  signature: candidate.signature,
350
351
  signerFingerprint: candidate.signerFingerprint,
352
+ builderCode: candidate.builderCode ?? null,
351
353
  timestamp: stringOrNull(decision.timestamp) ?? new Date(0).toISOString(),
352
354
  previousHash,
353
355
  legacyPreviousHash: previousHash,