@intentface/latch-otel 0.22.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 +67 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/langfuse.d.ts +38 -0
- package/dist/langfuse.d.ts.map +1 -0
- package/dist/langfuse.js +86 -0
- package/dist/langfuse.js.map +1 -0
- package/dist/telemetry.d.ts +95 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +214 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/trace-id.d.ts +11 -0
- package/dist/trace-id.d.ts.map +1 -0
- package/dist/trace-id.js +16 -0
- package/dist/trace-id.js.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Intentface
|
|
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 THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @intentface/latch-otel
|
|
2
|
+
|
|
3
|
+
Tracing for Latch over OpenTelemetry. Implements `@intentface/latch-core`'s
|
|
4
|
+
`telemetry` hook: each run is one trace, the AI SDK's model and tool spans nest
|
|
5
|
+
under it, and everything goes over OTLP to the backend you choose. A preset for
|
|
6
|
+
Langfuse is included.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm add @intentface/latch-otel @ai-sdk/otel # @ai-sdk/otel at the version matching your `ai`
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Langfuse
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { createRuntime } from "@intentface/latch-core";
|
|
16
|
+
import { otelTelemetry } from "@intentface/latch-otel";
|
|
17
|
+
import { isLangfuseConfigured, langfuse } from "@intentface/latch-otel/langfuse";
|
|
18
|
+
|
|
19
|
+
const runtime = createRuntime({
|
|
20
|
+
// …storage, agents…
|
|
21
|
+
telemetry: isLangfuseConfigured()
|
|
22
|
+
? otelTelemetry({ user: (p) => ownerKey(p), export: langfuse(), environment: "production" })
|
|
23
|
+
: undefined,
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`langfuse()` reads `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY` and
|
|
28
|
+
`LANGFUSE_BASE_URL` (default `https://cloud.langfuse.com`), and sends to the
|
|
29
|
+
project's OTLP endpoint. Each run shows as one trace named after the agent, in
|
|
30
|
+
the conversation's session, with the user, tags (`owner:`, `agent:`, `run:`,
|
|
31
|
+
`agent-version:`, `parent-run:`, `agent-owner:`) and run metadata. Under it:
|
|
32
|
+
the AI SDK's agent span, each step, each model call as a generation with its
|
|
33
|
+
messages and token usage, and each tool call with its arguments and result.
|
|
34
|
+
|
|
35
|
+
## Any other backend
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
telemetry: otelTelemetry({ user: (p) => ownerKey(p) }),
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
With no `export`, spans go over OTLP/HTTP to `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`
|
|
42
|
+
or `OTEL_EXPORTER_OTLP_ENDPOINT`, with `OTEL_EXPORTER_OTLP_HEADERS`: Grafana,
|
|
43
|
+
Honeycomb, Datadog, Jaeger, a collector. Or pass any `SpanExporter`, or a
|
|
44
|
+
destination of your own (`{ exporter, runAttributes, finishedAttributes }`, the
|
|
45
|
+
shape `langfuse()` returns) to add the attributes your backend wants.
|
|
46
|
+
|
|
47
|
+
## What's on the spans
|
|
48
|
+
|
|
49
|
+
- The run's span, `latch.run <agent>`: the whole turn, saving it included.
|
|
50
|
+
Its trace id is `traceIdForRun(runId)`, so anything that knows a run can link
|
|
51
|
+
to its trace. When the run ends it gets `input.value`, `output.value`,
|
|
52
|
+
`latch.run.status`, token usage and `latch.cost`, and an error status if the
|
|
53
|
+
run failed.
|
|
54
|
+
- On every span of the run: `session.id` (the conversation; a subagent shares
|
|
55
|
+
its parent's), `user.id`, `gen_ai.agent.name`, `latch.run.id`,
|
|
56
|
+
`latch.chat.id`, `latch.agent.version`, `latch.parent_run.id`, `latch.depth`,
|
|
57
|
+
`latch.trigger`, `deployment.environment.name`.
|
|
58
|
+
- The AI SDK's spans, in the GenAI semantic conventions, from `@ai-sdk/otel`
|
|
59
|
+
(configure it with `aiSdk`).
|
|
60
|
+
|
|
61
|
+
It runs its own tracer provider and registers an async context manager only if
|
|
62
|
+
the app has none, so it doesn't take over tracing your app already does. On
|
|
63
|
+
serverless, pass `waitUntil` so the flush outlives the response.
|
|
64
|
+
|
|
65
|
+
Replaces `@intentface/latch-langfuse` (0.21 and earlier), whose trace ids it
|
|
66
|
+
keeps. Reading traces back and writing scores were left out: they belong to
|
|
67
|
+
your app or an evaluation tool.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,OAAO,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,GAId,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
2
|
+
import type { OtelDestination } from "./telemetry.js";
|
|
3
|
+
export interface LangfuseOptions {
|
|
4
|
+
/** Default `LANGFUSE_PUBLIC_KEY`. */
|
|
5
|
+
publicKey?: string;
|
|
6
|
+
/** Default `LANGFUSE_SECRET_KEY`. */
|
|
7
|
+
secretKey?: string;
|
|
8
|
+
/** Default `LANGFUSE_BASE_URL`, else Langfuse Cloud EU (`https://cloud.langfuse.com`). */
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
/** Langfuse's environment for these traces. Default: `otelTelemetry`'s `environment`. */
|
|
11
|
+
environment?: string;
|
|
12
|
+
/** Send through this exporter instead of the OTLP one `langfuseOtlp` describes (a proxy, a test). */
|
|
13
|
+
exporter?: SpanExporter;
|
|
14
|
+
}
|
|
15
|
+
/** Whether the Langfuse keys are in the environment: `langfuse()` with no arguments would work. */
|
|
16
|
+
export declare function isLangfuseConfigured(): boolean;
|
|
17
|
+
/** Langfuse's OTLP endpoint and headers, for an exporter of your own. */
|
|
18
|
+
export declare function langfuseOtlp(opts?: Pick<LangfuseOptions, "publicKey" | "secretKey" | "baseUrl">): {
|
|
19
|
+
url: string;
|
|
20
|
+
headers: Record<string, string>;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Send traces to Langfuse, over its OTLP endpoint. Each run is one trace named
|
|
24
|
+
* after the agent, in the conversation's session, with the user, tags
|
|
25
|
+
* (`owner:`, `agent:`, `run:`, `agent-version:`, `parent-run:`,
|
|
26
|
+
* `agent-owner:`) and run metadata Langfuse filters on. The run's own span is
|
|
27
|
+
* an agent observation, marked as an error when the run errored.
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* otelTelemetry({ user: (p) => ownerKey(p), export: langfuse() })
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Reads `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY` and `LANGFUSE_BASE_URL`;
|
|
34
|
+
* throws without the keys (check `isLangfuseConfigured()` first if they're
|
|
35
|
+
* optional).
|
|
36
|
+
*/
|
|
37
|
+
export declare function langfuse(opts?: LangfuseOptions): OtelDestination;
|
|
38
|
+
//# sourceMappingURL=langfuse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langfuse.d.ts","sourceRoot":"","sources":["../src/langfuse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAW,MAAM,gBAAgB,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qGAAqG;IACrG,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AAID,mGAAmG;AACnG,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,IAAI,GAAE,IAAI,CAAC,eAAe,EAAE,WAAW,GAAG,WAAW,GAAG,SAAS,CAAM,GAAG;IACrG,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,CAeA;AAYD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,IAAI,GAAE,eAAoB,GAAG,eAAe,CAgCpE"}
|
package/dist/langfuse.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
2
|
+
const env = (name) => (typeof process === "undefined" ? undefined : process.env[name]) || undefined;
|
|
3
|
+
/** Whether the Langfuse keys are in the environment: `langfuse()` with no arguments would work. */
|
|
4
|
+
export function isLangfuseConfigured() {
|
|
5
|
+
return Boolean(env("LANGFUSE_PUBLIC_KEY") && env("LANGFUSE_SECRET_KEY"));
|
|
6
|
+
}
|
|
7
|
+
/** Langfuse's OTLP endpoint and headers, for an exporter of your own. */
|
|
8
|
+
export function langfuseOtlp(opts = {}) {
|
|
9
|
+
const publicKey = opts.publicKey ?? env("LANGFUSE_PUBLIC_KEY");
|
|
10
|
+
const secretKey = opts.secretKey ?? env("LANGFUSE_SECRET_KEY");
|
|
11
|
+
if (!publicKey || !secretKey) {
|
|
12
|
+
throw new Error("langfuse(): set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY, or pass publicKey and secretKey");
|
|
13
|
+
}
|
|
14
|
+
const baseUrl = (opts.baseUrl ?? env("LANGFUSE_BASE_URL") ?? "https://cloud.langfuse.com").replace(/\/+$/, "");
|
|
15
|
+
return {
|
|
16
|
+
url: `${baseUrl}/api/public/otel/v1/traces`,
|
|
17
|
+
headers: {
|
|
18
|
+
Authorization: `Basic ${btoa(`${publicKey}:${secretKey}`)}`,
|
|
19
|
+
// Ingest as spans arrive rather than in Langfuse's batch path.
|
|
20
|
+
"x-langfuse-ingestion-version": "4",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** The tags a run's trace gets: what Langfuse's trace list filters on. */
|
|
25
|
+
function tagsOf(run) {
|
|
26
|
+
const tags = [`agent:${run.agent}`, `run:${run.runId}`];
|
|
27
|
+
if (run.user)
|
|
28
|
+
tags.unshift(`owner:${run.user}`);
|
|
29
|
+
if (run.parentRunId)
|
|
30
|
+
tags.push(`parent-run:${run.parentRunId}`, "subagent");
|
|
31
|
+
if (run.agentVersion != null)
|
|
32
|
+
tags.push(`agent-version:${run.agentVersion}`);
|
|
33
|
+
if (run.agentOwner)
|
|
34
|
+
tags.push(`agent-owner:${run.agentOwner}`);
|
|
35
|
+
return tags;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Send traces to Langfuse, over its OTLP endpoint. Each run is one trace named
|
|
39
|
+
* after the agent, in the conversation's session, with the user, tags
|
|
40
|
+
* (`owner:`, `agent:`, `run:`, `agent-version:`, `parent-run:`,
|
|
41
|
+
* `agent-owner:`) and run metadata Langfuse filters on. The run's own span is
|
|
42
|
+
* an agent observation, marked as an error when the run errored.
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* otelTelemetry({ user: (p) => ownerKey(p), export: langfuse() })
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* Reads `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY` and `LANGFUSE_BASE_URL`;
|
|
49
|
+
* throws without the keys (check `isLangfuseConfigured()` first if they're
|
|
50
|
+
* optional).
|
|
51
|
+
*/
|
|
52
|
+
export function langfuse(opts = {}) {
|
|
53
|
+
const exporter = opts.exporter ?? new OTLPTraceExporter(langfuseOtlp(opts));
|
|
54
|
+
return {
|
|
55
|
+
exporter,
|
|
56
|
+
runAttributes(run) {
|
|
57
|
+
const environment = opts.environment ?? run.environment;
|
|
58
|
+
return {
|
|
59
|
+
everySpan: {
|
|
60
|
+
"langfuse.trace.name": run.agent,
|
|
61
|
+
"langfuse.session.id": run.sessionId,
|
|
62
|
+
...(run.user ? { "langfuse.user.id": run.user } : {}),
|
|
63
|
+
"langfuse.trace.tags": tagsOf(run),
|
|
64
|
+
"langfuse.trace.metadata.runId": run.runId,
|
|
65
|
+
"langfuse.trace.metadata.chatId": run.chatId,
|
|
66
|
+
"langfuse.trace.metadata.agent": run.agent,
|
|
67
|
+
"langfuse.trace.metadata.model": run.modelId,
|
|
68
|
+
"langfuse.trace.metadata.depth": String(run.depth),
|
|
69
|
+
...(run.agentVersion != null ? { "langfuse.trace.metadata.agentVersion": String(run.agentVersion) } : {}),
|
|
70
|
+
...(run.parentRunId ? { "langfuse.trace.metadata.parentRunId": run.parentRunId } : {}),
|
|
71
|
+
...(environment ? { "langfuse.environment": environment } : {}),
|
|
72
|
+
},
|
|
73
|
+
root: { "langfuse.observation.type": "agent" },
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
finishedAttributes(_run, outcome) {
|
|
77
|
+
if (outcome.status !== "errored")
|
|
78
|
+
return undefined;
|
|
79
|
+
return {
|
|
80
|
+
"langfuse.observation.level": "ERROR",
|
|
81
|
+
...(outcome.error ? { "langfuse.observation.status_message": outcome.error } : {}),
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=langfuse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langfuse.js","sourceRoot":"","sources":["../src/langfuse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAiB5E,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,SAAS,CAAC;AAE5G,mGAAmG;AACnG,MAAM,UAAU,oBAAoB;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,IAAI,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAC,OAAqE,EAAE;IAIlG,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC/D,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,8FAA8F,CAAC,CAAC;IAClH,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC,mBAAmB,CAAC,IAAI,4BAA4B,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC/G,OAAO;QACL,GAAG,EAAE,GAAG,OAAO,4BAA4B;QAC3C,OAAO,EAAE;YACP,aAAa,EAAE,SAAS,IAAI,CAAC,GAAG,SAAS,IAAI,SAAS,EAAE,CAAC,EAAE;YAC3D,+DAA+D;YAC/D,8BAA8B,EAAE,GAAG;SACpC;KACF,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,SAAS,MAAM,CAAC,GAAY;IAC1B,MAAM,IAAI,GAAG,CAAC,SAAS,GAAG,CAAC,KAAK,EAAE,EAAE,OAAO,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IACxD,IAAI,GAAG,CAAC,IAAI;QAAE,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,IAAI,GAAG,CAAC,WAAW;QAAE,IAAI,CAAC,IAAI,CAAC,cAAc,GAAG,CAAC,WAAW,EAAE,EAAE,UAAU,CAAC,CAAC;IAC5E,IAAI,GAAG,CAAC,YAAY,IAAI,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,iBAAiB,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;IAC7E,IAAI,GAAG,CAAC,UAAU;QAAE,IAAI,CAAC,IAAI,CAAC,eAAe,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IAC/D,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAwB,EAAE;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,iBAAiB,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5E,OAAO;QACL,QAAQ;QACR,aAAa,CAAC,GAAG;YACf,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC;YACxD,OAAO;gBACL,SAAS,EAAE;oBACT,qBAAqB,EAAE,GAAG,CAAC,KAAK;oBAChC,qBAAqB,EAAE,GAAG,CAAC,SAAS;oBACpC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACrD,qBAAqB,EAAE,MAAM,CAAC,GAAG,CAAC;oBAClC,+BAA+B,EAAE,GAAG,CAAC,KAAK;oBAC1C,gCAAgC,EAAE,GAAG,CAAC,MAAM;oBAC5C,+BAA+B,EAAE,GAAG,CAAC,KAAK;oBAC1C,+BAA+B,EAAE,GAAG,CAAC,OAAO;oBAC5C,+BAA+B,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;oBAClD,GAAG,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,sCAAsC,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACzG,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,qCAAqC,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACtF,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAChE;gBACD,IAAI,EAAE,EAAE,2BAA2B,EAAE,OAAO,EAAE;aAC/C,CAAC;QACJ,CAAC;QACD,kBAAkB,CAAC,IAAI,EAAE,OAAO;YAC9B,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YACnD,OAAO;gBACL,4BAA4B,EAAE,OAAO;gBACrC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,qCAAqC,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { LatchTelemetry, TelemetryRunMeta, TelemetryRunOutcome, TurnTrigger } from "@intentface/latch-core";
|
|
2
|
+
import { type OpenTelemetryOptions } from "@ai-sdk/otel";
|
|
3
|
+
import { type Attributes } from "@opentelemetry/api";
|
|
4
|
+
import { type SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
5
|
+
/** What a run's spans are labelled with: what latch knows about the run when it starts. */
|
|
6
|
+
export interface OtelRun {
|
|
7
|
+
runId: string;
|
|
8
|
+
chatId: string;
|
|
9
|
+
/** The top-level conversation: a subagent's run shares it with the run that spawned it. */
|
|
10
|
+
sessionId: string;
|
|
11
|
+
agent: string;
|
|
12
|
+
modelId: string;
|
|
13
|
+
/** 0 for a top-level turn, more for a subagent. */
|
|
14
|
+
depth: number;
|
|
15
|
+
trigger: TurnTrigger;
|
|
16
|
+
/** The stored agent version behind the run, for agents that come from a store. */
|
|
17
|
+
agentVersion?: number;
|
|
18
|
+
/** The run that spawned this one, for a subagent. */
|
|
19
|
+
parentRunId?: string;
|
|
20
|
+
/** From `user`. */
|
|
21
|
+
user?: string;
|
|
22
|
+
/** From `agentOwner`. */
|
|
23
|
+
agentOwner?: string;
|
|
24
|
+
/** From `environment`. */
|
|
25
|
+
environment?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Where spans go, and anything that backend wants on them beyond the standard
|
|
29
|
+
* attributes. `langfuse()` from `@intentface/latch-otel/langfuse` is one.
|
|
30
|
+
*/
|
|
31
|
+
export interface OtelDestination {
|
|
32
|
+
exporter: SpanExporter;
|
|
33
|
+
/** Attributes for every span of a run (`everySpan`), and for the run's own span (`root`). */
|
|
34
|
+
runAttributes?(run: OtelRun): {
|
|
35
|
+
everySpan?: Attributes;
|
|
36
|
+
root?: Attributes;
|
|
37
|
+
};
|
|
38
|
+
/** Attributes for the run's own span once the run has finished. */
|
|
39
|
+
finishedAttributes?(run: OtelRun, outcome: TelemetryRunOutcome): Attributes | undefined;
|
|
40
|
+
}
|
|
41
|
+
export interface OtelTelemetryOptions<P> {
|
|
42
|
+
/**
|
|
43
|
+
* Where spans go: an exporter, or a destination such as `langfuse()`.
|
|
44
|
+
* Default: OTLP over HTTP to `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` (or
|
|
45
|
+
* `OTEL_EXPORTER_OTLP_ENDPOINT`), with `OTEL_EXPORTER_OTLP_HEADERS`.
|
|
46
|
+
*/
|
|
47
|
+
export?: SpanExporter | OtelDestination;
|
|
48
|
+
/**
|
|
49
|
+
* Who a run is for, as one stable string (usually the owner key). Becomes
|
|
50
|
+
* `user.id`. Leave it out to record no user.
|
|
51
|
+
*/
|
|
52
|
+
user?: (principal: P) => string;
|
|
53
|
+
/**
|
|
54
|
+
* Who owns the agent being run, when that's someone other than the user (an
|
|
55
|
+
* org's shared agent). Becomes `latch.agent_owner`. Runs before every turn,
|
|
56
|
+
* so keep it cheap; a throw only loses the attribute.
|
|
57
|
+
*/
|
|
58
|
+
agentOwner?: (run: TelemetryRunMeta<P>) => Promise<string | undefined>;
|
|
59
|
+
/** `deployment.environment.name`, e.g. "production". */
|
|
60
|
+
environment?: string;
|
|
61
|
+
/** More attributes for every span of a run. */
|
|
62
|
+
attributes?: (run: TelemetryRunMeta<P>) => Attributes | undefined;
|
|
63
|
+
/** Options for the AI SDK's spans (`@ai-sdk/otel`), e.g. `{ usage: true }`. */
|
|
64
|
+
aiSdk?: Omit<OpenTelemetryOptions, "tracer">;
|
|
65
|
+
/**
|
|
66
|
+
* Keeps the flush alive after the response on serverless (Vercel's
|
|
67
|
+
* `waitUntil`). Without it, flush is only awaited.
|
|
68
|
+
*/
|
|
69
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
70
|
+
/** Default true. False turns every hook into a no-op. */
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Latch's telemetry hook over OpenTelemetry. Each run is one trace, its id
|
|
75
|
+
* derived from the run id (`traceIdForRun`), rooted in a `latch.run <agent>`
|
|
76
|
+
* span: the whole turn, saving it included. The AI SDK's own spans (its
|
|
77
|
+
* `invoke_agent`, each step, each model call and tool call, in the GenAI
|
|
78
|
+
* semantic conventions, from `@ai-sdk/otel`) nest under it. Every span carries the session, the user and
|
|
79
|
+
* the run, so a backend can filter on any of them.
|
|
80
|
+
*
|
|
81
|
+
* ```ts
|
|
82
|
+
* import { otelTelemetry } from "@intentface/latch-otel";
|
|
83
|
+
* import { langfuse } from "@intentface/latch-otel/langfuse";
|
|
84
|
+
*
|
|
85
|
+
* createRuntime({
|
|
86
|
+
* ...,
|
|
87
|
+
* telemetry: otelTelemetry({ user: (p) => ownerKey(p), export: langfuse() }),
|
|
88
|
+
* });
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* It runs its own tracer provider, so it neither needs nor replaces one your
|
|
92
|
+
* app registered. Add `@ai-sdk/otel` at the version matching your `ai`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function otelTelemetry<P>(opts?: OtelTelemetryOptions<P>): LatchTelemetry<P>;
|
|
95
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACjH,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAkC,KAAK,UAAU,EAA2B,MAAM,oBAAoB,CAAC;AAG9G,OAAO,EAKL,KAAK,YAAY,EAElB,MAAM,+BAA+B,CAAC;AAGvC,2FAA2F;AAC3F,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,2FAA2F;IAC3F,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;IACrB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,CAAC;IACvB,6FAA6F;IAC7F,aAAa,CAAC,CAAC,GAAG,EAAE,OAAO,GAAG;QAAE,SAAS,CAAC,EAAE,UAAU,CAAC;QAAC,IAAI,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IAC5E,mEAAmE;IACnE,kBAAkB,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,GAAG,UAAU,GAAG,SAAS,CAAC;CACzF;AAED,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,GAAG,eAAe,CAAC;IACxC;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,KAAK,MAAM,CAAC;IAChC;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvE,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,UAAU,GAAG,SAAS,CAAC;IAClE,+EAA+E;IAC/E,KAAK,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;IAC7C;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAChD,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA+DD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,IAAI,GAAE,oBAAoB,CAAC,CAAC,CAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAgItF"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { OpenTelemetry } from "@ai-sdk/otel";
|
|
2
|
+
import { context, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
3
|
+
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
|
|
4
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
5
|
+
import { BasicTracerProvider, BatchSpanProcessor, RandomIdGenerator, } from "@opentelemetry/sdk-trace-base";
|
|
6
|
+
import { traceIdForRun } from "./trace-id.js";
|
|
7
|
+
/** Run attributes carried in the context, copied onto every span the run starts. */
|
|
8
|
+
const RUN_ATTRIBUTES = Symbol.for("latch-otel.run-attributes");
|
|
9
|
+
let contextManagerSet = false;
|
|
10
|
+
/**
|
|
11
|
+
* Spans only nest across `await`s with an async context manager. Register one
|
|
12
|
+
* unless the app already has: `setGlobalContextManager` refuses a second.
|
|
13
|
+
*/
|
|
14
|
+
function ensureContextManager() {
|
|
15
|
+
if (contextManagerSet)
|
|
16
|
+
return;
|
|
17
|
+
contextManagerSet = true;
|
|
18
|
+
context.setGlobalContextManager(new AsyncLocalStorageContextManager().enable());
|
|
19
|
+
}
|
|
20
|
+
/** Text of the last assistant message: the run's output. */
|
|
21
|
+
function lastAssistantText(messages) {
|
|
22
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
23
|
+
const m = messages[i];
|
|
24
|
+
if (m?.role !== "assistant")
|
|
25
|
+
continue;
|
|
26
|
+
const text = (m.parts ?? [])
|
|
27
|
+
.filter((p) => p.type === "text" && typeof p.text === "string")
|
|
28
|
+
.map((p) => p.text)
|
|
29
|
+
.join(" ")
|
|
30
|
+
.trim();
|
|
31
|
+
return text || undefined;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
/** A host callback's result, or undefined (and a warning) when it throws: telemetry never fails a turn. */
|
|
36
|
+
function guarded(label, runId, f) {
|
|
37
|
+
try {
|
|
38
|
+
return f();
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
console.warn(`latch-otel: ${label} failed (run ${runId}):`, e);
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const defined = (a) => Object.fromEntries(Object.entries(a).filter(([, v]) => v !== undefined));
|
|
46
|
+
/** The standard attributes, on every span of a run. */
|
|
47
|
+
function standardAttributes(run) {
|
|
48
|
+
return defined({
|
|
49
|
+
"session.id": run.sessionId,
|
|
50
|
+
"user.id": run.user,
|
|
51
|
+
"gen_ai.conversation.id": run.sessionId,
|
|
52
|
+
"gen_ai.agent.name": run.agent,
|
|
53
|
+
"deployment.environment.name": run.environment,
|
|
54
|
+
"latch.run.id": run.runId,
|
|
55
|
+
"latch.chat.id": run.chatId,
|
|
56
|
+
"latch.agent": run.agent,
|
|
57
|
+
"latch.agent.version": run.agentVersion,
|
|
58
|
+
"latch.agent_owner": run.agentOwner,
|
|
59
|
+
"latch.parent_run.id": run.parentRunId,
|
|
60
|
+
"latch.depth": run.depth,
|
|
61
|
+
"latch.trigger": run.trigger,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Latch's telemetry hook over OpenTelemetry. Each run is one trace, its id
|
|
66
|
+
* derived from the run id (`traceIdForRun`), rooted in a `latch.run <agent>`
|
|
67
|
+
* span: the whole turn, saving it included. The AI SDK's own spans (its
|
|
68
|
+
* `invoke_agent`, each step, each model call and tool call, in the GenAI
|
|
69
|
+
* semantic conventions, from `@ai-sdk/otel`) nest under it. Every span carries the session, the user and
|
|
70
|
+
* the run, so a backend can filter on any of them.
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { otelTelemetry } from "@intentface/latch-otel";
|
|
74
|
+
* import { langfuse } from "@intentface/latch-otel/langfuse";
|
|
75
|
+
*
|
|
76
|
+
* createRuntime({
|
|
77
|
+
* ...,
|
|
78
|
+
* telemetry: otelTelemetry({ user: (p) => ownerKey(p), export: langfuse() }),
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* It runs its own tracer provider, so it neither needs nor replaces one your
|
|
83
|
+
* app registered. Add `@ai-sdk/otel` at the version matching your `ai`.
|
|
84
|
+
*/
|
|
85
|
+
export function otelTelemetry(opts = {}) {
|
|
86
|
+
if (opts.enabled === false)
|
|
87
|
+
return {};
|
|
88
|
+
const destination = opts.export && "exporter" in opts.export ? opts.export : { exporter: opts.export ?? new OTLPTraceExporter() };
|
|
89
|
+
// The next root span's trace id. Set right before `startSpan` and consumed by
|
|
90
|
+
// it, synchronously, so a root span gets the run's id and nothing else does.
|
|
91
|
+
let nextTraceId;
|
|
92
|
+
const random = new RandomIdGenerator();
|
|
93
|
+
const idGenerator = {
|
|
94
|
+
generateTraceId: () => {
|
|
95
|
+
const id = nextTraceId ?? random.generateTraceId();
|
|
96
|
+
nextTraceId = undefined;
|
|
97
|
+
return id;
|
|
98
|
+
},
|
|
99
|
+
generateSpanId: () => random.generateSpanId(),
|
|
100
|
+
};
|
|
101
|
+
const propagate = {
|
|
102
|
+
onStart(span, parent) {
|
|
103
|
+
const attributes = parent.getValue(RUN_ATTRIBUTES);
|
|
104
|
+
if (attributes)
|
|
105
|
+
span.setAttributes(attributes);
|
|
106
|
+
},
|
|
107
|
+
onEnd() { },
|
|
108
|
+
forceFlush: async () => { },
|
|
109
|
+
shutdown: async () => { },
|
|
110
|
+
};
|
|
111
|
+
const provider = new BasicTracerProvider({
|
|
112
|
+
idGenerator,
|
|
113
|
+
spanProcessors: [propagate, new BatchSpanProcessor(destination.exporter)],
|
|
114
|
+
});
|
|
115
|
+
const tracer = provider.getTracer("@intentface/latch-otel");
|
|
116
|
+
const aiSdk = new OpenTelemetry({ ...opts.aiSdk, tracer });
|
|
117
|
+
// The run's span stays open until the run finishes, for a real duration and its output.
|
|
118
|
+
const open = new Map();
|
|
119
|
+
return {
|
|
120
|
+
telemetryFor(run) {
|
|
121
|
+
return { isEnabled: true, functionId: `agent:${run.agent}`, integrations: [aiSdk] };
|
|
122
|
+
},
|
|
123
|
+
async withRunSpan(meta, fn) {
|
|
124
|
+
// Core awaits this unguarded (it has to run `fn`), so nothing before
|
|
125
|
+
// `fn` may throw: a failing callback loses its attributes, anything else
|
|
126
|
+
// loses the span, and the turn runs either way.
|
|
127
|
+
let ctx;
|
|
128
|
+
try {
|
|
129
|
+
ctx = await startRun(meta);
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
console.warn(`latch-otel: couldn't start the run's span (run ${meta.runId}):`, e);
|
|
133
|
+
}
|
|
134
|
+
return ctx ? context.with(ctx, fn) : fn();
|
|
135
|
+
},
|
|
136
|
+
async onRunFinished(meta, outcome) {
|
|
137
|
+
const entry = open.get(meta.runId);
|
|
138
|
+
if (!entry)
|
|
139
|
+
return;
|
|
140
|
+
open.delete(meta.runId);
|
|
141
|
+
const { span, run } = entry;
|
|
142
|
+
try {
|
|
143
|
+
span.setAttributes(defined({
|
|
144
|
+
"input.value": outcome.inputText,
|
|
145
|
+
"output.value": lastAssistantText(outcome.messages),
|
|
146
|
+
"latch.run.status": outcome.status,
|
|
147
|
+
"latch.usage.input_tokens": outcome.usage.inputTokens,
|
|
148
|
+
"latch.usage.output_tokens": outcome.usage.outputTokens,
|
|
149
|
+
"latch.usage.cache_read_tokens": outcome.usage.cacheReadTokens,
|
|
150
|
+
"latch.usage.cache_write_tokens": outcome.usage.cacheWriteTokens,
|
|
151
|
+
"latch.cost": outcome.cost,
|
|
152
|
+
}));
|
|
153
|
+
if (outcome.status === "errored")
|
|
154
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: outcome.error });
|
|
155
|
+
const finished = guarded("finishedAttributes", meta.runId, () => destination.finishedAttributes?.(run, outcome));
|
|
156
|
+
if (finished)
|
|
157
|
+
span.setAttributes(finished);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
span.end();
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
async flush() {
|
|
164
|
+
const done = provider.forceFlush();
|
|
165
|
+
opts.waitUntil?.(done.catch(() => { }));
|
|
166
|
+
await done;
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
/** Open the run's span; the context the run then runs in. */
|
|
170
|
+
async function startRun(meta) {
|
|
171
|
+
ensureContextManager();
|
|
172
|
+
let agentOwner;
|
|
173
|
+
try {
|
|
174
|
+
agentOwner = await opts.agentOwner?.(meta);
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
console.warn(`latch-otel: agentOwner failed (run ${meta.runId}):`, e);
|
|
178
|
+
}
|
|
179
|
+
const run = {
|
|
180
|
+
runId: meta.runId,
|
|
181
|
+
chatId: meta.chatId,
|
|
182
|
+
sessionId: meta.rootChatId ?? meta.chatId,
|
|
183
|
+
agent: meta.agent,
|
|
184
|
+
modelId: meta.modelId,
|
|
185
|
+
depth: meta.depth,
|
|
186
|
+
trigger: meta.trigger,
|
|
187
|
+
agentVersion: meta.agentVersion,
|
|
188
|
+
parentRunId: meta.parentRunId,
|
|
189
|
+
user: opts.user ? guarded("user", meta.runId, () => opts.user(meta.principal)) : undefined,
|
|
190
|
+
agentOwner,
|
|
191
|
+
environment: opts.environment,
|
|
192
|
+
};
|
|
193
|
+
const extra = guarded("runAttributes", meta.runId, () => destination.runAttributes?.(run));
|
|
194
|
+
const everySpan = {
|
|
195
|
+
...standardAttributes(run),
|
|
196
|
+
...guarded("attributes", meta.runId, () => opts.attributes?.(meta)),
|
|
197
|
+
...extra?.everySpan,
|
|
198
|
+
};
|
|
199
|
+
nextTraceId = await traceIdForRun(meta.runId);
|
|
200
|
+
let span;
|
|
201
|
+
try {
|
|
202
|
+
span = tracer.startSpan(`latch.run ${meta.agent}`, {
|
|
203
|
+
root: true,
|
|
204
|
+
attributes: { ...everySpan, "latch.model": meta.modelId, ...extra?.root },
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
nextTraceId = undefined;
|
|
209
|
+
}
|
|
210
|
+
open.set(meta.runId, { span, run });
|
|
211
|
+
return trace.setSpan(context.active().setValue(RUN_ATTRIBUTES, everySpan), span);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The trace id a latch run is recorded under: derived from the run id, so
|
|
3
|
+
* anything that knows the run (a feedback button, a later score writer, a link
|
|
4
|
+
* from your own UI) can address its trace without looking it up.
|
|
5
|
+
*
|
|
6
|
+
* The first 32 hex characters of SHA-256(`latch-run:<runId>`), the same id
|
|
7
|
+
* `@intentface/latch-langfuse` produced, so traces recorded before keep
|
|
8
|
+
* matching.
|
|
9
|
+
*/
|
|
10
|
+
export declare function traceIdForRun(runId: string): Promise<string>;
|
|
11
|
+
//# sourceMappingURL=trace-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-id.d.ts","sourceRoot":"","sources":["../src/trace-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlE"}
|
package/dist/trace-id.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The trace id a latch run is recorded under: derived from the run id, so
|
|
3
|
+
* anything that knows the run (a feedback button, a later score writer, a link
|
|
4
|
+
* from your own UI) can address its trace without looking it up.
|
|
5
|
+
*
|
|
6
|
+
* The first 32 hex characters of SHA-256(`latch-run:<runId>`), the same id
|
|
7
|
+
* `@intentface/latch-langfuse` produced, so traces recorded before keep
|
|
8
|
+
* matching.
|
|
9
|
+
*/
|
|
10
|
+
export async function traceIdForRun(runId) {
|
|
11
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`latch-run:${runId}`));
|
|
12
|
+
return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, "0"))
|
|
13
|
+
.join("")
|
|
14
|
+
.slice(0, 32);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=trace-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-id.js","sourceRoot":"","sources":["../src/trace-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAa;IAC/C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,aAAa,KAAK,EAAE,CAAC,CAAC,CAAC;IACrG,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC9E,IAAI,CAAC,EAAE,CAAC;SACR,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@intentface/latch-otel",
|
|
3
|
+
"version": "0.22.0",
|
|
4
|
+
"description": "Latch tracing over OpenTelemetry — one trace per run, the AI SDK's GenAI spans under it, sent over OTLP to any backend, with a ready preset for Langfuse.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"ai-sdk",
|
|
8
|
+
"agents",
|
|
9
|
+
"llm",
|
|
10
|
+
"opentelemetry",
|
|
11
|
+
"otlp",
|
|
12
|
+
"observability",
|
|
13
|
+
"langfuse"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./langfuse": {
|
|
26
|
+
"types": "./dist/langfuse.d.ts",
|
|
27
|
+
"import": "./dist/langfuse.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public",
|
|
36
|
+
"registry": "https://registry.npmjs.org"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@opentelemetry/api": "^1.9.0",
|
|
40
|
+
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
41
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.217.0",
|
|
42
|
+
"@opentelemetry/sdk-trace-base": "^2.7.1",
|
|
43
|
+
"@intentface/latch-core": "^0.22.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@ai-sdk/otel": ">=1.0.0",
|
|
47
|
+
"ai": ">=7.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@ai-sdk/otel": "1.0.106",
|
|
51
|
+
"@libsql/client": "^0.15.0",
|
|
52
|
+
"@types/node": "^22",
|
|
53
|
+
"ai": "^7.0.106",
|
|
54
|
+
"drizzle-orm": "^0.45.0",
|
|
55
|
+
"typescript": "^5.7.0",
|
|
56
|
+
"vitest": "^4",
|
|
57
|
+
"zod": "^4.0.0",
|
|
58
|
+
"@intentface/latch-drizzle": "^0.22.0"
|
|
59
|
+
},
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "git+https://github.com/Intentface/intentface-latch.git",
|
|
63
|
+
"directory": "packages/otel"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tsc -p tsconfig.build.json",
|
|
67
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
68
|
+
"test": "vitest run"
|
|
69
|
+
}
|
|
70
|
+
}
|