@axlsdk/axl 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/README.md ADDED
@@ -0,0 +1,363 @@
1
+ # axl
2
+
3
+ Core SDK for orchestrating agentic systems in TypeScript.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install axl zod
9
+ ```
10
+
11
+ ## API
12
+
13
+ ### `tool(config)`
14
+
15
+ Define a tool with Zod input validation:
16
+
17
+ ```typescript
18
+ import { tool } from 'axl';
19
+ import { z } from 'zod';
20
+
21
+ const calculator = tool({
22
+ name: 'calculator',
23
+ description: 'Evaluate arithmetic expressions',
24
+ input: z.object({ expression: z.string() }),
25
+ handler: ({ expression }) => {
26
+ const result = new Function(`return (${expression})`)();
27
+ return { result };
28
+ },
29
+ retry: { attempts: 3, backoff: 'exponential' },
30
+ sensitive: false,
31
+ });
32
+ ```
33
+
34
+ ### `agent(config)`
35
+
36
+ Define an agent with model, system prompt, tools, and handoffs:
37
+
38
+ ```typescript
39
+ import { agent } from 'axl';
40
+
41
+ const researcher = agent({
42
+ name: 'researcher',
43
+ model: 'openai:gpt-4o',
44
+ system: 'You are a research assistant.',
45
+ tools: [calculator],
46
+ maxTurns: 10,
47
+ timeout: '30s',
48
+ temperature: 0.7,
49
+ version: 'v1.2',
50
+ });
51
+ ```
52
+
53
+ Dynamic model and system prompt selection:
54
+
55
+ ```typescript
56
+ const dynamicAgent = agent({
57
+ model: (ctx) => ctx.metadata?.tier === 'premium'
58
+ ? 'openai:gpt-4o'
59
+ : 'openai:gpt-4.1-nano',
60
+ system: (ctx) => `You are a ${ctx.metadata?.role ?? 'general'} assistant.`,
61
+ });
62
+ ```
63
+
64
+ ### `workflow(config)`
65
+
66
+ Define a named workflow with typed input/output:
67
+
68
+ ```typescript
69
+ import { workflow } from 'axl';
70
+ import { z } from 'zod';
71
+
72
+ const myWorkflow = workflow({
73
+ name: 'my-workflow',
74
+ input: z.object({ query: z.string() }),
75
+ output: z.object({ answer: z.string() }),
76
+ handler: async (ctx) => {
77
+ const answer = await ctx.ask(researcher, ctx.input.query);
78
+ return { answer };
79
+ },
80
+ });
81
+ ```
82
+
83
+ ### `AxlRuntime`
84
+
85
+ Register and execute workflows:
86
+
87
+ ```typescript
88
+ import { AxlRuntime } from 'axl';
89
+
90
+ const runtime = new AxlRuntime();
91
+ runtime.register(myWorkflow);
92
+
93
+ // Execute
94
+ const result = await runtime.execute('my-workflow', { query: 'Hello' });
95
+
96
+ // Stream
97
+ const stream = runtime.stream('my-workflow', { query: 'Hello' });
98
+ for await (const event of stream) {
99
+ if (event.type === 'token') process.stdout.write(event.data);
100
+ }
101
+
102
+ // Sessions
103
+ const session = runtime.session('user-123');
104
+ await session.send('my-workflow', { query: 'Hello' });
105
+ await session.send('my-workflow', { query: 'Follow-up' });
106
+ const history = await session.history();
107
+ ```
108
+
109
+ ### Context Primitives
110
+
111
+ All available on `ctx` inside workflow handlers:
112
+
113
+ ```typescript
114
+ // Invoke an agent
115
+ const answer = await ctx.ask(agent, 'prompt', { schema, retries });
116
+
117
+ // Run N concurrent tasks
118
+ const results = await ctx.spawn(3, async (i) => ctx.ask(agent, prompts[i]));
119
+
120
+ // Consensus vote
121
+ const winner = ctx.vote(results, { strategy: 'majority', key: 'answer' });
122
+
123
+ // Self-correcting validation
124
+ const valid = await ctx.verify(
125
+ async (lastOutput, error) => ctx.ask(agent, prompt),
126
+ schema,
127
+ { retries: 3, fallback: defaultValue },
128
+ );
129
+
130
+ // Cost control
131
+ const budgeted = await ctx.budget({ cost: '$1.00', onExceed: 'hard_stop' }, async () => {
132
+ return ctx.ask(agent, prompt);
133
+ });
134
+
135
+ // First to complete
136
+ const fastest = await ctx.race([
137
+ () => ctx.ask(agentA, prompt),
138
+ () => ctx.ask(agentB, prompt),
139
+ ], { schema });
140
+
141
+ // Concurrent independent tasks
142
+ const [a, b] = await ctx.parallel([
143
+ () => ctx.ask(agentA, promptA),
144
+ () => ctx.ask(agentB, promptB),
145
+ ]);
146
+
147
+ // Map with bounded concurrency
148
+ const mapped = await ctx.map(items, async (item) => ctx.ask(agent, item), {
149
+ concurrency: 5,
150
+ quorum: 3,
151
+ });
152
+
153
+ // Human-in-the-loop
154
+ const decision = await ctx.awaitHuman({
155
+ channel: 'slack',
156
+ prompt: 'Approve this action?',
157
+ });
158
+
159
+ // Durable checkpoint
160
+ const value = await ctx.checkpoint(async () => expensiveOperation());
161
+ ```
162
+
163
+ ### OpenTelemetry Observability
164
+
165
+ Automatic span emission for every `ctx.*` primitive with cost-per-span attribution. Install `@opentelemetry/api` as an optional peer dependency.
166
+
167
+ ```typescript
168
+ import { defineConfig, AxlRuntime } from 'axl';
169
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
170
+
171
+ const config = defineConfig({
172
+ telemetry: {
173
+ enabled: true,
174
+ serviceName: 'my-app',
175
+ exporter: new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' }),
176
+ },
177
+ });
178
+
179
+ const runtime = new AxlRuntime(config);
180
+ runtime.initializeTelemetry();
181
+ ```
182
+
183
+ **Span model:** `axl.workflow.execute` > `axl.agent.ask` > `axl.tool.call`. Also: `axl.ctx.spawn`, `axl.ctx.race`, `axl.ctx.vote`, `axl.ctx.budget`, `axl.ctx.checkpoint`, `axl.ctx.awaitHuman`. Each span includes relevant attributes (cost, duration, token counts, etc.).
184
+
185
+ When disabled (default), `NoopSpanManager` provides zero overhead.
186
+
187
+ ```typescript
188
+ import { createSpanManager, NoopSpanManager } from 'axl';
189
+ ```
190
+
191
+ ### Memory Primitives
192
+
193
+ Working memory backed by the existing `StateStore` interface:
194
+
195
+ ```typescript
196
+ // Store and retrieve structured state
197
+ await ctx.remember('user-preferences', { theme: 'dark', lang: 'en' });
198
+ const prefs = await ctx.recall('user-preferences');
199
+ await ctx.forget('user-preferences');
200
+
201
+ // Scoped to session (default) or global
202
+ await ctx.remember('user-profile', data, { scope: 'global' });
203
+ const profile = await ctx.recall('user-profile', { scope: 'global' });
204
+ ```
205
+
206
+ Semantic recall requires a vector store and embedder configured on the runtime:
207
+
208
+ ```typescript
209
+ import { AxlRuntime, InMemoryVectorStore, OpenAIEmbedder } from 'axl';
210
+
211
+ const runtime = new AxlRuntime({
212
+ memory: {
213
+ vector: new InMemoryVectorStore(),
214
+ embedder: new OpenAIEmbedder({ model: 'text-embedding-3-small' }),
215
+ },
216
+ });
217
+
218
+ // In a workflow:
219
+ const relevant = await ctx.recall('knowledge-base', {
220
+ query: 'refund policy',
221
+ topK: 5,
222
+ });
223
+ ```
224
+
225
+ Vector store implementations: `InMemoryVectorStore` (testing), `SqliteVectorStore` (production, requires `better-sqlite3`).
226
+
227
+ ### Agent Guardrails
228
+
229
+ Input and output validation at the agent boundary:
230
+
231
+ ```typescript
232
+ const safe = agent({
233
+ model: 'openai:gpt-4o',
234
+ system: 'You are a helpful assistant.',
235
+ guardrails: {
236
+ input: async (prompt, ctx) => {
237
+ if (containsPII(prompt)) return { block: true, reason: 'PII detected' };
238
+ return { block: false };
239
+ },
240
+ output: async (response, ctx) => {
241
+ if (isOffTopic(response)) return { block: true, reason: 'Off-topic response' };
242
+ return { block: false };
243
+ },
244
+ onBlock: 'retry', // 'retry' | 'throw' | (reason, ctx) => fallbackResponse
245
+ maxRetries: 2,
246
+ },
247
+ });
248
+ ```
249
+
250
+ When `onBlock` is `'retry'`, the LLM sees the block reason and self-corrects (same pattern as `ctx.verify()`). Throws `GuardrailError` if retries are exhausted or `onBlock` is `'throw'`.
251
+
252
+ ### Session Options
253
+
254
+ ```typescript
255
+ const session = runtime.session('user-123', {
256
+ history: {
257
+ maxMessages: 100, // Trim oldest messages when exceeded
258
+ summarize: true, // Auto-summarize trimmed messages
259
+ },
260
+ persist: true, // Save to StateStore (default: true)
261
+ });
262
+ ```
263
+
264
+ `SessionOptions` type:
265
+
266
+ | Option | Type | Default | Description |
267
+ |--------|------|---------|-------------|
268
+ | `history.maxMessages` | `number` | unlimited | Max messages to retain |
269
+ | `history.summarize` | `boolean` | `false` | Summarize trimmed messages |
270
+ | `persist` | `boolean` | `true` | Persist history to StateStore |
271
+
272
+ ### Error Hierarchy
273
+
274
+ ```typescript
275
+ import {
276
+ AxlError, // Base class
277
+ VerifyError, // Schema validation failed after retries
278
+ QuorumNotMet, // Quorum threshold not reached
279
+ NoConsensus, // Vote could not reach consensus
280
+ TimeoutError, // Operation exceeded timeout
281
+ MaxTurnsError, // Agent exceeded max tool-calling turns
282
+ BudgetExceededError, // Budget limit exceeded
283
+ GuardrailError, // Guardrail blocked input or output
284
+ ToolDenied, // Agent tried to call unauthorized tool
285
+ } from 'axl';
286
+ ```
287
+
288
+ ### State Stores
289
+
290
+ ```typescript
291
+ import { MemoryStore, SQLiteStore, RedisStore } from 'axl';
292
+
293
+ // In-memory (default)
294
+ const runtime = new AxlRuntime();
295
+
296
+ // SQLite (requires better-sqlite3)
297
+ const runtime = new AxlRuntime({
298
+ state: { store: 'sqlite', sqlite: { path: './data/axl.db' } },
299
+ });
300
+
301
+ // Redis (requires ioredis)
302
+ const runtime = new AxlRuntime({
303
+ state: { store: 'redis', redis: { url: 'redis://localhost:6379' } },
304
+ });
305
+ ```
306
+
307
+ ### Provider URIs
308
+
309
+ Four built-in providers are supported:
310
+
311
+ ```
312
+ # OpenAI — Chat Completions API
313
+ openai:gpt-4o # Flagship multimodal
314
+ openai:gpt-4o-mini # Fast and affordable
315
+ openai:gpt-4.1 # GPT-4.1
316
+ openai:gpt-4.1-mini # GPT-4.1 small
317
+ openai:gpt-4.1-nano # GPT-4.1 cheapest
318
+ openai:gpt-5 # GPT-5
319
+ openai:gpt-5-mini # GPT-5 small
320
+ openai:gpt-5-nano # GPT-5 cheapest
321
+ openai:gpt-5.1 # GPT-5.1
322
+ openai:gpt-5.2 # GPT-5.2
323
+ openai:o1 # Reasoning
324
+ openai:o1-mini # Reasoning (small)
325
+ openai:o1-pro # Reasoning (pro)
326
+ openai:o3 # Reasoning
327
+ openai:o3-mini # Reasoning (small)
328
+ openai:o3-pro # Reasoning (pro)
329
+ openai:o4-mini # Reasoning (small)
330
+ openai:gpt-4-turbo # Legacy
331
+ openai:gpt-4 # Legacy
332
+ openai:gpt-3.5-turbo # Legacy
333
+
334
+ # OpenAI — Responses API (same models, better caching, native reasoning)
335
+ openai-responses:gpt-4o
336
+ openai-responses:o3
337
+
338
+ # Anthropic
339
+ anthropic:claude-opus-4-6 # Most capable
340
+ anthropic:claude-sonnet-4-5 # Balanced
341
+ anthropic:claude-haiku-4-5 # Fast and affordable
342
+ anthropic:claude-sonnet-4 # Previous gen
343
+ anthropic:claude-opus-4 # Previous gen
344
+ anthropic:claude-3-7-sonnet # Legacy
345
+ anthropic:claude-3-5-sonnet # Legacy
346
+ anthropic:claude-3-5-haiku # Legacy
347
+ anthropic:claude-3-opus # Legacy
348
+ anthropic:claude-3-sonnet # Legacy
349
+ anthropic:claude-3-haiku # Legacy
350
+
351
+ # Google Gemini
352
+ google:gemini-2.5-pro # Most capable
353
+ google:gemini-2.5-flash # Fast
354
+ google:gemini-2.5-flash-lite # Cheapest 2.5
355
+ google:gemini-2.0-flash # Previous gen
356
+ google:gemini-2.0-flash-lite # Previous gen (lite)
357
+ google:gemini-3-pro-preview # Next gen (preview)
358
+ google:gemini-3-flash-preview # Next gen fast (preview)
359
+ ```
360
+
361
+ ## License
362
+
363
+ [Apache 2.0](../../LICENSE)
@@ -0,0 +1,79 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/telemetry/span-manager.ts
9
+ var OTelSpanManager = class _OTelSpanManager {
10
+ tracer;
11
+ otelApi;
12
+ tracerProvider;
13
+ constructor(otelApi, tracer, tracerProvider) {
14
+ this.otelApi = otelApi;
15
+ this.tracer = tracer;
16
+ this.tracerProvider = tracerProvider;
17
+ }
18
+ static async create(config) {
19
+ let otelApi;
20
+ try {
21
+ otelApi = await import("@opentelemetry/api");
22
+ } catch {
23
+ throw new Error(
24
+ "@opentelemetry/api is required for telemetry. Install it with: npm install @opentelemetry/api"
25
+ );
26
+ }
27
+ const tracerProvider = config.tracerProvider ?? otelApi.trace.getTracerProvider();
28
+ const serviceName = config.serviceName ?? "axl";
29
+ const tracer = tracerProvider.getTracer(serviceName);
30
+ return new _OTelSpanManager(otelApi, tracer, tracerProvider);
31
+ }
32
+ async withSpanAsync(name, attributes, fn) {
33
+ const otelApi = this.otelApi;
34
+ return this.tracer.startActiveSpan(name, { attributes }, async (otelSpan) => {
35
+ const handle = {
36
+ setAttribute(key, value) {
37
+ otelSpan.setAttribute(key, value);
38
+ },
39
+ addEvent(eventName, attrs) {
40
+ otelSpan.addEvent(eventName, attrs);
41
+ },
42
+ setStatus(code, message) {
43
+ const statusCode = code === "ok" ? otelApi.SpanStatusCode.OK : otelApi.SpanStatusCode.ERROR;
44
+ otelSpan.setStatus({ code: statusCode, message });
45
+ },
46
+ end() {
47
+ otelSpan.end();
48
+ }
49
+ };
50
+ try {
51
+ const result = await fn(handle);
52
+ handle.setStatus("ok");
53
+ return result;
54
+ } catch (err) {
55
+ handle.setStatus("error", err instanceof Error ? err.message : String(err));
56
+ throw err;
57
+ } finally {
58
+ handle.end();
59
+ }
60
+ });
61
+ }
62
+ addEventToActiveSpan(name, attributes) {
63
+ const activeSpan = this.otelApi.trace.getActiveSpan?.();
64
+ if (activeSpan) {
65
+ activeSpan.addEvent(name, attributes);
66
+ }
67
+ }
68
+ async shutdown() {
69
+ if (this.tracerProvider && typeof this.tracerProvider.shutdown === "function") {
70
+ await this.tracerProvider.shutdown();
71
+ }
72
+ }
73
+ };
74
+
75
+ export {
76
+ __require,
77
+ OTelSpanManager
78
+ };
79
+ //# sourceMappingURL=chunk-EE2BCC37.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/telemetry/span-manager.ts"],"sourcesContent":["import type { SpanHandle, SpanManager, TelemetryConfig } from './types.js';\n\n/**\n * OpenTelemetry-backed SpanManager.\n * Dynamically imports @opentelemetry/api to avoid hard dependency.\n */\nexport class OTelSpanManager implements SpanManager {\n private tracer: any;\n private otelApi: any;\n private tracerProvider: any;\n\n private constructor(otelApi: any, tracer: any, tracerProvider: any) {\n this.otelApi = otelApi;\n this.tracer = tracer;\n this.tracerProvider = tracerProvider;\n }\n\n static async create(config: TelemetryConfig): Promise<OTelSpanManager> {\n let otelApi: any;\n try {\n otelApi = await import('@opentelemetry/api');\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 = config.tracerProvider ?? otelApi.trace.getTracerProvider();\n const serviceName = config.serviceName ?? 'axl';\n const tracer = (tracerProvider as any).getTracer(serviceName);\n\n return new OTelSpanManager(otelApi, tracer, tracerProvider);\n }\n\n async withSpanAsync<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: (span: SpanHandle) => Promise<T>,\n ): Promise<T> {\n const otelApi = this.otelApi;\n\n return this.tracer.startActiveSpan(name, { attributes }, async (otelSpan: any) => {\n const handle: SpanHandle = {\n setAttribute(key: string, value: string | number | boolean) {\n otelSpan.setAttribute(key, value);\n },\n addEvent(eventName: string, attrs?: Record<string, string | number | boolean>) {\n otelSpan.addEvent(eventName, attrs);\n },\n setStatus(code: 'ok' | 'error', message?: string) {\n const statusCode =\n code === 'ok' ? otelApi.SpanStatusCode.OK : otelApi.SpanStatusCode.ERROR;\n otelSpan.setStatus({ code: statusCode, message });\n },\n end() {\n otelSpan.end();\n },\n };\n\n try {\n const result = await fn(handle);\n handle.setStatus('ok');\n return result;\n } catch (err) {\n handle.setStatus('error', err instanceof Error ? err.message : String(err));\n throw err;\n } finally {\n handle.end();\n }\n });\n }\n\n addEventToActiveSpan(name: string, attributes?: Record<string, string | number | boolean>): void {\n const activeSpan = this.otelApi.trace.getActiveSpan?.();\n if (activeSpan) {\n activeSpan.addEvent(name, attributes);\n }\n }\n\n async shutdown(): Promise<void> {\n if (this.tracerProvider && typeof this.tracerProvider.shutdown === 'function') {\n await this.tracerProvider.shutdown();\n }\n }\n}\n"],"mappings":";;;;;;;;AAMO,IAAM,kBAAN,MAAM,iBAAuC;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,SAAc,QAAa,gBAAqB;AAClE,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,aAAa,OAAO,QAAmD;AACrE,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,OAAO,oBAAoB;AAAA,IAC7C,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,iBAAiB,OAAO,kBAAkB,QAAQ,MAAM,kBAAkB;AAChF,UAAM,cAAc,OAAO,eAAe;AAC1C,UAAM,SAAU,eAAuB,UAAU,WAAW;AAE5D,WAAO,IAAI,iBAAgB,SAAS,QAAQ,cAAc;AAAA,EAC5D;AAAA,EAEA,MAAM,cACJ,MACA,YACA,IACY;AACZ,UAAM,UAAU,KAAK;AAErB,WAAO,KAAK,OAAO,gBAAgB,MAAM,EAAE,WAAW,GAAG,OAAO,aAAkB;AAChF,YAAM,SAAqB;AAAA,QACzB,aAAa,KAAa,OAAkC;AAC1D,mBAAS,aAAa,KAAK,KAAK;AAAA,QAClC;AAAA,QACA,SAAS,WAAmB,OAAmD;AAC7E,mBAAS,SAAS,WAAW,KAAK;AAAA,QACpC;AAAA,QACA,UAAU,MAAsB,SAAkB;AAChD,gBAAM,aACJ,SAAS,OAAO,QAAQ,eAAe,KAAK,QAAQ,eAAe;AACrE,mBAAS,UAAU,EAAE,MAAM,YAAY,QAAQ,CAAC;AAAA,QAClD;AAAA,QACA,MAAM;AACJ,mBAAS,IAAI;AAAA,QACf;AAAA,MACF;AAEA,UAAI;AACF,cAAM,SAAS,MAAM,GAAG,MAAM;AAC9B,eAAO,UAAU,IAAI;AACrB,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,eAAO,UAAU,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC1E,cAAM;AAAA,MACR,UAAE;AACA,eAAO,IAAI;AAAA,MACb;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,qBAAqB,MAAc,YAA8D;AAC/F,UAAM,aAAa,KAAK,QAAQ,MAAM,gBAAgB;AACtD,QAAI,YAAY;AACd,iBAAW,SAAS,MAAM,UAAU;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,MAAM,WAA0B;AAC9B,QAAI,KAAK,kBAAkB,OAAO,KAAK,eAAe,aAAa,YAAY;AAC7E,YAAM,KAAK,eAAe,SAAS;AAAA,IACrC;AAAA,EACF;AACF;","names":[]}