@glassflow-ai/rius 0.1.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 +328 -0
- package/dist/index.cjs +914 -0
- package/dist/index.d.cts +156 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +889 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GlassFlow
|
|
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,328 @@
|
|
|
1
|
+
# @glassflow-ai/rius
|
|
2
|
+
|
|
3
|
+
OpenTelemetry-native tracing for AI agents and LLM applications.
|
|
4
|
+
|
|
5
|
+
Rius wraps the OpenTelemetry SDK with agent-shaped primitives: spans for
|
|
6
|
+
tool calls and chains, generations for LLM calls, and a function wrapper
|
|
7
|
+
that turns a plain `async` function into an instrumented one. Traces are
|
|
8
|
+
sent as OTLP over HTTP, so any OTLP-compatible backend can receive them.
|
|
9
|
+
|
|
10
|
+
> Status: alpha and unpublished (0.1.0). APIs may change before the first release.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @glassflow-ai/rius @opentelemetry/api
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`@opentelemetry/api` is a peer dependency and is never bundled. Install
|
|
19
|
+
it alongside `@glassflow-ai/rius` at a version satisfying `^1.9.0`.
|
|
20
|
+
|
|
21
|
+
## Quickstart
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import {
|
|
25
|
+
init,
|
|
26
|
+
observe,
|
|
27
|
+
startAsCurrentSpan,
|
|
28
|
+
startAsCurrentGeneration,
|
|
29
|
+
SpanKind,
|
|
30
|
+
} from "@glassflow-ai/rius";
|
|
31
|
+
|
|
32
|
+
const client = init({
|
|
33
|
+
apiKey: process.env.RIUS_API_KEY, // or set RIUS_API_KEY
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Optional: wait for auto-instrumentations to finish attaching before the
|
|
37
|
+
// first span is created.
|
|
38
|
+
await client.ready;
|
|
39
|
+
|
|
40
|
+
// 1. Function wrapper — trace a whole call. Use a named function expression
|
|
41
|
+
// (or pass { name }): an arrow function has no inferrable name and its
|
|
42
|
+
// span would fall back to "anonymous".
|
|
43
|
+
const handle = observe(async function handle(query: string) {
|
|
44
|
+
return await callModel(query);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// 2. Scoped span — trace a block
|
|
48
|
+
await startAsCurrentSpan(
|
|
49
|
+
"retrieve",
|
|
50
|
+
{ kind: SpanKind.RETRIEVER, input: query },
|
|
51
|
+
async (obs) => {
|
|
52
|
+
const docs = await retrieveDocuments(query);
|
|
53
|
+
obs.setOutput(docs);
|
|
54
|
+
return docs;
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// 3. LLM generations — gen_ai-native
|
|
59
|
+
await startAsCurrentGeneration(
|
|
60
|
+
"chat",
|
|
61
|
+
{ model: "gpt-4o", input: messages },
|
|
62
|
+
async (gen) => {
|
|
63
|
+
const reply = await callModel(messages);
|
|
64
|
+
gen.setOutput(reply);
|
|
65
|
+
gen.setUsage({ inputTokens: 42, outputTokens: 17 });
|
|
66
|
+
return reply;
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Each surface has more detail below: `observe` for the function wrapper,
|
|
72
|
+
`startSpan`/`startAsCurrentSpan` for manual and scoped spans, and
|
|
73
|
+
`startGeneration`/`startAsCurrentGeneration` for LLM calls.
|
|
74
|
+
|
|
75
|
+
### `observe`: wrap a function
|
|
76
|
+
|
|
77
|
+
`observe` wraps a function so every call becomes a span. It always
|
|
78
|
+
returns a **promise**, even when the wrapped function is synchronous.
|
|
79
|
+
|
|
80
|
+
Use a named function expression (or pass `{ name }`) so the span gets a
|
|
81
|
+
real name. An arrow function has no inferrable name, and its span falls
|
|
82
|
+
back to `"anonymous"`:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { observe } from "@glassflow-ai/rius";
|
|
86
|
+
|
|
87
|
+
const handleQuery = observe(async function handleQuery(query: string) {
|
|
88
|
+
return await callModel(query);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Equivalent, with an explicit name:
|
|
92
|
+
const handleQuery2 = observe(
|
|
93
|
+
async (query: string) => callModel(query),
|
|
94
|
+
{ name: "handleQuery" },
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
await handleQuery("hello");
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `startSpan` / `startAsCurrentSpan`: manual spans
|
|
101
|
+
|
|
102
|
+
For finer control than `observe`, create spans directly:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { startAsCurrentSpan } from "@glassflow-ai/rius";
|
|
106
|
+
|
|
107
|
+
await startAsCurrentSpan("fetch-documents", { input: query }, async (span) => {
|
|
108
|
+
const docs = await fetchDocuments(query);
|
|
109
|
+
span.setOutput(docs);
|
|
110
|
+
return docs;
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`startSpan` returns a handle you end yourself (or dispose with `using`);
|
|
115
|
+
`startAsCurrentSpan` runs a callback with the span active, ending it
|
|
116
|
+
automatically and recording any thrown exception. The options argument is
|
|
117
|
+
optional in both, so `startAsCurrentSpan("step", async (span) => { ... })`
|
|
118
|
+
works when you have nothing to configure.
|
|
119
|
+
|
|
120
|
+
On the manual path, `recordException()` does what the scoped form does for
|
|
121
|
+
you: it records the error and sets ERROR status.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const span = startSpan("fetch-documents");
|
|
125
|
+
try {
|
|
126
|
+
span.setOutput(await fetchDocuments(query));
|
|
127
|
+
} catch (error) {
|
|
128
|
+
span.recordException(error);
|
|
129
|
+
throw error;
|
|
130
|
+
} finally {
|
|
131
|
+
span.end();
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `startGeneration` / `startAsCurrentGeneration`: LLM calls
|
|
136
|
+
|
|
137
|
+
A `Generation` is a span specialised for LLM calls, with setters for the
|
|
138
|
+
gen_ai message and usage attributes:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { startAsCurrentGeneration } from "@glassflow-ai/rius";
|
|
142
|
+
|
|
143
|
+
await startAsCurrentGeneration(
|
|
144
|
+
"chat-completion",
|
|
145
|
+
{
|
|
146
|
+
model: "gpt-4o",
|
|
147
|
+
provider: "openai",
|
|
148
|
+
input: messages,
|
|
149
|
+
modelParameters: { temperature: 0.2, max_tokens: 512 },
|
|
150
|
+
},
|
|
151
|
+
async (generation) => {
|
|
152
|
+
const response = await client.chat.completions.create({ model: "gpt-4o", messages });
|
|
153
|
+
generation.setOutput(response.choices[0]?.message);
|
|
154
|
+
generation.setUsage({
|
|
155
|
+
inputTokens: response.usage?.prompt_tokens,
|
|
156
|
+
outputTokens: response.usage?.completion_tokens,
|
|
157
|
+
});
|
|
158
|
+
generation.setFinishReasons(response.choices[0]?.finish_reason ?? "stop");
|
|
159
|
+
return response;
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Each `modelParameters` entry is recorded as `gen_ai.request.<key>`, so use the
|
|
165
|
+
provider's own parameter names. `setFinishReasons` accepts one reason or a
|
|
166
|
+
list.
|
|
167
|
+
|
|
168
|
+
## Auto-instrumentation
|
|
169
|
+
|
|
170
|
+
The SDK bundles five integrations. Each one is an optional peer
|
|
171
|
+
dependency: install the packages you need and `init()` enables whatever
|
|
172
|
+
it finds, so a plain `npm install @glassflow-ai/rius` patches nothing.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm install @arizeai/openinference-instrumentation-openai @arizeai/openinference-instrumentation-anthropic @arizeai/openinference-instrumentation-langchain @arizeai/openinference-vercel @modelcontextprotocol/sdk
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Install only the ones you use:
|
|
179
|
+
|
|
180
|
+
- **`openai`** wraps the OpenAI SDK, via `@arizeai/openinference-instrumentation-openai`, turning provider calls into spans.
|
|
181
|
+
- **`anthropic`** wraps the Anthropic SDK, via `@arizeai/openinference-instrumentation-anthropic`, turning Messages calls into LLM spans with model, messages and token counts.
|
|
182
|
+
- **`langchain`** traces LangChain.js chains, models, tools and retrievers, via `@arizeai/openinference-instrumentation-langchain`. It patches the callback manager in `@langchain/core`, which every LangChain.js application already has, and the SDK resolves that module for you so nothing has to be passed in at `init()`.
|
|
183
|
+
- **`vercel-ai`** attaches to spans the Vercel AI SDK's own OpenTelemetry integration produces, via `@arizeai/openinference-vercel`, adding OpenInference attributes to them. This package requires Node 22 or newer.
|
|
184
|
+
- **`mcp`** patches `@modelcontextprotocol/sdk`'s `Client.callTool` so every MCP tool call becomes a TOOL span, carrying the tool name, arguments, result, latency, and error status.
|
|
185
|
+
|
|
186
|
+
There is no selection option: `init()` always attempts every bundled
|
|
187
|
+
integration, so which ones actually attach is determined entirely by
|
|
188
|
+
which optional peers are installed. `client.ready` resolves with the
|
|
189
|
+
names that attached (for example `["openai", "mcp"]`) and never rejects,
|
|
190
|
+
even if every peer is missing or one of them is broken. A package that
|
|
191
|
+
is not installed stays quiet; a package that is installed but fails to
|
|
192
|
+
load logs a warning naming the integration and the underlying error, and
|
|
193
|
+
the SDK continues without it.
|
|
194
|
+
|
|
195
|
+
The `openai` and `anthropic` integrations work in both module systems:
|
|
196
|
+
CommonJS applications that `require` the provider SDK and pure-ESM
|
|
197
|
+
applications that `import` it are both patched, regardless of whether
|
|
198
|
+
the provider was loaded before or after `init()`. One combination is
|
|
199
|
+
not covered: the provider SDKs ship separate CommonJS and ESM builds,
|
|
200
|
+
and the instrumentation packages can patch only one of them per process
|
|
201
|
+
([Arize-ai/openinference#3557](https://github.com/Arize-ai/openinference/issues/3557)),
|
|
202
|
+
so exactly one build gets patched: the CommonJS build when anything in
|
|
203
|
+
the process `require`d the provider before `init()`, the ESM build
|
|
204
|
+
otherwise. Two combinations therefore stay uncovered until the
|
|
205
|
+
upstream fix lands: a CommonJS application whose *only* `require` of
|
|
206
|
+
the provider happens lazily after `init()` (requiring it anywhere
|
|
207
|
+
before `init()` avoids this), and — the mirror image — an ESM
|
|
208
|
+
application where some CommonJS dependency `require`d the provider
|
|
209
|
+
before `init()`, which wins the choice away from the ESM build the
|
|
210
|
+
application itself is calling. The `langchain`, `vercel-ai` and `mcp`
|
|
211
|
+
integrations attach differently and have no such edge.
|
|
212
|
+
|
|
213
|
+
Content captured by these integrations, prompts, tool arguments and
|
|
214
|
+
results, and so on, is covered by the same `mask` and `captureContent`
|
|
215
|
+
controls as our own spans, since sanitisation happens centrally at
|
|
216
|
+
export rather than per integration.
|
|
217
|
+
|
|
218
|
+
`disabled: true` now means no third-party patching happens at all: no
|
|
219
|
+
optional package is even imported, and `client.ready` resolves to an
|
|
220
|
+
empty array.
|
|
221
|
+
|
|
222
|
+
## Configuration
|
|
223
|
+
|
|
224
|
+
Options passed to `init()` take priority, then `RIUS_*` environment
|
|
225
|
+
variables, then the defaults below.
|
|
226
|
+
|
|
227
|
+
| Option | Environment variable | Default |
|
|
228
|
+
| ---------------- | ------------------------ | --------------------------------------------- |
|
|
229
|
+
| `endpoint` | `RIUS_ENDPOINT` | `https://ingest.eu.console.rius-glassflow.com` |
|
|
230
|
+
| `apiKey` | `RIUS_API_KEY` | none |
|
|
231
|
+
| `serviceName` | `RIUS_SERVICE_NAME` | `unknown_service` |
|
|
232
|
+
| `disabled` | `RIUS_DISABLED` | `false` |
|
|
233
|
+
| `sampleRate` | `RIUS_SAMPLE_RATE` | `1.0` |
|
|
234
|
+
| `captureContent` | `RIUS_CAPTURE_CONTENT` | `true` |
|
|
235
|
+
| `mask` | (options only) | none |
|
|
236
|
+
|
|
237
|
+
Traces are posted to `<endpoint>/v1/traces`.
|
|
238
|
+
|
|
239
|
+
`captureContent` controls whether input/output content (prompts,
|
|
240
|
+
completions, tool arguments) is attached to spans. It defaults to
|
|
241
|
+
`true`. Set it to `false`, or supply a `mask` function, if your spans
|
|
242
|
+
must not carry raw content. Both apply to span attributes and to the
|
|
243
|
+
attributes of span events and links. With `captureContent: false` the
|
|
244
|
+
message and stacktrace of a recorded exception are stripped as well,
|
|
245
|
+
since provider errors routinely echo the request back; the exception
|
|
246
|
+
event and its `exception.type` are kept, so failures stay visible.
|
|
247
|
+
`mask` does not extend that far: with `captureContent: true`, a `mask`
|
|
248
|
+
function scrubs content attributes on spans, events and links, but
|
|
249
|
+
exception messages, stacktraces and the status message pass through
|
|
250
|
+
unmasked, so a raw provider error can still reach your backend. Disable
|
|
251
|
+
`captureContent` if you need those scrubbed too.
|
|
252
|
+
|
|
253
|
+
`disabled` turns the SDK off completely: nothing is exported, and no
|
|
254
|
+
optional integration is loaded, so no third-party module is patched in your
|
|
255
|
+
process. `client.ready` resolves to an empty list. Spans can still be
|
|
256
|
+
created and are simply dropped.
|
|
257
|
+
|
|
258
|
+
## Reliability
|
|
259
|
+
|
|
260
|
+
Export is designed to never block or crash your application:
|
|
261
|
+
|
|
262
|
+
- **Async batched export.** Spans are queued in-process and exported in
|
|
263
|
+
batches from a background timer via OpenTelemetry's `BatchSpanProcessor`.
|
|
264
|
+
Span creation stays fast even when the backend is slow or unreachable.
|
|
265
|
+
- **Retries.** The OTLP/HTTP transport retries transient and retryable
|
|
266
|
+
failures with exponential backoff and jitter, up to 5 attempts, bounded
|
|
267
|
+
by the export timeout.
|
|
268
|
+
- **Graceful degradation.** If the backend stays unreachable, spans are
|
|
269
|
+
dropped and the failure is logged, it is never raised into application
|
|
270
|
+
code. Only the first export failure in a process is logged, naming
|
|
271
|
+
`RIUS_API_KEY`/`RIUS_ENDPOINT` as the likely cause; later failures stay
|
|
272
|
+
silent so a persistent outage does not spam your logs. `client.flush()`
|
|
273
|
+
returns `false` when the most recent export failed, so you can check
|
|
274
|
+
delivery explicitly instead of relying on logs alone.
|
|
275
|
+
- **Mask failures fail safe.** If a `mask` callback throws, the affected
|
|
276
|
+
attribute value is replaced with the literal `"[mask error]"` rather
|
|
277
|
+
than dropping the batch or letting the exception escape.
|
|
278
|
+
- **No automatic flush on exit.** The SDK does not register a process-exit
|
|
279
|
+
hook. Call `client.flush()` to force a pending export, or
|
|
280
|
+
`client.shutdown()` to drain the queue and tear the client down, before
|
|
281
|
+
your process exits:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
await client.flush();
|
|
285
|
+
await client.shutdown();
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Batching is tunable via the standard OpenTelemetry env vars:
|
|
289
|
+
`OTEL_BSP_MAX_QUEUE_SIZE` (default 2048; spans beyond this are dropped),
|
|
290
|
+
`OTEL_BSP_SCHEDULE_DELAY` (default 5000 ms), `OTEL_BSP_MAX_EXPORT_BATCH_SIZE`
|
|
291
|
+
(default 512), and `OTEL_BSP_EXPORT_TIMEOUT` (default 30000 ms).
|
|
292
|
+
|
|
293
|
+
## Development
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
npm install
|
|
297
|
+
npm run lint
|
|
298
|
+
npm run typecheck
|
|
299
|
+
npm test
|
|
300
|
+
npm run build
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Releasing
|
|
304
|
+
|
|
305
|
+
Releases are automated with [release-please](https://github.com/googleapis/release-please)
|
|
306
|
+
and published to npm.
|
|
307
|
+
|
|
308
|
+
1. Merge changes to `main` using [Conventional Commits](https://www.conventionalcommits.org/)
|
|
309
|
+
(`feat:` → minor, `fix:` → patch, `feat!:`/`BREAKING CHANGE` → major).
|
|
310
|
+
2. release-please keeps a **Release PR** open that bumps the package
|
|
311
|
+
version and updates the changelog. Merge it when you want to cut a
|
|
312
|
+
release.
|
|
313
|
+
3. Merging the Release PR tags `vX.Y.Z`, creates a GitHub Release, and
|
|
314
|
+
publishes to npm automatically using npm trusted publishing, no token
|
|
315
|
+
required.
|
|
316
|
+
|
|
317
|
+
Non-conventional commits are ignored for versioning.
|
|
318
|
+
|
|
319
|
+
The package has not been published yet, so the very first release cannot
|
|
320
|
+
use trusted publishing: npm requires a package to already exist before
|
|
321
|
+
it can be linked to a trusted publisher. That first publish needs either
|
|
322
|
+
a one-time manual `npm publish` or a temporary `NPM_TOKEN` secret; once
|
|
323
|
+
the package exists on npm, trusted publishing takes over and the token
|
|
324
|
+
can be removed.
|
|
325
|
+
|
|
326
|
+
## License
|
|
327
|
+
|
|
328
|
+
MIT
|