@opviva/recorder 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 +48 -0
- package/dist/adapters/ai-sdk.d.ts +31 -0
- package/dist/adapters/ai-sdk.js +36 -0
- package/dist/adapters/ai-sdk.js.map +1 -0
- package/dist/adapters/langchain.d.ts +17 -0
- package/dist/adapters/langchain.js +68 -0
- package/dist/adapters/langchain.js.map +1 -0
- package/dist/adapters/llm.d.ts +15 -0
- package/dist/adapters/llm.js +21 -0
- package/dist/adapters/llm.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/recorder.d.ts +40 -0
- package/dist/recorder.js +112 -0
- package/dist/recorder.js.map +1 -0
- package/dist/redact.d.ts +3 -0
- package/dist/redact.js +25 -0
- package/dist/redact.js.map +1 -0
- package/dist/transport.d.ts +15 -0
- package/dist/transport.js +62 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Opviva
|
|
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,48 @@
|
|
|
1
|
+
# @opviva/recorder
|
|
2
|
+
|
|
3
|
+
**Opviva Flight Recorder SDK** — capture what your AI agents do, in a tamper-evident timeline.
|
|
4
|
+
|
|
5
|
+
Record every tool call, model response, and decision your agents make into a signed, verifiable
|
|
6
|
+
event stream — so you can prove what happened, debug failures, and pass security/compliance review.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm i @opviva/recorder
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createRecorder } from "@opviva/recorder";
|
|
18
|
+
|
|
19
|
+
const recorder = createRecorder({ apiKey: process.env.OPVIVA_API_KEY });
|
|
20
|
+
const session = recorder.session({ agent: "support-bot" });
|
|
21
|
+
|
|
22
|
+
session.record({ type: "tool_call", name: "search", input: { q: "refund policy" } });
|
|
23
|
+
await session.flush();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Adapters
|
|
27
|
+
|
|
28
|
+
Instrument popular agent stacks with one line:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// Vercel AI SDK
|
|
32
|
+
import { instrumentTools, recorderMiddleware } from "@opviva/recorder";
|
|
33
|
+
|
|
34
|
+
// LangChain
|
|
35
|
+
import { opvivaLangChainHandler } from "@opviva/recorder";
|
|
36
|
+
|
|
37
|
+
// Raw LLM chat calls
|
|
38
|
+
import { instrumentChat } from "@opviva/recorder";
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Redaction
|
|
42
|
+
|
|
43
|
+
Sensitive fields (secrets, PII) are redactable before anything leaves your process — configure via
|
|
44
|
+
`RedactConfig`. See the full guide at **https://opviva.com/docs/recorder**.
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Session } from "../recorder.js";
|
|
2
|
+
/** A Vercel-AI-SDK-style tool: an object that may carry an `execute` function. Structurally typed so the
|
|
3
|
+
* adapter never depends on the `ai` package. */
|
|
4
|
+
export interface InstrumentableTool {
|
|
5
|
+
execute?: (...args: unknown[]) => unknown;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Instrument a Vercel AI SDK `tools` map: every tool with an `execute` gets it wrapped by the recorder so
|
|
10
|
+
* each call is captured (args, result, errors, timing) into the session timeline. Tools without an
|
|
11
|
+
* `execute` pass through untouched. Use the returned map in `generateText`/`streamText`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function instrumentTools<T extends Record<string, InstrumentableTool>>(session: Session, tools: T): T;
|
|
14
|
+
/** Structural shape of the Vercel AI SDK `wrapGenerate` middleware argument (no `ai` import). */
|
|
15
|
+
export interface GenerateMiddlewareArgs {
|
|
16
|
+
doGenerate: () => Promise<unknown>;
|
|
17
|
+
params: {
|
|
18
|
+
prompt?: unknown;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
};
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A Vercel AI SDK `LanguageModelV1Middleware` that records the prompt + generation for every model call.
|
|
25
|
+
* Use it with `wrapLanguageModel`:
|
|
26
|
+
* const model = wrapLanguageModel({ model: openai("gpt-4o"), middleware: recorderMiddleware(session) });
|
|
27
|
+
* Captures prompts/generations (pair this with `instrumentTools` for the agent's tool actions).
|
|
28
|
+
*/
|
|
29
|
+
export declare function recorderMiddleware(session: Session): {
|
|
30
|
+
wrapGenerate: (args: GenerateMiddlewareArgs) => Promise<unknown>;
|
|
31
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instrument a Vercel AI SDK `tools` map: every tool with an `execute` gets it wrapped by the recorder so
|
|
3
|
+
* each call is captured (args, result, errors, timing) into the session timeline. Tools without an
|
|
4
|
+
* `execute` pass through untouched. Use the returned map in `generateText`/`streamText`.
|
|
5
|
+
*/
|
|
6
|
+
export function instrumentTools(session, tools) {
|
|
7
|
+
const out = {};
|
|
8
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
9
|
+
out[name] = typeof tool.execute === "function" ? { ...tool, execute: session.wrapTool(name, tool.execute) } : tool;
|
|
10
|
+
}
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A Vercel AI SDK `LanguageModelV1Middleware` that records the prompt + generation for every model call.
|
|
15
|
+
* Use it with `wrapLanguageModel`:
|
|
16
|
+
* const model = wrapLanguageModel({ model: openai("gpt-4o"), middleware: recorderMiddleware(session) });
|
|
17
|
+
* Captures prompts/generations (pair this with `instrumentTools` for the agent's tool actions).
|
|
18
|
+
*/
|
|
19
|
+
export function recorderMiddleware(session) {
|
|
20
|
+
return {
|
|
21
|
+
async wrapGenerate({ doGenerate, params }) {
|
|
22
|
+
session.logPrompt({ prompt: params?.prompt });
|
|
23
|
+
try {
|
|
24
|
+
const result = await doGenerate();
|
|
25
|
+
const r = result;
|
|
26
|
+
session.logDecision({ output: r?.text, toolArgs: r?.toolCalls });
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
session.recordError(err);
|
|
31
|
+
throw err; // re-raise the host's own error
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=ai-sdk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-sdk.js","sourceRoot":"","sources":["../../src/adapters/ai-sdk.ts"],"names":[],"mappings":"AASA;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAA+C,OAAgB,EAAE,KAAQ;IACtG,MAAM,GAAG,GAAuC,EAAE,CAAC;IACnD,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,IAAI,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACrH,CAAC;IACD,OAAO,GAAQ,CAAC;AAClB,CAAC;AASD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,OAAO;QACL,KAAK,CAAC,YAAY,CAAC,EAAE,UAAU,EAAE,MAAM,EAA0B;YAC/D,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;YAC9C,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;gBAClC,MAAM,CAAC,GAAG,MAA6D,CAAC;gBACxE,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;gBACjE,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;gBACzB,MAAM,GAAG,CAAC,CAAC,gCAAgC;YAC7C,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Session } from "../recorder.js";
|
|
2
|
+
/** The handler shape LangChain JS accepts in `callbacks: [...]`. Loosely typed so we never import langchain. */
|
|
3
|
+
export interface LangChainCallbackHandler {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
handleLLMStart(llm: unknown, prompts: unknown, runId?: string): void;
|
|
6
|
+
handleChatModelStart(llm: unknown, messages: unknown, runId?: string): void;
|
|
7
|
+
handleToolStart(tool: unknown, input: unknown, runId?: string): void;
|
|
8
|
+
handleToolEnd(output: unknown, runId?: string): void;
|
|
9
|
+
handleToolError(err: unknown, runId?: string): void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A LangChain JS callback handler that records LLM/chat starts (as prompts) and tool start/end/error
|
|
13
|
+
* (as tool_call/tool_result) into an Opviva session. Pass it in `callbacks`:
|
|
14
|
+
* chain.invoke(input, { callbacks: [opvivaLangChainHandler(session)] })
|
|
15
|
+
* Never throws into the LangChain run.
|
|
16
|
+
*/
|
|
17
|
+
export declare function opvivaLangChainHandler(session: Session): LangChainCallbackHandler;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
function nameOf(serialized) {
|
|
2
|
+
const s = serialized;
|
|
3
|
+
if (s?.id && s.id.length > 0)
|
|
4
|
+
return s.id[s.id.length - 1];
|
|
5
|
+
return s?.name;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A LangChain JS callback handler that records LLM/chat starts (as prompts) and tool start/end/error
|
|
9
|
+
* (as tool_call/tool_result) into an Opviva session. Pass it in `callbacks`:
|
|
10
|
+
* chain.invoke(input, { callbacks: [opvivaLangChainHandler(session)] })
|
|
11
|
+
* Never throws into the LangChain run.
|
|
12
|
+
*/
|
|
13
|
+
export function opvivaLangChainHandler(session) {
|
|
14
|
+
const toolNames = new Map();
|
|
15
|
+
return {
|
|
16
|
+
name: "opviva_recorder",
|
|
17
|
+
handleLLMStart(llm, prompts, _runId) {
|
|
18
|
+
try {
|
|
19
|
+
session.logPrompt({ prompts, model: nameOf(llm) });
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
/* never break the chain */
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
handleChatModelStart(llm, messages, _runId) {
|
|
26
|
+
try {
|
|
27
|
+
session.logPrompt({ messages: JSON.stringify(messages), model: nameOf(llm) });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
/* never break the chain */
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
handleToolStart(tool, input, runId) {
|
|
34
|
+
try {
|
|
35
|
+
const name = tool?.name ?? "tool";
|
|
36
|
+
if (runId)
|
|
37
|
+
toolNames.set(runId, name);
|
|
38
|
+
session.logToolCall(name, { input });
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* never break the chain */
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
handleToolEnd(output, runId) {
|
|
45
|
+
try {
|
|
46
|
+
const name = (runId && toolNames.get(runId)) || "tool";
|
|
47
|
+
if (runId)
|
|
48
|
+
toolNames.delete(runId);
|
|
49
|
+
session.logToolResult(name, { result: output });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
/* never break the chain */
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
handleToolError(err, runId) {
|
|
56
|
+
try {
|
|
57
|
+
const name = (runId && toolNames.get(runId)) || "tool";
|
|
58
|
+
if (runId)
|
|
59
|
+
toolNames.delete(runId);
|
|
60
|
+
session.logToolResult(name, { error: err });
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* never break the chain */
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=langchain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langchain.js","sourceRoot":"","sources":["../../src/adapters/langchain.ts"],"names":[],"mappings":"AAkBA,SAAS,MAAM,CAAC,UAAmB;IACjC,MAAM,CAAC,GAAG,UAAoC,CAAC;IAC/C,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,EAAE,IAAI,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAgB;IACrD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC5C,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM;YACjC,IAAI,CAAC;gBACH,OAAO,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACrD,CAAC;YAAC,MAAM,CAAC;gBACP,2BAA2B;YAC7B,CAAC;QACH,CAAC;QACD,oBAAoB,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM;YACxC,IAAI,CAAC;gBACH,OAAO,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChF,CAAC;YAAC,MAAM,CAAC;gBACP,2BAA2B;YAC7B,CAAC;QACH,CAAC;QACD,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK;YAChC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAI,IAA+B,EAAE,IAAI,IAAI,MAAM,CAAC;gBAC9D,IAAI,KAAK;oBAAE,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;gBACtC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,2BAA2B;YAC7B,CAAC;QACH,CAAC;QACD,aAAa,CAAC,MAAM,EAAE,KAAK;YACzB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,CAAC,KAAK,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC;gBACvD,IAAI,KAAK;oBAAE,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACnC,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;YAClD,CAAC;YAAC,MAAM,CAAC;gBACP,2BAA2B;YAC7B,CAAC;QACH,CAAC;QACD,eAAe,CAAC,GAAG,EAAE,KAAK;YACxB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,CAAC,KAAK,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC;gBACvD,IAAI,KAAK;oBAAE,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACnC,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAC9C,CAAC;YAAC,MAAM,CAAC;gBACP,2BAA2B;YAC7B,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Session } from "../recorder.js";
|
|
2
|
+
/** OpenAI/Anthropic-style chat request (provider-agnostic; only the common fields are read). */
|
|
3
|
+
export interface ChatCreateArgs {
|
|
4
|
+
model?: string;
|
|
5
|
+
messages?: unknown;
|
|
6
|
+
system?: unknown;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Wrap an OpenAI/Anthropic-style chat-create call so each request's prompt and the model's response are
|
|
11
|
+
* recorded. Works with `openai.chat.completions.create` and `anthropic.messages.create`:
|
|
12
|
+
* const create = instrumentChat(session, (args) => openai.chat.completions.create(args));
|
|
13
|
+
* Records the prompt + decision; on failure records the error and re-raises the host's error.
|
|
14
|
+
*/
|
|
15
|
+
export declare function instrumentChat<A extends ChatCreateArgs, R>(session: Session, create: (args: A) => Promise<R>): (args: A) => Promise<R>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap an OpenAI/Anthropic-style chat-create call so each request's prompt and the model's response are
|
|
3
|
+
* recorded. Works with `openai.chat.completions.create` and `anthropic.messages.create`:
|
|
4
|
+
* const create = instrumentChat(session, (args) => openai.chat.completions.create(args));
|
|
5
|
+
* Records the prompt + decision; on failure records the error and re-raises the host's error.
|
|
6
|
+
*/
|
|
7
|
+
export function instrumentChat(session, create) {
|
|
8
|
+
return async (args) => {
|
|
9
|
+
session.logPrompt({ model: args.model, messages: args.messages, system: args.system });
|
|
10
|
+
try {
|
|
11
|
+
const result = await create(args);
|
|
12
|
+
session.logDecision({ model: args.model, output: result });
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
session.recordError(err);
|
|
17
|
+
throw err; // re-raise the host's own error
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=llm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/adapters/llm.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAgB,EAChB,MAA+B;IAE/B,OAAO,KAAK,EAAE,IAAO,EAAc,EAAE;QACnC,OAAO,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACvF,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;YAClC,OAAO,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;YAC3D,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACzB,MAAM,GAAG,CAAC,CAAC,gCAAgC;QAC7C,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { AgentEventType, RecorderEvent, SessionDescriptor, RedactConfig, RecorderConfig, } from "./types.js";
|
|
2
|
+
export { EVENT_TYPES } from "./types.js";
|
|
3
|
+
export { redact } from "./redact.js";
|
|
4
|
+
export { createRecorder } from "./recorder.js";
|
|
5
|
+
export type { Recorder, Session, SessionOptions } from "./recorder.js";
|
|
6
|
+
export { instrumentTools, recorderMiddleware } from "./adapters/ai-sdk.js";
|
|
7
|
+
export type { InstrumentableTool, GenerateMiddlewareArgs } from "./adapters/ai-sdk.js";
|
|
8
|
+
export { opvivaLangChainHandler } from "./adapters/langchain.js";
|
|
9
|
+
export type { LangChainCallbackHandler } from "./adapters/langchain.js";
|
|
10
|
+
export { instrumentChat } from "./adapters/llm.js";
|
|
11
|
+
export type { ChatCreateArgs } from "./adapters/llm.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { EVENT_TYPES } from "./types.js";
|
|
2
|
+
export { redact } from "./redact.js";
|
|
3
|
+
export { createRecorder } from "./recorder.js";
|
|
4
|
+
export { instrumentTools, recorderMiddleware } from "./adapters/ai-sdk.js";
|
|
5
|
+
export { opvivaLangChainHandler } from "./adapters/langchain.js";
|
|
6
|
+
export { instrumentChat } from "./adapters/llm.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RecorderConfig } from "./types.js";
|
|
2
|
+
export interface SessionOptions {
|
|
3
|
+
readonly id?: string;
|
|
4
|
+
readonly actor?: string | null;
|
|
5
|
+
readonly externalSessionId?: string | null;
|
|
6
|
+
}
|
|
7
|
+
export interface Session {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
logPrompt(p: Record<string, unknown>): void;
|
|
10
|
+
logDecision(d: {
|
|
11
|
+
model?: string;
|
|
12
|
+
output?: unknown;
|
|
13
|
+
toolSelected?: string;
|
|
14
|
+
toolArgs?: unknown;
|
|
15
|
+
}): void;
|
|
16
|
+
wrapTool<A extends unknown[], R>(name: string, fn: (...args: A) => R | Promise<R>): (...args: A) => Promise<R>;
|
|
17
|
+
logToolCall(name: string, fields?: Record<string, unknown>): void;
|
|
18
|
+
logToolResult(name: string, opts?: {
|
|
19
|
+
result?: unknown;
|
|
20
|
+
error?: unknown;
|
|
21
|
+
} & Record<string, unknown>): void;
|
|
22
|
+
logDataAccess(d: {
|
|
23
|
+
resource: string;
|
|
24
|
+
operation: string;
|
|
25
|
+
rowsAffected?: number;
|
|
26
|
+
destination?: string;
|
|
27
|
+
}): void;
|
|
28
|
+
outcome(o: {
|
|
29
|
+
status: string;
|
|
30
|
+
summary?: string;
|
|
31
|
+
}): void;
|
|
32
|
+
recordError(err: unknown): void;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export interface Recorder {
|
|
36
|
+
session(opts?: SessionOptions): Session;
|
|
37
|
+
flush(): Promise<void>;
|
|
38
|
+
stop(): void;
|
|
39
|
+
}
|
|
40
|
+
export declare function createRecorder(config: RecorderConfig): Recorder;
|
package/dist/recorder.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { redact } from "./redact.js";
|
|
2
|
+
import { createTransport } from "./transport.js";
|
|
3
|
+
const DEFAULT_ENDPOINT = "https://api.opviva.com";
|
|
4
|
+
const DEFAULT_FLUSH_MS = 5_000;
|
|
5
|
+
const DEFAULT_MAX_BATCH = 50;
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
7
|
+
export function createRecorder(config) {
|
|
8
|
+
const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;
|
|
9
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
10
|
+
const maxBatch = config.maxBatch ?? DEFAULT_MAX_BATCH;
|
|
11
|
+
const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_MS;
|
|
12
|
+
const onError = config.onError ?? ((err) => {
|
|
13
|
+
try {
|
|
14
|
+
console.warn("[opviva-recorder]", err);
|
|
15
|
+
}
|
|
16
|
+
catch { /* noop */ }
|
|
17
|
+
});
|
|
18
|
+
const redactCfg = config.redact;
|
|
19
|
+
const transport = createTransport({
|
|
20
|
+
endpoint,
|
|
21
|
+
apiKey: config.apiKey,
|
|
22
|
+
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
23
|
+
fetchImpl,
|
|
24
|
+
onError,
|
|
25
|
+
});
|
|
26
|
+
const timer = setInterval(() => void transport.flush(), flushIntervalMs);
|
|
27
|
+
if (typeof timer.unref === "function") {
|
|
28
|
+
timer.unref();
|
|
29
|
+
}
|
|
30
|
+
const onExit = () => void transport.flush();
|
|
31
|
+
if (typeof process !== "undefined" && typeof process.on === "function") {
|
|
32
|
+
process.on("beforeExit", onExit);
|
|
33
|
+
}
|
|
34
|
+
function makeSession(opts = {}) {
|
|
35
|
+
const descriptor = {
|
|
36
|
+
id: opts.id ?? globalThis.crypto.randomUUID(),
|
|
37
|
+
app: config.app,
|
|
38
|
+
actor: opts.actor ?? null,
|
|
39
|
+
externalSessionId: opts.externalSessionId ?? null,
|
|
40
|
+
};
|
|
41
|
+
let seq = 0;
|
|
42
|
+
function record(type, payload, dataTouched = []) {
|
|
43
|
+
try {
|
|
44
|
+
seq += 1;
|
|
45
|
+
const event = {
|
|
46
|
+
seq,
|
|
47
|
+
type,
|
|
48
|
+
payload: redactCfg ? redact(payload, redactCfg) : payload,
|
|
49
|
+
dataTouched: redactCfg ? redact(dataTouched, redactCfg) : dataTouched,
|
|
50
|
+
occurredAt: new Date().toISOString(),
|
|
51
|
+
};
|
|
52
|
+
// Probe serializability now so a bad payload fails here (caught) — never inside flush().
|
|
53
|
+
JSON.stringify(event);
|
|
54
|
+
transport.enqueue(descriptor, event);
|
|
55
|
+
if (transport.pending() >= maxBatch)
|
|
56
|
+
void transport.flush();
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
onError(err); // never throw into the host
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
id: descriptor.id,
|
|
64
|
+
logPrompt: (p) => record("prompt", p),
|
|
65
|
+
logDecision: (d) => record("decision", d),
|
|
66
|
+
logDataAccess: (d) => record("data_access", d, [{ resource: d.resource, operation: d.operation }]),
|
|
67
|
+
outcome: (o) => record("outcome", o),
|
|
68
|
+
recordError: (err) => record("error", { message: err instanceof Error ? err.message : String(err) }),
|
|
69
|
+
wrapTool(name, fn) {
|
|
70
|
+
return async (...args) => {
|
|
71
|
+
const start = Date.now();
|
|
72
|
+
record("tool_call", { name, args });
|
|
73
|
+
try {
|
|
74
|
+
const result = await fn(...args);
|
|
75
|
+
record("tool_result", { name, result, durationMs: Date.now() - start });
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
record("tool_result", {
|
|
80
|
+
name,
|
|
81
|
+
error: err instanceof Error ? err.message : String(err),
|
|
82
|
+
durationMs: Date.now() - start,
|
|
83
|
+
});
|
|
84
|
+
throw err; // re-throw: never swallow the host's tool error
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
logToolCall: (name, fields = {}) => record("tool_call", { name, ...fields }),
|
|
89
|
+
logToolResult: (name, opts = {}) => {
|
|
90
|
+
const { result, error, ...rest } = opts;
|
|
91
|
+
const payload = { name, ...rest };
|
|
92
|
+
if (result !== undefined)
|
|
93
|
+
payload.result = result;
|
|
94
|
+
if (error !== undefined)
|
|
95
|
+
payload.error = error instanceof Error ? error.message : String(error);
|
|
96
|
+
record("tool_result", payload);
|
|
97
|
+
},
|
|
98
|
+
close: () => transport.closeSession(descriptor),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
session: makeSession,
|
|
103
|
+
flush: () => transport.flush(),
|
|
104
|
+
stop: () => {
|
|
105
|
+
clearInterval(timer);
|
|
106
|
+
if (typeof process !== "undefined" && typeof process.off === "function") {
|
|
107
|
+
process.off("beforeExit", onExit);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.js","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"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"}
|
package/dist/redact.d.ts
ADDED
package/dist/redact.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Recursively redact a value: drop configured object keys and scrub strings. Pure; never mutates input. */
|
|
2
|
+
export function redact(value, cfg) {
|
|
3
|
+
const dropKeys = new Set(cfg.dropKeys ?? []);
|
|
4
|
+
const scrubbers = cfg.scrubbers ?? [];
|
|
5
|
+
function walk(v) {
|
|
6
|
+
if (typeof v === "string") {
|
|
7
|
+
let s = v;
|
|
8
|
+
for (const re of scrubbers)
|
|
9
|
+
s = s.replace(re, "[redacted]");
|
|
10
|
+
return s;
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(v))
|
|
13
|
+
return v.map(walk);
|
|
14
|
+
if (v && typeof v === "object") {
|
|
15
|
+
const out = {};
|
|
16
|
+
for (const [k, val] of Object.entries(v)) {
|
|
17
|
+
out[k] = dropKeys.has(k) ? "[redacted]" : walk(val);
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
return v;
|
|
22
|
+
}
|
|
23
|
+
return walk(value);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAEA,4GAA4G;AAC5G,MAAM,UAAU,MAAM,CAAC,KAAc,EAAE,GAAiB;IACtD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;IAEtC,SAAS,IAAI,CAAC,CAAU;QACtB,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC1B,IAAI,CAAC,GAAG,CAAC,CAAC;YACV,KAAK,MAAM,EAAE,IAAI,SAAS;gBAAE,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;YAC5D,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC/B,MAAM,GAAG,GAA4B,EAAE,CAAC;YACxC,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAA4B,CAAC,EAAE,CAAC;gBACpE,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtD,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RecorderEvent, SessionDescriptor } from "./types.js";
|
|
2
|
+
export interface TransportConfig {
|
|
3
|
+
readonly endpoint: string;
|
|
4
|
+
readonly apiKey: string;
|
|
5
|
+
readonly timeoutMs: number;
|
|
6
|
+
readonly fetchImpl: typeof fetch;
|
|
7
|
+
readonly onError: (err: unknown) => void;
|
|
8
|
+
}
|
|
9
|
+
export interface Transport {
|
|
10
|
+
enqueue(session: SessionDescriptor, event: RecorderEvent): void;
|
|
11
|
+
flush(): Promise<void>;
|
|
12
|
+
closeSession(session: SessionDescriptor): Promise<void>;
|
|
13
|
+
pending(): number;
|
|
14
|
+
}
|
|
15
|
+
export declare function createTransport(cfg: TransportConfig): Transport;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function createTransport(cfg) {
|
|
2
|
+
const buffers = new Map();
|
|
3
|
+
function safeOnError(err) {
|
|
4
|
+
try {
|
|
5
|
+
cfg.onError(err);
|
|
6
|
+
}
|
|
7
|
+
catch { /* an onError that throws must not break flush */ }
|
|
8
|
+
}
|
|
9
|
+
function enqueue(session, event) {
|
|
10
|
+
const existing = buffers.get(session.id);
|
|
11
|
+
if (existing)
|
|
12
|
+
existing.events.push(event);
|
|
13
|
+
else
|
|
14
|
+
buffers.set(session.id, { session, events: [event] });
|
|
15
|
+
}
|
|
16
|
+
async function post(path, body) {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);
|
|
19
|
+
try {
|
|
20
|
+
const response = await cfg.fetchImpl(`${cfg.endpoint}${path}`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: { "content-type": "application/json", "x-api-key": cfg.apiKey },
|
|
23
|
+
body: JSON.stringify(body),
|
|
24
|
+
signal: controller.signal,
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok)
|
|
27
|
+
throw new Error(`opviva ingest failed: ${response.status}`);
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function flush() {
|
|
34
|
+
const batches = [...buffers.values()].filter((b) => b.events.length > 0);
|
|
35
|
+
buffers.clear();
|
|
36
|
+
for (const b of batches) {
|
|
37
|
+
try {
|
|
38
|
+
await post("/recorder/events", { session: b.session, events: b.events });
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
safeOnError(err); // at-most-once: events already cleared, never re-thrown
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function closeSession(session) {
|
|
46
|
+
await flush();
|
|
47
|
+
try {
|
|
48
|
+
await post(`/recorder/sessions/${session.id}/close`, {});
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
safeOnError(err);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function pending() {
|
|
55
|
+
let n = 0;
|
|
56
|
+
for (const b of buffers.values())
|
|
57
|
+
n += b.events.length;
|
|
58
|
+
return n;
|
|
59
|
+
}
|
|
60
|
+
return { enqueue, flush, closeSession, pending };
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type AgentEventType = "prompt" | "decision" | "tool_call" | "tool_result" | "data_access" | "outcome" | "error";
|
|
2
|
+
export declare const EVENT_TYPES: readonly AgentEventType[];
|
|
3
|
+
export interface RecorderEvent {
|
|
4
|
+
readonly seq: number;
|
|
5
|
+
readonly type: AgentEventType;
|
|
6
|
+
readonly payload: unknown;
|
|
7
|
+
readonly dataTouched: unknown;
|
|
8
|
+
readonly occurredAt: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SessionDescriptor {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly app: string;
|
|
13
|
+
readonly actor?: string | null;
|
|
14
|
+
readonly externalSessionId?: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface RedactConfig {
|
|
17
|
+
readonly dropKeys?: readonly string[];
|
|
18
|
+
readonly scrubbers?: readonly RegExp[];
|
|
19
|
+
}
|
|
20
|
+
export interface RecorderConfig {
|
|
21
|
+
readonly apiKey: string;
|
|
22
|
+
readonly app: string;
|
|
23
|
+
readonly endpoint?: string;
|
|
24
|
+
readonly fetch?: typeof fetch;
|
|
25
|
+
readonly flushIntervalMs?: number;
|
|
26
|
+
readonly maxBatch?: number;
|
|
27
|
+
readonly timeoutMs?: number;
|
|
28
|
+
readonly redact?: RedactConfig;
|
|
29
|
+
readonly onError?: (err: unknown) => void;
|
|
30
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,MAAM,WAAW,GAA8B;IACpD,QAAQ;IACR,UAAU;IACV,WAAW;IACX,aAAa;IACb,aAAa;IACb,SAAS;IACT,OAAO;CACR,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opviva/recorder",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Opviva Flight Recorder SDK — capture what your AI agents do, in a tamper-evident timeline.",
|
|
6
|
+
"keywords": ["opviva", "ai-agent", "observability", "forensics", "llm", "vercel-ai-sdk"],
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/Varun369A/vibe-ops.git",
|
|
20
|
+
"directory": "packages/recorder"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://opviva.com/docs/recorder",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.9.0",
|
|
33
|
+
"typescript": "^5.6.3",
|
|
34
|
+
"vitest": "^2.1.8"
|
|
35
|
+
}
|
|
36
|
+
}
|