langprotect-armor 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/.env.example ADDED
@@ -0,0 +1,27 @@
1
+ # ── OpenAI ─────────────────────────────────────────────────────────────────────
2
+ OPENAI_API_KEY=sk-...
3
+
4
+ # ── Langfuse tracing (required) ────────────────────────────────────────────────
5
+ # Project keys from your Langfuse instance. LANGFUSE_BASEURL points at your
6
+ # self-hosted LangProtect/Langfuse URL (defaults to https://cloud.langfuse.com).
7
+ LANGFUSE_PUBLIC_KEY=pk-lf-...
8
+ LANGFUSE_SECRET_KEY=sk-lf-...
9
+ LANGFUSE_BASEURL=http://localhost:3000
10
+
11
+ # ── Armour security (optional) ─────────────────────────────────────────────────
12
+ # Set these to enable Armour input/output scanning via setSecurity(true).
13
+ ARMOUR_BASE_URL=https://your-armour-instance.run.app
14
+ ARMOUR_API_KEY=your-armour-api-key
15
+ # true = use sanitized_prompt when available before blocking;
16
+ # false = block immediately on any non-safe status.
17
+ ARMOUR_SECURITY_ON=true
18
+ # true = log only, skip security checks; false = full scanning.
19
+ ARMOUR_TRACE_ONLY=false
20
+ # Armour scan HTTP timeout, in seconds.
21
+ ARMOUR_SCAN_TIMEOUT=60
22
+
23
+ # ── LangProtect dashboard (used by score()) ────────────────────────────────────
24
+ LANGPROTECT_HOST=http://localhost:8000
25
+
26
+ # ── LiteLLM proxy (optional — required for non-OpenAI models) ──────────────────
27
+ LITELLM_HOST=http://localhost:4000
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Quokka Labs LLP
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,283 @@
1
+ # LangProtect TypeScript SDK
2
+
3
+ Self-hosted LLM security and observability SDK. Every model call is scanned by Armour for threats and PII, routed to the right provider, and linked to the LangProtect dashboard — all with a single import change.
4
+
5
+ ---
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install langprotect-armor
11
+ ```
12
+
13
+ ---
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import { init, openai } from "langprotect-armor/langfuse";
19
+
20
+ init({
21
+ openaiApiKey: "sk-...",
22
+ langfusePublicKey: "pk-lf-...",
23
+ langfuseSecretKey: "sk-lf-...",
24
+ langfuseHost: "http://localhost:3000",
25
+ armourBaseUrl: "https://langprotect-xxx.run.app",
26
+ armourApiKey: "your-armour-key",
27
+ securityOn: true,
28
+ });
29
+
30
+ const response = await openai.chat.completions.create({
31
+ model: "gpt-4o-mini",
32
+ messages: [{ role: "user", content: "Hello!" }],
33
+ sessionId: "sess_001",
34
+ userId: "user_42",
35
+ });
36
+
37
+ console.log(response.choices[0].message.content);
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Configuration
43
+
44
+ All configuration is passed to `init()` — no `.env` file required. `init()` can be called at any point before the first `openai.chat.completions.create()` call.
45
+
46
+ ```typescript
47
+ import { init } from "langprotect-armor/langfuse";
48
+
49
+ init({
50
+ // LLM provider
51
+ openaiApiKey: "sk-...",
52
+
53
+ // Langfuse tracing
54
+ langfusePublicKey: "pk-lf-...",
55
+ langfuseSecretKey: "sk-lf-...",
56
+ langfuseHost: "http://localhost:3000", // defaults to Langfuse Cloud if unset
57
+
58
+ // Armour security scanning
59
+ armourBaseUrl: "https://langprotect-xxx.run.app",
60
+ armourApiKey: "your-armour-key",
61
+ securityOn: false, // true → sanitize/block unsafe input
62
+ // false → scanning disabled entirely
63
+ traceOnly: false, // true → log only, never enforce
64
+ scanTimeout: 60, // Armour HTTP timeout in seconds
65
+
66
+ // LangProtect dashboard (used by score())
67
+ langprotectHost: "http://localhost:8000",
68
+
69
+ // LiteLLM proxy (optional — enables non-OpenAI models)
70
+ litellmHost: "", // e.g. "http://localhost:4000"
71
+ });
72
+ ```
73
+
74
+ | Parameter | Default | Description |
75
+ |---|---|---|
76
+ | `openaiApiKey` | `""` | OpenAI API key |
77
+ | `langfusePublicKey` | `""` | Langfuse public key (`pk-lf-…`) |
78
+ | `langfuseSecretKey` | `""` | Langfuse secret key (`sk-lf-…`) |
79
+ | `langfuseHost` | `""` | Langfuse base URL (defaults to Langfuse Cloud if unset) |
80
+ | `armourBaseUrl` | `""` | Armour API base URL |
81
+ | `armourApiKey` | `""` | Armour `X-API-Key` header |
82
+ | `securityOn` | `false` | Enforce sanitization / blocking |
83
+ | `traceOnly` | `false` | Log only — never block or sanitize |
84
+ | `scanTimeout` | `60` | Armour HTTP timeout (seconds) |
85
+ | `langprotectHost` | `http://localhost:8000` | Dashboard URL for `score()` |
86
+ | `litellmHost` | `""` | LiteLLM proxy URL for non-OpenAI models |
87
+
88
+ > Values passed to `init()` override environment variables. Config is read lazily on each call so order of import vs. `init()` does not matter.
89
+
90
+ ---
91
+
92
+ ## Usage
93
+
94
+ ### Chat completions
95
+
96
+ ```typescript
97
+ import { openai } from "langprotect-armor/langfuse";
98
+
99
+ const response = await openai.chat.completions.create({
100
+ model: "gpt-4o",
101
+ messages: [{ role: "user", content: "Explain async/await" }],
102
+ sessionId: "sess_123",
103
+ userId: "u_42",
104
+ metadata: { turn_number: 1 },
105
+ });
106
+
107
+ console.log(response.choices[0].message.content);
108
+ ```
109
+
110
+ ### Handling blocked responses
111
+
112
+ When `securityOn: true` and Armour hard-blocks a request, a `BlockedResponse` is returned instead of a normal completion. Use the `in` operator to narrow the type:
113
+
114
+ ```typescript
115
+ import { openai, BlockedResponse } from "langprotect-armor/langfuse";
116
+
117
+ const response = await openai.chat.completions.create({ ... });
118
+
119
+ if ("blocked" in response) {
120
+ console.log(response.content); // "This response has been blocked by security policy."
121
+ } else {
122
+ console.log(response.choices[0].message.content);
123
+ }
124
+ ```
125
+
126
+ ### Toggling security at runtime
127
+
128
+ ```typescript
129
+ import { setSecurity } from "langprotect-armor/langfuse";
130
+
131
+ setSecurity(true); // Armour ON
132
+ setSecurity(false); // Armour OFF
133
+ ```
134
+
135
+ ### Feedback scoring
136
+
137
+ ```typescript
138
+ import { score } from "langprotect-armor/langfuse";
139
+
140
+ await score("trace-id-abc", 1, "Correct and concise");
141
+ await score("trace-id-abc", -1);
142
+ ```
143
+
144
+ `value` is `1` (positive) or `-1` (negative). Scores are posted to `{langprotectHost}/api/feedback`.
145
+
146
+ ### Supported call parameters
147
+
148
+ These are stripped before the OpenAI call and used for metadata only:
149
+
150
+ | Parameter | Type | Description |
151
+ |---|---|---|
152
+ | `sessionId` | `string` | Groups all turns of a conversation |
153
+ | `userId` | `string` | Identifies the end user |
154
+ | `name` | `string` | Trace label |
155
+ | `metadata` | `object` | Any JSON key-value context |
156
+ | `traceId` | `string` | Override the auto-generated UUID |
157
+ | `tags` | `string[]` | Labels for filtering |
158
+
159
+ ---
160
+
161
+ ## Security Scanning
162
+
163
+ The SDK calls the Armour `/v1/scan` endpoint on every input and output.
164
+
165
+ ### Scan modes
166
+
167
+ | `traceOnly` | `securityOn` | Behaviour |
168
+ |---|---|---|
169
+ | `true` | any | Log the scan result, always proceed |
170
+ | `false` | `false` | Block if input is not safe, no sanitization |
171
+ | `false` | `true` | Sanitize if possible, block otherwise |
172
+
173
+ ### Anonymization
174
+
175
+ When Armour returns a `sanitized_prompt` (PII replaced with tokens like `[PERSON_1]`, `[EMAIL_ADDRESS_1]`), the SDK:
176
+
177
+ 1. Replaces the user message with the sanitized version
178
+ 2. Injects a system prompt hint so the LLM treats tokens as real values
179
+ 3. On the output scan, restores the original values via `sanitized_output` transparently
180
+
181
+ The caller always receives the deanonymized response.
182
+
183
+ ---
184
+
185
+ ## Model Routing
186
+
187
+ The SDK detects the provider from the model name and routes accordingly:
188
+
189
+ | Model prefix | Provider | Route |
190
+ |---|---|---|
191
+ | `gpt-`, `o1`, `o3`, `text-davinci`, `text-embedding` | OpenAI | Direct to OpenAI API |
192
+ | `claude`, `anthropic` | Anthropic | Via LiteLLM proxy |
193
+ | `gemini`, `palm` | Google | Via LiteLLM proxy |
194
+ | `llama`, `mistral`, `mixtral`, `codellama`, `phi`, `qwen` | Ollama | Via LiteLLM proxy |
195
+ | anything else | LiteLLM | Via LiteLLM proxy |
196
+
197
+ Non-OpenAI models require `litellmHost` to be set.
198
+
199
+ ### Starting LiteLLM
200
+
201
+ ```bash
202
+ litellm --model claude-3-5-sonnet-20241022 --port 4000
203
+ ```
204
+
205
+ Then in `init()`:
206
+
207
+ ```typescript
208
+ init({
209
+ ...
210
+ litellmHost: "http://localhost:4000",
211
+ });
212
+ ```
213
+
214
+ ### Adding a new model via config
215
+
216
+ ```yaml
217
+ # litellm_config.yaml
218
+ - model_name: phi3
219
+ litellm_params:
220
+ model: ollama/phi3
221
+ api_base: http://localhost:11434
222
+ ```
223
+
224
+ ```bash
225
+ ollama pull phi3
226
+ litellm --config litellm_config.yaml --port 4000
227
+ ```
228
+
229
+ ---
230
+
231
+ ## Data Flow
232
+
233
+ ```
234
+ Your App
235
+ openai.chat.completions.create({ model: "gpt-4o", messages: [...] })
236
+
237
+
238
+ langprotect SDK
239
+ 1. Armour input scan → sanitize / block / log
240
+ 2. LLM call → OpenAI direct or LiteLLM proxy
241
+ 3. Armour output scan → deanonymize + eval trigger
242
+
243
+
244
+ Response returned to caller
245
+ (eval runs as background task on LangProtect backend)
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Build
251
+
252
+ ```bash
253
+ npm run build # compile TypeScript to dist/
254
+ npm run dev # watch mode
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Troubleshooting
260
+
261
+ **`blocked` check not working**
262
+
263
+ Use the `in` operator to type-narrow correctly:
264
+
265
+ ```typescript
266
+ // Wrong — TypeScript will complain
267
+ if (response.blocked) { ... }
268
+
269
+ // Correct — narrows the else branch to a normal completion
270
+ if ("blocked" in response) { ... }
271
+ ```
272
+
273
+ **Non-OpenAI model fails**
274
+
275
+ Set `litellmHost` in `init()` and ensure the LiteLLM proxy is running with the model configured.
276
+
277
+ **Armour scan times out**
278
+
279
+ Increase `scanTimeout` in `init()` (default 60 s). Check that `armourBaseUrl` is reachable from your environment.
280
+
281
+ **Module not found: `langprotect-armor/langfuse`**
282
+
283
+ Run `npm run build` first to compile TypeScript to `dist/`, then the package exports map resolves correctly.
@@ -0,0 +1,41 @@
1
+ /**
2
+ * langprotect-armor/langfuse/armour — Armour security scanning.
3
+ *
4
+ * All config is read lazily via getConfig() on each call, so init() values
5
+ * take effect immediately without requiring a specific import order.
6
+ *
7
+ * Environment variables (overridden by init()):
8
+ * ARMOUR_BASE_URL — Armour API base URL
9
+ * ARMOUR_API_KEY — Armour API key (X-API-Key header)
10
+ * ARMOUR_TRACE_ONLY — "true" = log only, skip security checks
11
+ * ARMOUR_SECURITY_ON — "true" = enable full Armour scanning
12
+ * ARMOUR_SCAN_TIMEOUT — scan HTTP timeout in seconds (default 60)
13
+ */
14
+ export declare function setSecurity(enabled: boolean): void;
15
+ export declare function isSecurityOn(): boolean;
16
+ export declare function isTraceOnly(): boolean;
17
+ export declare class BlockedResponse {
18
+ readonly blocked = true;
19
+ readonly content: string;
20
+ readonly model: string;
21
+ readonly usage: {
22
+ prompt_tokens: number;
23
+ completion_tokens: number;
24
+ total_tokens: number;
25
+ };
26
+ constructor(model?: string);
27
+ }
28
+ export interface ArmourScanResult {
29
+ status: string;
30
+ risk_score?: number;
31
+ request_id?: string;
32
+ sanitized_prompt?: string;
33
+ sanitized_output?: string;
34
+ }
35
+ export declare function callArmourScan(userInput: string, sessionId: string, userId: string, options?: {
36
+ aiResponse?: string;
37
+ scanType?: "input" | "output";
38
+ traceId?: string;
39
+ evalTrace?: Record<string, unknown>;
40
+ }): Promise<ArmourScanResult>;
41
+ //# sourceMappingURL=armour.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"armour.d.ts","sourceRoot":"","sources":["../../src/langfuse/armour.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAElD;AAED,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED,qBAAa,eAAe;IAC1B,QAAQ,CAAC,OAAO,QAAQ;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;gBAE/E,KAAK,SAAK;CAKvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAa,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAQ,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAQ,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAG,MAAM,EAClB,SAAS,EAAG,MAAM,EAClB,MAAM,EAAM,MAAM,EAClB,OAAO,GAAE;IACP,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAI,OAAO,GAAG,QAAQ,CAAC;IAChC,OAAO,CAAC,EAAK,MAAM,CAAC;IACpB,SAAS,CAAC,EAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,GACL,OAAO,CAAC,gBAAgB,CAAC,CA4C3B"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * langprotect-armor/langfuse/armour — Armour security scanning.
3
+ *
4
+ * All config is read lazily via getConfig() on each call, so init() values
5
+ * take effect immediately without requiring a specific import order.
6
+ *
7
+ * Environment variables (overridden by init()):
8
+ * ARMOUR_BASE_URL — Armour API base URL
9
+ * ARMOUR_API_KEY — Armour API key (X-API-Key header)
10
+ * ARMOUR_TRACE_ONLY — "true" = log only, skip security checks
11
+ * ARMOUR_SECURITY_ON — "true" = enable full Armour scanning
12
+ * ARMOUR_SCAN_TIMEOUT — scan HTTP timeout in seconds (default 60)
13
+ */
14
+ import { getConfig } from "./config.js";
15
+ const _BLOCKED_MESSAGE = "This response has been blocked by security policy.";
16
+ // Runtime toggle — setSecurity() writes here; takes priority over getConfig().securityOn.
17
+ let _securityOnOverride = null;
18
+ export function setSecurity(enabled) {
19
+ _securityOnOverride = enabled;
20
+ }
21
+ export function isSecurityOn() {
22
+ return _securityOnOverride !== null ? _securityOnOverride : getConfig().securityOn;
23
+ }
24
+ export function isTraceOnly() {
25
+ return getConfig().traceOnly;
26
+ }
27
+ export class BlockedResponse {
28
+ blocked = true;
29
+ content;
30
+ model;
31
+ usage;
32
+ constructor(model = "") {
33
+ this.content = _BLOCKED_MESSAGE;
34
+ this.model = model;
35
+ this.usage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
36
+ }
37
+ }
38
+ export async function callArmourScan(userInput, sessionId, userId, options = {}) {
39
+ const { aiResponse = "", scanType = "input", traceId = "", evalTrace } = options;
40
+ const { armourBaseUrl, armourApiKey, traceOnly, scanTimeout } = getConfig();
41
+ if (!armourBaseUrl || !armourApiKey) {
42
+ return { status: "safe" };
43
+ }
44
+ try {
45
+ const payload = {
46
+ user_input: userInput,
47
+ ai_response: aiResponse,
48
+ end_user_id: userId,
49
+ session_id: sessionId,
50
+ trace_only: traceOnly,
51
+ trace: traceId,
52
+ };
53
+ if (evalTrace !== undefined) {
54
+ payload.eval_trace = evalTrace;
55
+ }
56
+ const response = await fetch(`${armourBaseUrl}/v1/scan`, {
57
+ method: "POST",
58
+ headers: {
59
+ "Content-Type": "application/json",
60
+ "X-API-Key": armourApiKey,
61
+ },
62
+ body: JSON.stringify(payload),
63
+ signal: AbortSignal.timeout(scanTimeout * 1000),
64
+ });
65
+ const result = await response.json();
66
+ console.log(`[Armour] ${scanType} scan — ` +
67
+ `status: ${result.status} | ` +
68
+ `risk_score: ${result.risk_score} | ` +
69
+ `sanitized_prompt: ${result.sanitized_prompt} | ` +
70
+ `sanitized_output: ${result.sanitized_output}`);
71
+ return result;
72
+ }
73
+ catch (e) {
74
+ console.log(`[Armour] ${scanType} scan — exception: ${e}`);
75
+ return { status: "safe" }; // fail-open
76
+ }
77
+ }
78
+ //# sourceMappingURL=armour.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"armour.js","sourceRoot":"","sources":["../../src/langfuse/armour.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * langprotect-armor/langfuse/config — SDK configuration.
3
+ *
4
+ * Stores values set via init() and merges them on top of environment-variable
5
+ * defaults every time getConfig() is called, so init() values always win and
6
+ * take effect immediately without requiring a specific import order.
7
+ */
8
+ export interface LangProtectConfig {
9
+ openaiApiKey?: string;
10
+ /** Langfuse project public key (pk-lf-…). Also read from LANGFUSE_PUBLIC_KEY. */
11
+ langfusePublicKey?: string;
12
+ /** Langfuse project secret key (sk-lf-…). Also read from LANGFUSE_SECRET_KEY. */
13
+ langfuseSecretKey?: string;
14
+ /**
15
+ * Langfuse base URL. Defaults to https://cloud.langfuse.com.
16
+ * Set to your self-hosted URL when running LangProtect locally.
17
+ * Also read from LANGFUSE_BASEURL (JS) / LANGFUSE_HOST (Python parity).
18
+ */
19
+ langfuseHost?: string;
20
+ armourBaseUrl?: string;
21
+ armourApiKey?: string;
22
+ /** Enable Armour enforcement — sanitize or block unsafe input (default false). */
23
+ securityOn?: boolean;
24
+ /** Log-only mode — scans run but never block or sanitize (default false). */
25
+ traceOnly?: boolean;
26
+ /** Armour scan HTTP timeout in seconds (default 60). */
27
+ scanTimeout?: number;
28
+ /** LangProtect dashboard URL — used by score() (default http://localhost:8000). */
29
+ langprotectHost?: string;
30
+ /** LiteLLM proxy URL — enables non-OpenAI routing when set. */
31
+ litellmHost?: string;
32
+ }
33
+ type ResolvedConfig = Required<LangProtectConfig>;
34
+ /** Returns the current effective config (env defaults merged with init() overrides). */
35
+ export declare function getConfig(): ResolvedConfig;
36
+ /**
37
+ * Configure the LangProtect SDK programmatically.
38
+ *
39
+ * Call this once at application startup, before any LLM calls. Values passed
40
+ * here override environment variables and take effect immediately because all
41
+ * SDK modules read config lazily via getConfig().
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * import { init, openai } from "langprotect-armor/langfuse";
46
+ *
47
+ * init({
48
+ * openaiApiKey: "sk-...",
49
+ * langfusePublicKey: "pk-lf-...",
50
+ * langfuseSecretKey: "sk-lf-...",
51
+ * langfuseHost: "https://cloud.langfuse.com",
52
+ * armourBaseUrl: "https://langprotect-xxx.run.app",
53
+ * armourApiKey: "sk-armour-...",
54
+ * securityOn: true,
55
+ * });
56
+ * ```
57
+ */
58
+ export declare function init(cfg: LangProtectConfig): void;
59
+ export {};
60
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/langfuse/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAG,MAAM,CAAC;IACvB,kFAAkF;IAClF,UAAU,CAAC,EAAK,OAAO,CAAC;IACxB,6EAA6E;IAC7E,SAAS,CAAC,EAAM,OAAO,CAAC;IACxB,wDAAwD;IACxD,WAAW,CAAC,EAAI,MAAM,CAAC;IACvB,mFAAmF;IACnF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,KAAK,cAAc,GAAG,QAAQ,CAAC,iBAAiB,CAAC,CAAC;AAuBlD,wFAAwF;AACxF,wBAAgB,SAAS,IAAI,cAAc,CAQ1C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI,CAgBjD"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * langprotect-armor/langfuse/config — SDK configuration.
3
+ *
4
+ * Stores values set via init() and merges them on top of environment-variable
5
+ * defaults every time getConfig() is called, so init() values always win and
6
+ * take effect immediately without requiring a specific import order.
7
+ */
8
+ // Values explicitly set via init() — merged on top of env-var defaults.
9
+ let _override = {};
10
+ function _fromEnv() {
11
+ return {
12
+ openaiApiKey: process.env.OPENAI_API_KEY ?? "",
13
+ langfusePublicKey: process.env.LANGFUSE_PUBLIC_KEY ?? "",
14
+ langfuseSecretKey: process.env.LANGFUSE_SECRET_KEY ?? "",
15
+ langfuseHost: (process.env.LANGFUSE_BASEURL ?? process.env.LANGFUSE_HOST ?? "").replace(/\/$/, ""),
16
+ armourBaseUrl: (process.env.ARMOUR_BASE_URL ?? "").replace(/\/$/, ""),
17
+ armourApiKey: process.env.ARMOUR_API_KEY ?? "",
18
+ securityOn: (process.env.ARMOUR_SECURITY_ON ?? "false").toLowerCase() === "true",
19
+ traceOnly: (process.env.ARMOUR_TRACE_ONLY ?? "false").toLowerCase() === "true",
20
+ scanTimeout: parseInt(process.env.ARMOUR_SCAN_TIMEOUT ?? "60", 10),
21
+ langprotectHost: process.env.LANGPROTECT_HOST ?? "http://localhost:8000",
22
+ litellmHost: (process.env.LITELLM_HOST ?? "").trim(),
23
+ };
24
+ }
25
+ const _stripSlash = (v) => v.replace(/\/$/, "");
26
+ /** Returns the current effective config (env defaults merged with init() overrides). */
27
+ export function getConfig() {
28
+ const merged = { ..._fromEnv(), ..._override };
29
+ // Normalize URL fields — init() overrides may carry a trailing slash that would
30
+ // otherwise produce double-slash request paths (e.g. host//v1/scan).
31
+ merged.langfuseHost = _stripSlash(merged.langfuseHost);
32
+ merged.armourBaseUrl = _stripSlash(merged.armourBaseUrl);
33
+ merged.langprotectHost = _stripSlash(merged.langprotectHost);
34
+ return merged;
35
+ }
36
+ /**
37
+ * Configure the LangProtect SDK programmatically.
38
+ *
39
+ * Call this once at application startup, before any LLM calls. Values passed
40
+ * here override environment variables and take effect immediately because all
41
+ * SDK modules read config lazily via getConfig().
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * import { init, openai } from "langprotect-armor/langfuse";
46
+ *
47
+ * init({
48
+ * openaiApiKey: "sk-...",
49
+ * langfusePublicKey: "pk-lf-...",
50
+ * langfuseSecretKey: "sk-lf-...",
51
+ * langfuseHost: "https://cloud.langfuse.com",
52
+ * armourBaseUrl: "https://langprotect-xxx.run.app",
53
+ * armourApiKey: "sk-armour-...",
54
+ * securityOn: true,
55
+ * });
56
+ * ```
57
+ */
58
+ export function init(cfg) {
59
+ _override = { ..._override, ...cfg };
60
+ // Mirror to env vars so that the Langfuse SDK (which reads process.env
61
+ // on client construction) picks up the values set here.
62
+ if (cfg.langfusePublicKey)
63
+ process.env.LANGFUSE_PUBLIC_KEY = cfg.langfusePublicKey;
64
+ if (cfg.langfuseSecretKey)
65
+ process.env.LANGFUSE_SECRET_KEY = cfg.langfuseSecretKey;
66
+ if (cfg.langfuseHost) {
67
+ const host = cfg.langfuseHost.replace(/\/$/, "");
68
+ process.env.LANGFUSE_BASEURL = host;
69
+ process.env.LANGFUSE_HOST = host;
70
+ }
71
+ if (cfg.openaiApiKey)
72
+ process.env.OPENAI_API_KEY = cfg.openaiApiKey;
73
+ if (cfg.armourBaseUrl)
74
+ process.env.ARMOUR_BASE_URL = cfg.armourBaseUrl.replace(/\/$/, "");
75
+ if (cfg.armourApiKey)
76
+ process.env.ARMOUR_API_KEY = cfg.armourApiKey;
77
+ if (cfg.litellmHost !== undefined)
78
+ process.env.LITELLM_HOST = cfg.litellmHost;
79
+ }
80
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/langfuse/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * langprotect-armor/langfuse — public interface for LangProtect SDK.
3
+ *
4
+ * Architecture:
5
+ * User code → LangProtect proxy → observeOpenAI (Langfuse tracing) → OpenAI API
6
+ * ↓
7
+ * Armour input / output scan
8
+ *
9
+ * Langfuse handles full LLM tracing (input, output, tokens, latency, cost).
10
+ * Armour adds security enforcement: PII detection, prompt-injection defence, policy checks.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { init, openai } from "langprotect-armor/langfuse";
15
+ *
16
+ * init({
17
+ * openaiApiKey: "sk-...",
18
+ * langfusePublicKey: "pk-lf-...",
19
+ * langfuseSecretKey: "sk-lf-...",
20
+ * langfuseHost: "https://cloud.langfuse.com",
21
+ * armourBaseUrl: "https://langprotect-xxx.run.app",
22
+ * armourApiKey: "sk-armour-...",
23
+ * securityOn: true,
24
+ * });
25
+ *
26
+ * const response = await openai.chat.completions.create({
27
+ * model: "gpt-4o",
28
+ * messages: [{ role: "user", content: "Hello!" }],
29
+ * sessionId: "sess-123",
30
+ * userId: "user-42",
31
+ * });
32
+ *
33
+ * if ("blocked" in response && response.blocked) {
34
+ * console.log(response.content);
35
+ * }
36
+ * ```
37
+ */
38
+ import type { ChatCompletionCreateParamsNonStreaming, ChatCompletion } from "openai/resources/chat/completions";
39
+ import { BlockedResponse } from "./armour.js";
40
+ export { init } from "./config.js";
41
+ export { setSecurity } from "./armour.js";
42
+ export { score } from "./tracing.js";
43
+ export { BlockedResponse } from "./armour.js";
44
+ type TracingParams = {
45
+ name?: string;
46
+ sessionId?: string;
47
+ userId?: string;
48
+ tags?: string[];
49
+ metadata?: Record<string, unknown>;
50
+ traceId?: string;
51
+ };
52
+ type CreateParams = Omit<ChatCompletionCreateParamsNonStreaming, "stream" | "metadata"> & TracingParams;
53
+ export declare const openai: {
54
+ chat: {
55
+ completions: {
56
+ create(params: CreateParams): Promise<ChatCompletion | BlockedResponse>;
57
+ };
58
+ };
59
+ };
60
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/langfuse/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAKH,OAAO,KAAK,EACV,sCAAsC,EACtC,cAAc,EACf,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EAAkB,eAAe,EAA6B,MAAM,aAAa,CAAC;AAIzF,OAAO,EAAE,IAAI,EAAE,MAAiB,aAAa,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAU,aAAa,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAgB,cAAc,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA0C9C,KAAK,aAAa,GAAG;IACnB,IAAI,CAAC,EAAO,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAK,MAAM,CAAC;IACnB,IAAI,CAAC,EAAO,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,OAAO,CAAC,EAAI,MAAM,CAAC;CACpB,CAAC;AAKF,KAAK,YAAY,GAAG,IAAI,CAAC,sCAAsC,EAAE,QAAQ,GAAG,UAAU,CAAC,GAAG,aAAa,CAAC;AA0JxG,eAAO,MAAM,MAAM;;;2BAvJI,YAAY,GAAG,OAAO,CAAC,cAAc,GAAG,eAAe,CAAC;;;CA2J9E,CAAC"}
@@ -0,0 +1,218 @@
1
+ /**
2
+ * langprotect-armor/langfuse — public interface for LangProtect SDK.
3
+ *
4
+ * Architecture:
5
+ * User code → LangProtect proxy → observeOpenAI (Langfuse tracing) → OpenAI API
6
+ * ↓
7
+ * Armour input / output scan
8
+ *
9
+ * Langfuse handles full LLM tracing (input, output, tokens, latency, cost).
10
+ * Armour adds security enforcement: PII detection, prompt-injection defence, policy checks.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { init, openai } from "langprotect-armor/langfuse";
15
+ *
16
+ * init({
17
+ * openaiApiKey: "sk-...",
18
+ * langfusePublicKey: "pk-lf-...",
19
+ * langfuseSecretKey: "sk-lf-...",
20
+ * langfuseHost: "https://cloud.langfuse.com",
21
+ * armourBaseUrl: "https://langprotect-xxx.run.app",
22
+ * armourApiKey: "sk-armour-...",
23
+ * securityOn: true,
24
+ * });
25
+ *
26
+ * const response = await openai.chat.completions.create({
27
+ * model: "gpt-4o",
28
+ * messages: [{ role: "user", content: "Hello!" }],
29
+ * sessionId: "sess-123",
30
+ * userId: "user-42",
31
+ * });
32
+ *
33
+ * if ("blocked" in response && response.blocked) {
34
+ * console.log(response.content);
35
+ * }
36
+ * ```
37
+ */
38
+ import { randomUUID } from "node:crypto";
39
+ import OpenAI from "openai";
40
+ import { observeOpenAI } from "@langfuse/openai";
41
+ import { callArmourScan, BlockedResponse, isSecurityOn, isTraceOnly } from "./armour.js";
42
+ import { detectProvider, getLitellmClient } from "./routing.js";
43
+ import { getConfig } from "./config.js";
44
+ export { init } from "./config.js";
45
+ export { setSecurity } from "./armour.js";
46
+ export { score } from "./tracing.js";
47
+ export { BlockedResponse } from "./armour.js";
48
+ const _ANONYMIZATION_HINT = "Some values in this conversation have been anonymized with tokens " +
49
+ "such as [PERSON_1], [EMAIL_ADDRESS_1], [PHONE_NUMBER_1], [DATE_TIME_1], etc. " +
50
+ "These tokens represent real values. Respond naturally and reference them " +
51
+ "as-is when needed — do not ask the user to provide the actual values.";
52
+ /**
53
+ * Return the scannable text of a chat message's content.
54
+ *
55
+ * OpenAI message content is either a plain string or, for multimodal input, an
56
+ * array of content parts (text parts plus image parts). We concatenate the text
57
+ * parts so the Armour scan always receives a string. Missing content yields "".
58
+ */
59
+ function extractText(content) {
60
+ if (content == null)
61
+ return "";
62
+ if (typeof content === "string")
63
+ return content;
64
+ if (Array.isArray(content)) {
65
+ return content
66
+ .filter((p) => typeof p === "object" && p !== null && p.type === "text")
67
+ .map(p => p.text)
68
+ .join(" ");
69
+ }
70
+ return String(content);
71
+ }
72
+ // Base OpenAI client — constructed lazily on first use (never at import time) so
73
+ // that init({ openaiApiKey }) can run first. Constructing at module load would
74
+ // throw if OPENAI_API_KEY is not already in the environment. Wrapped per-call
75
+ // with observeOpenAI for Langfuse tracing.
76
+ let _client = null;
77
+ function getClient() {
78
+ if (_client === null) {
79
+ const { openaiApiKey } = getConfig();
80
+ _client = new OpenAI(openaiApiKey ? { apiKey: openaiApiKey } : {});
81
+ }
82
+ return _client;
83
+ }
84
+ const _completions = {
85
+ async create(params) {
86
+ // Streaming is not supported: the SDK needs the full completion to run the
87
+ // Armour output scan and trigger evaluations. The type already excludes
88
+ // `stream`, but guard at runtime for plain-JS callers who bypass the types.
89
+ if (params.stream) {
90
+ throw new Error("langprotect: streaming responses are not supported (stream: true). " +
91
+ "The Armour output scan requires the complete response. Remove `stream` " +
92
+ "or call the OpenAI client directly for streaming.");
93
+ }
94
+ const { name, sessionId = "", userId = "", tags, traceId = "", metadata: rawMetadata, ...openaiParams } = params;
95
+ const model = params.model ?? "";
96
+ const effectiveTraceId = traceId || randomUUID();
97
+ const _startMs = Date.now();
98
+ const metadata = { ...(rawMetadata ?? {}) };
99
+ if (sessionId)
100
+ metadata.session_id ??= sessionId;
101
+ if (userId)
102
+ metadata.user_id ??= userId;
103
+ if (tags)
104
+ metadata.tags ??= tags;
105
+ // ── Armour input scan ──────────────────────────────────────────────────
106
+ const messages = [...(params.messages ?? [])];
107
+ const lastUser = [...messages].reverse().find(m => m.role === "user");
108
+ const userInput = extractText(lastUser?.content);
109
+ const inputScan = await callArmourScan(userInput, sessionId, userId, {
110
+ scanType: "input",
111
+ traceId: effectiveTraceId,
112
+ });
113
+ const sanitized = inputScan.sanitized_prompt;
114
+ Object.assign(metadata, {
115
+ armour_input_status: inputScan.status,
116
+ armour_risk_score: inputScan.risk_score ?? 0,
117
+ armour_request_id: inputScan.request_id,
118
+ armour_sanitized: sanitized !== undefined,
119
+ });
120
+ let usedSanitizedPrompt = false;
121
+ if (!isTraceOnly() && inputScan.status !== "safe") {
122
+ if (isSecurityOn() && sanitized) {
123
+ const idx = [...messages].reverse().findIndex(m => m.role === "user");
124
+ if (idx !== -1) {
125
+ const realIdx = messages.length - 1 - idx;
126
+ messages[realIdx] = { ...messages[realIdx], content: sanitized };
127
+ }
128
+ const sysIdx = messages.findIndex(m => m.role === "system");
129
+ if (sysIdx !== -1) {
130
+ const existing = messages[sysIdx].content ?? "";
131
+ messages[sysIdx] = {
132
+ ...messages[sysIdx],
133
+ content: `${existing}\n\n${_ANONYMIZATION_HINT}`,
134
+ };
135
+ }
136
+ else {
137
+ messages.unshift({ role: "system", content: _ANONYMIZATION_HINT });
138
+ }
139
+ openaiParams.messages = messages;
140
+ usedSanitizedPrompt = true;
141
+ }
142
+ else {
143
+ return new BlockedResponse(model);
144
+ }
145
+ }
146
+ // ── LLM call via observeOpenAI (Langfuse traces automatically) ────────
147
+ // observeOpenAI wraps the OpenAI client for this call only, capturing
148
+ // input, output, tokens, latency, and cost in the Langfuse dashboard.
149
+ // We pass effectiveTraceId as traceName so the Langfuse trace is findable
150
+ // by the same ID used in the Armour scan.
151
+ let response;
152
+ const litellmClient = getLitellmClient();
153
+ if (litellmClient && detectProvider(model) !== "openai") {
154
+ // LiteLLM path: wrap the LiteLLM client with observeOpenAI too.
155
+ const tracedLitellm = observeOpenAI(litellmClient, {
156
+ traceName: effectiveTraceId,
157
+ sessionId: sessionId || undefined,
158
+ userId: userId || undefined,
159
+ tags: tags || undefined,
160
+ generationName: name || "chat",
161
+ generationMetadata: metadata,
162
+ });
163
+ response = await tracedLitellm.chat.completions.create(openaiParams);
164
+ }
165
+ else {
166
+ const tracedClient = observeOpenAI(getClient(), {
167
+ traceName: effectiveTraceId,
168
+ sessionId: sessionId || undefined,
169
+ userId: userId || undefined,
170
+ tags: tags || undefined,
171
+ generationName: name || "chat",
172
+ generationMetadata: metadata,
173
+ });
174
+ response = await tracedClient.chat.completions.create(openaiParams);
175
+ }
176
+ // ── Collect trace metrics for Armour eval trigger ─────────────────────
177
+ const latency = Date.now() - _startMs;
178
+ const inTok = response.usage?.prompt_tokens ?? 0;
179
+ const outTok = response.usage?.completion_tokens ?? 0;
180
+ const outModel = response.model ?? model;
181
+ // ── Armour output scan + server-side eval trigger ─────────────────────
182
+ let aiReply = response.choices[0]?.message.content ?? "";
183
+ const evalTracePayload = {
184
+ trace_id: effectiveTraceId,
185
+ input: userInput,
186
+ output: aiReply,
187
+ model: outModel,
188
+ latency_ms: latency,
189
+ prompt_tokens: inTok,
190
+ completion_tokens: outTok,
191
+ cost_usd: null,
192
+ session_id: sessionId,
193
+ user_id: userId,
194
+ metadata,
195
+ };
196
+ const outputScan = await callArmourScan("", sessionId, userId, {
197
+ aiResponse: aiReply,
198
+ scanType: "output",
199
+ traceId: effectiveTraceId,
200
+ evalTrace: evalTracePayload,
201
+ });
202
+ // ── De-anonymization — restore real values in the AI reply ────────────
203
+ if (usedSanitizedPrompt && outputScan.sanitized_output) {
204
+ aiReply = outputScan.sanitized_output;
205
+ if (response.choices[0]?.message) {
206
+ response.choices[0].message.content = aiReply;
207
+ }
208
+ console.log("[Armour] deanonymization — restored=yes");
209
+ }
210
+ return response;
211
+ },
212
+ };
213
+ export const openai = {
214
+ chat: {
215
+ completions: _completions,
216
+ },
217
+ };
218
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/langfuse/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * langprotect-armor/langfuse/routing — Model provider detection and LiteLLM client setup.
3
+ *
4
+ * Environment variables (overridden by init()):
5
+ * LITELLM_HOST — LiteLLM proxy base URL (optional; required for non-OpenAI models)
6
+ */
7
+ import OpenAI from "openai";
8
+ /** Returns the LiteLLM client if a host is configured, otherwise null. */
9
+ export declare function getLitellmClient(): OpenAI | null;
10
+ export declare function detectProvider(model: string): string;
11
+ //# sourceMappingURL=routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/langfuse/routing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,MAAM,MAAY,QAAQ,CAAC;AAOlC,0EAA0E;AAC1E,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAQhD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAapD"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * langprotect-armor/langfuse/routing — Model provider detection and LiteLLM client setup.
3
+ *
4
+ * Environment variables (overridden by init()):
5
+ * LITELLM_HOST — LiteLLM proxy base URL (optional; required for non-OpenAI models)
6
+ */
7
+ import OpenAI from "openai";
8
+ import { getConfig } from "./config.js";
9
+ // Cached per litellmHost value so we don't recreate on every call.
10
+ let _cachedHost = "";
11
+ let _cachedClient = null;
12
+ /** Returns the LiteLLM client if a host is configured, otherwise null. */
13
+ export function getLitellmClient() {
14
+ const host = getConfig().litellmHost;
15
+ if (!host)
16
+ return null;
17
+ if (host !== _cachedHost) {
18
+ _cachedHost = host;
19
+ _cachedClient = new OpenAI({ baseURL: `${host}/v1`, apiKey: "anything" });
20
+ }
21
+ return _cachedClient;
22
+ }
23
+ export function detectProvider(model) {
24
+ const m = (model ?? "").toLowerCase();
25
+ if (m.startsWith("gpt-") || m.startsWith("o1") || m.startsWith("o3") ||
26
+ m.startsWith("text-davinci") || m.startsWith("text-embedding"))
27
+ return "openai";
28
+ if (m.startsWith("claude") || m.startsWith("anthropic"))
29
+ return "anthropic";
30
+ if (m.startsWith("gemini") || m.startsWith("palm"))
31
+ return "google";
32
+ if (m.startsWith("llama") || m.startsWith("mistral") || m.startsWith("mixtral") ||
33
+ m.startsWith("codellama") || m.startsWith("phi") || m.startsWith("qwen"))
34
+ return "ollama";
35
+ return "litellm";
36
+ }
37
+ //# sourceMappingURL=routing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.js","sourceRoot":"","sources":["../../src/langfuse/routing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,MAAM,MAAY,QAAQ,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,mEAAmE;AACnE,IAAI,WAAW,GAAK,EAAE,CAAC;AACvB,IAAI,aAAa,GAAkB,IAAI,CAAC;AAExC,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC,WAAW,CAAC;IACrC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,WAAW,GAAK,IAAI,CAAC;QACrB,aAAa,GAAG,IAAI,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACtC,IACE,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAChE,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,gBAAgB,CAAC;QAC9D,OAAO,QAAQ,CAAC;IAClB,IAAI,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,WAAW,CAAC;IAC5E,IAAI,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;QAAQ,OAAO,QAAQ,CAAC;IAC1E,IACE,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC;QAC3E,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;QACxE,OAAO,QAAQ,CAAC;IAClB,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * langprotect-armor/langfuse/tracing — Feedback scoring.
3
+ *
4
+ * Environment variables (overridden by init()):
5
+ * LANGPROTECT_HOST (default: http://localhost:8000)
6
+ */
7
+ export declare function score(traceId: string, value: number, comment?: string): Promise<void>;
8
+ //# sourceMappingURL=tracing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../../src/langfuse/tracing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,wBAAsB,KAAK,CACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAI,MAAM,EACf,OAAO,SAAK,GACX,OAAO,CAAC,IAAI,CAAC,CAYf"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * langprotect-armor/langfuse/tracing — Feedback scoring.
3
+ *
4
+ * Environment variables (overridden by init()):
5
+ * LANGPROTECT_HOST (default: http://localhost:8000)
6
+ */
7
+ import { getConfig } from "./config.js";
8
+ export async function score(traceId, value, comment = "") {
9
+ const host = getConfig().langprotectHost;
10
+ try {
11
+ await fetch(`${host}/api/feedback`, {
12
+ method: "POST",
13
+ headers: { "Content-Type": "application/json" },
14
+ body: JSON.stringify({ trace_id: traceId, value, comment }),
15
+ signal: AbortSignal.timeout(5_000),
16
+ });
17
+ }
18
+ catch {
19
+ // fail-open — scoring errors must never surface to the caller
20
+ }
21
+ }
22
+ //# sourceMappingURL=tracing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing.js","sourceRoot":"","sources":["../../src/langfuse/tracing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,OAAe,EACf,KAAe,EACf,OAAO,GAAG,EAAE;IAEZ,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC,eAAe,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,GAAG,IAAI,eAAe,EAAE;YAClC,MAAM,EAAG,MAAM;YACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAK,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;YAC9D,MAAM,EAAG,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;SACpC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,8DAA8D;IAChE,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "langprotect-armor",
3
+ "version": "0.1.0",
4
+ "description": "Self-hosted LLM observability SDK — trace any model call to your own dashboard",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Quokka-Labs-LLP/langprotect-armor-sdk.git",
9
+ "directory": "typescript"
10
+ },
11
+ "homepage": "https://github.com/Quokka-Labs-LLP/langprotect-armor-sdk#readme",
12
+ "type": "module",
13
+ "main": "./dist/langfuse/index.js",
14
+ "types": "./dist/langfuse/index.d.ts",
15
+ "exports": {
16
+ "./langfuse": {
17
+ "types": "./dist/langfuse/index.d.ts",
18
+ "import": "./dist/langfuse/index.js"
19
+ }
20
+ },
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE",
28
+ ".env.example"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc",
32
+ "dev": "tsc --watch",
33
+ "prepublishOnly": "npm run build"
34
+ },
35
+ "dependencies": {
36
+ "@langfuse/openai": "^5.4.1",
37
+ "openai": ">=4 <6"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^20.0.0",
41
+ "typescript": "^5.0.0"
42
+ }
43
+ }