agentledger 0.3.0
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 +176 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +175 -0
- package/dist/integrations/express.d.ts +69 -0
- package/dist/integrations/express.js +103 -0
- package/dist/integrations/index.d.ts +7 -0
- package/dist/integrations/index.js +15 -0
- package/dist/integrations/langchain.d.ts +54 -0
- package/dist/integrations/langchain.js +154 -0
- package/dist/integrations/mcp.d.ts +64 -0
- package/dist/integrations/mcp.js +82 -0
- package/dist/integrations/openai.d.ts +64 -0
- package/dist/integrations/openai.js +89 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# AgentLedger
|
|
2
|
+
|
|
3
|
+
**See everything your AI agents do.** Track actions, monitor costs, and kill agents when things go wrong.
|
|
4
|
+
|
|
5
|
+
Your agents send emails, create tickets, charge credit cards, and call APIs. AgentLedger logs every action, tracks every cost, and lets you kill agents instantly when things go sideways.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install agentledger
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { AgentLedger } from 'agentledger';
|
|
17
|
+
|
|
18
|
+
const ledger = new AgentLedger({
|
|
19
|
+
apiKey: process.env.AGENTLEDGER_KEY, // Get this from agentledger.co
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Wrap any async function — it's logged, timed, and budget-checked
|
|
23
|
+
const result = await ledger.track({
|
|
24
|
+
agent: 'support-bot',
|
|
25
|
+
service: 'slack',
|
|
26
|
+
action: 'send_message',
|
|
27
|
+
}, async () => {
|
|
28
|
+
return await slack.chat.postMessage({ channel: '#support', text: 'Hello!' });
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Open your dashboard at [agentledger.co](https://agentledger.co) and watch your agents in real-time.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **Zero dependencies** — just this package, nothing else
|
|
37
|
+
- **Fail-open by default** — if AgentLedger is down, your agents keep running
|
|
38
|
+
- **Pre-flight checks** — block actions before they happen if agent is over budget
|
|
39
|
+
- **Kill switches** — pause or kill any agent instantly
|
|
40
|
+
- **Cost tracking** — know exactly what each agent costs
|
|
41
|
+
- **5ms overhead** — async logging, doesn't slow your agents down
|
|
42
|
+
|
|
43
|
+
## API
|
|
44
|
+
|
|
45
|
+
### `new AgentLedger(config)`
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
const ledger = new AgentLedger({
|
|
49
|
+
apiKey: 'al_...', // Required. Get from agentledger.co
|
|
50
|
+
baseUrl: 'https://...', // Optional. Default: https://agentledger.co
|
|
51
|
+
failOpen: true, // Optional. If true, agents run even if AgentLedger is unreachable
|
|
52
|
+
timeout: 5000, // Optional. API timeout in ms
|
|
53
|
+
onError: (err) => {}, // Optional. Called on communication errors
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `ledger.track(options, fn)`
|
|
58
|
+
|
|
59
|
+
Wrap an async function. Runs a pre-flight budget check, executes the function, logs the result.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const { result, allowed, durationMs } = await ledger.track({
|
|
63
|
+
agent: 'my-bot',
|
|
64
|
+
service: 'stripe',
|
|
65
|
+
action: 'charge',
|
|
66
|
+
costCents: 50,
|
|
67
|
+
metadata: { customerId: '123' },
|
|
68
|
+
}, async () => {
|
|
69
|
+
return await stripe.charges.create({ amount: 5000 });
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `ledger.check(options)`
|
|
74
|
+
|
|
75
|
+
Pre-flight check without executing. Use before expensive operations.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const { allowed, blockReason } = await ledger.check({
|
|
79
|
+
agent: 'my-bot',
|
|
80
|
+
service: 'openai',
|
|
81
|
+
action: 'completion',
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!allowed) {
|
|
85
|
+
console.log(`Blocked: ${blockReason}`);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `ledger.log(options)`
|
|
90
|
+
|
|
91
|
+
Manual logging without wrapping a function.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
await ledger.log({
|
|
95
|
+
agent: 'my-bot',
|
|
96
|
+
service: 'sendgrid',
|
|
97
|
+
action: 'send_email',
|
|
98
|
+
costCents: 1,
|
|
99
|
+
durationMs: 340,
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `ledger.pauseAgent(name)` / `resumeAgent(name)` / `killAgent(name)`
|
|
104
|
+
|
|
105
|
+
Control agents programmatically.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
await ledger.pauseAgent('runaway-bot'); // All future actions blocked
|
|
109
|
+
await ledger.resumeAgent('runaway-bot'); // Back in action
|
|
110
|
+
await ledger.killAgent('runaway-bot'); // Permanently stopped
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Framework Integrations
|
|
114
|
+
|
|
115
|
+
### LangChain
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { AgentLedger } from 'agentledger';
|
|
119
|
+
import { AgentLedgerCallbackHandler } from 'agentledger/integrations/langchain';
|
|
120
|
+
|
|
121
|
+
const handler = new AgentLedgerCallbackHandler(ledger, {
|
|
122
|
+
agent: 'research-bot',
|
|
123
|
+
serviceMap: { 'tavily_search': { service: 'tavily', action: 'search' } },
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const agent = createReactAgent({ llm, tools, callbacks: [handler] });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### OpenAI
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { createToolExecutor } from 'agentledger/integrations/openai';
|
|
133
|
+
|
|
134
|
+
const execute = createToolExecutor(ledger, 'my-agent', tools, serviceMap);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### MCP Servers
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { wrapMCPServer } from 'agentledger/integrations/mcp';
|
|
141
|
+
|
|
142
|
+
wrapMCPServer(ledger, server, { agent: 'my-mcp-server' });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Express
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { agentLedgerMiddleware } from 'agentledger/integrations/express';
|
|
149
|
+
|
|
150
|
+
app.post('/api/action', agentLedgerMiddleware(ledger, {
|
|
151
|
+
agent: 'api-bot', service: 'internal', action: 'process',
|
|
152
|
+
}), handler);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Self-Hosting
|
|
156
|
+
|
|
157
|
+
AgentLedger is open source. You can self-host the dashboard:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
git clone https://github.com/mnoonan/agentledger.git
|
|
161
|
+
cd agentledger
|
|
162
|
+
npm install && npm run dev
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Point your SDK to your own instance:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const ledger = new AgentLedger({
|
|
169
|
+
apiKey: 'al_...',
|
|
170
|
+
baseUrl: 'https://your-instance.com',
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export interface AgentLedgerConfig {
|
|
2
|
+
/** Your AgentLedger API key (starts with al_) */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Base URL for the AgentLedger API. Default: https://agentledger.co */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** If true, actions proceed even if AgentLedger is unreachable. Default: true */
|
|
7
|
+
failOpen?: boolean;
|
|
8
|
+
/** Timeout in ms for API calls. Default: 5000 */
|
|
9
|
+
timeout?: number;
|
|
10
|
+
/** Called when an error occurs communicating with AgentLedger */
|
|
11
|
+
onError?: (error: Error) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface TrackOptions {
|
|
14
|
+
/** Name of the agent performing the action */
|
|
15
|
+
agent: string;
|
|
16
|
+
/** Service being called (e.g. 'slack', 'stripe', 'openai') */
|
|
17
|
+
service: string;
|
|
18
|
+
/** Action being performed (e.g. 'send_message', 'charge', 'completion') */
|
|
19
|
+
action: string;
|
|
20
|
+
/** Estimated cost in cents. Auto-calculated from duration if not provided. */
|
|
21
|
+
costCents?: number;
|
|
22
|
+
/** Additional metadata to log with the action */
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
export interface TrackResult<T> {
|
|
26
|
+
/** The return value of the wrapped function */
|
|
27
|
+
result: T;
|
|
28
|
+
/** Whether AgentLedger allowed the action */
|
|
29
|
+
allowed: boolean;
|
|
30
|
+
/** Duration of the action in milliseconds */
|
|
31
|
+
durationMs: number;
|
|
32
|
+
/** The logged action ID */
|
|
33
|
+
actionId?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface CheckResult {
|
|
36
|
+
allowed: boolean;
|
|
37
|
+
blockReason?: string;
|
|
38
|
+
remainingBudget?: {
|
|
39
|
+
actions?: number;
|
|
40
|
+
costCents?: number;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export declare class AgentLedger {
|
|
44
|
+
private apiKey;
|
|
45
|
+
private baseUrl;
|
|
46
|
+
private failOpen;
|
|
47
|
+
private timeout;
|
|
48
|
+
private onError?;
|
|
49
|
+
constructor(config: AgentLedgerConfig);
|
|
50
|
+
/**
|
|
51
|
+
* Track an agent action. Wraps an async function with logging and budget checks.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const result = await ledger.track({
|
|
55
|
+
* agent: 'support-bot',
|
|
56
|
+
* service: 'slack',
|
|
57
|
+
* action: 'send_message',
|
|
58
|
+
* }, async () => {
|
|
59
|
+
* return await slack.chat.postMessage({ channel: '#support', text: 'Hello!' });
|
|
60
|
+
* });
|
|
61
|
+
*/
|
|
62
|
+
track<T>(options: TrackOptions, fn: () => Promise<T>): Promise<TrackResult<T>>;
|
|
63
|
+
/**
|
|
64
|
+
* Check if an action is allowed without executing it.
|
|
65
|
+
* Useful for pre-flight checks before expensive operations.
|
|
66
|
+
*/
|
|
67
|
+
check(options: Pick<TrackOptions, 'agent' | 'service' | 'action'>): Promise<CheckResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Log an action directly without wrapping a function.
|
|
70
|
+
* Useful when you want manual control over timing.
|
|
71
|
+
*/
|
|
72
|
+
log(options: TrackOptions & {
|
|
73
|
+
status?: string;
|
|
74
|
+
durationMs?: number;
|
|
75
|
+
}): Promise<{
|
|
76
|
+
id?: string;
|
|
77
|
+
}>;
|
|
78
|
+
/**
|
|
79
|
+
* Pause an agent. All future actions will be blocked until resumed.
|
|
80
|
+
*/
|
|
81
|
+
pauseAgent(name: string): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Resume a paused agent.
|
|
84
|
+
*/
|
|
85
|
+
resumeAgent(name: string): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Kill an agent permanently. All future actions will be blocked.
|
|
88
|
+
*/
|
|
89
|
+
killAgent(name: string): Promise<void>;
|
|
90
|
+
private logAction;
|
|
91
|
+
private fetch;
|
|
92
|
+
private handleError;
|
|
93
|
+
}
|
|
94
|
+
export default AgentLedger;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentLedger = void 0;
|
|
4
|
+
class AgentLedger {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
if (!config.apiKey || !config.apiKey.startsWith('al_')) {
|
|
7
|
+
throw new Error('AgentLedger: Invalid API key. Keys start with "al_".');
|
|
8
|
+
}
|
|
9
|
+
this.apiKey = config.apiKey;
|
|
10
|
+
this.baseUrl = (config.baseUrl || 'https://agentledger.co').replace(/\/$/, '');
|
|
11
|
+
this.failOpen = config.failOpen !== false; // default true
|
|
12
|
+
this.timeout = config.timeout || 5000;
|
|
13
|
+
this.onError = config.onError;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Track an agent action. Wraps an async function with logging and budget checks.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const result = await ledger.track({
|
|
20
|
+
* agent: 'support-bot',
|
|
21
|
+
* service: 'slack',
|
|
22
|
+
* action: 'send_message',
|
|
23
|
+
* }, async () => {
|
|
24
|
+
* return await slack.chat.postMessage({ channel: '#support', text: 'Hello!' });
|
|
25
|
+
* });
|
|
26
|
+
*/
|
|
27
|
+
async track(options, fn) {
|
|
28
|
+
// Pre-flight check
|
|
29
|
+
let allowed = true;
|
|
30
|
+
try {
|
|
31
|
+
const check = await this.check(options);
|
|
32
|
+
allowed = check.allowed;
|
|
33
|
+
if (!allowed) {
|
|
34
|
+
throw new Error(`AgentLedger: Action blocked - ${check.blockReason || 'budget exceeded'}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (err instanceof Error && err.message.startsWith('AgentLedger: Action blocked')) {
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
// Communication error — fail open or closed
|
|
42
|
+
if (!this.failOpen) {
|
|
43
|
+
throw new Error('AgentLedger: Cannot verify action (fail-closed mode)');
|
|
44
|
+
}
|
|
45
|
+
this.handleError(err);
|
|
46
|
+
}
|
|
47
|
+
// Execute the action
|
|
48
|
+
const start = Date.now();
|
|
49
|
+
let status = 'success';
|
|
50
|
+
let result;
|
|
51
|
+
try {
|
|
52
|
+
result = await fn();
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
status = 'error';
|
|
56
|
+
const durationMs = Date.now() - start;
|
|
57
|
+
// Log the error, then re-throw
|
|
58
|
+
this.logAction(options, status, durationMs).catch(this.handleError.bind(this));
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
const durationMs = Date.now() - start;
|
|
62
|
+
// Log the action (fire and forget for speed, unless we need the ID)
|
|
63
|
+
let actionId;
|
|
64
|
+
try {
|
|
65
|
+
const logResult = await this.logAction(options, status, durationMs);
|
|
66
|
+
actionId = logResult?.id;
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
this.handleError(err);
|
|
70
|
+
}
|
|
71
|
+
return { result, allowed, durationMs, actionId };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if an action is allowed without executing it.
|
|
75
|
+
* Useful for pre-flight checks before expensive operations.
|
|
76
|
+
*/
|
|
77
|
+
async check(options) {
|
|
78
|
+
const res = await this.fetch('/api/v1/check', {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
agent: options.agent,
|
|
82
|
+
service: options.service,
|
|
83
|
+
action: options.action,
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
if (!res.ok) {
|
|
87
|
+
throw new Error(`AgentLedger: Check failed (${res.status})`);
|
|
88
|
+
}
|
|
89
|
+
return res.json();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Log an action directly without wrapping a function.
|
|
93
|
+
* Useful when you want manual control over timing.
|
|
94
|
+
*/
|
|
95
|
+
async log(options) {
|
|
96
|
+
return this.logAction(options, options.status || 'success', options.durationMs || 0);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Pause an agent. All future actions will be blocked until resumed.
|
|
100
|
+
*/
|
|
101
|
+
async pauseAgent(name) {
|
|
102
|
+
const res = await this.fetch(`/api/v1/agents/${encodeURIComponent(name)}/pause`, {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
});
|
|
105
|
+
if (!res.ok)
|
|
106
|
+
throw new Error(`AgentLedger: Failed to pause agent (${res.status})`);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resume a paused agent.
|
|
110
|
+
*/
|
|
111
|
+
async resumeAgent(name) {
|
|
112
|
+
const res = await this.fetch(`/api/v1/agents/${encodeURIComponent(name)}/resume`, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
});
|
|
115
|
+
if (!res.ok)
|
|
116
|
+
throw new Error(`AgentLedger: Failed to resume agent (${res.status})`);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Kill an agent permanently. All future actions will be blocked.
|
|
120
|
+
*/
|
|
121
|
+
async killAgent(name) {
|
|
122
|
+
const res = await this.fetch(`/api/v1/agents/${encodeURIComponent(name)}/kill`, {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
});
|
|
125
|
+
if (!res.ok)
|
|
126
|
+
throw new Error(`AgentLedger: Failed to kill agent (${res.status})`);
|
|
127
|
+
}
|
|
128
|
+
// Internal: log action to API
|
|
129
|
+
async logAction(options, status, durationMs) {
|
|
130
|
+
const res = await this.fetch('/api/v1/actions', {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
body: JSON.stringify({
|
|
133
|
+
agent: options.agent,
|
|
134
|
+
service: options.service,
|
|
135
|
+
action: options.action,
|
|
136
|
+
status,
|
|
137
|
+
cost_cents: options.costCents || 0,
|
|
138
|
+
duration_ms: durationMs,
|
|
139
|
+
metadata: options.metadata || {},
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
if (!res.ok) {
|
|
143
|
+
throw new Error(`AgentLedger: Failed to log action (${res.status})`);
|
|
144
|
+
}
|
|
145
|
+
const data = await res.json();
|
|
146
|
+
return { id: data.id };
|
|
147
|
+
}
|
|
148
|
+
// Internal: fetch with timeout and auth
|
|
149
|
+
async fetch(path, init = {}) {
|
|
150
|
+
const controller = new AbortController();
|
|
151
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
152
|
+
try {
|
|
153
|
+
return await fetch(`${this.baseUrl}${path}`, {
|
|
154
|
+
...init,
|
|
155
|
+
signal: controller.signal,
|
|
156
|
+
headers: {
|
|
157
|
+
'Content-Type': 'application/json',
|
|
158
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
159
|
+
...(init.headers || {}),
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
clearTimeout(timeoutId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
handleError(err) {
|
|
168
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
169
|
+
if (this.onError) {
|
|
170
|
+
this.onError(error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
exports.AgentLedger = AgentLedger;
|
|
175
|
+
exports.default = AgentLedger;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentLedger Express middleware and generic HTTP integration.
|
|
3
|
+
*
|
|
4
|
+
* Usage with Express:
|
|
5
|
+
* import { AgentLedger } from '@agentledger/sdk';
|
|
6
|
+
* import { agentLedgerMiddleware } from '@agentledger/sdk/integrations/express';
|
|
7
|
+
*
|
|
8
|
+
* const ledger = new AgentLedger({ apiKey: 'al_...' });
|
|
9
|
+
*
|
|
10
|
+
* // Track all requests to a specific route
|
|
11
|
+
* app.post('/api/agent/send-email', agentLedgerMiddleware(ledger, {
|
|
12
|
+
* agent: 'email-bot',
|
|
13
|
+
* service: 'sendgrid',
|
|
14
|
+
* action: 'send_email',
|
|
15
|
+
* }), (req, res) => {
|
|
16
|
+
* // your handler
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Or track all routes with auto-detection
|
|
20
|
+
* app.use('/api/agent', agentLedgerMiddleware(ledger, {
|
|
21
|
+
* agent: 'my-agent',
|
|
22
|
+
* autoDetect: true, // uses req.path as action, req.method as metadata
|
|
23
|
+
* }));
|
|
24
|
+
*/
|
|
25
|
+
import type { AgentLedger, TrackOptions } from '../index';
|
|
26
|
+
export interface MiddlewareConfig {
|
|
27
|
+
/** Agent name */
|
|
28
|
+
agent: string;
|
|
29
|
+
/** Service name. If autoDetect is true, can be omitted */
|
|
30
|
+
service?: string;
|
|
31
|
+
/** Action name. If autoDetect is true, uses req.path */
|
|
32
|
+
action?: string;
|
|
33
|
+
/** Auto-detect service/action from request path */
|
|
34
|
+
autoDetect?: boolean;
|
|
35
|
+
/** Custom cost extractor from request/response */
|
|
36
|
+
costExtractor?: (req: unknown, res: unknown) => number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Express-compatible middleware that tracks requests as agent actions.
|
|
40
|
+
*/
|
|
41
|
+
export declare function agentLedgerMiddleware(ledger: AgentLedger, config: MiddlewareConfig): (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Simple wrapper for any async function — no Express dependency needed.
|
|
44
|
+
* Wraps a function with AgentLedger tracking and returns a new function
|
|
45
|
+
* with the same signature.
|
|
46
|
+
*
|
|
47
|
+
* Usage:
|
|
48
|
+
* const trackedFn = trackFunction(ledger, {
|
|
49
|
+
* agent: 'my-bot',
|
|
50
|
+
* service: 'slack',
|
|
51
|
+
* action: 'send_message',
|
|
52
|
+
* }, slackSendMessage);
|
|
53
|
+
*
|
|
54
|
+
* await trackedFn('#general', 'Hello!');
|
|
55
|
+
*/
|
|
56
|
+
export declare function trackFunction<TArgs extends unknown[], TResult>(ledger: AgentLedger, options: Pick<TrackOptions, 'agent' | 'service' | 'action'>, fn: (...args: TArgs) => Promise<TResult>): (...args: TArgs) => Promise<TResult>;
|
|
57
|
+
interface ExpressRequest {
|
|
58
|
+
method: string;
|
|
59
|
+
path?: string;
|
|
60
|
+
url?: string;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
interface ExpressResponse {
|
|
64
|
+
statusCode: number;
|
|
65
|
+
end: (...args: unknown[]) => ExpressResponse;
|
|
66
|
+
[key: string]: unknown;
|
|
67
|
+
}
|
|
68
|
+
type NextFunction = (err?: unknown) => void;
|
|
69
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AgentLedger Express middleware and generic HTTP integration.
|
|
4
|
+
*
|
|
5
|
+
* Usage with Express:
|
|
6
|
+
* import { AgentLedger } from '@agentledger/sdk';
|
|
7
|
+
* import { agentLedgerMiddleware } from '@agentledger/sdk/integrations/express';
|
|
8
|
+
*
|
|
9
|
+
* const ledger = new AgentLedger({ apiKey: 'al_...' });
|
|
10
|
+
*
|
|
11
|
+
* // Track all requests to a specific route
|
|
12
|
+
* app.post('/api/agent/send-email', agentLedgerMiddleware(ledger, {
|
|
13
|
+
* agent: 'email-bot',
|
|
14
|
+
* service: 'sendgrid',
|
|
15
|
+
* action: 'send_email',
|
|
16
|
+
* }), (req, res) => {
|
|
17
|
+
* // your handler
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // Or track all routes with auto-detection
|
|
21
|
+
* app.use('/api/agent', agentLedgerMiddleware(ledger, {
|
|
22
|
+
* agent: 'my-agent',
|
|
23
|
+
* autoDetect: true, // uses req.path as action, req.method as metadata
|
|
24
|
+
* }));
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.agentLedgerMiddleware = agentLedgerMiddleware;
|
|
28
|
+
exports.trackFunction = trackFunction;
|
|
29
|
+
/**
|
|
30
|
+
* Express-compatible middleware that tracks requests as agent actions.
|
|
31
|
+
*/
|
|
32
|
+
function agentLedgerMiddleware(ledger, config) {
|
|
33
|
+
return async (req, res, next) => {
|
|
34
|
+
const start = Date.now();
|
|
35
|
+
// Determine service/action
|
|
36
|
+
let service = config.service || 'http';
|
|
37
|
+
let action = config.action || 'request';
|
|
38
|
+
if (config.autoDetect) {
|
|
39
|
+
// /api/agent/send-email → service: 'agent', action: 'send-email'
|
|
40
|
+
const pathParts = (req.path || req.url || '').split('/').filter(Boolean);
|
|
41
|
+
if (pathParts.length >= 2) {
|
|
42
|
+
service = pathParts[pathParts.length - 2] || service;
|
|
43
|
+
action = pathParts[pathParts.length - 1] || action;
|
|
44
|
+
}
|
|
45
|
+
else if (pathParts.length === 1) {
|
|
46
|
+
action = pathParts[0] || action;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Hook into response finish to log with timing
|
|
50
|
+
const originalEnd = res.end.bind(res);
|
|
51
|
+
let logged = false;
|
|
52
|
+
res.end = function (...args) {
|
|
53
|
+
if (!logged) {
|
|
54
|
+
logged = true;
|
|
55
|
+
const durationMs = Date.now() - start;
|
|
56
|
+
const status = res.statusCode >= 400 ? 'error' : 'success';
|
|
57
|
+
const costCents = config.costExtractor ? config.costExtractor(req, res) : 0;
|
|
58
|
+
// Fire and forget — don't block the response
|
|
59
|
+
ledger.log({
|
|
60
|
+
agent: config.agent,
|
|
61
|
+
service,
|
|
62
|
+
action,
|
|
63
|
+
status,
|
|
64
|
+
durationMs,
|
|
65
|
+
costCents,
|
|
66
|
+
metadata: {
|
|
67
|
+
source: 'express',
|
|
68
|
+
method: req.method,
|
|
69
|
+
path: req.path || req.url,
|
|
70
|
+
statusCode: res.statusCode,
|
|
71
|
+
},
|
|
72
|
+
}).catch(() => { }); // fail-open
|
|
73
|
+
}
|
|
74
|
+
return originalEnd(...args);
|
|
75
|
+
};
|
|
76
|
+
next();
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Simple wrapper for any async function — no Express dependency needed.
|
|
81
|
+
* Wraps a function with AgentLedger tracking and returns a new function
|
|
82
|
+
* with the same signature.
|
|
83
|
+
*
|
|
84
|
+
* Usage:
|
|
85
|
+
* const trackedFn = trackFunction(ledger, {
|
|
86
|
+
* agent: 'my-bot',
|
|
87
|
+
* service: 'slack',
|
|
88
|
+
* action: 'send_message',
|
|
89
|
+
* }, slackSendMessage);
|
|
90
|
+
*
|
|
91
|
+
* await trackedFn('#general', 'Hello!');
|
|
92
|
+
*/
|
|
93
|
+
function trackFunction(ledger, options, fn) {
|
|
94
|
+
return async (...args) => {
|
|
95
|
+
const { result } = await ledger.track({
|
|
96
|
+
agent: options.agent,
|
|
97
|
+
service: options.service,
|
|
98
|
+
action: options.action,
|
|
99
|
+
metadata: { source: 'tracked-function' },
|
|
100
|
+
}, () => fn(...args));
|
|
101
|
+
return result;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { AgentLedgerCallbackHandler } from './langchain';
|
|
2
|
+
export type { AgentLedgerCallbackConfig } from './langchain';
|
|
3
|
+
export { withAgentLedger, createToolExecutor, wrapOpenAICompletion } from './openai';
|
|
4
|
+
export { wrapMCPServer, wrapMCPTool } from './mcp';
|
|
5
|
+
export type { MCPWrapConfig } from './mcp';
|
|
6
|
+
export { agentLedgerMiddleware, trackFunction } from './express';
|
|
7
|
+
export type { MiddlewareConfig } from './express';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trackFunction = exports.agentLedgerMiddleware = exports.wrapMCPTool = exports.wrapMCPServer = exports.wrapOpenAICompletion = exports.createToolExecutor = exports.withAgentLedger = exports.AgentLedgerCallbackHandler = void 0;
|
|
4
|
+
var langchain_1 = require("./langchain");
|
|
5
|
+
Object.defineProperty(exports, "AgentLedgerCallbackHandler", { enumerable: true, get: function () { return langchain_1.AgentLedgerCallbackHandler; } });
|
|
6
|
+
var openai_1 = require("./openai");
|
|
7
|
+
Object.defineProperty(exports, "withAgentLedger", { enumerable: true, get: function () { return openai_1.withAgentLedger; } });
|
|
8
|
+
Object.defineProperty(exports, "createToolExecutor", { enumerable: true, get: function () { return openai_1.createToolExecutor; } });
|
|
9
|
+
Object.defineProperty(exports, "wrapOpenAICompletion", { enumerable: true, get: function () { return openai_1.wrapOpenAICompletion; } });
|
|
10
|
+
var mcp_1 = require("./mcp");
|
|
11
|
+
Object.defineProperty(exports, "wrapMCPServer", { enumerable: true, get: function () { return mcp_1.wrapMCPServer; } });
|
|
12
|
+
Object.defineProperty(exports, "wrapMCPTool", { enumerable: true, get: function () { return mcp_1.wrapMCPTool; } });
|
|
13
|
+
var express_1 = require("./express");
|
|
14
|
+
Object.defineProperty(exports, "agentLedgerMiddleware", { enumerable: true, get: function () { return express_1.agentLedgerMiddleware; } });
|
|
15
|
+
Object.defineProperty(exports, "trackFunction", { enumerable: true, get: function () { return express_1.trackFunction; } });
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentLedger integration for LangChain.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { AgentLedger } from '@agentledger/sdk';
|
|
6
|
+
* import { AgentLedgerCallbackHandler } from '@agentledger/sdk/integrations/langchain';
|
|
7
|
+
*
|
|
8
|
+
* const ledger = new AgentLedger({ apiKey: 'al_...' });
|
|
9
|
+
* const handler = new AgentLedgerCallbackHandler(ledger, { agent: 'my-agent' });
|
|
10
|
+
*
|
|
11
|
+
* const chain = new ChatOpenAI({ callbacks: [handler] });
|
|
12
|
+
* // or
|
|
13
|
+
* await agent.invoke({ input: '...' }, { callbacks: [handler] });
|
|
14
|
+
*/
|
|
15
|
+
import type { AgentLedger } from '../index';
|
|
16
|
+
interface Serialized {
|
|
17
|
+
id?: string[];
|
|
18
|
+
name?: string;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface AgentLedgerCallbackConfig {
|
|
22
|
+
/** Name of the agent in AgentLedger */
|
|
23
|
+
agent: string;
|
|
24
|
+
/** Map LangChain tool names to AgentLedger services. Default: tool name becomes both service and action */
|
|
25
|
+
serviceMap?: Record<string, {
|
|
26
|
+
service: string;
|
|
27
|
+
action?: string;
|
|
28
|
+
}>;
|
|
29
|
+
/** Whether to track LLM calls (tokens/cost). Default: true */
|
|
30
|
+
trackLLM?: boolean;
|
|
31
|
+
/** Whether to track tool calls. Default: true */
|
|
32
|
+
trackTools?: boolean;
|
|
33
|
+
/** Whether to track chain/agent runs. Default: false */
|
|
34
|
+
trackChains?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare class AgentLedgerCallbackHandler {
|
|
37
|
+
name: string;
|
|
38
|
+
private ledger;
|
|
39
|
+
private config;
|
|
40
|
+
private runTimers;
|
|
41
|
+
constructor(ledger: AgentLedger, config: AgentLedgerCallbackConfig);
|
|
42
|
+
handleToolStart(tool: Serialized, input: string, runId: string): Promise<void>;
|
|
43
|
+
handleToolEnd(output: string, runId: string): Promise<void>;
|
|
44
|
+
handleToolError(err: Error, runId: string): Promise<void>;
|
|
45
|
+
handleLLMStart(llm: Serialized, prompts: string[], runId: string): Promise<void>;
|
|
46
|
+
handleLLMEnd(output: unknown, runId: string): Promise<void>;
|
|
47
|
+
handleLLMError(err: Error, runId: string): Promise<void>;
|
|
48
|
+
handleChainStart(chain: Serialized, inputs: Record<string, unknown>, runId: string): Promise<void>;
|
|
49
|
+
handleChainEnd(outputs: Record<string, unknown>, runId: string): Promise<void>;
|
|
50
|
+
handleChainError(err: Error, runId: string): Promise<void>;
|
|
51
|
+
private toolNameStore;
|
|
52
|
+
private extractToolName;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AgentLedger integration for LangChain.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { AgentLedger } from '@agentledger/sdk';
|
|
7
|
+
* import { AgentLedgerCallbackHandler } from '@agentledger/sdk/integrations/langchain';
|
|
8
|
+
*
|
|
9
|
+
* const ledger = new AgentLedger({ apiKey: 'al_...' });
|
|
10
|
+
* const handler = new AgentLedgerCallbackHandler(ledger, { agent: 'my-agent' });
|
|
11
|
+
*
|
|
12
|
+
* const chain = new ChatOpenAI({ callbacks: [handler] });
|
|
13
|
+
* // or
|
|
14
|
+
* await agent.invoke({ input: '...' }, { callbacks: [handler] });
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.AgentLedgerCallbackHandler = void 0;
|
|
18
|
+
class AgentLedgerCallbackHandler {
|
|
19
|
+
constructor(ledger, config) {
|
|
20
|
+
this.name = 'AgentLedgerCallbackHandler';
|
|
21
|
+
this.runTimers = new Map();
|
|
22
|
+
// ==================== HELPERS ====================
|
|
23
|
+
this.toolNameStore = new Map();
|
|
24
|
+
this.ledger = ledger;
|
|
25
|
+
this.config = {
|
|
26
|
+
trackLLM: true,
|
|
27
|
+
trackTools: true,
|
|
28
|
+
trackChains: false,
|
|
29
|
+
...config,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// ==================== TOOL CALLS ====================
|
|
33
|
+
async handleToolStart(tool, input, runId) {
|
|
34
|
+
if (!this.config.trackTools)
|
|
35
|
+
return;
|
|
36
|
+
this.runTimers.set(runId, Date.now());
|
|
37
|
+
}
|
|
38
|
+
async handleToolEnd(output, runId) {
|
|
39
|
+
if (!this.config.trackTools)
|
|
40
|
+
return;
|
|
41
|
+
const startTime = this.runTimers.get(runId);
|
|
42
|
+
const durationMs = startTime ? Date.now() - startTime : 0;
|
|
43
|
+
this.runTimers.delete(runId);
|
|
44
|
+
// Extract tool name from runId context (stored during handleToolStart)
|
|
45
|
+
const toolName = this.extractToolName(runId) || 'unknown_tool';
|
|
46
|
+
const mapped = this.config.serviceMap?.[toolName];
|
|
47
|
+
await this.ledger.log({
|
|
48
|
+
agent: this.config.agent,
|
|
49
|
+
service: mapped?.service || toolName,
|
|
50
|
+
action: mapped?.action || 'invoke',
|
|
51
|
+
durationMs,
|
|
52
|
+
metadata: { source: 'langchain', runId, outputLength: output?.length },
|
|
53
|
+
}).catch(() => { }); // fail-open
|
|
54
|
+
}
|
|
55
|
+
async handleToolError(err, runId) {
|
|
56
|
+
if (!this.config.trackTools)
|
|
57
|
+
return;
|
|
58
|
+
const startTime = this.runTimers.get(runId);
|
|
59
|
+
const durationMs = startTime ? Date.now() - startTime : 0;
|
|
60
|
+
this.runTimers.delete(runId);
|
|
61
|
+
const toolName = this.extractToolName(runId) || 'unknown_tool';
|
|
62
|
+
const mapped = this.config.serviceMap?.[toolName];
|
|
63
|
+
await this.ledger.log({
|
|
64
|
+
agent: this.config.agent,
|
|
65
|
+
service: mapped?.service || toolName,
|
|
66
|
+
action: mapped?.action || 'invoke',
|
|
67
|
+
status: 'error',
|
|
68
|
+
durationMs,
|
|
69
|
+
metadata: { source: 'langchain', runId, error: err.message },
|
|
70
|
+
}).catch(() => { });
|
|
71
|
+
}
|
|
72
|
+
// ==================== LLM CALLS ====================
|
|
73
|
+
async handleLLMStart(llm, prompts, runId) {
|
|
74
|
+
if (!this.config.trackLLM)
|
|
75
|
+
return;
|
|
76
|
+
this.runTimers.set(runId, Date.now());
|
|
77
|
+
}
|
|
78
|
+
async handleLLMEnd(output, runId) {
|
|
79
|
+
if (!this.config.trackLLM)
|
|
80
|
+
return;
|
|
81
|
+
const startTime = this.runTimers.get(runId);
|
|
82
|
+
const durationMs = startTime ? Date.now() - startTime : 0;
|
|
83
|
+
this.runTimers.delete(runId);
|
|
84
|
+
// Try to extract token usage and model info
|
|
85
|
+
const llmOutput = output;
|
|
86
|
+
const tokenUsage = llmOutput?.llmOutput?.tokenUsage;
|
|
87
|
+
const model = llmOutput?.llmOutput?.modelName;
|
|
88
|
+
// Estimate cost from tokens (rough: $0.01 per 1K tokens for GPT-4 class)
|
|
89
|
+
const totalTokens = tokenUsage?.totalTokens || 0;
|
|
90
|
+
const estimatedCostCents = Math.ceil(totalTokens * 0.001);
|
|
91
|
+
await this.ledger.log({
|
|
92
|
+
agent: this.config.agent,
|
|
93
|
+
service: model?.includes('claude') ? 'anthropic' : model?.includes('gpt') ? 'openai' : 'llm',
|
|
94
|
+
action: 'completion',
|
|
95
|
+
costCents: estimatedCostCents,
|
|
96
|
+
durationMs,
|
|
97
|
+
metadata: { source: 'langchain', runId, model, tokenUsage },
|
|
98
|
+
}).catch(() => { });
|
|
99
|
+
}
|
|
100
|
+
async handleLLMError(err, runId) {
|
|
101
|
+
if (!this.config.trackLLM)
|
|
102
|
+
return;
|
|
103
|
+
const startTime = this.runTimers.get(runId);
|
|
104
|
+
const durationMs = startTime ? Date.now() - startTime : 0;
|
|
105
|
+
this.runTimers.delete(runId);
|
|
106
|
+
await this.ledger.log({
|
|
107
|
+
agent: this.config.agent,
|
|
108
|
+
service: 'llm',
|
|
109
|
+
action: 'completion',
|
|
110
|
+
status: 'error',
|
|
111
|
+
durationMs,
|
|
112
|
+
metadata: { source: 'langchain', runId, error: err.message },
|
|
113
|
+
}).catch(() => { });
|
|
114
|
+
}
|
|
115
|
+
// ==================== CHAIN/AGENT RUNS ====================
|
|
116
|
+
async handleChainStart(chain, inputs, runId) {
|
|
117
|
+
if (!this.config.trackChains)
|
|
118
|
+
return;
|
|
119
|
+
this.runTimers.set(runId, Date.now());
|
|
120
|
+
}
|
|
121
|
+
async handleChainEnd(outputs, runId) {
|
|
122
|
+
if (!this.config.trackChains)
|
|
123
|
+
return;
|
|
124
|
+
const startTime = this.runTimers.get(runId);
|
|
125
|
+
const durationMs = startTime ? Date.now() - startTime : 0;
|
|
126
|
+
this.runTimers.delete(runId);
|
|
127
|
+
await this.ledger.log({
|
|
128
|
+
agent: this.config.agent,
|
|
129
|
+
service: 'langchain',
|
|
130
|
+
action: 'chain_run',
|
|
131
|
+
durationMs,
|
|
132
|
+
metadata: { source: 'langchain', runId },
|
|
133
|
+
}).catch(() => { });
|
|
134
|
+
}
|
|
135
|
+
async handleChainError(err, runId) {
|
|
136
|
+
if (!this.config.trackChains)
|
|
137
|
+
return;
|
|
138
|
+
const startTime = this.runTimers.get(runId);
|
|
139
|
+
const durationMs = startTime ? Date.now() - startTime : 0;
|
|
140
|
+
this.runTimers.delete(runId);
|
|
141
|
+
await this.ledger.log({
|
|
142
|
+
agent: this.config.agent,
|
|
143
|
+
service: 'langchain',
|
|
144
|
+
action: 'chain_run',
|
|
145
|
+
status: 'error',
|
|
146
|
+
durationMs,
|
|
147
|
+
metadata: { source: 'langchain', runId, error: err.message },
|
|
148
|
+
}).catch(() => { });
|
|
149
|
+
}
|
|
150
|
+
extractToolName(runId) {
|
|
151
|
+
return this.toolNameStore.get(runId);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
exports.AgentLedgerCallbackHandler = AgentLedgerCallbackHandler;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentLedger integration for MCP (Model Context Protocol) servers.
|
|
3
|
+
*
|
|
4
|
+
* Wraps MCP tool handlers so every tool invocation is logged.
|
|
5
|
+
*
|
|
6
|
+
* Usage with @modelcontextprotocol/sdk:
|
|
7
|
+
*
|
|
8
|
+
* import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
|
+
* import { AgentLedger } from '@agentledger/sdk';
|
|
10
|
+
* import { wrapMCPServer } from '@agentledger/sdk/integrations/mcp';
|
|
11
|
+
*
|
|
12
|
+
* const ledger = new AgentLedger({ apiKey: 'al_...' });
|
|
13
|
+
* const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
14
|
+
*
|
|
15
|
+
* // Register tools normally
|
|
16
|
+
* server.tool('send_email', { to: z.string(), body: z.string() }, async (args) => {
|
|
17
|
+
* return await sendEmail(args.to, args.body);
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // Wrap the server — all tool calls are now logged
|
|
21
|
+
* wrapMCPServer(ledger, server, { agent: 'my-mcp-server' });
|
|
22
|
+
*/
|
|
23
|
+
import type { AgentLedger } from '../index';
|
|
24
|
+
export interface MCPWrapConfig {
|
|
25
|
+
/** Agent name in AgentLedger */
|
|
26
|
+
agent: string;
|
|
27
|
+
/** Map tool names to AgentLedger service/action. Default: tool name as service, 'invoke' as action */
|
|
28
|
+
serviceMap?: Record<string, {
|
|
29
|
+
service: string;
|
|
30
|
+
action?: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Wraps an MCP server instance to log all tool invocations.
|
|
35
|
+
* Works by monkey-patching the tool registration method.
|
|
36
|
+
*
|
|
37
|
+
* Compatible with @modelcontextprotocol/sdk McpServer.
|
|
38
|
+
*/
|
|
39
|
+
export declare function wrapMCPServer(ledger: AgentLedger, server: MCPServerLike, config: MCPWrapConfig): void;
|
|
40
|
+
/**
|
|
41
|
+
* Alternative: wrap a single MCP tool handler function directly.
|
|
42
|
+
*
|
|
43
|
+
* Usage:
|
|
44
|
+
* server.tool('send_email', schema, wrapMCPTool(ledger, {
|
|
45
|
+
* agent: 'my-server',
|
|
46
|
+
* service: 'sendgrid',
|
|
47
|
+
* action: 'send_email',
|
|
48
|
+
* }, async (args) => {
|
|
49
|
+
* return await sendEmail(args.to, args.body);
|
|
50
|
+
* }));
|
|
51
|
+
*/
|
|
52
|
+
export declare function wrapMCPTool<TArgs, TResult>(ledger: AgentLedger, options: {
|
|
53
|
+
agent: string;
|
|
54
|
+
service: string;
|
|
55
|
+
action: string;
|
|
56
|
+
}, handler: (args: TArgs) => Promise<TResult>): (args: TArgs) => Promise<TResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Minimal interface for MCP server compatibility.
|
|
59
|
+
* We don't import @modelcontextprotocol/sdk to keep zero dependencies.
|
|
60
|
+
*/
|
|
61
|
+
interface MCPServerLike {
|
|
62
|
+
tool: (name: string, ...args: unknown[]) => unknown;
|
|
63
|
+
}
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AgentLedger integration for MCP (Model Context Protocol) servers.
|
|
4
|
+
*
|
|
5
|
+
* Wraps MCP tool handlers so every tool invocation is logged.
|
|
6
|
+
*
|
|
7
|
+
* Usage with @modelcontextprotocol/sdk:
|
|
8
|
+
*
|
|
9
|
+
* import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
|
+
* import { AgentLedger } from '@agentledger/sdk';
|
|
11
|
+
* import { wrapMCPServer } from '@agentledger/sdk/integrations/mcp';
|
|
12
|
+
*
|
|
13
|
+
* const ledger = new AgentLedger({ apiKey: 'al_...' });
|
|
14
|
+
* const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
15
|
+
*
|
|
16
|
+
* // Register tools normally
|
|
17
|
+
* server.tool('send_email', { to: z.string(), body: z.string() }, async (args) => {
|
|
18
|
+
* return await sendEmail(args.to, args.body);
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Wrap the server — all tool calls are now logged
|
|
22
|
+
* wrapMCPServer(ledger, server, { agent: 'my-mcp-server' });
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.wrapMCPServer = wrapMCPServer;
|
|
26
|
+
exports.wrapMCPTool = wrapMCPTool;
|
|
27
|
+
/**
|
|
28
|
+
* Wraps an MCP server instance to log all tool invocations.
|
|
29
|
+
* Works by monkey-patching the tool registration method.
|
|
30
|
+
*
|
|
31
|
+
* Compatible with @modelcontextprotocol/sdk McpServer.
|
|
32
|
+
*/
|
|
33
|
+
function wrapMCPServer(ledger, server, config) {
|
|
34
|
+
const originalTool = server.tool.bind(server);
|
|
35
|
+
server.tool = function wrappedTool(name, ...rest) {
|
|
36
|
+
// tool(name, schema, handler) or tool(name, description, schema, handler)
|
|
37
|
+
// The handler is always the last argument
|
|
38
|
+
const handler = rest[rest.length - 1];
|
|
39
|
+
const otherArgs = rest.slice(0, -1);
|
|
40
|
+
const wrappedHandler = async (...handlerArgs) => {
|
|
41
|
+
const mapped = config.serviceMap?.[name];
|
|
42
|
+
const { result } = await ledger.track({
|
|
43
|
+
agent: config.agent,
|
|
44
|
+
service: mapped?.service || name,
|
|
45
|
+
action: mapped?.action || 'invoke',
|
|
46
|
+
metadata: {
|
|
47
|
+
source: 'mcp',
|
|
48
|
+
toolName: name,
|
|
49
|
+
argsPreview: JSON.stringify(handlerArgs[0]).slice(0, 500),
|
|
50
|
+
},
|
|
51
|
+
}, () => handler(...handlerArgs));
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
return originalTool(name, ...otherArgs, wrappedHandler);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Alternative: wrap a single MCP tool handler function directly.
|
|
59
|
+
*
|
|
60
|
+
* Usage:
|
|
61
|
+
* server.tool('send_email', schema, wrapMCPTool(ledger, {
|
|
62
|
+
* agent: 'my-server',
|
|
63
|
+
* service: 'sendgrid',
|
|
64
|
+
* action: 'send_email',
|
|
65
|
+
* }, async (args) => {
|
|
66
|
+
* return await sendEmail(args.to, args.body);
|
|
67
|
+
* }));
|
|
68
|
+
*/
|
|
69
|
+
function wrapMCPTool(ledger, options, handler) {
|
|
70
|
+
return async (args) => {
|
|
71
|
+
const { result } = await ledger.track({
|
|
72
|
+
agent: options.agent,
|
|
73
|
+
service: options.service,
|
|
74
|
+
action: options.action,
|
|
75
|
+
metadata: {
|
|
76
|
+
source: 'mcp',
|
|
77
|
+
argsPreview: JSON.stringify(args).slice(0, 500),
|
|
78
|
+
},
|
|
79
|
+
}, () => handler(args));
|
|
80
|
+
return result;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentLedger integration for the OpenAI Agents SDK.
|
|
3
|
+
*
|
|
4
|
+
* Wraps tool functions so every tool invocation is automatically logged.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* import { AgentLedger } from '@agentledger/sdk';
|
|
8
|
+
* import { withAgentLedger } from '@agentledger/sdk/integrations/openai';
|
|
9
|
+
*
|
|
10
|
+
* const ledger = new AgentLedger({ apiKey: 'al_...' });
|
|
11
|
+
*
|
|
12
|
+
* // Wrap individual tool functions
|
|
13
|
+
* const trackedSendEmail = withAgentLedger(ledger, {
|
|
14
|
+
* agent: 'support-bot',
|
|
15
|
+
* service: 'sendgrid',
|
|
16
|
+
* action: 'send_email',
|
|
17
|
+
* }, sendEmail);
|
|
18
|
+
*
|
|
19
|
+
* // Or wrap an entire tools array
|
|
20
|
+
* const tools = wrapOpenAITools(ledger, 'my-agent', [
|
|
21
|
+
* { type: 'function', function: { name: 'send_email', ... } }
|
|
22
|
+
* ]);
|
|
23
|
+
*/
|
|
24
|
+
import type { AgentLedger, TrackOptions } from '../index';
|
|
25
|
+
/**
|
|
26
|
+
* Wraps a single async function with AgentLedger tracking.
|
|
27
|
+
* The function signature is preserved — drop-in replacement.
|
|
28
|
+
*/
|
|
29
|
+
export declare function withAgentLedger<TArgs extends unknown[], TResult>(ledger: AgentLedger, options: Pick<TrackOptions, 'agent' | 'service' | 'action'>, fn: (...args: TArgs) => Promise<TResult>): (...args: TArgs) => Promise<TResult>;
|
|
30
|
+
/**
|
|
31
|
+
* Map of tool names to their handler functions.
|
|
32
|
+
*/
|
|
33
|
+
type ToolHandlers = Record<string, (args: Record<string, unknown>) => Promise<unknown>>;
|
|
34
|
+
/**
|
|
35
|
+
* Service mapping: how to categorize each tool for AgentLedger tracking.
|
|
36
|
+
* If a tool isn't in the map, its name is used as both service and action.
|
|
37
|
+
*/
|
|
38
|
+
type ServiceMap = Record<string, {
|
|
39
|
+
service: string;
|
|
40
|
+
action?: string;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Creates a tool execution wrapper that logs all tool calls to AgentLedger.
|
|
44
|
+
*
|
|
45
|
+
* Usage:
|
|
46
|
+
* const executeTools = createToolExecutor(ledger, 'my-agent', {
|
|
47
|
+
* send_email: sendEmailFn,
|
|
48
|
+
* create_ticket: createTicketFn,
|
|
49
|
+
* }, {
|
|
50
|
+
* send_email: { service: 'sendgrid', action: 'send' },
|
|
51
|
+
* create_ticket: { service: 'jira', action: 'create_issue' },
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* // In your OpenAI agent loop:
|
|
55
|
+
* for (const toolCall of message.tool_calls) {
|
|
56
|
+
* const result = await executeTools(toolCall.function.name, JSON.parse(toolCall.function.arguments));
|
|
57
|
+
* }
|
|
58
|
+
*/
|
|
59
|
+
export declare function createToolExecutor(ledger: AgentLedger, agent: string, handlers: ToolHandlers, serviceMap?: ServiceMap): (toolName: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
60
|
+
/**
|
|
61
|
+
* Convenience: wraps the OpenAI chat.completions.create call itself to track LLM usage.
|
|
62
|
+
*/
|
|
63
|
+
export declare function wrapOpenAICompletion<TArgs extends unknown[], TResult>(ledger: AgentLedger, agent: string, createFn: (...args: TArgs) => Promise<TResult>): (...args: TArgs) => Promise<TResult>;
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AgentLedger integration for the OpenAI Agents SDK.
|
|
4
|
+
*
|
|
5
|
+
* Wraps tool functions so every tool invocation is automatically logged.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { AgentLedger } from '@agentledger/sdk';
|
|
9
|
+
* import { withAgentLedger } from '@agentledger/sdk/integrations/openai';
|
|
10
|
+
*
|
|
11
|
+
* const ledger = new AgentLedger({ apiKey: 'al_...' });
|
|
12
|
+
*
|
|
13
|
+
* // Wrap individual tool functions
|
|
14
|
+
* const trackedSendEmail = withAgentLedger(ledger, {
|
|
15
|
+
* agent: 'support-bot',
|
|
16
|
+
* service: 'sendgrid',
|
|
17
|
+
* action: 'send_email',
|
|
18
|
+
* }, sendEmail);
|
|
19
|
+
*
|
|
20
|
+
* // Or wrap an entire tools array
|
|
21
|
+
* const tools = wrapOpenAITools(ledger, 'my-agent', [
|
|
22
|
+
* { type: 'function', function: { name: 'send_email', ... } }
|
|
23
|
+
* ]);
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.withAgentLedger = withAgentLedger;
|
|
27
|
+
exports.createToolExecutor = createToolExecutor;
|
|
28
|
+
exports.wrapOpenAICompletion = wrapOpenAICompletion;
|
|
29
|
+
/**
|
|
30
|
+
* Wraps a single async function with AgentLedger tracking.
|
|
31
|
+
* The function signature is preserved — drop-in replacement.
|
|
32
|
+
*/
|
|
33
|
+
function withAgentLedger(ledger, options, fn) {
|
|
34
|
+
return async (...args) => {
|
|
35
|
+
const { result } = await ledger.track({
|
|
36
|
+
agent: options.agent,
|
|
37
|
+
service: options.service,
|
|
38
|
+
action: options.action,
|
|
39
|
+
metadata: {
|
|
40
|
+
source: 'openai-agents',
|
|
41
|
+
argsPreview: JSON.stringify(args).slice(0, 500),
|
|
42
|
+
},
|
|
43
|
+
}, () => fn(...args));
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a tool execution wrapper that logs all tool calls to AgentLedger.
|
|
49
|
+
*
|
|
50
|
+
* Usage:
|
|
51
|
+
* const executeTools = createToolExecutor(ledger, 'my-agent', {
|
|
52
|
+
* send_email: sendEmailFn,
|
|
53
|
+
* create_ticket: createTicketFn,
|
|
54
|
+
* }, {
|
|
55
|
+
* send_email: { service: 'sendgrid', action: 'send' },
|
|
56
|
+
* create_ticket: { service: 'jira', action: 'create_issue' },
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* // In your OpenAI agent loop:
|
|
60
|
+
* for (const toolCall of message.tool_calls) {
|
|
61
|
+
* const result = await executeTools(toolCall.function.name, JSON.parse(toolCall.function.arguments));
|
|
62
|
+
* }
|
|
63
|
+
*/
|
|
64
|
+
function createToolExecutor(ledger, agent, handlers, serviceMap) {
|
|
65
|
+
return async (toolName, args) => {
|
|
66
|
+
const handler = handlers[toolName];
|
|
67
|
+
if (!handler) {
|
|
68
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
69
|
+
}
|
|
70
|
+
const mapped = serviceMap?.[toolName];
|
|
71
|
+
const { result } = await ledger.track({
|
|
72
|
+
agent,
|
|
73
|
+
service: mapped?.service || toolName,
|
|
74
|
+
action: mapped?.action || 'invoke',
|
|
75
|
+
metadata: {
|
|
76
|
+
source: 'openai-agents',
|
|
77
|
+
toolName,
|
|
78
|
+
argsPreview: JSON.stringify(args).slice(0, 500),
|
|
79
|
+
},
|
|
80
|
+
}, () => handler(args));
|
|
81
|
+
return result;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Convenience: wraps the OpenAI chat.completions.create call itself to track LLM usage.
|
|
86
|
+
*/
|
|
87
|
+
function wrapOpenAICompletion(ledger, agent, createFn) {
|
|
88
|
+
return withAgentLedger(ledger, { agent, service: 'openai', action: 'completion' }, createFn);
|
|
89
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentledger",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Track, monitor, and control AI agent actions. The missing observability layer for AI agents. Zero dependencies.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./integrations/langchain": {
|
|
14
|
+
"import": "./dist/integrations/langchain.js",
|
|
15
|
+
"types": "./dist/integrations/langchain.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./integrations/openai": {
|
|
18
|
+
"import": "./dist/integrations/openai.js",
|
|
19
|
+
"types": "./dist/integrations/openai.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./integrations/mcp": {
|
|
22
|
+
"import": "./dist/integrations/mcp.js",
|
|
23
|
+
"types": "./dist/integrations/mcp.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./integrations/express": {
|
|
26
|
+
"import": "./dist/integrations/express.js",
|
|
27
|
+
"types": "./dist/integrations/express.d.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": ["dist", "README.md"],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"prepublishOnly": "npm run build"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"ai", "agents", "monitoring", "observability", "langchain", "openai",
|
|
37
|
+
"mcp", "safety", "llm", "ai-agents", "kill-switch", "budget",
|
|
38
|
+
"cost-tracking", "anthropic", "claude"
|
|
39
|
+
],
|
|
40
|
+
"author": "AgentLedger",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"homepage": "https://agentledger.co",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/mnoonan/agentledger"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/mnoonan/agentledger/issues"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|