@ciphyrshq/sdk 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -0
- package/package.json +30 -0
- package/src/client.js +973 -0
- package/src/errors.js +52 -0
- package/src/eval-runner.js +127 -0
- package/src/index.js +13 -0
- package/src/secret-detector.js +49 -0
- package/src/tracer.js +220 -0
- package/types.d.ts +382 -0
package/src/errors.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export class CiphyrsError extends Error {
|
|
2
|
+
constructor(message, { status, code } = {}) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'CiphyrsError';
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class CiphyrsAuthError extends CiphyrsError {
|
|
11
|
+
constructor(message = 'Unauthorized') {
|
|
12
|
+
super(message, { status: 401, code: 'AUTH_ERROR' });
|
|
13
|
+
this.name = 'CiphyrsAuthError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class CiphyrsPermissionError extends CiphyrsError {
|
|
18
|
+
constructor(message = 'Forbidden') {
|
|
19
|
+
super(message, { status: 403, code: 'PERMISSION_ERROR' });
|
|
20
|
+
this.name = 'CiphyrsPermissionError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class CiphyrsNotFoundError extends CiphyrsError {
|
|
25
|
+
constructor(message = 'Not found') {
|
|
26
|
+
super(message, { status: 404, code: 'NOT_FOUND' });
|
|
27
|
+
this.name = 'CiphyrsNotFoundError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class CiphyrsRateLimitError extends CiphyrsError {
|
|
32
|
+
constructor(message = 'Rate limit exceeded', { retryAfter } = {}) {
|
|
33
|
+
super(message, { status: 429, code: 'RATE_LIMIT' });
|
|
34
|
+
this.name = 'CiphyrsRateLimitError';
|
|
35
|
+
this.retryAfter = retryAfter ? parseInt(retryAfter) : null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class CiphyrsTimeoutError extends CiphyrsError {
|
|
40
|
+
constructor(message = 'Request timed out') {
|
|
41
|
+
super(message, { code: 'TIMEOUT' });
|
|
42
|
+
this.name = 'CiphyrsTimeoutError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class CiphyrsJobTimeoutError extends CiphyrsError {
|
|
47
|
+
constructor(jobId, timeoutMs) {
|
|
48
|
+
super(`Job ${jobId} did not complete within ${timeoutMs}ms`, { code: 'JOB_TIMEOUT' });
|
|
49
|
+
this.name = 'CiphyrsJobTimeoutError';
|
|
50
|
+
this.jobId = jobId;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// EvalRunner — run evaluation datasets against agent functions
|
|
3
|
+
// Port of the Python SDK's EvalRunner
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export class EvalRunner {
|
|
7
|
+
#client;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {import('./client.js').CiphyrsClient} client
|
|
11
|
+
*/
|
|
12
|
+
constructor(client) {
|
|
13
|
+
this.#client = client;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load a dataset by ID from the Ciphyrs eval service.
|
|
18
|
+
* @param {string} datasetId
|
|
19
|
+
* @returns {Promise<object>}
|
|
20
|
+
*/
|
|
21
|
+
async loadDataset(datasetId) {
|
|
22
|
+
return this.#client._request(
|
|
23
|
+
`${this.#client._baseUrl}/v1/evals/datasets/${encodeURIComponent(datasetId)}`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Run an agent function against every test case in a dataset.
|
|
29
|
+
*
|
|
30
|
+
* @param {(input: any) => Promise<any>} agentFn — The agent under test
|
|
31
|
+
* @param {string} datasetId — Dataset to evaluate against
|
|
32
|
+
* @param {object} [opts]
|
|
33
|
+
* @param {(input: any, expected: any, actual: any) => Promise<number>} [opts.judgeFn]
|
|
34
|
+
* Custom judge that returns a score 0-1. If omitted, strict equality is used.
|
|
35
|
+
* @param {string} [opts.agentName] — Name of the agent being evaluated
|
|
36
|
+
* @param {string} [opts.agentVersion] — Version string for the agent
|
|
37
|
+
* @returns {Promise<{run_id: string, score: number, passed: number, failed: number, total: number, duration_ms: number, results: Array}>}
|
|
38
|
+
*/
|
|
39
|
+
async run(agentFn, datasetId, { judgeFn, agentName, agentVersion } = {}) {
|
|
40
|
+
const { dataset } = await this.loadDataset(datasetId);
|
|
41
|
+
const cases = dataset.test_cases || [];
|
|
42
|
+
|
|
43
|
+
// Create eval run on the backend
|
|
44
|
+
const { run: evalRun } = await this.#client._request(
|
|
45
|
+
`${this.#client._baseUrl}/v1/evals/runs`,
|
|
46
|
+
{
|
|
47
|
+
method: 'POST',
|
|
48
|
+
body: {
|
|
49
|
+
dataset_id: datasetId,
|
|
50
|
+
agent_name: agentName,
|
|
51
|
+
agent_version: agentVersion,
|
|
52
|
+
total_cases: cases.length,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const results = [];
|
|
58
|
+
let passed = 0;
|
|
59
|
+
let failed = 0;
|
|
60
|
+
let totalScore = 0;
|
|
61
|
+
const startTime = Date.now();
|
|
62
|
+
|
|
63
|
+
for (const testCase of cases) {
|
|
64
|
+
const caseStart = Date.now();
|
|
65
|
+
try {
|
|
66
|
+
const actual = await agentFn(testCase.input);
|
|
67
|
+
const score = judgeFn
|
|
68
|
+
? await judgeFn(testCase.input, testCase.expected, actual)
|
|
69
|
+
: (actual === testCase.expected ? 1.0 : 0.0);
|
|
70
|
+
const pass = score >= (testCase.threshold || 0.5);
|
|
71
|
+
|
|
72
|
+
results.push({
|
|
73
|
+
input: testCase.input,
|
|
74
|
+
expected: testCase.expected,
|
|
75
|
+
actual,
|
|
76
|
+
score,
|
|
77
|
+
passed: pass,
|
|
78
|
+
latency_ms: Date.now() - caseStart,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (pass) passed++;
|
|
82
|
+
else failed++;
|
|
83
|
+
totalScore += score;
|
|
84
|
+
} catch (err) {
|
|
85
|
+
results.push({
|
|
86
|
+
input: testCase.input,
|
|
87
|
+
expected: testCase.expected,
|
|
88
|
+
actual: null,
|
|
89
|
+
score: 0,
|
|
90
|
+
passed: false,
|
|
91
|
+
error: err.message,
|
|
92
|
+
latency_ms: Date.now() - caseStart,
|
|
93
|
+
});
|
|
94
|
+
failed++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const avgScore = cases.length > 0 ? totalScore / cases.length : 0;
|
|
99
|
+
|
|
100
|
+
// Update the eval run with results
|
|
101
|
+
await this.#client._request(
|
|
102
|
+
`${this.#client._baseUrl}/v1/evals/runs/${encodeURIComponent(evalRun.id)}`,
|
|
103
|
+
{
|
|
104
|
+
method: 'PATCH',
|
|
105
|
+
body: {
|
|
106
|
+
status: 'completed',
|
|
107
|
+
passed,
|
|
108
|
+
failed,
|
|
109
|
+
avg_score: avgScore,
|
|
110
|
+
avg_latency_ms: cases.length > 0 ? Math.round((Date.now() - startTime) / cases.length) : 0,
|
|
111
|
+
results,
|
|
112
|
+
completed_at: new Date().toISOString(),
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
run_id: evalRun.id,
|
|
119
|
+
score: avgScore,
|
|
120
|
+
passed,
|
|
121
|
+
failed,
|
|
122
|
+
total: cases.length,
|
|
123
|
+
duration_ms: Date.now() - startTime,
|
|
124
|
+
results,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { CiphyrsClient } from './client.js';
|
|
2
|
+
export {
|
|
3
|
+
CiphyrsError,
|
|
4
|
+
CiphyrsAuthError,
|
|
5
|
+
CiphyrsPermissionError,
|
|
6
|
+
CiphyrsNotFoundError,
|
|
7
|
+
CiphyrsRateLimitError,
|
|
8
|
+
CiphyrsTimeoutError,
|
|
9
|
+
CiphyrsJobTimeoutError,
|
|
10
|
+
} from './errors.js';
|
|
11
|
+
export { CiphyrsTracer, Trace, Span } from './tracer.js';
|
|
12
|
+
export { SecretDetector } from './secret-detector.js';
|
|
13
|
+
export { EvalRunner } from './eval-runner.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// SecretDetector — scan text for leaked credentials and API keys
|
|
3
|
+
// Port of the Python SDK's SecretDetector
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
const PATTERNS = [
|
|
7
|
+
{ type: 'aws_access_key', pattern: /AKIA[0-9A-Z]{16}/g, confidence: 0.95 },
|
|
8
|
+
{ type: 'aws_secret_key', pattern: /[A-Za-z0-9/+=]{40}(?=\s|$|")/g, confidence: 0.7 },
|
|
9
|
+
{ type: 'github_token', pattern: /gh[psortu]_[A-Za-z0-9_]{36,}/g, confidence: 0.99 },
|
|
10
|
+
{ type: 'gitlab_token', pattern: /glpat-[A-Za-z0-9\-_]{20,}/g, confidence: 0.95 },
|
|
11
|
+
{ type: 'slack_token', pattern: /xox[bpras]-[A-Za-z0-9\-]+/g, confidence: 0.95 },
|
|
12
|
+
{ type: 'stripe_key', pattern: /[sr]k_(test|live)_[A-Za-z0-9]{24,}/g, confidence: 0.99 },
|
|
13
|
+
{ type: 'generic_api_key', pattern: /(?:api[_-]?key|apikey|token|secret)['":\s]*[=:]\s*['"]?([A-Za-z0-9_\-]{20,})['"]?/gi, confidence: 0.6 },
|
|
14
|
+
{ type: 'jwt', pattern: /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, confidence: 0.9 },
|
|
15
|
+
{ type: 'private_key', pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/g, confidence: 0.99 },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export class SecretDetector {
|
|
19
|
+
/**
|
|
20
|
+
* Scan text for potential secrets and credentials.
|
|
21
|
+
* @param {string} text — The text to scan
|
|
22
|
+
* @returns {Array<{type: string, start: number, end: number, value_masked: string, confidence: number}>}
|
|
23
|
+
*/
|
|
24
|
+
detect(text) {
|
|
25
|
+
if (!text || typeof text !== 'string') return [];
|
|
26
|
+
|
|
27
|
+
const findings = [];
|
|
28
|
+
|
|
29
|
+
for (const { type, pattern, confidence } of PATTERNS) {
|
|
30
|
+
// Clone the regex so lastIndex resets for each call
|
|
31
|
+
const re = new RegExp(pattern.source, pattern.flags);
|
|
32
|
+
let match;
|
|
33
|
+
while ((match = re.exec(text)) !== null) {
|
|
34
|
+
const value = match[0];
|
|
35
|
+
findings.push({
|
|
36
|
+
type,
|
|
37
|
+
start: match.index,
|
|
38
|
+
end: match.index + value.length,
|
|
39
|
+
value_masked: value.length > 8
|
|
40
|
+
? value.slice(0, 4) + '***' + value.slice(-4)
|
|
41
|
+
: value.slice(0, 2) + '***',
|
|
42
|
+
confidence,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return findings;
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/tracer.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
// CiphyrsTracer — buffered span/metric collection with auto-flush
|
|
5
|
+
// Mirrors the Python SDK's tracer.py
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export class CiphyrsTracer {
|
|
9
|
+
#client;
|
|
10
|
+
#projectName;
|
|
11
|
+
#config;
|
|
12
|
+
#spanBuffer = [];
|
|
13
|
+
#metricBuffer = [];
|
|
14
|
+
#flushInterval;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {import('./client.js').CiphyrsClient} client
|
|
18
|
+
* @param {object} opts
|
|
19
|
+
* @param {string} opts.projectName — Project name for grouping traces
|
|
20
|
+
* @param {number} [opts.flushIntervalMs=5000] — Auto-flush interval in ms
|
|
21
|
+
* @param {number} [opts.batchSize=50] — Flush when buffer reaches this size
|
|
22
|
+
*/
|
|
23
|
+
constructor(client, { projectName, flushIntervalMs = 5000, batchSize = 50 } = {}) {
|
|
24
|
+
this.#client = client;
|
|
25
|
+
this.#projectName = projectName;
|
|
26
|
+
this.#config = { flushIntervalMs, batchSize };
|
|
27
|
+
this.#flushInterval = setInterval(() => this.flush().catch(() => {}), flushIntervalMs);
|
|
28
|
+
// Prevent the interval from keeping the process alive
|
|
29
|
+
if (this.#flushInterval.unref) this.#flushInterval.unref();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Start a new trace.
|
|
34
|
+
* @param {string} name — Human-readable trace name
|
|
35
|
+
* @param {object} [opts]
|
|
36
|
+
* @param {string} [opts.traceId] — Provide your own trace ID, or one is generated
|
|
37
|
+
* @returns {Trace}
|
|
38
|
+
*/
|
|
39
|
+
trace(name, opts = {}) {
|
|
40
|
+
return new Trace(this, name, opts);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @internal — called by Span.end() to enqueue completed spans */
|
|
44
|
+
_enqueueSpan(span) {
|
|
45
|
+
this.#spanBuffer.push(span);
|
|
46
|
+
if (this.#spanBuffer.length >= this.#config.batchSize) {
|
|
47
|
+
this.flush().catch(() => {});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Record a custom metric.
|
|
53
|
+
* @param {string} name — Metric name (e.g. "agent.latency")
|
|
54
|
+
* @param {number} value — Metric value
|
|
55
|
+
* @param {object} [opts]
|
|
56
|
+
* @param {string} [opts.unit] — Unit label (e.g. "ms", "usd")
|
|
57
|
+
* @param {Record<string,string>} [opts.tags] — Key-value tags
|
|
58
|
+
* @param {string} [opts.agentName] — Agent that emitted the metric
|
|
59
|
+
*/
|
|
60
|
+
emitMetric(name, value, { unit, tags, agentName } = {}) {
|
|
61
|
+
this.#metricBuffer.push({ name, value, unit, tags, agent_name: agentName });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Flush buffered spans and metrics to the Ciphyrs backend.
|
|
66
|
+
* Called automatically on the flush interval, or when batchSize is reached.
|
|
67
|
+
*/
|
|
68
|
+
async flush() {
|
|
69
|
+
if (!this.#spanBuffer.length && !this.#metricBuffer.length) return;
|
|
70
|
+
|
|
71
|
+
const spans = this.#spanBuffer.splice(0);
|
|
72
|
+
const metrics = this.#metricBuffer.splice(0);
|
|
73
|
+
|
|
74
|
+
const promises = [];
|
|
75
|
+
|
|
76
|
+
if (spans.length) {
|
|
77
|
+
// Group spans by trace_id so each ingest call contains one trace
|
|
78
|
+
const byTrace = {};
|
|
79
|
+
for (const s of spans) {
|
|
80
|
+
(byTrace[s.trace_id] ||= []).push(s);
|
|
81
|
+
}
|
|
82
|
+
for (const [traceId, traceSpans] of Object.entries(byTrace)) {
|
|
83
|
+
promises.push(
|
|
84
|
+
this.#client.trace.ingest(
|
|
85
|
+
{ name: this.#projectName },
|
|
86
|
+
{ trace_id: traceId, name: traceSpans[0]?.trace_name || traceId },
|
|
87
|
+
traceSpans,
|
|
88
|
+
).catch(err => console.error('Ciphyrs flush error:', err.message)),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (metrics.length) {
|
|
94
|
+
promises.push(
|
|
95
|
+
this.#client._request(`${this.#client._baseUrl}/v1/observe/metrics`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
body: { metrics },
|
|
98
|
+
}).catch(err => console.error('Ciphyrs metrics flush error:', err.message)),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await Promise.allSettled(promises);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Stop the auto-flush timer and flush any remaining data.
|
|
107
|
+
* Always call this before process exit to avoid data loss.
|
|
108
|
+
*/
|
|
109
|
+
async shutdown() {
|
|
110
|
+
clearInterval(this.#flushInterval);
|
|
111
|
+
await this.flush();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
116
|
+
// Trace — groups related spans under a single trace ID
|
|
117
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
export class Trace {
|
|
120
|
+
#tracer;
|
|
121
|
+
#traceId;
|
|
122
|
+
#name;
|
|
123
|
+
#startTime;
|
|
124
|
+
|
|
125
|
+
constructor(tracer, name, opts = {}) {
|
|
126
|
+
this.#tracer = tracer;
|
|
127
|
+
this.#traceId = opts.traceId || randomUUID();
|
|
128
|
+
this.#name = name;
|
|
129
|
+
this.#startTime = Date.now();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get traceId() { return this.#traceId; }
|
|
133
|
+
get name() { return this.#name; }
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Create a new span within this trace.
|
|
137
|
+
* @param {string} name — Operation name for the span
|
|
138
|
+
* @param {object} [opts]
|
|
139
|
+
* @param {string} [opts.parentSpanId] — Parent span ID for nested spans
|
|
140
|
+
* @param {string} [opts.agentName] — Agent performing this operation
|
|
141
|
+
* @param {string} [opts.kind] — Span kind: 'agent' | 'tool' | 'llm' | 'retriever'
|
|
142
|
+
* @returns {Span}
|
|
143
|
+
*/
|
|
144
|
+
span(name, opts = {}) {
|
|
145
|
+
return new Span(this.#tracer, this.#traceId, this.#name, name, opts);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
150
|
+
// Span — a single timed operation within a trace
|
|
151
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
export class Span {
|
|
154
|
+
#tracer;
|
|
155
|
+
#data;
|
|
156
|
+
#startTime;
|
|
157
|
+
|
|
158
|
+
constructor(tracer, traceId, traceName, name, opts = {}) {
|
|
159
|
+
this.#tracer = tracer;
|
|
160
|
+
this.#startTime = Date.now();
|
|
161
|
+
this.#data = {
|
|
162
|
+
span_id: randomUUID(),
|
|
163
|
+
trace_id: traceId,
|
|
164
|
+
trace_name: traceName,
|
|
165
|
+
parent_span_id: opts.parentSpanId || null,
|
|
166
|
+
agent_name: opts.agentName || null,
|
|
167
|
+
kind: opts.kind || 'agent',
|
|
168
|
+
operation_name: name,
|
|
169
|
+
input_text: null,
|
|
170
|
+
output_text: null,
|
|
171
|
+
status: 'ok',
|
|
172
|
+
cost_usd: null,
|
|
173
|
+
model_name: null,
|
|
174
|
+
prompt_tokens: null,
|
|
175
|
+
completion_tokens: null,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
get spanId() { return this.#data.span_id; }
|
|
180
|
+
|
|
181
|
+
/** Set the input text for this span. */
|
|
182
|
+
setInput(text) { this.#data.input_text = text; return this; }
|
|
183
|
+
|
|
184
|
+
/** Set the output text for this span. */
|
|
185
|
+
setOutput(text) { this.#data.output_text = text; return this; }
|
|
186
|
+
|
|
187
|
+
/** Set span status ('ok' | 'error'). */
|
|
188
|
+
setStatus(s) { this.#data.status = s; return this; }
|
|
189
|
+
|
|
190
|
+
/** Mark span as errored with a message. */
|
|
191
|
+
setError(msg) { this.#data.status = 'error'; this.#data.error_message = msg; return this; }
|
|
192
|
+
|
|
193
|
+
/** Set the LLM cost in USD. */
|
|
194
|
+
setCost(usd) { this.#data.cost_usd = usd; return this; }
|
|
195
|
+
|
|
196
|
+
/** Set the model name (e.g. 'gpt-4', 'claude-3-opus'). */
|
|
197
|
+
setModel(name) { this.#data.model_name = name; return this; }
|
|
198
|
+
|
|
199
|
+
/** Set prompt and completion token counts. */
|
|
200
|
+
setTokens(prompt, completion) {
|
|
201
|
+
this.#data.prompt_tokens = prompt;
|
|
202
|
+
this.#data.completion_tokens = completion;
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Attach arbitrary metadata / attributes. */
|
|
207
|
+
setMetadata(meta) { this.#data.attributes = meta; return this; }
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* End the span, recording timestamps and duration, then enqueue for flush.
|
|
211
|
+
* @returns {this}
|
|
212
|
+
*/
|
|
213
|
+
end() {
|
|
214
|
+
this.#data.started_at = new Date(this.#startTime).toISOString();
|
|
215
|
+
this.#data.ended_at = new Date().toISOString();
|
|
216
|
+
this.#data.duration_ms = Date.now() - this.#startTime;
|
|
217
|
+
this.#tracer._enqueueSpan(this.#data);
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
}
|