@agentguard-run/spend 0.15.11 → 0.15.12

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
@@ -223,6 +223,42 @@ Only `InvokeModel` / `InvokeModelWithResponseStream` commands are gated; other B
223
223
 
224
224
  Python includes OpenAI, Anthropic, Bedrock, LangChain, CrewAI, and LlamaIndex integration helpers.
225
225
 
226
+ ## Framework integrations
227
+
228
+ Drop-in adapters translate each framework's own hook/middleware surface into the
229
+ same shared enforcement core (`guard.decide()` + the Ed25519-signed receipt
230
+ chain). They never proxy prompts, completions, or provider keys — only redacted
231
+ request shape reaches a receipt. One line each:
232
+
233
+ ```ts
234
+ // Vercel AI SDK — wrapLanguageModel middleware (runs guard.decide before generate/stream)
235
+ import { wrapLanguageModel } from 'ai';
236
+ import { agentguardAiSdkMiddleware } from '@agentguard-run/spend/frameworks/vercel-ai';
237
+ const governed = wrapLanguageModel({ model, middleware: agentguardAiSdkMiddleware({ policy, scope }) });
238
+
239
+ // OpenAI SDK / OpenAI Agents SDK — wrap the client's chat.completions.create
240
+ import OpenAI from 'openai';
241
+ import { withSpendGuardOpenAI } from '@agentguard-run/spend/frameworks/openai';
242
+ const client = withSpendGuardOpenAI(new OpenAI(), { policy, scope });
243
+
244
+ // LangChain (JS) — BaseCallbackHandler (handleChatModelStart preflights, handleLLMEnd settles)
245
+ import { ChatOpenAI } from '@langchain/openai';
246
+ import { createLangChainHandler } from '@agentguard-run/spend/frameworks/langchain';
247
+ const llm = new ChatOpenAI({ callbacks: [createLangChainHandler({ policy, scope })] });
248
+
249
+ // OpenRouter — wrap an OpenAI-compatible client pointed at openrouter.ai
250
+ import { withSpendGuardOpenRouter } from '@agentguard-run/spend/frameworks/openrouter';
251
+ const or = withSpendGuardOpenRouter(new OpenAI({ baseURL: 'https://openrouter.ai/api/v1' }), { policy, scope });
252
+
253
+ // Claude Code — PreToolUse/PostToolUse hooks
254
+ import { createClaudeCodeHooks } from '@agentguard-run/spend/frameworks/claude-code';
255
+ const hooks = createClaudeCodeHooks({ policy, scope });
256
+
257
+ // Hermes — pre-tool-call / approval / post-tool-call plugin hooks
258
+ import { createHermesPlugin } from '@agentguard-run/spend/frameworks/hermes';
259
+ export default createHermesPlugin({ policy, scope });
260
+ ```
261
+
226
262
  ## Spend-aware model router (removes vendor risk)
227
263
 
228
264
  `createRouter(...)` composes the bindings, the enforcement engine, and the signed receipt chain into one call: it picks a candidate model across providers by policy, enforces the spend cap with the **same** enforcement path (block / downgrade / allow), executes it, and **fails over to the next provider** on a provider error/timeout or a cap breach on the chosen model. Every route is recorded in a signed, hash-chained routing receipt (candidates considered, winner, reason, resolved cost, fallbacks) that verifies with the existing `verifyChain`. No prompt/completion content or keys ever enter the receipt.
@@ -4,4 +4,6 @@ export * from './vercel-ai';
4
4
  export * from './claude-code';
5
5
  export * from './hermes';
6
6
  export * from './hermes-kanban';
7
+ export * from './langchain';
8
+ export * from './openai';
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/frameworks/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/frameworks/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC"}
@@ -20,4 +20,6 @@ __exportStar(require("./vercel-ai"), exports);
20
20
  __exportStar(require("./claude-code"), exports);
21
21
  __exportStar(require("./hermes"), exports);
22
22
  __exportStar(require("./hermes-kanban"), exports);
