@intelli-1113/stratos-sdk 1.3.4 → 1.3.6

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,115 @@
1
+ import spawn from "cross-spawn";
2
+ import { setTimeout as sleep } from "node:timers/promises";
3
+
4
+ export class OtelBridgeHarness {
5
+ constructor(opts = {}) {
6
+ this.token = opts.token || process.env.STRATOS_TOKEN;
7
+ this.url = opts.url || process.env.STRATOS_URL || "http://localhost:4000";
8
+ this.surface = opts.surface || process.env.STRATOS_SURFACE || "unknown";
9
+ this.port = opts.port || 4318;
10
+ this.child = null;
11
+ this.restartCount = 0;
12
+ this.maxRestarts = 5;
13
+ this.restartDelay = 1000;
14
+ }
15
+
16
+ start() {
17
+ if (this.child) {
18
+ console.warn("[otel-bridge-harness] Already running");
19
+ return;
20
+ }
21
+
22
+ const env = {
23
+ ...process.env,
24
+ STRATOS_TOKEN: this.token,
25
+ STRATOS_URL: this.url,
26
+ STRATOS_SURFACE: this.surface,
27
+ OTEL_EXPORTER_OTLP_PORT: String(this.port),
28
+ };
29
+
30
+ this.child = spawn("node", [require.resolve("./otel-bridge.js")], {
31
+ stdio: ["ignore", "pipe", "pipe"],
32
+ env,
33
+ detached: false,
34
+ });
35
+
36
+ this.child.stdout.on("data", (data) => {
37
+ console.log(`[otel-bridge] ${data.toString().trim()}`);
38
+ });
39
+
40
+ this.child.stderr.on("data", (data) => {
41
+ console.error(`[otel-bridge] ${data.toString().trim()}`);
42
+ });
43
+
44
+ this.child.on("error", (err) => {
45
+ console.error(`[otel-bridge-harness] Error: ${err.message}`);
46
+ this.child = null;
47
+ this._scheduleRestart();
48
+ });
49
+
50
+ this.child.on("exit", (code) => {
51
+ console.warn(`[otel-bridge-harness] Bridge exited (code: ${code})`);
52
+ this.child = null;
53
+ if (code !== 0) {
54
+ this._scheduleRestart();
55
+ }
56
+ });
57
+
58
+ console.log(`[otel-bridge-harness] Started (PID: ${this.child.pid})`);
59
+ }
60
+
61
+ async _scheduleRestart() {
62
+ if (this.restartCount >= this.maxRestarts) {
63
+ console.error("[otel-bridge-harness] Max restarts exceeded. Giving up.");
64
+ return;
65
+ }
66
+
67
+ this.restartCount++;
68
+ const delay = this.restartDelay * Math.pow(2, this.restartCount - 1);
69
+ console.log(`[otel-bridge-harness] Restarting in ${delay}ms (attempt ${this.restartCount}/${this.maxRestarts})`);
70
+
71
+ await sleep(delay);
72
+ this.start();
73
+ }
74
+
75
+ stop() {
76
+ if (!this.child) return;
77
+
78
+ console.log("[otel-bridge-harness] Stopping...");
79
+ this.child.kill("SIGTERM");
80
+
81
+ setTimeout(() => {
82
+ if (this.child && !this.child.killed) {
83
+ console.warn("[otel-bridge-harness] Force killing after timeout");
84
+ this.child.kill("SIGKILL");
85
+ }
86
+ }, 2000);
87
+
88
+ this.child = null;
89
+ }
90
+
91
+ isRunning() {
92
+ return this.child != null && !this.child.killed;
93
+ }
94
+
95
+ getPid() {
96
+ return this.child?.pid || null;
97
+ }
98
+ }
99
+
100
+ export async function startOtelBridge(opts = {}) {
101
+ const harness = new OtelBridgeHarness(opts);
102
+ harness.start();
103
+
104
+ process.on("SIGINT", () => {
105
+ harness.stop();
106
+ process.exit(0);
107
+ });
108
+
109
+ process.on("SIGTERM", () => {
110
+ harness.stop();
111
+ process.exit(0);
112
+ });
113
+
114
+ return harness;
115
+ }
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+
3
+ import http from "node:http";
4
+ import { randomBytes } from "node:crypto";
5
+
6
+ const TOKEN = process.env.STRATOS_TOKEN || "";
7
+ const STRATOS_URL = process.env.STRATOS_URL || "http://localhost:4000";
8
+ const PORT = process.env.OTEL_EXPORTER_OTLP_PORT || 4318;
9
+ const SURFACE = process.env.STRATOS_SURFACE || "unknown";
10
+
11
+ if (!TOKEN) {
12
+ console.warn("[otel-bridge] Warning: STRATOS_TOKEN not set. Telemetry will not be forwarded.");
13
+ }
14
+
15
+ const hdr = { "Content-Type": "application/json", "x-stratos-token": TOKEN };
16
+
17
+ function redactContent(obj) {
18
+ if (!obj || typeof obj !== "object") return obj;
19
+ if (Array.isArray(obj)) return obj.map(redactContent);
20
+ const copy = { ...obj };
21
+ for (const key of Object.keys(copy)) {
22
+ if (key.includes("prompt") || key.includes("request") || key.includes("response") || key.includes("content")) {
23
+ copy[key] = null;
24
+ } else if (typeof copy[key] === "object") {
25
+ copy[key] = redactContent(copy[key]);
26
+ }
27
+ }
28
+ return copy;
29
+ }
30
+
31
+ function otlpToStratosSpan(otlpMetric) {
32
+ const traceId = (randomBytes(16)).toString("hex");
33
+ const spanId = (randomBytes(8)).toString("hex");
34
+ const now = BigInt(Date.now()) * 1000000n;
35
+
36
+ const attributes = otlpMetric.attributes || [];
37
+ const attrMap = {};
38
+ for (const attr of attributes) {
39
+ attrMap[attr.key] = attr.value?.stringValue || attr.value?.doubleValue || String(attr.value);
40
+ }
41
+
42
+ const redacted = redactContent(attrMap);
43
+
44
+ return {
45
+ traceId,
46
+ spanId,
47
+ name: `${SURFACE}.otel.usage`,
48
+ startTimeUnixNano: now.toString(),
49
+ endTimeUnixNano: now.toString(),
50
+ attributes: [
51
+ { key: "traceloop.span.kind", value: { stringValue: "llm" } },
52
+ { key: "stratos.surface", value: { stringValue: SURFACE } },
53
+ { key: "otel.bridge", value: { stringValue: "true" } },
54
+ ...Object.entries(redacted).map(([k, v]) => ({
55
+ key: `otel.${k}`,
56
+ value: { stringValue: String(v) },
57
+ })),
58
+ ],
59
+ };
60
+ }
61
+
62
+ function buildStratosPayload(otlpSpans) {
63
+ const attr = (k, v) => ({ key: k, value: { stringValue: String(v) } });
64
+ return {
65
+ resourceSpans: [
66
+ {
67
+ resource: { attributes: [attr("service.name", SURFACE)] },
68
+ scopeSpans: [
69
+ {
70
+ spans: otlpSpans.map((span) => ({
71
+ traceId: span.traceId,
72
+ spanId: span.spanId,
73
+ name: span.name,
74
+ startTimeUnixNano: span.startTimeUnixNano,
75
+ endTimeUnixNano: span.endTimeUnixNano,
76
+ attributes: span.attributes,
77
+ })),
78
+ },
79
+ ],
80
+ },
81
+ ],
82
+ };
83
+ }
84
+
85
+ const server = http.createServer(async (req, res) => {
86
+ const { method, url } = req;
87
+
88
+ if (url === "/v1/traces" && method === "POST") {
89
+ let body = "";
90
+ req.on("data", (chunk) => {
91
+ body += chunk.toString();
92
+ });
93
+
94
+ req.on("end", async () => {
95
+ try {
96
+ const otlpPayload = JSON.parse(body);
97
+ const otlpSpans = [];
98
+
99
+ for (const resourceSpan of otlpPayload.resourceSpans || []) {
100
+ for (const scopeSpan of resourceSpan.scopeSpans || []) {
101
+ for (const span of scopeSpan.spans || []) {
102
+ otlpSpans.push(otlpToStratosSpan(span));
103
+ }
104
+ }
105
+ }
106
+
107
+ if (otlpSpans.length > 0 && TOKEN) {
108
+ const stratosPayload = buildStratosPayload(otlpSpans);
109
+ try {
110
+ const fres = await fetch(`${STRATOS_URL}/api/ingest`, {
111
+ method: "POST",
112
+ headers: hdr,
113
+ body: JSON.stringify(stratosPayload),
114
+ });
115
+ if (!fres.ok) {
116
+ console.error(`[otel-bridge] Stratos returned ${fres.status}`);
117
+ }
118
+ } catch (err) {
119
+ console.error(`[otel-bridge] Failed to forward to Stratos: ${err.message}`);
120
+ }
121
+ }
122
+
123
+ res.writeHead(200, { "Content-Type": "application/json" });
124
+ res.end(JSON.stringify({}));
125
+ } catch (err) {
126
+ console.error(`[otel-bridge] Parse error: ${err.message}`);
127
+ res.writeHead(400, { "Content-Type": "application/json" });
128
+ res.end(JSON.stringify({ error: "parse error" }));
129
+ }
130
+ });
131
+ return;
132
+ }
133
+
134
+ if (url === "/v1/metrics" && method === "POST") {
135
+ req.on("data", () => {}); // Drain
136
+ req.on("end", () => {
137
+ res.writeHead(200, { "Content-Type": "application/json" });
138
+ res.end(JSON.stringify({}));
139
+ });
140
+ return;
141
+ }
142
+
143
+ if (url === "/health" && method === "GET") {
144
+ res.writeHead(200, { "Content-Type": "application/json" });
145
+ res.end(JSON.stringify({ status: "ok", surface: SURFACE }));
146
+ return;
147
+ }
148
+
149
+ res.writeHead(404, { "Content-Type": "application/json" });
150
+ res.end(JSON.stringify({ error: "not found" }));
151
+ });
152
+
153
+ server.listen(PORT, "localhost", () => {
154
+ console.log(
155
+ `[otel-bridge] listening on http://localhost:${PORT}` +
156
+ (TOKEN ? ` (forwarding to ${STRATOS_URL})` : " (token not set; telemetry disabled)")
157
+ );
158
+ console.log(`[otel-bridge] surface: ${SURFACE}`);
159
+ console.log(`[otel-bridge] Set OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:${PORT} in your app`);
160
+ });
161
+
162
+ process.on("SIGINT", () => {
163
+ console.log("[otel-bridge] shutting down");
164
+ server.close(() => process.exit(0));
165
+ });
166
+
167
+ process.on("SIGTERM", () => {
168
+ console.log("[otel-bridge] shutting down");
169
+ server.close(() => process.exit(0));
170
+ });
@@ -6,6 +6,16 @@
6
6
  // (not node_modules/@intelli-1113/stratos-sdk).
