ironside 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +58 -0
- package/README.md +91 -0
- package/dist/src/batcher.d.ts +51 -0
- package/dist/src/batcher.js +90 -0
- package/dist/src/batcher.js.map +1 -0
- package/dist/src/client.d.ts +70 -0
- package/dist/src/client.js +171 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/types.d.ts +64 -0
- package/dist/src/types.js +6 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/wrappers/anthropic.d.ts +22 -0
- package/dist/src/wrappers/anthropic.js +188 -0
- package/dist/src/wrappers/anthropic.js.map +1 -0
- package/dist/src/wrappers/openai.d.ts +26 -0
- package/dist/src/wrappers/openai.js +162 -0
- package/dist/src/wrappers/openai.js.map +1 -0
- package/dist/src/wrappers/streaming.d.ts +17 -0
- package/dist/src/wrappers/streaming.js +86 -0
- package/dist/src/wrappers/streaming.js.map +1 -0
- package/dist/src/wrappers/vercel-ai.d.ts +29 -0
- package/dist/src/wrappers/vercel-ai.js +30 -0
- package/dist/src/wrappers/vercel-ai.js.map +1 -0
- package/package.json +55 -0
- package/src/batcher.ts +109 -0
- package/src/client.ts +267 -0
- package/src/index.ts +24 -0
- package/src/types.ts +75 -0
- package/src/wrappers/anthropic.ts +276 -0
- package/src/wrappers/openai.ts +283 -0
- package/src/wrappers/streaming.ts +92 -0
- package/src/wrappers/vercel-ai.ts +73 -0
package/src/batcher.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { IngestRequestEvent } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export interface BatcherOptions {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
host: string;
|
|
6
|
+
/** Flush automatically once this many events are buffered. */
|
|
7
|
+
maxBatchSize?: number;
|
|
8
|
+
/** Flush automatically after this many ms, even if under maxBatchSize. */
|
|
9
|
+
flushIntervalMs?: number;
|
|
10
|
+
/** Injectable for tests; defaults to global fetch. */
|
|
11
|
+
fetchImpl?: typeof fetch;
|
|
12
|
+
/** Called with a failed batch's events + the error; default: console.error. Never throws back into the caller's hot path. */
|
|
13
|
+
onError?: (error: unknown, events: IngestRequestEvent[]) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const DEFAULT_MAX_BATCH_SIZE = 50;
|
|
17
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 5000;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Buffers ingest events in memory and flushes them to POST /api/v1/ingest
|
|
21
|
+
* in the background — instrumentation calls (trace/span/generation) never
|
|
22
|
+
* block on network I/O. Flushes are fire-and-forget from the caller's
|
|
23
|
+
* perspective; failures are reported via onError, not thrown, since a
|
|
24
|
+
* trace SDK must never be the reason an application request fails.
|
|
25
|
+
*/
|
|
26
|
+
export class EventBatcher {
|
|
27
|
+
private readonly apiKey: string;
|
|
28
|
+
private readonly host: string;
|
|
29
|
+
private readonly maxBatchSize: number;
|
|
30
|
+
private readonly flushIntervalMs: number;
|
|
31
|
+
private readonly fetchImpl: typeof fetch;
|
|
32
|
+
private readonly onError: (error: unknown, events: IngestRequestEvent[]) => void;
|
|
33
|
+
|
|
34
|
+
private buffer: IngestRequestEvent[] = [];
|
|
35
|
+
private timer: ReturnType<typeof setInterval> | null = null;
|
|
36
|
+
private inFlight: Promise<void> = Promise.resolve();
|
|
37
|
+
private closed = false;
|
|
38
|
+
|
|
39
|
+
constructor(options: BatcherOptions) {
|
|
40
|
+
this.apiKey = options.apiKey;
|
|
41
|
+
this.host = options.host.replace(/\/$/, "");
|
|
42
|
+
this.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
|
|
43
|
+
this.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
44
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
45
|
+
this.onError =
|
|
46
|
+
options.onError ??
|
|
47
|
+
((error) => console.error("[ironside] failed to send trace events:", error));
|
|
48
|
+
|
|
49
|
+
this.timer = setInterval(() => void this.flush(), this.flushIntervalMs);
|
|
50
|
+
// Don't let the flush timer keep the process alive on its own.
|
|
51
|
+
this.timer.unref?.();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
enqueue(event: IngestRequestEvent): void {
|
|
55
|
+
if (this.closed) return;
|
|
56
|
+
this.buffer.push(event);
|
|
57
|
+
if (this.buffer.length >= this.maxBatchSize) {
|
|
58
|
+
void this.flush();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Sends whatever is currently buffered. Safe to call concurrently — flushes serialize via inFlight. */
|
|
63
|
+
async flush(): Promise<void> {
|
|
64
|
+
if (this.buffer.length === 0) return;
|
|
65
|
+
const events = this.buffer;
|
|
66
|
+
this.buffer = [];
|
|
67
|
+
|
|
68
|
+
this.inFlight = this.inFlight.then(() => this.send(events));
|
|
69
|
+
await this.inFlight;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private async send(events: IngestRequestEvent[]): Promise<void> {
|
|
73
|
+
try {
|
|
74
|
+
const res = await this.fetchImpl(`${this.host}/api/v1/ingest`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: {
|
|
77
|
+
"content-type": "application/json",
|
|
78
|
+
authorization: `Bearer ${this.apiKey}`
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({ events })
|
|
81
|
+
});
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
this.onError(new Error(`ingest request failed: HTTP ${res.status}`), events);
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
this.onError(error, events);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Stops the background timer and flushes any remaining buffered events.
|
|
92
|
+
* Call on process shutdown.
|
|
93
|
+
*
|
|
94
|
+
* Must wait on `inFlight` explicitly, not just call `flush()` — if a
|
|
95
|
+
* different caller (the interval timer, or an enqueue() that just hit
|
|
96
|
+
* maxBatchSize) already claimed the buffer into its own in-progress
|
|
97
|
+
* send() moments earlier, flush() here sees an empty buffer and returns
|
|
98
|
+
* immediately without waiting for that still-pending request. Without
|
|
99
|
+
* this, shutdown() could resolve while a real network request is still
|
|
100
|
+
* in flight, and an immediately-following process.exit() would silently
|
|
101
|
+
* drop that batch.
|
|
102
|
+
*/
|
|
103
|
+
async close(): Promise<void> {
|
|
104
|
+
this.closed = true;
|
|
105
|
+
if (this.timer) clearInterval(this.timer);
|
|
106
|
+
await this.flush();
|
|
107
|
+
await this.inFlight;
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { ulid } from "ulid";
|
|
2
|
+
import { EventBatcher, type BatcherOptions } from "./batcher.js";
|
|
3
|
+
import type {
|
|
4
|
+
EndObservationOptions,
|
|
5
|
+
ScoreOptions,
|
|
6
|
+
StartGenerationOptions,
|
|
7
|
+
StartSpanOptions,
|
|
8
|
+
StartTraceOptions,
|
|
9
|
+
UpdateTraceOptions
|
|
10
|
+
} from "./types.js";
|
|
11
|
+
|
|
12
|
+
export interface IronsideClientOptions {
|
|
13
|
+
apiKey: string;
|
|
14
|
+
/** Ironside API host, e.g. "https://ironside.example.com" or "http://localhost:8788". */
|
|
15
|
+
host: string;
|
|
16
|
+
maxBatchSize?: number;
|
|
17
|
+
flushIntervalMs?: number;
|
|
18
|
+
fetchImpl?: BatcherOptions["fetchImpl"];
|
|
19
|
+
onError?: BatcherOptions["onError"];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ObservationHandle {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
/** Starts a child span nested under this observation. */
|
|
25
|
+
span(options?: StartSpanOptions): ObservationHandle;
|
|
26
|
+
/** Starts a child generation nested under this observation. */
|
|
27
|
+
generation(options?: StartGenerationOptions): ObservationHandle;
|
|
28
|
+
/** Marks this observation complete. Safe to call at most once meaningfully; a second call overwrites endTime. */
|
|
29
|
+
end(options?: EndObservationOptions): void;
|
|
30
|
+
/** Records a score (user feedback, eval result, ...) attached to this observation. */
|
|
31
|
+
score(options: ScoreOptions): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TraceHandle {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
span(options?: StartSpanOptions): ObservationHandle;
|
|
37
|
+
generation(options?: StartGenerationOptions): ObservationHandle;
|
|
38
|
+
update(options: UpdateTraceOptions): void;
|
|
39
|
+
/** Records a score (user feedback, eval result, ...) attached to this trace. */
|
|
40
|
+
score(options: ScoreOptions): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface UploadMediaOptions {
|
|
44
|
+
/** The raw bytes to store. */
|
|
45
|
+
data: Uint8Array | ArrayBuffer;
|
|
46
|
+
/** Real content type of the bytes, e.g. "image/png". */
|
|
47
|
+
contentType: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface UploadedMedia {
|
|
51
|
+
id: string;
|
|
52
|
+
/** Compact ref string ("ironside://media/<id>") to embed in trace input/output/metadata. */
|
|
53
|
+
ref: string;
|
|
54
|
+
contentType: string;
|
|
55
|
+
sizeBytes: number;
|
|
56
|
+
sha256: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface IronsideClient {
|
|
60
|
+
trace(options?: StartTraceOptions): TraceHandle;
|
|
61
|
+
/**
|
|
62
|
+
* Uploads a media blob (image, audio, document, ...) and returns a
|
|
63
|
+
* compact ref string to embed in trace input/output instead of the
|
|
64
|
+
* bytes themselves — base64 payloads inside trace JSON bloat the
|
|
65
|
+
* columnar store. Content-addressed: uploading identical bytes twice
|
|
66
|
+
* returns the same asset. Unlike instrumentation calls this awaits the
|
|
67
|
+
* network (the ref doesn't exist until the server has the bytes).
|
|
68
|
+
*/
|
|
69
|
+
uploadMedia(options: UploadMediaOptions): Promise<UploadedMedia>;
|
|
70
|
+
/** Sends buffered events immediately instead of waiting for the next automatic flush. */
|
|
71
|
+
flush(): Promise<void>;
|
|
72
|
+
/** Stops background flushing and sends any remaining buffered events. Call before process exit. */
|
|
73
|
+
shutdown(): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Creates an Ironside client. Instrumentation calls (trace/span/generation)
|
|
78
|
+
* never block on network I/O — events are buffered and flushed in the
|
|
79
|
+
* background. Deliberately does NOT auto-register a process-exit handler:
|
|
80
|
+
* doing that inside a library is a footgun (can't be un-registered,
|
|
81
|
+
* surprising under multiple init() calls or in serverless runtimes with
|
|
82
|
+
* their own lifecycle hooks). Call `shutdown()` explicitly wherever your
|
|
83
|
+
* app already handles graceful shutdown.
|
|
84
|
+
*/
|
|
85
|
+
export function init(options: IronsideClientOptions): IronsideClient {
|
|
86
|
+
const batcher = new EventBatcher({
|
|
87
|
+
apiKey: options.apiKey,
|
|
88
|
+
host: options.host,
|
|
89
|
+
...(options.maxBatchSize !== undefined && { maxBatchSize: options.maxBatchSize }),
|
|
90
|
+
...(options.flushIntervalMs !== undefined && { flushIntervalMs: options.flushIntervalMs }),
|
|
91
|
+
...(options.fetchImpl !== undefined && { fetchImpl: options.fetchImpl }),
|
|
92
|
+
...(options.onError !== undefined && { onError: options.onError })
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
function enqueueScore(
|
|
96
|
+
traceId: string,
|
|
97
|
+
observationId: string | undefined,
|
|
98
|
+
scoreOptions: ScoreOptions
|
|
99
|
+
): void {
|
|
100
|
+
batcher.enqueue({
|
|
101
|
+
type: "score-upsert",
|
|
102
|
+
body: {
|
|
103
|
+
id: scoreOptions.id ?? ulid(),
|
|
104
|
+
traceId,
|
|
105
|
+
...(observationId && { observationId }),
|
|
106
|
+
name: scoreOptions.name,
|
|
107
|
+
dataType: scoreOptions.value !== undefined ? "numeric" : "categorical",
|
|
108
|
+
source: scoreOptions.source ?? "api",
|
|
109
|
+
...(scoreOptions.value !== undefined && { value: scoreOptions.value }),
|
|
110
|
+
...(scoreOptions.stringValue !== undefined && { stringValue: scoreOptions.stringValue }),
|
|
111
|
+
...(scoreOptions.comment && { comment: scoreOptions.comment }),
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
metadata: scoreOptions.metadata ?? {}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function makeObservationHandle(
|
|
119
|
+
id: string,
|
|
120
|
+
traceId: string,
|
|
121
|
+
type: "span" | "generation",
|
|
122
|
+
parentObservationId: string | undefined,
|
|
123
|
+
startOptions: StartSpanOptions | StartGenerationOptions
|
|
124
|
+
): ObservationHandle {
|
|
125
|
+
const startTime = new Date().toISOString();
|
|
126
|
+
const model = "model" in startOptions ? startOptions.model : undefined;
|
|
127
|
+
const modelParameters =
|
|
128
|
+
"modelParameters" in startOptions ? startOptions.modelParameters : undefined;
|
|
129
|
+
|
|
130
|
+
batcher.enqueue({
|
|
131
|
+
type: "observation-upsert",
|
|
132
|
+
body: {
|
|
133
|
+
id,
|
|
134
|
+
traceId,
|
|
135
|
+
...(parentObservationId && { parentObservationId }),
|
|
136
|
+
type,
|
|
137
|
+
...(startOptions.name && { name: startOptions.name }),
|
|
138
|
+
startTime,
|
|
139
|
+
...(model && { model }),
|
|
140
|
+
...(modelParameters && { modelParameters }),
|
|
141
|
+
...(startOptions.input !== undefined && { input: startOptions.input }),
|
|
142
|
+
metadata: startOptions.metadata ?? {}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
function child(childType: "span" | "generation") {
|
|
147
|
+
return (options: StartSpanOptions | StartGenerationOptions = {}) =>
|
|
148
|
+
makeObservationHandle(ulid(), traceId, childType, id, options);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
id,
|
|
153
|
+
span: child("span"),
|
|
154
|
+
generation: child("generation"),
|
|
155
|
+
end(endOptions: EndObservationOptions = {}) {
|
|
156
|
+
batcher.enqueue({
|
|
157
|
+
type: "observation-upsert",
|
|
158
|
+
body: {
|
|
159
|
+
id,
|
|
160
|
+
traceId,
|
|
161
|
+
...(parentObservationId && { parentObservationId }),
|
|
162
|
+
type,
|
|
163
|
+
...(startOptions.name && { name: startOptions.name }),
|
|
164
|
+
startTime,
|
|
165
|
+
endTime: new Date().toISOString(),
|
|
166
|
+
level: endOptions.level ?? "default",
|
|
167
|
+
...(model && { model }),
|
|
168
|
+
...(modelParameters && { modelParameters }),
|
|
169
|
+
...(startOptions.input !== undefined && { input: startOptions.input }),
|
|
170
|
+
...(endOptions.output !== undefined && { output: endOptions.output }),
|
|
171
|
+
...(endOptions.statusMessage && { statusMessage: endOptions.statusMessage }),
|
|
172
|
+
...(endOptions.usageDetails && { usageDetails: endOptions.usageDetails }),
|
|
173
|
+
...(endOptions.costDetails && { costDetails: endOptions.costDetails }),
|
|
174
|
+
metadata: { ...startOptions.metadata, ...endOptions.metadata }
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
score: (scoreOptions: ScoreOptions) => enqueueScore(traceId, id, scoreOptions)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
183
|
+
const host = options.host.replace(/\/$/, "");
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
async uploadMedia(uploadOptions: UploadMediaOptions): Promise<UploadedMedia> {
|
|
187
|
+
const body =
|
|
188
|
+
uploadOptions.data instanceof ArrayBuffer
|
|
189
|
+
? new Uint8Array(uploadOptions.data)
|
|
190
|
+
: uploadOptions.data;
|
|
191
|
+
const res = await fetchImpl(`${host}/api/v1/media`, {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: {
|
|
194
|
+
authorization: `Bearer ${options.apiKey}`,
|
|
195
|
+
"content-type": uploadOptions.contentType
|
|
196
|
+
},
|
|
197
|
+
// DOM's BodyInit type isn't in the SDK's lib set; Uint8Array is a
|
|
198
|
+
// valid fetch body in Node 18+ and browsers alike.
|
|
199
|
+
body: body as unknown as NonNullable<RequestInit["body"]>
|
|
200
|
+
});
|
|
201
|
+
if (!res.ok) {
|
|
202
|
+
const text = await res.text().catch(() => "");
|
|
203
|
+
throw new Error(`media upload failed: ${res.status}${text ? ` ${text}` : ""}`);
|
|
204
|
+
}
|
|
205
|
+
return (await res.json()) as UploadedMedia;
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
trace(startOptions: StartTraceOptions = {}) {
|
|
209
|
+
const id = startOptions.id ?? ulid();
|
|
210
|
+
// Fixed at trace-start time and reused by update() below — the trace's
|
|
211
|
+
// timestamp must not drift to "whenever update() happened to be
|
|
212
|
+
// called" (e.g. re-stamping it at completion time would corrupt the
|
|
213
|
+
// recorded start time on every partial update).
|
|
214
|
+
const timestamp = new Date().toISOString();
|
|
215
|
+
batcher.enqueue({
|
|
216
|
+
type: "trace-upsert",
|
|
217
|
+
body: {
|
|
218
|
+
id,
|
|
219
|
+
timestamp,
|
|
220
|
+
...(startOptions.name && { name: startOptions.name }),
|
|
221
|
+
...(startOptions.userId && { userId: startOptions.userId }),
|
|
222
|
+
...(startOptions.sessionId && { sessionId: startOptions.sessionId }),
|
|
223
|
+
...(startOptions.environment && { environment: startOptions.environment }),
|
|
224
|
+
...(startOptions.release && { release: startOptions.release }),
|
|
225
|
+
...(startOptions.version && { version: startOptions.version }),
|
|
226
|
+
tags: startOptions.tags ?? [],
|
|
227
|
+
metadata: startOptions.metadata ?? {},
|
|
228
|
+
...(startOptions.input !== undefined && { input: startOptions.input })
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
id,
|
|
234
|
+
span: (options: StartSpanOptions = {}) =>
|
|
235
|
+
makeObservationHandle(ulid(), id, "span", undefined, options),
|
|
236
|
+
generation: (options: StartGenerationOptions = {}) =>
|
|
237
|
+
makeObservationHandle(ulid(), id, "generation", undefined, options),
|
|
238
|
+
update(updateOptions: UpdateTraceOptions) {
|
|
239
|
+
// A trace-upsert replaces the whole row (ClickHouse
|
|
240
|
+
// ReplacingMergeTree has no field-level merge) — every field set
|
|
241
|
+
// by trace() must be carried forward here too, or update() would
|
|
242
|
+
// silently wipe name/userId/sessionId/input back to absent.
|
|
243
|
+
batcher.enqueue({
|
|
244
|
+
type: "trace-upsert",
|
|
245
|
+
body: {
|
|
246
|
+
id,
|
|
247
|
+
timestamp,
|
|
248
|
+
...(startOptions.name && { name: startOptions.name }),
|
|
249
|
+
...(startOptions.userId && { userId: startOptions.userId }),
|
|
250
|
+
...(startOptions.sessionId && { sessionId: startOptions.sessionId }),
|
|
251
|
+
...(startOptions.environment && { environment: startOptions.environment }),
|
|
252
|
+
...(startOptions.release && { release: startOptions.release }),
|
|
253
|
+
...(startOptions.version && { version: startOptions.version }),
|
|
254
|
+
tags: startOptions.tags ?? [],
|
|
255
|
+
...(startOptions.input !== undefined && { input: startOptions.input }),
|
|
256
|
+
...(updateOptions.output !== undefined && { output: updateOptions.output }),
|
|
257
|
+
metadata: { ...startOptions.metadata, ...updateOptions.metadata }
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
},
|
|
261
|
+
score: (scoreOptions: ScoreOptions) => enqueueScore(id, undefined, scoreOptions)
|
|
262
|
+
};
|
|
263
|
+
},
|
|
264
|
+
flush: () => batcher.flush(),
|
|
265
|
+
shutdown: () => batcher.close()
|
|
266
|
+
};
|
|
267
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { init } from "./client.js";
|
|
2
|
+
export type {
|
|
3
|
+
IronsideClient,
|
|
4
|
+
IronsideClientOptions,
|
|
5
|
+
TraceHandle,
|
|
6
|
+
ObservationHandle,
|
|
7
|
+
UploadMediaOptions,
|
|
8
|
+
UploadedMedia
|
|
9
|
+
} from "./client.js";
|
|
10
|
+
export type {
|
|
11
|
+
StartTraceOptions,
|
|
12
|
+
UpdateTraceOptions,
|
|
13
|
+
StartSpanOptions,
|
|
14
|
+
StartGenerationOptions,
|
|
15
|
+
EndObservationOptions,
|
|
16
|
+
ScoreOptions
|
|
17
|
+
} from "./types.js";
|
|
18
|
+
|
|
19
|
+
export { wrapOpenAI } from "./wrappers/openai.js";
|
|
20
|
+
export type { WrapOpenAIOptions } from "./wrappers/openai.js";
|
|
21
|
+
export { wrapAnthropic } from "./wrappers/anthropic.js";
|
|
22
|
+
export type { WrapAnthropicOptions } from "./wrappers/anthropic.js";
|
|
23
|
+
export { recordGenerateTextResult } from "./wrappers/vercel-ai.js";
|
|
24
|
+
export type { RecordGenerateTextOptions } from "./wrappers/vercel-ai.js";
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Wire-contract types, deliberately duplicated from (not imported from)
|
|
2
|
+
// @ironside/shared: this package ships standalone to npm and cannot depend
|
|
3
|
+
// on a private workspace package. Kept intentionally minimal — only the
|
|
4
|
+
// fields the SDK itself constructs.
|
|
5
|
+
|
|
6
|
+
export interface StartTraceOptions {
|
|
7
|
+
id?: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
userId?: string;
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
environment?: string;
|
|
12
|
+
release?: string;
|
|
13
|
+
version?: string;
|
|
14
|
+
tags?: string[];
|
|
15
|
+
metadata?: Record<string, string>;
|
|
16
|
+
input?: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UpdateTraceOptions {
|
|
20
|
+
output?: unknown;
|
|
21
|
+
metadata?: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ScoreOptionsBase {
|
|
25
|
+
id?: string;
|
|
26
|
+
/** Attaches the score to a specific observation instead of the trace as a whole. */
|
|
27
|
+
observationId?: string;
|
|
28
|
+
name: string;
|
|
29
|
+
source?: "api" | "eval" | "annotation";
|
|
30
|
+
comment?: string;
|
|
31
|
+
metadata?: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Exactly one of value/stringValue — enforced at the type level (not just
|
|
36
|
+
* documented) so a caller can't produce a score with neither (dropped
|
|
37
|
+
* server-side against the domain schema's invariant, silently, since the
|
|
38
|
+
* SDK's ingest is fire-and-forget) or both (an internally inconsistent
|
|
39
|
+
* dataType/payload pairing).
|
|
40
|
+
*/
|
|
41
|
+
export type ScoreOptions =
|
|
42
|
+
| (ScoreOptionsBase & { value: number; stringValue?: undefined })
|
|
43
|
+
| (ScoreOptionsBase & { stringValue: string; value?: undefined });
|
|
44
|
+
|
|
45
|
+
export interface StartSpanOptions {
|
|
46
|
+
id?: string;
|
|
47
|
+
name?: string;
|
|
48
|
+
input?: unknown;
|
|
49
|
+
metadata?: Record<string, string>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface StartGenerationOptions extends StartSpanOptions {
|
|
53
|
+
model?: string;
|
|
54
|
+
modelParameters?: Record<string, string | number | boolean | null>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface EndObservationOptions {
|
|
58
|
+
output?: unknown;
|
|
59
|
+
statusMessage?: string;
|
|
60
|
+
level?: "debug" | "default" | "warning" | "error";
|
|
61
|
+
usageDetails?: Record<string, number>;
|
|
62
|
+
costDetails?: Record<string, number>;
|
|
63
|
+
metadata?: Record<string, string>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type IngestEventType =
|
|
67
|
+
| "trace-upsert"
|
|
68
|
+
| "observation-upsert"
|
|
69
|
+
| "score-upsert";
|
|
70
|
+
|
|
71
|
+
export interface IngestRequestEvent {
|
|
72
|
+
id?: string;
|
|
73
|
+
type: IngestEventType;
|
|
74
|
+
body: unknown;
|
|
75
|
+
}
|