@agent-ledger/sdk-ts 0.0.3 → 0.0.5

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
@@ -1,6 +1,31 @@
1
1
  # @agent-ledger/sdk-ts
2
2
 
3
- Official TypeScript client for Agent Ledger. The SDK instruments your agents so you can stream sessions, log LLM/tool activity, and enforce budget guardrails against the Agent Ledger API.
3
+ Official TypeScript client for Agent Ledger. Use it to instrument any Node.js/Edge agent with structured telemetry, stream session events, and receive immediate feedback when budget guardrails block spending.
4
+
5
+ Portal: https://agent-ledger.thabo.xyz/
6
+
7
+ ## Table of contents
8
+
9
+ 1. [Features](#features)
10
+ 2. [Installation](#installation)
11
+ 3. [Runtime requirements](#runtime-requirements)
12
+ 4. [Getting started](#getting-started)
13
+ 5. [Session lifecycle](#session-lifecycle)
14
+ 6. [Event reference](#event-reference)
15
+ 7. [API reference](#api-reference)
16
+ 8. [Error handling](#error-handling)
17
+ 9. [Configuration & environments](#configuration--environments)
18
+ 10. [Recipes](#recipes)
19
+ 11. [Testing & local dev](#testing--local-dev)
20
+ 12. [License](#license)
21
+
22
+ ## Features
23
+
24
+ - Minimal, dependency-free client that speaks directly to the Agent Ledger REST API (`/v1/sessions` and `/v1/events`).
25
+ - First-class TypeScript typings for every event structure (`LlmCallEvent`, `ToolCallEvent`, `ToolResultEvent`).
26
+ - Built-in budget guardrail awareness through `BudgetGuardrailError` so you can halt expensive runs immediately.
27
+ - Works anywhere `fetch` is available (Node.js 18+, Bun, Deno, Edge runtimes, or browsers talking to your own proxy).
28
+ - Simple abstractions so you can reuse the same instrumentation across CLI scripts, background workers, or serverless functions.
4
29
 
5
30
  ## Installation
6
31
 
@@ -8,77 +33,202 @@ Official TypeScript client for Agent Ledger. The SDK instruments your agents so
8
33
  pnpm add @agent-ledger/sdk-ts
9
34
  # or
10
35
  npm install @agent-ledger/sdk-ts
36
+ # or
37
+ yarn add @agent-ledger/sdk-ts
11
38
  ```
12
39
 
13
- ## Quick start
40
+ ## Runtime requirements
41
+
42
+ - Node.js 18 or newer (for the built-in `fetch` implementation). If you run older Node versions, polyfill `fetch` before importing the SDK.
43
+ - An Agent Ledger API key generated from the dashboard (Settings → API Keys).
44
+ - Outbound HTTPS access to `https://agent-ledger-api.azurewebsites.net` (or your self-hosted instance).
45
+
46
+ ## Getting started
14
47
 
15
48
  ```ts
16
- import { AgentLedgerClient } from "@agent-ledger/sdk-ts";
49
+ import {
50
+ AgentLedgerClient,
51
+ BudgetGuardrailError,
52
+ withSession,
53
+ instrumentTool,
54
+ } from "@agent-ledger/sdk-ts";
17
55
 
18
56
  const ledger = new AgentLedgerClient({
19
57
  apiKey: process.env.AGENT_LEDGER_API_KEY!,
20
- // baseUrl is optional. Defaults to the production Agent Ledger API.
21
58
  });
22
59
 
23
- async function runAgent() {
24
- const sessionId = await ledger.startSession("support-bot");
25
- try {
26
- await ledger.logLLMCall(sessionId, {
27
- stepIndex: 0,
28
- model: "gpt-4o",
29
- provider: "openai",
30
- prompt: "Draft a welcome email",
31
- response: "Hello ...",
32
- tokensIn: 120,
33
- tokensOut: 98,
34
- latencyMs: 2300,
35
- });
60
+ export async function runSupportAgent(prompt: string) {
61
+ return withSession(ledger, "support-bot", async ({ sessionId, steps }) => {
62
+ try {
63
+ // 1. Run your own LLM logic
64
+ const response = await callModel(prompt);
65
+
66
+ // 2. Log the LLM call (Agent Ledger auto-computes spend from provider/model/tokens)
67
+ await ledger.logLLMCall(sessionId, {
68
+ stepIndex: steps.next(),
69
+ provider: "openai",
70
+ model: "gpt-4o-mini",
71
+ prompt,
72
+ response: response.text,
73
+ tokensIn: response.usage.inputTokens,
74
+ tokensOut: response.usage.outputTokens,
75
+ latencyMs: response.latencyMs,
76
+ });
77
+
78
+ // 3. Example tool wrapper (automatically logs tool_call + tool_result)
79
+ await instrumentTool({
80
+ ledger,
81
+ sessionId,
82
+ stepIndex: steps.next(),
83
+ toolName: "weather",
84
+ toolInput: { city: "Boston" },
85
+ run: async () => fetchWeather("Boston"),
86
+ });
87
+
88
+ return response.text;
89
+ } catch (err) {
90
+ if (err instanceof BudgetGuardrailError) {
91
+ console.warn("Budget exceeded", err.details);
92
+ }
93
+ throw err;
94
+ }
95
+ });
96
+ }
97
+ ```
98
+
99
+ ## Session lifecycle
100
+
101
+ 1. **Start sessions** early with `startSession(agentName)` to capture every downstream event.
102
+ 2. **Log events** whenever you call an LLM or tool:
103
+ - `logLLMCall` for prompts/responses.
104
+ - `logToolCall` for tool invocations (store the inputs).
105
+ - `logToolResult` for tool responses (store outputs/latency).
106
+ - `logEvents` if you need to batch arbitrary event objects.
107
+ 3. **End sessions** with `endSession(sessionId, "success" | "error", { errorMessage? })` so the dashboard knows whether the run finished cleanly.
108
+
109
+ Tip: keep a simple helper that wraps this flow so every agent in your repo emits consistent telemetry.
110
+
111
+ ## Event reference
112
+
113
+ | Event | Required fields | Optional fields | Notes |
114
+ | --- | --- | --- | --- |
115
+ | `LlmCallEvent` | `stepIndex`, `model`, `provider`, `prompt`, `response`, `tokensIn`, `tokensOut`, `latencyMs` | — | `logLLMCall` automatically sets `type` to `llm_call` and lets the backend price the call based on provider/model. |
116
+ | `ToolCallEvent` | `stepIndex`, `toolName`, `toolInput` | — | Capture the structured input you sent to an internal or external tool. |
117
+ | `ToolResultEvent` | `stepIndex`, `toolName`, `toolOutput`, `latencyMs` | — | Use together with `ToolCallEvent` to understand tool latency and result size. |
118
+ | Custom | Whatever your workflow needs plus `type` | — | Supply via `logEvents` if you want to store derived signals (examples: `session_start`, `session_end`, `guardrail_trigger`). |
119
+
120
+ Conventions:
121
+
122
+ - `stepIndex` is a zero-based counter that makes it easy to diff runs. Increment it in the order events happen, even if multiple tools share the same LLM output.
123
+ - Keep prompts/responses under 64 KB per event so they render nicely in the dashboard diff view.
124
+ - All numeric values are stored as numbers (no strings) so the API can aggregate cost statistics.
125
+
126
+ ## API reference
127
+
128
+ ### `new AgentLedgerClient(options)`
129
+
130
+ | Option | Type | Description |
131
+ | --- | --- | --- |
132
+ | `apiKey` | `string` (required) | Workspace API key from the dashboard. |
133
+
134
+ ### `startSession(agentName: string): Promise<string>`
135
+
136
+ Creates a session row and returns its UUID. `agentName` should match how you identify the workflow in the dashboard (e.g., `support-bot`, `retrieval-worker`).
36
137
 
37
- // ...additional tool calls/events...
138
+ ### `endSession(sessionId, status, opts?)`
38
139
 
39
- await ledger.endSession(sessionId, "success");
40
- } catch (err) {
41
- await ledger.endSession(sessionId, "error", { errorMessage: (err as Error).message });
42
- throw err;
140
+ Marks the session closed. Pass `{ errorMessage }` for failures so the UI shows context next to the run.
141
+
142
+ ### `logEvents(sessionId, events)`
143
+
144
+ Lowest-level ingestion helper. Accepts an array of plain objects, so you can batch multiple events into a single network call. Events must include a `type` string (e.g., `llm_call`).
145
+
146
+ ### `logLLMCall(sessionId, event)` / `logToolCall` / `logToolResult`
147
+
148
+ Typed helpers that:
149
+
150
+ - Fill the `type` automatically.
151
+ - Validate required fields at compile time.
152
+ - Call `logEvents` under the hood.
153
+
154
+ ### Types exported
155
+
156
+ `AgentLedgerClient`, `AgentLedgerClientOptions`, `BudgetGuardrailError`, `BudgetGuardrailDetails`, `LlmCallEvent`, `ToolCallEvent`, `ToolResultEvent`, `AnyEvent`, `EventType`.
157
+
158
+ ## Error handling
159
+
160
+ - **`BudgetGuardrailError`** (HTTP 429): thrown when the backend refuses the event because the agent exceeded its daily limit. Inspect `error.details`:
161
+
162
+ ```ts
163
+ {
164
+ agentName: string;
165
+ dailyLimitUsd: number;
166
+ spentTodayUsd: number;
167
+ attemptedCostUsd: number;
168
+ projectedCostUsd: number;
169
+ remainingBudgetUsd: number;
43
170
  }
44
- }
45
- ```
171
+ ```
46
172
 
47
- ## API surface
173
+ - **Generic `Error`**: wraps any other non-2xx response (`startSession`, `endSession`, `logEvents`). The `.message` contains the server-provided text when available.
48
174
 
49
- - `new AgentLedgerClient({ apiKey, baseUrl? })`
50
- - `startSession(agentName)` → session ID
51
- - `endSession(sessionId, status, { errorMessage? })`
52
- - `logEvents(sessionId, events)` (low-level entry point)
53
- - `logLLMCall`, `logToolCall`, `logToolResult` (typed helpers)
175
+ Recommended practice: catch errors where you call `logEvents` so your business logic can continue (or at least emit a structured failure) even when the telemetry call is rejected.
54
176
 
55
- All helpers eventually POST to the Agent Ledger REST API using the API key supplied in the constructor.
177
+ ## Configuration & environments
56
178
 
57
- ### Budget guardrail errors
179
+ - Provide `AGENT_LEDGER_API_KEY` (or load it from your preferred secrets manager) and the SDK connects to the hosted API automatically.
180
+ - Default endpoint → `https://agent-ledger-api.azurewebsites.net`.
181
+ - For local API experiments, keep the SDK untouched and proxy traffic through your own tooling (MSW, mock servers, etc.).
58
182
 
59
- If Agent Ledger blocks an event because of a per-agent budget limit, the SDK throws `BudgetGuardrailError`. Catch it to inspect the `details` payload and react accordingly:
183
+ Because the client is stateless, you can instantiate one per agent type or share a singleton across the entire app.
184
+
185
+ ## Recipes
186
+
187
+ ### Streaming agents / multi-step workflows
188
+
189
+ Reuse a monotonically increasing `stepIndex` while you stream partial responses. You can emit interim tool calls before the final LLM response lands to visualize branching logic.
190
+
191
+ ### Custom tool instrumentation
60
192
 
61
193
  ```ts
62
- import { BudgetGuardrailError } from "@agent-ledger/sdk-ts";
194
+ async function callWeather(sessionId: string, city: string, stepIndex: number) {
195
+ await ledger.logToolCall(sessionId, {
196
+ stepIndex,
197
+ toolName: "weather",
198
+ toolInput: { city },
199
+ });
200
+
201
+ const result = await fetchWeather(city);
202
+
203
+ await ledger.logToolResult(sessionId, {
204
+ stepIndex,
205
+ toolName: "weather",
206
+ toolOutput: result,
207
+ latencyMs: result.latencyMs,
208
+ });
209
+ }
210
+ ```
63
211
 
212
+ ### Handling guardrail blocks
213
+
214
+ ```ts
64
215
  try {
65
- await ledger.logEvents(sessionId, events);
216
+ await ledger.logLLMCall(sessionId, event);
66
217
  } catch (err) {
67
218
  if (err instanceof BudgetGuardrailError) {
68
- console.warn("Budget exceeded", err.details);
219
+ await ledger.endSession(sessionId, "error", {
220
+ errorMessage: `Budget exceeded: remaining ${err.details.remainingBudgetUsd}`,
221
+ });
222
+ return;
69
223
  }
224
+ throw err;
70
225
  }
71
226
  ```
72
227
 
73
- ### Environment overrides
74
-
75
- - `baseUrl` option overrides the API host per client.
76
- - When omitted, the SDK looks for `process.env.AGENT_LEDGER_BASE_URL` and falls back to `https://agent-ledger-api.azurewebsites.net`.
77
- - For local testing, set `AGENT_LEDGER_BASE_URL=http://localhost:3000` (or your tunnel URL) before instantiating the client.
78
-
79
- ## TypeScript support
228
+ ## Testing & local dev
80
229
 
81
- This package ships its own `.d.ts` files (generated from `src/index.ts`). No additional typings are required.
230
+ - The SDK performs real HTTP requests. For unit tests, stub `global.fetch` or intercept calls with tools like [MSW](https://mswjs.io/).
231
+ - When running the Agent Ledger API locally, ensure your test key exists in the development database and export it via `AGENT_LEDGER_API_KEY`.
82
232
 
83
233
  ## License
84
234
 
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export type EventType = "llm_call" | "tool_call" | "tool_result" | "session_star
2
2
  export interface AgentLedgerClientOptions {
3
3
  apiKey: string;
4
4
  baseUrl?: string;
5
+ fetch?: typeof fetch;
6
+ defaultHeaders?: Record<string, string>;
5
7
  }
6
8
  export type BudgetGuardrailDetails = {
7
9
  agentName: string;
@@ -18,12 +20,14 @@ export declare class BudgetGuardrailError extends Error {
18
20
  export declare class AgentLedgerClient {
19
21
  private apiKey;
20
22
  private baseUrl;
23
+ private fetchImpl;
24
+ private defaultHeaders;
21
25
  constructor(opts: AgentLedgerClientOptions);
22
26
  startSession(agentName: string): Promise<string>;
23
27
  endSession(sessionId: string, status: "success" | "error", opts?: {
24
28
  errorMessage?: string;
25
29
  }): Promise<void>;
26
- logEvents(sessionId: string, events: any[]): Promise<void>;
30
+ logEvents(sessionId: string, events: AnyEvent[] | Array<Record<string, any>>): Promise<void>;
27
31
  logLLMCall(sessionId: string, e: Omit<LlmCallEvent, "type">): Promise<void>;
28
32
  logToolCall(sessionId: string, e: Omit<ToolCallEvent, "type">): Promise<void>;
29
33
  logToolResult(sessionId: string, e: Omit<ToolResultEvent, "type">): Promise<void>;
@@ -53,3 +57,22 @@ export type ToolResultEvent = {
53
57
  latencyMs: number;
54
58
  };
55
59
  export type AnyEvent = LlmCallEvent | ToolCallEvent | ToolResultEvent;
60
+ export type StepCounter = {
61
+ next: () => number;
62
+ peek: () => number;
63
+ reset: (value?: number) => void;
64
+ };
65
+ export declare function createStepCounter(startAt?: number): StepCounter;
66
+ export declare function withSession<T>(ledger: AgentLedgerClient, agentName: string, fn: (ctx: {
67
+ sessionId: string;
68
+ steps: StepCounter;
69
+ }) => Promise<T>): Promise<T>;
70
+ export declare function instrumentTool<TInput, TOutput>(opts: {
71
+ ledger: AgentLedgerClient;
72
+ sessionId: string;
73
+ stepIndex: number;
74
+ toolName: string;
75
+ toolInput: TInput;
76
+ run: () => Promise<TOutput> | TOutput;
77
+ captureLatencyMs?: boolean;
78
+ }): Promise<TOutput>;
package/dist/index.js CHANGED
@@ -18,16 +18,23 @@ export class BudgetGuardrailError extends Error {
18
18
  }
19
19
  export class AgentLedgerClient {
20
20
  constructor(opts) {
21
- this.apiKey = opts.apiKey;
21
+ const key = (opts.apiKey ?? "").trim();
22
+ if (!key) {
23
+ throw new Error("AgentLedgerClient: apiKey is required");
24
+ }
25
+ this.apiKey = key;
22
26
  const resolvedBaseUrl = resolveBaseUrl(opts.baseUrl).trim();
23
27
  this.baseUrl = resolvedBaseUrl.replace(/\/$/, "");
28
+ this.fetchImpl = opts.fetch ?? fetch;
29
+ this.defaultHeaders = { ...(opts.defaultHeaders ?? {}) };
24
30
  }
25
31
  async startSession(agentName) {
26
- const res = await fetch(`${this.baseUrl}/v1/sessions`, {
32
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/sessions`, {
27
33
  method: "POST",
28
34
  headers: {
29
35
  "Content-Type": "application/json",
30
- "x-api-key": this.apiKey
36
+ "x-api-key": this.apiKey,
37
+ ...this.defaultHeaders,
31
38
  },
32
39
  body: JSON.stringify({ agentName })
33
40
  });
@@ -39,11 +46,12 @@ export class AgentLedgerClient {
39
46
  return data.id;
40
47
  }
41
48
  async endSession(sessionId, status, opts) {
42
- const res = await fetch(`${this.baseUrl}/v1/sessions/${sessionId}/end`, {
49
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/sessions/${sessionId}/end`, {
43
50
  method: "POST",
44
51
  headers: {
45
52
  "Content-Type": "application/json",
46
- "x-api-key": this.apiKey
53
+ "x-api-key": this.apiKey,
54
+ ...this.defaultHeaders,
47
55
  },
48
56
  body: JSON.stringify({ status, errorMessage: opts?.errorMessage ?? null })
49
57
  });
@@ -53,11 +61,12 @@ export class AgentLedgerClient {
53
61
  }
54
62
  }
55
63
  async logEvents(sessionId, events) {
56
- const res = await fetch(`${this.baseUrl}/v1/events`, {
64
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/events`, {
57
65
  method: "POST",
58
66
  headers: {
59
67
  "Content-Type": "application/json",
60
- "x-api-key": this.apiKey
68
+ "x-api-key": this.apiKey,
69
+ ...this.defaultHeaders,
61
70
  },
62
71
  body: JSON.stringify({ sessionId, events })
63
72
  });
@@ -90,4 +99,70 @@ export class AgentLedgerClient {
90
99
  return this.logEvents(sessionId, [event]);
91
100
  }
92
101
  }
102
+ export function createStepCounter(startAt = 0) {
103
+ let value = startAt;
104
+ return {
105
+ next: () => value++,
106
+ peek: () => value,
107
+ reset: (v = 0) => {
108
+ value = v;
109
+ },
110
+ };
111
+ }
112
+ export async function withSession(ledger, agentName, fn) {
113
+ const sessionId = await ledger.startSession(agentName);
114
+ const steps = createStepCounter(0);
115
+ try {
116
+ const result = await fn({ sessionId, steps });
117
+ await ledger.endSession(sessionId, "success");
118
+ return result;
119
+ }
120
+ catch (err) {
121
+ const message = err instanceof Error ? err.message : String(err);
122
+ try {
123
+ await ledger.endSession(sessionId, "error", { errorMessage: message });
124
+ }
125
+ catch {
126
+ // ignore telemetry failures on shutdown
127
+ }
128
+ throw err;
129
+ }
130
+ }
131
+ export async function instrumentTool(opts) {
132
+ await opts.ledger.logToolCall(opts.sessionId, {
133
+ stepIndex: opts.stepIndex,
134
+ toolName: opts.toolName,
135
+ toolInput: opts.toolInput,
136
+ });
137
+ const started = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
138
+ try {
139
+ const output = await opts.run();
140
+ const ended = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
141
+ const latencyMs = Math.max(0, Math.round(ended - started));
142
+ await opts.ledger.logToolResult(opts.sessionId, {
143
+ stepIndex: opts.stepIndex,
144
+ toolName: opts.toolName,
145
+ toolOutput: output,
146
+ latencyMs: opts.captureLatencyMs === false ? 0 : latencyMs,
147
+ });
148
+ return output;
149
+ }
150
+ catch (err) {
151
+ const ended = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
152
+ const latencyMs = Math.max(0, Math.round(ended - started));
153
+ const message = err instanceof Error ? err.message : String(err);
154
+ try {
155
+ await opts.ledger.logToolResult(opts.sessionId, {
156
+ stepIndex: opts.stepIndex,
157
+ toolName: opts.toolName,
158
+ toolOutput: { error: message },
159
+ latencyMs: opts.captureLatencyMs === false ? 0 : latencyMs,
160
+ });
161
+ }
162
+ catch {
163
+ // ignore
164
+ }
165
+ throw err;
166
+ }
167
+ }
93
168
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,MAAM,aAAa,GAAG,4CAA4C,CAAC;AACnE,MAAM,WAAW,GACf,OAAO,UAAU,KAAK,WAAW,IAAK,UAAkB,CAAC,OAAO,CAAC,CAAC,CAAE,UAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAE7G,SAAS,cAAc,CAAC,QAAiB;IACvC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,OAAO,GAAG,WAAW,EAAE,GAAG,CAAC;IACjC,IAAI,OAAO,EAAE,qBAAqB,EAAE,CAAC;QACnC,OAAO,OAAO,CAAC,qBAAqB,CAAC;IACvC,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAWD,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAG7C,YAAY,OAAe,EAAE,OAA+B;QAC1D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED,MAAM,OAAO,iBAAiB;IAM5B,YAAY,IAA8B;QACxC,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACvC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;QAClB,MAAM,eAAe,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5D,IAAI,CAAC,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAElD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC;QACrC,IAAI,CAAC,cAAc,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC,EAAE,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,SAAiB;QAClC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,OAAO,cAAc,EAAE;YAC9D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,WAAW,EAAE,IAAI,CAAC,MAAM;gBACxB,GAAG,IAAI,CAAC,cAAc;aACvB;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC;SACpC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,4BAA4B,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;QACxE,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC,EAAY,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,UAAU,CACd,SAAiB,EACjB,MAA2B,EAC3B,IAAgC;QAEhC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,OAAO,gBAAgB,SAAS,MAAM,EAAE;YAC/E,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,WAAW,EAAE,IAAI,CAAC,MAAM;gBACxB,GAAG,IAAI,CAAC,cAAc;aACvB;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,IAAI,IAAI,EAAE,CAAC;SAC3E,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,0BAA0B,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,SAAiB,EAAE,MAA+C;QAChF,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,OAAO,YAAY,EAAE;YAC5D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,WAAW,EAAE,IAAI,CAAC,MAAM;gBACxB,GAAG,IAAI,CAAC,cAAc;aACvB;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;SAC5C,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,OAAO,GAAQ,IAAI,CAAC;YACxB,IAAI,CAAC;gBACH,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC3C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC;gBAC3C,MAAM,IAAI,oBAAoB,CAAC,OAAO,EAAE,KAAK,IAAI,uBAAuB,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YAC7F,CAAC;YAED,MAAM,OAAO,GAAG,OAAO,EAAE,KAAK,IAAI,IAAI,IAAI,sBAAsB,CAAC;YACjE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CACd,SAAiB,EACjB,CAA6B;QAE7B,MAAM,KAAK,GAAiB,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,WAAW,CACf,SAAiB,EACjB,CAA8B;QAE9B,MAAM,KAAK,GAAkB,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC;QACzD,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,SAAiB,EACjB,CAAgC;QAEhC,MAAM,KAAK,GAAoB,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,EAAE,CAAC;QAC7D,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5C,CAAC;CACF;AAqCD,MAAM,UAAU,iBAAiB,CAAC,OAAO,GAAG,CAAC;IAC3C,IAAI,KAAK,GAAG,OAAO,CAAC;IACpB,OAAO;QACL,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE;QACnB,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK;QACjB,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE;YACf,KAAK,GAAG,CAAC,CAAC;QACZ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAyB,EACzB,SAAiB,EACjB,EAAkE;IAElE,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9C,MAAM,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAC9C,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC;QACzE,CAAC;QAAC,MAAM,CAAC;YACP,wCAAwC;QAC1C,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAkB,IAQrD;IACC,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE;QAC5C,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,OAAO,WAAW,KAAK,WAAW,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IACvG,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,OAAO,WAAW,KAAK,WAAW,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACrG,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC;QAE3D,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE;YAC9C,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,UAAU,EAAE,MAAM;YAClB,SAAS,EAAE,IAAI,CAAC,gBAAgB,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;SAC3D,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,OAAO,WAAW,KAAK,WAAW,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACrG,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE;gBAC9C,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,UAAU,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE;gBAC9B,SAAS,EAAE,IAAI,CAAC,gBAAgB,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;aAC3D,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-ledger/sdk-ts",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -8,6 +8,8 @@ export type EventType =
8
8
  export interface AgentLedgerClientOptions {
9
9
  apiKey: string;
10
10
  baseUrl?: string;
11
+ fetch?: typeof fetch;
12
+ defaultHeaders?: Record<string, string>;
11
13
  }
12
14
 
13
15
  const PROD_BASE_URL = "https://agent-ledger-api.azurewebsites.net";
@@ -45,19 +47,29 @@ export class BudgetGuardrailError extends Error {
45
47
  export class AgentLedgerClient {
46
48
  private apiKey: string;
47
49
  private baseUrl: string;
50
+ private fetchImpl: typeof fetch;
51
+ private defaultHeaders: Record<string, string>;
48
52
 
49
53
  constructor(opts: AgentLedgerClientOptions) {
50
- this.apiKey = opts.apiKey;
54
+ const key = (opts.apiKey ?? "").trim();
55
+ if (!key) {
56
+ throw new Error("AgentLedgerClient: apiKey is required");
57
+ }
58
+ this.apiKey = key;
51
59
  const resolvedBaseUrl = resolveBaseUrl(opts.baseUrl).trim();
52
60
  this.baseUrl = resolvedBaseUrl.replace(/\/$/, "");
61
+
62
+ this.fetchImpl = opts.fetch ?? fetch;
63
+ this.defaultHeaders = { ...(opts.defaultHeaders ?? {}) };
53
64
  }
54
65
 
55
66
  async startSession(agentName: string): Promise<string> {
56
- const res = await fetch(`${this.baseUrl}/v1/sessions`, {
67
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/sessions`, {
57
68
  method: "POST",
58
69
  headers: {
59
70
  "Content-Type": "application/json",
60
- "x-api-key": this.apiKey
71
+ "x-api-key": this.apiKey,
72
+ ...this.defaultHeaders,
61
73
  },
62
74
  body: JSON.stringify({ agentName })
63
75
  });
@@ -76,11 +88,12 @@ export class AgentLedgerClient {
76
88
  status: "success" | "error",
77
89
  opts?: { errorMessage?: string }
78
90
  ) {
79
- const res = await fetch(`${this.baseUrl}/v1/sessions/${sessionId}/end`, {
91
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/sessions/${sessionId}/end`, {
80
92
  method: "POST",
81
93
  headers: {
82
94
  "Content-Type": "application/json",
83
- "x-api-key": this.apiKey
95
+ "x-api-key": this.apiKey,
96
+ ...this.defaultHeaders,
84
97
  },
85
98
  body: JSON.stringify({ status, errorMessage: opts?.errorMessage ?? null })
86
99
  });
@@ -91,12 +104,13 @@ export class AgentLedgerClient {
91
104
  }
92
105
  }
93
106
 
94
- async logEvents(sessionId: string, events: any[]) {
95
- const res = await fetch(`${this.baseUrl}/v1/events`, {
107
+ async logEvents(sessionId: string, events: AnyEvent[] | Array<Record<string, any>>) {
108
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/events`, {
96
109
  method: "POST",
97
110
  headers: {
98
111
  "Content-Type": "application/json",
99
- "x-api-key": this.apiKey
112
+ "x-api-key": this.apiKey,
113
+ ...this.defaultHeaders,
100
114
  },
101
115
  body: JSON.stringify({ sessionId, events })
102
116
  });
@@ -171,4 +185,90 @@ export type ToolResultEvent = {
171
185
  latencyMs: number;
172
186
  };
173
187
 
174
- export type AnyEvent = LlmCallEvent | ToolCallEvent | ToolResultEvent;
188
+ export type AnyEvent = LlmCallEvent | ToolCallEvent | ToolResultEvent;
189
+
190
+ export type StepCounter = {
191
+ next: () => number;
192
+ peek: () => number;
193
+ reset: (value?: number) => void;
194
+ };
195
+
196
+ export function createStepCounter(startAt = 0): StepCounter {
197
+ let value = startAt;
198
+ return {
199
+ next: () => value++,
200
+ peek: () => value,
201
+ reset: (v = 0) => {
202
+ value = v;
203
+ },
204
+ };
205
+ }
206
+
207
+ export async function withSession<T>(
208
+ ledger: AgentLedgerClient,
209
+ agentName: string,
210
+ fn: (ctx: { sessionId: string; steps: StepCounter }) => Promise<T>
211
+ ): Promise<T> {
212
+ const sessionId = await ledger.startSession(agentName);
213
+ const steps = createStepCounter(0);
214
+ try {
215
+ const result = await fn({ sessionId, steps });
216
+ await ledger.endSession(sessionId, "success");
217
+ return result;
218
+ } catch (err) {
219
+ const message = err instanceof Error ? err.message : String(err);
220
+ try {
221
+ await ledger.endSession(sessionId, "error", { errorMessage: message });
222
+ } catch {
223
+ // ignore telemetry failures on shutdown
224
+ }
225
+ throw err;
226
+ }
227
+ }
228
+
229
+ export async function instrumentTool<TInput, TOutput>(opts: {
230
+ ledger: AgentLedgerClient;
231
+ sessionId: string;
232
+ stepIndex: number;
233
+ toolName: string;
234
+ toolInput: TInput;
235
+ run: () => Promise<TOutput> | TOutput;
236
+ captureLatencyMs?: boolean;
237
+ }): Promise<TOutput> {
238
+ await opts.ledger.logToolCall(opts.sessionId, {
239
+ stepIndex: opts.stepIndex,
240
+ toolName: opts.toolName,
241
+ toolInput: opts.toolInput,
242
+ });
243
+
244
+ const started = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
245
+ try {
246
+ const output = await opts.run();
247
+ const ended = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
248
+ const latencyMs = Math.max(0, Math.round(ended - started));
249
+
250
+ await opts.ledger.logToolResult(opts.sessionId, {
251
+ stepIndex: opts.stepIndex,
252
+ toolName: opts.toolName,
253
+ toolOutput: output,
254
+ latencyMs: opts.captureLatencyMs === false ? 0 : latencyMs,
255
+ });
256
+
257
+ return output;
258
+ } catch (err) {
259
+ const ended = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
260
+ const latencyMs = Math.max(0, Math.round(ended - started));
261
+ const message = err instanceof Error ? err.message : String(err);
262
+ try {
263
+ await opts.ledger.logToolResult(opts.sessionId, {
264
+ stepIndex: opts.stepIndex,
265
+ toolName: opts.toolName,
266
+ toolOutput: { error: message },
267
+ latencyMs: opts.captureLatencyMs === false ? 0 : latencyMs,
268
+ });
269
+ } catch {
270
+ // ignore
271
+ }
272
+ throw err;
273
+ }
274
+ }