23
+ __exportStar(require("./langchain"), exports);
24
+ __exportStar(require("./openai"), exports);
23
25
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/frameworks/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,+CAA6B;AAC7B,8CAA4B;AAC5B,gDAA8B;AAC9B,2CAAyB;AACzB,kDAAgC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/frameworks/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,+CAA6B;AAC7B,8CAA4B;AAC5B,gDAA8B;AAC9B,2CAAyB;AACzB,kDAAgC;AAChC,8CAA4B;AAC5B,2CAAyB"}
@@ -0,0 +1,52 @@
1
+ import type { SpendGuard } from '../spend-guard';
2
+ import { type FrameworkAdapterOptions } from './common';
3
+ /**
4
+ * LangChain.js callback handler that runs `guard.decide()` before every LLM /
5
+ * chat-model call and settles real token usage after it completes.
6
+ *
7
+ * Hook point: LangChain's `BaseCallbackHandler` lifecycle methods
8
+ * `handleChatModelStart` / `handleLLMStart` (preflight, may throw
9
+ * AgentGuardBlockedError) and `handleLLMEnd` (usage settlement). We do NOT
10
+ * extend the real BaseCallbackHandler (that would force a peer-dep import at
11
+ * module load); LangChain duck-types handlers by these method names, and we set
12
+ * `awaitHandlers = true` so a thrown block aborts the call.
13
+ *
14
+ * This adapter reuses the shared framework core (createFrameworkGuard +
15
+ * preflightFrameworkCall + settleFrameworkCall). It never proxies prompts,
16
+ * completions, or provider keys — only redacted request shape leaves for the
17
+ * signed receipt.
18
+ *
19
+ * @example
20
+ * import { ChatOpenAI } from '@langchain/openai';
21
+ * import { createLangChainHandler } from '@agentguard-run/spend/frameworks/langchain';
22
+ *
23
+ * const handler = createLangChainHandler({ policy, scope });
24
+ * const llm = new ChatOpenAI({ callbacks: [handler] });
25
+ * await llm.invoke('hello'); // throws AgentGuardBlockedError if a cap blocks
26
+ */
27
+ export interface LangChainAdapterOptions extends FrameworkAdapterOptions {
28
+ framework?: 'langchain';
29
+ }
30
+ interface LangChainSerialized {
31
+ id?: string[];
32
+ kwargs?: Record<string, unknown>;
33
+ [key: string]: unknown;
34
+ }
35
+ export declare class AgentGuardLangChainHandler {
36
+ readonly name = "agentguard_spend";
37
+ /** Ensures LangChain awaits the handler so a thrown block actually aborts. */
38
+ readonly awaitHandlers = true;
39
+ readonly guard: SpendGuard;
40
+ private readonly opts;
41
+ private readonly pending;
42
+ constructor(opts: LangChainAdapterOptions);
43
+ handleChatModelStart(llm: LangChainSerialized, messages: unknown[][], runId: string, _parentRunId?: string, extraParams?: Record<string, unknown>, _tags?: string[], metadata?: Record<string, unknown>): Promise<void>;
44
+ handleLLMStart(llm: LangChainSerialized, prompts: unknown[], runId: string, _parentRunId?: string, extraParams?: Record<string, unknown>, _tags?: string[], metadata?: Record<string, unknown>): Promise<void>;
45
+ handleLLMEnd(output: unknown, runId: string): Promise<void>;
46
+ handleLLMError(_error: unknown, runId: string): Promise<void>;
47
+ private start;
48
+ }
49
+ export declare function createLangChainHandler(opts: LangChainAdapterOptions): AgentGuardLangChainHandler;
50
+ export declare const agentguardLangChainHandler: typeof createLangChainHandler;
51
+ export {};
52
+ //# sourceMappingURL=langchain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"langchain.d.ts","sourceRoot":"","sources":["../../src/frameworks/langchain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAUL,KAAK,uBAAuB,EAE7B,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,uBAAwB,SAAQ,uBAAuB;IACtE,SAAS,CAAC,EAAE,WAAW,CAAC;CACzB;AAED,UAAU,mBAAmB;IAC3B,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,0BAA0B;IACrC,QAAQ,CAAC,IAAI,sBAAsB;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,QAAQ;IAC9B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0B;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;gBAErD,IAAI,EAAE,uBAAuB;IAKnC,oBAAoB,CACxB,GAAG,EAAE,mBAAmB,EACxB,QAAQ,EAAE,OAAO,EAAE,EAAE,EACrB,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,KAAK,CAAC,EAAE,MAAM,EAAE,EAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC;IAIV,cAAc,CAClB,GAAG,EAAE,mBAAmB,EACxB,OAAO,EAAE,OAAO,EAAE,EAClB,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,KAAK,CAAC,EAAE,MAAM,EAAE,EAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC;IAIV,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY3D,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAIrD,KAAK;CA2BpB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,uBAAuB,GAAG,0BAA0B,CAEhG;AAED,eAAO,MAAM,0BAA0B,+BAAyB,CAAC"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agentguardLangChainHandler = exports.AgentGuardLangChainHandler = void 0;
4
+ exports.createLangChainHandler = createLangChainHandler;
5
+ const common_1 = require("./common");
6
+ class AgentGuardLangChainHandler {
7
+ name = 'agentguard_spend';
8
+ /** Ensures LangChain awaits the handler so a thrown block actually aborts. */
9
+ awaitHandlers = true;
10
+ guard;
11
+ opts;
12
+ pending = new Map();
13
+ constructor(opts) {
14
+ this.opts = { ...opts, framework: 'langchain' };
15
+ this.guard = (0, common_1.createFrameworkGuard)(this.opts);
16
+ }
17
+ async handleChatModelStart(llm, messages, runId, _parentRunId, extraParams, _tags, metadata) {
18
+ await this.start(runId, llm, messages, extraParams, metadata);
19
+ }
20
+ async handleLLMStart(llm, prompts, runId, _parentRunId, extraParams, _tags, metadata) {
21
+ await this.start(runId, llm, prompts, extraParams, metadata);
22
+ }
23
+ async handleLLMEnd(output, runId) {
24
+ const preflight = this.pending.get(runId);
25
+ if (!preflight)
26
+ return;
27
+ this.pending.delete(runId);
28
+ await (0, common_1.settleFrameworkCall)(preflight, normalizeUsage(output));
29
+ await (0, common_1.recordFrameworkReceipt)(this.guard, 'langchain', {
30
+ event: 'llm_end',
31
+ decisionId: preflight.decision.decisionId,
32
+ model: preflight.decision.modelResolved,
33
+ });
34
+ }
35
+ async handleLLMError(_error, runId) {
36
+ this.pending.delete(runId);
37
+ }
38
+ async start(runId, llm, payload, extraParams, metadata) {
39
+ const invocation = (0, common_1.objectRecord)(extraParams?.invocation_params) ?? {};
40
+ const model = (0, common_1.modelFromUnknown)(invocation.model ?? invocation.model_name ?? lastId(llm) ?? (0, common_1.objectRecord)(llm?.kwargs)?.model, this.opts.defaultModel ?? 'unknown');
41
+ const preflight = await (0, common_1.preflightFrameworkCall)(this.guard, this.opts, {
42
+ framework: 'langchain',
43
+ model,
44
+ params: payload,
45
+ outputTokens: (0, common_1.outputTokensFromParams)(invocation, this.opts.defaultOutputTokens ?? 1024),
46
+ metadata: (0, common_1.redactDataPlane)(metadata ?? {}),
47
+ workflowId: (0, common_1.stringValue)(metadata?.workflowId) ?? (0, common_1.stringValue)(metadata?.thread_id),
48
+ requestShape: {
49
+ toolCount: Array.isArray(invocation.tools) ? invocation.tools.length : 0,
50
+ maxTokens: invocation.max_tokens ?? invocation.maxTokens,
51
+ stream: invocation.stream === true,
52
+ },
53
+ });
54
+ this.pending.set(runId, preflight);
55
+ }
56
+ }
57
+ exports.AgentGuardLangChainHandler = AgentGuardLangChainHandler;
58
+ function createLangChainHandler(opts) {
59
+ return new AgentGuardLangChainHandler(opts);
60
+ }
61
+ exports.agentguardLangChainHandler = createLangChainHandler;
62
+ function lastId(llm) {
63
+ const id = llm?.id;
64
+ return Array.isArray(id) && id.length ? id[id.length - 1] : undefined;
65
+ }
66
+ /**
67
+ * Map a LangChain LLMResult into the `{ usage: { inputTokens, outputTokens } }`
68
+ * shape that settleFrameworkCall expects. LangChain reports token usage on
69
+ * `llmOutput.tokenUsage` (OpenAI) or `llmOutput.usage`.
70
+ */
71
+ function normalizeUsage(output) {
72
+ const record = (0, common_1.objectRecord)(output);
73
+ const llmOutput = (0, common_1.objectRecord)(record?.llmOutput);
74
+ const tokenUsage = (0, common_1.objectRecord)(llmOutput?.tokenUsage) ?? (0, common_1.objectRecord)(llmOutput?.usage);
75
+ if (!tokenUsage)
76
+ return null;
77
+ return {
78
+ usage: {
79
+ inputTokens: tokenUsage.promptTokens ?? tokenUsage.prompt_tokens ?? tokenUsage.input_tokens ?? tokenUsage.inputTokens,
80
+ outputTokens: tokenUsage.completionTokens ?? tokenUsage.completion_tokens ?? tokenUsage.output_tokens ?? tokenUsage.outputTokens,
81
+ },
82
+ };
83
+ }
84
+ //# sourceMappingURL=langchain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"langchain.js","sourceRoot":"","sources":["../../src/frameworks/langchain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type OpenAIBindingOptions } from '../spend-guard';
2
+ /**
3
+ * One-line spend governance for the OpenAI SDK and the OpenAI Agents SDK.
4
+ *
5
+ * Hook point: the OpenAI client's `chat.completions.create`. `withSpendGuard`
6
+ * (the shared core binding) wraps that method so `guard.decide()` runs before
7
+ * every dispatch and can block/downgrade. Because the OpenAI Agents SDK
8
+ * (`@openai/agents`) drives the same underlying `OpenAI` client, passing the
9
+ * guarded client to the agent runner governs every model call the agent makes.
10
+ *
11
+ * This is a thin, named re-export of `withSpendGuard` so the OpenAI integration
12
+ * is discoverable at `@agentguard-run/spend/frameworks/openai`. It reuses the
13
+ * existing enforcement verbatim — no second code path.
14
+ *
15
+ * @example OpenAI SDK
16
+ * import OpenAI from 'openai';
17
+ * import { withSpendGuardOpenAI } from '@agentguard-run/spend/frameworks/openai';
18
+ *
19
+ * const client = withSpendGuardOpenAI(new OpenAI(), { policy, scope });
20
+ * await client.chat.completions.create({ model: 'gpt-5', messages });
21
+ *
22
+ * @example OpenAI Agents SDK
23
+ * import OpenAI from 'openai';
24
+ * import { Agent, run, setDefaultOpenAIClient } from '@openai/agents';
25
+ * import { withSpendGuardOpenAI } from '@agentguard-run/spend/frameworks/openai';
26
+ *
27
+ * setDefaultOpenAIClient(withSpendGuardOpenAI(new OpenAI(), { policy, scope }));
28
+ * await run(new Agent({ name: 'assistant' }), 'hello');
29
+ */
30
+ export declare function withSpendGuardOpenAI<TClient>(client: TClient, opts: OpenAIBindingOptions): TClient;
31
+ export declare const withOpenAISpendGuard: typeof withSpendGuardOpenAI;
32
+ export type { OpenAIBindingOptions } from '../spend-guard';
33
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/frameworks/openai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAElG;AAED,eAAO,MAAM,oBAAoB,6BAAuB,CAAC;AACzD,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withOpenAISpendGuard = void 0;
4
+ exports.withSpendGuardOpenAI = withSpendGuardOpenAI;
5
+ const spend_guard_1 = require("../spend-guard");
6
+ /**
7
+ * One-line spend governance for the OpenAI SDK and the OpenAI Agents SDK.
8
+ *
9
+ * Hook point: the OpenAI client's `chat.completions.create`. `withSpendGuard`
10
+ * (the shared core binding) wraps that method so `guard.decide()` runs before
11
+ * every dispatch and can block/downgrade. Because the OpenAI Agents SDK
12
+ * (`@openai/agents`) drives the same underlying `OpenAI` client, passing the
13
+ * guarded client to the agent runner governs every model call the agent makes.
14
+ *
15
+ * This is a thin, named re-export of `withSpendGuard` so the OpenAI integration
16
+ * is discoverable at `@agentguard-run/spend/frameworks/openai`. It reuses the
17
+ * existing enforcement verbatim — no second code path.
18
+ *
19
+ * @example OpenAI SDK
20
+ * import OpenAI from 'openai';
21
+ * import { withSpendGuardOpenAI } from '@agentguard-run/spend/frameworks/openai';
22
+ *
23
+ * const client = withSpendGuardOpenAI(new OpenAI(), { policy, scope });
24
+ * await client.chat.completions.create({ model: 'gpt-5', messages });
25
+ *
26
+ * @example OpenAI Agents SDK
27
+ * import OpenAI from 'openai';
28
+ * import { Agent, run, setDefaultOpenAIClient } from '@openai/agents';
29
+ * import { withSpendGuardOpenAI } from '@agentguard-run/spend/frameworks/openai';
30
+ *
31
+ * setDefaultOpenAIClient(withSpendGuardOpenAI(new OpenAI(), { policy, scope }));
32
+ * await run(new Agent({ name: 'assistant' }), 'hello');
33
+ */
34
+ function withSpendGuardOpenAI(client, opts) {
35
+ return (0, spend_guard_1.withSpendGuard)(client, opts);
36
+ }
37
+ exports.withOpenAISpendGuard = withSpendGuardOpenAI;
38
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/frameworks/openai.ts"],"names":[],"mappings":";;;AA8BA,oDAEC;AAhCD,gDAA2E;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,SAAgB,oBAAoB,CAAU,MAAe,EAAE,IAA0B;IACvF,OAAO,IAAA,4BAAc,EAAC,MAAM,EAAE,IAAI,CAAY,CAAC;AACjD,CAAC;AAEY,QAAA,oBAAoB,GAAG,oBAAoB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -35,7 +35,7 @@ export { withSpendGuardBedrock, type BedrockBindingOptions } from './bindings/be
35
35
  export { recommend, defaultKey, type ModelRouteRecommendation, type RecommendOptions } from './router';
