@voightxyz/anthropic 0.1.0-beta.1
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/CHANGELOG.md +20 -0
- package/LICENSE +189 -0
- package/README.md +54 -0
- package/dist/index.cjs +488 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +54 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +461 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture aggressiveness for prompts and responses.
|
|
3
|
+
*
|
|
4
|
+
* - `minimal`: model, tokens, latency, errors only. Zero content.
|
|
5
|
+
* - `standard` (default): + prompts/responses redacted of common
|
|
6
|
+
* PII (emails, phone numbers, credit cards, API keys, JWTs).
|
|
7
|
+
* - `full`: everything raw, no redaction.
|
|
8
|
+
*/
|
|
9
|
+
type PrivacyLevel = 'minimal' | 'standard' | 'full';
|
|
10
|
+
interface WrapOptions {
|
|
11
|
+
/** Voight API key. Falls back to `process.env.VOIGHT_KEY`. */
|
|
12
|
+
voightApiKey?: string;
|
|
13
|
+
/** Voight API base URL. Defaults to `https://api.voight.xyz`. */
|
|
14
|
+
apiBase?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Stable agent identifier surfaced in the dashboard. Falls back
|
|
17
|
+
* to `process.env.VOIGHT_AGENT`, then `process.env.HOSTNAME`,
|
|
18
|
+
* then `'unknown-agent'`.
|
|
19
|
+
*/
|
|
20
|
+
agent?: string;
|
|
21
|
+
/** Default `'standard'`. See {@link PrivacyLevel}. */
|
|
22
|
+
privacy?: PrivacyLevel;
|
|
23
|
+
/** Kill switch. When `false` the wrapper is a no-op pass-through. */
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* `wrapAnthropic` — the public entrypoint of @voightxyz/anthropic.
|
|
29
|
+
*
|
|
30
|
+
* Layered `Proxy`: level 0 intercepts the `messages` property,
|
|
31
|
+
* level 1 intercepts the `create` function on it. Everything outside
|
|
32
|
+
* the `client.messages.create` path passes through untouched via
|
|
33
|
+
* `Reflect.get`, so the legacy `completions` namespace, the model
|
|
34
|
+
* list endpoints, batch endpoints, and any future SDK additions
|
|
35
|
+
* keep working with zero special-casing.
|
|
36
|
+
*
|
|
37
|
+
* The proxy is one level shallower than the openai port because
|
|
38
|
+
* Anthropic exposes `messages` at the top level (no intermediate
|
|
39
|
+
* `chat` namespace).
|
|
40
|
+
*
|
|
41
|
+
* Failure modes are intentionally non-fatal — same contract as
|
|
42
|
+
* @voightxyz/openai:
|
|
43
|
+
*
|
|
44
|
+
* - `enabled: false` → return the original client.
|
|
45
|
+
* - no API key resolves → log a one-line warning and return
|
|
46
|
+
* the original client.
|
|
47
|
+
*
|
|
48
|
+
* Internal `_fetch` and `_env` options exist so tests can drive
|
|
49
|
+
* the network + environment surface without touching globals.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
declare function wrapAnthropic<T extends object>(client: T, options?: WrapOptions): T;
|
|
53
|
+
|
|
54
|
+
export { type PrivacyLevel, type WrapOptions, wrapAnthropic };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
// src/identity.ts
|
|
2
|
+
function nonBlank(value) {
|
|
3
|
+
if (typeof value !== "string") return null;
|
|
4
|
+
const trimmed = value.trim();
|
|
5
|
+
return trimmed.length === 0 ? null : trimmed;
|
|
6
|
+
}
|
|
7
|
+
function resolveApiKey(options = {}, env = process.env) {
|
|
8
|
+
return nonBlank(options.voightApiKey) ?? nonBlank(env.VOIGHT_KEY);
|
|
9
|
+
}
|
|
10
|
+
function resolveAgent(options = {}, env = process.env) {
|
|
11
|
+
return nonBlank(options.agent) ?? nonBlank(env.VOIGHT_AGENT) ?? nonBlank(env.HOSTNAME) ?? "unknown-agent";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/ingest.ts
|
|
15
|
+
function createIngestClient(opts) {
|
|
16
|
+
const base = opts.apiBase.replace(/\/+$/, "");
|
|
17
|
+
const url = `${base}/v1/events`;
|
|
18
|
+
const headers = {
|
|
19
|
+
"content-type": "application/json",
|
|
20
|
+
authorization: `Bearer ${opts.apiKey}`
|
|
21
|
+
};
|
|
22
|
+
const fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
23
|
+
const onError = opts.onError ?? (() => {
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
send(event) {
|
|
27
|
+
void (async () => {
|
|
28
|
+
try {
|
|
29
|
+
const body = JSON.stringify(event);
|
|
30
|
+
const res = await fetchImpl(url, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers,
|
|
33
|
+
body
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
onError(
|
|
37
|
+
new Error(
|
|
38
|
+
`voight ingest failed: ${res.status} ${res.statusText}`
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
onError(err);
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/privacy.ts
|
|
51
|
+
var RE_PEM_PRIVATE_KEY = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
|
|
52
|
+
var RE_JWT = /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g;
|
|
53
|
+
var RE_ANTHROPIC = /\bsk-ant-[A-Za-z0-9_-]{40,}\b/g;
|
|
54
|
+
var RE_OPENAI = /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g;
|
|
55
|
+
var RE_STRIPE_LIVE = /\b(?:sk|pk)_live_[A-Za-z0-9]{20,}\b/g;
|
|
56
|
+
var RE_GITHUB_FINE = /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g;
|
|
57
|
+
var RE_GITHUB_CLASSIC = /\bghp_[A-Za-z0-9]{36}\b/g;
|
|
58
|
+
var RE_AWS_ACCESS_KEY = /\bAKIA[A-Z0-9]{16}\b/g;
|
|
59
|
+
var RE_SLACK = /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g;
|
|
60
|
+
var RE_VOIGHT = /\bvk_[A-Za-z0-9_-]{32,}\b/g;
|
|
61
|
+
var RE_EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
62
|
+
var RE_PHONE_E164 = /\+\d{10,15}\b/g;
|
|
63
|
+
var KEY_PATTERNS = [
|
|
64
|
+
{ name: "pem-private-key", re: RE_PEM_PRIVATE_KEY, replacement: "[REDACTED-PRIVATE-KEY]" },
|
|
65
|
+
{ name: "jwt", re: RE_JWT, replacement: "[REDACTED-JWT]" },
|
|
66
|
+
{ name: "anthropic-key", re: RE_ANTHROPIC, replacement: "[REDACTED-API-KEY]" },
|
|
67
|
+
{ name: "openai-key", re: RE_OPENAI, replacement: "[REDACTED-API-KEY]" },
|
|
68
|
+
{ name: "stripe-live-key", re: RE_STRIPE_LIVE, replacement: "[REDACTED-API-KEY]" },
|
|
69
|
+
{ name: "github-fine-pat", re: RE_GITHUB_FINE, replacement: "[REDACTED-API-KEY]" },
|
|
70
|
+
{ name: "github-classic-pat", re: RE_GITHUB_CLASSIC, replacement: "[REDACTED-API-KEY]" },
|
|
71
|
+
{ name: "aws-access-key", re: RE_AWS_ACCESS_KEY, replacement: "[REDACTED-API-KEY]" },
|
|
72
|
+
{ name: "slack-token", re: RE_SLACK, replacement: "[REDACTED-API-KEY]" },
|
|
73
|
+
{ name: "voight-key", re: RE_VOIGHT, replacement: "[REDACTED-API-KEY]" },
|
|
74
|
+
{ name: "email", re: RE_EMAIL, replacement: "[REDACTED-EMAIL]" },
|
|
75
|
+
{ name: "phone-e164", re: RE_PHONE_E164, replacement: "[REDACTED-PHONE]" }
|
|
76
|
+
];
|
|
77
|
+
function luhnValid(digits) {
|
|
78
|
+
if (digits.length === 0) return false;
|
|
79
|
+
let sum = 0;
|
|
80
|
+
let alternate = false;
|
|
81
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
82
|
+
const ch = digits.charCodeAt(i);
|
|
83
|
+
if (ch < 48 || ch > 57) return false;
|
|
84
|
+
let n = ch - 48;
|
|
85
|
+
if (alternate) {
|
|
86
|
+
n *= 2;
|
|
87
|
+
if (n > 9) n -= 9;
|
|
88
|
+
}
|
|
89
|
+
sum += n;
|
|
90
|
+
alternate = !alternate;
|
|
91
|
+
}
|
|
92
|
+
return sum > 0 && sum % 10 === 0;
|
|
93
|
+
}
|
|
94
|
+
var RE_CARD_CANDIDATE = /\b(?:\d[ -]?){12,18}\d\b/g;
|
|
95
|
+
function scrubCreditCards(input) {
|
|
96
|
+
return input.replace(RE_CARD_CANDIDATE, (match) => {
|
|
97
|
+
const digits = match.replace(/[ -]/g, "");
|
|
98
|
+
if (digits.length < 13 || digits.length > 19) return match;
|
|
99
|
+
if (!luhnValid(digits)) return match;
|
|
100
|
+
return "[REDACTED-CARD]";
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function scrubPii(input) {
|
|
104
|
+
if (typeof input !== "string" || input.length === 0) return input;
|
|
105
|
+
let out = input;
|
|
106
|
+
for (const { re, replacement } of KEY_PATTERNS) {
|
|
107
|
+
out = out.replace(new RegExp(re.source, re.flags), replacement);
|
|
108
|
+
}
|
|
109
|
+
out = scrubCreditCards(out);
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
function scrubAnyValue(value) {
|
|
113
|
+
if (typeof value === "string") return scrubPii(value);
|
|
114
|
+
if (Array.isArray(value)) return value.map(scrubAnyValue);
|
|
115
|
+
if (value !== null && typeof value === "object") {
|
|
116
|
+
const out = {};
|
|
117
|
+
for (const [k, v] of Object.entries(value)) {
|
|
118
|
+
out[k] = scrubAnyValue(v);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/instruments/messages.ts
|
|
126
|
+
function instrumentMessages(original, ctx) {
|
|
127
|
+
return async function wrappedCreate(params) {
|
|
128
|
+
const startedAt = ctx.now();
|
|
129
|
+
const isStream = params.stream === true;
|
|
130
|
+
let result;
|
|
131
|
+
try {
|
|
132
|
+
result = await original(params);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
ctx.ingest.send(
|
|
135
|
+
buildFailureEvent({ ctx, params, startedAt, error: err })
|
|
136
|
+
);
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
if (!isStream) {
|
|
140
|
+
ctx.ingest.send(
|
|
141
|
+
buildSuccessEvent({
|
|
142
|
+
ctx,
|
|
143
|
+
params,
|
|
144
|
+
startedAt,
|
|
145
|
+
response: result
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
return wrapStream(
|
|
151
|
+
result,
|
|
152
|
+
ctx,
|
|
153
|
+
params,
|
|
154
|
+
startedAt
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function buildSuccessEvent(args) {
|
|
159
|
+
const { ctx, params, startedAt, response } = args;
|
|
160
|
+
const responseText = extractText(response.content ?? []);
|
|
161
|
+
const toolCalls = extractToolCalls(response.content ?? []);
|
|
162
|
+
const tokens = normaliseTokens(response.usage);
|
|
163
|
+
const durationMs = ctx.now() - startedAt;
|
|
164
|
+
return assembleEvent({
|
|
165
|
+
ctx,
|
|
166
|
+
params,
|
|
167
|
+
durationMs,
|
|
168
|
+
outcome: "success",
|
|
169
|
+
responseText: responseText.length > 0 ? responseText : void 0,
|
|
170
|
+
tokens,
|
|
171
|
+
toolCalls,
|
|
172
|
+
streaming: false,
|
|
173
|
+
finishReason: response.stop_reason ?? null,
|
|
174
|
+
modelFromResponse: response.model
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function buildFailureEvent(args) {
|
|
178
|
+
const { ctx, params, startedAt, error } = args;
|
|
179
|
+
const durationMs = ctx.now() - startedAt;
|
|
180
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
181
|
+
return assembleEvent({
|
|
182
|
+
ctx,
|
|
183
|
+
params,
|
|
184
|
+
durationMs,
|
|
185
|
+
outcome: "failed",
|
|
186
|
+
streaming: params.stream === true,
|
|
187
|
+
errorMessage: message
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function buildStreamEvent(args) {
|
|
191
|
+
return assembleEvent({
|
|
192
|
+
ctx: args.ctx,
|
|
193
|
+
params: args.params,
|
|
194
|
+
durationMs: args.ctx.now() - args.startedAt,
|
|
195
|
+
outcome: "success",
|
|
196
|
+
responseText: args.aggregatedText.length > 0 ? args.aggregatedText : void 0,
|
|
197
|
+
tokens: args.tokens,
|
|
198
|
+
toolCalls: args.toolCalls,
|
|
199
|
+
streaming: true,
|
|
200
|
+
finishReason: args.finishReason,
|
|
201
|
+
modelFromResponse: args.modelFromResponse
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function assembleEvent(args) {
|
|
205
|
+
const { ctx, params, durationMs, outcome, streaming, errorMessage } = args;
|
|
206
|
+
const tokens = args.tokens ?? null;
|
|
207
|
+
const toolCalls = args.toolCalls ?? null;
|
|
208
|
+
const model = args.modelFromResponse ?? params.model;
|
|
209
|
+
const metadata = {
|
|
210
|
+
source: "anthropic-sdk",
|
|
211
|
+
privacyLevel: ctx.privacy,
|
|
212
|
+
streaming
|
|
213
|
+
};
|
|
214
|
+
if (tokens) metadata.tokens = tokens;
|
|
215
|
+
if (args.finishReason !== void 0 && args.finishReason !== null) {
|
|
216
|
+
metadata.finishReason = args.finishReason;
|
|
217
|
+
}
|
|
218
|
+
const firstToolName = toolCalls && toolCalls.length > 0 ? toolCalls[0].name : void 0;
|
|
219
|
+
if (ctx.privacy === "minimal") {
|
|
220
|
+
return {
|
|
221
|
+
agentId: ctx.agentId,
|
|
222
|
+
type: "reasoning",
|
|
223
|
+
model,
|
|
224
|
+
durationMs,
|
|
225
|
+
outcome,
|
|
226
|
+
...firstToolName ? { toolExecuted: firstToolName } : {},
|
|
227
|
+
metadata,
|
|
228
|
+
...errorMessage ? { errorMessage } : {}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const messages = ctx.privacy === "standard" ? scrubAnyValue(params.messages) : params.messages;
|
|
232
|
+
const responseText = args.responseText;
|
|
233
|
+
const scrubbedResponse = responseText !== void 0 ? ctx.privacy === "standard" ? scrubPii(responseText) : responseText : void 0;
|
|
234
|
+
if (scrubbedResponse !== void 0) {
|
|
235
|
+
metadata.responseText = scrubbedResponse;
|
|
236
|
+
}
|
|
237
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
238
|
+
metadata.toolCalls = ctx.privacy === "standard" ? toolCalls.map((t) => ({
|
|
239
|
+
id: t.id,
|
|
240
|
+
name: t.name,
|
|
241
|
+
arguments: scrubPii(t.arguments)
|
|
242
|
+
})) : toolCalls;
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
agentId: ctx.agentId,
|
|
246
|
+
type: "reasoning",
|
|
247
|
+
model,
|
|
248
|
+
durationMs,
|
|
249
|
+
outcome,
|
|
250
|
+
...firstToolName ? { toolExecuted: firstToolName } : {},
|
|
251
|
+
input: { messages },
|
|
252
|
+
metadata,
|
|
253
|
+
...errorMessage ? { errorMessage } : {}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function extractText(content) {
|
|
257
|
+
let out = "";
|
|
258
|
+
for (const block of content) {
|
|
259
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
260
|
+
out += block.text;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
function extractToolCalls(content) {
|
|
266
|
+
const out = [];
|
|
267
|
+
for (const block of content) {
|
|
268
|
+
if (block.type !== "tool_use") continue;
|
|
269
|
+
const t = block;
|
|
270
|
+
out.push({
|
|
271
|
+
id: typeof t.id === "string" ? t.id : "",
|
|
272
|
+
name: typeof t.name === "string" ? t.name : "",
|
|
273
|
+
// Serialise the model's chosen tool input to a JSON string so
|
|
274
|
+
// the wire shape matches openai's `arguments: string` exactly.
|
|
275
|
+
// Anthropic gives us a parsed object; openai gives us the raw
|
|
276
|
+
// string. We normalise here.
|
|
277
|
+
arguments: safeStringify(t.input)
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return out.length > 0 ? out : null;
|
|
281
|
+
}
|
|
282
|
+
function safeStringify(v) {
|
|
283
|
+
if (typeof v === "string") return v;
|
|
284
|
+
try {
|
|
285
|
+
return JSON.stringify(v ?? {});
|
|
286
|
+
} catch {
|
|
287
|
+
return "";
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function normaliseTokens(u) {
|
|
291
|
+
if (!u) return null;
|
|
292
|
+
const input = numberOrZero(u.input_tokens);
|
|
293
|
+
const output = numberOrZero(u.output_tokens);
|
|
294
|
+
const total = input + output;
|
|
295
|
+
const cacheRead = numberOrZero(u.cache_read_input_tokens);
|
|
296
|
+
const cacheCreation = numberOrZero(u.cache_creation_input_tokens);
|
|
297
|
+
const base = { input, output, total };
|
|
298
|
+
if (cacheRead > 0) base.cache_read = cacheRead;
|
|
299
|
+
if (cacheCreation > 0) base.cache_creation = cacheCreation;
|
|
300
|
+
return base;
|
|
301
|
+
}
|
|
302
|
+
function numberOrZero(v) {
|
|
303
|
+
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
304
|
+
}
|
|
305
|
+
function wrapStream(source, ctx, params, startedAt) {
|
|
306
|
+
const state = {
|
|
307
|
+
aggregatedText: "",
|
|
308
|
+
toolBlocks: /* @__PURE__ */ new Map(),
|
|
309
|
+
usage: null,
|
|
310
|
+
modelFromResponse: void 0,
|
|
311
|
+
finishReason: null
|
|
312
|
+
};
|
|
313
|
+
let emitted = false;
|
|
314
|
+
function emit() {
|
|
315
|
+
if (emitted) return;
|
|
316
|
+
emitted = true;
|
|
317
|
+
ctx.ingest.send(
|
|
318
|
+
buildStreamEvent({
|
|
319
|
+
ctx,
|
|
320
|
+
params,
|
|
321
|
+
startedAt,
|
|
322
|
+
aggregatedText: state.aggregatedText,
|
|
323
|
+
tokens: normaliseTokens(state.usage ?? void 0),
|
|
324
|
+
toolCalls: snapshotTools(state.toolBlocks),
|
|
325
|
+
modelFromResponse: state.modelFromResponse,
|
|
326
|
+
finishReason: state.finishReason
|
|
327
|
+
})
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
async *[Symbol.asyncIterator]() {
|
|
332
|
+
try {
|
|
333
|
+
for await (const ev of source) {
|
|
334
|
+
applyEvent(state, ev);
|
|
335
|
+
yield ev;
|
|
336
|
+
}
|
|
337
|
+
} catch (err) {
|
|
338
|
+
ctx.ingest.send(
|
|
339
|
+
buildFailureEvent({ ctx, params, startedAt, error: err })
|
|
340
|
+
);
|
|
341
|
+
emitted = true;
|
|
342
|
+
throw err;
|
|
343
|
+
} finally {
|
|
344
|
+
emit();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function applyEvent(state, ev) {
|
|
350
|
+
switch (ev.type) {
|
|
351
|
+
case "message_start": {
|
|
352
|
+
const e = ev;
|
|
353
|
+
if (e.message?.model && !state.modelFromResponse) {
|
|
354
|
+
state.modelFromResponse = e.message.model;
|
|
355
|
+
}
|
|
356
|
+
if (e.message?.usage) {
|
|
357
|
+
state.usage = { ...state.usage ?? {}, ...e.message.usage };
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
case "content_block_start": {
|
|
362
|
+
const e = ev;
|
|
363
|
+
if (e.content_block?.type === "tool_use") {
|
|
364
|
+
const tu = e.content_block;
|
|
365
|
+
state.toolBlocks.set(e.index, {
|
|
366
|
+
id: typeof tu.id === "string" ? tu.id : "",
|
|
367
|
+
name: typeof tu.name === "string" ? tu.name : "",
|
|
368
|
+
arguments: ""
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
case "content_block_delta": {
|
|
374
|
+
const e = ev;
|
|
375
|
+
const d = e.delta;
|
|
376
|
+
if (d.type === "text_delta" && typeof d.text === "string") {
|
|
377
|
+
state.aggregatedText += d.text;
|
|
378
|
+
} else if (d.type === "input_json_delta" && typeof d.partial_json === "string") {
|
|
379
|
+
const entry = state.toolBlocks.get(e.index);
|
|
380
|
+
if (entry) entry.arguments += d.partial_json;
|
|
381
|
+
}
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
case "message_delta": {
|
|
385
|
+
const e = ev;
|
|
386
|
+
if (e.delta?.stop_reason && state.finishReason === null) {
|
|
387
|
+
state.finishReason = e.delta.stop_reason;
|
|
388
|
+
}
|
|
389
|
+
if (e.usage) {
|
|
390
|
+
state.usage = { ...state.usage ?? {}, ...e.usage };
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function snapshotTools(acc) {
|
|
397
|
+
if (acc.size === 0) return null;
|
|
398
|
+
const entries = [...acc.entries()].sort(([a], [b]) => a - b);
|
|
399
|
+
const out = entries.map(([, v]) => v).filter((t) => t.name.length > 0);
|
|
400
|
+
return out.length > 0 ? out : null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/wrap.ts
|
|
404
|
+
var DEFAULT_API_BASE = "https://api.voight.xyz";
|
|
405
|
+
function wrapAnthropic(client, options = {}) {
|
|
406
|
+
const opts = options;
|
|
407
|
+
if (opts.enabled === false) return client;
|
|
408
|
+
const env = opts._env ?? process.env;
|
|
409
|
+
const apiKey = resolveApiKey(
|
|
410
|
+
{ voightApiKey: opts.voightApiKey, agent: opts.agent },
|
|
411
|
+
env
|
|
412
|
+
);
|
|
413
|
+
if (apiKey === null) {
|
|
414
|
+
console.warn(
|
|
415
|
+
"[voight] no VOIGHT_KEY resolved \u2014 wrapper is a pass-through. Set process.env.VOIGHT_KEY or pass `voightApiKey` to wrapAnthropic() to enable capture."
|
|
416
|
+
);
|
|
417
|
+
return client;
|
|
418
|
+
}
|
|
419
|
+
const agentId = resolveAgent(
|
|
420
|
+
{ voightApiKey: opts.voightApiKey, agent: opts.agent },
|
|
421
|
+
env
|
|
422
|
+
);
|
|
423
|
+
const ingest = createIngestClient({
|
|
424
|
+
apiBase: opts.apiBase ?? DEFAULT_API_BASE,
|
|
425
|
+
apiKey,
|
|
426
|
+
fetch: opts._fetch
|
|
427
|
+
});
|
|
428
|
+
const ctx = {
|
|
429
|
+
agentId,
|
|
430
|
+
privacy: opts.privacy ?? "standard",
|
|
431
|
+
ingest,
|
|
432
|
+
now: () => Date.now()
|
|
433
|
+
};
|
|
434
|
+
return new Proxy(client, {
|
|
435
|
+
get(target, prop, receiver) {
|
|
436
|
+
if (prop === "messages") {
|
|
437
|
+
const messages = Reflect.get(target, prop, receiver);
|
|
438
|
+
return wrapMessages(messages, ctx);
|
|
439
|
+
}
|
|
440
|
+
return Reflect.get(target, prop, receiver);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function wrapMessages(messages, ctx) {
|
|
445
|
+
return new Proxy(messages, {
|
|
446
|
+
get(target, prop, receiver) {
|
|
447
|
+
if (prop === "create") {
|
|
448
|
+
const original = Reflect.get(target, prop, receiver);
|
|
449
|
+
return instrumentMessages(
|
|
450
|
+
original.bind(target),
|
|
451
|
+
ctx
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
return Reflect.get(target, prop, receiver);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
export {
|
|
459
|
+
wrapAnthropic
|
|
460
|
+
};
|
|
461
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/identity.ts","../src/ingest.ts","../src/privacy.ts","../src/instruments/messages.ts","../src/wrap.ts"],"sourcesContent":["/**\n * Identity resolution: API key + agent label.\n *\n * Both helpers are pure — they take the user's options and an `env`\n * record, and return a result. `env` defaults to `process.env` when\n * called bare; passing it explicitly lets tests exercise every\n * fallback path without mutating global state.\n *\n * Resolution priority is fixed and documented at each call site:\n *\n * resolveApiKey: options.voightApiKey → env.VOIGHT_KEY → null\n * resolveAgent: options.agent → env.VOIGHT_AGENT\n * → env.HOSTNAME → 'unknown-agent'\n *\n * Empty and whitespace-only values are treated as missing so a\n * misconfigured env (`VOIGHT_KEY=`) falls through cleanly instead\n * of being mistaken for a real key.\n */\n\nexport interface IdentityOptions {\n voightApiKey?: string | undefined\n agent?: string | undefined\n}\n\ntype Env = Record<string, string | undefined>\n\n/**\n * Trim and return the value, or `null` if it's missing / blank.\n * Centralises the \"empty counts as missing\" rule that both helpers\n * apply at every layer.\n */\nfunction nonBlank(value: string | undefined): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length === 0 ? null : trimmed\n}\n\n/**\n * Resolve the Voight API key for outgoing events.\n *\n * Returns `null` when nothing usable is configured. Callers decide\n * how to react — `wrapOpenAI` logs a one-time warning and falls\n * back to a no-op transport, so a missing key never crashes the\n * host app.\n */\nexport function resolveApiKey(\n options: IdentityOptions = {},\n env: Env = process.env,\n): string | null {\n return nonBlank(options.voightApiKey) ?? nonBlank(env.VOIGHT_KEY)\n}\n\n/**\n * Resolve the agent label that groups events in the dashboard.\n *\n * Always returns a non-empty string: when nothing else resolves we\n * emit `'unknown-agent'` so the event is still ingestable and the\n * misconfiguration shows up on screen instead of being silently\n * dropped.\n */\nexport function resolveAgent(\n options: IdentityOptions = {},\n env: Env = process.env,\n): string {\n return (\n nonBlank(options.agent) ??\n nonBlank(env.VOIGHT_AGENT) ??\n nonBlank(env.HOSTNAME) ??\n 'unknown-agent'\n )\n}\n","/**\n * Fire-and-forget HTTP client for the Voight ingest endpoint.\n *\n * Why \"fire and forget\":\n *\n * The wrapper is in the user's app's hot path. A failing or slow\n * Voight backend must NEVER turn into a failing or slow OpenAI\n * call for the user. `send` returns synchronously; the actual\n * POST happens on the microtask queue and any error reaches the\n * optional `onError` hook rather than the caller's stack.\n *\n * We intentionally do not retry, batch, or buffer in beta.1.\n * Those add state and complexity — we'd rather drop the\n * occasional event than ship a half-implemented queue. Retry +\n * buffer arrive in 0.2.0 once we have real-world failure-mode\n * data to design against.\n *\n * Why inject `fetch`:\n *\n * Lets unit tests assert request shape (URL, headers, body)\n * without going to the network and without monkey-patching\n * the global. In production the option is omitted and the\n * runtime's `fetch` (Node 18+) is used.\n */\n\nimport type { EventPayload } from './types.js'\n\nexport interface IngestOptions {\n apiBase: string\n apiKey: string\n /**\n * Optional override for the network call. Tests inject a mock;\n * production callers leave this unset and `globalThis.fetch` is\n * used at dispatch time.\n */\n fetch?: typeof fetch | undefined\n /**\n * Called when a network error or non-2xx response would otherwise\n * be silently dropped. Useful for surfacing misconfiguration\n * (bad key, wrong apiBase) during development. Defaults to a\n * no-op so production stays quiet.\n */\n onError?: ((err: unknown) => void) | undefined\n}\n\nexport interface IngestClient {\n send: (event: EventPayload) => void\n}\n\n/**\n * Build a client bound to a single apiBase + apiKey pair.\n *\n * The returned `send` is synchronous and never throws. Errors are\n * routed to `onError` (a no-op by default).\n */\nexport function createIngestClient(opts: IngestOptions): IngestClient {\n // Normalise the base URL once so callers can pass it with or\n // without a trailing slash and we don't end up with `//v1/events`.\n const base = opts.apiBase.replace(/\\/+$/, '')\n const url = `${base}/v1/events`\n const headers = {\n 'content-type': 'application/json',\n authorization: `Bearer ${opts.apiKey}`,\n }\n const fetchImpl = opts.fetch ?? globalThis.fetch\n const onError = opts.onError ?? (() => {})\n\n return {\n send(event) {\n // Wrap the dispatch in an IIFE so a synchronous throw inside\n // `JSON.stringify` (e.g. circular structure) still ends up\n // at `onError` instead of bubbling to the user's hot path.\n void (async () => {\n try {\n const body = JSON.stringify(event)\n const res = await fetchImpl(url, {\n method: 'POST',\n headers,\n body,\n })\n if (!res.ok) {\n onError(\n new Error(\n `voight ingest failed: ${res.status} ${res.statusText}`,\n ),\n )\n }\n } catch (err) {\n onError(err)\n }\n })()\n },\n }\n}\n","/**\n * PII scrubbing utilities. Ports the regex catalogue proven in\n * @voightxyz/sdk's privacy.ts (12 patterns + Luhn-validated cards),\n * trimmed to the surface this package needs.\n *\n * `scrubPii` and `scrubAnyValue` are pure: same input produces same\n * output, no I/O, no randomness. Safe to call in a hot path.\n *\n * Why duplicate the catalogue here (rather than depend on the SDK)?\n *\n * v0.1.0-beta.1 ships standalone — no runtime dependency on\n * `@voightxyz/sdk` — so a user installing only `@voightxyz/openai`\n * gets a complete package. If duplication ever becomes painful\n * (a third copy under @voightxyz/anthropic, say), the right move\n * is to extract a private `@voightxyz/core` package; until then,\n * the cost of a 12-pattern table is lower than the cost of a\n * peer-dep + version-skew matrix.\n */\n\n// ─── PII patterns ──────────────────────────────────────────────────\n//\n// Order matters. Multi-line / most-specific patterns run FIRST so\n// that once a span is consumed (e.g. a PEM block), later patterns\n// can't re-match its insides. JWTs run before generic API-key\n// patterns to avoid partial matches.\n//\n// Each pattern has a `name` for unit-test traceability and a `re`\n// that uses the global flag (so `replaceAll` semantics apply).\n\ntype Pattern = {\n name: string\n re: RegExp\n replacement: string\n}\n\nconst RE_PEM_PRIVATE_KEY =\n /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g\n\nconst RE_JWT =\n /\\beyJ[A-Za-z0-9_-]{10,}\\.eyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\b/g\n\n// Anthropic must precede the generic OpenAI rule so `sk-ant-...`\n// doesn't get partially consumed as `sk-...`.\nconst RE_ANTHROPIC = /\\bsk-ant-[A-Za-z0-9_-]{40,}\\b/g\n\nconst RE_OPENAI = /\\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\\b/g\n\nconst RE_STRIPE_LIVE = /\\b(?:sk|pk)_live_[A-Za-z0-9]{20,}\\b/g\n\nconst RE_GITHUB_FINE = /\\bgithub_pat_[A-Za-z0-9_]{20,}\\b/g\nconst RE_GITHUB_CLASSIC = /\\bghp_[A-Za-z0-9]{36}\\b/g\n\nconst RE_AWS_ACCESS_KEY = /\\bAKIA[A-Z0-9]{16}\\b/g\n\nconst RE_SLACK = /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/g\n\n// Voight's own keys. Defense-in-depth: even if the user accidentally\n// pastes their `vk_…` into a prompt, it never leaves the process in\n// the clear under standard privacy.\nconst RE_VOIGHT = /\\bvk_[A-Za-z0-9_-]{32,}\\b/g\n\n// Strict email: requires a TLD with ≥2 letters. `support@app`\n// (no TLD) and `email_template` (no @) do not match.\nconst RE_EMAIL = /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/g\n\n// Phone in E.164. Looser variants produce too many false positives\n// over order numbers and identifiers.\nconst RE_PHONE_E164 = /\\+\\d{10,15}\\b/g\n\nconst KEY_PATTERNS: readonly Pattern[] = [\n { name: 'pem-private-key', re: RE_PEM_PRIVATE_KEY, replacement: '[REDACTED-PRIVATE-KEY]' },\n { name: 'jwt', re: RE_JWT, replacement: '[REDACTED-JWT]' },\n { name: 'anthropic-key', re: RE_ANTHROPIC, replacement: '[REDACTED-API-KEY]' },\n { name: 'openai-key', re: RE_OPENAI, replacement: '[REDACTED-API-KEY]' },\n { name: 'stripe-live-key', re: RE_STRIPE_LIVE, replacement: '[REDACTED-API-KEY]' },\n { name: 'github-fine-pat', re: RE_GITHUB_FINE, replacement: '[REDACTED-API-KEY]' },\n { name: 'github-classic-pat', re: RE_GITHUB_CLASSIC, replacement: '[REDACTED-API-KEY]' },\n { name: 'aws-access-key', re: RE_AWS_ACCESS_KEY, replacement: '[REDACTED-API-KEY]' },\n { name: 'slack-token', re: RE_SLACK, replacement: '[REDACTED-API-KEY]' },\n { name: 'voight-key', re: RE_VOIGHT, replacement: '[REDACTED-API-KEY]' },\n { name: 'email', re: RE_EMAIL, replacement: '[REDACTED-EMAIL]' },\n { name: 'phone-e164', re: RE_PHONE_E164, replacement: '[REDACTED-PHONE]' },\n]\n\n// ─── Credit cards ─────────────────────────────────────────────────\n//\n// Credit-card numbers need Luhn validation to avoid false positives\n// over long numeric ID strings, so they don't fit the simple\n// {re, replacement} table.\n\n/**\n * Validate a digit string against the Luhn checksum used by all\n * major card brands. Returns false on empty / non-digit input.\n */\nexport function luhnValid(digits: string): boolean {\n if (digits.length === 0) return false\n let sum = 0\n let alternate = false\n for (let i = digits.length - 1; i >= 0; i--) {\n const ch = digits.charCodeAt(i)\n if (ch < 48 || ch > 57) return false\n let n = ch - 48\n if (alternate) {\n n *= 2\n if (n > 9) n -= 9\n }\n sum += n\n alternate = !alternate\n }\n return sum > 0 && sum % 10 === 0\n}\n\n// 13–19 digits, optionally with single spaces or dashes between\n// groups. Anchored with \\b so we don't match the middle of a longer\n// digit string.\nconst RE_CARD_CANDIDATE = /\\b(?:\\d[ -]?){12,18}\\d\\b/g\n\nfunction scrubCreditCards(input: string): string {\n return input.replace(RE_CARD_CANDIDATE, (match) => {\n const digits = match.replace(/[ -]/g, '')\n if (digits.length < 13 || digits.length > 19) return match\n if (!luhnValid(digits)) return match\n return '[REDACTED-CARD]'\n })\n}\n\n/**\n * Run the credential / PII patterns over a string. Pure, idempotent\n * (already-scrubbed text stays stable), no I/O. Non-string input\n * is returned unchanged so this function is safe to call inside\n * the recursive walker.\n */\nexport function scrubPii(input: string): string {\n // The non-string branch matters: `scrubAnyValue` calls this on\n // unknown values, and we'd rather return the raw value than crash.\n if (typeof input !== 'string' || input.length === 0) return input\n let out = input\n for (const { re, replacement } of KEY_PATTERNS) {\n // Pin a fresh RegExp instance per call: `g`-flagged regexes\n // carry mutable `lastIndex` state, and although `replace` does\n // not depend on it, future Worker-based callers might.\n out = out.replace(new RegExp(re.source, re.flags), replacement)\n }\n out = scrubCreditCards(out)\n return out\n}\n\n/**\n * Recursively scrub every string leaf in a JSON-like value.\n * Non-string primitives, undefined, arrays, and plain objects are\n * walked structurally. Anything else (Date, Map, Set, etc.) is\n * returned unchanged — this package never serialises those.\n *\n * Returns a fresh value; the input is never mutated.\n */\nexport function scrubAnyValue(value: unknown): unknown {\n if (typeof value === 'string') return scrubPii(value)\n if (Array.isArray(value)) return value.map(scrubAnyValue)\n if (value !== null && typeof value === 'object') {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n out[k] = scrubAnyValue(v)\n }\n return out\n }\n return value\n}\n","/**\n * Instrument for `client.messages.create`.\n *\n * Wraps the Anthropic SDK's primary Messages endpoint. Handles both\n * transports:\n *\n * - Non-streaming: pulls text from `content[].text` blocks, tool\n * calls from `content[].tool_use` blocks (input is unknown JSON,\n * we serialise via JSON.stringify so the wire format matches the\n * openai wrapper's `arguments` string).\n *\n * - Streaming: a state machine over the event sequence\n * `message_start` → `content_block_start` → many\n * `content_block_delta` → `content_block_stop` → `message_delta`\n * → `message_stop`. We maintain a per-block aggregator keyed by\n * `index`. Text deltas concatenate `delta.text`. Tool-use deltas\n * carry `delta.partial_json` fragments that we append to that\n * block's `arguments` string. The final `message_delta` carries\n * the output_tokens; the initial `message_start` carries the\n * input_tokens + the two cache fields.\n *\n * Token surface emitted on `metadata.tokens`:\n * - input, output, total: always\n * - cache_read: only when `cache_read_input_tokens > 0`\n * - cache_creation: only when `cache_creation_input_tokens > 0`\n *\n * Privacy fan-out: identical contract to @voightxyz/openai\n * (`minimal` → tags only, `standard` → scrubbed content,\n * `full` → verbatim). The first tool call's name flows into\n * `toolExecuted` at every level so the audit-log DETAIL column\n * keeps rendering meaningfully even under minimal.\n */\n\nimport type { EventPayload, PrivacyLevel } from '../types.js'\nimport { scrubAnyValue, scrubPii } from '../privacy.js'\n\n// ─── Loose Anthropic types ────────────────────────────────────────\n//\n// We model only the surface this instrument actually reads. Keeping\n// the types loose insulates us from upstream SDK changes that don't\n// affect our wire shape.\n\ninterface MessageCreateParams {\n model: string\n max_tokens: number\n messages: Array<Record<string, unknown>>\n stream?: boolean\n [k: string]: unknown\n}\n\ninterface AnthropicUsage {\n input_tokens?: number\n output_tokens?: number\n cache_creation_input_tokens?: number | null\n cache_read_input_tokens?: number | null\n [k: string]: unknown\n}\n\ninterface TextContentBlock {\n type: 'text'\n text: string\n}\n\ninterface ToolUseContentBlock {\n type: 'tool_use'\n id: string\n name: string\n input: unknown\n}\n\ntype ContentBlock =\n | TextContentBlock\n | ToolUseContentBlock\n | { type: string; [k: string]: unknown }\n\ninterface NonStreamingMessage {\n id?: string\n model?: string\n content?: ContentBlock[]\n stop_reason?: string | null\n usage?: AnthropicUsage\n [k: string]: unknown\n}\n\n// Streaming events\ninterface StreamMessageStart {\n type: 'message_start'\n message: NonStreamingMessage\n}\ninterface StreamContentBlockStart {\n type: 'content_block_start'\n index: number\n content_block: ContentBlock\n}\ninterface StreamContentBlockDelta {\n type: 'content_block_delta'\n index: number\n delta:\n | { type: 'text_delta'; text: string }\n | { type: 'input_json_delta'; partial_json: string }\n | { type: string; [k: string]: unknown }\n}\ninterface StreamContentBlockStop {\n type: 'content_block_stop'\n index: number\n}\ninterface StreamMessageDelta {\n type: 'message_delta'\n delta: { stop_reason?: string | null; [k: string]: unknown }\n usage?: { output_tokens?: number; [k: string]: unknown }\n}\ninterface StreamMessageStop {\n type: 'message_stop'\n}\n\ntype StreamEvent =\n | StreamMessageStart\n | StreamContentBlockStart\n | StreamContentBlockDelta\n | StreamContentBlockStop\n | StreamMessageDelta\n | StreamMessageStop\n | { type: string; [k: string]: unknown }\n\ntype CreateFn = (\n params: MessageCreateParams,\n) => Promise<NonStreamingMessage | AsyncIterable<StreamEvent>>\n\n/**\n * Flat tool-call shape we emit. Mirrors the openai wrapper's\n * `metadata.toolCalls[*]` schema so dashboard rendering doesn't\n * need to know which provider produced the event.\n */\ninterface CapturedToolCall {\n id: string\n name: string\n arguments: string\n}\n\ninterface NormalisedTokens {\n input: number\n output: number\n total: number\n cache_read?: number\n cache_creation?: number\n}\n\nexport interface EventSink {\n send: (event: EventPayload) => void\n}\n\nexport interface InstrumentContext {\n agentId: string\n privacy: PrivacyLevel\n ingest: EventSink\n /** Time source in ms; injected so tests can produce deterministic `durationMs`. */\n now: () => number\n}\n\nexport function instrumentMessages(\n original: CreateFn,\n ctx: InstrumentContext,\n): CreateFn {\n return async function wrappedCreate(params: MessageCreateParams) {\n const startedAt = ctx.now()\n const isStream = params.stream === true\n\n let result: NonStreamingMessage | AsyncIterable<StreamEvent>\n try {\n result = await original(params)\n } catch (err) {\n ctx.ingest.send(\n buildFailureEvent({ ctx, params, startedAt, error: err }),\n )\n throw err\n }\n\n if (!isStream) {\n ctx.ingest.send(\n buildSuccessEvent({\n ctx,\n params,\n startedAt,\n response: result as NonStreamingMessage,\n }),\n )\n return result\n }\n\n return wrapStream(\n result as AsyncIterable<StreamEvent>,\n ctx,\n params,\n startedAt,\n )\n }\n}\n\n// ─── Event builders ──────────────────────────────────────────────\n\nfunction buildSuccessEvent(args: {\n ctx: InstrumentContext\n params: MessageCreateParams\n startedAt: number\n response: NonStreamingMessage\n}): EventPayload {\n const { ctx, params, startedAt, response } = args\n const responseText = extractText(response.content ?? [])\n const toolCalls = extractToolCalls(response.content ?? [])\n const tokens = normaliseTokens(response.usage)\n const durationMs = ctx.now() - startedAt\n\n return assembleEvent({\n ctx,\n params,\n durationMs,\n outcome: 'success',\n responseText: responseText.length > 0 ? responseText : undefined,\n tokens,\n toolCalls,\n streaming: false,\n finishReason: response.stop_reason ?? null,\n modelFromResponse: response.model,\n })\n}\n\nfunction buildFailureEvent(args: {\n ctx: InstrumentContext\n params: MessageCreateParams\n startedAt: number\n error: unknown\n}): EventPayload {\n const { ctx, params, startedAt, error } = args\n const durationMs = ctx.now() - startedAt\n const message = error instanceof Error ? error.message : String(error)\n return assembleEvent({\n ctx,\n params,\n durationMs,\n outcome: 'failed',\n streaming: params.stream === true,\n errorMessage: message,\n })\n}\n\nfunction buildStreamEvent(args: {\n ctx: InstrumentContext\n params: MessageCreateParams\n startedAt: number\n aggregatedText: string\n tokens: NormalisedTokens | null\n toolCalls: CapturedToolCall[] | null\n modelFromResponse: string | undefined\n finishReason: string | null\n}): EventPayload {\n return assembleEvent({\n ctx: args.ctx,\n params: args.params,\n durationMs: args.ctx.now() - args.startedAt,\n outcome: 'success',\n responseText:\n args.aggregatedText.length > 0 ? args.aggregatedText : undefined,\n tokens: args.tokens,\n toolCalls: args.toolCalls,\n streaming: true,\n finishReason: args.finishReason,\n modelFromResponse: args.modelFromResponse,\n })\n}\n\n/**\n * Single assembler — privacy fan-out + payload shape in one place\n * so the three callers above can't drift apart.\n */\nfunction assembleEvent(args: {\n ctx: InstrumentContext\n params: MessageCreateParams\n durationMs: number\n outcome: 'success' | 'failed'\n responseText?: string | undefined\n tokens?: NormalisedTokens | null\n toolCalls?: CapturedToolCall[] | null\n streaming: boolean\n finishReason?: string | null\n errorMessage?: string\n modelFromResponse?: string | undefined\n}): EventPayload {\n const { ctx, params, durationMs, outcome, streaming, errorMessage } = args\n const tokens = args.tokens ?? null\n const toolCalls = args.toolCalls ?? null\n const model = args.modelFromResponse ?? params.model\n\n const metadata: Record<string, unknown> = {\n source: 'anthropic-sdk',\n privacyLevel: ctx.privacy,\n streaming,\n }\n if (tokens) metadata.tokens = tokens\n if (args.finishReason !== undefined && args.finishReason !== null) {\n metadata.finishReason = args.finishReason\n }\n\n const firstToolName =\n toolCalls && toolCalls.length > 0 ? toolCalls[0]!.name : undefined\n\n if (ctx.privacy === 'minimal') {\n return {\n agentId: ctx.agentId,\n type: 'reasoning',\n model,\n durationMs,\n outcome,\n ...(firstToolName ? { toolExecuted: firstToolName } : {}),\n metadata,\n ...(errorMessage ? { errorMessage } : {}),\n }\n }\n\n const messages =\n ctx.privacy === 'standard'\n ? (scrubAnyValue(params.messages) as MessageCreateParams['messages'])\n : params.messages\n\n const responseText = args.responseText\n const scrubbedResponse =\n responseText !== undefined\n ? ctx.privacy === 'standard'\n ? scrubPii(responseText)\n : responseText\n : undefined\n if (scrubbedResponse !== undefined) {\n metadata.responseText = scrubbedResponse\n }\n\n if (toolCalls && toolCalls.length > 0) {\n metadata.toolCalls =\n ctx.privacy === 'standard'\n ? toolCalls.map((t) => ({\n id: t.id,\n name: t.name,\n arguments: scrubPii(t.arguments),\n }))\n : toolCalls\n }\n\n return {\n agentId: ctx.agentId,\n type: 'reasoning',\n model,\n durationMs,\n outcome,\n ...(firstToolName ? { toolExecuted: firstToolName } : {}),\n input: { messages },\n metadata,\n ...(errorMessage ? { errorMessage } : {}),\n }\n}\n\n// ─── Non-streaming helpers ──────────────────────────────────────\n\nfunction extractText(content: ContentBlock[]): string {\n let out = ''\n for (const block of content) {\n if (block.type === 'text' && typeof (block as TextContentBlock).text === 'string') {\n out += (block as TextContentBlock).text\n }\n }\n return out\n}\n\nfunction extractToolCalls(content: ContentBlock[]): CapturedToolCall[] | null {\n const out: CapturedToolCall[] = []\n for (const block of content) {\n if (block.type !== 'tool_use') continue\n const t = block as ToolUseContentBlock\n out.push({\n id: typeof t.id === 'string' ? t.id : '',\n name: typeof t.name === 'string' ? t.name : '',\n // Serialise the model's chosen tool input to a JSON string so\n // the wire shape matches openai's `arguments: string` exactly.\n // Anthropic gives us a parsed object; openai gives us the raw\n // string. We normalise here.\n arguments: safeStringify(t.input),\n })\n }\n return out.length > 0 ? out : null\n}\n\nfunction safeStringify(v: unknown): string {\n if (typeof v === 'string') return v\n try {\n return JSON.stringify(v ?? {})\n } catch {\n return ''\n }\n}\n\nfunction normaliseTokens(u: AnthropicUsage | undefined): NormalisedTokens | null {\n if (!u) return null\n const input = numberOrZero(u.input_tokens)\n const output = numberOrZero(u.output_tokens)\n const total = input + output\n const cacheRead = numberOrZero(u.cache_read_input_tokens)\n const cacheCreation = numberOrZero(u.cache_creation_input_tokens)\n const base: NormalisedTokens = { input, output, total }\n if (cacheRead > 0) base.cache_read = cacheRead\n if (cacheCreation > 0) base.cache_creation = cacheCreation\n return base\n}\n\nfunction numberOrZero(v: unknown): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : 0\n}\n\n// ─── Streaming wrapper ──────────────────────────────────────────\n\n/**\n * Mutating accumulator for the streaming state machine. We hold a\n * single source of truth across chunks: text aggregator, per-index\n * tool-call entries, latest usage seen, final stop_reason. The wrap\n * function yields each event to the user untouched and only mutates\n * this struct as bytes pass through.\n */\ninterface StreamState {\n aggregatedText: string\n toolBlocks: Map<number, CapturedToolCall>\n usage: AnthropicUsage | null\n modelFromResponse: string | undefined\n finishReason: string | null\n}\n\nfunction wrapStream(\n source: AsyncIterable<StreamEvent>,\n ctx: InstrumentContext,\n params: MessageCreateParams,\n startedAt: number,\n): AsyncIterable<StreamEvent> {\n const state: StreamState = {\n aggregatedText: '',\n toolBlocks: new Map(),\n usage: null,\n modelFromResponse: undefined,\n finishReason: null,\n }\n let emitted = false\n\n function emit() {\n if (emitted) return\n emitted = true\n ctx.ingest.send(\n buildStreamEvent({\n ctx,\n params,\n startedAt,\n aggregatedText: state.aggregatedText,\n tokens: normaliseTokens(state.usage ?? undefined),\n toolCalls: snapshotTools(state.toolBlocks),\n modelFromResponse: state.modelFromResponse,\n finishReason: state.finishReason,\n }),\n )\n }\n\n return {\n async *[Symbol.asyncIterator]() {\n try {\n for await (const ev of source) {\n applyEvent(state, ev)\n yield ev\n }\n } catch (err) {\n ctx.ingest.send(\n buildFailureEvent({ ctx, params, startedAt, error: err }),\n )\n emitted = true\n throw err\n } finally {\n emit()\n }\n },\n }\n}\n\n/**\n * Step the streaming state machine one event forward. Pure on the\n * `state` argument (mutates it in place). Unknown event types pass\n * through silently — Anthropic adds new event types over time and\n * we don't want a new event class to break capture.\n */\nfunction applyEvent(state: StreamState, ev: StreamEvent): void {\n switch (ev.type) {\n case 'message_start': {\n const e = ev as StreamMessageStart\n if (e.message?.model && !state.modelFromResponse) {\n state.modelFromResponse = e.message.model\n }\n if (e.message?.usage) {\n // message_start carries input_tokens + cache fields. We\n // seed `usage` here so the final tokens object includes\n // cache numbers even if message_delta only overwrites\n // output_tokens.\n state.usage = { ...(state.usage ?? {}), ...e.message.usage }\n }\n return\n }\n case 'content_block_start': {\n const e = ev as StreamContentBlockStart\n if (e.content_block?.type === 'tool_use') {\n const tu = e.content_block as ToolUseContentBlock\n state.toolBlocks.set(e.index, {\n id: typeof tu.id === 'string' ? tu.id : '',\n name: typeof tu.name === 'string' ? tu.name : '',\n arguments: '',\n })\n }\n return\n }\n case 'content_block_delta': {\n const e = ev as StreamContentBlockDelta\n const d = e.delta as { type: string; [k: string]: unknown }\n if (d.type === 'text_delta' && typeof d.text === 'string') {\n state.aggregatedText += d.text\n } else if (\n d.type === 'input_json_delta' &&\n typeof d.partial_json === 'string'\n ) {\n const entry = state.toolBlocks.get(e.index)\n if (entry) entry.arguments += d.partial_json\n }\n return\n }\n case 'message_delta': {\n const e = ev as StreamMessageDelta\n if (e.delta?.stop_reason && state.finishReason === null) {\n state.finishReason = e.delta.stop_reason\n }\n if (e.usage) {\n // message_delta only carries the final output_tokens. Merge,\n // don't replace, so we keep input_tokens + cache fields from\n // message_start.\n state.usage = { ...(state.usage ?? {}), ...e.usage }\n }\n return\n }\n // content_block_stop, message_stop, and unknown future event\n // types pass through without state updates.\n }\n}\n\nfunction snapshotTools(\n acc: Map<number, CapturedToolCall>,\n): CapturedToolCall[] | null {\n if (acc.size === 0) return null\n const entries = [...acc.entries()].sort(([a], [b]) => a - b)\n const out = entries.map(([, v]) => v).filter((t) => t.name.length > 0)\n return out.length > 0 ? out : null\n}\n","/**\n * `wrapAnthropic` — the public entrypoint of @voightxyz/anthropic.\n *\n * Layered `Proxy`: level 0 intercepts the `messages` property,\n * level 1 intercepts the `create` function on it. Everything outside\n * the `client.messages.create` path passes through untouched via\n * `Reflect.get`, so the legacy `completions` namespace, the model\n * list endpoints, batch endpoints, and any future SDK additions\n * keep working with zero special-casing.\n *\n * The proxy is one level shallower than the openai port because\n * Anthropic exposes `messages` at the top level (no intermediate\n * `chat` namespace).\n *\n * Failure modes are intentionally non-fatal — same contract as\n * @voightxyz/openai:\n *\n * - `enabled: false` → return the original client.\n * - no API key resolves → log a one-line warning and return\n * the original client.\n *\n * Internal `_fetch` and `_env` options exist so tests can drive\n * the network + environment surface without touching globals.\n */\n\nimport type { WrapOptions } from './types.js'\nimport { resolveApiKey, resolveAgent } from './identity.js'\nimport { createIngestClient } from './ingest.js'\nimport {\n instrumentMessages,\n type InstrumentContext,\n} from './instruments/messages.js'\n\ninterface InternalOptions extends WrapOptions {\n _fetch?: typeof fetch\n _env?: Record<string, string | undefined>\n}\n\nconst DEFAULT_API_BASE = 'https://api.voight.xyz'\n\nexport function wrapAnthropic<T extends object>(\n client: T,\n options: WrapOptions = {},\n): T {\n const opts = options as InternalOptions\n\n if (opts.enabled === false) return client\n\n const env = opts._env ?? process.env\n const apiKey = resolveApiKey(\n { voightApiKey: opts.voightApiKey, agent: opts.agent },\n env,\n )\n\n if (apiKey === null) {\n console.warn(\n '[voight] no VOIGHT_KEY resolved — wrapper is a pass-through. ' +\n 'Set process.env.VOIGHT_KEY or pass `voightApiKey` to wrapAnthropic() to enable capture.',\n )\n return client\n }\n\n const agentId = resolveAgent(\n { voightApiKey: opts.voightApiKey, agent: opts.agent },\n env,\n )\n\n const ingest = createIngestClient({\n apiBase: opts.apiBase ?? DEFAULT_API_BASE,\n apiKey,\n fetch: opts._fetch,\n })\n\n const ctx: InstrumentContext = {\n agentId,\n privacy: opts.privacy ?? 'standard',\n ingest,\n now: () => Date.now(),\n }\n\n return new Proxy(client, {\n get(target, prop, receiver) {\n if (prop === 'messages') {\n const messages = Reflect.get(target, prop, receiver)\n return wrapMessages(messages as object, ctx)\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n}\n\nfunction wrapMessages<M extends object>(\n messages: M,\n ctx: InstrumentContext,\n): M {\n return new Proxy(messages, {\n get(target, prop, receiver) {\n if (prop === 'create') {\n const original = Reflect.get(target, prop, receiver) as (\n params: never,\n ) => Promise<unknown>\n // .bind so `this` inside the SDK's `create` stays the real\n // messages instance, not the proxy. Without this the\n // Anthropic SDK loses access to its internal http client.\n return instrumentMessages(\n original.bind(target) as never,\n ctx,\n )\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voightxyz/anthropic",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"description": "Voight observability for the Anthropic SDK. Wrap your Anthropic client and capture every Messages call — prompts, tokens, costs, cache reads, tool use, latency, errors — surfaced live in the Voight dashboard.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"anthropic",
|
|
7
|
+
"claude",
|
|
8
|
+
"observability",
|
|
9
|
+
"ai-agents",
|
|
10
|
+
"voight",
|
|
11
|
+
"llm",
|
|
12
|
+
"monitoring",
|
|
13
|
+
"instrumentation"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://voight.xyz",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/voightxyz/voight-anthropic.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/voightxyz/voight-anthropic/issues"
|
|
22
|
+
},
|
|
23
|
+
"license": "Apache-2.0",
|
|
24
|
+
"author": "Voight (https://voight.xyz)",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "./dist/index.cjs",
|
|
27
|
+
"module": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"require": "./dist/index.cjs"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE",
|
|
40
|
+
"CHANGELOG.md"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"type-check": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"prepublishOnly": "npm run build"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@anthropic-ai/sdk": ">=0.30.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@anthropic-ai/sdk": "^0.96.0",
|
|
54
|
+
"@types/node": "^25.6.0",
|
|
55
|
+
"tsup": "^8.3.5",
|
|
56
|
+
"typescript": "^5.6.3",
|
|
57
|
+
"vitest": "^4.1.5"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=18"
|
|
61
|
+
},
|
|
62
|
+
"publishConfig": {
|
|
63
|
+
"access": "public",
|
|
64
|
+
"tag": "beta"
|
|
65
|
+
}
|
|
66
|
+
}
|