@halo-sdk/otel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/dist/index.cjs +144 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +118 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @halo-sdk/otel
|
|
2
|
+
|
|
3
|
+
Observability & resilience decorators for Halo SDK adapters. Both are composable `ModelAdapter` wrappers — stack them with `@halo-sdk/gateway` in any order.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { withRetry, withTelemetry } from "@halo-sdk/otel";
|
|
7
|
+
|
|
8
|
+
const adapter = withTelemetry(withRetry(base, { maxRetries: 3 }), {
|
|
9
|
+
system: "anthropic",
|
|
10
|
+
onSpan: (span) => exporter.record(span), // bridge to your OTel exporter
|
|
11
|
+
});
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## `withTelemetry(adapter, { onSpan, system? })`
|
|
15
|
+
|
|
16
|
+
Times each `chat`/`stream` call and hands `onSpan` a `GenAISpan` shaped after the OpenTelemetry GenAI semantic conventions (`gen_ai.system`, `gen_ai.request.model`, `gen_ai.usage.input_tokens` / `output_tokens`, plus `halo.cache.*` attributes). Dependency-free — it does not import `@opentelemetry/*`; you bridge spans to whatever exporter you use.
|
|
17
|
+
|
|
18
|
+
## `withRetry(adapter, { maxRetries?, baseDelayMs?, isRetryable? })`
|
|
19
|
+
|
|
20
|
+
Exponential-backoff retries on transient errors. Retries `chat`, and `stream` failures that happen **before the first chunk** (retrying mid-stream would duplicate emitted output). Pass `baseDelayMs: 0` and a custom `sleep` for deterministic tests.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
withRetry: () => withRetry,
|
|
24
|
+
withTelemetry: () => withTelemetry
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
function spanAttributes(adapter, system, operation, usage) {
|
|
28
|
+
const attrs = {
|
|
29
|
+
"gen_ai.system": system,
|
|
30
|
+
"gen_ai.operation.name": operation,
|
|
31
|
+
"gen_ai.request.model": adapter.modelId
|
|
32
|
+
};
|
|
33
|
+
if (usage) {
|
|
34
|
+
attrs["gen_ai.usage.input_tokens"] = usage.promptTokens;
|
|
35
|
+
attrs["gen_ai.usage.output_tokens"] = usage.completionTokens;
|
|
36
|
+
if (usage.caching) {
|
|
37
|
+
attrs["halo.cache.hit_tokens"] = usage.caching.hitTokens;
|
|
38
|
+
attrs["halo.cache.miss_tokens"] = usage.caching.missTokens;
|
|
39
|
+
attrs["halo.cache.hit_rate"] = usage.caching.hitRate;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return attrs;
|
|
43
|
+
}
|
|
44
|
+
function withTelemetry(adapter, opts) {
|
|
45
|
+
const system = opts.system ?? adapter.modelId;
|
|
46
|
+
const now = opts.now ?? (() => Date.now());
|
|
47
|
+
return {
|
|
48
|
+
...passthrough(adapter),
|
|
49
|
+
async chat(params) {
|
|
50
|
+
const start = now();
|
|
51
|
+
try {
|
|
52
|
+
const result = await adapter.chat(params);
|
|
53
|
+
opts.onSpan({
|
|
54
|
+
name: "chat",
|
|
55
|
+
attributes: spanAttributes(adapter, system, opts.operation ?? "chat", result.usage),
|
|
56
|
+
durationMs: now() - start
|
|
57
|
+
});
|
|
58
|
+
return result;
|
|
59
|
+
} catch (err) {
|
|
60
|
+
opts.onSpan({
|
|
61
|
+
name: "chat",
|
|
62
|
+
attributes: spanAttributes(adapter, system, opts.operation ?? "chat"),
|
|
63
|
+
durationMs: now() - start,
|
|
64
|
+
error: err instanceof Error ? err.message : String(err)
|
|
65
|
+
});
|
|
66
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
async *stream(params) {
|
|
70
|
+
const start = now();
|
|
71
|
+
let usage;
|
|
72
|
+
try {
|
|
73
|
+
for await (const chunk of adapter.stream(params)) {
|
|
74
|
+
if (chunk.type === "done") usage = chunk.usage;
|
|
75
|
+
yield chunk;
|
|
76
|
+
}
|
|
77
|
+
opts.onSpan({
|
|
78
|
+
name: "stream",
|
|
79
|
+
attributes: spanAttributes(adapter, system, opts.operation ?? "stream", usage),
|
|
80
|
+
durationMs: now() - start
|
|
81
|
+
});
|
|
82
|
+
} catch (err) {
|
|
83
|
+
opts.onSpan({
|
|
84
|
+
name: "stream",
|
|
85
|
+
attributes: spanAttributes(adapter, system, opts.operation ?? "stream", usage),
|
|
86
|
+
durationMs: now() - start,
|
|
87
|
+
error: err instanceof Error ? err.message : String(err)
|
|
88
|
+
});
|
|
89
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
var defaultSleep = (ms) => ms <= 0 ? Promise.resolve() : new Promise((r) => setTimeout(r, ms));
|
|
95
|
+
function withRetry(adapter, opts) {
|
|
96
|
+
const maxRetries = opts?.maxRetries ?? 2;
|
|
97
|
+
const baseDelay = opts?.baseDelayMs ?? 200;
|
|
98
|
+
const isRetryable = opts?.isRetryable ?? (() => true);
|
|
99
|
+
const sleep = opts?.sleep ?? defaultSleep;
|
|
100
|
+
async function run(fn) {
|
|
101
|
+
let lastErr = new Error("retry failed");
|
|
102
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
103
|
+
try {
|
|
104
|
+
return await fn();
|
|
105
|
+
} catch (err) {
|
|
106
|
+
lastErr = err instanceof Error ? err : new Error(String(err));
|
|
107
|
+
if (attempt === maxRetries || !isRetryable(lastErr)) throw lastErr;
|
|
108
|
+
await sleep(baseDelay * 2 ** attempt);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
throw lastErr;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
...passthrough(adapter),
|
|
115
|
+
chat(params) {
|
|
116
|
+
return run(() => adapter.chat(params));
|
|
117
|
+
},
|
|
118
|
+
async *stream(params) {
|
|
119
|
+
const gen = await run(async () => {
|
|
120
|
+
const g = adapter.stream(params);
|
|
121
|
+
const first = await g.next();
|
|
122
|
+
return { g, first };
|
|
123
|
+
});
|
|
124
|
+
if (!gen.first.done) yield gen.first.value;
|
|
125
|
+
yield* gen.g;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function passthrough(adapter) {
|
|
130
|
+
const base = {
|
|
131
|
+
modelId: adapter.modelId,
|
|
132
|
+
contextWindow: adapter.contextWindow,
|
|
133
|
+
capabilities: adapter.capabilities
|
|
134
|
+
};
|
|
135
|
+
if (adapter.pricing) base.pricing = adapter.pricing;
|
|
136
|
+
if (adapter.keepAlive) base.keepAlive = adapter.keepAlive.bind(adapter);
|
|
137
|
+
return base;
|
|
138
|
+
}
|
|
139
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
140
|
+
0 && (module.exports = {
|
|
141
|
+
withRetry,
|
|
142
|
+
withTelemetry
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n ChatParams,\n ModelAdapter,\n ModelCapabilities,\n PricingInfo,\n TurnChunk,\n Usage,\n} from \"@halo-sdk/core\";\n\n// ── Telemetry ──\n\n/** A finished span, shaped after the OpenTelemetry GenAI semantic conventions. */\nexport interface GenAISpan {\n name: string;\n /** OTel GenAI semconv attributes (`gen_ai.*`). */\n attributes: Record<string, string | number | boolean>;\n durationMs: number;\n error?: string;\n}\n\nexport interface TelemetryOptions {\n /** Provider/system label → `gen_ai.system` (e.g. \"anthropic\"). Default: modelId. */\n system?: string;\n /** Operation label → `gen_ai.operation.name`. Default per method. */\n operation?: string;\n /** Receives each finished span. Bridge to an OTel exporter or a logger. */\n onSpan: (span: GenAISpan) => void;\n /** Clock for durations (tests). Default `Date.now`. */\n now?: () => number;\n}\n\nfunction spanAttributes(\n adapter: ModelAdapter,\n system: string,\n operation: string,\n usage?: Usage,\n): Record<string, string | number | boolean> {\n const attrs: Record<string, string | number | boolean> = {\n \"gen_ai.system\": system,\n \"gen_ai.operation.name\": operation,\n \"gen_ai.request.model\": adapter.modelId,\n };\n if (usage) {\n attrs[\"gen_ai.usage.input_tokens\"] = usage.promptTokens;\n attrs[\"gen_ai.usage.output_tokens\"] = usage.completionTokens;\n if (usage.caching) {\n attrs[\"halo.cache.hit_tokens\"] = usage.caching.hitTokens;\n attrs[\"halo.cache.miss_tokens\"] = usage.caching.missTokens;\n attrs[\"halo.cache.hit_rate\"] = usage.caching.hitRate;\n }\n }\n return attrs;\n}\n\n/**\n * Wrap a {@link ModelAdapter} with OpenTelemetry GenAI-semconv telemetry.\n *\n * Each `chat`/`stream` call produces a {@link GenAISpan} (timed, with\n * `gen_ai.*` token attributes and Halo cache attributes) handed to `onSpan` —\n * bridge it to an OTel exporter or any sink. Dependency-free: it does not import\n * `@opentelemetry/*`, so it adds no weight unless you wire a real exporter.\n */\nexport function withTelemetry(adapter: ModelAdapter, opts: TelemetryOptions): ModelAdapter {\n const system = opts.system ?? adapter.modelId;\n const now = opts.now ?? (() => Date.now());\n\n return {\n ...passthrough(adapter),\n async chat(params: ChatParams) {\n const start = now();\n try {\n const result = await adapter.chat(params);\n opts.onSpan({\n name: \"chat\",\n attributes: spanAttributes(adapter, system, opts.operation ?? \"chat\", result.usage),\n durationMs: now() - start,\n });\n return result;\n } catch (err: unknown) {\n opts.onSpan({\n name: \"chat\",\n attributes: spanAttributes(adapter, system, opts.operation ?? \"chat\"),\n durationMs: now() - start,\n error: err instanceof Error ? err.message : String(err),\n });\n throw err instanceof Error ? err : new Error(String(err));\n }\n },\n async *stream(params: ChatParams): AsyncGenerator<TurnChunk> {\n const start = now();\n let usage: Usage | undefined;\n try {\n for await (const chunk of adapter.stream(params)) {\n if (chunk.type === \"done\") usage = chunk.usage;\n yield chunk;\n }\n opts.onSpan({\n name: \"stream\",\n attributes: spanAttributes(adapter, system, opts.operation ?? \"stream\", usage),\n durationMs: now() - start,\n });\n } catch (err: unknown) {\n opts.onSpan({\n name: \"stream\",\n attributes: spanAttributes(adapter, system, opts.operation ?? \"stream\", usage),\n durationMs: now() - start,\n error: err instanceof Error ? err.message : String(err),\n });\n throw err instanceof Error ? err : new Error(String(err));\n }\n },\n };\n}\n\n// ── Retry ──\n\nexport interface RetryOptions {\n /** Max additional attempts after the first. Default 2. */\n maxRetries?: number;\n /** Base backoff in ms (doubles each attempt). Default 200. Set 0 in tests. */\n baseDelayMs?: number;\n /** Decide whether an error is worth retrying. Default: retry everything. */\n isRetryable?: (error: Error) => boolean;\n /** Sleep impl (tests). Default real setTimeout. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nconst defaultSleep = (ms: number): Promise<void> =>\n ms <= 0 ? Promise.resolve() : new Promise((r) => setTimeout(r, ms));\n\n/**\n * Wrap a {@link ModelAdapter} with exponential-backoff retries on transient\n * errors. Retries `chat`, and `stream` failures that occur **before the first\n * chunk** (retrying mid-stream would duplicate already-emitted output).\n */\nexport function withRetry(adapter: ModelAdapter, opts?: RetryOptions): ModelAdapter {\n const maxRetries = opts?.maxRetries ?? 2;\n const baseDelay = opts?.baseDelayMs ?? 200;\n const isRetryable = opts?.isRetryable ?? (() => true);\n const sleep = opts?.sleep ?? defaultSleep;\n\n async function run<T>(fn: () => Promise<T>): Promise<T> {\n let lastErr: Error = new Error(\"retry failed\");\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err: unknown) {\n lastErr = err instanceof Error ? err : new Error(String(err));\n if (attempt === maxRetries || !isRetryable(lastErr)) throw lastErr;\n await sleep(baseDelay * 2 ** attempt);\n }\n }\n throw lastErr;\n }\n\n return {\n ...passthrough(adapter),\n chat(params: ChatParams) {\n return run(() => adapter.chat(params));\n },\n async *stream(params: ChatParams): AsyncGenerator<TurnChunk> {\n // Retry only the initial connection (first chunk); then stream straight through.\n const gen = await run(async () => {\n const g = adapter.stream(params);\n const first = await g.next();\n return { g, first };\n });\n if (!gen.first.done) yield gen.first.value;\n yield* gen.g;\n },\n };\n}\n\n/** Copy the non-method surface (identity, capabilities, pricing, keepAlive). */\nfunction passthrough(adapter: ModelAdapter): {\n modelId: string;\n contextWindow: number;\n capabilities: ModelCapabilities;\n pricing?: PricingInfo;\n keepAlive?: ModelAdapter[\"keepAlive\"];\n} {\n const base: {\n modelId: string;\n contextWindow: number;\n capabilities: ModelCapabilities;\n pricing?: PricingInfo;\n keepAlive?: ModelAdapter[\"keepAlive\"];\n } = {\n modelId: adapter.modelId,\n contextWindow: adapter.contextWindow,\n capabilities: adapter.capabilities,\n };\n if (adapter.pricing) base.pricing = adapter.pricing;\n if (adapter.keepAlive) base.keepAlive = adapter.keepAlive.bind(adapter);\n return base;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ModelAdapter } from "@halo-sdk/core";
|
|
2
|
+
/** A finished span, shaped after the OpenTelemetry GenAI semantic conventions. */
|
|
3
|
+
export interface GenAISpan {
|
|
4
|
+
name: string;
|
|
5
|
+
/** OTel GenAI semconv attributes (`gen_ai.*`). */
|
|
6
|
+
attributes: Record<string, string | number | boolean>;
|
|
7
|
+
durationMs: number;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface TelemetryOptions {
|
|
11
|
+
/** Provider/system label → `gen_ai.system` (e.g. "anthropic"). Default: modelId. */
|
|
12
|
+
system?: string;
|
|
13
|
+
/** Operation label → `gen_ai.operation.name`. Default per method. */
|
|
14
|
+
operation?: string;
|
|
15
|
+
/** Receives each finished span. Bridge to an OTel exporter or a logger. */
|
|
16
|
+
onSpan: (span: GenAISpan) => void;
|
|
17
|
+
/** Clock for durations (tests). Default `Date.now`. */
|
|
18
|
+
now?: () => number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Wrap a {@link ModelAdapter} with OpenTelemetry GenAI-semconv telemetry.
|
|
22
|
+
*
|
|
23
|
+
* Each `chat`/`stream` call produces a {@link GenAISpan} (timed, with
|
|
24
|
+
* `gen_ai.*` token attributes and Halo cache attributes) handed to `onSpan` —
|
|
25
|
+
* bridge it to an OTel exporter or any sink. Dependency-free: it does not import
|
|
26
|
+
* `@opentelemetry/*`, so it adds no weight unless you wire a real exporter.
|
|
27
|
+
*/
|
|
28
|
+
export declare function withTelemetry(adapter: ModelAdapter, opts: TelemetryOptions): ModelAdapter;
|
|
29
|
+
export interface RetryOptions {
|
|
30
|
+
/** Max additional attempts after the first. Default 2. */
|
|
31
|
+
maxRetries?: number;
|
|
32
|
+
/** Base backoff in ms (doubles each attempt). Default 200. Set 0 in tests. */
|
|
33
|
+
baseDelayMs?: number;
|
|
34
|
+
/** Decide whether an error is worth retrying. Default: retry everything. */
|
|
35
|
+
isRetryable?: (error: Error) => boolean;
|
|
36
|
+
/** Sleep impl (tests). Default real setTimeout. */
|
|
37
|
+
sleep?: (ms: number) => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Wrap a {@link ModelAdapter} with exponential-backoff retries on transient
|
|
41
|
+
* errors. Retries `chat`, and `stream` failures that occur **before the first
|
|
42
|
+
* chunk** (retrying mid-stream would duplicate already-emitted output).
|
|
43
|
+
*/
|
|
44
|
+
export declare function withRetry(adapter: ModelAdapter, opts?: RetryOptions): ModelAdapter;
|
|
45
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,YAAY,EAKb,MAAM,gBAAgB,CAAC;AAIxB,kFAAkF;AAClF,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACtD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAClC,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAyBD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,gBAAgB,GAAG,YAAY,CAkDzF;AAID,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;IACxC,mDAAmD;IACnD,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAKD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,YAAY,CAoClF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
function spanAttributes(adapter, system, operation, usage) {
|
|
3
|
+
const attrs = {
|
|
4
|
+
"gen_ai.system": system,
|
|
5
|
+
"gen_ai.operation.name": operation,
|
|
6
|
+
"gen_ai.request.model": adapter.modelId
|
|
7
|
+
};
|
|
8
|
+
if (usage) {
|
|
9
|
+
attrs["gen_ai.usage.input_tokens"] = usage.promptTokens;
|
|
10
|
+
attrs["gen_ai.usage.output_tokens"] = usage.completionTokens;
|
|
11
|
+
if (usage.caching) {
|
|
12
|
+
attrs["halo.cache.hit_tokens"] = usage.caching.hitTokens;
|
|
13
|
+
attrs["halo.cache.miss_tokens"] = usage.caching.missTokens;
|
|
14
|
+
attrs["halo.cache.hit_rate"] = usage.caching.hitRate;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return attrs;
|
|
18
|
+
}
|
|
19
|
+
function withTelemetry(adapter, opts) {
|
|
20
|
+
const system = opts.system ?? adapter.modelId;
|
|
21
|
+
const now = opts.now ?? (() => Date.now());
|
|
22
|
+
return {
|
|
23
|
+
...passthrough(adapter),
|
|
24
|
+
async chat(params) {
|
|
25
|
+
const start = now();
|
|
26
|
+
try {
|
|
27
|
+
const result = await adapter.chat(params);
|
|
28
|
+
opts.onSpan({
|
|
29
|
+
name: "chat",
|
|
30
|
+
attributes: spanAttributes(adapter, system, opts.operation ?? "chat", result.usage),
|
|
31
|
+
durationMs: now() - start
|
|
32
|
+
});
|
|
33
|
+
return result;
|
|
34
|
+
} catch (err) {
|
|
35
|
+
opts.onSpan({
|
|
36
|
+
name: "chat",
|
|
37
|
+
attributes: spanAttributes(adapter, system, opts.operation ?? "chat"),
|
|
38
|
+
durationMs: now() - start,
|
|
39
|
+
error: err instanceof Error ? err.message : String(err)
|
|
40
|
+
});
|
|
41
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
async *stream(params) {
|
|
45
|
+
const start = now();
|
|
46
|
+
let usage;
|
|
47
|
+
try {
|
|
48
|
+
for await (const chunk of adapter.stream(params)) {
|
|
49
|
+
if (chunk.type === "done") usage = chunk.usage;
|
|
50
|
+
yield chunk;
|
|
51
|
+
}
|
|
52
|
+
opts.onSpan({
|
|
53
|
+
name: "stream",
|
|
54
|
+
attributes: spanAttributes(adapter, system, opts.operation ?? "stream", usage),
|
|
55
|
+
durationMs: now() - start
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
opts.onSpan({
|
|
59
|
+
name: "stream",
|
|
60
|
+
attributes: spanAttributes(adapter, system, opts.operation ?? "stream", usage),
|
|
61
|
+
durationMs: now() - start,
|
|
62
|
+
error: err instanceof Error ? err.message : String(err)
|
|
63
|
+
});
|
|
64
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
var defaultSleep = (ms) => ms <= 0 ? Promise.resolve() : new Promise((r) => setTimeout(r, ms));
|
|
70
|
+
function withRetry(adapter, opts) {
|
|
71
|
+
const maxRetries = opts?.maxRetries ?? 2;
|
|
72
|
+
const baseDelay = opts?.baseDelayMs ?? 200;
|
|
73
|
+
const isRetryable = opts?.isRetryable ?? (() => true);
|
|
74
|
+
const sleep = opts?.sleep ?? defaultSleep;
|
|
75
|
+
async function run(fn) {
|
|
76
|
+
let lastErr = new Error("retry failed");
|
|
77
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
78
|
+
try {
|
|
79
|
+
return await fn();
|
|
80
|
+
} catch (err) {
|
|
81
|
+
lastErr = err instanceof Error ? err : new Error(String(err));
|
|
82
|
+
if (attempt === maxRetries || !isRetryable(lastErr)) throw lastErr;
|
|
83
|
+
await sleep(baseDelay * 2 ** attempt);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
throw lastErr;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...passthrough(adapter),
|
|
90
|
+
chat(params) {
|
|
91
|
+
return run(() => adapter.chat(params));
|
|
92
|
+
},
|
|
93
|
+
async *stream(params) {
|
|
94
|
+
const gen = await run(async () => {
|
|
95
|
+
const g = adapter.stream(params);
|
|
96
|
+
const first = await g.next();
|
|
97
|
+
return { g, first };
|
|
98
|
+
});
|
|
99
|
+
if (!gen.first.done) yield gen.first.value;
|
|
100
|
+
yield* gen.g;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function passthrough(adapter) {
|
|
105
|
+
const base = {
|
|
106
|
+
modelId: adapter.modelId,
|
|
107
|
+
contextWindow: adapter.contextWindow,
|
|
108
|
+
capabilities: adapter.capabilities
|
|
109
|
+
};
|
|
110
|
+
if (adapter.pricing) base.pricing = adapter.pricing;
|
|
111
|
+
if (adapter.keepAlive) base.keepAlive = adapter.keepAlive.bind(adapter);
|
|
112
|
+
return base;
|
|
113
|
+
}
|
|
114
|
+
export {
|
|
115
|
+
withRetry,
|
|
116
|
+
withTelemetry
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n ChatParams,\n ModelAdapter,\n ModelCapabilities,\n PricingInfo,\n TurnChunk,\n Usage,\n} from \"@halo-sdk/core\";\n\n// ── Telemetry ──\n\n/** A finished span, shaped after the OpenTelemetry GenAI semantic conventions. */\nexport interface GenAISpan {\n name: string;\n /** OTel GenAI semconv attributes (`gen_ai.*`). */\n attributes: Record<string, string | number | boolean>;\n durationMs: number;\n error?: string;\n}\n\nexport interface TelemetryOptions {\n /** Provider/system label → `gen_ai.system` (e.g. \"anthropic\"). Default: modelId. */\n system?: string;\n /** Operation label → `gen_ai.operation.name`. Default per method. */\n operation?: string;\n /** Receives each finished span. Bridge to an OTel exporter or a logger. */\n onSpan: (span: GenAISpan) => void;\n /** Clock for durations (tests). Default `Date.now`. */\n now?: () => number;\n}\n\nfunction spanAttributes(\n adapter: ModelAdapter,\n system: string,\n operation: string,\n usage?: Usage,\n): Record<string, string | number | boolean> {\n const attrs: Record<string, string | number | boolean> = {\n \"gen_ai.system\": system,\n \"gen_ai.operation.name\": operation,\n \"gen_ai.request.model\": adapter.modelId,\n };\n if (usage) {\n attrs[\"gen_ai.usage.input_tokens\"] = usage.promptTokens;\n attrs[\"gen_ai.usage.output_tokens\"] = usage.completionTokens;\n if (usage.caching) {\n attrs[\"halo.cache.hit_tokens\"] = usage.caching.hitTokens;\n attrs[\"halo.cache.miss_tokens\"] = usage.caching.missTokens;\n attrs[\"halo.cache.hit_rate\"] = usage.caching.hitRate;\n }\n }\n return attrs;\n}\n\n/**\n * Wrap a {@link ModelAdapter} with OpenTelemetry GenAI-semconv telemetry.\n *\n * Each `chat`/`stream` call produces a {@link GenAISpan} (timed, with\n * `gen_ai.*` token attributes and Halo cache attributes) handed to `onSpan` —\n * bridge it to an OTel exporter or any sink. Dependency-free: it does not import\n * `@opentelemetry/*`, so it adds no weight unless you wire a real exporter.\n */\nexport function withTelemetry(adapter: ModelAdapter, opts: TelemetryOptions): ModelAdapter {\n const system = opts.system ?? adapter.modelId;\n const now = opts.now ?? (() => Date.now());\n\n return {\n ...passthrough(adapter),\n async chat(params: ChatParams) {\n const start = now();\n try {\n const result = await adapter.chat(params);\n opts.onSpan({\n name: \"chat\",\n attributes: spanAttributes(adapter, system, opts.operation ?? \"chat\", result.usage),\n durationMs: now() - start,\n });\n return result;\n } catch (err: unknown) {\n opts.onSpan({\n name: \"chat\",\n attributes: spanAttributes(adapter, system, opts.operation ?? \"chat\"),\n durationMs: now() - start,\n error: err instanceof Error ? err.message : String(err),\n });\n throw err instanceof Error ? err : new Error(String(err));\n }\n },\n async *stream(params: ChatParams): AsyncGenerator<TurnChunk> {\n const start = now();\n let usage: Usage | undefined;\n try {\n for await (const chunk of adapter.stream(params)) {\n if (chunk.type === \"done\") usage = chunk.usage;\n yield chunk;\n }\n opts.onSpan({\n name: \"stream\",\n attributes: spanAttributes(adapter, system, opts.operation ?? \"stream\", usage),\n durationMs: now() - start,\n });\n } catch (err: unknown) {\n opts.onSpan({\n name: \"stream\",\n attributes: spanAttributes(adapter, system, opts.operation ?? \"stream\", usage),\n durationMs: now() - start,\n error: err instanceof Error ? err.message : String(err),\n });\n throw err instanceof Error ? err : new Error(String(err));\n }\n },\n };\n}\n\n// ── Retry ──\n\nexport interface RetryOptions {\n /** Max additional attempts after the first. Default 2. */\n maxRetries?: number;\n /** Base backoff in ms (doubles each attempt). Default 200. Set 0 in tests. */\n baseDelayMs?: number;\n /** Decide whether an error is worth retrying. Default: retry everything. */\n isRetryable?: (error: Error) => boolean;\n /** Sleep impl (tests). Default real setTimeout. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nconst defaultSleep = (ms: number): Promise<void> =>\n ms <= 0 ? Promise.resolve() : new Promise((r) => setTimeout(r, ms));\n\n/**\n * Wrap a {@link ModelAdapter} with exponential-backoff retries on transient\n * errors. Retries `chat`, and `stream` failures that occur **before the first\n * chunk** (retrying mid-stream would duplicate already-emitted output).\n */\nexport function withRetry(adapter: ModelAdapter, opts?: RetryOptions): ModelAdapter {\n const maxRetries = opts?.maxRetries ?? 2;\n const baseDelay = opts?.baseDelayMs ?? 200;\n const isRetryable = opts?.isRetryable ?? (() => true);\n const sleep = opts?.sleep ?? defaultSleep;\n\n async function run<T>(fn: () => Promise<T>): Promise<T> {\n let lastErr: Error = new Error(\"retry failed\");\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err: unknown) {\n lastErr = err instanceof Error ? err : new Error(String(err));\n if (attempt === maxRetries || !isRetryable(lastErr)) throw lastErr;\n await sleep(baseDelay * 2 ** attempt);\n }\n }\n throw lastErr;\n }\n\n return {\n ...passthrough(adapter),\n chat(params: ChatParams) {\n return run(() => adapter.chat(params));\n },\n async *stream(params: ChatParams): AsyncGenerator<TurnChunk> {\n // Retry only the initial connection (first chunk); then stream straight through.\n const gen = await run(async () => {\n const g = adapter.stream(params);\n const first = await g.next();\n return { g, first };\n });\n if (!gen.first.done) yield gen.first.value;\n yield* gen.g;\n },\n };\n}\n\n/** Copy the non-method surface (identity, capabilities, pricing, keepAlive). */\nfunction passthrough(adapter: ModelAdapter): {\n modelId: string;\n contextWindow: number;\n capabilities: ModelCapabilities;\n pricing?: PricingInfo;\n keepAlive?: ModelAdapter[\"keepAlive\"];\n} {\n const base: {\n modelId: string;\n contextWindow: number;\n capabilities: ModelCapabilities;\n pricing?: PricingInfo;\n keepAlive?: ModelAdapter[\"keepAlive\"];\n } = {\n modelId: adapter.modelId,\n contextWindow: adapter.contextWindow,\n capabilities: adapter.capabilities,\n };\n if (adapter.pricing) base.pricing = adapter.pricing;\n if (adapter.keepAlive) base.keepAlive = adapter.keepAlive.bind(adapter);\n return base;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@halo-sdk/otel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Observability & resilience decorators for Halo SDK adapters — OpenTelemetry GenAI spans + retry/backoff",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"llm",
|
|
8
|
+
"observability",
|
|
9
|
+
"opentelemetry",
|
|
10
|
+
"resilience",
|
|
11
|
+
"retry"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/halo-sdk/halo-ai",
|
|
17
|
+
"directory": "packages/otel"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"require": "./dist/index.cjs"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"typescript": "^5.8.0",
|
|
37
|
+
"vitest": "^3.0.0",
|
|
38
|
+
"@halo-sdk/core": "1.1.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@halo-sdk/core": ">=1.1.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc --build --emitDeclarationOnly && tsup",
|
|
45
|
+
"dev": "tsup --watch",
|
|
46
|
+
"clean": "del-cli dist *.tsbuildinfo",
|
|
47
|
+
"publint": "publint",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:watch": "vitest"
|
|
50
|
+
}
|
|
51
|
+
}
|