@rivus/agent 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.
@@ -0,0 +1,175 @@
1
+ import { execFile } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { promisify } from "node:util";
7
+
8
+ import { runLangfuseDriveE2E } from "../dist/testing/index.js";
9
+
10
+ const execFileAsync = promisify(execFile);
11
+ const baseUrl = requiredEnv("LANGFUSE_BASE_URL");
12
+ const publicKey = requiredEnv("LANGFUSE_PUBLIC_KEY");
13
+ const secretKey = requiredEnv("LANGFUSE_SECRET_KEY");
14
+ const marker = `${new Date()
15
+ .toISOString()
16
+ .replace(/[^0-9]/g, "")
17
+ .slice(0, 14)}-${randomUUID().slice(0, 8)}`;
18
+ const sessionKey = `e2e:langfuse-drive:${marker}`;
19
+ const expectedDriveTitle = `langfuse-e2e-${marker}.html`;
20
+ const discoveredTraceIds = new Set();
21
+ const deploymentStateDirectory = await mkdtemp(join(tmpdir(), "rivus-langfuse-e2e-state-"));
22
+ const prompt = [
23
+ "Read the granted Langfuse HTML publishing Skill.",
24
+ "Create a polished, accessible, self-contained Chinese Langfuse introduction page.",
25
+ `Include the visible E2E marker ${marker}, then upload it as ${expectedDriveTitle}.`,
26
+ "Return the verified Feishu Drive URL."
27
+ ].join(" ");
28
+
29
+ const result = await runLangfuseDriveE2E({
30
+ dependencies: {
31
+ inspectDrive: async (url) => {
32
+ const output = await command(process.env.RIVUS_LARK_CLI_PATH?.trim() || "lark-cli", [
33
+ "drive",
34
+ "+inspect",
35
+ "--as",
36
+ "user",
37
+ "--url",
38
+ url,
39
+ "--format",
40
+ "json"
41
+ ]);
42
+ const parsed = JSON.parse(output);
43
+ return isRecord(parsed.data) ? parsed.data : parsed;
44
+ },
45
+ listObservations: async ({ deadlineAt, fromStartTime, sessionKey: expectedSessionKey, toStartTime }) => {
46
+ if (discoveredTraceIds.size === 0) {
47
+ let cursor;
48
+ do {
49
+ const endpoint = new URL("/api/public/v2/observations", baseUrl);
50
+ endpoint.searchParams.set("fields", "core,basic");
51
+ endpoint.searchParams.set("fromStartTime", fromStartTime);
52
+ endpoint.searchParams.set("limit", "1000");
53
+ endpoint.searchParams.set("toStartTime", toStartTime);
54
+ if (cursor) endpoint.searchParams.set("cursor", cursor);
55
+ const body = await fetchLangfuseJson(endpoint, deadlineAt);
56
+ if (!isRecord(body) || !Array.isArray(body.data))
57
+ throw new Error("Langfuse returned an invalid observation page");
58
+ for (const observation of body.data.filter(isObservation)) {
59
+ if (observation.sessionId === expectedSessionKey) discoveredTraceIds.add(observation.traceId);
60
+ }
61
+ cursor = isRecord(body.meta) && typeof body.meta.cursor === "string" ? body.meta.cursor : undefined;
62
+ } while (cursor && discoveredTraceIds.size === 0);
63
+ }
64
+ const traces = [];
65
+ for (const traceId of [...discoveredTraceIds].slice(0, 4)) {
66
+ traces.push(await fetchLangfuseJson(new URL(`/api/public/traces/${traceId}`, baseUrl), deadlineAt));
67
+ }
68
+ return traces.flatMap((trace) => {
69
+ if (
70
+ !isRecord(trace) ||
71
+ typeof trace.id !== "string" ||
72
+ typeof trace.sessionId !== "string" ||
73
+ !Array.isArray(trace.observations)
74
+ )
75
+ return [];
76
+ return trace.observations.filter(isTraceObservation).map((observation) => ({
77
+ ...observation,
78
+ sessionId: trace.sessionId,
79
+ traceId: trace.id
80
+ }));
81
+ });
82
+ },
83
+ now: () => new Date(),
84
+ readDriveFile: async (fileToken) => {
85
+ const directory = await mkdtemp(join(tmpdir(), "rivus-langfuse-drive-e2e-"));
86
+ const outputName = `./${expectedDriveTitle}`;
87
+ try {
88
+ await command(
89
+ process.env.RIVUS_LARK_CLI_PATH?.trim() || "lark-cli",
90
+ ["drive", "+download", "--as", "user", "--file-token", fileToken, "--output", outputName],
91
+ directory
92
+ );
93
+ return await readFile(join(directory, expectedDriveTitle), "utf8");
94
+ } finally {
95
+ await rm(directory, { force: true, recursive: true });
96
+ }
97
+ },
98
+ runAgent: ({ prompt: agentPrompt, sessionKey: agentSessionKey }) =>
99
+ command(
100
+ process.execPath,
101
+ [
102
+ "dist/cli.js",
103
+ "--bootstrap",
104
+ "./examples/pi-feishu-deployment.bootstrap.ts",
105
+ "--manifest",
106
+ "./examples/rivus-langfuse-demo.config.json",
107
+ "--prompt",
108
+ agentPrompt,
109
+ "--session-key",
110
+ agentSessionKey
111
+ ],
112
+ process.cwd(),
113
+ { RIVUS_DEPLOYMENT_STATE_DIR: deploymentStateDirectory }
114
+ ),
115
+ sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))
116
+ },
117
+ expectedDriveContentMarker: marker,
118
+ expectedDriveTitle,
119
+ prompt,
120
+ sessionKey
121
+ }).finally(() => rm(deploymentStateDirectory, { force: true, recursive: true }));
122
+
123
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
124
+
125
+ async function fetchLangfuseJson(endpoint, deadlineAt) {
126
+ const remainingMs = new Date(deadlineAt).getTime() - Date.now();
127
+ if (!Number.isFinite(remainingMs) || remainingMs <= 0) throw new Error("Langfuse request deadline expired");
128
+ const response = await fetch(endpoint, {
129
+ headers: {
130
+ authorization: `Basic ${Buffer.from(`${publicKey}:${secretKey}`).toString("base64")}`
131
+ },
132
+ signal: AbortSignal.timeout(Math.min(remainingMs, 10_000))
133
+ });
134
+ if (!response.ok) throw new Error(`Langfuse request failed with HTTP ${response.status}`);
135
+ return response.json();
136
+ }
137
+
138
+ async function command(executable, args, cwd = process.cwd(), extraEnv = {}) {
139
+ const { stdout } = await execFileAsync(executable, args, {
140
+ cwd,
141
+ env: {
142
+ ...process.env,
143
+ ...extraEnv,
144
+ LARKSUITE_CLI_NO_SKILLS_NOTIFIER: "1",
145
+ LARKSUITE_CLI_NO_UPDATE_NOTIFIER: "1"
146
+ },
147
+ maxBuffer: 2 * 1024 * 1024,
148
+ timeout: 300_000
149
+ });
150
+ return stdout;
151
+ }
152
+
153
+ function requiredEnv(name) {
154
+ const value = process.env[name]?.trim();
155
+ if (!value) throw new Error(`${name} is required`);
156
+ return value;
157
+ }
158
+
159
+ function isObservation(value) {
160
+ return isTraceObservation(value) && typeof value.traceId === "string" && typeof value.sessionId === "string";
161
+ }
162
+
163
+ function isTraceObservation(value) {
164
+ return (
165
+ isRecord(value) &&
166
+ typeof value.id === "string" &&
167
+ typeof value.name === "string" &&
168
+ typeof value.startTime === "string" &&
169
+ typeof value.type === "string"
170
+ );
171
+ }
172
+
173
+ function isRecord(value) {
174
+ return value !== null && typeof value === "object" && !Array.isArray(value);
175
+ }