7
7
 
8
8
  import { inspectLangChain, printInspectReport } from "./inspect-langchain.js";
9
+ import { readFileSync } from "node:fs";
10
+
11
+ function getSDKVersion() {
12
+ try {
13
+ const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
14
+ return pkg.version;
15
+ } catch {
16
+ return "latest";
17
+ }
18
+ }
9
19
 
10
20
  try {
11
21
  if (process.env.STRATOS_SKIP_POSTINSTALL === "1") process.exit(0);
@@ -13,17 +23,28 @@ try {
13
23
  // Prefer the consumer project root
14
24
  const cwd = process.env.INIT_CWD || process.cwd();
15
25
  const report = inspectLangChain({ cwd });
16
- if (!report.hasLangChain) process.exit(0);
17
- if (report.compatible) process.exit(0);
18
-
19
- console.warn(
20
- "\n[stratos] ⚠ LangChain version is not compatible with Stratos auto-metrics."
21
- );
22
- printInspectReport(report);
23
- console.warn(
24
- "[stratos] In your project, run:\n npx stratos-inspect --fix\n"
25
- );
26
+
27
+ // Show LangChain warnings if needed
28
+ if (report.hasLangChain && !report.compatible) {
29
+ console.warn(
30
+ "\n[stratos] ⚠ LangChain version is not compatible with Stratos auto-metrics."
31
+ );
32
+ printInspectReport(report);
33
+ console.warn(
34
+ "[stratos] In your project, run:\n npx stratos-inspect --fix\n"
35
+ );
36
+ }
26
37
  } catch {
27
38
  // Never fail the host install
28
- process.exit(0);
29
39
  }
40
+
41
+ // Always show welcome message
42
+ const version = getSDKVersion();
43
+ console.log(`
44
+ ✨ Welcome to Stratos SDK v${version}!
45
+
46
+ Get started:
47
+ → Run: stratos --help
48
+
49
+ This will show all available commands and a 4-step integration guide.
50
+ `);