@argosvix/sdk 0.1.2 → 0.3.0-alpha.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 +44 -0
- package/LICENSE +1 -1
- package/README.md +102 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +211 -4
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pricing.d.ts.map +1 -1
- package/dist/pricing.js +11 -1
- package/dist/pricing.js.map +1 -1
- package/dist/query.d.ts +140 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +87 -0
- package/dist/query.js.map +1 -0
- package/dist/recorder.d.ts +9 -0
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +49 -1
- package/dist/recorder.js.map +1 -1
- package/dist/redaction.d.ts +48 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +111 -0
- package/dist/redaction.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,50 @@ All notable changes to `@argosvix/sdk` are documented in this file.
|
|
|
4
4
|
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.3.0-alpha.1] - 2026-06-01
|
|
8
|
+
|
|
9
|
+
Pre-release adding the foundation for the **Pro+ plaintext content capture**
|
|
10
|
+
feature. The SDK can now optionally send prompt and completion bodies in
|
|
11
|
+
addition to metadata when the user explicitly opts in via dashboard consent.
|
|
12
|
+
The backend gate (`PLAINTEXT_STORAGE_ENABLED` env + per-account
|
|
13
|
+
`plaintext_storage_optin` column) is still closed by default, so this release
|
|
14
|
+
does not actually persist plaintext anywhere until the feature ships in
|
|
15
|
+
production.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `ArgosvixConfig.captureContent` (default `false`). When set to `true`, the
|
|
19
|
+
SDK extracts prompt / completion text from supported providers and forwards
|
|
20
|
+
them on the ingest payload. Without this flag, no plaintext is ever sent.
|
|
21
|
+
- `ArgosvixConfig.disablePiiRedaction` (default `false`). When `captureContent`
|
|
22
|
+
is `true`, the SDK runs a built-in PII redaction pass (email, credit card,
|
|
23
|
+
phone, マイナンバー, IPv4, IPv6) over the captured bodies before send.
|
|
24
|
+
Setting this flag disables the filter at the user's own risk.
|
|
25
|
+
- `LlmCallRecord.promptBody`, `completionBody`, and `toolCalls` fields populated
|
|
26
|
+
only when `captureContent` is `true`.
|
|
27
|
+
- New `redaction.ts` module with `redactPii()` and `redactToolCall()` helpers.
|
|
28
|
+
- Double safety net inside `Recorder.record()`: even if a wrapper accidentally
|
|
29
|
+
attaches `promptBody` while `captureContent` is `false`, the recorder strips
|
|
30
|
+
the field before buffering. This makes wrapper bugs non-leaky.
|
|
31
|
+
|
|
32
|
+
### Provider coverage in this release
|
|
33
|
+
- OpenAI Chat Completions (non-stream success path): prompt body, completion
|
|
34
|
+
body, and tool calls captured.
|
|
35
|
+
- Anthropic Messages (non-stream success path): prompt body and completion body
|
|
36
|
+
captured (tool calls in a follow-up).
|
|
37
|
+
- OpenAI Responses, Mistral, Gemini (Legacy and New): metadata still recorded,
|
|
38
|
+
but `promptBody` / `completionBody` are omitted. A one-time `console.warn`
|
|
39
|
+
fires when `captureContent` is enabled to make this visible.
|
|
40
|
+
- All streaming paths across every provider: same as above. Coverage will
|
|
41
|
+
expand in `0.3.0-alpha.2+`.
|
|
42
|
+
|
|
43
|
+
### Notes
|
|
44
|
+
- This is an `alpha` pre-release. Do not enable `captureContent` in production
|
|
45
|
+
until the corresponding dashboard opt-in UI ships and the legal documents
|
|
46
|
+
(v2.1) flip to production. See https://argosvix.com/ja/docs/plaintext for the
|
|
47
|
+
rollout status.
|
|
48
|
+
- Existing users who do not set `captureContent` are unaffected. The default
|
|
49
|
+
behavior — metadata-only ingest — is byte-identical to 0.2.0.
|
|
50
|
+
|
|
7
51
|
## [0.1.2] - 2026-05-22
|
|
8
52
|
|
|
9
53
|
Documentation-only release that aligns the npm README with global SaaS norms.
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -140,6 +140,108 @@ The [`examples/`](../../examples/) directory contains minimum-viable integration
|
|
|
140
140
|
- [`examples/lambda-gemini/`](../../examples/lambda-gemini/) — AWS Lambda with `@google/genai` (note: container-reuse semantics).
|
|
141
141
|
- [`examples/workers-anthropic/`](../../examples/workers-anthropic/) — Cloudflare Workers with the `flushClient` pattern.
|
|
142
142
|
|
|
143
|
+
## Alert webhooks
|
|
144
|
+
|
|
145
|
+
Argosvix can deliver alert notifications to any HTTPS endpoint of your choice (in addition to email and Slack). Configure a webhook channel from <https://dashboard.argosvix.com/en/alerts>; this section documents the public contract so you can implement a receiver.
|
|
146
|
+
|
|
147
|
+
### Delivery
|
|
148
|
+
|
|
149
|
+
- Method: `POST`
|
|
150
|
+
- Content-Type: `application/json`
|
|
151
|
+
- Timeout: 5 seconds (no retry — the next evaluator cycle will re-fire if the condition still holds)
|
|
152
|
+
- Redirects: disabled (`fetch redirect: "error"`)
|
|
153
|
+
- URL constraints: HTTPS only; private / loopback / link-local / cloud-metadata IPs and URLs with credentials are rejected at registration time
|
|
154
|
+
|
|
155
|
+
### Headers
|
|
156
|
+
|
|
157
|
+
| Header | Value |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `User-Agent` | `Argosvix-Webhook/1.0` |
|
|
160
|
+
| `X-Argosvix-Event` | `alert.triggered` (only event type for now) |
|
|
161
|
+
| `X-Argosvix-Signature` | `sha256=<hex>` — present only when a signing secret is configured |
|
|
162
|
+
| `X-Argosvix-Timestamp` | ISO-8601 UTC — present only when a signing secret is configured |
|
|
163
|
+
|
|
164
|
+
### Payload
|
|
165
|
+
|
|
166
|
+
```jsonc
|
|
167
|
+
{
|
|
168
|
+
"alertId": "01HXZ...",
|
|
169
|
+
"name": "monthly cost > $50",
|
|
170
|
+
"alertType": "monthly_budget", // cost_threshold | error_rate | latency_degradation | monthly_budget
|
|
171
|
+
"thresholdValue": 50,
|
|
172
|
+
"observedValue": 64.20,
|
|
173
|
+
"windowMinutes": 60,
|
|
174
|
+
"windowDescription": "60 min",
|
|
175
|
+
"filterProvider": "openai", // or null
|
|
176
|
+
"filterModel": null, // or string
|
|
177
|
+
"comparison": "exceeded threshold",
|
|
178
|
+
"triggeredAt": "2026-05-25T10:23:45.678Z",
|
|
179
|
+
"dashboardUrl": "https://dashboard.argosvix.com/en/alerts?alertId=01HXZ..."
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`thresholdValue` and `observedValue` units are USD for cost alerts, percent for `error_rate`, and milliseconds for `latency_degradation`. Schema is stable: new fields may be added (additive only); existing fields keep their names and semantics.
|
|
184
|
+
|
|
185
|
+
### Verifying the signature
|
|
186
|
+
|
|
187
|
+
When a signing secret is configured, the signature header is computed as:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
X-Argosvix-Signature = "sha256=" + hex(HMAC_SHA256(secret, X-Argosvix-Timestamp + "." + body))
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Reject any request whose signature does not match (and optionally drop requests with a timestamp older than your replay window).
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// Node 18+ — verifyArgosvixSignature.ts
|
|
197
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
198
|
+
|
|
199
|
+
export function verifyArgosvixSignature(
|
|
200
|
+
secret: string,
|
|
201
|
+
body: string, // raw request body string
|
|
202
|
+
signatureHeader: string, // "sha256=<hex>"
|
|
203
|
+
timestampHeader: string, // ISO-8601 string
|
|
204
|
+
maxAgeMs = 5 * 60 * 1000,
|
|
205
|
+
): boolean {
|
|
206
|
+
const ts = Date.parse(timestampHeader);
|
|
207
|
+
if (Number.isNaN(ts) || Math.abs(Date.now() - ts) > maxAgeMs) return false;
|
|
208
|
+
|
|
209
|
+
const expected = "sha256=" +
|
|
210
|
+
createHmac("sha256", secret).update(`${timestampHeader}.${body}`).digest("hex");
|
|
211
|
+
const a = Buffer.from(signatureHeader, "utf8");
|
|
212
|
+
const b = Buffer.from(expected, "utf8");
|
|
213
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
# Python 3 — verify_argosvix_signature.py
|
|
219
|
+
import hashlib, hmac, time
|
|
220
|
+
from datetime import datetime, timezone
|
|
221
|
+
|
|
222
|
+
def verify_argosvix_signature(secret: str, body: str,
|
|
223
|
+
signature_header: str, timestamp_header: str,
|
|
224
|
+
max_age_seconds: int = 300) -> bool:
|
|
225
|
+
try:
|
|
226
|
+
ts = datetime.fromisoformat(timestamp_header.replace("Z", "+00:00"))
|
|
227
|
+
except ValueError:
|
|
228
|
+
return False
|
|
229
|
+
if abs(time.time() - ts.timestamp()) > max_age_seconds:
|
|
230
|
+
return False
|
|
231
|
+
expected = "sha256=" + hmac.new(
|
|
232
|
+
secret.encode(), f"{timestamp_header}.{body}".encode(), hashlib.sha256
|
|
233
|
+
).hexdigest()
|
|
234
|
+
return hmac.compare_digest(signature_header, expected)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Tag-key conventions
|
|
238
|
+
|
|
239
|
+
Tag keys used in queries (`channelTargets.webhook.url` request paths, dashboard filters, etc.) must match `^[A-Za-z0-9_](?:[A-Za-z0-9_-]{0,62}[A-Za-z0-9_])?$` — alphanumeric plus `_` `-`, no leading/trailing hyphen, 1–64 characters. Sticking to this in the SDK `tags: { ... }` map keeps every downstream filter / aggregation usable.
|
|
240
|
+
|
|
241
|
+
### Residual SSRF risk (DNS rebinding)
|
|
242
|
+
|
|
243
|
+
Hostname validation happens at registration and at delivery time, but Cloudflare Workers does not expose connect-level IP control, so an attacker-controlled DNS that flips a public name to an RFC1918 / metadata IP between checks can still bypass the policy. Treat webhook receivers as part of your trust boundary; do not host them inside private networks that your perimeter would otherwise protect.
|
|
244
|
+
|
|
143
245
|
## API
|
|
144
246
|
|
|
145
247
|
### `wrap<T>(client: T, config?: ArgosvixConfig): T`
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,YAAY,CAAC;AAE1E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAKzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAE,cAAmB,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,YAAY,CAAC;AAE1E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAKzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAE,cAAmB,GAAG,CAAC,CA6ChF;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAE3D"}
|
package/dist/client.js
CHANGED
|
@@ -56,6 +56,20 @@ export function wrap(client, config = {}) {
|
|
|
56
56
|
break;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
// 平文 capture が有効でも、未対応 provider / 経路では promptBody や
|
|
60
|
+
// completionBody が乗らないことを 1 回だけ console.warn で明示する。
|
|
61
|
+
// v0.3.0-alpha.1 対応 = openai (Chat Completions) と anthropic (Messages)
|
|
62
|
+
// の非ストリーミング success path のみ。 OpenAI Responses や Mistral、
|
|
63
|
+
// Gemini、 全 streaming 経路は段階追加。
|
|
64
|
+
if (config.captureContent === true) {
|
|
65
|
+
if (provider === "openai" && isOpenAIResponsesLike(client)) {
|
|
66
|
+
warnUnsupportedCaptureProvider("openai (Responses API)");
|
|
67
|
+
}
|
|
68
|
+
if (provider === "mistral")
|
|
69
|
+
warnUnsupportedCaptureProvider("mistral");
|
|
70
|
+
if (provider === "gemini")
|
|
71
|
+
warnUnsupportedCaptureProvider("gemini");
|
|
72
|
+
}
|
|
59
73
|
wrappedClients.set(client, recorder);
|
|
60
74
|
return client;
|
|
61
75
|
}
|
|
@@ -95,6 +109,153 @@ function detectProvider(client, override) {
|
|
|
95
109
|
return "openai";
|
|
96
110
|
return "unknown";
|
|
97
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* F-2 carry = wrap config に渡された trace 軸 (traceId / spanId /
|
|
114
|
+
* parentSpanId) を record に乗せる helper。 全 provider の record()
|
|
115
|
+
* 呼び出しで spread して 一律 carry する。 未指定 field は object に
|
|
116
|
+
* 出さない (= JSON 送信 payload 軽量化 + backend で undefined と NULL
|
|
117
|
+
* の区別 不要)。
|
|
118
|
+
*/
|
|
119
|
+
function buildTraceMeta(config) {
|
|
120
|
+
const meta = {};
|
|
121
|
+
if (config.traceId)
|
|
122
|
+
meta.traceId = config.traceId;
|
|
123
|
+
if (config.spanId)
|
|
124
|
+
meta.spanId = config.spanId;
|
|
125
|
+
if (config.parentSpanId)
|
|
126
|
+
meta.parentSpanId = config.parentSpanId;
|
|
127
|
+
return meta;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Pro+ 平文保存機能の SDK 側 opt-in 抽出 helper。
|
|
131
|
+
*
|
|
132
|
+
* captureContent が true のときに wrapper が呼んで、 provider 毎の
|
|
133
|
+
* request / response 形状から prompt / completion / tool calls を 文字列
|
|
134
|
+
* (および JSON 文字列)として抽出する。 PII redaction は Recorder 側で
|
|
135
|
+
* 一括して適用されるので、 ここでは生の文字列を返すのみ。
|
|
136
|
+
*
|
|
137
|
+
* v0.3.0-alpha.1 段階の対応 provider:
|
|
138
|
+
* - openai (Chat Completions) = 全項目対応
|
|
139
|
+
* - anthropic (Messages) = prompt + completion 対応(tool call は段階追加)
|
|
140
|
+
* - 他 provider と全 streaming 経路 = 未対応、 captureContent が true でも
|
|
141
|
+
* promptBody / completionBody は付与されない(段階4 以降で順次追加)。
|
|
142
|
+
* console.warn で明示する。
|
|
143
|
+
*/
|
|
144
|
+
function extractOpenAIChatPromptBody(requestArgs) {
|
|
145
|
+
if (!Array.isArray(requestArgs.messages) || requestArgs.messages.length === 0) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
return JSON.stringify(requestArgs.messages);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function extractOpenAIChatCompletionBody(response) {
|
|
156
|
+
if (!response || typeof response !== "object")
|
|
157
|
+
return undefined;
|
|
158
|
+
const choices = response.choices;
|
|
159
|
+
if (!Array.isArray(choices) || choices.length === 0)
|
|
160
|
+
return undefined;
|
|
161
|
+
const first = choices[0];
|
|
162
|
+
const content = first?.message?.content;
|
|
163
|
+
if (typeof content === "string")
|
|
164
|
+
return content;
|
|
165
|
+
// content が array (multi-modal の場合) の場合は JSON 文字列で carry
|
|
166
|
+
if (Array.isArray(content)) {
|
|
167
|
+
try {
|
|
168
|
+
return JSON.stringify(content);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
function extractOpenAIChatToolCalls(response) {
|
|
177
|
+
if (!response || typeof response !== "object")
|
|
178
|
+
return undefined;
|
|
179
|
+
const choices = response.choices;
|
|
180
|
+
if (!Array.isArray(choices) || choices.length === 0)
|
|
181
|
+
return undefined;
|
|
182
|
+
const first = choices[0];
|
|
183
|
+
const toolCalls = first?.message?.tool_calls;
|
|
184
|
+
if (!Array.isArray(toolCalls) || toolCalls.length === 0)
|
|
185
|
+
return undefined;
|
|
186
|
+
const result = [];
|
|
187
|
+
for (const tc of toolCalls) {
|
|
188
|
+
if (!tc || typeof tc !== "object")
|
|
189
|
+
continue;
|
|
190
|
+
const fn = tc.function;
|
|
191
|
+
const name = typeof fn?.name === "string" ? fn.name : "unknown";
|
|
192
|
+
const args = typeof fn?.arguments === "string"
|
|
193
|
+
? fn.arguments
|
|
194
|
+
: fn?.arguments !== undefined
|
|
195
|
+
? safeStringify(fn.arguments)
|
|
196
|
+
: undefined;
|
|
197
|
+
result.push(args !== undefined ? { name, arguments: args } : { name });
|
|
198
|
+
}
|
|
199
|
+
return result.length > 0 ? result : undefined;
|
|
200
|
+
}
|
|
201
|
+
function extractAnthropicPromptBody(requestArgs) {
|
|
202
|
+
if (!Array.isArray(requestArgs.messages) || requestArgs.messages.length === 0) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
return JSON.stringify(requestArgs.messages);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function extractAnthropicCompletionBody(response) {
|
|
213
|
+
if (!response || typeof response !== "object")
|
|
214
|
+
return undefined;
|
|
215
|
+
// Anthropic response shape = { content: [{ type: "text", text: "..." }, ...] }
|
|
216
|
+
const content = response.content;
|
|
217
|
+
if (Array.isArray(content)) {
|
|
218
|
+
const texts = [];
|
|
219
|
+
for (const block of content) {
|
|
220
|
+
if (block && typeof block === "object") {
|
|
221
|
+
const t = block.type;
|
|
222
|
+
const txt = block.text;
|
|
223
|
+
if (t === "text" && typeof txt === "string") {
|
|
224
|
+
texts.push(txt);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (texts.length > 0)
|
|
229
|
+
return texts.join("\n");
|
|
230
|
+
// text block がなければ全 content を JSON 文字列として carry
|
|
231
|
+
try {
|
|
232
|
+
return JSON.stringify(content);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
function safeStringify(value) {
|
|
241
|
+
try {
|
|
242
|
+
return JSON.stringify(value);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
let captureContentWarnedProviders = null;
|
|
249
|
+
function warnUnsupportedCaptureProvider(label) {
|
|
250
|
+
if (captureContentWarnedProviders === null) {
|
|
251
|
+
captureContentWarnedProviders = new Set();
|
|
252
|
+
}
|
|
253
|
+
if (captureContentWarnedProviders.has(label))
|
|
254
|
+
return;
|
|
255
|
+
captureContentWarnedProviders.add(label);
|
|
256
|
+
// eslint-disable-next-line no-console
|
|
257
|
+
console.warn(`[argosvix] captureContent is enabled but ${label} body extraction is not yet supported in this SDK version. Metadata is still recorded; plaintext is omitted. Coverage will expand in subsequent releases.`);
|
|
258
|
+
}
|
|
98
259
|
function extractErrorDetails(err) {
|
|
99
260
|
if (!err || typeof err !== "object")
|
|
100
261
|
return undefined;
|
|
@@ -139,7 +300,7 @@ function wrapOpenAIChat(client, recorder, config) {
|
|
|
139
300
|
const model = r.model || requestArgs.model || "unknown";
|
|
140
301
|
const promptTokens = r.usage?.prompt_tokens ?? 0;
|
|
141
302
|
const completionTokens = r.usage?.completion_tokens ?? 0;
|
|
142
|
-
|
|
303
|
+
const record = {
|
|
143
304
|
id,
|
|
144
305
|
provider: "openai",
|
|
145
306
|
model,
|
|
@@ -150,8 +311,21 @@ function wrapOpenAIChat(client, recorder, config) {
|
|
|
150
311
|
latencyMs,
|
|
151
312
|
timestamp: new Date().toISOString(),
|
|
152
313
|
tags: { ...(config.tags ?? {}) },
|
|
314
|
+
...buildTraceMeta(config),
|
|
153
315
|
requestMeta: buildOpenAIRequestMeta(requestArgs),
|
|
154
|
-
}
|
|
316
|
+
};
|
|
317
|
+
if (config.captureContent === true) {
|
|
318
|
+
const promptBody = extractOpenAIChatPromptBody(requestArgs);
|
|
319
|
+
if (promptBody !== undefined)
|
|
320
|
+
record.promptBody = promptBody;
|
|
321
|
+
const completionBody = extractOpenAIChatCompletionBody(response);
|
|
322
|
+
if (completionBody !== undefined)
|
|
323
|
+
record.completionBody = completionBody;
|
|
324
|
+
const toolCalls = extractOpenAIChatToolCalls(response);
|
|
325
|
+
if (toolCalls !== undefined)
|
|
326
|
+
record.toolCalls = toolCalls;
|
|
327
|
+
}
|
|
328
|
+
recorder.record(record);
|
|
155
329
|
return response;
|
|
156
330
|
}
|
|
157
331
|
catch (err) {
|
|
@@ -167,6 +341,7 @@ function wrapOpenAIChat(client, recorder, config) {
|
|
|
167
341
|
latencyMs: Date.now() - start,
|
|
168
342
|
timestamp: new Date().toISOString(),
|
|
169
343
|
tags: { ...(config.tags ?? {}) },
|
|
344
|
+
...buildTraceMeta(config),
|
|
170
345
|
error: err instanceof Error ? err.message : String(err),
|
|
171
346
|
...(errorDetails ? { errorDetails } : {}),
|
|
172
347
|
requestMeta: buildOpenAIRequestMeta(requestArgs),
|
|
@@ -204,6 +379,7 @@ async function* wrapOpenAIStream(stream, recorder, requestArgs, start, id, confi
|
|
|
204
379
|
latencyMs: Date.now() - start,
|
|
205
380
|
timestamp: new Date().toISOString(),
|
|
206
381
|
tags: { ...(config.tags ?? {}) },
|
|
382
|
+
...buildTraceMeta(config),
|
|
207
383
|
requestMeta: buildOpenAIRequestMeta(requestArgs),
|
|
208
384
|
});
|
|
209
385
|
}
|
|
@@ -220,6 +396,7 @@ async function* wrapOpenAIStream(stream, recorder, requestArgs, start, id, confi
|
|
|
220
396
|
latencyMs: Date.now() - start,
|
|
221
397
|
timestamp: new Date().toISOString(),
|
|
222
398
|
tags: { ...(config.tags ?? {}) },
|
|
399
|
+
...buildTraceMeta(config),
|
|
223
400
|
error: err instanceof Error ? err.message : String(err),
|
|
224
401
|
...(errorDetails ? { errorDetails } : {}),
|
|
225
402
|
requestMeta: buildOpenAIRequestMeta(requestArgs),
|
|
@@ -273,6 +450,7 @@ function wrapOpenAIResponses(client, recorder, config) {
|
|
|
273
450
|
latencyMs,
|
|
274
451
|
timestamp: new Date().toISOString(),
|
|
275
452
|
tags: { ...(config.tags ?? {}) },
|
|
453
|
+
...buildTraceMeta(config),
|
|
276
454
|
requestMeta: buildResponsesRequestMeta(requestArgs),
|
|
277
455
|
});
|
|
278
456
|
return response;
|
|
@@ -290,6 +468,7 @@ function wrapOpenAIResponses(client, recorder, config) {
|
|
|
290
468
|
latencyMs: Date.now() - start,
|
|
291
469
|
timestamp: new Date().toISOString(),
|
|
292
470
|
tags: { ...(config.tags ?? {}) },
|
|
471
|
+
...buildTraceMeta(config),
|
|
293
472
|
error: err instanceof Error ? err.message : String(err),
|
|
294
473
|
...(errorDetails ? { errorDetails } : {}),
|
|
295
474
|
requestMeta: buildResponsesRequestMeta(requestArgs),
|
|
@@ -332,7 +511,7 @@ function wrapAnthropic(client, recorder, config) {
|
|
|
332
511
|
const model = r.model || requestArgs.model || "unknown";
|
|
333
512
|
const promptTokens = r.usage?.input_tokens ?? 0;
|
|
334
513
|
const completionTokens = r.usage?.output_tokens ?? 0;
|
|
335
|
-
|
|
514
|
+
const record = {
|
|
336
515
|
id,
|
|
337
516
|
provider: "anthropic",
|
|
338
517
|
model,
|
|
@@ -343,8 +522,18 @@ function wrapAnthropic(client, recorder, config) {
|
|
|
343
522
|
latencyMs,
|
|
344
523
|
timestamp: new Date().toISOString(),
|
|
345
524
|
tags: { ...(config.tags ?? {}) },
|
|
525
|
+
...buildTraceMeta(config),
|
|
346
526
|
requestMeta: buildAnthropicRequestMeta(requestArgs),
|
|
347
|
-
}
|
|
527
|
+
};
|
|
528
|
+
if (config.captureContent === true) {
|
|
529
|
+
const promptBody = extractAnthropicPromptBody(requestArgs);
|
|
530
|
+
if (promptBody !== undefined)
|
|
531
|
+
record.promptBody = promptBody;
|
|
532
|
+
const completionBody = extractAnthropicCompletionBody(response);
|
|
533
|
+
if (completionBody !== undefined)
|
|
534
|
+
record.completionBody = completionBody;
|
|
535
|
+
}
|
|
536
|
+
recorder.record(record);
|
|
348
537
|
return response;
|
|
349
538
|
}
|
|
350
539
|
catch (err) {
|
|
@@ -360,6 +549,7 @@ function wrapAnthropic(client, recorder, config) {
|
|
|
360
549
|
latencyMs: Date.now() - start,
|
|
361
550
|
timestamp: new Date().toISOString(),
|
|
362
551
|
tags: { ...(config.tags ?? {}) },
|
|
552
|
+
...buildTraceMeta(config),
|
|
363
553
|
error: err instanceof Error ? err.message : String(err),
|
|
364
554
|
...(errorDetails ? { errorDetails } : {}),
|
|
365
555
|
requestMeta: buildAnthropicRequestMeta(requestArgs),
|
|
@@ -398,6 +588,7 @@ async function* wrapAnthropicStream(stream, recorder, requestArgs, start, id, co
|
|
|
398
588
|
latencyMs: Date.now() - start,
|
|
399
589
|
timestamp: new Date().toISOString(),
|
|
400
590
|
tags: { ...(config.tags ?? {}) },
|
|
591
|
+
...buildTraceMeta(config),
|
|
401
592
|
requestMeta: buildAnthropicRequestMeta(requestArgs),
|
|
402
593
|
});
|
|
403
594
|
}
|
|
@@ -414,6 +605,7 @@ async function* wrapAnthropicStream(stream, recorder, requestArgs, start, id, co
|
|
|
414
605
|
latencyMs: Date.now() - start,
|
|
415
606
|
timestamp: new Date().toISOString(),
|
|
416
607
|
tags: { ...(config.tags ?? {}) },
|
|
608
|
+
...buildTraceMeta(config),
|
|
417
609
|
error: err instanceof Error ? err.message : String(err),
|
|
418
610
|
...(errorDetails ? { errorDetails } : {}),
|
|
419
611
|
requestMeta: buildAnthropicRequestMeta(requestArgs),
|
|
@@ -459,6 +651,7 @@ function wrapMistral(client, recorder, config) {
|
|
|
459
651
|
latencyMs,
|
|
460
652
|
timestamp: new Date().toISOString(),
|
|
461
653
|
tags: { ...(config.tags ?? {}) },
|
|
654
|
+
...buildTraceMeta(config),
|
|
462
655
|
requestMeta: buildMistralRequestMeta(requestArgs),
|
|
463
656
|
});
|
|
464
657
|
return response;
|
|
@@ -476,6 +669,7 @@ function wrapMistral(client, recorder, config) {
|
|
|
476
669
|
latencyMs: Date.now() - start,
|
|
477
670
|
timestamp: new Date().toISOString(),
|
|
478
671
|
tags: { ...(config.tags ?? {}) },
|
|
672
|
+
...buildTraceMeta(config),
|
|
479
673
|
error: err instanceof Error ? err.message : String(err),
|
|
480
674
|
...(errorDetails ? { errorDetails } : {}),
|
|
481
675
|
requestMeta: buildMistralRequestMeta(requestArgs),
|
|
@@ -507,6 +701,7 @@ function wrapMistral(client, recorder, config) {
|
|
|
507
701
|
latencyMs: Date.now() - start,
|
|
508
702
|
timestamp: new Date().toISOString(),
|
|
509
703
|
tags: { ...(config.tags ?? {}) },
|
|
704
|
+
...buildTraceMeta(config),
|
|
510
705
|
error: err instanceof Error ? err.message : String(err),
|
|
511
706
|
...(errorDetails ? { errorDetails } : {}),
|
|
512
707
|
requestMeta: buildMistralRequestMeta(requestArgs),
|
|
@@ -546,6 +741,7 @@ async function* wrapMistralStream(stream, recorder, requestArgs, start, id, conf
|
|
|
546
741
|
latencyMs: Date.now() - start,
|
|
547
742
|
timestamp: new Date().toISOString(),
|
|
548
743
|
tags: { ...(config.tags ?? {}) },
|
|
744
|
+
...buildTraceMeta(config),
|
|
549
745
|
requestMeta: buildMistralRequestMeta(requestArgs),
|
|
550
746
|
});
|
|
551
747
|
}
|
|
@@ -562,6 +758,7 @@ async function* wrapMistralStream(stream, recorder, requestArgs, start, id, conf
|
|
|
562
758
|
latencyMs: Date.now() - start,
|
|
563
759
|
timestamp: new Date().toISOString(),
|
|
564
760
|
tags: { ...(config.tags ?? {}) },
|
|
761
|
+
...buildTraceMeta(config),
|
|
565
762
|
error: err instanceof Error ? err.message : String(err),
|
|
566
763
|
...(errorDetails ? { errorDetails } : {}),
|
|
567
764
|
requestMeta: buildMistralRequestMeta(requestArgs),
|
|
@@ -619,6 +816,7 @@ function wrapGeminiLegacyModel(model, modelName, recorder, config) {
|
|
|
619
816
|
latencyMs: Date.now() - start,
|
|
620
817
|
timestamp: new Date().toISOString(),
|
|
621
818
|
tags: { ...(config.tags ?? {}) },
|
|
819
|
+
...buildTraceMeta(config),
|
|
622
820
|
requestMeta: buildGeminiRequestMeta(requestArgs),
|
|
623
821
|
});
|
|
624
822
|
return result;
|
|
@@ -636,6 +834,7 @@ function wrapGeminiLegacyModel(model, modelName, recorder, config) {
|
|
|
636
834
|
latencyMs: Date.now() - start,
|
|
637
835
|
timestamp: new Date().toISOString(),
|
|
638
836
|
tags: { ...(config.tags ?? {}) },
|
|
837
|
+
...buildTraceMeta(config),
|
|
639
838
|
error: err instanceof Error ? err.message : String(err),
|
|
640
839
|
...(errorDetails ? { errorDetails } : {}),
|
|
641
840
|
requestMeta: buildGeminiRequestMeta(requestArgs),
|
|
@@ -675,6 +874,7 @@ function wrapGeminiLegacyModel(model, modelName, recorder, config) {
|
|
|
675
874
|
latencyMs: Date.now() - start,
|
|
676
875
|
timestamp: new Date().toISOString(),
|
|
677
876
|
tags: { ...(config.tags ?? {}) },
|
|
877
|
+
...buildTraceMeta(config),
|
|
678
878
|
requestMeta: buildGeminiRequestMeta(requestArgs),
|
|
679
879
|
});
|
|
680
880
|
}
|
|
@@ -691,6 +891,7 @@ function wrapGeminiLegacyModel(model, modelName, recorder, config) {
|
|
|
691
891
|
latencyMs: Date.now() - start,
|
|
692
892
|
timestamp: new Date().toISOString(),
|
|
693
893
|
tags: { ...(config.tags ?? {}) },
|
|
894
|
+
...buildTraceMeta(config),
|
|
694
895
|
error: err instanceof Error ? err.message : String(err),
|
|
695
896
|
...(errorDetails ? { errorDetails } : {}),
|
|
696
897
|
requestMeta: buildGeminiRequestMeta(requestArgs),
|
|
@@ -713,6 +914,7 @@ function wrapGeminiLegacyModel(model, modelName, recorder, config) {
|
|
|
713
914
|
latencyMs: Date.now() - start,
|
|
714
915
|
timestamp: new Date().toISOString(),
|
|
715
916
|
tags: { ...(config.tags ?? {}) },
|
|
917
|
+
...buildTraceMeta(config),
|
|
716
918
|
error: err instanceof Error ? err.message : String(err),
|
|
717
919
|
...(errorDetails ? { errorDetails } : {}),
|
|
718
920
|
requestMeta: buildGeminiRequestMeta(requestArgs),
|
|
@@ -750,6 +952,7 @@ function wrapGeminiNew(client, recorder, config) {
|
|
|
750
952
|
latencyMs: Date.now() - start,
|
|
751
953
|
timestamp: new Date().toISOString(),
|
|
752
954
|
tags: { ...(config.tags ?? {}) },
|
|
955
|
+
...buildTraceMeta(config),
|
|
753
956
|
requestMeta: buildGeminiNewRequestMeta(requestArgs),
|
|
754
957
|
});
|
|
755
958
|
return result;
|
|
@@ -767,6 +970,7 @@ function wrapGeminiNew(client, recorder, config) {
|
|
|
767
970
|
latencyMs: Date.now() - start,
|
|
768
971
|
timestamp: new Date().toISOString(),
|
|
769
972
|
tags: { ...(config.tags ?? {}) },
|
|
973
|
+
...buildTraceMeta(config),
|
|
770
974
|
error: err instanceof Error ? err.message : String(err),
|
|
771
975
|
...(errorDetails ? { errorDetails } : {}),
|
|
772
976
|
requestMeta: buildGeminiNewRequestMeta(requestArgs),
|
|
@@ -805,6 +1009,7 @@ function wrapGeminiNew(client, recorder, config) {
|
|
|
805
1009
|
latencyMs: Date.now() - start,
|
|
806
1010
|
timestamp: new Date().toISOString(),
|
|
807
1011
|
tags: { ...(config.tags ?? {}) },
|
|
1012
|
+
...buildTraceMeta(config),
|
|
808
1013
|
requestMeta: buildGeminiNewRequestMeta(requestArgs),
|
|
809
1014
|
});
|
|
810
1015
|
}
|
|
@@ -823,6 +1028,7 @@ function wrapGeminiNew(client, recorder, config) {
|
|
|
823
1028
|
latencyMs: Date.now() - start,
|
|
824
1029
|
timestamp: new Date().toISOString(),
|
|
825
1030
|
tags: { ...(config.tags ?? {}) },
|
|
1031
|
+
...buildTraceMeta(config),
|
|
826
1032
|
error: err instanceof Error ? err.message : String(err),
|
|
827
1033
|
...(errorDetails ? { errorDetails } : {}),
|
|
828
1034
|
requestMeta: buildGeminiNewRequestMeta(requestArgs),
|
|
@@ -844,6 +1050,7 @@ function wrapGeminiNew(client, recorder, config) {
|
|
|
844
1050
|
latencyMs: Date.now() - start,
|
|
845
1051
|
timestamp: new Date().toISOString(),
|
|
846
1052
|
tags: { ...(config.tags ?? {}) },
|
|
1053
|
+
...buildTraceMeta(config),
|
|
847
1054
|
error: err instanceof Error ? err.message : String(err),
|
|
848
1055
|
...(errorDetails ? { errorDetails } : {}),
|
|
849
1056
|
requestMeta: buildGeminiNewRequestMeta(requestArgs),
|