@apo-ai/sdk 0.2.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/LICENSE +21 -0
- package/README.md +314 -0
- package/dist/agent-task/integrations/ai-sdk.d.ts +121 -0
- package/dist/agent-task/integrations/ai-sdk.js +10 -0
- package/dist/agent-task/integrations/ai-sdk.js.map +1 -0
- package/dist/agent-task/integrations/anthropic.d.ts +67 -0
- package/dist/agent-task/integrations/anthropic.js +10 -0
- package/dist/agent-task/integrations/anthropic.js.map +1 -0
- package/dist/agent-task/integrations/openai.d.ts +77 -0
- package/dist/agent-task/integrations/openai.js +10 -0
- package/dist/agent-task/integrations/openai.js.map +1 -0
- package/dist/agent-task/public.d.ts +1326 -0
- package/dist/agent-task/public.js +3026 -0
- package/dist/agent-task/public.js.map +1 -0
- package/dist/chunk-DT2LMB7O.js +80 -0
- package/dist/chunk-DT2LMB7O.js.map +1 -0
- package/dist/chunk-GDB6SDGN.js +75 -0
- package/dist/chunk-GDB6SDGN.js.map +1 -0
- package/dist/chunk-HKLBPIB4.js +232 -0
- package/dist/chunk-HKLBPIB4.js.map +1 -0
- package/dist/chunk-HWZVL6TS.js +37 -0
- package/dist/chunk-HWZVL6TS.js.map +1 -0
- package/dist/chunk-PMYLL2H4.js +495 -0
- package/dist/chunk-PMYLL2H4.js.map +1 -0
- package/dist/chunk-SZDSIPXC.js +74 -0
- package/dist/chunk-SZDSIPXC.js.map +1 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/otel/index.d.ts +270 -0
- package/dist/otel/index.js +32 -0
- package/dist/otel/index.js.map +1 -0
- package/dist/tracing-DtbhYQV6.d.ts +26 -0
- package/dist/types-C-nrDEVf.d.ts +140 -0
- package/package.json +92 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sami Kuikka
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# apo SDK
|
|
2
|
+
|
|
3
|
+
TypeScript/JavaScript SDK for the [apo](.) backend — an agent testing and
|
|
4
|
+
observability platform. The SDK has three entry points:
|
|
5
|
+
|
|
6
|
+
- **`@apo-ai/sdk`** — config + error types shared across the surface.
|
|
7
|
+
- **`@apo-ai/sdk/otel`** — OpenTelemetry-native tracing. Wrap your LLM calls and
|
|
8
|
+
agent steps so they land in apo as structured spans, then attach scores.
|
|
9
|
+
- **`@apo-ai/sdk/agent-task`** — the agent-task evaluation framework. Define tasks,
|
|
10
|
+
adapters, and checks; run them against an agent and collect structured
|
|
11
|
+
results.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @apo-ai/sdk
|
|
17
|
+
# or: npm install @apo-ai/sdk / yarn add @apo-ai/sdk
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requires Node.js ≥ 20. The package ships compiled ESM + `.d.ts` (built via
|
|
21
|
+
`tsup`), so no TypeScript-transpiling runtime and no `allowImportingTsExtensions`
|
|
22
|
+
tsconfig flag are required on the consumer side.
|
|
23
|
+
|
|
24
|
+
For TypeScript consumers, also install `@types/node` (the SDK's emitted
|
|
25
|
+
declarations reference Node globals like `Buffer`):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add -D @types/node
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Releases
|
|
32
|
+
|
|
33
|
+
See [RELEASING.md](./RELEASING.md) for the maintainer runbook. The package
|
|
34
|
+
is published to npm via GitHub OIDC trusted publishing from `sdk-v*` tags —
|
|
35
|
+
no long-lived npm token is stored in the repository.
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
`readConfig()` reads backend/project/credentials from environment variables.
|
|
40
|
+
|
|
41
|
+
| Field | Env var (server) | Env var (Next.js, browser-safe) | Required |
|
|
42
|
+
|-------------|-----------------------------|--------------------------------------|----------|
|
|
43
|
+
| `endpoint` | `APO_BACKEND_URL` | `NEXT_PUBLIC_APO_BACKEND_URL` | yes |
|
|
44
|
+
| `project` | `APO_PROJECT` | `NEXT_PUBLIC_APO_PROJECT` | yes |
|
|
45
|
+
| `publicKey` | `APO_PUBLIC_KEY` | — *(server-side only — see below)* | two-key |
|
|
46
|
+
| `secretKey` | `APO_SECRET_KEY` | — | two-key |
|
|
47
|
+
| `apiKey` | `APO_API_KEY` | — | legacy |
|
|
48
|
+
|
|
49
|
+
The Next.js (`NEXT_PUBLIC_*`) variants are read first, then the plain server
|
|
50
|
+
variants. `apiKey` is the legacy single-key format (`sk-…`); prefer the
|
|
51
|
+
two-key model (`publicKey` + `secretKey`, generated as `pk-apo-…` / `sk-apo-…`
|
|
52
|
+
in the dashboard).
|
|
53
|
+
|
|
54
|
+
there is no browser-safe variant of `APO_PUBLIC_KEY`. The public
|
|
55
|
+
identifier alone does not authorize ingestion, and exposing it in a
|
|
56
|
+
browser bundle creates a misleading direct-browser integration.
|
|
57
|
+
`NEXT_PUBLIC_APO_PUBLIC_KEY` is intentionally not read. Telemetry
|
|
58
|
+
submission always requires both halves of an API-key pair sent as HTTP
|
|
59
|
+
Basic (`base64("pk-apo-…:sk-apo-…")`); the SDK never synthesizes partial
|
|
60
|
+
Basic credentials from only one half.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { readConfig, type EnvConfig } from "@apo-ai/sdk";
|
|
64
|
+
|
|
65
|
+
const config: EnvConfig = readConfig();
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## API reference — `@apo-ai/sdk`
|
|
69
|
+
|
|
70
|
+
The root entry is intentionally thin: shared types + config + errors. The
|
|
71
|
+
actual tracing surface lives at `@apo-ai/sdk/otel`.
|
|
72
|
+
|
|
73
|
+
### Types
|
|
74
|
+
|
|
75
|
+
The trace/observation parameter types used by the OTel helpers:
|
|
76
|
+
`CreateTraceParams`, `CreateSpanParams`, `EndSpanParams`, `TraceRunContext`,
|
|
77
|
+
`TraceRunOptions`, `TraceStepOptions`, `TraceEventOptions`, `IngestionEvent`,
|
|
78
|
+
`CreateScoreParams`, `ClientOptions`, `ParameterOverrides`, `ObserveOptions`,
|
|
79
|
+
`ObservationContext`.
|
|
80
|
+
|
|
81
|
+
### `readConfig(): EnvConfig`
|
|
82
|
+
|
|
83
|
+
Read endpoint/project/keys from environment variables (table above).
|
|
84
|
+
|
|
85
|
+
### Errors
|
|
86
|
+
|
|
87
|
+
`ClientError` (Effect `Data.TaggedError`), `ConfigurationError`,
|
|
88
|
+
`ClientErrorCode`. `ClientError` carries a `code` field for categorising
|
|
89
|
+
failures.
|
|
90
|
+
|
|
91
|
+
## Tracing — `@apo-ai/sdk/otel`
|
|
92
|
+
|
|
93
|
+
The canonical tracing path is OpenTelemetry via `@apo-ai/sdk/otel`. The old
|
|
94
|
+
`TraceTracker` / `createClient` custom protocol has been removed.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import {
|
|
98
|
+
configureApoTelemetry,
|
|
99
|
+
withApoTrace,
|
|
100
|
+
traceTool,
|
|
101
|
+
traceAgent,
|
|
102
|
+
traceRetriever,
|
|
103
|
+
score,
|
|
104
|
+
injectTraceparent,
|
|
105
|
+
extractTraceparent,
|
|
106
|
+
} from "@apo-ai/sdk/otel";
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `configureApoTelemetry(options)`
|
|
110
|
+
|
|
111
|
+
Sets up an OTel tracer provider with an OTLP exporter pointing at apo. Requires
|
|
112
|
+
`takeOwnership: true` (explicit permission to own the OTel lifecycle — don't
|
|
113
|
+
call this if your app already configures OTel itself).
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const handle = await configureApoTelemetry({
|
|
117
|
+
takeOwnership: true,
|
|
118
|
+
endpoint: process.env.APO_OTLP_ENDPOINT ?? "http://localhost:8000/v1/otlp",
|
|
119
|
+
// headers, resource attributes, batch/simple processor, etc.
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// …your traced work…
|
|
123
|
+
|
|
124
|
+
await handle.shutdown(); // flush + tear down
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `withApoTrace(name, fn, options?)`
|
|
128
|
+
|
|
129
|
+
Open a root span, run `fn` inside it, end the span, and return the result.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const summary = await withApoTrace("analyze-doc", async (span) => {
|
|
133
|
+
span.setAttribute("apo.flow.name", "analyze-doc");
|
|
134
|
+
return runAnalysis();
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Typed observation helpers
|
|
139
|
+
|
|
140
|
+
For non-LLM work (tools, retrievers, chains, agents), each helper emits a
|
|
141
|
+
correctly-typed GenAI span:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const docs = await traceRetriever("what is apo?", async () => [...]);
|
|
145
|
+
const out = await traceAgent("research-agent", async () => ({ ... }));
|
|
146
|
+
const tool = await traceTool("search", async () => ({ hits: 3 }));
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Available: `traceTool`, `traceAgent`, `traceRetriever`, `traceChain`.
|
|
150
|
+
|
|
151
|
+
### Scoring
|
|
152
|
+
|
|
153
|
+
Attach scores (numeric / categorical / boolean) to a run or observation:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
await score(
|
|
157
|
+
{ traceId: runId, name: "correctness", value: 0.92, source: "EVAL" },
|
|
158
|
+
{ endpoint: config.endpoint, headers: { Authorization: `Bearer ${token}` } },
|
|
159
|
+
);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Trace-context propagation
|
|
163
|
+
|
|
164
|
+
`injectTraceparent(carrier, fields)` / `extractTraceparent(carrier)` use the W3C
|
|
165
|
+
Trace Context propagator (correctly handles `traceparent`/`tracestate` parsing,
|
|
166
|
+
versioning, and edge cases) — for bridging spans across service boundaries.
|
|
167
|
+
|
|
168
|
+
### Other exports
|
|
169
|
+
|
|
170
|
+
`createApoTraceExporter`, `createApoSpanProcessor`, `buildApoAuthHeaders`,
|
|
171
|
+
`ApoSpanProcessor`, plus the option types (`ConfigureApoTelemetryOptions`,
|
|
172
|
+
`ApoTelemetryHandle`, `ApoTraceExporterOptions`, `ApoSpanProcessorOptions`,
|
|
173
|
+
`ApoTraceOptions`, `ScoreOptions`).
|
|
174
|
+
|
|
175
|
+
## Agent-task evaluation — `@apo-ai/sdk/agent-task`
|
|
176
|
+
|
|
177
|
+
The product's primary surface: define an agent task and its checks, wire an
|
|
178
|
+
adapter to the agent under test, then run it and collect structured results.
|
|
179
|
+
Import from the subpath:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import {
|
|
183
|
+
task,
|
|
184
|
+
defineAdapter,
|
|
185
|
+
runTask,
|
|
186
|
+
turn,
|
|
187
|
+
includes,
|
|
188
|
+
} from "@apo-ai/sdk/agent-task";
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Core building blocks
|
|
192
|
+
|
|
193
|
+
- **`task(id, { adapter, deliverables, … })`** — register the task definition
|
|
194
|
+
and return its adapter-typed `test`.
|
|
195
|
+
- **`defineAdapter({ initialize, startSession, collectDeliverables, cleanup })`**
|
|
196
|
+
— the contract between apo and the agent under test (init, run a session,
|
|
197
|
+
collect deliverables, tear down).
|
|
198
|
+
- **`turn(fn)`** — optionally define task-specific single- or multi-turn input.
|
|
199
|
+
- **`test(id, fn)`** — register a test through the scope returned by `task()`.
|
|
200
|
+
The callback receives `t` (flat, eve-style assertions over the run's flow)
|
|
201
|
+
and the task-selected deliverables, inferred from the adapter.
|
|
202
|
+
- **`runTask(task, adapter, options?)`** — execute a single task and return a
|
|
203
|
+
`TaskRunResult` (transcript + evaluation).
|
|
204
|
+
- **`loadTask(dir)` / `discoverAgentTaskDirs()`** — load tasks from disk.
|
|
205
|
+
- **`createAgentTaskTraceClient(config)`** — the adapter-side trace client
|
|
206
|
+
passed into runs; the auth token is supplied by the backend runner via
|
|
207
|
+
`APO_AUTH_TOKEN`.
|
|
208
|
+
|
|
209
|
+
### Testing (`*.eval.ts`)
|
|
210
|
+
|
|
211
|
+
One `<task-id>.eval.ts` file holds the task definition, optional turn behavior, and code or
|
|
212
|
+
LLM-backed checks. Checks read both the run's **flow** (what the agent did) and
|
|
213
|
+
its **deliverables** (what it produced).
|
|
214
|
+
Checks are written with a flat `t` context and value matchers — the single way
|
|
215
|
+
to assert. Every assertion is recorded (no die-on-first), so each check reports
|
|
216
|
+
all of its failures.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { task, includes, satisfies } from "@apo-ai/sdk/agent-task";
|
|
220
|
+
import { myAdapter } from "./adapter";
|
|
221
|
+
|
|
222
|
+
const { test } = task("review-output", {
|
|
223
|
+
adapter: myAdapter,
|
|
224
|
+
deliverables: ["result", "stats"],
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("used-the-right-tools", (t) => {
|
|
228
|
+
t.calledTool("read_file"); // did it call this tool
|
|
229
|
+
t.notCalledTool(/delete_/); // did it avoid this tool
|
|
230
|
+
t.toolOrder(["read_file", "search_content"]); // tools ran in this order
|
|
231
|
+
t.maxToolCalls(30); // didn't flail
|
|
232
|
+
t.noFailedActions(); // nothing errored
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("output-correct", (t, { deliverables }) => {
|
|
236
|
+
// result and stats are inferred from myAdapter.collectDeliverables().
|
|
237
|
+
t.check(deliverables.result, includes("finding"));
|
|
238
|
+
t.check(deliverables.stats, satisfies((s: { turn_count: number }) => s.turn_count > 0, "has turns"));
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The global `test<TDeliverables>(...)` export remains available for
|
|
243
|
+
framework-agnostic checks and existing task files. Prefer the `test` returned
|
|
244
|
+
by `task()` for new task files: it requires no manual generic or cast and
|
|
245
|
+
exposes only the deliverables selected by that task.
|
|
246
|
+
|
|
247
|
+
**`t` — flow assertions** (read the run's trace, automatically captured):
|
|
248
|
+
`calledTool(name, opts?)`, `notCalledTool(name, opts?)`, `toolOrder([...])`,
|
|
249
|
+
`usedNoTools()`, `maxToolCalls(n)`, `noFailedActions()`, `loadedSkill(skill)`,
|
|
250
|
+
`calledSubagent(agent)`, `messageIncludes(token)`, `maxTurns(n)`,
|
|
251
|
+
`maxDurationMs(n)`, `assert(label, predicate)`.
|
|
252
|
+
|
|
253
|
+
`calledTool` / `notCalledTool` take optional constraints to match a call's
|
|
254
|
+
**input**, **output**, and **status** (and `count` for an exact number). Each
|
|
255
|
+
constraint accepts a literal (partial-deep for objects), a RegExp, or a
|
|
256
|
+
predicate:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
t.calledTool("read_file", { input: { path: "source.py" } }); // exact arg
|
|
260
|
+
t.calledTool("read_file", { input: { path: /^src\// }, count: 2 }); // regex + count
|
|
261
|
+
t.calledTool("compute", { output: (v) => v === 42 }); // predicate on output
|
|
262
|
+
t.calledTool("flakey_tool", { status: "error" }); // a failed call
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`t.assert(label, predicate)` is the escape hatch — assert any predicate over the
|
|
266
|
+
typed flow view when the named methods don't cover your case:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
t.assert("read before write", (flow) =>
|
|
270
|
+
flow.toolNamesInOrder.indexOf("read_file") < flow.toolNamesInOrder.indexOf("write_file"),
|
|
271
|
+
);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**`t.check(value, matcher)` — value assertions** (deliverables, parsed JSON,
|
|
275
|
+
anything). Matchers: `includes(substring|RegExp)`, `equals(value)` (deep),
|
|
276
|
+
`matches(standardSchema)` (Zod/Valibot — anything with `safeParse`),
|
|
277
|
+
`satisfies(predicate, label)`, `similarity(expected, threshold = 0.8)` (fuzzy,
|
|
278
|
+
normalized Levenshtein).
|
|
279
|
+
|
|
280
|
+
For apo tasks the flow is built automatically from the run's trace (via
|
|
281
|
+
`createFlowTee`) — no extra wiring.
|
|
282
|
+
|
|
283
|
+
**`t.judge(value, instruction)` — LLM-backed assertions.** Configure the judge
|
|
284
|
+
with `runTask(dir, { judge: { model, apiKey?, baseURL? } })`, or set
|
|
285
|
+
`OPENROUTER_MODEL`/`OPENAI_MODEL` for the CLI runtime. Judge verdicts use the
|
|
286
|
+
same recorder and result format as code assertions, including model, prompt,
|
|
287
|
+
response, token usage, and latency metadata.
|
|
288
|
+
|
|
289
|
+
**Testing agents not built on apo's adapter.** Build a `Flow` from the
|
|
290
|
+
framework's own output with a normalizer, then run the same checks:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
import { runFlowChecks, test } from "@apo-ai/sdk/agent-task";
|
|
294
|
+
import { fromAISDK } from "@apo-ai/sdk/agent-task"; // or fromOpenAIMessages / fromAnthropicMessages
|
|
295
|
+
|
|
296
|
+
const flow = fromAISDK(myGenerateTextResult); // their framework → Flow
|
|
297
|
+
test("used-tools", (t) => { t.calledTool("read_file"); });
|
|
298
|
+
const results = await runFlowChecks({ flow, deliverables: { reply: myGenerateTextResult.text } });
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`fromOpenAIMessages(messages)`, `fromAnthropicMessages(messages)`, and
|
|
302
|
+
`fromAISDK(result)` each convert one source into the neutral `Flow` the checks
|
|
303
|
+
read — the plugs that make the testing framework reusable across agent stacks.
|
|
304
|
+
|
|
305
|
+
### CLI / runtime
|
|
306
|
+
|
|
307
|
+
`parseAgentTaskCliArgs`, `runAgentTaskCli`, `loadTaskRuntime`, `runTaskDir`,
|
|
308
|
+
`AgentTaskRuntime` — used by the `apo` CLI and the backend's agent-task runner.
|
|
309
|
+
|
|
310
|
+
See the `apo` CLI (`pnpm apo`) and the agent-task specs for end-to-end usage.
|
|
311
|
+
|
|
312
|
+
## License
|
|
313
|
+
|
|
314
|
+
MIT
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { A as AgentTaskTraceContext } from '../../tracing-DtbhYQV6.js';
|
|
2
|
+
import '../../types-C-nrDEVf.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vercel AI SDK tracing integration for apo.
|
|
6
|
+
*
|
|
7
|
+
* Pass the result of {@link createApoTracer} to `generateText`'s
|
|
8
|
+
* `experimental_telemetry` option. The AI SDK already emits OpenTelemetry
|
|
9
|
+
* spans natively (e.g. `ai.toolCall`, `ai.generateText`); this tracer
|
|
10
|
+
* intercepts them and translates them into apo trace spans. The FlowTee
|
|
11
|
+
* picks up TOOL/GENERATION observations and builds the Flow that
|
|
12
|
+
* `t.calledTool`, `t.noFailedActions`, and `t.messageIncludes` read.
|
|
13
|
+
*
|
|
14
|
+
* The adapter author writes zero manual span code.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { generateText } from "ai";
|
|
19
|
+
* import { createApoTracer } from "@apo-ai/sdk/agent-task";
|
|
20
|
+
*
|
|
21
|
+
* async sendUserTurn(turn, { trace, parentSpanId }) {
|
|
22
|
+
* const result = await generateText({
|
|
23
|
+
* model, system, messages, tools,
|
|
24
|
+
* experimental_telemetry: createApoTracer({ trace, parentSpanId }),
|
|
25
|
+
* });
|
|
26
|
+
* return { response: result.text };
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @module
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
type OTelAttributeValue = string | number | boolean | null | undefined | Array<string | number | boolean | null | undefined>;
|
|
34
|
+
type OTelAttributes = Record<string, OTelAttributeValue>;
|
|
35
|
+
interface OTelSpanContext {
|
|
36
|
+
traceId: string;
|
|
37
|
+
spanId: string;
|
|
38
|
+
traceFlags: number;
|
|
39
|
+
}
|
|
40
|
+
interface OTelSpanStatus {
|
|
41
|
+
code: number;
|
|
42
|
+
message?: string;
|
|
43
|
+
}
|
|
44
|
+
interface OTelException {
|
|
45
|
+
name: string;
|
|
46
|
+
message: string;
|
|
47
|
+
stack?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Minimal `Span` implementation. The AI SDK calls:
|
|
51
|
+
* - `setAttributes` / `setAttribute` (tool result, response text)
|
|
52
|
+
* - `setStatus` / `recordException` (errors)
|
|
53
|
+
* - `end()` (finalises the span)
|
|
54
|
+
*
|
|
55
|
+
* Other methods (`addEvent`, `addLink`, `updateName`) are no-ops but must
|
|
56
|
+
* exist on the object for structural type compatibility with the OTel `Span`
|
|
57
|
+
* interface the AI SDK expects.
|
|
58
|
+
*/
|
|
59
|
+
interface ApoSpan {
|
|
60
|
+
spanContext(): OTelSpanContext;
|
|
61
|
+
setAttribute(key: string, value: OTelAttributeValue): this;
|
|
62
|
+
setAttributes(attrs: OTelAttributes): this;
|
|
63
|
+
addEvent(name: string, attributesOrStartTime?: unknown, startTime?: unknown): this;
|
|
64
|
+
addLink(link: unknown): this;
|
|
65
|
+
addLinks(links: unknown[]): this;
|
|
66
|
+
setStatus(status: OTelSpanStatus): this;
|
|
67
|
+
updateName(name: string): this;
|
|
68
|
+
end(endTime?: unknown): void;
|
|
69
|
+
isRecording(): boolean;
|
|
70
|
+
recordException(exception: OTelException | string, time?: unknown): this;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Minimal `Tracer` implementation. The AI SDK's `recordSpan` uses
|
|
74
|
+
* `startActiveSpan(name, { attributes }, fn)`. We also implement
|
|
75
|
+
* `startSpan(name, opts)` for structural compatibility — it returns a span
|
|
76
|
+
* without invoking a callback, and the caller is responsible for `.end()`.
|
|
77
|
+
*/
|
|
78
|
+
interface ApoTracer {
|
|
79
|
+
startSpan(name: string, options?: {
|
|
80
|
+
attributes?: OTelAttributes;
|
|
81
|
+
}): ApoSpan;
|
|
82
|
+
startActiveSpan<T>(name: string, options: {
|
|
83
|
+
attributes?: OTelAttributes;
|
|
84
|
+
}, fn: (span: ApoSpan) => Promise<T> | T): Promise<T>;
|
|
85
|
+
startActiveSpan<T>(name: string, options: {
|
|
86
|
+
attributes?: OTelAttributes;
|
|
87
|
+
}, context: unknown, fn: (span: ApoSpan) => Promise<T> | T): Promise<T>;
|
|
88
|
+
startActiveSpan<T>(name: string, fn: (span: ApoSpan) => Promise<T> | T): Promise<T>;
|
|
89
|
+
}
|
|
90
|
+
interface CreateApoTracerOptions {
|
|
91
|
+
/** The tee'd trace context from `sendUserTurn`'s second argument. */
|
|
92
|
+
trace: AgentTaskTraceContext;
|
|
93
|
+
/** The parent span id for this turn (from `sendUserTurn`'s context). */
|
|
94
|
+
parentSpanId?: string;
|
|
95
|
+
/** Optional task id, surfaced in span metadata. */
|
|
96
|
+
taskId?: string;
|
|
97
|
+
/** Optional turn number, surfaced in span metadata. */
|
|
98
|
+
turnNumber?: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Build a `Tracer` compatible with the Vercel AI SDK's
|
|
102
|
+
* `experimental_telemetry.tracer` option. The returned object satisfies the
|
|
103
|
+
* shape `{ isEnabled: true, tracer }` so it can be passed directly to
|
|
104
|
+
* `experimental_telemetry`.
|
|
105
|
+
*
|
|
106
|
+
* The AI SDK emits spans named `ai.generateText`, `ai.generateText.doGenerate`,
|
|
107
|
+
* and `ai.toolCall`. We translate the load-bearing ones:
|
|
108
|
+
*
|
|
109
|
+
* - `ai.toolCall` → a TOOL span (so `t.calledTool(name)` works)
|
|
110
|
+
* - `ai.generateText` → a GENERATION span (so `t.messageIncludes(text)` works)
|
|
111
|
+
* and error status → `t.noFailedActions()` catches it
|
|
112
|
+
*
|
|
113
|
+
* Other spans (`ai.generateText.doGenerate`, etc.) are ignored — the FlowTee
|
|
114
|
+
* only cares about TOOL/AGENT/GENERATION observation types.
|
|
115
|
+
*/
|
|
116
|
+
declare function createApoTracer(options: CreateApoTracerOptions): {
|
|
117
|
+
isEnabled: true;
|
|
118
|
+
tracer: ApoTracer;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export { type CreateApoTracerOptions, createApoTracer };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Auto-generated by `pnpm --filter @apo-ai/sdk build` (tsup.config.ts).
|
|
2
|
+
// Do not edit by hand — edit the src/ originals and rebuild.
|
|
3
|
+
import {
|
|
4
|
+
createApoTracer
|
|
5
|
+
} from "../../chunk-HKLBPIB4.js";
|
|
6
|
+
import "../../chunk-HWZVL6TS.js";
|
|
7
|
+
export {
|
|
8
|
+
createApoTracer
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=ai-sdk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { A as AgentTaskTraceContext } from '../../tracing-DtbhYQV6.js';
|
|
2
|
+
import '../../types-C-nrDEVf.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Anthropic JS SDK tracing integration for apo.
|
|
6
|
+
*
|
|
7
|
+
* Wraps an `Anthropic` client so `messages.create()` calls are
|
|
8
|
+
* automatically traced. Tool calls and generations are captured without
|
|
9
|
+
* any manual span code in the adapter.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import Anthropic from "@anthropic-ai/sdk";
|
|
14
|
+
* import { createApoAnthropic } from "@apo-ai/sdk/agent-task";
|
|
15
|
+
*
|
|
16
|
+
* async sendUserTurn(turn, { trace, parentSpanId }) {
|
|
17
|
+
* const client = createApoAnthropic(
|
|
18
|
+
* new Anthropic({ apiKey }),
|
|
19
|
+
* { trace, parentSpanId, taskId: ctx.task.id, turnNumber }
|
|
20
|
+
* );
|
|
21
|
+
* const response = await client.messages.create({
|
|
22
|
+
* model: "claude-sonnet-4-20250514",
|
|
23
|
+
* max_tokens: 1024,
|
|
24
|
+
* messages: [{ role: "user", content: String(turn) }],
|
|
25
|
+
* tools: [...],
|
|
26
|
+
* });
|
|
27
|
+
* const text = response.content.filter(b => b.type === "text").map(b => b.text).join("");
|
|
28
|
+
* return { response: text };
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @module
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
interface CreateApoAnthropicOptions {
|
|
36
|
+
/** The tee'd trace context from `sendUserTurn`'s second argument. */
|
|
37
|
+
trace: AgentTaskTraceContext;
|
|
38
|
+
/** The parent span id for this turn (from `sendUserTurn`'s context). */
|
|
39
|
+
parentSpanId?: string;
|
|
40
|
+
/** Optional task id, surfaced in span metadata. */
|
|
41
|
+
taskId?: string;
|
|
42
|
+
/** Optional turn number, surfaced in span metadata. */
|
|
43
|
+
turnNumber?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Minimal structural shape of the Anthropic client we intercept.
|
|
47
|
+
*/
|
|
48
|
+
interface AnthropicClientLike {
|
|
49
|
+
messages: {
|
|
50
|
+
create: (...args: any[]) => Promise<any>;
|
|
51
|
+
};
|
|
52
|
+
[key: string]: any;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Wrap an Anthropic client so `messages.create()` is traced.
|
|
56
|
+
* Returns a Proxy-wrapped client — call it exactly as you would the
|
|
57
|
+
* original. Non-streaming only; streaming responses pass through
|
|
58
|
+
* untraced.
|
|
59
|
+
*
|
|
60
|
+
* For SDKs that emit OpenTelemetry natively (Claude Agent SDK, Vercel AI
|
|
61
|
+
* SDK), prefer `configureApoTelemetry()` instead — no wrapper needed.
|
|
62
|
+
* This manual wrapper is for the raw `@anthropic-ai/sdk` package or
|
|
63
|
+
* custom clients that don't emit OTel on their own.
|
|
64
|
+
*/
|
|
65
|
+
declare function createApoAnthropic<T extends AnthropicClientLike>(client: T, options: CreateApoAnthropicOptions): T;
|
|
66
|
+
|
|
67
|
+
export { type CreateApoAnthropicOptions, createApoAnthropic };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Auto-generated by `pnpm --filter @apo-ai/sdk build` (tsup.config.ts).
|
|
2
|
+
// Do not edit by hand — edit the src/ originals and rebuild.
|
|
3
|
+
import {
|
|
4
|
+
createApoAnthropic
|
|
5
|
+
} from "../../chunk-SZDSIPXC.js";
|
|
6
|
+
import "../../chunk-GDB6SDGN.js";
|
|
7
|
+
export {
|
|
8
|
+
createApoAnthropic
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { A as AgentTaskTraceContext } from '../../tracing-DtbhYQV6.js';
|
|
2
|
+
import '../../types-C-nrDEVf.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* OpenAI JS SDK tracing integration for apo.
|
|
6
|
+
*
|
|
7
|
+
* Wraps an `OpenAI` client so `chat.completions.create()` calls are
|
|
8
|
+
* automatically traced. Tool calls and generations are captured without
|
|
9
|
+
* any manual span code in the adapter.
|
|
10
|
+
*
|
|
11
|
+
* Uses a Proxy to intercept the `create` method, then emits GENERATION +
|
|
12
|
+
* TOOL spans through the trace context. The FlowTee picks these up and
|
|
13
|
+
* builds the Flow that `t.calledTool` reads.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import OpenAI from "openai";
|
|
18
|
+
* import { createApoOpenAI } from "@apo-ai/sdk/agent-task";
|
|
19
|
+
*
|
|
20
|
+
* async sendUserTurn(turn, { trace, parentSpanId }) {
|
|
21
|
+
* const client = createApoOpenAI(
|
|
22
|
+
* new OpenAI({ apiKey, baseURL: "https://openrouter.ai/api/v1" }),
|
|
23
|
+
* { trace, parentSpanId, taskId: ctx.task.id, turnNumber }
|
|
24
|
+
* );
|
|
25
|
+
* const response = await client.chat.completions.create({
|
|
26
|
+
* model: "google/gemini-2.5-flash-lite",
|
|
27
|
+
* messages: [{ role: "user", content: String(turn) }],
|
|
28
|
+
* tools: [...],
|
|
29
|
+
* });
|
|
30
|
+
* return { response: response.choices[0].message.content ?? "" };
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @module
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
interface CreateApoOpenAIOptions {
|
|
38
|
+
/** The tee'd trace context from `sendUserTurn`'s second argument. */
|
|
39
|
+
trace: AgentTaskTraceContext;
|
|
40
|
+
/** The parent span id for this turn (from `sendUserTurn`'s context). */
|
|
41
|
+
parentSpanId?: string;
|
|
42
|
+
/** Optional task id, surfaced in span metadata. */
|
|
43
|
+
taskId?: string;
|
|
44
|
+
/** Optional turn number, surfaced in span metadata. */
|
|
45
|
+
turnNumber?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Minimal structural shape of the OpenAI client we need to intercept.
|
|
49
|
+
* We avoid importing `openai` at the type level so this module doesn't
|
|
50
|
+
* take a runtime dependency — the wrapper works with any client that has
|
|
51
|
+
* `chat.completions.create`.
|
|
52
|
+
*
|
|
53
|
+
* The `create` signature uses a broad type so the OpenAI SDK's overloaded
|
|
54
|
+
* `create` method (which has specific param types) is assignable to this.
|
|
55
|
+
*/
|
|
56
|
+
interface OpenAIClientLike {
|
|
57
|
+
chat: {
|
|
58
|
+
completions: {
|
|
59
|
+
create: (...args: any[]) => Promise<any>;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
[key: string]: any;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Wrap an OpenAI client so `chat.completions.create()` is traced.
|
|
66
|
+
* Returns a Proxy-wrapped client — call it exactly as you would the
|
|
67
|
+
* original. Non-streaming only; streaming responses pass through
|
|
68
|
+
* untraced.
|
|
69
|
+
*
|
|
70
|
+
* For SDKs that emit OpenTelemetry natively (OpenAI Agents SDK, Vercel AI
|
|
71
|
+
* SDK, Claude Agent SDK), prefer `configureApoTelemetry()` instead — no
|
|
72
|
+
* wrapper needed. This manual wrapper is for the raw `openai` package or
|
|
73
|
+
* custom clients that don't emit OTel on their own.
|
|
74
|
+
*/
|
|
75
|
+
declare function createApoOpenAI<T extends OpenAIClientLike>(client: T, options: CreateApoOpenAIOptions): T;
|
|
76
|
+
|
|
77
|
+
export { type CreateApoOpenAIOptions, createApoOpenAI };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Auto-generated by `pnpm --filter @apo-ai/sdk build` (tsup.config.ts).
|
|
2
|
+
// Do not edit by hand — edit the src/ originals and rebuild.
|
|
3
|
+
import {
|
|
4
|
+
createApoOpenAI
|
|
5
|
+
} from "../../chunk-DT2LMB7O.js";
|
|
6
|
+
import "../../chunk-GDB6SDGN.js";
|
|
7
|
+
export {
|
|
8
|
+
createApoOpenAI
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|