36
36
  export { createRouter, RoutingExhaustedError, type ModelRouter, type CreateRouterConfig, type RouterCandidate, type RouterPolicy, type RouterProvider, type RouterRequest, type RouteResult, type RouteReason, type RouteFallback, } from './router-engine';
37
37
  export { DEFAULT_LOCALE, SUPPORTED_LOCALES, TRANSLATIONS, type SupportedLocale, resolveLocale, t, formatBlockedTrace, type BlockedTraceArgs, } from './i18n';
38
- export declare const AGENTGUARD_SPEND_VERSION = "0.15.11";
38
+ export declare const AGENTGUARD_SPEND_VERSION = "0.15.12";
39
39
  export declare const agentguard: {
40
40
  workflow: typeof runWorkflow;
41
41
  };
package/dist/index.js CHANGED
@@ -167,7 +167,7 @@ Object.defineProperty(exports, "TRANSLATIONS", { enumerable: true, get: function
167
167
  Object.defineProperty(exports, "resolveLocale", { enumerable: true, get: function () { return i18n_1.resolveLocale; } });
168
168
  Object.defineProperty(exports, "t", { enumerable: true, get: function () { return i18n_1.t; } });
169
169
  Object.defineProperty(exports, "formatBlockedTrace", { enumerable: true, get: function () { return i18n_1.formatBlockedTrace; } });
170
- exports.AGENTGUARD_SPEND_VERSION = '0.15.11';
170
+ exports.AGENTGUARD_SPEND_VERSION = '0.15.12';
171
171
  exports.agentguard = {
172
172
  workflow: context_1.workflow,
173
173
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentguard-run/spend",
3
- "version": "0.15.11",
3
+ "version": "0.15.12",
4
4
  "description": "All terminology and labels used in AgentGuard materials are descriptive of software functionality only, not legal definitions or guarantees of compliance. Terms like receipt, audit log, evidence, audit trail, and attestation refer solely to cryptographically-signed records produced by the software. Full functional-use disclaimer in README.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -110,6 +110,16 @@
110
110
  "require": "./dist/frameworks/openrouter.js",
111
111
  "default": "./dist/frameworks/openrouter.js"
112
112
  },
113
+ "./frameworks/langchain": {
114
+ "types": "./dist/frameworks/langchain.d.ts",
115
+ "require": "./dist/frameworks/langchain.js",
116
+ "default": "./dist/frameworks/langchain.js"
117
+ },
118
+ "./frameworks/openai": {
119
+ "types": "./dist/frameworks/openai.d.ts",
120
+ "require": "./dist/frameworks/openai.js",
121
+ "default": "./dist/frameworks/openai.js"
122
+ },
113
123
  "./operating-pack": {
114
124
  "types": "./dist/operating-pack.d.ts",
115
125
  "require": "./dist/operating-pack.js",
@@ -42,3 +42,38 @@ const client = withSpendGuardOpenRouter(new OpenAI({
42
42
  apiKey: process.env.OPENROUTER_API_KEY,
43
43
  }), { policy, scope });
44
44
  ```
45
+
46
+ ## OpenAI SDK / OpenAI Agents SDK
47
+
48
+ Hook point: the OpenAI client's `chat.completions.create`. Wrap the client once
49
+ and every call the agent runner makes is governed.
50
+
51
+ ```ts
52
+ import OpenAI from 'openai';
53
+ import { withSpendGuardOpenAI } from '@agentguard-run/spend/frameworks/openai';
54
+
55
+ const client = withSpendGuardOpenAI(new OpenAI(), { policy, scope });
56
+ ```
57
+
58
+ ```ts
59
+ // OpenAI Agents SDK — govern every model call the agent makes
60
+ import OpenAI from 'openai';
61
+ import { run, Agent, setDefaultOpenAIClient } from '@openai/agents';
62
+ import { withSpendGuardOpenAI } from '@agentguard-run/spend/frameworks/openai';
63
+
64
+ setDefaultOpenAIClient(withSpendGuardOpenAI(new OpenAI(), { policy, scope }));
65
+ ```
66
+
67
+ ## LangChain (JS)
68
+
69
+ Hook point: LangChain's `BaseCallbackHandler` lifecycle
70
+ (`handleChatModelStart` / `handleLLMStart` preflight `guard.decide()`,
71
+ `handleLLMEnd` settles real token usage). A blocked call throws
72
+ `AgentGuardBlockedError`.
73
+
74
+ ```ts
75
+ import { ChatOpenAI } from '@langchain/openai';
76
+ import { createLangChainHandler } from '@agentguard-run/spend/frameworks/langchain';
77
+
78
+ const llm = new ChatOpenAI({ callbacks: [createLangChainHandler({ policy, scope })] });
79
+ ```
@@ -4,3 +4,5 @@ export * from './vercel-ai';
4
4
  export * from './claude-code';
5
5
  export * from './hermes';
6
6
  export * from './hermes-kanban';
7
+ export * from './langchain';
8
+ export * from './openai';
@@ -0,0 +1,159 @@
1
+ import type { SpendGuard } from '../spend-guard';
2
+ import {
3
+ createFrameworkGuard,
4
+ modelFromUnknown,
5
+ objectRecord,
6
+ outputTokensFromParams,
7
+ preflightFrameworkCall,
8
+ recordFrameworkReceipt,
9
+ redactDataPlane,
10
+ settleFrameworkCall,
11
+ stringValue,
12
+ type FrameworkAdapterOptions,
13
+ type FrameworkPreflight,
14
+ } from './common';
15
+
16
+ /**
17
+ * LangChain.js callback handler that runs `guard.decide()` before every LLM /
18
+ * chat-model call and settles real token usage after it completes.
19
+ *
20
+ * Hook point: LangChain's `BaseCallbackHandler` lifecycle methods
21
+ * `handleChatModelStart` / `handleLLMStart` (preflight, may throw
22
+ * AgentGuardBlockedError) and `handleLLMEnd` (usage settlement). We do NOT
23
+ * extend the real BaseCallbackHandler (that would force a peer-dep import at
24
+ * module load); LangChain duck-types handlers by these method names, and we set
25
+ * `awaitHandlers = true` so a thrown block aborts the call.
26
+ *
27
+ * This adapter reuses the shared framework core (createFrameworkGuard +
28
+ * preflightFrameworkCall + settleFrameworkCall). It never proxies prompts,
29
+ * completions, or provider keys — only redacted request shape leaves for the
30
+ * signed receipt.
31
+ *
32
+ * @example
33
+ * import { ChatOpenAI } from '@langchain/openai';
34
+ * import { createLangChainHandler } from '@agentguard-run/spend/frameworks/langchain';
35
+ *
36
+ * const handler = createLangChainHandler({ policy, scope });
37
+ * const llm = new ChatOpenAI({ callbacks: [handler] });
38
+ * await llm.invoke('hello'); // throws AgentGuardBlockedError if a cap blocks
39
+ */
40
+ export interface LangChainAdapterOptions extends FrameworkAdapterOptions {
41
+ framework?: 'langchain';
42
+ }
43
+
44
+ interface LangChainSerialized {
45
+ id?: string[];
46
+ kwargs?: Record<string, unknown>;
47
+ [key: string]: unknown;
48
+ }
49
+
50
+ export class AgentGuardLangChainHandler {
51
+ readonly name = 'agentguard_spend';
52
+ /** Ensures LangChain awaits the handler so a thrown block actually aborts. */
53
+ readonly awaitHandlers = true;
54
+ readonly guard: SpendGuard;
55
+ private readonly opts: LangChainAdapterOptions;
56
+ private readonly pending = new Map<string, FrameworkPreflight>();
57
+
58
+ constructor(opts: LangChainAdapterOptions) {
59
+ this.opts = { ...opts, framework: 'langchain' };
60
+ this.guard = createFrameworkGuard(this.opts);
61
+ }
62
+
63
+ async handleChatModelStart(
64
+ llm: LangChainSerialized,
65
+ messages: unknown[][],
66
+ runId: string,
67
+ _parentRunId?: string,
68
+ extraParams?: Record<string, unknown>,
69
+ _tags?: string[],
70
+ metadata?: Record<string, unknown>,
71
+ ): Promise<void> {
72
+ await this.start(runId, llm, messages, extraParams, metadata);
73
+ }
74
+
75
+ async handleLLMStart(
76
+ llm: LangChainSerialized,
77
+ prompts: unknown[],
78
+ runId: string,
79
+ _parentRunId?: string,
80
+ extraParams?: Record<string, unknown>,
81
+ _tags?: string[],
82
+ metadata?: Record<string, unknown>,
83
+ ): Promise<void> {
84
+ await this.start(runId, llm, prompts, extraParams, metadata);
85
+ }
86
+
87
+ async handleLLMEnd(output: unknown, runId: string): Promise<void> {
88
+ const preflight = this.pending.get(runId);
89
+ if (!preflight) return;
90
+ this.pending.delete(runId);
91
+ await settleFrameworkCall(preflight, normalizeUsage(output));
92
+ await recordFrameworkReceipt(this.guard, 'langchain', {
93
+ event: 'llm_end',
94
+ decisionId: preflight.decision.decisionId,
95
+ model: preflight.decision.modelResolved,
96
+ });
97
+ }
98
+
99
+ async handleLLMError(_error: unknown, runId: string): Promise<void> {
100
+ this.pending.delete(runId);
101
+ }
102
+
103
+ private async start(
104
+ runId: string,
105
+ llm: LangChainSerialized,
106
+ payload: unknown,
107
+ extraParams?: Record<string, unknown>,
108
+ metadata?: Record<string, unknown>,
109
+ ): Promise<void> {
110
+ const invocation = objectRecord(extraParams?.invocation_params) ?? {};
111
+ const model = modelFromUnknown(
112
+ invocation.model ?? invocation.model_name ?? lastId(llm) ?? objectRecord(llm?.kwargs)?.model,
113
+ this.opts.defaultModel ?? 'unknown',
114
+ );
115
+ const preflight = await preflightFrameworkCall(this.guard, this.opts, {
116
+ framework: 'langchain',
117
+ model,
118
+ params: payload,
119
+ outputTokens: outputTokensFromParams(invocation, this.opts.defaultOutputTokens ?? 1024),
120
+ metadata: redactDataPlane(metadata ?? {}) as Record<string, unknown>,
121
+ workflowId: stringValue(metadata?.workflowId) ?? stringValue(metadata?.thread_id),
122
+ requestShape: {
123
+ toolCount: Array.isArray(invocation.tools) ? invocation.tools.length : 0,
124
+ maxTokens: invocation.max_tokens ?? invocation.maxTokens,
125
+ stream: invocation.stream === true,
126
+ },
127
+ });
128
+ this.pending.set(runId, preflight);
129
+ }
130
+ }
131
+
132
+ export function createLangChainHandler(opts: LangChainAdapterOptions): AgentGuardLangChainHandler {
133
+ return new AgentGuardLangChainHandler(opts);
134
+ }
135
+
136
+ export const agentguardLangChainHandler = createLangChainHandler;
137
+
138
+ function lastId(llm: LangChainSerialized | undefined): string | undefined {
139
+ const id = llm?.id;
140
+ return Array.isArray(id) && id.length ? id[id.length - 1] : undefined;
141
+ }
142
+
143
+ /**
144
+ * Map a LangChain LLMResult into the `{ usage: { inputTokens, outputTokens } }`
145
+ * shape that settleFrameworkCall expects. LangChain reports token usage on
146
+ * `llmOutput.tokenUsage` (OpenAI) or `llmOutput.usage`.
147
+ */
148
+ function normalizeUsage(output: unknown): { usage: Record<string, unknown> } | null {
149
+ const record = objectRecord(output);
150
+ const llmOutput = objectRecord(record?.llmOutput);
151
+ const tokenUsage = objectRecord(llmOutput?.tokenUsage) ?? objectRecord(llmOutput?.usage);
152
+ if (!tokenUsage) return null;
153
+ return {
154
+ usage: {
155
+ inputTokens: tokenUsage.promptTokens ?? tokenUsage.prompt_tokens ?? tokenUsage.input_tokens ?? tokenUsage.inputTokens,
156
+ outputTokens: tokenUsage.completionTokens ?? tokenUsage.completion_tokens ?? tokenUsage.output_tokens ?? tokenUsage.outputTokens,
157
+ },
158
+ };
159
+ }
@@ -0,0 +1,36 @@
1
+ import { withSpendGuard, type OpenAIBindingOptions } from '../spend-guard';
2
+
3
+ /**
4
+ * One-line spend governance for the OpenAI SDK and the OpenAI Agents SDK.
5
+ *
6
+ * Hook point: the OpenAI client's `chat.completions.create`. `withSpendGuard`
7
+ * (the shared core binding) wraps that method so `guard.decide()` runs before
8
+ * every dispatch and can block/downgrade. Because the OpenAI Agents SDK
9
+ * (`@openai/agents`) drives the same underlying `OpenAI` client, passing the
10
+ * guarded client to the agent runner governs every model call the agent makes.
11
+ *
12
+ * This is a thin, named re-export of `withSpendGuard` so the OpenAI integration
13
+ * is discoverable at `@agentguard-run/spend/frameworks/openai`. It reuses the
14
+ * existing enforcement verbatim — no second code path.
15
+ *
16
+ * @example OpenAI SDK
17
+ * import OpenAI from 'openai';
18
+ * import { withSpendGuardOpenAI } from '@agentguard-run/spend/frameworks/openai';
19
+ *
20
+ * const client = withSpendGuardOpenAI(new OpenAI(), { policy, scope });
21
+ * await client.chat.completions.create({ model: 'gpt-5', messages });
22
+ *
23
+ * @example OpenAI Agents SDK
24
+ * import OpenAI from 'openai';
25
+ * import { Agent, run, setDefaultOpenAIClient } from '@openai/agents';
26
+ * import { withSpendGuardOpenAI } from '@agentguard-run/spend/frameworks/openai';
27
+ *
28
+ * setDefaultOpenAIClient(withSpendGuardOpenAI(new OpenAI(), { policy, scope }));
29
+ * await run(new Agent({ name: 'assistant' }), 'hello');
30
+ */
31
+ export function withSpendGuardOpenAI<TClient>(client: TClient, opts: OpenAIBindingOptions): TClient {
32
+ return withSpendGuard(client, opts) as TClient;
33
+ }
34
+
35
+ export const withOpenAISpendGuard = withSpendGuardOpenAI;
36
+ export type { OpenAIBindingOptions } from '../spend-guard';