@axlsdk/axl 0.25.3 → 0.27.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 +53 -13
- package/dist/{chunk-PJROX3XU.js → chunk-SBQL47BB.js} +40 -25
- package/dist/chunk-SBQL47BB.js.map +1 -0
- package/dist/index.cjs +14128 -11094
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2139 -94
- package/dist/index.d.ts +2139 -94
- package/dist/index.js +13948 -10935
- package/dist/index.js.map +1 -1
- package/dist/{span-manager-L4H6ZCRQ.js → span-manager-I4JFIB5F.js} +2 -2
- package/package.json +2 -2
- package/dist/chunk-PJROX3XU.js.map +0 -1
- /package/dist/{span-manager-L4H6ZCRQ.js.map → span-manager-I4JFIB5F.js.map} +0 -0
package/README.md
CHANGED
|
@@ -509,22 +509,28 @@ const checkpointed = await ctx.checkpoint('expensive-op', async () => expensiveO
|
|
|
509
509
|
|
|
510
510
|
### OpenTelemetry Observability
|
|
511
511
|
|
|
512
|
-
Automatic
|
|
512
|
+
Automatic spans for `ctx.*` primitives and every model call Axl sends. Install
|
|
513
|
+
`@opentelemetry/api` as an optional peer dependency.
|
|
513
514
|
|
|
514
515
|
```typescript
|
|
515
516
|
import { defineConfig, AxlRuntime } from '@axlsdk/axl';
|
|
517
|
+
import { context, trace } from '@opentelemetry/api';
|
|
518
|
+
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
|
|
516
519
|
import {
|
|
517
520
|
BasicTracerProvider,
|
|
518
521
|
SimpleSpanProcessor,
|
|
519
522
|
} from '@opentelemetry/sdk-trace-base';
|
|
520
523
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
521
524
|
|
|
522
|
-
const tracerProvider = new BasicTracerProvider(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
525
|
+
const tracerProvider = new BasicTracerProvider({
|
|
526
|
+
spanProcessors: [
|
|
527
|
+
new SimpleSpanProcessor(
|
|
528
|
+
new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' }),
|
|
529
|
+
),
|
|
530
|
+
],
|
|
531
|
+
});
|
|
532
|
+
context.setGlobalContextManager(new AsyncLocalStorageContextManager());
|
|
533
|
+
trace.setGlobalTracerProvider(tracerProvider);
|
|
528
534
|
|
|
529
535
|
const config = defineConfig({
|
|
530
536
|
telemetry: {
|
|
@@ -536,11 +542,29 @@ const config = defineConfig({
|
|
|
536
542
|
|
|
537
543
|
const runtime = new AxlRuntime(config);
|
|
538
544
|
await runtime.initializeTelemetry();
|
|
545
|
+
|
|
546
|
+
// After the application stops accepting work:
|
|
547
|
+
await runtime.shutdown();
|
|
548
|
+
await tracerProvider.shutdown();
|
|
549
|
+
context.disable();
|
|
550
|
+
trace.disable();
|
|
539
551
|
```
|
|
540
552
|
|
|
541
|
-
**Span model:** `axl.workflow.execute` > `axl.agent.ask` > `
|
|
553
|
+
**Span model:** `axl.workflow.execute` > `axl.agent.ask` > `chat {model}`
|
|
554
|
+
for model calls, with `axl.tool.call` under an agent when tools run. Also:
|
|
555
|
+
`axl.ctx.spawn`, `axl.ctx.race`, `axl.ctx.vote`, `axl.ctx.budget`,
|
|
556
|
+
`axl.ctx.awaitHuman`. Model-call spans use standard `gen_ai.*` attributes.
|
|
557
|
+
Prompt and response content is off by default; see
|
|
558
|
+
[content capture](../../docs/observability.md#genai-content-capture).
|
|
542
559
|
|
|
543
|
-
When disabled (default), `NoopSpanManager`
|
|
560
|
+
When telemetry is disabled (default), `NoopSpanManager` adds no overhead. Axl
|
|
561
|
+
never shuts down your tracer provider; do that after `runtime.shutdown()`.
|
|
562
|
+
|
|
563
|
+
For this Node setup, install the optional OTel packages used above:
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
npm install @opentelemetry/api @opentelemetry/context-async-hooks @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http
|
|
567
|
+
```
|
|
544
568
|
|
|
545
569
|
```typescript
|
|
546
570
|
import { createSpanManager, NoopSpanManager } from '@axlsdk/axl';
|
|
@@ -595,6 +619,9 @@ eval history row that owns them is saved, deleted with it, and reclaimed by a
|
|
|
595
619
|
startup pass plus a periodic sweep. See
|
|
596
620
|
[observability.md](../../docs/observability.md#captured-requests-opt-in) and
|
|
597
621
|
[integration.md](../../docs/integration.md#diagnostic-artifact-storage).
|
|
622
|
+
`foldJournal()` turns journal lines into one validated record per call, and
|
|
623
|
+
`redactModelCallRecord()` returns a redacted copy. See
|
|
624
|
+
[What a record contains](../../docs/observability.md#what-a-record-contains).
|
|
598
625
|
|
|
599
626
|
Compatible loaded ESM/CJS copies share accounting, admission, and capture in
|
|
600
627
|
one JavaScript realm. Cross-copy work emits one warning identifying both copies;
|
|
@@ -728,7 +755,9 @@ const store = await RedisStore.create({
|
|
|
728
755
|
|
|
729
756
|
The prefix is concatenated as-given — no normalization. Include a trailing colon if you want one. Empty string is rejected.
|
|
730
757
|
|
|
731
|
-
**TTLs are strongly recommended in production** — without them every `save*` accumulates forever and Redis eventually OOMs. Sliding window: `memory`, `session`, `sessionMeta` (every write refreshes; reads do NOT). Fixed
|
|
758
|
+
**TTLs are strongly recommended in production** — without them every `save*` accumulates forever and Redis eventually OOMs. Sliding window: `memory`, `session`, `sessionMeta` (every write refreshes; reads do NOT). Fixed from the first write: `checkpoint`, `streamingEvents`, `evalHistory` (re-saves and corrections keep an eval row's expiry). Restarted on every save: `executionState`, `executionHistory`. `streamingEvents` is opt-in only (does NOT fall back to `defaultTtl`). See [api-reference.md](../../docs/api-reference.md#redisstore-options) for the full table.
|
|
759
|
+
|
|
760
|
+
`MemoryStore` and `SQLiteStore` take the same `ttls.executionHistory` / `ttls.evalHistory`; `MemoryStore` keeps the newest 1 000 runs by default (`maxExecutions`). See the [execution history retention guide](../../docs/migration/execution-history-retention.md).
|
|
732
761
|
|
|
733
762
|
#### Crash survival: `state.persist: 'streaming'`
|
|
734
763
|
|
|
@@ -744,8 +773,7 @@ const runtime = new AxlRuntime({
|
|
|
744
773
|
},
|
|
745
774
|
});
|
|
746
775
|
|
|
747
|
-
// Boot sequence:
|
|
748
|
-
await runtime.getExecutions();
|
|
776
|
+
// Boot sequence: recover THEN accept new work
|
|
749
777
|
const recovered = await runtime.recoverIncompleteStreams();
|
|
750
778
|
console.log(`Recovered ${recovered.length} crashed executions`);
|
|
751
779
|
app.listen(3000);
|
|
@@ -776,7 +804,19 @@ runtime.on('decision_cleanup_failed', (e) => {
|
|
|
776
804
|
|
|
777
805
|
If the execution is still running, the workflow is aborted (and a paused `ctx.awaitHuman()` correctly wakes with `AbortError` — fixed in 0.17.7). The resurrection guard ensures the workflow's eventual `workflow_end` doesn't re-create the row. Deletion waits for any in-process approval resolution or cancellation compensation before the store sweep. Custom stores must make `resolveDecision` idempotent and implement the complete execution-scoped sweep in `deleteExecution`.
|
|
778
806
|
|
|
779
|
-
`ExecutionInfo.metadata` round-trips from `ExecuteOptions.metadata` (`userId`, `tenantId`, etc.)
|
|
807
|
+
`ExecutionInfo.metadata` round-trips from `ExecuteOptions.metadata` (`userId`, `tenantId`, etc.). Filter on it as you read history; for large histories, read a page at a time:
|
|
808
|
+
|
|
809
|
+
```typescript
|
|
810
|
+
let cursor: ExecutionCursor | undefined;
|
|
811
|
+
do {
|
|
812
|
+
const page = await runtime.getExecutions({ limit: 500, cursor });
|
|
813
|
+
for (const e of page) if (e.metadata?.tenantId === tenantId) handle(e);
|
|
814
|
+
const last = page.at(-1);
|
|
815
|
+
cursor = last && { startedAt: last.startedAt, executionId: last.executionId };
|
|
816
|
+
} while (cursor);
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
Internal session control-plane keys (`sessionHistory`, `sessionId`) are stripped before persistence; they remain available via `ctx.metadata` for dynamic selectors.
|
|
780
820
|
|
|
781
821
|
### Session Options
|
|
782
822
|
|
|
@@ -12,11 +12,9 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
12
12
|
var OTelSpanManager = class _OTelSpanManager {
|
|
13
13
|
tracer;
|
|
14
14
|
otelApi;
|
|
15
|
-
|
|
16
|
-
constructor(otelApi, tracer, tracerProvider) {
|
|
15
|
+
constructor(otelApi, tracer) {
|
|
17
16
|
this.otelApi = otelApi;
|
|
18
17
|
this.tracer = tracer;
|
|
19
|
-
this.tracerProvider = tracerProvider;
|
|
20
18
|
}
|
|
21
19
|
static async create(config) {
|
|
22
20
|
let otelApi;
|
|
@@ -30,28 +28,14 @@ var OTelSpanManager = class _OTelSpanManager {
|
|
|
30
28
|
const tracerProvider = config.tracerProvider ?? otelApi.trace.getTracerProvider();
|
|
31
29
|
const serviceName = config.serviceName ?? "axl";
|
|
32
30
|
const tracer = tracerProvider.getTracer(serviceName);
|
|
33
|
-
return new _OTelSpanManager(otelApi, tracer
|
|
31
|
+
return new _OTelSpanManager(otelApi, tracer);
|
|
34
32
|
}
|
|
35
33
|
async withSpanAsync(name, attributes, fn) {
|
|
36
|
-
const otelApi = this.otelApi;
|
|
37
34
|
return this.tracer.startActiveSpan(name, { attributes }, async (otelSpan) => {
|
|
38
35
|
let statusWasSet = false;
|
|
39
|
-
const handle = {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
},
|
|
43
|
-
addEvent(eventName, attrs) {
|
|
44
|
-
otelSpan.addEvent(eventName, attrs);
|
|
45
|
-
},
|
|
46
|
-
setStatus(code, message) {
|
|
47
|
-
statusWasSet = true;
|
|
48
|
-
const statusCode = code === "ok" ? otelApi.SpanStatusCode.OK : code === "error" ? otelApi.SpanStatusCode.ERROR : otelApi.SpanStatusCode.UNSET;
|
|
49
|
-
otelSpan.setStatus({ code: statusCode, message });
|
|
50
|
-
},
|
|
51
|
-
end() {
|
|
52
|
-
otelSpan.end();
|
|
53
|
-
}
|
|
54
|
-
};
|
|
36
|
+
const handle = this.handle(otelSpan, () => {
|
|
37
|
+
statusWasSet = true;
|
|
38
|
+
});
|
|
55
39
|
try {
|
|
56
40
|
const result = await fn(handle);
|
|
57
41
|
if (!statusWasSet) handle.setStatus("ok");
|
|
@@ -64,16 +48,47 @@ var OTelSpanManager = class _OTelSpanManager {
|
|
|
64
48
|
}
|
|
65
49
|
});
|
|
66
50
|
}
|
|
51
|
+
/** Start a CLIENT inference span under the current OTel context. The caller ends it. */
|
|
52
|
+
startSpan(name, attributes, options) {
|
|
53
|
+
const span = this.tracer.startSpan(name, {
|
|
54
|
+
attributes,
|
|
55
|
+
...options?.kind === "client" ? { kind: this.otelApi.SpanKind.CLIENT } : {},
|
|
56
|
+
...options?.startTime !== void 0 ? { startTime: options.startTime } : {}
|
|
57
|
+
});
|
|
58
|
+
return this.handle(span);
|
|
59
|
+
}
|
|
60
|
+
handle(span, onStatus) {
|
|
61
|
+
const otelApi = this.otelApi;
|
|
62
|
+
let ended = false;
|
|
63
|
+
return {
|
|
64
|
+
setAttribute(key, value) {
|
|
65
|
+
if (!ended) span.setAttribute(key, value);
|
|
66
|
+
},
|
|
67
|
+
addEvent(name, attributes) {
|
|
68
|
+
if (!ended) span.addEvent(name, attributes);
|
|
69
|
+
},
|
|
70
|
+
setStatus(code, message) {
|
|
71
|
+
if (ended) return;
|
|
72
|
+
onStatus?.();
|
|
73
|
+
const statusCode = code === "ok" ? otelApi.SpanStatusCode.OK : code === "error" ? otelApi.SpanStatusCode.ERROR : otelApi.SpanStatusCode.UNSET;
|
|
74
|
+
span.setStatus({ code: statusCode, message });
|
|
75
|
+
},
|
|
76
|
+
end() {
|
|
77
|
+
if (ended) return;
|
|
78
|
+
ended = true;
|
|
79
|
+
span.end();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
67
83
|
addEventToActiveSpan(name, attributes) {
|
|
68
84
|
const activeSpan = this.otelApi.trace.getActiveSpan?.();
|
|
69
85
|
if (activeSpan) {
|
|
70
86
|
activeSpan.addEvent(name, attributes);
|
|
71
87
|
}
|
|
72
88
|
}
|
|
89
|
+
// Axl uses an application-owned provider, whether supplied or global.
|
|
90
|
+
// Its lifecycle, including flush and shutdown, belongs to the application.
|
|
73
91
|
async shutdown() {
|
|
74
|
-
if (this.tracerProvider && typeof this.tracerProvider.shutdown === "function") {
|
|
75
|
-
await this.tracerProvider.shutdown();
|
|
76
|
-
}
|
|
77
92
|
}
|
|
78
93
|
};
|
|
79
94
|
|
|
@@ -81,4 +96,4 @@ export {
|
|
|
81
96
|
__require,
|
|
82
97
|
OTelSpanManager
|
|
83
98
|
};
|
|
84
|
-
//# sourceMappingURL=chunk-
|
|
99
|
+
//# sourceMappingURL=chunk-SBQL47BB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/telemetry/span-manager.ts"],"sourcesContent":["import type {\n SpanAttributeValue,\n SpanAttributes,\n SpanHandle,\n SpanManager,\n TelemetryConfig,\n} from './types.js';\n\n/** Minimal interface for an OTel span. */\ninterface OTelSpan {\n setAttribute(key: string, value: SpanAttributeValue): void;\n addEvent(name: string, attributes?: SpanAttributes): void;\n setStatus(status: { code: unknown; message?: string }): void;\n end(): void;\n}\n\n/** Minimal interface for an OTel tracer. */\ninterface OTelTracer {\n startSpan(\n name: string,\n options: { attributes: SpanAttributes; kind?: number; startTime?: number },\n ): OTelSpan;\n startActiveSpan<T>(\n name: string,\n options: { attributes: SpanAttributes },\n fn: (span: OTelSpan) => Promise<T>,\n ): Promise<T>;\n}\n\n/** Minimal interface for an OTel tracer provider. */\ninterface OTelTracerProvider {\n getTracer(name: string): OTelTracer;\n}\n\n/** Minimal interface for the @opentelemetry/api module. */\ninterface OTelApi {\n SpanKind: { CLIENT: number };\n SpanStatusCode: { UNSET: unknown; OK: unknown; ERROR: unknown };\n trace: {\n getTracerProvider(): OTelTracerProvider;\n getActiveSpan?(): OTelSpan | undefined;\n };\n}\n\n/**\n * OpenTelemetry-backed SpanManager.\n * Dynamically imports @opentelemetry/api to avoid hard dependency.\n */\nexport class OTelSpanManager implements SpanManager {\n private tracer: OTelTracer;\n private otelApi: OTelApi;\n\n private constructor(otelApi: OTelApi, tracer: OTelTracer) {\n this.otelApi = otelApi;\n this.tracer = tracer;\n }\n\n static async create(config: TelemetryConfig): Promise<OTelSpanManager> {\n let otelApi: OTelApi;\n try {\n otelApi = (await import('@opentelemetry/api')) as unknown as OTelApi;\n } catch {\n throw new Error(\n '@opentelemetry/api is required for telemetry. Install it with: npm install @opentelemetry/api',\n );\n }\n\n const tracerProvider =\n (config.tracerProvider as OTelTracerProvider | undefined) ??\n otelApi.trace.getTracerProvider();\n const serviceName = config.serviceName ?? 'axl';\n const tracer = tracerProvider.getTracer(serviceName);\n\n return new OTelSpanManager(otelApi, tracer);\n }\n\n async withSpanAsync<T>(\n name: string,\n attributes: SpanAttributes,\n fn: (span: SpanHandle) => Promise<T>,\n ): Promise<T> {\n return this.tracer.startActiveSpan(name, { attributes }, async (otelSpan: OTelSpan) => {\n let statusWasSet = false;\n const handle = this.handle(otelSpan, () => {\n statusWasSet = true;\n });\n\n try {\n const result = await fn(handle);\n if (!statusWasSet) handle.setStatus('ok');\n return result;\n } catch (err) {\n // Error text can contain provider, prompt, or sensitive tool data.\n // Keep exported status structural; host diagnostics remain in Axl's\n // own error and redacted event channels.\n handle.setStatus('error');\n throw err;\n } finally {\n handle.end();\n }\n });\n }\n\n /** Start a CLIENT inference span under the current OTel context. The caller ends it. */\n startSpan(\n name: string,\n attributes: SpanAttributes,\n options?: { kind?: 'client'; startTime?: number },\n ): SpanHandle {\n const span = this.tracer.startSpan(name, {\n attributes,\n ...(options?.kind === 'client' ? { kind: this.otelApi.SpanKind.CLIENT } : {}),\n ...(options?.startTime !== undefined ? { startTime: options.startTime } : {}),\n });\n return this.handle(span);\n }\n\n private handle(span: OTelSpan, onStatus?: () => void): SpanHandle {\n const otelApi = this.otelApi;\n let ended = false;\n return {\n setAttribute(key, value) {\n if (!ended) span.setAttribute(key, value);\n },\n addEvent(name, attributes) {\n if (!ended) span.addEvent(name, attributes);\n },\n setStatus(code, message) {\n if (ended) return;\n onStatus?.();\n const statusCode =\n code === 'ok'\n ? otelApi.SpanStatusCode.OK\n : code === 'error'\n ? otelApi.SpanStatusCode.ERROR\n : otelApi.SpanStatusCode.UNSET;\n span.setStatus({ code: statusCode, message });\n },\n end() {\n if (ended) return;\n ended = true;\n span.end();\n },\n };\n }\n\n addEventToActiveSpan(name: string, attributes?: SpanAttributes): void {\n const activeSpan = this.otelApi.trace.getActiveSpan?.();\n if (activeSpan) {\n activeSpan.addEvent(name, attributes);\n }\n }\n\n // Axl uses an application-owned provider, whether supplied or global.\n // Its lifecycle, including flush and shutdown, belongs to the application.\n async shutdown(): Promise<void> {}\n}\n"],"mappings":";;;;;;;;;;;AAgDO,IAAM,kBAAN,MAAM,iBAAuC;AAAA,EAC1C;AAAA,EACA;AAAA,EAEA,YAAY,SAAkB,QAAoB;AACxD,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,aAAa,OAAO,QAAmD;AACrE,QAAI;AACJ,QAAI;AACF,gBAAW,MAAM,OAAO,oBAAoB;AAAA,IAC9C,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,iBACH,OAAO,kBACR,QAAQ,MAAM,kBAAkB;AAClC,UAAM,cAAc,OAAO,eAAe;AAC1C,UAAM,SAAS,eAAe,UAAU,WAAW;AAEnD,WAAO,IAAI,iBAAgB,SAAS,MAAM;AAAA,EAC5C;AAAA,EAEA,MAAM,cACJ,MACA,YACA,IACY;AACZ,WAAO,KAAK,OAAO,gBAAgB,MAAM,EAAE,WAAW,GAAG,OAAO,aAAuB;AACrF,UAAI,eAAe;AACnB,YAAM,SAAS,KAAK,OAAO,UAAU,MAAM;AACzC,uBAAe;AAAA,MACjB,CAAC;AAED,UAAI;AACF,cAAM,SAAS,MAAM,GAAG,MAAM;AAC9B,YAAI,CAAC,aAAc,QAAO,UAAU,IAAI;AACxC,eAAO;AAAA,MACT,SAAS,KAAK;AAIZ,eAAO,UAAU,OAAO;AACxB,cAAM;AAAA,MACR,UAAE;AACA,eAAO,IAAI;AAAA,MACb;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,UACE,MACA,YACA,SACY;AACZ,UAAM,OAAO,KAAK,OAAO,UAAU,MAAM;AAAA,MACvC;AAAA,MACA,GAAI,SAAS,SAAS,WAAW,EAAE,MAAM,KAAK,QAAQ,SAAS,OAAO,IAAI,CAAC;AAAA,MAC3E,GAAI,SAAS,cAAc,SAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,IAC7E,CAAC;AACD,WAAO,KAAK,OAAO,IAAI;AAAA,EACzB;AAAA,EAEQ,OAAO,MAAgB,UAAmC;AAChE,UAAM,UAAU,KAAK;AACrB,QAAI,QAAQ;AACZ,WAAO;AAAA,MACL,aAAa,KAAK,OAAO;AACvB,YAAI,CAAC,MAAO,MAAK,aAAa,KAAK,KAAK;AAAA,MAC1C;AAAA,MACA,SAAS,MAAM,YAAY;AACzB,YAAI,CAAC,MAAO,MAAK,SAAS,MAAM,UAAU;AAAA,MAC5C;AAAA,MACA,UAAU,MAAM,SAAS;AACvB,YAAI,MAAO;AACX,mBAAW;AACX,cAAM,aACJ,SAAS,OACL,QAAQ,eAAe,KACvB,SAAS,UACP,QAAQ,eAAe,QACvB,QAAQ,eAAe;AAC/B,aAAK,UAAU,EAAE,MAAM,YAAY,QAAQ,CAAC;AAAA,MAC9C;AAAA,MACA,MAAM;AACJ,YAAI,MAAO;AACX,gBAAQ;AACR,aAAK,IAAI;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EAEA,qBAAqB,MAAc,YAAmC;AACpE,UAAM,aAAa,KAAK,QAAQ,MAAM,gBAAgB;AACtD,QAAI,YAAY;AACd,iBAAW,SAAS,MAAM,UAAU;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,MAAM,WAA0B;AAAA,EAAC;AACnC;","names":[]}
|