@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 +36 -0
- package/dist/frameworks/index.d.ts +2 -0
- package/dist/frameworks/index.d.ts.map +1 -1
- package/dist/frameworks/index.js +2 -0
- package/dist/frameworks/index.js.map +1 -1
- package/dist/frameworks/langchain.d.ts +52 -0
- package/dist/frameworks/langchain.d.ts.map +1 -0
- package/dist/frameworks/langchain.js +84 -0
- package/dist/frameworks/langchain.js.map +1 -0
- package/dist/frameworks/openai.d.ts +33 -0
- package/dist/frameworks/openai.d.ts.map +1 -0
- package/dist/frameworks/openai.js +38 -0
- package/dist/frameworks/openai.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +11 -1
- package/src/frameworks/README.md +35 -0
- package/src/frameworks/index.ts +2 -0
- package/src/frameworks/langchain.ts +159 -0
- package/src/frameworks/openai.ts +36 -0
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.
|
|
@@ -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"}
|
package/dist/frameworks/index.js
CHANGED
|
@@ -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":";;;AAmIA,wDAEC;AApID,qCAYkB;AAoClB,MAAa,0BAA0B;IAC5B,IAAI,GAAG,kBAAkB,CAAC;IACnC,8EAA8E;IACrE,aAAa,GAAG,IAAI,CAAC;IACrB,KAAK,CAAa;IACV,IAAI,CAA0B;IAC9B,OAAO,GAAG,IAAI,GAAG,EAA8B,CAAC;IAEjE,YAAY,IAA6B;QACvC,IAAI,CAAC,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;QAChD,IAAI,CAAC,KAAK,GAAG,IAAA,6BAAoB,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,oBAAoB,CACxB,GAAwB,EACxB,QAAqB,EACrB,KAAa,EACb,YAAqB,EACrB,WAAqC,EACrC,KAAgB,EAChB,QAAkC;QAElC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,cAAc,CAClB,GAAwB,EACxB,OAAkB,EAClB,KAAa,EACb,YAAqB,EACrB,WAAqC,EACrC,KAAgB,EAChB,QAAkC;QAElC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAe,EAAE,KAAa;QAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,SAAS;YAAE,OAAO;QACvB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3B,MAAM,IAAA,4BAAmB,EAAC,SAAS,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7D,MAAM,IAAA,+BAAsB,EAAC,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE;YACpD,KAAK,EAAE,SAAS;YAChB,UAAU,EAAE,SAAS,CAAC,QAAQ,CAAC,UAAU;YACzC,KAAK,EAAE,SAAS,CAAC,QAAQ,CAAC,aAAa;SACxC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,MAAe,EAAE,KAAa;QACjD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,KAAK,CACjB,KAAa,EACb,GAAwB,EACxB,OAAgB,EAChB,WAAqC,EACrC,QAAkC;QAElC,MAAM,UAAU,GAAG,IAAA,qBAAY,EAAC,WAAW,EAAE,iBAAiB,CAAC,IAAI,EAAE,CAAC;QACtE,MAAM,KAAK,GAAG,IAAA,yBAAgB,EAC5B,UAAU,CAAC,KAAK,IAAI,UAAU,CAAC,UAAU,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,IAAA,qBAAY,EAAC,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,EAC5F,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,SAAS,CACpC,CAAC;QACF,MAAM,SAAS,GAAG,MAAM,IAAA,+BAAsB,EAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE;YACpE,SAAS,EAAE,WAAW;YACtB,KAAK;YACL,MAAM,EAAE,OAAO;YACf,YAAY,EAAE,IAAA,+BAAsB,EAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC;YACvF,QAAQ,EAAE,IAAA,wBAAe,EAAC,QAAQ,IAAI,EAAE,CAA4B;YACpE,UAAU,EAAE,IAAA,oBAAW,EAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,IAAA,oBAAW,EAAC,QAAQ,EAAE,SAAS,CAAC;YACjF,YAAY,EAAE;gBACZ,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACxE,SAAS,EAAE,UAAU,CAAC,UAAU,IAAI,UAAU,CAAC,SAAS;gBACxD,MAAM,EAAE,UAAU,CAAC,MAAM,KAAK,IAAI;aACnC;SACF,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IACrC,CAAC;CACF;AAhFD,gEAgFC;AAED,SAAgB,sBAAsB,CAAC,IAA6B;IAClE,OAAO,IAAI,0BAA0B,CAAC,IAAI,CAAC,CAAC;AAC9C,CAAC;AAEY,QAAA,0BAA0B,GAAG,sBAAsB,CAAC;AAEjE,SAAS,MAAM,CAAC,GAAoC;IAClD,MAAM,EAAE,GAAG,GAAG,EAAE,EAAE,CAAC;IACnB,OAAO,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxE,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,MAAe;IACrC,MAAM,MAAM,GAAG,IAAA,qBAAY,EAAC,MAAM,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,IAAA,qBAAY,EAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAClD,MAAM,UAAU,GAAG,IAAA,qBAAY,EAAC,SAAS,EAAE,UAAU,CAAC,IAAI,IAAA,qBAAY,EAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACzF,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC7B,OAAO;QACL,KAAK,EAAE;YACL,WAAW,EAAE,UAAU,CAAC,YAAY,IAAI,UAAU,CAAC,aAAa,IAAI,UAAU,CAAC,YAAY,IAAI,UAAU,CAAC,WAAW;YACrH,YAAY,EAAE,UAAU,CAAC,gBAAgB,IAAI,UAAU,CAAC,iBAAiB,IAAI,UAAU,CAAC,aAAa,IAAI,UAAU,CAAC,YAAY;SACjI;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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",
|
package/src/frameworks/README.md
CHANGED
|
@@ -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
|
+
```
|
package/src/frameworks/index.ts
CHANGED
|
@@ -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';
|