@akai_80percent/traceai-sdk 0.0.0-stage → 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 +21 -0
- package/README.md +90 -2
- package/dist/index.d.ts +121 -0
- package/dist/index.js +521 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TraceAI contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,91 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @akai_80percent/traceai-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A standalone TypeScript client for best-effort, privacy-first AI operation telemetry.
|
|
4
|
+
It preserves operation results/errors, batches events, and bounds memory and delivery attempts.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
Use the public SDK package in your server-side application:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
bun add @akai_80percent/traceai-sdk
|
|
12
|
+
# Or: npm install @akai_80percent/traceai-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For the current registry release status and verified version, see the
|
|
16
|
+
[publication guide](https://github.com/Metricra-code/traceai/blob/main/docs/npm.md).
|
|
17
|
+
An install command requires an available registry release; packing alone is not publication.
|
|
18
|
+
For local development, build the repository workspace or install a locally packed tarball.
|
|
19
|
+
From the repository root:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
bun install --frozen-lockfile
|
|
23
|
+
bun run --filter @akai_80percent/traceai-sdk build
|
|
24
|
+
mkdir -p .local
|
|
25
|
+
(cd packages/sdk && bun pm pack --filename ../../.local/traceai-sdk.tgz --ignore-scripts)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
In a separate consumer project, use `bun add /absolute/path/to/traceai/.local/traceai-sdk.tgz`
|
|
29
|
+
(or `npm install` with the same file path). Packing is not publishing; generated artifacts stay in
|
|
30
|
+
ignored `.local/`. Repository examples already resolve the workspace package after it is built.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { TraceAI } from '@akai_80percent/traceai-sdk';
|
|
36
|
+
|
|
37
|
+
const telemetry = new TraceAI({
|
|
38
|
+
apiKey: process.env.TRACEAI_API_KEY!,
|
|
39
|
+
endpoint: process.env.TRACEAI_ENDPOINT!,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const result = await telemetry.trace(
|
|
44
|
+
{ name: 'summarize', provider: 'example', model: 'my-model' },
|
|
45
|
+
async (span) => {
|
|
46
|
+
const response = await callModel();
|
|
47
|
+
span.setUsage({ inputTokens: response.inputTokens, outputTokens: response.outputTokens });
|
|
48
|
+
return response;
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
} finally {
|
|
52
|
+
await telemetry.shutdown();
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Server-side only:** ingestion keys must never be included in a browser bundle.
|
|
57
|
+
Bun 1.4+ or Node.js 22+ provides the required Fetch, Web Crypto and performance APIs; the SDK has no Bun-specific dependencies.
|
|
58
|
+
Only explicit scalar metadata is sent; prompts, results and raw error messages are never collected by default.
|
|
59
|
+
Failure-only `TraceOptions.errorSummary(error)` is a deliberate opt-in for a safe summary, not automatic
|
|
60
|
+
`.message` capture. Known-secret redaction is best effort, not an all-PII guarantee; invalid/oversized
|
|
61
|
+
summaries and callback failures are omitted while the original application error is rethrown.
|
|
62
|
+
Treat custom metadata as potentially sensitive.
|
|
63
|
+
|
|
64
|
+
Default limits: batch 50, UTF-8 payload 256 KiB, metadata 8 KiB, 1,000 queued/in-flight events,
|
|
65
|
+
5-second flush/timeout, 3 delivery attempts. Constructor rejects unsafe configuration with a fixed error.
|
|
66
|
+
HTTPS is required except localhost/127.0.0.1/::1 development endpoints.
|
|
67
|
+
|
|
68
|
+
`trace()` never awaits upload. `flush()` shares concurrent drains and resolves after bounded attempts;
|
|
69
|
+
it is **not** a durable delivery acknowledgment. Observe sanitized `onDiagnostic` codes for loss.
|
|
70
|
+
`shutdown()` stops new telemetry, waits for previously started operations and drains their events.
|
|
71
|
+
Operations must have their own deadlines; never await shutdown from inside a traced operation.
|
|
72
|
+
|
|
73
|
+
Retry: only network failures, timeouts, HTTP 408/429/5xx; exponential full jitter and bounded Retry-After.
|
|
74
|
+
Permanent failures and retry exhaustion drop the batch. Queue overflow drops the newest event.
|
|
75
|
+
The queue is in memory: process crashes, abrupt termination and serverless suspension can lose it.
|
|
76
|
+
|
|
77
|
+
`record(completedEvent)` validates/snapshots an already-ended operation and returns an actual
|
|
78
|
+
HTTP-acknowledged `delivered` or explicit `dropped` receipt. Do not mistake fulfillment of
|
|
79
|
+
best-effort `flush()` for acceptance. Call `flush()` before awaiting a short-lived process's receipt.
|
|
80
|
+
The optional `@traceai/opentelemetry` package uses this path; the core SDK has no OTel dependency.
|
|
81
|
+
|
|
82
|
+
The repository's workspace builds and Bun-packed tarballs are independently checked. Its
|
|
83
|
+
`bun run test:packages` verifies isolated public types, MIT license files, and Bun + Node 22 runtime
|
|
84
|
+
consumers. It does not publish anything.
|
|
85
|
+
|
|
86
|
+
Full integration, tuning, testing and benchmark guidance:
|
|
87
|
+
[SDK guide](https://github.com/Metricra-code/traceai/blob/main/docs/sdk.md).
|
|
88
|
+
For optional real-provider usage, see the
|
|
89
|
+
[Gemini integration guide](https://github.com/Metricra-code/traceai/blob/main/docs/gemini.md).
|
|
90
|
+
Gemini requires your own server-side provider credentials; free-tier/model availability and quotas
|
|
91
|
+
depend on your account and may change. The mock examples do not call or bill an AI provider.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/** Best-effort known-pattern redaction, not a guarantee that arbitrary summaries contain no PII. */
|
|
4
|
+
declare function sanitizeErrorSummary(value: string): string | undefined;
|
|
5
|
+
|
|
6
|
+
declare const traceEventSchema: z.ZodObject<{
|
|
7
|
+
traceId: z.ZodString;
|
|
8
|
+
name: z.ZodString;
|
|
9
|
+
provider: z.ZodString;
|
|
10
|
+
model: z.ZodString;
|
|
11
|
+
status: z.ZodEnum<{
|
|
12
|
+
error: "error";
|
|
13
|
+
success: "success";
|
|
14
|
+
}>;
|
|
15
|
+
startedAt: z.ZodISODateTime;
|
|
16
|
+
endedAt: z.ZodISODateTime;
|
|
17
|
+
durationMs: z.ZodNumber;
|
|
18
|
+
inputTokens: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
errorType: z.ZodOptional<z.ZodEnum<{
|
|
21
|
+
timeout: "timeout";
|
|
22
|
+
rate_limit: "rate_limit";
|
|
23
|
+
network: "network";
|
|
24
|
+
application: "application";
|
|
25
|
+
unknown: "unknown";
|
|
26
|
+
}>>;
|
|
27
|
+
errorSummary: z.ZodOptional<z.ZodString>;
|
|
28
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>>;
|
|
29
|
+
}, z.core.$strict>;
|
|
30
|
+
type TraceEvent = z.infer<typeof traceEventSchema>;
|
|
31
|
+
|
|
32
|
+
/** Only explicitly supplied scalar metadata is captured. Never put secrets here. */
|
|
33
|
+
type TraceMetadata = Record<string, string | number | boolean | null>;
|
|
34
|
+
interface Usage {
|
|
35
|
+
inputTokens: number;
|
|
36
|
+
outputTokens: number;
|
|
37
|
+
}
|
|
38
|
+
interface TraceOptions {
|
|
39
|
+
name: string;
|
|
40
|
+
provider: string;
|
|
41
|
+
model: string;
|
|
42
|
+
metadata?: TraceMetadata;
|
|
43
|
+
/** Explicit opt-in only. Known secrets are redacted; arbitrary PII is your responsibility. */
|
|
44
|
+
errorSummary?: (error: unknown) => string | undefined;
|
|
45
|
+
}
|
|
46
|
+
interface TraceSpan {
|
|
47
|
+
/** Invalid usage is ignored; the last valid report wins. Closed spans ignore reports. */
|
|
48
|
+
setUsage(usage: Usage): void;
|
|
49
|
+
}
|
|
50
|
+
interface Diagnostic {
|
|
51
|
+
code: 'invalid_event' | 'invalid_metadata' | 'invalid_usage' | 'invalid_error_summary' | 'queue_full' | 'delivery_retry' | 'delivery_failed' | 'client_closed';
|
|
52
|
+
count: number;
|
|
53
|
+
attempt?: number;
|
|
54
|
+
httpStatus?: number;
|
|
55
|
+
}
|
|
56
|
+
interface TraceAIConfig {
|
|
57
|
+
apiKey: string;
|
|
58
|
+
/** API base URL or full /v1/events/batch URL. HTTPS except loopback development. */
|
|
59
|
+
endpoint: string;
|
|
60
|
+
enabled?: boolean;
|
|
61
|
+
batchSize?: number;
|
|
62
|
+
flushIntervalMs?: number;
|
|
63
|
+
requestTimeoutMs?: number;
|
|
64
|
+
maxQueueSize?: number;
|
|
65
|
+
/** Includes the initial attempt. */
|
|
66
|
+
maxAttempts?: number;
|
|
67
|
+
retryBaseMs?: number;
|
|
68
|
+
/** Upper bound for exponential backoff and Retry-After. */
|
|
69
|
+
retryMaxMs?: number;
|
|
70
|
+
/** A lightweight callback receiving sanitized codes only. Exceptions are ignored. */
|
|
71
|
+
onDiagnostic?: (diagnostic: Readonly<Diagnostic>) => void;
|
|
72
|
+
/** Optional transport override for testing or a compatible platform fetch. */
|
|
73
|
+
fetch?: typeof globalThis.fetch;
|
|
74
|
+
}
|
|
75
|
+
/** An already-ended operation, e.g. a server-side OpenTelemetry span. */
|
|
76
|
+
type CompletedTrace = TraceEvent;
|
|
77
|
+
/** Acknowledged by HTTP, not merely queued. Delivery is best effort, never exactly-once. */
|
|
78
|
+
type DeliveryResult = {
|
|
79
|
+
status: 'delivered';
|
|
80
|
+
} | {
|
|
81
|
+
status: 'dropped';
|
|
82
|
+
reason: 'disabled' | 'client_closed' | 'invalid_event' | 'queue_full' | 'delivery_failed';
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Snapshots valid plain scalar metadata; never serializes arbitrary objects or array indices. */
|
|
86
|
+
declare const snapshotTraceMetadata: (value: unknown) => TraceMetadata | undefined;
|
|
87
|
+
|
|
88
|
+
/** A best-effort in-memory telemetry client; operation results and errors always win. */
|
|
89
|
+
declare class TraceAI {
|
|
90
|
+
private readonly config;
|
|
91
|
+
private readonly report;
|
|
92
|
+
private readonly transport;
|
|
93
|
+
private readonly interval?;
|
|
94
|
+
private queue;
|
|
95
|
+
private inFlightCount;
|
|
96
|
+
private drainPromise?;
|
|
97
|
+
private drainAllRequested;
|
|
98
|
+
private shutdownPromise?;
|
|
99
|
+
private acceptingTraces;
|
|
100
|
+
private activeCount;
|
|
101
|
+
private activeBarrier?;
|
|
102
|
+
private resolveActiveBarrier?;
|
|
103
|
+
constructor(config: TraceAIConfig);
|
|
104
|
+
trace<T>(options: TraceOptions, operation: (span: TraceSpan) => Promise<T>): Promise<T>;
|
|
105
|
+
/** Validate and snapshot an already-completed event. Resolves only on delivery or explicit drop. */
|
|
106
|
+
record(event: CompletedTrace): Promise<DeliveryResult>;
|
|
107
|
+
/** Singleflight best-effort drain. Use record() receipts when acknowledged delivery matters. */
|
|
108
|
+
flush(): Promise<void>;
|
|
109
|
+
/** Stops new telemetry, awaits already-started operations, then drains. Idempotent. */
|
|
110
|
+
shutdown(): Promise<void>;
|
|
111
|
+
private waitForActiveOperations;
|
|
112
|
+
private finishActiveOperation;
|
|
113
|
+
private completeSafely;
|
|
114
|
+
private enqueue;
|
|
115
|
+
private startDrain;
|
|
116
|
+
private shouldDrain;
|
|
117
|
+
private drainQueue;
|
|
118
|
+
private sendNextBatch;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { type CompletedTrace, type DeliveryResult, type Diagnostic, TraceAI, type TraceAIConfig, type TraceMetadata, type TraceOptions, type TraceSpan, type Usage, sanitizeErrorSummary, snapshotTraceMetadata };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
// ../shared/src/index.ts
|
|
2
|
+
import { z as z2 } from "zod";
|
|
3
|
+
|
|
4
|
+
// ../shared/src/error-summary.ts
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
var MAX_ERROR_SUMMARY_CHARS = 1e3;
|
|
7
|
+
var MAX_ERROR_SUMMARY_BYTES = 4 * 1024;
|
|
8
|
+
var errorSummarySchema = z.string().min(1).max(MAX_ERROR_SUMMARY_CHARS).refine((value) => new TextEncoder().encode(value).length <= MAX_ERROR_SUMMARY_BYTES);
|
|
9
|
+
function stripControlCharacters(value) {
|
|
10
|
+
return Array.from(value, (character) => {
|
|
11
|
+
const code = character.charCodeAt(0);
|
|
12
|
+
return code < 32 || code === 127 ? " " : character;
|
|
13
|
+
}).join("");
|
|
14
|
+
}
|
|
15
|
+
function sanitizeErrorSummary(value) {
|
|
16
|
+
if (!errorSummarySchema.safeParse(value).success) return void 0;
|
|
17
|
+
const sanitized = stripControlCharacters(value).replace(/(https?:\/\/)[^\s/@]+@/gi, "$1[redacted]@").replace(
|
|
18
|
+
/\b(?:authorization|proxy-authorization)["']?\s*[:=]\s*["']?(?:bearer\s+|basic\s+)?[^\s,;"'}]+/gi,
|
|
19
|
+
"authorization: [redacted]"
|
|
20
|
+
).replace(/\b(?:bearer|basic)\s+[a-z0-9._~+/=-]+/gi, "[redacted-auth]").replace(/\btai_[a-f0-9]{32}_[a-f0-9]{64}\b/gi, "[redacted-key]").replace(
|
|
21
|
+
/\b(?:sk|sess|ghp|gho|github_pat|xox[baprs])[-_][a-z0-9_-]{12,}\b/gi,
|
|
22
|
+
"[redacted-token]"
|
|
23
|
+
).replace(/\beyJ[a-z0-9_-]+\.[a-z0-9_-]+\.[a-z0-9_-]+\b/gi, "[redacted-token]").replace(
|
|
24
|
+
/\b(api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|passwd|signature)["']?\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;&]+)/gi,
|
|
25
|
+
"$1=[redacted]"
|
|
26
|
+
).replace(/\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/gi, "[redacted-email]").trim();
|
|
27
|
+
return sanitized && errorSummarySchema.safeParse(sanitized).success ? sanitized : void 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ../shared/src/index.ts
|
|
31
|
+
var MAX_BATCH_SIZE = 50;
|
|
32
|
+
var MAX_PAYLOAD_BYTES = 256 * 1024;
|
|
33
|
+
var MAX_METADATA_BYTES = 8 * 1024;
|
|
34
|
+
var MAX_WINDOW_MS = 31 * 24 * 60 * 60 * 1e3;
|
|
35
|
+
var label = z2.string().trim().min(1).max(120);
|
|
36
|
+
var timestamp = z2.iso.datetime();
|
|
37
|
+
var utcTimestampParts = (value) => {
|
|
38
|
+
const [whole, fraction = ""] = value.slice(0, -1).split(".");
|
|
39
|
+
return { second: Date.parse(`${whole}Z`), fraction };
|
|
40
|
+
};
|
|
41
|
+
var timestampsOrdered = (startedAt, endedAt) => {
|
|
42
|
+
const start = utcTimestampParts(startedAt);
|
|
43
|
+
const end = utcTimestampParts(endedAt);
|
|
44
|
+
if (start.second !== end.second) return start.second < end.second;
|
|
45
|
+
const precision = Math.max(start.fraction.length, end.fraction.length);
|
|
46
|
+
return start.fraction.padEnd(precision, "0") <= end.fraction.padEnd(precision, "0");
|
|
47
|
+
};
|
|
48
|
+
var tokenCount = z2.number().int().min(0).max(1e7);
|
|
49
|
+
var usageSchema = z2.object({ inputTokens: tokenCount, outputTokens: tokenCount }).strict();
|
|
50
|
+
var metadataSchema = z2.record(
|
|
51
|
+
z2.string().max(100),
|
|
52
|
+
z2.union([z2.string().max(2e3), z2.number().finite(), z2.boolean(), z2.null()])
|
|
53
|
+
).refine(
|
|
54
|
+
(value) => new TextEncoder().encode(JSON.stringify(value)).length <= MAX_METADATA_BYTES,
|
|
55
|
+
"Metadata exceeds 8 KiB"
|
|
56
|
+
);
|
|
57
|
+
var traceEventSchema = z2.object({
|
|
58
|
+
traceId: z2.string().min(1).max(128).regex(/^[a-zA-Z0-9_-]+$/),
|
|
59
|
+
name: label,
|
|
60
|
+
provider: label,
|
|
61
|
+
model: label,
|
|
62
|
+
status: z2.enum(["success", "error"]),
|
|
63
|
+
startedAt: timestamp,
|
|
64
|
+
endedAt: timestamp,
|
|
65
|
+
durationMs: z2.number().finite().min(0).max(864e5),
|
|
66
|
+
inputTokens: tokenCount.optional(),
|
|
67
|
+
outputTokens: tokenCount.optional(),
|
|
68
|
+
errorType: z2.enum(["timeout", "rate_limit", "network", "application", "unknown"]).optional(),
|
|
69
|
+
errorSummary: errorSummarySchema.optional(),
|
|
70
|
+
metadata: metadataSchema.optional()
|
|
71
|
+
}).strict().refine((event) => timestampsOrdered(event.startedAt, event.endedAt), {
|
|
72
|
+
message: "End timestamp must not precede start"
|
|
73
|
+
}).refine((event) => event.errorSummary === void 0 || event.status === "error", {
|
|
74
|
+
message: "An explicit error summary is only permitted on failed operations"
|
|
75
|
+
});
|
|
76
|
+
var batchSchema = z2.object({ events: z2.array(traceEventSchema).min(1).max(MAX_BATCH_SIZE) }).strict();
|
|
77
|
+
var projectSchema = z2.object({
|
|
78
|
+
name: z2.string().trim().min(1).max(80),
|
|
79
|
+
description: z2.string().trim().max(500).default("")
|
|
80
|
+
}).strict();
|
|
81
|
+
var credentialsSchema = z2.object({
|
|
82
|
+
email: z2.email().max(254).transform((value) => value.toLowerCase()),
|
|
83
|
+
password: z2.string().min(12).max(128)
|
|
84
|
+
}).strict();
|
|
85
|
+
var pricingProvenanceSchema = z2.object({
|
|
86
|
+
version: z2.string().min(1).max(120),
|
|
87
|
+
provider: label,
|
|
88
|
+
model: label,
|
|
89
|
+
currency: z2.string().min(1).max(10),
|
|
90
|
+
inputNanoUsdPerMillion: z2.string().regex(/^\d{1,20}$/),
|
|
91
|
+
outputNanoUsdPerMillion: z2.string().regex(/^\d{1,20}$/),
|
|
92
|
+
effectiveFrom: timestamp,
|
|
93
|
+
effectiveTo: timestamp.nullable(),
|
|
94
|
+
sourceUrl: z2.url().max(2048).refine((value) => {
|
|
95
|
+
const url = new URL(value);
|
|
96
|
+
return url.protocol === "https:" && !url.username && !url.password;
|
|
97
|
+
}),
|
|
98
|
+
simulated: z2.boolean(),
|
|
99
|
+
verifiedAt: timestamp.nullable(),
|
|
100
|
+
billingBasis: z2.string().max(100).nullable()
|
|
101
|
+
}).strict();
|
|
102
|
+
|
|
103
|
+
// src/events.ts
|
|
104
|
+
var snapshotTraceMetadata = (value) => {
|
|
105
|
+
try {
|
|
106
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
107
|
+
const prototype = Object.getPrototypeOf(value);
|
|
108
|
+
if (prototype !== Object.prototype && prototype !== null) return void 0;
|
|
109
|
+
return metadataSchema.parse(value);
|
|
110
|
+
} catch {
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
var snapshotOptions = (options, report) => {
|
|
115
|
+
const labels = { name: options.name, provider: options.provider, model: options.model };
|
|
116
|
+
if (options.metadata === void 0) return labels;
|
|
117
|
+
const metadata = snapshotTraceMetadata(options.metadata);
|
|
118
|
+
if (metadata) return { ...labels, metadata };
|
|
119
|
+
report({ code: "invalid_metadata", count: 1 });
|
|
120
|
+
return labels;
|
|
121
|
+
};
|
|
122
|
+
var captureErrorSummary = (options, report) => {
|
|
123
|
+
let callback;
|
|
124
|
+
try {
|
|
125
|
+
callback = options.errorSummary;
|
|
126
|
+
} catch {
|
|
127
|
+
report({ code: "invalid_error_summary", count: 1 });
|
|
128
|
+
}
|
|
129
|
+
return (error) => {
|
|
130
|
+
if (callback === void 0) return void 0;
|
|
131
|
+
try {
|
|
132
|
+
const value = callback(error);
|
|
133
|
+
if (value === void 0) return void 0;
|
|
134
|
+
if (typeof value === "string") {
|
|
135
|
+
const sanitized = sanitizeErrorSummary(value);
|
|
136
|
+
if (sanitized !== void 0) return sanitized;
|
|
137
|
+
} else {
|
|
138
|
+
void Promise.resolve(value).catch(() => void 0);
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
report({ code: "invalid_error_summary", count: 1 });
|
|
143
|
+
return void 0;
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
var snapshotCompletedEvent = (value) => {
|
|
147
|
+
try {
|
|
148
|
+
const parsed = traceEventSchema.safeParse(value);
|
|
149
|
+
if (!parsed.success) return void 0;
|
|
150
|
+
const { errorSummary, ...event } = parsed.data;
|
|
151
|
+
const sanitized = errorSummary === void 0 ? void 0 : sanitizeErrorSummary(errorSummary);
|
|
152
|
+
return sanitized === void 0 ? event : { ...event, errorSummary: sanitized };
|
|
153
|
+
} catch {
|
|
154
|
+
return void 0;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
var classifyError = (error) => {
|
|
158
|
+
try {
|
|
159
|
+
if (typeof error !== "object" || error === null) return "unknown";
|
|
160
|
+
const classified = error;
|
|
161
|
+
if (classified.name === "AbortError" || classified.name === "TimeoutError" || classified.code === "ETIMEDOUT")
|
|
162
|
+
return "timeout";
|
|
163
|
+
if (classified.status === 429) return "rate_limit";
|
|
164
|
+
if (classified.name === "NetworkError" || typeof classified.code === "string" && ["ECONNRESET", "ENOTFOUND", "ECONNREFUSED"].includes(classified.code))
|
|
165
|
+
return "network";
|
|
166
|
+
return error instanceof Error ? "application" : "unknown";
|
|
167
|
+
} catch {
|
|
168
|
+
return "unknown";
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var captureOperation = (options, report) => {
|
|
172
|
+
try {
|
|
173
|
+
const labels = snapshotOptions(options, report);
|
|
174
|
+
const summarizeError = captureErrorSummary(options, report);
|
|
175
|
+
const traceId = globalThis.crypto.randomUUID();
|
|
176
|
+
const startedAtMs = Date.now();
|
|
177
|
+
const monotonicStart = performance.now();
|
|
178
|
+
let usage;
|
|
179
|
+
let closed = false;
|
|
180
|
+
const span = {
|
|
181
|
+
setUsage(value) {
|
|
182
|
+
if (closed) return;
|
|
183
|
+
try {
|
|
184
|
+
usage = usageSchema.parse(value);
|
|
185
|
+
} catch {
|
|
186
|
+
report({ code: "invalid_usage", count: 1 });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
span,
|
|
192
|
+
summarizeError,
|
|
193
|
+
complete(errorType, errorSummary) {
|
|
194
|
+
closed = true;
|
|
195
|
+
const event = traceEventSchema.safeParse({
|
|
196
|
+
...labels,
|
|
197
|
+
traceId,
|
|
198
|
+
status: errorType === void 0 ? "success" : "error",
|
|
199
|
+
startedAt: new Date(startedAtMs).toISOString(),
|
|
200
|
+
endedAt: new Date(Math.max(startedAtMs, Date.now())).toISOString(),
|
|
201
|
+
durationMs: Math.max(0, performance.now() - monotonicStart),
|
|
202
|
+
...usage,
|
|
203
|
+
...errorSummary === void 0 ? {} : { errorSummary },
|
|
204
|
+
...errorType === void 0 ? {} : { errorType }
|
|
205
|
+
});
|
|
206
|
+
if (event.success) return event.data;
|
|
207
|
+
report({ code: "invalid_event", count: 1 });
|
|
208
|
+
return void 0;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
} catch {
|
|
212
|
+
report({ code: "invalid_event", count: 1 });
|
|
213
|
+
return void 0;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// src/batching.ts
|
|
218
|
+
var createBatch = (queue, batchSize) => {
|
|
219
|
+
let events = [];
|
|
220
|
+
let body = "";
|
|
221
|
+
for (const event of queue.slice(0, batchSize)) {
|
|
222
|
+
const candidate = [...events, event];
|
|
223
|
+
const encoded = JSON.stringify({ events: candidate });
|
|
224
|
+
if (new TextEncoder().encode(encoded).length > MAX_PAYLOAD_BYTES) break;
|
|
225
|
+
events = candidate;
|
|
226
|
+
body = encoded;
|
|
227
|
+
}
|
|
228
|
+
return { events, body };
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// src/config.ts
|
|
232
|
+
import { z as z3 } from "zod";
|
|
233
|
+
var milliseconds = z3.number().int().min(1).max(36e5);
|
|
234
|
+
var configSchema = z3.object({
|
|
235
|
+
apiKey: z3.string().min(1).max(4096).regex(/^[\x21-\x7e]+$/),
|
|
236
|
+
endpoint: z3.string().max(2048),
|
|
237
|
+
enabled: z3.boolean().default(true),
|
|
238
|
+
batchSize: z3.number().int().min(1).max(MAX_BATCH_SIZE).default(MAX_BATCH_SIZE),
|
|
239
|
+
flushIntervalMs: milliseconds.default(5e3),
|
|
240
|
+
requestTimeoutMs: milliseconds.default(5e3),
|
|
241
|
+
maxQueueSize: z3.number().int().min(1).max(1e5).default(1e3),
|
|
242
|
+
maxAttempts: z3.number().int().min(1).max(10).default(3),
|
|
243
|
+
retryBaseMs: z3.number().int().min(0).max(6e4).default(250),
|
|
244
|
+
retryMaxMs: z3.number().int().min(0).max(6e4).default(3e4),
|
|
245
|
+
onDiagnostic: z3.custom((value) => typeof value === "function").optional(),
|
|
246
|
+
fetch: z3.custom((value) => typeof value === "function").optional()
|
|
247
|
+
}).strict();
|
|
248
|
+
var resolveEndpoint = (endpoint) => {
|
|
249
|
+
const url = new URL(endpoint);
|
|
250
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
251
|
+
if (url.username || url.password || url.search || url.hash) throw new TypeError();
|
|
252
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) throw new TypeError();
|
|
253
|
+
const base = url.pathname.replace(/\/+$/, "");
|
|
254
|
+
url.pathname = base.endsWith("/v1/events/batch") ? base : `${base}/v1/events/batch`;
|
|
255
|
+
return url.toString();
|
|
256
|
+
};
|
|
257
|
+
var resolveConfig = (config) => {
|
|
258
|
+
try {
|
|
259
|
+
const parsed = configSchema.parse(config);
|
|
260
|
+
const fetch = parsed.fetch ?? globalThis.fetch;
|
|
261
|
+
if (typeof fetch !== "function") throw new TypeError();
|
|
262
|
+
return Object.freeze({ ...parsed, endpoint: resolveEndpoint(parsed.endpoint), fetch });
|
|
263
|
+
} catch {
|
|
264
|
+
throw new TypeError("Invalid TraceAI configuration");
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// src/diagnostics.ts
|
|
269
|
+
var createDiagnosticReporter = (callback) => (diagnostic) => {
|
|
270
|
+
try {
|
|
271
|
+
const returned = callback?.(Object.freeze({ ...diagnostic }));
|
|
272
|
+
if (returned !== void 0) void Promise.resolve(returned).catch(() => void 0);
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/transport.ts
|
|
278
|
+
var retryAfterDelay = (header, maximum) => {
|
|
279
|
+
if (!header) return 0;
|
|
280
|
+
const seconds = Number(header);
|
|
281
|
+
const duration = Number.isFinite(seconds) ? seconds * 1e3 : Date.parse(header) - Date.now();
|
|
282
|
+
return Number.isFinite(duration) ? Math.min(maximum, Math.max(0, duration)) : 0;
|
|
283
|
+
};
|
|
284
|
+
var waitForRetry = (result, attempt, config) => {
|
|
285
|
+
const exponentialCap = Math.min(config.retryMaxMs, config.retryBaseMs * 2 ** (attempt - 1));
|
|
286
|
+
const jitter = Math.floor(Math.random() * exponentialCap);
|
|
287
|
+
const delay = Math.max(jitter, result.retryAfterMs ?? 0);
|
|
288
|
+
return delay > 0 ? new Promise((resolve) => setTimeout(resolve, delay)) : Promise.resolve();
|
|
289
|
+
};
|
|
290
|
+
var BatchTransport = class {
|
|
291
|
+
constructor(config, report) {
|
|
292
|
+
this.config = config;
|
|
293
|
+
this.report = report;
|
|
294
|
+
}
|
|
295
|
+
config;
|
|
296
|
+
report;
|
|
297
|
+
async send(body, count) {
|
|
298
|
+
for (let attempt = 1; attempt <= this.config.maxAttempts; attempt++) {
|
|
299
|
+
const result = await this.sendAttempt(body);
|
|
300
|
+
if (result.successful) return true;
|
|
301
|
+
const detail = {
|
|
302
|
+
count,
|
|
303
|
+
attempt,
|
|
304
|
+
...result.httpStatus === void 0 ? {} : { httpStatus: result.httpStatus }
|
|
305
|
+
};
|
|
306
|
+
if (!result.retryable || attempt === this.config.maxAttempts) {
|
|
307
|
+
this.report({ code: "delivery_failed", ...detail });
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
this.report({ code: "delivery_retry", ...detail });
|
|
311
|
+
await waitForRetry(result, attempt, this.config);
|
|
312
|
+
}
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
async sendAttempt(body) {
|
|
316
|
+
const controller = new AbortController();
|
|
317
|
+
let timer;
|
|
318
|
+
try {
|
|
319
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
320
|
+
timer = setTimeout(() => {
|
|
321
|
+
controller.abort();
|
|
322
|
+
reject(new Error("Telemetry request timed out"));
|
|
323
|
+
}, this.config.requestTimeoutMs);
|
|
324
|
+
});
|
|
325
|
+
const request = this.config.fetch(this.config.endpoint, {
|
|
326
|
+
method: "POST",
|
|
327
|
+
headers: {
|
|
328
|
+
"Content-Type": "application/json",
|
|
329
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
330
|
+
},
|
|
331
|
+
body,
|
|
332
|
+
signal: controller.signal,
|
|
333
|
+
redirect: "error"
|
|
334
|
+
});
|
|
335
|
+
const response = await Promise.race([request, timeout]);
|
|
336
|
+
void response.body?.cancel().catch(() => void 0);
|
|
337
|
+
return {
|
|
338
|
+
successful: response.ok,
|
|
339
|
+
retryable: response.status === 408 || response.status === 429 || response.status >= 500 && response.status <= 599,
|
|
340
|
+
httpStatus: response.status,
|
|
341
|
+
retryAfterMs: retryAfterDelay(response.headers.get("Retry-After"), this.config.retryMaxMs)
|
|
342
|
+
};
|
|
343
|
+
} catch {
|
|
344
|
+
return { successful: false, retryable: true };
|
|
345
|
+
} finally {
|
|
346
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// src/index.ts
|
|
352
|
+
var inactiveSpan = Object.freeze({ setUsage: () => void 0 });
|
|
353
|
+
var delivered = Object.freeze({ status: "delivered" });
|
|
354
|
+
var deliveryFailed = Object.freeze({
|
|
355
|
+
status: "dropped",
|
|
356
|
+
reason: "delivery_failed"
|
|
357
|
+
});
|
|
358
|
+
var TraceAI = class {
|
|
359
|
+
config;
|
|
360
|
+
report;
|
|
361
|
+
transport;
|
|
362
|
+
interval;
|
|
363
|
+
queue = [];
|
|
364
|
+
inFlightCount = 0;
|
|
365
|
+
drainPromise;
|
|
366
|
+
drainAllRequested = false;
|
|
367
|
+
shutdownPromise;
|
|
368
|
+
acceptingTraces = true;
|
|
369
|
+
activeCount = 0;
|
|
370
|
+
activeBarrier;
|
|
371
|
+
resolveActiveBarrier;
|
|
372
|
+
constructor(config) {
|
|
373
|
+
this.config = resolveConfig(config);
|
|
374
|
+
this.report = createDiagnosticReporter(this.config.onDiagnostic);
|
|
375
|
+
this.transport = new BatchTransport(this.config, this.report);
|
|
376
|
+
if (this.config.enabled) {
|
|
377
|
+
this.interval = setInterval(() => {
|
|
378
|
+
void this.flush();
|
|
379
|
+
}, this.config.flushIntervalMs);
|
|
380
|
+
if (typeof this.interval === "object" && "unref" in this.interval) this.interval.unref();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async trace(options, operation) {
|
|
384
|
+
if (!this.config.enabled) return operation(inactiveSpan);
|
|
385
|
+
if (!this.acceptingTraces) {
|
|
386
|
+
this.report({ code: "client_closed", count: 1 });
|
|
387
|
+
return operation(inactiveSpan);
|
|
388
|
+
}
|
|
389
|
+
this.activeCount++;
|
|
390
|
+
const capture = captureOperation(options, this.report);
|
|
391
|
+
let errorType;
|
|
392
|
+
let errorSummary;
|
|
393
|
+
try {
|
|
394
|
+
return await operation(capture?.span ?? inactiveSpan);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
errorType = classifyError(error);
|
|
397
|
+
errorSummary = capture?.summarizeError(error);
|
|
398
|
+
throw error;
|
|
399
|
+
} finally {
|
|
400
|
+
this.completeSafely(() => capture?.complete(errorType, errorSummary));
|
|
401
|
+
this.finishActiveOperation();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/** Validate and snapshot an already-completed event. Resolves only on delivery or explicit drop. */
|
|
405
|
+
record(event) {
|
|
406
|
+
if (!this.config.enabled) return Promise.resolve({ status: "dropped", reason: "disabled" });
|
|
407
|
+
if (!this.acceptingTraces) {
|
|
408
|
+
this.report({ code: "client_closed", count: 1 });
|
|
409
|
+
return Promise.resolve({ status: "dropped", reason: "client_closed" });
|
|
410
|
+
}
|
|
411
|
+
const snapshot = snapshotCompletedEvent(event);
|
|
412
|
+
if (!snapshot) {
|
|
413
|
+
this.report({ code: "invalid_event", count: 1 });
|
|
414
|
+
return Promise.resolve({ status: "dropped", reason: "invalid_event" });
|
|
415
|
+
}
|
|
416
|
+
return new Promise((settle) => this.enqueue({ event: snapshot, settle }));
|
|
417
|
+
}
|
|
418
|
+
/** Singleflight best-effort drain. Use record() receipts when acknowledged delivery matters. */
|
|
419
|
+
flush() {
|
|
420
|
+
if (!this.queue.length && !this.drainPromise) return Promise.resolve();
|
|
421
|
+
this.drainAllRequested = true;
|
|
422
|
+
return this.startDrain();
|
|
423
|
+
}
|
|
424
|
+
/** Stops new telemetry, awaits already-started operations, then drains. Idempotent. */
|
|
425
|
+
shutdown() {
|
|
426
|
+
if (this.shutdownPromise) return this.shutdownPromise;
|
|
427
|
+
this.acceptingTraces = false;
|
|
428
|
+
if (this.interval !== void 0) clearInterval(this.interval);
|
|
429
|
+
this.shutdownPromise = this.waitForActiveOperations().then(async () => {
|
|
430
|
+
await this.flush();
|
|
431
|
+
while (this.queue.length) await this.flush();
|
|
432
|
+
});
|
|
433
|
+
return this.shutdownPromise;
|
|
434
|
+
}
|
|
435
|
+
waitForActiveOperations() {
|
|
436
|
+
if (!this.activeCount) return Promise.resolve();
|
|
437
|
+
this.activeBarrier ??= new Promise((resolve) => {
|
|
438
|
+
this.resolveActiveBarrier = resolve;
|
|
439
|
+
});
|
|
440
|
+
return this.activeBarrier;
|
|
441
|
+
}
|
|
442
|
+
finishActiveOperation() {
|
|
443
|
+
this.activeCount--;
|
|
444
|
+
if (this.activeCount === 0) this.resolveActiveBarrier?.();
|
|
445
|
+
}
|
|
446
|
+
completeSafely(complete) {
|
|
447
|
+
try {
|
|
448
|
+
const event = complete();
|
|
449
|
+
if (event) this.enqueue({ event });
|
|
450
|
+
} catch {
|
|
451
|
+
this.report({ code: "invalid_event", count: 1 });
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
enqueue(entry) {
|
|
455
|
+
if (this.queue.length + this.inFlightCount >= this.config.maxQueueSize) {
|
|
456
|
+
this.report({ code: "queue_full", count: 1 });
|
|
457
|
+
entry.settle?.({ status: "dropped", reason: "queue_full" });
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
this.queue = [...this.queue, entry];
|
|
461
|
+
if (this.queue.length >= this.config.batchSize)
|
|
462
|
+
queueMicrotask(() => {
|
|
463
|
+
void this.startDrain();
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
startDrain() {
|
|
467
|
+
if (this.drainPromise) return this.drainPromise;
|
|
468
|
+
if (!this.shouldDrain()) return Promise.resolve();
|
|
469
|
+
let finish;
|
|
470
|
+
const draining = new Promise((resolve) => {
|
|
471
|
+
finish = resolve;
|
|
472
|
+
});
|
|
473
|
+
this.drainPromise = draining;
|
|
474
|
+
void this.drainQueue(finish);
|
|
475
|
+
return draining;
|
|
476
|
+
}
|
|
477
|
+
shouldDrain() {
|
|
478
|
+
return this.queue.length > 0 && (this.drainAllRequested || this.queue.length >= this.config.batchSize);
|
|
479
|
+
}
|
|
480
|
+
async drainQueue(finish) {
|
|
481
|
+
try {
|
|
482
|
+
while (this.shouldDrain()) await this.sendNextBatch();
|
|
483
|
+
} catch {
|
|
484
|
+
this.report({ code: "delivery_failed", count: this.queue.length });
|
|
485
|
+
for (const entry of this.queue) entry.settle?.(deliveryFailed);
|
|
486
|
+
this.queue = [];
|
|
487
|
+
} finally {
|
|
488
|
+
this.drainPromise = void 0;
|
|
489
|
+
this.drainAllRequested = false;
|
|
490
|
+
finish();
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
async sendNextBatch() {
|
|
494
|
+
const { events, body } = createBatch(
|
|
495
|
+
this.queue.slice(0, this.config.batchSize).map(({ event }) => event),
|
|
496
|
+
this.config.batchSize
|
|
497
|
+
);
|
|
498
|
+
if (!events.length) {
|
|
499
|
+
this.queue[0]?.settle?.({ status: "dropped", reason: "invalid_event" });
|
|
500
|
+
this.queue = this.queue.slice(1);
|
|
501
|
+
this.report({ code: "invalid_event", count: 1 });
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const entries = this.queue.slice(0, events.length);
|
|
505
|
+
this.queue = this.queue.slice(events.length);
|
|
506
|
+
this.inFlightCount = entries.length;
|
|
507
|
+
let result = deliveryFailed;
|
|
508
|
+
try {
|
|
509
|
+
if (await this.transport.send(body, entries.length)) result = delivered;
|
|
510
|
+
} finally {
|
|
511
|
+
this.inFlightCount = 0;
|
|
512
|
+
for (const entry of entries) entry.settle?.(result);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
export {
|
|
517
|
+
TraceAI,
|
|
518
|
+
sanitizeErrorSummary,
|
|
519
|
+
snapshotTraceMetadata
|
|
520
|
+
};
|
|
521
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../shared/src/index.ts","../../shared/src/error-summary.ts","../src/events.ts","../src/batching.ts","../src/config.ts","../src/diagnostics.ts","../src/transport.ts","../src/index.ts"],"sourcesContent":["import { z } from 'zod';\nimport { errorSummarySchema } from './error-summary';\nexport {\n ERROR_CAPTURE_POLICY,\n MAX_ERROR_SUMMARY_CHARS,\n MAX_ERROR_SUMMARY_BYTES,\n errorSummarySchema,\n sanitizeErrorSummary,\n} from './error-summary';\nexport const MAX_BATCH_SIZE = 50;\nexport const MAX_PAYLOAD_BYTES = 256 * 1024;\nexport const MAX_METADATA_BYTES = 8 * 1024;\nexport const MAX_WINDOW_MS = 31 * 24 * 60 * 60 * 1000;\nconst label = z.string().trim().min(1).max(120);\nconst timestamp = z.iso.datetime();\nconst utcTimestampParts = (value: string) => {\n const [whole, fraction = ''] = value.slice(0, -1).split('.');\n return { second: Date.parse(`${whole}Z`), fraction };\n};\nconst timestampsOrdered = (startedAt: string, endedAt: string): boolean => {\n const start = utcTimestampParts(startedAt);\n const end = utcTimestampParts(endedAt);\n if (start.second !== end.second) return start.second < end.second;\n // Date.parse truncates sub-millisecond digits; compare exact decimal fractions before normalization.\n const precision = Math.max(start.fraction.length, end.fraction.length);\n return start.fraction.padEnd(precision, '0') <= end.fraction.padEnd(precision, '0');\n};\nconst tokenCount = z.number().int().min(0).max(10_000_000);\nexport const usageSchema = z.object({ inputTokens: tokenCount, outputTokens: tokenCount }).strict();\nexport const metadataSchema = z\n .record(\n z.string().max(100),\n z.union([z.string().max(2000), z.number().finite(), z.boolean(), z.null()]),\n )\n .refine(\n (value) => new TextEncoder().encode(JSON.stringify(value)).length <= MAX_METADATA_BYTES,\n 'Metadata exceeds 8 KiB',\n );\nexport const traceEventSchema = z\n .object({\n traceId: z\n .string()\n .min(1)\n .max(128)\n .regex(/^[a-zA-Z0-9_-]+$/),\n name: label,\n provider: label,\n model: label,\n status: z.enum(['success', 'error']),\n startedAt: timestamp,\n endedAt: timestamp,\n durationMs: z.number().finite().min(0).max(86_400_000),\n inputTokens: tokenCount.optional(),\n outputTokens: tokenCount.optional(),\n errorType: z.enum(['timeout', 'rate_limit', 'network', 'application', 'unknown']).optional(),\n errorSummary: errorSummarySchema.optional(),\n metadata: metadataSchema.optional(),\n })\n .strict()\n .refine((event) => timestampsOrdered(event.startedAt, event.endedAt), {\n message: 'End timestamp must not precede start',\n })\n .refine((event) => event.errorSummary === undefined || event.status === 'error', {\n message: 'An explicit error summary is only permitted on failed operations',\n });\nexport const batchSchema = z\n .object({ events: z.array(traceEventSchema).min(1).max(MAX_BATCH_SIZE) })\n .strict();\nexport type TraceEvent = z.infer<typeof traceEventSchema>;\nexport type Usage = z.infer<typeof usageSchema>;\nexport type TraceMetadata = z.infer<typeof metadataSchema>;\nexport const projectSchema = z\n .object({\n name: z.string().trim().min(1).max(80),\n description: z.string().trim().max(500).default(''),\n })\n .strict();\nexport const credentialsSchema = z\n .object({\n email: z\n .email()\n .max(254)\n .transform((value) => value.toLowerCase()),\n password: z.string().min(12).max(128),\n })\n .strict();\n\nexport interface Trace extends TraceEvent {\n projectId: string;\n estimatedCostNanoUsd: string | null;\n pricingVersion: string | null;\n createdAt: string;\n pricing?: PricingProvenance;\n}\nexport interface PricingProvenance {\n version: string;\n provider: string;\n model: string;\n currency: string;\n inputNanoUsdPerMillion: string;\n outputNanoUsdPerMillion: string;\n effectiveFrom: string;\n effectiveTo: string | null;\n sourceUrl: string;\n simulated: boolean;\n verifiedAt: string | null;\n billingBasis: string | null;\n}\nexport const pricingProvenanceSchema = z\n .object({\n version: z.string().min(1).max(120),\n provider: label,\n model: label,\n currency: z.string().min(1).max(10),\n inputNanoUsdPerMillion: z.string().regex(/^\\d{1,20}$/),\n outputNanoUsdPerMillion: z.string().regex(/^\\d{1,20}$/),\n effectiveFrom: timestamp,\n effectiveTo: timestamp.nullable(),\n sourceUrl: z\n .url()\n .max(2048)\n .refine((value) => {\n const url = new URL(value);\n return url.protocol === 'https:' && !url.username && !url.password;\n }),\n simulated: z.boolean(),\n verifiedAt: timestamp.nullable(),\n billingBasis: z.string().max(100).nullable(),\n })\n .strict();\nexport interface Overview {\n totalRequests: number;\n successfulRequests: number;\n failedRequests: number;\n errorRate: number;\n averageLatencyMs: number;\n p50LatencyMs: number;\n p95LatencyMs: number;\n p99LatencyMs: number;\n inputTokens: number;\n outputTokens: number;\n estimatedCostNanoUsd: string | null;\n /** Sum of priced requests only, never a complete total if unpricedRequests > 0. */\n knownEstimatedCostNanoUsd: string | null;\n pricedRequests: number;\n unpricedRequests: number;\n}\nexport interface MetricBucket extends Overview {\n timestamp: string;\n}\nexport interface ModelComparison extends Overview {\n provider: string;\n model: string;\n}\nexport interface TracePage {\n items: Trace[];\n nextCursor: string | null;\n}\nexport interface Project {\n id: string;\n name: string;\n description: string;\n createdAt: string;\n updatedAt: string;\n}\nexport interface ApiKey {\n id: string;\n keyPrefix: string;\n createdAt: string;\n lastUsedAt: string | null;\n revokedAt: string | null;\n}\nexport interface QueryFilters {\n from: string;\n to: string;\n provider?: string;\n model?: string;\n status?: 'success' | 'error';\n traceId?: string;\n limit?: number;\n cursor?: string;\n sort?: 'newest' | 'oldest';\n}\nexport const percentile = (sortedValues: readonly number[], percentileValue: number): number => {\n if (!sortedValues.length) return 0;\n // Nearest-rank: rank = ceil(p * n), one-based; stable for ties and singleton sets.\n return sortedValues[Math.max(0, Math.ceil(percentileValue * sortedValues.length) - 1)] ?? 0;\n};\nexport const estimateCostNanoUsd = (\n usage: Usage,\n pricing: { inputNanoUsdPerMillion: string; outputNanoUsdPerMillion: string },\n): string => {\n const numerator =\n BigInt(usage.inputTokens) * BigInt(pricing.inputNanoUsdPerMillion) +\n BigInt(usage.outputTokens) * BigInt(pricing.outputNanoUsdPerMillion);\n // Round half up ONCE after adding input and output costs. Never use floats for money.\n return ((numerator + 500_000n) / 1_000_000n).toString();\n};\n","import { z } from 'zod';\n\nexport const MAX_ERROR_SUMMARY_CHARS = 1000;\nexport const MAX_ERROR_SUMMARY_BYTES = 4 * 1024;\nexport const ERROR_CAPTURE_POLICY = 'explicit-summary-v1';\nexport const errorSummarySchema = z\n .string()\n .min(1)\n .max(MAX_ERROR_SUMMARY_CHARS)\n .refine((value) => new TextEncoder().encode(value).length <= MAX_ERROR_SUMMARY_BYTES);\n\nfunction stripControlCharacters(value: string): string {\n return Array.from(value, (character) => {\n const code = character.charCodeAt(0);\n return code < 32 || code === 127 ? ' ' : character;\n }).join('');\n}\n\n/** Best-effort known-pattern redaction, not a guarantee that arbitrary summaries contain no PII. */\nexport function sanitizeErrorSummary(value: string): string | undefined {\n if (!errorSummarySchema.safeParse(value).success) return undefined;\n const sanitized = stripControlCharacters(value)\n .replace(/(https?:\\/\\/)[^\\s/@]+@/gi, '$1[redacted]@')\n .replace(\n /\\b(?:authorization|proxy-authorization)[\"']?\\s*[:=]\\s*[\"']?(?:bearer\\s+|basic\\s+)?[^\\s,;\"'}]+/gi,\n 'authorization: [redacted]',\n )\n .replace(/\\b(?:bearer|basic)\\s+[a-z0-9._~+/=-]+/gi, '[redacted-auth]')\n .replace(/\\btai_[a-f0-9]{32}_[a-f0-9]{64}\\b/gi, '[redacted-key]')\n .replace(\n /\\b(?:sk|sess|ghp|gho|github_pat|xox[baprs])[-_][a-z0-9_-]{12,}\\b/gi,\n '[redacted-token]',\n )\n .replace(/\\beyJ[a-z0-9_-]+\\.[a-z0-9_-]+\\.[a-z0-9_-]+\\b/gi, '[redacted-token]')\n .replace(\n /\\b(api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|passwd|signature)[\"']?\\s*[:=]\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s,;&]+)/gi,\n '$1=[redacted]',\n )\n .replace(/\\b[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}\\b/gi, '[redacted-email]')\n .trim();\n return sanitized && errorSummarySchema.safeParse(sanitized).success ? sanitized : undefined;\n}\n","import {\n metadataSchema,\n sanitizeErrorSummary,\n traceEventSchema,\n usageSchema,\n type TraceEvent,\n} from '@traceai/shared';\nimport type { ReportDiagnostic } from './diagnostics';\nimport type { TraceMetadata, TraceOptions, TraceSpan, Usage } from './types';\n\nexport interface OperationCapture {\n span: TraceSpan;\n summarizeError(error: unknown): string | undefined;\n complete(errorType?: TraceEvent['errorType'], errorSummary?: string): TraceEvent | undefined;\n}\n\n/** Snapshots valid plain scalar metadata; never serializes arbitrary objects or array indices. */\nexport const snapshotTraceMetadata = (value: unknown): TraceMetadata | undefined => {\n try {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n const prototype: unknown = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) return undefined;\n return metadataSchema.parse(value);\n } catch {\n return undefined;\n }\n};\nconst snapshotOptions = (options: TraceOptions, report: ReportDiagnostic) => {\n const labels = { name: options.name, provider: options.provider, model: options.model };\n if (options.metadata === undefined) return labels;\n const metadata = snapshotTraceMetadata(options.metadata);\n if (metadata) return { ...labels, metadata };\n report({ code: 'invalid_metadata', count: 1 });\n return labels;\n};\n\nconst captureErrorSummary = (options: TraceOptions, report: ReportDiagnostic) => {\n let callback: TraceOptions['errorSummary'];\n try {\n callback = options.errorSummary;\n } catch {\n report({ code: 'invalid_error_summary', count: 1 });\n }\n return (error: unknown): string | undefined => {\n if (callback === undefined) return undefined;\n try {\n const value: unknown = callback(error);\n if (value === undefined) return undefined;\n if (typeof value === 'string') {\n const sanitized = sanitizeErrorSummary(value);\n if (sanitized !== undefined) return sanitized;\n } else {\n // A mistakenly async callback must not create an unhandled rejection.\n void Promise.resolve(value).catch(() => undefined);\n }\n } catch {\n // Callback failures must never mask the application's original exception.\n }\n report({ code: 'invalid_error_summary', count: 1 });\n return undefined;\n };\n};\n\nexport const snapshotCompletedEvent = (value: unknown): TraceEvent | undefined => {\n try {\n const parsed = traceEventSchema.safeParse(value);\n if (!parsed.success) return undefined;\n const { errorSummary, ...event } = parsed.data;\n const sanitized = errorSummary === undefined ? undefined : sanitizeErrorSummary(errorSummary);\n return sanitized === undefined ? event : { ...event, errorSummary: sanitized };\n } catch {\n return undefined;\n }\n};\n\nexport const classifyError = (error: unknown): NonNullable<TraceEvent['errorType']> => {\n try {\n if (typeof error !== 'object' || error === null) return 'unknown';\n const classified = error as { name?: unknown; status?: unknown; code?: unknown };\n if (\n classified.name === 'AbortError' ||\n classified.name === 'TimeoutError' ||\n classified.code === 'ETIMEDOUT'\n )\n return 'timeout';\n if (classified.status === 429) return 'rate_limit';\n if (\n classified.name === 'NetworkError' ||\n (typeof classified.code === 'string' &&\n ['ECONNRESET', 'ENOTFOUND', 'ECONNREFUSED'].includes(classified.code))\n )\n return 'network';\n return error instanceof Error ? 'application' : 'unknown';\n } catch {\n return 'unknown';\n }\n};\n\nexport const captureOperation = (\n options: TraceOptions,\n report: ReportDiagnostic,\n): OperationCapture | undefined => {\n try {\n const labels = snapshotOptions(options, report);\n const summarizeError = captureErrorSummary(options, report);\n const traceId = globalThis.crypto.randomUUID();\n const startedAtMs = Date.now();\n const monotonicStart = performance.now();\n let usage: Usage | undefined;\n let closed = false;\n const span: TraceSpan = {\n setUsage(value) {\n if (closed) return;\n try {\n usage = usageSchema.parse(value);\n } catch {\n report({ code: 'invalid_usage', count: 1 });\n }\n },\n };\n return {\n span,\n summarizeError,\n complete(errorType, errorSummary) {\n closed = true;\n const event = traceEventSchema.safeParse({\n ...labels,\n traceId,\n status: errorType === undefined ? 'success' : 'error',\n startedAt: new Date(startedAtMs).toISOString(),\n endedAt: new Date(Math.max(startedAtMs, Date.now())).toISOString(),\n durationMs: Math.max(0, performance.now() - monotonicStart),\n ...usage,\n ...(errorSummary === undefined ? {} : { errorSummary }),\n ...(errorType === undefined ? {} : { errorType }),\n });\n if (event.success) return event.data;\n report({ code: 'invalid_event', count: 1 });\n return undefined;\n },\n };\n } catch {\n report({ code: 'invalid_event', count: 1 });\n return undefined;\n }\n};\n","import { MAX_PAYLOAD_BYTES, type TraceEvent } from '@traceai/shared';\n\nexport const createBatch = (queue: readonly TraceEvent[], batchSize: number) => {\n let events: readonly TraceEvent[] = [];\n let body = '';\n for (const event of queue.slice(0, batchSize)) {\n const candidate = [...events, event];\n const encoded = JSON.stringify({ events: candidate });\n if (new TextEncoder().encode(encoded).length > MAX_PAYLOAD_BYTES) break;\n events = candidate;\n body = encoded;\n }\n return { events, body };\n};\n","import { z } from 'zod';\nimport { MAX_BATCH_SIZE } from '@traceai/shared';\nimport type { TraceAIConfig } from './types';\n\nconst milliseconds = z.number().int().min(1).max(3_600_000);\nconst configSchema = z\n .object({\n apiKey: z\n .string()\n .min(1)\n .max(4096)\n .regex(/^[\\x21-\\x7e]+$/),\n endpoint: z.string().max(2048),\n enabled: z.boolean().default(true),\n batchSize: z.number().int().min(1).max(MAX_BATCH_SIZE).default(MAX_BATCH_SIZE),\n flushIntervalMs: milliseconds.default(5000),\n requestTimeoutMs: milliseconds.default(5000),\n maxQueueSize: z.number().int().min(1).max(100_000).default(1000),\n maxAttempts: z.number().int().min(1).max(10).default(3),\n retryBaseMs: z.number().int().min(0).max(60_000).default(250),\n retryMaxMs: z.number().int().min(0).max(60_000).default(30_000),\n onDiagnostic: z\n .custom<NonNullable<TraceAIConfig['onDiagnostic']>>((value) => typeof value === 'function')\n .optional(),\n fetch: z.custom<typeof globalThis.fetch>((value) => typeof value === 'function').optional(),\n })\n .strict();\n\nexport type ResolvedConfig = ReturnType<typeof resolveConfig>;\n\nconst resolveEndpoint = (endpoint: string): string => {\n const url = new URL(endpoint);\n const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);\n if (url.username || url.password || url.search || url.hash) throw new TypeError();\n if (url.protocol !== 'https:' && !(url.protocol === 'http:' && loopback)) throw new TypeError();\n const base = url.pathname.replace(/\\/+$/, '');\n url.pathname = base.endsWith('/v1/events/batch') ? base : `${base}/v1/events/batch`;\n return url.toString();\n};\n\nexport const resolveConfig = (config: TraceAIConfig) => {\n try {\n const parsed = configSchema.parse(config);\n const fetch = parsed.fetch ?? globalThis.fetch;\n if (typeof fetch !== 'function') throw new TypeError();\n return Object.freeze({ ...parsed, endpoint: resolveEndpoint(parsed.endpoint), fetch });\n } catch {\n // Do not expose Zod issues: rejected values may contain credentials.\n throw new TypeError('Invalid TraceAI configuration');\n }\n};\n","import type { Diagnostic, TraceAIConfig } from './types';\n\nexport type ReportDiagnostic = (diagnostic: Diagnostic) => void;\n\nexport const createDiagnosticReporter =\n (callback?: TraceAIConfig['onDiagnostic']): ReportDiagnostic =>\n (diagnostic) => {\n try {\n const returned: unknown = callback?.(Object.freeze({ ...diagnostic }));\n // TypeScript permits async callbacks where void is expected. Swallow those too.\n if (returned !== undefined) void Promise.resolve(returned).catch(() => undefined);\n } catch {\n // An optional observer must never become part of the application's error path.\n }\n };\n","import type { ResolvedConfig } from './config';\nimport type { ReportDiagnostic } from './diagnostics';\n\ninterface AttemptResult {\n successful: boolean;\n retryable: boolean;\n httpStatus?: number;\n retryAfterMs?: number;\n}\n\nconst retryAfterDelay = (header: string | null, maximum: number): number => {\n if (!header) return 0;\n const seconds = Number(header);\n const duration = Number.isFinite(seconds) ? seconds * 1000 : Date.parse(header) - Date.now();\n return Number.isFinite(duration) ? Math.min(maximum, Math.max(0, duration)) : 0;\n};\n\nconst waitForRetry = (\n result: AttemptResult,\n attempt: number,\n config: ResolvedConfig,\n): Promise<void> => {\n const exponentialCap = Math.min(config.retryMaxMs, config.retryBaseMs * 2 ** (attempt - 1));\n const jitter = Math.floor(Math.random() * exponentialCap);\n const delay = Math.max(jitter, result.retryAfterMs ?? 0);\n return delay > 0 ? new Promise((resolve) => setTimeout(resolve, delay)) : Promise.resolve();\n};\n\nexport class BatchTransport {\n constructor(\n private readonly config: ResolvedConfig,\n private readonly report: ReportDiagnostic,\n ) {}\n\n async send(body: string, count: number): Promise<boolean> {\n for (let attempt = 1; attempt <= this.config.maxAttempts; attempt++) {\n const result = await this.sendAttempt(body);\n if (result.successful) return true;\n const detail = {\n count,\n attempt,\n ...(result.httpStatus === undefined ? {} : { httpStatus: result.httpStatus }),\n };\n if (!result.retryable || attempt === this.config.maxAttempts) {\n this.report({ code: 'delivery_failed', ...detail });\n return false;\n }\n this.report({ code: 'delivery_retry', ...detail });\n await waitForRetry(result, attempt, this.config);\n }\n return false;\n }\n\n private async sendAttempt(body: string): Promise<AttemptResult> {\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const timeout = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n reject(new Error('Telemetry request timed out'));\n }, this.config.requestTimeoutMs);\n });\n const request = this.config.fetch(this.config.endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body,\n signal: controller.signal,\n redirect: 'error',\n });\n const response = await Promise.race([request, timeout]);\n // No response/error body is read: it may contain server or credential details.\n void response.body?.cancel().catch(() => undefined);\n return {\n successful: response.ok,\n retryable:\n response.status === 408 ||\n response.status === 429 ||\n (response.status >= 500 && response.status <= 599),\n httpStatus: response.status,\n retryAfterMs: retryAfterDelay(response.headers.get('Retry-After'), this.config.retryMaxMs),\n };\n } catch {\n return { successful: false, retryable: true };\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n }\n}\n","import type { TraceEvent } from '@traceai/shared';\nexport { sanitizeErrorSummary } from '@traceai/shared';\nexport { snapshotTraceMetadata } from './events';\nimport { createBatch } from './batching';\nimport { resolveConfig, type ResolvedConfig } from './config';\nimport { createDiagnosticReporter, type ReportDiagnostic } from './diagnostics';\nimport { captureOperation, classifyError, snapshotCompletedEvent } from './events';\nimport { BatchTransport } from './transport';\nimport type {\n CompletedTrace,\n DeliveryResult,\n TraceAIConfig,\n TraceOptions,\n TraceSpan,\n} from './types';\n\nexport type {\n CompletedTrace,\n DeliveryResult,\n Diagnostic,\n TraceAIConfig,\n TraceMetadata,\n TraceOptions,\n TraceSpan,\n Usage,\n} from './types';\n\ninterface QueuedEvent {\n readonly event: TraceEvent;\n readonly settle?: (result: DeliveryResult) => void;\n}\nconst inactiveSpan: TraceSpan = Object.freeze({ setUsage: () => undefined });\nconst delivered: DeliveryResult = Object.freeze({ status: 'delivered' });\nconst deliveryFailed: DeliveryResult = Object.freeze({\n status: 'dropped',\n reason: 'delivery_failed',\n});\n\n/** A best-effort in-memory telemetry client; operation results and errors always win. */\nexport class TraceAI {\n private readonly config: ResolvedConfig;\n private readonly report: ReportDiagnostic;\n private readonly transport: BatchTransport;\n private readonly interval?: ReturnType<typeof setInterval>;\n private queue: readonly QueuedEvent[] = [];\n private inFlightCount = 0;\n private drainPromise?: Promise<void>;\n private drainAllRequested = false;\n private shutdownPromise?: Promise<void>;\n private acceptingTraces = true;\n private activeCount = 0;\n private activeBarrier?: Promise<void>;\n private resolveActiveBarrier?: () => void;\n\n constructor(config: TraceAIConfig) {\n this.config = resolveConfig(config);\n this.report = createDiagnosticReporter(this.config.onDiagnostic);\n this.transport = new BatchTransport(this.config, this.report);\n if (this.config.enabled) {\n this.interval = setInterval(() => {\n void this.flush();\n }, this.config.flushIntervalMs);\n // Node applications must opt in to draining; background timers must not keep them alive.\n if (typeof this.interval === 'object' && 'unref' in this.interval) this.interval.unref();\n }\n }\n\n async trace<T>(options: TraceOptions, operation: (span: TraceSpan) => Promise<T>): Promise<T> {\n if (!this.config.enabled) return operation(inactiveSpan);\n if (!this.acceptingTraces) {\n this.report({ code: 'client_closed', count: 1 });\n return operation(inactiveSpan);\n }\n // Constant state, independent of callback concurrency. Register before reentrant diagnostics.\n this.activeCount++;\n const capture = captureOperation(options, this.report);\n let errorType: TraceEvent['errorType'];\n let errorSummary: string | undefined;\n try {\n return await operation(capture?.span ?? inactiveSpan);\n } catch (error) {\n errorType = classifyError(error);\n errorSummary = capture?.summarizeError(error);\n throw error;\n } finally {\n this.completeSafely(() => capture?.complete(errorType, errorSummary));\n this.finishActiveOperation();\n }\n }\n\n /** Validate and snapshot an already-completed event. Resolves only on delivery or explicit drop. */\n record(event: CompletedTrace): Promise<DeliveryResult> {\n if (!this.config.enabled) return Promise.resolve({ status: 'dropped', reason: 'disabled' });\n if (!this.acceptingTraces) {\n this.report({ code: 'client_closed', count: 1 });\n return Promise.resolve({ status: 'dropped', reason: 'client_closed' });\n }\n const snapshot = snapshotCompletedEvent(event);\n if (!snapshot) {\n this.report({ code: 'invalid_event', count: 1 });\n return Promise.resolve({ status: 'dropped', reason: 'invalid_event' });\n }\n return new Promise((settle) => this.enqueue({ event: snapshot, settle }));\n }\n\n /** Singleflight best-effort drain. Use record() receipts when acknowledged delivery matters. */\n flush(): Promise<void> {\n if (!this.queue.length && !this.drainPromise) return Promise.resolve();\n this.drainAllRequested = true;\n return this.startDrain();\n }\n\n /** Stops new telemetry, awaits already-started operations, then drains. Idempotent. */\n shutdown(): Promise<void> {\n if (this.shutdownPromise) return this.shutdownPromise;\n this.acceptingTraces = false;\n if (this.interval !== undefined) clearInterval(this.interval);\n this.shutdownPromise = this.waitForActiveOperations().then(async () => {\n await this.flush();\n // An older drain may have observed an empty queue just before the active barrier.\n while (this.queue.length) await this.flush();\n });\n return this.shutdownPromise;\n }\n\n private waitForActiveOperations(): Promise<void> {\n if (!this.activeCount) return Promise.resolve();\n this.activeBarrier ??= new Promise((resolve) => {\n this.resolveActiveBarrier = resolve;\n });\n return this.activeBarrier;\n }\n\n private finishActiveOperation(): void {\n this.activeCount--;\n if (this.activeCount === 0) this.resolveActiveBarrier?.();\n }\n\n private completeSafely(complete: () => TraceEvent | undefined): void {\n try {\n const event = complete();\n if (event) this.enqueue({ event });\n } catch {\n this.report({ code: 'invalid_event', count: 1 });\n }\n }\n\n private enqueue(entry: QueuedEvent): void {\n if (this.queue.length + this.inFlightCount >= this.config.maxQueueSize) {\n this.report({ code: 'queue_full', count: 1 });\n entry.settle?.({ status: 'dropped', reason: 'queue_full' });\n return;\n }\n this.queue = [...this.queue, entry];\n if (this.queue.length >= this.config.batchSize)\n queueMicrotask(() => {\n void this.startDrain();\n });\n }\n\n private startDrain(): Promise<void> {\n if (this.drainPromise) return this.drainPromise;\n if (!this.shouldDrain()) return Promise.resolve();\n let finish!: () => void;\n const draining = new Promise<void>((resolve) => {\n finish = resolve;\n });\n this.drainPromise = draining;\n void this.drainQueue(finish);\n return draining;\n }\n\n private shouldDrain(): boolean {\n return (\n this.queue.length > 0 &&\n (this.drainAllRequested || this.queue.length >= this.config.batchSize)\n );\n }\n\n private async drainQueue(finish: () => void): Promise<void> {\n try {\n while (this.shouldDrain()) await this.sendNextBatch();\n } catch {\n this.report({ code: 'delivery_failed', count: this.queue.length });\n for (const entry of this.queue) entry.settle?.(deliveryFailed);\n this.queue = [];\n } finally {\n // Clear state and resolve atomically, without promise-chain gaps that strand new events.\n this.drainPromise = undefined;\n this.drainAllRequested = false;\n finish();\n }\n }\n\n private async sendNextBatch(): Promise<void> {\n const { events, body } = createBatch(\n this.queue.slice(0, this.config.batchSize).map(({ event }) => event),\n this.config.batchSize,\n );\n if (!events.length) {\n this.queue[0]?.settle?.({ status: 'dropped', reason: 'invalid_event' });\n this.queue = this.queue.slice(1);\n this.report({ code: 'invalid_event', count: 1 });\n return;\n }\n const entries = this.queue.slice(0, events.length);\n this.queue = this.queue.slice(events.length);\n this.inFlightCount = entries.length;\n let result = deliveryFailed;\n try {\n if (await this.transport.send(body, entries.length)) result = delivered;\n } finally {\n this.inFlightCount = 0;\n for (const entry of entries) entry.settle?.(result);\n }\n }\n}\n"],"mappings":"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","names":["z","z","z","z"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akai_80percent/traceai-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Non-blocking, privacy-first telemetry for asynchronous AI operations",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/Metricra-code/traceai.git",
|
|
11
|
+
"directory": "packages/sdk"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/Metricra-code/traceai/blob/main/docs/sdk.md",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/Metricra-code/traceai/issues"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public",
|
|
19
|
+
"registry": "https://registry.npmjs.org/"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22"
|
|
23
|
+
},
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"build": "tsup --config tsup.config.ts"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"zod": "4.6.5"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@traceai/shared": "0.1.0"
|
|
46
|
+
}
|
|
47
|
+
}
|