@rheonic/sdk 0.1.0-beta.5 → 0.1.0-beta.7
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
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
All notable changes to
|
|
3
|
+
All notable changes to `@rheonic/sdk` will be documented in this file.
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
7
|
- Publish-ready changelog entries will be added here for the next release.
|
|
8
8
|
|
|
9
|
-
## 0.1.0
|
|
9
|
+
## 0.1.0-beta.7
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- `RHEONICBlockedError` now exposes structured block feedback for apps and agents: `reason`, `retry_after_seconds`, `blocked_until`, `trace_id`, `request_id`, and `snapshot`.
|
|
13
|
+
- Fail-closed protect fallback now reports `reason="fail_closed"` instead of the generic `decision_unavailable` on blocked requests.
|
|
14
|
+
|
|
15
|
+
## 0.1.0-beta.6
|
|
10
16
|
|
|
11
17
|
### Added
|
|
12
18
|
- Initial public beta release of the Rheonic Node SDK.
|
package/README.md
CHANGED
|
@@ -48,7 +48,19 @@ try {
|
|
|
48
48
|
});
|
|
49
49
|
} catch (error) {
|
|
50
50
|
if (error instanceof RHEONICBlockedError) {
|
|
51
|
-
console.log(
|
|
51
|
+
console.log(
|
|
52
|
+
JSON.stringify(
|
|
53
|
+
{
|
|
54
|
+
reason: error.reason,
|
|
55
|
+
retry_after_seconds: error.retry_after_seconds,
|
|
56
|
+
blocked_until: error.blocked_until,
|
|
57
|
+
trace_id: error.trace_id,
|
|
58
|
+
request_id: error.request_id,
|
|
59
|
+
},
|
|
60
|
+
null,
|
|
61
|
+
2,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
52
64
|
}
|
|
53
65
|
}
|
|
54
66
|
```
|
|
@@ -74,7 +86,13 @@ try {
|
|
|
74
86
|
});
|
|
75
87
|
} catch (error) {
|
|
76
88
|
if (error instanceof RHEONICBlockedError) {
|
|
77
|
-
console.log(
|
|
89
|
+
console.log(JSON.stringify({
|
|
90
|
+
reason: error.reason,
|
|
91
|
+
retry_after_seconds: error.retry_after_seconds,
|
|
92
|
+
blocked_until: error.blocked_until,
|
|
93
|
+
trace_id: error.trace_id,
|
|
94
|
+
request_id: error.request_id,
|
|
95
|
+
}, null, 2));
|
|
78
96
|
}
|
|
79
97
|
}
|
|
80
98
|
```
|
|
@@ -97,11 +115,25 @@ try {
|
|
|
97
115
|
await model.generateContent("hello");
|
|
98
116
|
} catch (error) {
|
|
99
117
|
if (error instanceof RHEONICBlockedError) {
|
|
100
|
-
console.log(
|
|
118
|
+
console.log(JSON.stringify({
|
|
119
|
+
reason: error.reason,
|
|
120
|
+
retry_after_seconds: error.retry_after_seconds,
|
|
121
|
+
blocked_until: error.blocked_until,
|
|
122
|
+
trace_id: error.trace_id,
|
|
123
|
+
request_id: error.request_id,
|
|
124
|
+
}, null, 2));
|
|
101
125
|
}
|
|
102
126
|
}
|
|
103
127
|
```
|
|
104
128
|
|
|
129
|
+
`RHEONICBlockedError.reason` is meant to be operator-relevant. The main values are:
|
|
130
|
+
- `tok_cap_breach`
|
|
131
|
+
- `req_cap_breach`
|
|
132
|
+
- `cooldown_active`
|
|
133
|
+
- `fail_closed`
|
|
134
|
+
|
|
135
|
+
If Protect is `fail_open`, timeout or availability problems stay internal and the provider call continues. If Protect is `fail_closed`, the SDK raises `RHEONICBlockedError` with the feedback fields shown above.
|
|
136
|
+
|
|
105
137
|
Keep one long-lived SDK client per app process. Initialize it during app startup and reuse it for all capture and instrumentation calls so Rheonic can avoid repeated protect cold-start latency.
|
|
106
138
|
|
|
107
139
|
## Optional: custom event capture
|
package/dist/protectEngine.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ProtectDecision = "allow" | "
|
|
1
|
+
export type ProtectDecision = "allow" | "clamp" | "block";
|
|
2
2
|
export type ProtectFailMode = "open" | "closed";
|
|
3
3
|
export interface ProtectContext {
|
|
4
4
|
provider: string;
|
|
@@ -10,6 +10,10 @@ export interface ProtectContext {
|
|
|
10
10
|
export interface ProtectEvaluation {
|
|
11
11
|
decision: ProtectDecision;
|
|
12
12
|
reason: string;
|
|
13
|
+
trace_id: string;
|
|
14
|
+
request_id: string;
|
|
15
|
+
blocked_until?: string;
|
|
16
|
+
retry_after_seconds?: number;
|
|
13
17
|
snapshot?: Record<string, unknown>;
|
|
14
18
|
applyClampEnabled?: boolean;
|
|
15
19
|
clamp?: {
|
|
@@ -19,7 +23,19 @@ export interface ProtectEvaluation {
|
|
|
19
23
|
}
|
|
20
24
|
export declare class RHEONICBlockedError extends Error {
|
|
21
25
|
readonly reason: string;
|
|
22
|
-
|
|
26
|
+
readonly trace_id: string;
|
|
27
|
+
readonly request_id: string;
|
|
28
|
+
readonly blocked_until?: string;
|
|
29
|
+
readonly retry_after_seconds?: number;
|
|
30
|
+
readonly snapshot?: Record<string, unknown>;
|
|
31
|
+
constructor(params: {
|
|
32
|
+
reason: string;
|
|
33
|
+
trace_id: string;
|
|
34
|
+
request_id: string;
|
|
35
|
+
blocked_until?: string;
|
|
36
|
+
retry_after_seconds?: number;
|
|
37
|
+
snapshot?: Record<string, unknown>;
|
|
38
|
+
});
|
|
23
39
|
}
|
|
24
40
|
export declare class ProtectEngine {
|
|
25
41
|
private readonly baseUrl;
|
|
@@ -41,6 +57,7 @@ export declare class ProtectEngine {
|
|
|
41
57
|
debugLog?: (message: string, meta?: Record<string, unknown>) => void;
|
|
42
58
|
});
|
|
43
59
|
evaluate(context: ProtectContext): Promise<ProtectEvaluation>;
|
|
60
|
+
private fallbackEvaluation;
|
|
44
61
|
bootstrap(): Promise<void>;
|
|
45
62
|
private reportDecisionTimeout;
|
|
46
63
|
private reportDecisionUnavailable;
|
package/dist/protectEngine.js
CHANGED
|
@@ -4,10 +4,20 @@ import { requestJson } from "./httpTransport.js";
|
|
|
4
4
|
import { bindTraceContext, generateSpanId, generateTraceId, getTraceId } from "./logger.js";
|
|
5
5
|
export class RHEONICBlockedError extends Error {
|
|
6
6
|
reason;
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
trace_id;
|
|
8
|
+
request_id;
|
|
9
|
+
blocked_until;
|
|
10
|
+
retry_after_seconds;
|
|
11
|
+
snapshot;
|
|
12
|
+
constructor(params) {
|
|
13
|
+
super(`Request blocked by Rheonic: ${params.reason}`);
|
|
9
14
|
this.name = "RHEONICBlockedError";
|
|
10
|
-
this.reason = reason;
|
|
15
|
+
this.reason = params.reason;
|
|
16
|
+
this.trace_id = params.trace_id;
|
|
17
|
+
this.request_id = params.request_id;
|
|
18
|
+
this.blocked_until = params.blocked_until;
|
|
19
|
+
this.retry_after_seconds = params.retry_after_seconds;
|
|
20
|
+
this.snapshot = params.snapshot;
|
|
11
21
|
}
|
|
12
22
|
}
|
|
13
23
|
export class ProtectEngine {
|
|
@@ -35,6 +45,8 @@ export class ProtectEngine {
|
|
|
35
45
|
this.cooldownReason = null;
|
|
36
46
|
}
|
|
37
47
|
async evaluate(context) {
|
|
48
|
+
const requestId = randomUUID();
|
|
49
|
+
const traceId = generateTraceId();
|
|
38
50
|
const nowMs = Date.now();
|
|
39
51
|
if (this.cooldownUntilMs !== null && nowMs < this.cooldownUntilMs) {
|
|
40
52
|
this.debugLog?.("Protect preflight blocked locally from cached cooldown", {
|
|
@@ -42,15 +54,20 @@ export class ProtectEngine {
|
|
|
42
54
|
decision: "block",
|
|
43
55
|
reason: this.cooldownReason ?? "cooldown_active",
|
|
44
56
|
});
|
|
45
|
-
return {
|
|
57
|
+
return {
|
|
58
|
+
decision: "block",
|
|
59
|
+
reason: this.cooldownReason ?? "cooldown_active",
|
|
60
|
+
trace_id: traceId,
|
|
61
|
+
request_id: requestId,
|
|
62
|
+
blocked_until: formatBlockedUntilMs(this.cooldownUntilMs),
|
|
63
|
+
retry_after_seconds: toRetryAfterSeconds(this.cooldownUntilMs, nowMs),
|
|
64
|
+
};
|
|
46
65
|
}
|
|
47
66
|
const controller = new AbortController();
|
|
48
67
|
const timeoutMs = this.decisionTimeoutMs > 0 ? this.decisionTimeoutMs : this.fallbackRequestTimeoutMs;
|
|
49
68
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
50
69
|
timeout.unref?.();
|
|
51
70
|
const startedAt = Date.now();
|
|
52
|
-
const requestId = randomUUID();
|
|
53
|
-
const traceId = generateTraceId();
|
|
54
71
|
const spanId = generateSpanId();
|
|
55
72
|
try {
|
|
56
73
|
const response = await bindTraceContext(traceId, spanId, async () => await requestJson(`${this.baseUrl}/api/v1/protect/decision`, {
|
|
@@ -72,10 +89,8 @@ export class ProtectEngine {
|
|
|
72
89
|
status_code: response.status,
|
|
73
90
|
latency_ms: Date.now() - startedAt,
|
|
74
91
|
});
|
|
75
|
-
void this.reportDecisionUnavailable(context.provider, typeof context.model === "string" ? context.model : undefined, requestId);
|
|
76
|
-
return this.
|
|
77
|
-
? { decision: "block", reason: "decision_unavailable" }
|
|
78
|
-
: { decision: "allow", reason: "decision_unavailable" };
|
|
92
|
+
void this.reportDecisionUnavailable(context.provider, typeof context.model === "string" ? context.model : undefined, requestId, traceId);
|
|
93
|
+
return this.fallbackEvaluation(traceId, requestId);
|
|
79
94
|
}
|
|
80
95
|
const parsed = (await response.json());
|
|
81
96
|
const decision = parseDecision(parsed.decision);
|
|
@@ -107,6 +122,10 @@ export class ProtectEngine {
|
|
|
107
122
|
return {
|
|
108
123
|
decision,
|
|
109
124
|
reason,
|
|
125
|
+
trace_id: traceId,
|
|
126
|
+
request_id: requestId,
|
|
127
|
+
blocked_until: typeof parsed.blocked_until === "string" ? parsed.blocked_until : undefined,
|
|
128
|
+
retry_after_seconds: parseRetryAfterSeconds(parsed.retry_after_seconds),
|
|
110
129
|
snapshot: parseSnapshot(parsed.snapshot),
|
|
111
130
|
applyClampEnabled: typeof parsed.apply_clamp_enabled === "boolean" ? parsed.apply_clamp_enabled : undefined,
|
|
112
131
|
clamp: parseClamp(parsed.clamp),
|
|
@@ -120,7 +139,7 @@ export class ProtectEngine {
|
|
|
120
139
|
latency_ms: Date.now() - startedAt,
|
|
121
140
|
timeout_ms: timeoutMs,
|
|
122
141
|
});
|
|
123
|
-
void this.reportDecisionTimeout(context.provider, typeof context.model === "string" ? context.model : undefined, requestId);
|
|
142
|
+
void this.reportDecisionTimeout(context.provider, typeof context.model === "string" ? context.model : undefined, requestId, traceId);
|
|
124
143
|
}
|
|
125
144
|
else {
|
|
126
145
|
this.debugLog?.("Protect preflight failed", {
|
|
@@ -128,13 +147,19 @@ export class ProtectEngine {
|
|
|
128
147
|
latency_ms: Date.now() - startedAt,
|
|
129
148
|
error_type: extractErrorType(error),
|
|
130
149
|
});
|
|
131
|
-
void this.reportDecisionUnavailable(context.provider, typeof context.model === "string" ? context.model : undefined, requestId);
|
|
150
|
+
void this.reportDecisionUnavailable(context.provider, typeof context.model === "string" ? context.model : undefined, requestId, traceId);
|
|
132
151
|
}
|
|
133
|
-
return this.
|
|
134
|
-
? { decision: "block", reason: "decision_unavailable" }
|
|
135
|
-
: { decision: "allow", reason: "decision_unavailable" };
|
|
152
|
+
return this.fallbackEvaluation(traceId, requestId);
|
|
136
153
|
}
|
|
137
154
|
}
|
|
155
|
+
fallbackEvaluation(traceId, requestId) {
|
|
156
|
+
return {
|
|
157
|
+
decision: this.failMode === "closed" ? "block" : "allow",
|
|
158
|
+
reason: this.failMode === "closed" ? "fail_closed" : "decision_unavailable",
|
|
159
|
+
trace_id: traceId,
|
|
160
|
+
request_id: requestId,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
138
163
|
async bootstrap() {
|
|
139
164
|
try {
|
|
140
165
|
const response = await requestJson(`${this.baseUrl}/api/v1/protect/config`, {
|
|
@@ -162,14 +187,14 @@ export class ProtectEngine {
|
|
|
162
187
|
// Best effort only; keep local defaults if bootstrap fails.
|
|
163
188
|
}
|
|
164
189
|
}
|
|
165
|
-
async reportDecisionTimeout(provider, model, requestId) {
|
|
190
|
+
async reportDecisionTimeout(provider, model, requestId, traceId) {
|
|
166
191
|
try {
|
|
167
192
|
await requestJson(`${this.baseUrl}/api/v1/protect/decision-timeout`, {
|
|
168
193
|
method: "POST",
|
|
169
194
|
headers: {
|
|
170
195
|
"Content-Type": "application/json",
|
|
171
196
|
"X-Project-Ingest-Key": this.ingestKey,
|
|
172
|
-
"X-Trace-ID":
|
|
197
|
+
"X-Trace-ID": traceId,
|
|
173
198
|
"X-Span-ID": generateSpanId(),
|
|
174
199
|
"X-Rheonic-Protect-Request-Id": requestId,
|
|
175
200
|
},
|
|
@@ -180,14 +205,14 @@ export class ProtectEngine {
|
|
|
180
205
|
// Swallow timeout reporting errors; protect evaluation must never throw here.
|
|
181
206
|
}
|
|
182
207
|
}
|
|
183
|
-
async reportDecisionUnavailable(provider, model, requestId) {
|
|
208
|
+
async reportDecisionUnavailable(provider, model, requestId, traceId) {
|
|
184
209
|
try {
|
|
185
210
|
await requestJson(`${this.baseUrl}/api/v1/protect/decision-unavailable`, {
|
|
186
211
|
method: "POST",
|
|
187
212
|
headers: {
|
|
188
213
|
"Content-Type": "application/json",
|
|
189
214
|
"X-Project-Ingest-Key": this.ingestKey,
|
|
190
|
-
"X-Trace-ID":
|
|
215
|
+
"X-Trace-ID": traceId,
|
|
191
216
|
"X-Span-ID": generateSpanId(),
|
|
192
217
|
"X-Rheonic-Protect-Request-Id": requestId,
|
|
193
218
|
},
|
|
@@ -229,11 +254,29 @@ function parseBlockedUntilMs(value) {
|
|
|
229
254
|
return parsed;
|
|
230
255
|
}
|
|
231
256
|
function parseDecision(value) {
|
|
232
|
-
if (value === "
|
|
257
|
+
if (value === "clamp" || value === "block" || value === "allow") {
|
|
233
258
|
return value;
|
|
234
259
|
}
|
|
235
260
|
return "allow";
|
|
236
261
|
}
|
|
262
|
+
function parseRetryAfterSeconds(value) {
|
|
263
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
264
|
+
return Math.floor(value);
|
|
265
|
+
}
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
function formatBlockedUntilMs(value) {
|
|
269
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
return new Date(value).toISOString();
|
|
273
|
+
}
|
|
274
|
+
function toRetryAfterSeconds(blockedUntilMs, nowMs) {
|
|
275
|
+
if (typeof blockedUntilMs !== "number" || !Number.isFinite(blockedUntilMs) || blockedUntilMs <= nowMs) {
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
return Math.max(0, Math.ceil((blockedUntilMs - nowMs) / 1000));
|
|
279
|
+
}
|
|
237
280
|
function parseFailMode(value) {
|
|
238
281
|
if (value === "open" || value === "closed") {
|
|
239
282
|
return value;
|
|
@@ -42,7 +42,7 @@ export function instrumentAnthropic(anthropicClient, options) {
|
|
|
42
42
|
}
|
|
43
43
|
const protectDecision = await options.client.evaluateProtectDecision(protectPayload);
|
|
44
44
|
if (protectDecision.decision === "block") {
|
|
45
|
-
throw new RHEONICBlockedError(protectDecision
|
|
45
|
+
throw new RHEONICBlockedError(protectDecision);
|
|
46
46
|
}
|
|
47
47
|
const callArgs = maybeApplyAnthropicClamp(args, protectDecision);
|
|
48
48
|
markClampAppliedIfChanged(protectDecision, extractMaxOutputTokens(args), extractMaxOutputTokens(callArgs));
|
|
@@ -57,8 +57,8 @@ export function instrumentAnthropic(anthropicClient, options) {
|
|
|
57
57
|
feature: options.feature,
|
|
58
58
|
token_explosion_tokens: typeof estimatedInputTokens === "number" ? estimatedInputTokens : undefined,
|
|
59
59
|
input_tokens_estimate: typeof estimatedInputTokens === "number" ? estimatedInputTokens : undefined,
|
|
60
|
-
protect_decision: protectDecision.decision
|
|
61
|
-
protect_reason: protectDecision.decision
|
|
60
|
+
protect_decision: protectDecision.decision !== "allow" ? protectDecision.decision : undefined,
|
|
61
|
+
protect_reason: protectDecision.decision !== "allow" ? protectDecision.reason : undefined,
|
|
62
62
|
},
|
|
63
63
|
response: {
|
|
64
64
|
latency_ms: Date.now() - startedAt,
|
|
@@ -78,8 +78,8 @@ export function instrumentAnthropic(anthropicClient, options) {
|
|
|
78
78
|
feature: options.feature,
|
|
79
79
|
token_explosion_tokens: typeof estimatedInputTokens === "number" ? estimatedInputTokens : undefined,
|
|
80
80
|
input_tokens_estimate: typeof estimatedInputTokens === "number" ? estimatedInputTokens : undefined,
|
|
81
|
-
protect_decision: protectDecision.decision
|
|
82
|
-
protect_reason: protectDecision.decision
|
|
81
|
+
protect_decision: protectDecision.decision !== "allow" ? protectDecision.decision : undefined,
|
|
82
|
+
protect_reason: protectDecision.decision !== "allow" ? protectDecision.reason : undefined,
|
|
83
83
|
},
|
|
84
84
|
response: {
|
|
85
85
|
latency_ms: Date.now() - startedAt,
|
|
@@ -155,7 +155,7 @@ function extractHttpStatus(error) {
|
|
|
155
155
|
return undefined;
|
|
156
156
|
}
|
|
157
157
|
function maybeApplyAnthropicClamp(args, decision) {
|
|
158
|
-
if (decision.decision !== "
|
|
158
|
+
if (decision.decision !== "clamp") {
|
|
159
159
|
return args;
|
|
160
160
|
}
|
|
161
161
|
if (!decision.applyClampEnabled) {
|
|
@@ -42,7 +42,7 @@ export function instrumentGoogle(googleModel, options) {
|
|
|
42
42
|
}
|
|
43
43
|
const protectDecision = await options.client.evaluateProtectDecision(protectPayload);
|
|
44
44
|
if (protectDecision.decision === "block") {
|
|
45
|
-
throw new RHEONICBlockedError(protectDecision
|
|
45
|
+
throw new RHEONICBlockedError(protectDecision);
|
|
46
46
|
}
|
|
47
47
|
const callArgs = maybeApplyGoogleClamp(args, protectDecision);
|
|
48
48
|
markClampAppliedIfChanged(protectDecision, extractMaxOutputTokens(args), extractMaxOutputTokens(callArgs));
|
|
@@ -57,8 +57,8 @@ export function instrumentGoogle(googleModel, options) {
|
|
|
57
57
|
feature: options.feature,
|
|
58
58
|
token_explosion_tokens: typeof estimatedInputTokens === "number" ? estimatedInputTokens : undefined,
|
|
59
59
|
input_tokens_estimate: typeof estimatedInputTokens === "number" ? estimatedInputTokens : undefined,
|
|
60
|
-
protect_decision: protectDecision.decision
|
|
61
|
-
protect_reason: protectDecision.decision
|
|
60
|
+
protect_decision: protectDecision.decision !== "allow" ? protectDecision.decision : undefined,
|
|
61
|
+
protect_reason: protectDecision.decision !== "allow" ? protectDecision.reason : undefined,
|
|
62
62
|
},
|
|
63
63
|
response: {
|
|
64
64
|
latency_ms: Date.now() - startedAt,
|
|
@@ -78,8 +78,8 @@ export function instrumentGoogle(googleModel, options) {
|
|
|
78
78
|
feature: options.feature,
|
|
79
79
|
token_explosion_tokens: typeof estimatedInputTokens === "number" ? estimatedInputTokens : undefined,
|
|
80
80
|
input_tokens_estimate: typeof estimatedInputTokens === "number" ? estimatedInputTokens : undefined,
|
|
81
|
-
protect_decision: protectDecision.decision
|
|
82
|
-
protect_reason: protectDecision.decision
|
|
81
|
+
protect_decision: protectDecision.decision !== "allow" ? protectDecision.decision : undefined,
|
|
82
|
+
protect_reason: protectDecision.decision !== "allow" ? protectDecision.reason : undefined,
|
|
83
83
|
},
|
|
84
84
|
response: {
|
|
85
85
|
latency_ms: Date.now() - startedAt,
|
|
@@ -168,7 +168,7 @@ function extractHttpStatus(error) {
|
|
|
168
168
|
return undefined;
|
|
169
169
|
}
|
|
170
170
|
function maybeApplyGoogleClamp(args, decision) {
|
|
171
|
-
if (decision.decision !== "
|
|
171
|
+
if (decision.decision !== "clamp") {
|
|
172
172
|
return args;
|
|
173
173
|
}
|
|
174
174
|
if (!decision.applyClampEnabled) {
|
|
@@ -43,7 +43,7 @@ export function instrumentOpenAI(openaiClient, options) {
|
|
|
43
43
|
...protectPayload,
|
|
44
44
|
});
|
|
45
45
|
if (protectDecision.decision === "block") {
|
|
46
|
-
throw new RHEONICBlockedError(protectDecision
|
|
46
|
+
throw new RHEONICBlockedError(protectDecision);
|
|
47
47
|
}
|
|
48
48
|
const callArgs = maybeApplyOpenAIClamp(args, protectDecision);
|
|
49
49
|
markClampAppliedIfChanged(protectDecision, extractMaxOutputTokens(args), extractMaxOutputTokens(callArgs));
|
|
@@ -57,8 +57,8 @@ export function instrumentOpenAI(openaiClient, options) {
|
|
|
57
57
|
endpoint: options.endpoint,
|
|
58
58
|
feature: options.feature,
|
|
59
59
|
token_explosion_tokens: typeof estimatedInputTokens === "number" ? estimatedInputTokens : undefined,
|
|
60
|
-
protect_decision: protectDecision.decision
|
|
61
|
-
protect_reason: protectDecision.decision
|
|
60
|
+
protect_decision: protectDecision.decision !== "allow" ? protectDecision.decision : undefined,
|
|
61
|
+
protect_reason: protectDecision.decision !== "allow" ? protectDecision.reason : undefined,
|
|
62
62
|
},
|
|
63
63
|
response: {
|
|
64
64
|
latency_ms: Date.now() - startedAt,
|
|
@@ -77,8 +77,8 @@ export function instrumentOpenAI(openaiClient, options) {
|
|
|
77
77
|
endpoint: options.endpoint,
|
|
78
78
|
feature: options.feature,
|
|
79
79
|
token_explosion_tokens: typeof estimatedInputTokens === "number" ? estimatedInputTokens : undefined,
|
|
80
|
-
protect_decision: protectDecision.decision
|
|
81
|
-
protect_reason: protectDecision.decision
|
|
80
|
+
protect_decision: protectDecision.decision !== "allow" ? protectDecision.decision : undefined,
|
|
81
|
+
protect_reason: protectDecision.decision !== "allow" ? protectDecision.reason : undefined,
|
|
82
82
|
},
|
|
83
83
|
response: {
|
|
84
84
|
latency_ms: Date.now() - startedAt,
|
|
@@ -120,7 +120,7 @@ function extractMaxOutputTokens(args) {
|
|
|
120
120
|
return undefined;
|
|
121
121
|
}
|
|
122
122
|
function maybeApplyOpenAIClamp(args, decision) {
|
|
123
|
-
if (decision.decision !== "
|
|
123
|
+
if (decision.decision !== "clamp") {
|
|
124
124
|
return args;
|
|
125
125
|
}
|
|
126
126
|
if (!decision.applyClampEnabled) {
|
package/package.json
CHANGED