@alvin0/ai-agent-sdk-observability-fetch 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 +35 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +322 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 alvin0 (chaulamdinhai) <chaulamdinhai@gmail.com>
|
|
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
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @alvin0/ai-agent-sdk-observability-fetch
|
|
2
|
+
|
|
3
|
+
Runtime: **Universal** (Edge/Worker, browser, Deno, Bun, and Node).
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm add @alvin0/ai-agent-sdk-core @alvin0/ai-agent-sdk-observability-fetch
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Universal acknowledged HTTPS exporter for `@alvin0/ai-agent-sdk-core/observability`. It sends
|
|
10
|
+
bounded JSON batches with an idempotency key and retries only the observation
|
|
11
|
+
batch—not the model/provider operation that produced it.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { fetchObservationExporter } from '@alvin0/ai-agent-sdk-observability-fetch'
|
|
15
|
+
|
|
16
|
+
const exporter = fetchObservationExporter({
|
|
17
|
+
endpoint: 'https://telemetry.example.com/v1/observations',
|
|
18
|
+
headers: { authorization: `Bearer ${telemetryToken}` },
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use `flushObservabilityWithWaitUntil()` to hand a flush promise to an Edge host's
|
|
23
|
+
explicit `waitUntil` function. The package never assumes a platform global.
|
|
24
|
+
|
|
25
|
+
The exporter uses POST JSON with `idempotency-key: <batchId>`, a 10-second
|
|
26
|
+
request timeout, and batches capped at 256 events/512 KiB. It accepts HTTP 204
|
|
27
|
+
or `{ "acceptedBatchId": "<batchId>" }`. Network failures, 408, 425, 429, and
|
|
28
|
+
5xx responses retry the identical serialized batch with bounded full jitter;
|
|
29
|
+
other responses fail without retry. HTTPS is required except for an explicitly
|
|
30
|
+
enabled localhost/loopback test endpoint, and redirects or cross-origin
|
|
31
|
+
responses are rejected.
|
|
32
|
+
|
|
33
|
+
Composition: `runtime.observability.exporters`. Lifecycle:
|
|
34
|
+
`explicit-owned-or-borrowed`; every runtime registration states ownership,
|
|
35
|
+
requirement, and `remote-acknowledged` boundary.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ObservationBoundary } from "@alvin0/ai-agent-sdk-core";
|
|
2
|
+
import { ExportAck, FlushResult, Observability, ObservationBatch, ObservationExporter, ObservationExporterPlugin } from "@alvin0/ai-agent-sdk-core/observability";
|
|
3
|
+
//#region src/fetch-exporter.d.ts
|
|
4
|
+
interface FetchObservationExporterOptions {
|
|
5
|
+
readonly id?: string;
|
|
6
|
+
readonly endpoint: string | URL;
|
|
7
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
8
|
+
readonly fetch?: typeof fetch;
|
|
9
|
+
/** Permit cleartext only for localhost/loopback test endpoints. */
|
|
10
|
+
readonly allowInsecureHttp?: boolean;
|
|
11
|
+
readonly requestTimeoutMs?: number;
|
|
12
|
+
readonly maxAttempts?: number;
|
|
13
|
+
readonly baseDelayMs?: number;
|
|
14
|
+
readonly maxDelayMs?: number;
|
|
15
|
+
readonly maxBatchEvents?: number;
|
|
16
|
+
readonly maxBatchBytes?: number;
|
|
17
|
+
readonly maxAckBytes?: number;
|
|
18
|
+
readonly maxAckChunks?: number;
|
|
19
|
+
/** Deterministic injection for tests; defaults to `Math.random`. */
|
|
20
|
+
readonly random?: () => number;
|
|
21
|
+
/** Timer injection for tests; defaults to an abortable Web timer. */
|
|
22
|
+
readonly delay?: (milliseconds: number, signal: AbortSignal) => Promise<void>;
|
|
23
|
+
/** Clock injection used only for HTTP-date `Retry-After`. */
|
|
24
|
+
readonly now?: () => number;
|
|
25
|
+
}
|
|
26
|
+
/** A remote-acknowledged exporter using only Fetch and Web streams. */
|
|
27
|
+
declare class FetchObservationExporter implements ObservationExporter {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly supportedBoundaries: readonly ObservationBoundary[];
|
|
30
|
+
private readonly options;
|
|
31
|
+
constructor(options: FetchObservationExporterOptions);
|
|
32
|
+
export(batch: ObservationBatch, signal: AbortSignal): Promise<ExportAck>;
|
|
33
|
+
}
|
|
34
|
+
/** Recommended inert runtime exporter; transport begins only when core exports a batch. */
|
|
35
|
+
declare function fetchObservationExporter(options: FetchObservationExporterOptions): ObservationExporterPlugin;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/wait-until.d.ts
|
|
38
|
+
type WaitUntil = (pending: Promise<unknown>) => void;
|
|
39
|
+
/** Start one flush and explicitly attach it to an Edge host's lifetime. */
|
|
40
|
+
declare function flushObservabilityWithWaitUntil(observation: Pick<Observability, 'flush'>, waitUntil: WaitUntil, signal?: AbortSignal): Promise<FlushResult>;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { FetchObservationExporter, type FetchObservationExporterOptions, type WaitUntil, fetchObservationExporter, flushObservabilityWithWaitUntil };
|
|
43
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/fetch-exporter.ts","../src/wait-until.ts"],"mappings":";;;UA4BiB;WACN;WACA,mBAAmB;WACnB,UAAU,SAAS;WACnB,eAAe;;WAEf;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;WAEA;;WAEA,SAAS,sBAAsB,QAAQ,gBAAgB;;WAEvD;;;cAsME,oCAAoC;WACtC;WACA,8BAA8B;mBACtB;EAEjB,YAAY,SAAS;EAWf,OAAO,OAAO,kBAAkB,QAAQ,cAAc,QAAQ;;;iBAsFtD,yBACd,SAAS,kCACR;;;KC5VS,aAAa,SAAS;;iBAGlB,gCACd,aAAa,KAAK,yBAClB,WAAW,WACX,SAAS,cACR,QAAQ"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { waitForSettlement } from "@alvin0/ai-agent-sdk-core";
|
|
2
|
+
import { defineObservationExporter } from "@alvin0/ai-agent-sdk-core/observability";
|
|
3
|
+
|
|
4
|
+
//#region src/fetch-exporter.ts
|
|
5
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
|
|
6
|
+
const DEFAULT_MAX_ATTEMPTS = 8;
|
|
7
|
+
const DEFAULT_BASE_DELAY_MS = 250;
|
|
8
|
+
const DEFAULT_MAX_DELAY_MS = 3e4;
|
|
9
|
+
const MAX_RETRY_AFTER_MS = 6e4;
|
|
10
|
+
const DEFAULT_MAX_BATCH_EVENTS = 256;
|
|
11
|
+
const DEFAULT_MAX_BATCH_BYTES = 524288;
|
|
12
|
+
const DEFAULT_MAX_ACK_BYTES = 65536;
|
|
13
|
+
const DEFAULT_MAX_ACK_CHUNKS = 1e3;
|
|
14
|
+
const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
|
|
15
|
+
408,
|
|
16
|
+
425,
|
|
17
|
+
429
|
|
18
|
+
]);
|
|
19
|
+
const FORBIDDEN_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
20
|
+
"content-length",
|
|
21
|
+
"content-type",
|
|
22
|
+
"host",
|
|
23
|
+
"idempotency-key"
|
|
24
|
+
]);
|
|
25
|
+
var ObservationProtocolError = class extends Error {
|
|
26
|
+
name = "ObservationProtocolError";
|
|
27
|
+
};
|
|
28
|
+
function positiveSafeInteger(value, field) {
|
|
29
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`${field} must be a positive safe integer`);
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function nonNegativeFinite(value, field) {
|
|
33
|
+
if (!Number.isFinite(value) || value < 0) throw new RangeError(`${field} must be a non-negative finite number`);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function endpointOf(value, allowInsecureHttp) {
|
|
37
|
+
const endpoint = new URL(value);
|
|
38
|
+
if (endpoint.username.length > 0 || endpoint.password.length > 0) throw new TypeError("observation endpoint must not contain credentials");
|
|
39
|
+
const local = endpoint.hostname === "localhost" || endpoint.hostname === "127.0.0.1" || endpoint.hostname === "[::1]" || endpoint.hostname === "::1";
|
|
40
|
+
if (endpoint.protocol !== "https:" && !(allowInsecureHttp && endpoint.protocol === "http:" && local)) throw new TypeError("observation endpoint must use https");
|
|
41
|
+
return endpoint;
|
|
42
|
+
}
|
|
43
|
+
function headersOf(headers) {
|
|
44
|
+
const output = Object.create(null);
|
|
45
|
+
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
46
|
+
const normalized = name.toLowerCase();
|
|
47
|
+
if (FORBIDDEN_HEADER_NAMES.has(normalized)) throw new TypeError(`observation header ${name} is managed by the exporter`);
|
|
48
|
+
if (typeof value !== "string" || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name) || /[\r\n]/.test(value)) throw new TypeError(`observation header ${name} is invalid`);
|
|
49
|
+
output[normalized] = value;
|
|
50
|
+
}
|
|
51
|
+
return Object.freeze(output);
|
|
52
|
+
}
|
|
53
|
+
function resolveOptions(options) {
|
|
54
|
+
if (typeof options?.fetch !== "function" && typeof globalThis.fetch !== "function") throw new TypeError("Fetch observation exporter requires fetch");
|
|
55
|
+
if (options.random !== void 0 && typeof options.random !== "function") throw new TypeError("random must be a function");
|
|
56
|
+
if (options.delay !== void 0 && typeof options.delay !== "function") throw new TypeError("delay must be a function");
|
|
57
|
+
if (options.now !== void 0 && typeof options.now !== "function") throw new TypeError("now must be a function");
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
endpoint: endpointOf(options.endpoint, options.allowInsecureHttp ?? false),
|
|
60
|
+
headers: headersOf(options.headers),
|
|
61
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
62
|
+
requestTimeoutMs: positiveSafeInteger(options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS, "requestTimeoutMs"),
|
|
63
|
+
maxAttempts: positiveSafeInteger(options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS, "maxAttempts"),
|
|
64
|
+
baseDelayMs: nonNegativeFinite(options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS, "baseDelayMs"),
|
|
65
|
+
maxDelayMs: nonNegativeFinite(options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS, "maxDelayMs"),
|
|
66
|
+
maxBatchEvents: positiveSafeInteger(options.maxBatchEvents ?? DEFAULT_MAX_BATCH_EVENTS, "maxBatchEvents"),
|
|
67
|
+
maxBatchBytes: positiveSafeInteger(options.maxBatchBytes ?? DEFAULT_MAX_BATCH_BYTES, "maxBatchBytes"),
|
|
68
|
+
maxAckBytes: positiveSafeInteger(options.maxAckBytes ?? DEFAULT_MAX_ACK_BYTES, "maxAckBytes"),
|
|
69
|
+
maxAckChunks: positiveSafeInteger(options.maxAckChunks ?? DEFAULT_MAX_ACK_CHUNKS, "maxAckChunks"),
|
|
70
|
+
random: options.random ?? Math.random,
|
|
71
|
+
delay: options.delay ?? abortableDelay,
|
|
72
|
+
now: options.now ?? Date.now
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function retryableStatus(status) {
|
|
76
|
+
return RETRYABLE_STATUSES.has(status) || status >= 500 && status <= 599;
|
|
77
|
+
}
|
|
78
|
+
function retryAfterMs(value, now) {
|
|
79
|
+
if (value === null) return void 0;
|
|
80
|
+
const trimmed = value.trim();
|
|
81
|
+
if (/^\d+(?:\.\d+)?$/.test(trimmed)) return Math.min(MAX_RETRY_AFTER_MS, Number(trimmed) * 1e3);
|
|
82
|
+
const at = Date.parse(trimmed);
|
|
83
|
+
if (!Number.isFinite(at)) return void 0;
|
|
84
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, at - now));
|
|
85
|
+
}
|
|
86
|
+
function backoffMs(attempt, options) {
|
|
87
|
+
const cap = Math.min(options.maxDelayMs, options.baseDelayMs * 2 ** Math.max(0, attempt - 1));
|
|
88
|
+
const random = options.random();
|
|
89
|
+
return Math.floor(cap * (Number.isFinite(random) ? Math.min(1, Math.max(0, random)) : 0));
|
|
90
|
+
}
|
|
91
|
+
function combinedSignal(external, timeoutMs) {
|
|
92
|
+
const controller = new AbortController();
|
|
93
|
+
const timeout = setTimeout(() => controller.abort(new DOMException("observation request timed out", "TimeoutError")), timeoutMs);
|
|
94
|
+
const abort = () => controller.abort(external.reason ?? new DOMException("observation export aborted", "AbortError"));
|
|
95
|
+
if (external.aborted) abort();
|
|
96
|
+
else external.addEventListener("abort", abort, { once: true });
|
|
97
|
+
return {
|
|
98
|
+
signal: controller.signal,
|
|
99
|
+
clear() {
|
|
100
|
+
clearTimeout(timeout);
|
|
101
|
+
external.removeEventListener("abort", abort);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function raceAbort(pending, signal) {
|
|
106
|
+
if (signal.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("observation export aborted"));
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const abort = () => {
|
|
109
|
+
cleanup();
|
|
110
|
+
reject(signal.reason ?? /* @__PURE__ */ new Error("observation export aborted"));
|
|
111
|
+
};
|
|
112
|
+
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
113
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
114
|
+
pending.then((value) => {
|
|
115
|
+
cleanup();
|
|
116
|
+
resolve(value);
|
|
117
|
+
}, (error) => {
|
|
118
|
+
cleanup();
|
|
119
|
+
reject(error);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async function abortableDelay(milliseconds, signal) {
|
|
124
|
+
if (milliseconds <= 0) return;
|
|
125
|
+
await new Promise((resolve, reject) => {
|
|
126
|
+
const timer = setTimeout(() => {
|
|
127
|
+
cleanup();
|
|
128
|
+
resolve();
|
|
129
|
+
}, milliseconds);
|
|
130
|
+
const abort = () => {
|
|
131
|
+
cleanup();
|
|
132
|
+
reject(signal.reason ?? /* @__PURE__ */ new Error("observation export aborted"));
|
|
133
|
+
};
|
|
134
|
+
const cleanup = () => {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
signal.removeEventListener("abort", abort);
|
|
137
|
+
};
|
|
138
|
+
if (signal.aborted) abort();
|
|
139
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
async function cancelBody(response) {
|
|
143
|
+
if (response.body === null) return;
|
|
144
|
+
await waitForSettlement(response.body.cancel().catch(() => void 0), 1e3);
|
|
145
|
+
}
|
|
146
|
+
async function boundedText(response, options, signal) {
|
|
147
|
+
const declared = Number(response.headers.get("content-length"));
|
|
148
|
+
if (Number.isFinite(declared) && declared > options.maxAckBytes) {
|
|
149
|
+
await cancelBody(response);
|
|
150
|
+
throw new RangeError("observation acknowledgment exceeds its byte limit");
|
|
151
|
+
}
|
|
152
|
+
if (response.body === null) return "";
|
|
153
|
+
const reader = response.body.getReader();
|
|
154
|
+
const decoder = new TextDecoder();
|
|
155
|
+
let output = "";
|
|
156
|
+
let bytes = 0;
|
|
157
|
+
let chunks = 0;
|
|
158
|
+
try {
|
|
159
|
+
while (true) {
|
|
160
|
+
const next = await raceAbort(reader.read(), signal);
|
|
161
|
+
if (next.done) return output + decoder.decode();
|
|
162
|
+
if (next.value === void 0) continue;
|
|
163
|
+
bytes += next.value.byteLength;
|
|
164
|
+
chunks++;
|
|
165
|
+
if (bytes > options.maxAckBytes || chunks > options.maxAckChunks) {
|
|
166
|
+
await waitForSettlement(reader.cancel().catch(() => void 0), 1e3);
|
|
167
|
+
throw new RangeError("observation acknowledgment exceeds its resource limit");
|
|
168
|
+
}
|
|
169
|
+
output += decoder.decode(next.value, { stream: true });
|
|
170
|
+
}
|
|
171
|
+
} finally {
|
|
172
|
+
reader.releaseLock();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function validateNoRedirectResponse(response, endpoint) {
|
|
176
|
+
const redirectStatus = response.status >= 300 && response.status < 400;
|
|
177
|
+
const opaqueRedirect = response.type === "opaqueredirect";
|
|
178
|
+
const responseUrlChanged = response.url.length > 0 && response.url !== endpoint.href;
|
|
179
|
+
if (response.redirected || redirectStatus || opaqueRedirect || responseUrlChanged) {
|
|
180
|
+
await cancelBody(response);
|
|
181
|
+
throw new ObservationProtocolError("observation exporter rejected a redirect before following it");
|
|
182
|
+
}
|
|
183
|
+
if (response.url.length > 0) {
|
|
184
|
+
let responseOrigin;
|
|
185
|
+
try {
|
|
186
|
+
responseOrigin = new URL(response.url).origin;
|
|
187
|
+
} catch {
|
|
188
|
+
throw new ObservationProtocolError("observation response URL is invalid");
|
|
189
|
+
}
|
|
190
|
+
if (responseOrigin !== endpoint.origin) throw new ObservationProtocolError("observation response origin does not match its endpoint");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** A remote-acknowledged exporter using only Fetch and Web streams. */
|
|
194
|
+
var FetchObservationExporter = class {
|
|
195
|
+
id;
|
|
196
|
+
supportedBoundaries = Object.freeze(["remote-acknowledged"]);
|
|
197
|
+
options;
|
|
198
|
+
constructor(options) {
|
|
199
|
+
if (typeof options !== "object" || options === null) throw new TypeError("Fetch observation exporter options are required");
|
|
200
|
+
this.id = options.id ?? "fetch";
|
|
201
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,63})$/.test(this.id)) throw new TypeError("Fetch observation exporter id must be a safe 1-64 character identifier");
|
|
202
|
+
this.options = resolveOptions(options);
|
|
203
|
+
}
|
|
204
|
+
async export(batch, signal) {
|
|
205
|
+
const result = await sendBatch(this.options, batch.batchId, batch.events.length, batch, signal);
|
|
206
|
+
return Object.freeze({
|
|
207
|
+
batchId: batch.batchId,
|
|
208
|
+
...result
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
async function sendBatch(options, batchId, itemCount, batch, signal) {
|
|
213
|
+
if (itemCount > options.maxBatchEvents) return Object.freeze({
|
|
214
|
+
accepted: false,
|
|
215
|
+
retryable: false
|
|
216
|
+
});
|
|
217
|
+
const body = JSON.stringify(batch);
|
|
218
|
+
if (new TextEncoder().encode(body).byteLength > options.maxBatchBytes) return Object.freeze({
|
|
219
|
+
accepted: false,
|
|
220
|
+
retryable: false
|
|
221
|
+
});
|
|
222
|
+
const dispatch = options.fetch;
|
|
223
|
+
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
|
|
224
|
+
if (signal.aborted) throw signal.reason ?? /* @__PURE__ */ new Error("observation export aborted");
|
|
225
|
+
const request = combinedSignal(signal, options.requestTimeoutMs);
|
|
226
|
+
let retryDelay;
|
|
227
|
+
try {
|
|
228
|
+
const response = await raceAbort(Promise.resolve(dispatch(options.endpoint, {
|
|
229
|
+
method: "POST",
|
|
230
|
+
headers: {
|
|
231
|
+
...options.headers,
|
|
232
|
+
"content-type": "application/json",
|
|
233
|
+
"idempotency-key": batchId
|
|
234
|
+
},
|
|
235
|
+
body,
|
|
236
|
+
redirect: "manual",
|
|
237
|
+
signal: request.signal
|
|
238
|
+
})), request.signal);
|
|
239
|
+
await validateNoRedirectResponse(response, options.endpoint);
|
|
240
|
+
if (response.status === 204) {
|
|
241
|
+
await cancelBody(response);
|
|
242
|
+
return Object.freeze({
|
|
243
|
+
accepted: true,
|
|
244
|
+
retryable: false
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (response.status >= 200 && response.status < 300) {
|
|
248
|
+
const raw = await boundedText(response, options, request.signal);
|
|
249
|
+
let parsed;
|
|
250
|
+
try {
|
|
251
|
+
parsed = JSON.parse(raw);
|
|
252
|
+
} catch {
|
|
253
|
+
parsed = void 0;
|
|
254
|
+
}
|
|
255
|
+
const acceptedBatchId = typeof parsed === "object" && parsed !== null ? Reflect.get(parsed, "acceptedBatchId") : void 0;
|
|
256
|
+
return Object.freeze({
|
|
257
|
+
accepted: acceptedBatchId === batchId,
|
|
258
|
+
retryable: false
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const retryable = retryableStatus(response.status);
|
|
262
|
+
const retryAfter = retryAfterMs(response.headers.get("retry-after"), options.now());
|
|
263
|
+
await cancelBody(response);
|
|
264
|
+
if (!retryable || attempt === options.maxAttempts) return Object.freeze({
|
|
265
|
+
accepted: false,
|
|
266
|
+
retryable
|
|
267
|
+
});
|
|
268
|
+
retryDelay = retryAfter ?? backoffMs(attempt, options);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (signal.aborted) throw signal.reason ?? error;
|
|
271
|
+
if (error instanceof ObservationProtocolError || error instanceof RangeError) return Object.freeze({
|
|
272
|
+
accepted: false,
|
|
273
|
+
retryable: false
|
|
274
|
+
});
|
|
275
|
+
if (attempt === options.maxAttempts) return Object.freeze({
|
|
276
|
+
accepted: false,
|
|
277
|
+
retryable: true
|
|
278
|
+
});
|
|
279
|
+
retryDelay = backoffMs(attempt, options);
|
|
280
|
+
} finally {
|
|
281
|
+
request.clear();
|
|
282
|
+
}
|
|
283
|
+
await raceAbort(Promise.resolve(options.delay(retryDelay ?? 0, signal)), signal);
|
|
284
|
+
}
|
|
285
|
+
return Object.freeze({
|
|
286
|
+
accepted: false,
|
|
287
|
+
retryable: true
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
/** Recommended inert runtime exporter; transport begins only when core exports a batch. */
|
|
291
|
+
function fetchObservationExporter(options) {
|
|
292
|
+
if (typeof options !== "object" || options === null) throw new TypeError("Fetch observation exporter options are required");
|
|
293
|
+
const id = options.id ?? "fetch";
|
|
294
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,63})$/.test(id)) throw new TypeError("Fetch observation exporter id must be a safe 1-64 character identifier");
|
|
295
|
+
const captured = resolveOptions(options);
|
|
296
|
+
return defineObservationExporter({
|
|
297
|
+
id,
|
|
298
|
+
supportedBoundaries: ["remote-acknowledged"],
|
|
299
|
+
async export(batch, signal) {
|
|
300
|
+
const result = await sendBatch(captured, batch.id, batch.events.length + batch.runRecords.length, batch, signal);
|
|
301
|
+
return Object.freeze({
|
|
302
|
+
batchId: batch.id,
|
|
303
|
+
acceptedEventIds: result.accepted ? batch.events.map((event) => event.eventId) : [],
|
|
304
|
+
acceptedRunIds: result.accepted ? batch.runRecords.map((record) => record.runId) : []
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/wait-until.ts
|
|
312
|
+
/** Start one flush and explicitly attach it to an Edge host's lifetime. */
|
|
313
|
+
function flushObservabilityWithWaitUntil(observation, waitUntil, signal) {
|
|
314
|
+
if (typeof waitUntil !== "function") throw new TypeError("waitUntil must be a function");
|
|
315
|
+
const pending = observation.flush(signal);
|
|
316
|
+
waitUntil(pending);
|
|
317
|
+
return pending;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
//#endregion
|
|
321
|
+
export { FetchObservationExporter, fetchObservationExporter, flushObservabilityWithWaitUntil };
|
|
322
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/fetch-exporter.ts","../src/wait-until.ts"],"sourcesContent":["import { waitForSettlement, type ObservationBoundary } from '@alvin0/ai-agent-sdk-core'\nimport type {\n ExportAck,\n ObservationBatch,\n ObservationExporter,\n} from '@alvin0/ai-agent-sdk-core/observability'\nimport {\n defineObservationExporter,\n type ObservationDeliveryBatch,\n type ObservationExporterPlugin,\n} from '@alvin0/ai-agent-sdk-core/observability'\n\nconst DEFAULT_REQUEST_TIMEOUT_MS = 10_000\nconst DEFAULT_MAX_ATTEMPTS = 8\nconst DEFAULT_BASE_DELAY_MS = 250\nconst DEFAULT_MAX_DELAY_MS = 30_000\nconst MAX_RETRY_AFTER_MS = 60_000\nconst DEFAULT_MAX_BATCH_EVENTS = 256\nconst DEFAULT_MAX_BATCH_BYTES = 512 * 1024\nconst DEFAULT_MAX_ACK_BYTES = 64 * 1024\nconst DEFAULT_MAX_ACK_CHUNKS = 1_000\nconst RETRYABLE_STATUSES = new Set([408, 425, 429])\nconst FORBIDDEN_HEADER_NAMES = new Set(['content-length', 'content-type', 'host', 'idempotency-key'])\n\nclass ObservationProtocolError extends Error {\n override readonly name = 'ObservationProtocolError'\n}\n\nexport interface FetchObservationExporterOptions {\n readonly id?: string\n readonly endpoint: string | URL\n readonly headers?: Readonly<Record<string, string>>\n readonly fetch?: typeof fetch\n /** Permit cleartext only for localhost/loopback test endpoints. */\n readonly allowInsecureHttp?: boolean\n readonly requestTimeoutMs?: number\n readonly maxAttempts?: number\n readonly baseDelayMs?: number\n readonly maxDelayMs?: number\n readonly maxBatchEvents?: number\n readonly maxBatchBytes?: number\n readonly maxAckBytes?: number\n readonly maxAckChunks?: number\n /** Deterministic injection for tests; defaults to `Math.random`. */\n readonly random?: () => number\n /** Timer injection for tests; defaults to an abortable Web timer. */\n readonly delay?: (milliseconds: number, signal: AbortSignal) => Promise<void>\n /** Clock injection used only for HTTP-date `Retry-After`. */\n readonly now?: () => number\n}\n\ninterface ResolvedOptions {\n readonly endpoint: URL\n readonly headers: Readonly<Record<string, string>>\n readonly fetch: typeof fetch\n readonly requestTimeoutMs: number\n readonly maxAttempts: number\n readonly baseDelayMs: number\n readonly maxDelayMs: number\n readonly maxBatchEvents: number\n readonly maxBatchBytes: number\n readonly maxAckBytes: number\n readonly maxAckChunks: number\n readonly random: () => number\n readonly delay: (milliseconds: number, signal: AbortSignal) => Promise<void>\n readonly now: () => number\n}\n\nfunction positiveSafeInteger(value: number, field: string): number {\n if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`${field} must be a positive safe integer`)\n return value\n}\n\nfunction nonNegativeFinite(value: number, field: string): number {\n if (!Number.isFinite(value) || value < 0) throw new RangeError(`${field} must be a non-negative finite number`)\n return value\n}\n\nfunction endpointOf(value: string | URL, allowInsecureHttp: boolean): URL {\n const endpoint = new URL(value)\n if (endpoint.username.length > 0 || endpoint.password.length > 0) {\n throw new TypeError('observation endpoint must not contain credentials')\n }\n const local = endpoint.hostname === 'localhost' || endpoint.hostname === '127.0.0.1'\n || endpoint.hostname === '[::1]' || endpoint.hostname === '::1'\n if (endpoint.protocol !== 'https:' && !(allowInsecureHttp && endpoint.protocol === 'http:' && local)) {\n throw new TypeError('observation endpoint must use https')\n }\n return endpoint\n}\n\nfunction headersOf(headers: Readonly<Record<string, string>> | undefined): Readonly<Record<string, string>> {\n const output = Object.create(null) as Record<string, string>\n for (const [name, value] of Object.entries(headers ?? {})) {\n const normalized = name.toLowerCase()\n if (FORBIDDEN_HEADER_NAMES.has(normalized)) throw new TypeError(`observation header ${name} is managed by the exporter`)\n if (typeof value !== 'string' || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name) || /[\\r\\n]/.test(value)) {\n throw new TypeError(`observation header ${name} is invalid`)\n }\n output[normalized] = value\n }\n return Object.freeze(output)\n}\n\nfunction resolveOptions(options: FetchObservationExporterOptions): ResolvedOptions {\n if (typeof options?.fetch !== 'function' && typeof globalThis.fetch !== 'function') {\n throw new TypeError('Fetch observation exporter requires fetch')\n }\n if (options.random !== undefined && typeof options.random !== 'function') {\n throw new TypeError('random must be a function')\n }\n if (options.delay !== undefined && typeof options.delay !== 'function') {\n throw new TypeError('delay must be a function')\n }\n if (options.now !== undefined && typeof options.now !== 'function') {\n throw new TypeError('now must be a function')\n }\n return Object.freeze({\n endpoint: endpointOf(options.endpoint, options.allowInsecureHttp ?? false),\n headers: headersOf(options.headers),\n fetch: options.fetch ?? globalThis.fetch,\n requestTimeoutMs: positiveSafeInteger(options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS, 'requestTimeoutMs'),\n maxAttempts: positiveSafeInteger(options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS, 'maxAttempts'),\n baseDelayMs: nonNegativeFinite(options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS, 'baseDelayMs'),\n maxDelayMs: nonNegativeFinite(options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS, 'maxDelayMs'),\n maxBatchEvents: positiveSafeInteger(options.maxBatchEvents ?? DEFAULT_MAX_BATCH_EVENTS, 'maxBatchEvents'),\n maxBatchBytes: positiveSafeInteger(options.maxBatchBytes ?? DEFAULT_MAX_BATCH_BYTES, 'maxBatchBytes'),\n maxAckBytes: positiveSafeInteger(options.maxAckBytes ?? DEFAULT_MAX_ACK_BYTES, 'maxAckBytes'),\n maxAckChunks: positiveSafeInteger(options.maxAckChunks ?? DEFAULT_MAX_ACK_CHUNKS, 'maxAckChunks'),\n random: options.random ?? Math.random,\n delay: options.delay ?? abortableDelay,\n now: options.now ?? Date.now,\n })\n}\n\nfunction retryableStatus(status: number): boolean {\n return RETRYABLE_STATUSES.has(status) || (status >= 500 && status <= 599)\n}\n\nfunction retryAfterMs(value: string | null, now: number): number | undefined {\n if (value === null) return undefined\n const trimmed = value.trim()\n if (/^\\d+(?:\\.\\d+)?$/.test(trimmed)) return Math.min(MAX_RETRY_AFTER_MS, Number(trimmed) * 1_000)\n const at = Date.parse(trimmed)\n if (!Number.isFinite(at)) return undefined\n return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, at - now))\n}\n\nfunction backoffMs(attempt: number, options: ResolvedOptions): number {\n const cap = Math.min(options.maxDelayMs, options.baseDelayMs * (2 ** Math.max(0, attempt - 1)))\n const random = options.random()\n const unit = Number.isFinite(random) ? Math.min(1, Math.max(0, random)) : 0\n return Math.floor(cap * unit)\n}\n\nfunction combinedSignal(external: AbortSignal, timeoutMs: number): { signal: AbortSignal; clear(): void } {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(new DOMException('observation request timed out', 'TimeoutError')), timeoutMs)\n const abort = () => controller.abort(external.reason ?? new DOMException('observation export aborted', 'AbortError'))\n if (external.aborted) abort()\n else external.addEventListener('abort', abort, { once: true })\n return {\n signal: controller.signal,\n clear() { clearTimeout(timeout); external.removeEventListener('abort', abort) },\n }\n}\n\nfunction raceAbort<T>(pending: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(signal.reason ?? new Error('observation export aborted'))\n return new Promise<T>((resolve, reject) => {\n const abort = () => { cleanup(); reject(signal.reason ?? new Error('observation export aborted')) }\n const cleanup = () => signal.removeEventListener('abort', abort)\n signal.addEventListener('abort', abort, { once: true })\n void pending.then(\n value => { cleanup(); resolve(value) },\n error => { cleanup(); reject(error) },\n )\n })\n}\n\nasync function abortableDelay(milliseconds: number, signal: AbortSignal): Promise<void> {\n if (milliseconds <= 0) return\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => { cleanup(); resolve() }, milliseconds)\n const abort = () => { cleanup(); reject(signal.reason ?? new Error('observation export aborted')) }\n const cleanup = () => { clearTimeout(timer); signal.removeEventListener('abort', abort) }\n if (signal.aborted) abort()\n else signal.addEventListener('abort', abort, { once: true })\n })\n}\n\nasync function cancelBody(response: Response): Promise<void> {\n if (response.body === null) return\n await waitForSettlement(response.body.cancel().catch(() => undefined), 1_000)\n}\n\nasync function boundedText(response: Response, options: ResolvedOptions, signal: AbortSignal): Promise<string> {\n const declared = Number(response.headers.get('content-length'))\n if (Number.isFinite(declared) && declared > options.maxAckBytes) {\n await cancelBody(response)\n throw new RangeError('observation acknowledgment exceeds its byte limit')\n }\n if (response.body === null) return ''\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let output = ''\n let bytes = 0\n let chunks = 0\n try {\n while (true) {\n const next = await raceAbort(reader.read(), signal)\n if (next.done) return output + decoder.decode()\n if (next.value === undefined) continue\n bytes += next.value.byteLength\n chunks++\n if (bytes > options.maxAckBytes || chunks > options.maxAckChunks) {\n await waitForSettlement(reader.cancel().catch(() => undefined), 1_000)\n throw new RangeError('observation acknowledgment exceeds its resource limit')\n }\n output += decoder.decode(next.value, { stream: true })\n }\n } finally {\n reader.releaseLock()\n }\n}\n\nasync function validateNoRedirectResponse(response: Response, endpoint: URL): Promise<void> {\n const redirectStatus = response.status >= 300 && response.status < 400\n const opaqueRedirect = response.type === 'opaqueredirect'\n const responseUrlChanged = response.url.length > 0 && response.url !== endpoint.href\n if (response.redirected || redirectStatus || opaqueRedirect || responseUrlChanged) {\n await cancelBody(response)\n throw new ObservationProtocolError('observation exporter rejected a redirect before following it')\n }\n if (response.url.length > 0) {\n let responseOrigin: string\n try { responseOrigin = new URL(response.url).origin } catch {\n throw new ObservationProtocolError('observation response URL is invalid')\n }\n if (responseOrigin !== endpoint.origin) {\n throw new ObservationProtocolError('observation response origin does not match its endpoint')\n }\n }\n}\n\n/** A remote-acknowledged exporter using only Fetch and Web streams. */\nexport class FetchObservationExporter implements ObservationExporter {\n readonly id: string\n readonly supportedBoundaries: readonly ObservationBoundary[] = Object.freeze(['remote-acknowledged'])\n private readonly options: ResolvedOptions\n\n constructor(options: FetchObservationExporterOptions) {\n if (typeof options !== 'object' || options === null) {\n throw new TypeError('Fetch observation exporter options are required')\n }\n this.id = options.id ?? 'fetch'\n if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,63})$/.test(this.id)) {\n throw new TypeError('Fetch observation exporter id must be a safe 1-64 character identifier')\n }\n this.options = resolveOptions(options)\n }\n\n async export(batch: ObservationBatch, signal: AbortSignal): Promise<ExportAck> {\n const result = await sendBatch(this.options, batch.batchId, batch.events.length, batch, signal)\n return Object.freeze({ batchId: batch.batchId, ...result })\n }\n}\n\ninterface BatchSendResult {\n readonly accepted: boolean\n readonly retryable: boolean\n}\n\nasync function sendBatch(\n options: ResolvedOptions,\n batchId: string,\n itemCount: number,\n batch: unknown,\n signal: AbortSignal,\n): Promise<BatchSendResult> {\n if (itemCount > options.maxBatchEvents) return Object.freeze({ accepted: false, retryable: false })\n const body = JSON.stringify(batch)\n if (new TextEncoder().encode(body).byteLength > options.maxBatchBytes) {\n return Object.freeze({ accepted: false, retryable: false })\n }\n // Web IDL fetch rejects a property-call receiver such as `options.fetch(...)`\n // in browsers. Capture it as a standalone callable so native Window/Worker\n // fetch and injected implementations share the same invocation contract.\n const dispatch = options.fetch\n\n for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {\n if (signal.aborted) throw signal.reason ?? new Error('observation export aborted')\n const request = combinedSignal(signal, options.requestTimeoutMs)\n let retryDelay: number | undefined\n try {\n const response = await raceAbort(Promise.resolve(dispatch(options.endpoint, {\n method: 'POST',\n headers: {\n ...options.headers,\n 'content-type': 'application/json',\n 'idempotency-key': batchId,\n },\n body,\n redirect: 'manual',\n signal: request.signal,\n })), request.signal)\n await validateNoRedirectResponse(response, options.endpoint)\n if (response.status === 204) {\n await cancelBody(response)\n return Object.freeze({ accepted: true, retryable: false })\n }\n if (response.status >= 200 && response.status < 300) {\n const raw = await boundedText(response, options, request.signal)\n let parsed: unknown\n try { parsed = JSON.parse(raw) } catch { parsed = undefined }\n const acceptedBatchId = typeof parsed === 'object' && parsed !== null\n ? Reflect.get(parsed, 'acceptedBatchId')\n : undefined\n return Object.freeze({\n accepted: acceptedBatchId === batchId,\n retryable: false,\n })\n }\n const retryable = retryableStatus(response.status)\n const retryAfter = retryAfterMs(response.headers.get('retry-after'), options.now())\n await cancelBody(response)\n if (!retryable || attempt === options.maxAttempts) {\n return Object.freeze({ accepted: false, retryable })\n }\n retryDelay = retryAfter ?? backoffMs(attempt, options)\n } catch (error) {\n if (signal.aborted) throw signal.reason ?? error\n if (error instanceof ObservationProtocolError || error instanceof RangeError) {\n return Object.freeze({ accepted: false, retryable: false })\n }\n if (attempt === options.maxAttempts) {\n return Object.freeze({ accepted: false, retryable: true })\n }\n retryDelay = backoffMs(attempt, options)\n } finally {\n request.clear()\n }\n await raceAbort(Promise.resolve(options.delay(retryDelay ?? 0, signal)), signal)\n }\n return Object.freeze({ accepted: false, retryable: true })\n}\n\n/** Recommended inert runtime exporter; transport begins only when core exports a batch. */\nexport function fetchObservationExporter(\n options: FetchObservationExporterOptions,\n): ObservationExporterPlugin {\n if (typeof options !== 'object' || options === null) {\n throw new TypeError('Fetch observation exporter options are required')\n }\n const id = options.id ?? 'fetch'\n if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,63})$/.test(id)) {\n throw new TypeError('Fetch observation exporter id must be a safe 1-64 character identifier')\n }\n const captured = resolveOptions(options)\n return defineObservationExporter({\n id,\n supportedBoundaries: ['remote-acknowledged'],\n async export(batch: ObservationDeliveryBatch, signal: AbortSignal) {\n const result = await sendBatch(\n captured,\n batch.id,\n batch.events.length + batch.runRecords.length,\n batch,\n signal,\n )\n return Object.freeze({\n batchId: batch.id,\n acceptedEventIds: result.accepted ? batch.events.map(event => event.eventId) : [],\n acceptedRunIds: result.accepted ? batch.runRecords.map(record => record.runId) : [],\n })\n },\n })\n}\n","import type { FlushResult, Observability } from '@alvin0/ai-agent-sdk-core/observability'\n\nexport type WaitUntil = (pending: Promise<unknown>) => void\n\n/** Start one flush and explicitly attach it to an Edge host's lifetime. */\nexport function flushObservabilityWithWaitUntil(\n observation: Pick<Observability, 'flush'>,\n waitUntil: WaitUntil,\n signal?: AbortSignal,\n): Promise<FlushResult> {\n if (typeof waitUntil !== 'function') throw new TypeError('waitUntil must be a function')\n const pending = observation.flush(signal)\n waitUntil(pending)\n return pending\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alvin0/ai-agent-sdk-observability-fetch",
|
|
3
|
+
"author": {
|
|
4
|
+
"name": "alvin0 - chaulamdinhai",
|
|
5
|
+
"email": "chaulamdinhai@gmail.com"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"description": "Acknowledged HTTPS observation batch exporter for Edge and Web runtimes",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/alvin0/ai-agent-sdk.git",
|
|
13
|
+
"directory": "packages/observability-fetch"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/alvin0/ai-agent-sdk/tree/main/packages/observability-fetch#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/alvin0/ai-agent-sdk/issues"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"provenance": true
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@alvin0/ai-agent-sdk-core": "^0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@alvin0/ai-agent-sdk-core": "^0.1.0",
|
|
45
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
46
|
+
"playwright": "1.62.1",
|
|
47
|
+
"publint": "0.3.24",
|
|
48
|
+
"tsdown": "0.22.14",
|
|
49
|
+
"typescript": "7.0.2",
|
|
50
|
+
"vitest": "4.1.11",
|
|
51
|
+
"wrangler": "4.127.1"
|
|
52
|
+
},
|
|
53
|
+
"aiAgentSdk": {
|
|
54
|
+
"runtime": "universal",
|
|
55
|
+
"coreApi": 1,
|
|
56
|
+
"roles": [
|
|
57
|
+
"observation-exporter"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsdown",
|
|
62
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true});require('node:fs').rmSync('artifacts',{recursive:true,force:true})\"",
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
64
|
+
"test": "vitest run --config vitest.config.ts",
|
|
65
|
+
"pack": "pnpm pack --pack-destination artifacts",
|
|
66
|
+
"test:pack": "node scripts/test-packed.mts",
|
|
67
|
+
"check:publint": "publint",
|
|
68
|
+
"check:types": "attw --profile esm-only --pack ."
|
|
69
|
+
}
|
|
70
|
+
}
|