Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,33 @@ Create `~/.config/opencode/opencode-langfuse.json` with your Langfuse credential
"secretKey": "sk-lf-...",
"baseUrl": "https://cloud.langfuse.com",
"environment": "development",
"userId": "your-user-id"
"userId": "your-user-id",
"tags": ["app:my-app"]
}
```

Only `publicKey` and `secretKey` are required. If `baseUrl` is not set, the plugin uses `https://cloud.langfuse.com`. If `environment` is not set, it uses `development`.

`tags` sets Langfuse trace tags on every trace. It accepts an array of non-empty strings and can also be provided as a plugin option in the OpenCode config, which takes precedence over the config file:

```json
{
"plugins": [
["@langfuse/opencode-observability-plugin", { "tags": ["app:my-app"] }]
]
}
```

For OpenCode 1, use the same option under `plugin`:

```json
{
"plugin": [
["@langfuse/opencode-observability-plugin", { "tags": ["app:my-app"] }]
]
}
```

You can also set credentials with environment variables:

```bash
Expand Down
14 changes: 14 additions & 0 deletions src/langfuse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1990,6 +1990,16 @@ const makeUserIdSpanProcessor = (userId: string) =>
forceFlush: () => Promise.resolve(),
}) satisfies SpanProcessor;

const makeTagsSpanProcessor = (tags: string[]) =>
({
onStart: (span: Span) => {
span.setAttribute("langfuse.trace.tags", tags);
},
onEnd: () => undefined,
shutdown: () => Promise.resolve(),
forceFlush: () => Promise.resolve(),
}) satisfies SpanProcessor;

const makePluginVersionSpanProcessor = () =>
({
onStart: (span: Span) => {
Expand Down Expand Up @@ -2038,6 +2048,7 @@ export const createLangfuseClient = (input: {
environment: string;
userId?: string;
serviceName?: string;
tags?: string[];
opencodeVersion?: string;
}) =>
Effect.sync(() => {
Expand Down Expand Up @@ -2067,6 +2078,9 @@ export const createLangfuseClient = (input: {
...(input.userId != null
? [makeUserIdSpanProcessor(input.userId)]
: []),
...(input.tags != null && input.tags.length > 0
? [makeTagsSpanProcessor(input.tags)]
: []),
processor,
makeAppRootSpanProcessor(tracerName),
],
Expand Down
23 changes: 22 additions & 1 deletion src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,39 @@ import { join } from "node:path";
import {
Data,
Effect,
Option,
Schema,
SynchronizedRef,
type SynchronizedRef as SynchronizedRefType,
} from "effect";

import { createLangfuseClient, type LangfuseClient } from "./langfuse.js";

const TagsArraySchema = Schema.Array(Schema.NonEmptyString);

const LangfuseCredentialsSchema = Schema.Struct({
publicKey: Schema.NonEmptyString,
secretKey: Schema.NonEmptyString,
baseUrl: Schema.optional(Schema.NonEmptyString),
environment: Schema.optional(Schema.NonEmptyString),
userId: Schema.optional(Schema.NonEmptyString),
serviceName: Schema.optional(Schema.NonEmptyString),
tags: Schema.optional(TagsArraySchema),
});

type LangfuseCredentials = typeof LangfuseCredentialsSchema.Type;

const PluginOptionsSchema = Schema.Struct({
tags: Schema.optional(TagsArraySchema),
});

// Decodes the optional `tags` plugin option. Unknown or malformed values are
// dropped so a bad option never disables tracing for the rest of the config.
export const decodePluginTags = (options: unknown) => {
const decoded = Schema.decodeUnknownOption(PluginOptionsSchema)(options);
return Option.isSome(decoded) ? decoded.value.tags?.slice() : undefined;
};

class MissingLangfuseCredentials extends Data.TaggedError(
"MissingLangfuseCredentials",
)<{ readonly message: string }> {}
Expand Down Expand Up @@ -111,7 +126,10 @@ declare global {
| undefined;
}

export const createLangfuseRuntime = (input: { opencodeVersion?: string }) =>
export const createLangfuseRuntime = (input: {
opencodeVersion?: string;
tags?: string[];
}) =>
Effect.gen(function* () {
const credentials = yield* loadLangfuseCredentials;
const clientInput = {
Expand All @@ -128,6 +146,9 @@ export const createLangfuseRuntime = (input: { opencodeVersion?: string }) =>
"development",
userId: credentials.userId ?? process.env.LANGFUSE_USER_ID,
serviceName: credentials.serviceName ?? process.env.LANGFUSE_SERVICE_NAME,
// Plugin options win over the config file, mirroring how environment
// variables override it for the other settings.
tags: input.tags ?? credentials.tags?.slice(),
opencodeVersion: input.opencodeVersion,
} satisfies Parameters<typeof createLangfuseClient>[0];
const cacheKey = JSON.stringify(clientInput);
Expand Down
Loading