@axlsdk/axl 0.17.4 → 0.17.6

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/dist/index.cjs CHANGED
@@ -163,6 +163,7 @@ __export(index_exports, {
163
163
  registerConditions: () => registerConditions,
164
164
  resolveRuntime: () => resolveRuntime,
165
165
  resolveThinkingOptions: () => resolveThinkingOptions,
166
+ stringStreamFromEvents: () => stringStreamFromEvents,
166
167
  tool: () => tool,
167
168
  workflow: () => workflow,
168
169
  zodToJsonSchema: () => zodToJsonSchema
@@ -1006,6 +1007,7 @@ var OpenAIResponsesProvider = class {
1006
1007
  // src/providers/anthropic.ts
1007
1008
  var ANTHROPIC_API_VERSION = "2023-06-01";
1008
1009
  var ANTHROPIC_PRICING = {
1010
+ "claude-opus-4-7": [5e-6, 25e-6],
1009
1011
  "claude-opus-4-6": [5e-6, 25e-6],
1010
1012
  "claude-sonnet-4-6": [3e-6, 15e-6],
1011
1013
  "claude-opus-4-5": [5e-6, 25e-6],
@@ -1050,13 +1052,21 @@ var THINKING_BUDGETS = {
1050
1052
  max: 3e4
1051
1053
  };
1052
1054
  function supportsAdaptiveThinking(model) {
1053
- return model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6");
1055
+ return model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6");
1054
1056
  }
1055
1057
  function supportsMaxEffort(model) {
1056
- return model.startsWith("claude-opus-4-6");
1058
+ return model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6");
1059
+ }
1060
+ function supportsXhigh2(model) {
1061
+ return model.startsWith("claude-opus-4-7");
1057
1062
  }
1058
1063
  function supportsEffort(model) {
1059
- return model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6") || model.startsWith("claude-opus-4-5");
1064
+ return model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6") || model.startsWith("claude-opus-4-5");
1065
+ }
1066
+ function clampAnthropicEffort(model, effort) {
1067
+ if (effort === "max" && !supportsMaxEffort(model)) return "high";
1068
+ if (effort === "xhigh" && !supportsXhigh2(model)) return "high";
1069
+ return effort;
1060
1070
  }
1061
1071
  var AnthropicProvider = class {
1062
1072
  name = "anthropic";
@@ -1157,10 +1167,7 @@ var AnthropicProvider = class {
1157
1167
  body.tool_choice = this.mapToolChoice(options.toolChoice);
1158
1168
  }
1159
1169
  const { thinkingBudget, thinkingDisabled, activeEffort, hasBudgetOverride } = resolveThinkingOptions(options);
1160
- let resolvedEffort = activeEffort;
1161
- if (resolvedEffort === "max" && !supportsMaxEffort(options.model)) {
1162
- resolvedEffort = "high";
1163
- }
1170
+ const resolvedEffort = activeEffort ? clampAnthropicEffort(options.model, activeEffort) : void 0;
1164
1171
  if (hasBudgetOverride) {
1165
1172
  body.thinking = { type: "enabled", budget_tokens: thinkingBudget };
1166
1173
  const currentMax = body.max_tokens;
@@ -1527,6 +1534,7 @@ var GEMINI_PRICING = {
1527
1534
  "gemini-3-pro-preview": [2e-6, 12e-6],
1528
1535
  "gemini-3-flash-preview": [5e-7, 3e-6],
1529
1536
  "gemini-3.1-pro-preview": [2e-6, 12e-6],
1537
+ "gemini-3.1-flash-lite": [25e-8, 15e-7],
1530
1538
  "gemini-3.1-flash-lite-preview": [25e-8, 15e-7]
1531
1539
  };
1532
1540
  var GEMINI_PRICING_KEYS_BY_LENGTH = Object.keys(GEMINI_PRICING).sort(
@@ -1552,12 +1560,16 @@ var THINKING_BUDGETS2 = {
1552
1560
  low: 1024,
1553
1561
  medium: 5e3,
1554
1562
  high: 1e4,
1563
+ xhigh: 16384,
1564
+ // between high (10000) and max (24576)
1555
1565
  max: 24576
1556
1566
  };
1557
1567
  var THINKING_LEVELS = {
1558
1568
  low: "low",
1559
1569
  medium: "medium",
1560
1570
  high: "high",
1571
+ xhigh: "high",
1572
+ // 3.x has no xhigh tier; clamp to 'high'
1561
1573
  max: "high"
1562
1574
  // 3.x caps at 'high'
1563
1575
  };
@@ -2504,6 +2516,314 @@ var PartialParser = class {
2504
2516
  }
2505
2517
  };
2506
2518
 
2519
+ // src/streaming-walker.ts
2520
+ var MAX_DEPTH2 = 256;
2521
+ function encodeSegment(s) {
2522
+ if (typeof s === "number") return String(s);
2523
+ return s.replace(/~/g, "~0").replace(/\//g, "~1");
2524
+ }
2525
+ function computePath(stack) {
2526
+ let path = "";
2527
+ for (const frame of stack) {
2528
+ if (frame.type === "object") {
2529
+ path += "/" + encodeSegment(frame.currentKey ?? "");
2530
+ } else {
2531
+ path += "/" + encodeSegment(frame.currentIndex);
2532
+ }
2533
+ }
2534
+ return path;
2535
+ }
2536
+ var StreamingWalker = class {
2537
+ constructor(callbacks) {
2538
+ this.callbacks = callbacks;
2539
+ }
2540
+ state = "pre-json";
2541
+ stack = [];
2542
+ // String parsing state — survives across chunks so escapes can split.
2543
+ escaped = false;
2544
+ /** When > 0, we're collecting `\\uXXXX` hex digits. 4→0 as digits arrive. */
2545
+ unicodePending = 0;
2546
+ /** Accumulator for `\\uXXXX` digits. Cleared when the escape resolves. */
2547
+ unicodeBuf = "";
2548
+ // Per-string accumulators.
2549
+ keyBuf = "";
2550
+ deltaBuf = "";
2551
+ /** Cached JSON Pointer to the value currently being read. Recomputed
2552
+ * on entry to `read-value-string` (the only state that needs it). */
2553
+ currentPath = "";
2554
+ /** Set true when a structural boundary lands. Caller reads via
2555
+ * `consumeBoundary()` after each chunk to decide on partial_object emit. */
2556
+ boundaryPending = false;
2557
+ /** Walk `text` char-by-char, advancing state and accumulating deltas.
2558
+ * Calls `onStringDelta` at end-of-chunk for any pending value text. */
2559
+ processChunk(text) {
2560
+ for (let i = 0; i < text.length; i++) {
2561
+ if (this.state === "done") return;
2562
+ this.processChar(text[i]);
2563
+ }
2564
+ if (this.deltaBuf.length > 0 && this.state === "read-value-string") {
2565
+ this.callbacks.onStringDelta?.(this.currentPath, this.deltaBuf);
2566
+ this.deltaBuf = "";
2567
+ }
2568
+ }
2569
+ /** Read-and-clear the structural-boundary flag. Caller checks this
2570
+ * after `processChunk` to decide whether to emit `partial_object`. */
2571
+ consumeBoundary() {
2572
+ const v = this.boundaryPending;
2573
+ this.boundaryPending = false;
2574
+ return v;
2575
+ }
2576
+ /** True when the walker has decided it's done with this stream
2577
+ * (root closed or fatal malformed input). Caller may use this to
2578
+ * short-circuit further work. */
2579
+ get isDone() {
2580
+ return this.state === "done";
2581
+ }
2582
+ // ── Per-char dispatch ───────────────────────────────────────────────────
2583
+ processChar(ch) {
2584
+ switch (this.state) {
2585
+ case "pre-json":
2586
+ if (ch === "{") {
2587
+ if (!this.pushFrame({ type: "object" })) return;
2588
+ this.state = "expect-key-or-close";
2589
+ }
2590
+ return;
2591
+ case "expect-key-or-close":
2592
+ if (isWhitespace(ch)) return;
2593
+ if (ch === "}") {
2594
+ this.popFrame();
2595
+ return;
2596
+ }
2597
+ if (ch === '"') {
2598
+ this.keyBuf = "";
2599
+ this.escaped = false;
2600
+ this.unicodePending = 0;
2601
+ this.unicodeBuf = "";
2602
+ this.state = "read-key";
2603
+ return;
2604
+ }
2605
+ this.state = "done";
2606
+ return;
2607
+ case "read-key":
2608
+ this.processStringChar(
2609
+ ch,
2610
+ /* isValue */
2611
+ false
2612
+ );
2613
+ return;
2614
+ case "expect-colon":
2615
+ if (isWhitespace(ch)) return;
2616
+ if (ch === ":") {
2617
+ this.state = "expect-value";
2618
+ return;
2619
+ }
2620
+ this.state = "done";
2621
+ return;
2622
+ case "expect-value":
2623
+ if (isWhitespace(ch)) return;
2624
+ if (ch === "{") {
2625
+ if (!this.pushFrame({ type: "object" })) return;
2626
+ this.state = "expect-key-or-close";
2627
+ return;
2628
+ }
2629
+ if (ch === "[") {
2630
+ if (!this.pushFrame({ type: "array", currentIndex: 0 })) return;
2631
+ this.state = "expect-value";
2632
+ return;
2633
+ }
2634
+ if (ch === "]") {
2635
+ this.popFrame();
2636
+ return;
2637
+ }
2638
+ if (ch === '"') {
2639
+ this.currentPath = computePath(this.stack);
2640
+ this.deltaBuf = "";
2641
+ this.escaped = false;
2642
+ this.unicodePending = 0;
2643
+ this.unicodeBuf = "";
2644
+ this.state = "read-value-string";
2645
+ return;
2646
+ }
2647
+ if (isValueStartChar(ch)) {
2648
+ this.state = "read-value-other";
2649
+ return;
2650
+ }
2651
+ this.state = "done";
2652
+ return;
2653
+ case "read-value-string":
2654
+ this.processStringChar(
2655
+ ch,
2656
+ /* isValue */
2657
+ true
2658
+ );
2659
+ return;
2660
+ case "read-value-other":
2661
+ if (ch === "," || ch === "}" || ch === "]" || isWhitespace(ch)) {
2662
+ this.state = "expect-comma-close";
2663
+ this.processChar(ch);
2664
+ return;
2665
+ }
2666
+ return;
2667
+ case "expect-comma-close":
2668
+ if (isWhitespace(ch)) return;
2669
+ if (ch === ",") {
2670
+ this.boundaryPending = true;
2671
+ const top = this.stack[this.stack.length - 1];
2672
+ if (!top) {
2673
+ this.state = "done";
2674
+ return;
2675
+ }
2676
+ if (top.type === "object") {
2677
+ top.currentKey = void 0;
2678
+ this.state = "expect-key-or-close";
2679
+ } else {
2680
+ top.currentIndex += 1;
2681
+ this.state = "expect-value";
2682
+ }
2683
+ return;
2684
+ }
2685
+ if (ch === "}" || ch === "]") {
2686
+ this.popFrame();
2687
+ return;
2688
+ }
2689
+ this.state = "done";
2690
+ return;
2691
+ case "done":
2692
+ return;
2693
+ }
2694
+ }
2695
+ // ── String state machine (shared between key and value reads) ───────────
2696
+ /**
2697
+ * One char inside a string. Translates JSON escapes, accumulates
2698
+ * `\\uXXXX` digits across chunks, and routes the resolved char to
2699
+ * either `keyBuf` (when reading a key) or `deltaBuf` (when reading a
2700
+ * value).
2701
+ */
2702
+ processStringChar(ch, isValue) {
2703
+ if (this.unicodePending > 0) {
2704
+ if (isHex(ch)) {
2705
+ this.unicodeBuf += ch;
2706
+ this.unicodePending -= 1;
2707
+ if (this.unicodePending === 0) {
2708
+ const code = parseInt(this.unicodeBuf, 16);
2709
+ this.unicodeBuf = "";
2710
+ this.appendChar(String.fromCharCode(code), isValue);
2711
+ }
2712
+ } else {
2713
+ this.state = "done";
2714
+ }
2715
+ return;
2716
+ }
2717
+ if (this.escaped) {
2718
+ this.escaped = false;
2719
+ switch (ch) {
2720
+ case '"':
2721
+ this.appendChar('"', isValue);
2722
+ return;
2723
+ case "\\":
2724
+ this.appendChar("\\", isValue);
2725
+ return;
2726
+ case "/":
2727
+ this.appendChar("/", isValue);
2728
+ return;
2729
+ case "b":
2730
+ this.appendChar("\b", isValue);
2731
+ return;
2732
+ case "f":
2733
+ this.appendChar("\f", isValue);
2734
+ return;
2735
+ case "n":
2736
+ this.appendChar("\n", isValue);
2737
+ return;
2738
+ case "r":
2739
+ this.appendChar("\r", isValue);
2740
+ return;
2741
+ case "t":
2742
+ this.appendChar(" ", isValue);
2743
+ return;
2744
+ case "u":
2745
+ this.unicodePending = 4;
2746
+ this.unicodeBuf = "";
2747
+ return;
2748
+ default:
2749
+ this.state = "done";
2750
+ return;
2751
+ }
2752
+ }
2753
+ if (ch === "\\") {
2754
+ this.escaped = true;
2755
+ return;
2756
+ }
2757
+ if (ch === '"') {
2758
+ if (isValue) {
2759
+ if (this.deltaBuf.length > 0) {
2760
+ this.callbacks.onStringDelta?.(this.currentPath, this.deltaBuf);
2761
+ this.deltaBuf = "";
2762
+ }
2763
+ this.state = "expect-comma-close";
2764
+ } else {
2765
+ const top = this.stack[this.stack.length - 1];
2766
+ if (!top || top.type !== "object") {
2767
+ this.state = "done";
2768
+ return;
2769
+ }
2770
+ top.currentKey = this.keyBuf;
2771
+ this.keyBuf = "";
2772
+ this.state = "expect-colon";
2773
+ }
2774
+ return;
2775
+ }
2776
+ this.appendChar(ch, isValue);
2777
+ }
2778
+ appendChar(c, isValue) {
2779
+ if (isValue) {
2780
+ this.deltaBuf += c;
2781
+ } else {
2782
+ this.keyBuf += c;
2783
+ }
2784
+ }
2785
+ // ── Frame stack ─────────────────────────────────────────────────────────
2786
+ /** Push a frame onto the stack. Returns false if the stack is at the
2787
+ * depth cap — in which case the walker is also moved to `done` so the
2788
+ * caller's subsequent state assignment must be guarded. */
2789
+ pushFrame(frame) {
2790
+ if (this.stack.length >= MAX_DEPTH2) {
2791
+ this.state = "done";
2792
+ return false;
2793
+ }
2794
+ this.stack.push(frame);
2795
+ return true;
2796
+ }
2797
+ /** Pop a frame on `}` or `]`, fire boundary, transition to next state.
2798
+ * Defensive on empty stack — the state machine never invokes this
2799
+ * without an enclosing container today (callers gate on state, and
2800
+ * state transitions out of `pre-json` / `done` only after pushing),
2801
+ * but a future state-machine bug could land us here with nothing to
2802
+ * pop. Fail closed (move to `done`, no spurious boundary fire). */
2803
+ popFrame() {
2804
+ if (this.stack.length === 0) {
2805
+ this.state = "done";
2806
+ return;
2807
+ }
2808
+ this.stack.pop();
2809
+ this.boundaryPending = true;
2810
+ if (this.stack.length === 0) {
2811
+ this.state = "done";
2812
+ } else {
2813
+ this.state = "expect-comma-close";
2814
+ }
2815
+ }
2816
+ };
2817
+ function isWhitespace(ch) {
2818
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r";
2819
+ }
2820
+ function isHex(ch) {
2821
+ return ch >= "0" && ch <= "9" || ch >= "a" && ch <= "f" || ch >= "A" && ch <= "F";
2822
+ }
2823
+ function isValueStartChar(ch) {
2824
+ return ch === "-" || ch >= "0" && ch <= "9" || ch === "t" || ch === "f" || ch === "n";
2825
+ }
2826
+
2507
2827
  // src/event-utils.ts
2508
2828
  var COST_BEARING_LEAF_TYPES = [
2509
2829
  "agent_call_end",
@@ -2560,6 +2880,12 @@ var AXL_EVENT_TYPES = [
2560
2880
  "pipeline",
2561
2881
  // Progressive structured output — added in PR 2; reserved here.
2562
2882
  "partial_object",
2883
+ // Character-level deltas inside string values of progressive structured
2884
+ // output. Same gating as `partial_object` (schema set, no tools, root is
2885
+ // ZodObject); fires per text-delta chunk when the walker is inside a
2886
+ // string value at a known JSON Pointer path. Designed for chat-style
2887
+ // typewriter rendering of long string fields.
2888
+ "string_delta",
2563
2889
  // Verification
2564
2890
  "verify",
2565
2891
  // Observability
@@ -2657,6 +2983,27 @@ var AxlEventBus = class {
2657
2983
  * number of distinct askIds that emitted at least one partial.
2658
2984
  */
2659
2985
  latestPartialByAsk = /* @__PURE__ */ new Map();
2986
+ /**
2987
+ * Per-ask, per-path running accumulator for `string_delta` events,
2988
+ * updated on every `_push(string_delta)` regardless of consumer state.
2989
+ * Used to seed late `stringStream` subscribers with the current text-
2990
+ * so-far per (askId, path) pair, so a UI binding to `/summary` mid-
2991
+ * stream renders the field's current state on first event instead of
2992
+ * waiting for the next chunk.
2993
+ *
2994
+ * Cleared per-askId on `pipeline(failed)` (discarded attempt — old
2995
+ * deltas must not leak into the next attempt's view) and on `ask_end`
2996
+ * (frees memory; stream-event accumulators don't outlive the ask).
2997
+ *
2998
+ * Memory bound: O(active asks × distinct paths × total chars per
2999
+ * field). For typical chat workflows (1 ask, 1-3 streaming string
3000
+ * fields) this is a few KB. The clear-on-`ask_end` cap keeps a
3001
+ * long-running runtime from accumulating per-ask entries forever
3002
+ * (the way `latestPartialByAsk` does — partial snapshots are tiny by
3003
+ * comparison so that map's growth is acceptable; string accumulators
3004
+ * can be 4 KB+ each, hence the more aggressive cleanup here).
3005
+ */
3006
+ stringStreamByAsk = /* @__PURE__ */ new Map();
2660
3007
  /**
2661
3008
  * Hook fired when an iterator obtained from `[Symbol.asyncIterator]`
2662
3009
  * is `await using`-disposed. Set via the second constructor argument.
@@ -2960,6 +3307,141 @@ var AxlEventBus = class {
2960
3307
  }
2961
3308
  };
2962
3309
  }
3310
+ /**
3311
+ * Listener-based view over `string_delta` events with optional
3312
+ * filtering by `path` and/or `askId`. Yields `StringStreamEvent`s
3313
+ * carrying `delta` (the new chars) and `accumulated` (the full text-
3314
+ * so-far for that ask + path).
3315
+ *
3316
+ * Designed for chat-style typewriter rendering of long string fields:
3317
+ * a React component binding to `path: '/summary'` sets its text to
3318
+ * `event.accumulated` on every yield, getting smooth char-by-char
3319
+ * progression with no parser of its own.
3320
+ *
3321
+ * Late-subscriber seeding: at subscribe time, the bus's per-ask
3322
+ * accumulator is walked and one synthetic `StringStreamEvent` is
3323
+ * yielded per matching (askId, path) entry, with `delta ===
3324
+ * accumulated === <full text-so-far>`. After that, live deltas flow
3325
+ * normally. A consumer who attaches mid-ask sees the field's current
3326
+ * state on first iteration, then incremental updates.
3327
+ *
3328
+ * Like `partialObjects`, this view is listener-based — it does NOT
3329
+ * race the main async iterator (`for await (const e of bus)`).
3330
+ * Running both concurrently each receives every matching event.
3331
+ *
3332
+ * Schema-retry awareness: on `pipeline(status: 'failed')`, the
3333
+ * accumulator and any pending events for the affected ask are
3334
+ * dropped, so a consumer iterating during attempt-N never observes
3335
+ * stale text after attempt-N+1 begins. Mirrors `partialObjects`'
3336
+ * per-attempt reset.
3337
+ *
3338
+ * Termination: yields any pending events first (live + seeded), then
3339
+ * `done: true` once the bus is finished via `_finish()`.
3340
+ */
3341
+ stringStream(opts) {
3342
+ const self = this;
3343
+ const filterPath = opts?.path;
3344
+ const filterAskId = opts?.askId;
3345
+ if (filterPath !== void 0 && filterPath !== "" && !filterPath.startsWith("/")) {
3346
+ throw new Error(
3347
+ `AxlEventBus.stringStream({ path }) \u2014 path must start with '/' (RFC 6901 JSON Pointer); got ${JSON.stringify(filterPath)}. Examples: '/summary', '/sources/0/title'.`
3348
+ );
3349
+ }
3350
+ return {
3351
+ [Symbol.asyncIterator]() {
3352
+ const pending = [];
3353
+ let resolveWaiter = null;
3354
+ let finished = self.finished;
3355
+ let unsubscribed = false;
3356
+ const wakeWaiter = () => {
3357
+ const r = resolveWaiter;
3358
+ resolveWaiter = null;
3359
+ r?.();
3360
+ };
3361
+ const matchesFilter = (askId, path) => {
3362
+ if (filterAskId !== void 0 && askId !== filterAskId) return false;
3363
+ if (filterPath !== void 0 && path !== filterPath) return false;
3364
+ return true;
3365
+ };
3366
+ for (const [askId, paths] of self.stringStreamByAsk) {
3367
+ for (const [path, acc] of paths) {
3368
+ if (!matchesFilter(askId, path)) continue;
3369
+ pending.push({
3370
+ askId,
3371
+ agent: acc.agent,
3372
+ path,
3373
+ delta: acc.text,
3374
+ accumulated: acc.text,
3375
+ attempt: acc.attempt
3376
+ });
3377
+ }
3378
+ }
3379
+ const handler = (event) => {
3380
+ if (event.type !== "string_delta") return;
3381
+ const askId = event.askId ?? "";
3382
+ if (!matchesFilter(askId, event.data.path)) return;
3383
+ const paths = self.stringStreamByAsk.get(askId);
3384
+ const acc = paths?.get(event.data.path);
3385
+ const accumulated = acc?.text ?? event.data.delta;
3386
+ pending.push({
3387
+ askId,
3388
+ agent: event.agent,
3389
+ path: event.data.path,
3390
+ delta: event.data.delta,
3391
+ accumulated,
3392
+ attempt: event.attempt
3393
+ });
3394
+ wakeWaiter();
3395
+ };
3396
+ self.bus.on("string_delta", handler);
3397
+ const pipelineHandler = (event) => {
3398
+ if (event.type !== "pipeline") return;
3399
+ if (event.status !== "failed") return;
3400
+ const askId = event.askId ?? "";
3401
+ for (let i = pending.length - 1; i >= 0; i--) {
3402
+ if (pending[i].askId === askId) pending.splice(i, 1);
3403
+ }
3404
+ };
3405
+ self.bus.on("pipeline", pipelineHandler);
3406
+ const unsubscribe = () => {
3407
+ if (unsubscribed) return;
3408
+ unsubscribed = true;
3409
+ self.bus.off("string_delta", handler);
3410
+ self.bus.off("pipeline", pipelineHandler);
3411
+ unsubscribeFinish();
3412
+ };
3413
+ const unsubscribeFinish = self._onFinish(() => {
3414
+ finished = true;
3415
+ self.bus.off("string_delta", handler);
3416
+ self.bus.off("pipeline", pipelineHandler);
3417
+ wakeWaiter();
3418
+ });
3419
+ return {
3420
+ async next() {
3421
+ while (true) {
3422
+ if (pending.length > 0) {
3423
+ return { value: pending.shift(), done: false };
3424
+ }
3425
+ if (finished) {
3426
+ unsubscribe();
3427
+ return { value: void 0, done: true };
3428
+ }
3429
+ await new Promise((res) => {
3430
+ resolveWaiter = res;
3431
+ });
3432
+ }
3433
+ },
3434
+ async return() {
3435
+ unsubscribe();
3436
+ return { value: void 0, done: true };
3437
+ },
3438
+ [Symbol.asyncIterator]() {
3439
+ return this;
3440
+ }
3441
+ };
3442
+ }
3443
+ };
3444
+ }
2963
3445
  /** Iterator over structural lifecycle events only — skips per-token chatter
2964
3446
  * and progressive `partial_object` emissions. Useful for waterfall UIs
2965
3447
  * and any consumer that wants the "what happened" timeline. */
@@ -2996,14 +3478,6 @@ var AxlEventBus = class {
2996
3478
  */
2997
3479
  _push(event) {
2998
3480
  if (this.finished) return;
2999
- try {
3000
- this.bus.emit(event.type, event);
3001
- } catch (err) {
3002
- console.error(
3003
- `[axl] AxlEventBus listener for "${event.type}" threw; ignoring:`,
3004
- err instanceof Error ? err.message : String(err)
3005
- );
3006
- }
3007
3481
  if (event.type === "partial_object" && event.data?.object !== void 0) {
3008
3482
  const askId = event.askId ?? "";
3009
3483
  this.latestPartialByAsk.set(askId, {
@@ -3012,8 +3486,33 @@ var AxlEventBus = class {
3012
3486
  object: event.data.object,
3013
3487
  attempt: event.attempt
3014
3488
  });
3489
+ } else if (event.type === "string_delta") {
3490
+ const askId = event.askId ?? "";
3491
+ let paths = this.stringStreamByAsk.get(askId);
3492
+ if (!paths) {
3493
+ paths = /* @__PURE__ */ new Map();
3494
+ this.stringStreamByAsk.set(askId, paths);
3495
+ }
3496
+ const existing = paths.get(event.data.path);
3497
+ paths.set(event.data.path, {
3498
+ agent: event.agent,
3499
+ text: (existing?.text ?? "") + event.data.delta,
3500
+ attempt: event.attempt
3501
+ });
3015
3502
  } else if (event.type === "pipeline" && event.status === "failed") {
3016
- this.latestPartialByAsk.delete(event.askId ?? "");
3503
+ const askId = event.askId ?? "";
3504
+ this.latestPartialByAsk.delete(askId);
3505
+ this.stringStreamByAsk.delete(askId);
3506
+ } else if (event.type === "ask_end") {
3507
+ this.stringStreamByAsk.delete(event.askId ?? "");
3508
+ }
3509
+ try {
3510
+ this.bus.emit(event.type, event);
3511
+ } catch (err) {
3512
+ console.error(
3513
+ `[axl] AxlEventBus listener for "${event.type}" threw; ignoring:`,
3514
+ err instanceof Error ? err.message : String(err)
3515
+ );
3017
3516
  }
3018
3517
  const waiter = this.waiters.shift();
3019
3518
  if (waiter) {
@@ -3231,6 +3730,9 @@ var REDACTION_RULES = {
3231
3730
  handoff_return: passthrough,
3232
3731
  pipeline: (e) => e.status === "failed" ? { ...e, reason: REDACTED } : e,
3233
3732
  partial_object: (e) => ({ ...e, data: { ...e.data, object: REDACTED } }),
3733
+ // `path` is structural (schema-shape, no PII); `delta` is user content.
3734
+ // Scrub only the delta — preserving path keeps "which fields stream" telemetry.
3735
+ string_delta: (e) => ({ ...e, data: { ...e.data, delta: REDACTED } }),
3234
3736
  verify: (e) => e.data.lastError !== void 0 ? { ...e, data: { ...e.data, lastError: REDACTED } } : e,
3235
3737
  log: (e) => {
3236
3738
  if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
@@ -4074,36 +4576,26 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
4074
4576
  const toolCallBuffers = /* @__PURE__ */ new Map();
4075
4577
  let streamProviderMetadata;
4076
4578
  let thinkingContent = "";
4077
- const partialObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof import_zod.z.ZodObject;
4579
+ const streamingObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof import_zod.z.ZodObject;
4078
4580
  const currentAttempt = schemaRetries + 1;
4079
- let inString = false;
4080
- let escaped = false;
4081
- let boundaryPending = false;
4581
+ const walker = streamingObjectEnabled ? new StreamingWalker({
4582
+ onStringDelta: (path, delta) => {
4583
+ this.emitEvent({
4584
+ type: "string_delta",
4585
+ agent: agent2._name,
4586
+ attempt: currentAttempt,
4587
+ data: { path, delta }
4588
+ });
4589
+ }
4590
+ }) : void 0;
4082
4591
  for await (const chunk of provider.stream(currentMessages, chatOptions)) {
4083
4592
  if (chunk.type === "text_delta") {
4084
4593
  content2 += chunk.content;
4085
4594
  this.emitEvent({ type: "token", data: chunk.content });
4086
4595
  this.onToken?.(chunk.content, callbackMeta);
4087
- if (partialObjectEnabled) {
4088
- for (const ch of chunk.content) {
4089
- if (escaped) {
4090
- escaped = false;
4091
- continue;
4092
- }
4093
- if (ch === "\\") {
4094
- escaped = true;
4095
- continue;
4096
- }
4097
- if (ch === '"') {
4098
- inString = !inString;
4099
- continue;
4100
- }
4101
- if (!inString && (ch === "," || ch === "}" || ch === "]")) {
4102
- boundaryPending = true;
4103
- }
4104
- }
4105
- if (boundaryPending) {
4106
- boundaryPending = false;
4596
+ if (walker) {
4597
+ walker.processChunk(chunk.content);
4598
+ if (walker.consumeBoundary()) {
4107
4599
  let parsed;
4108
4600
  try {
4109
4601
  parsed = parsePartialJson(extractJson(content2));
@@ -6912,6 +7404,20 @@ var AxlStream = class extends import_node_stream.Readable {
6912
7404
  get partialObjects() {
6913
7405
  return this.events.partialObjects;
6914
7406
  }
7407
+ /** Listener-based view over `string_delta` events, with optional filter
7408
+ * on `path` and/or `askId`. Yields `StringStreamEvent`s carrying the new
7409
+ * `delta` and full text-so-far `accumulated`. Designed for chat-style
7410
+ * typewriter rendering of long string fields — bind a UI component to
7411
+ * one path and set its text to `event.accumulated` per yield.
7412
+ *
7413
+ * Late subscribers receive a synthetic event seeded from the bus's
7414
+ * per-ask accumulator (so the field's current state renders on the
7415
+ * first iteration, not after the next char arrives). Doesn't race the
7416
+ * main async iterator. See `AxlEventBus.stringStream` for full
7417
+ * semantics. */
7418
+ stringStream(opts) {
7419
+ return this.events.stringStream(opts);
7420
+ }
6915
7421
  pipe(destination, options) {
6916
7422
  const shouldEnd = options?.end !== false;
6917
7423
  this.events.on("token", (event) => {
@@ -7567,7 +8073,6 @@ var NoopSpanManager = class {
7567
8073
  };
7568
8074
 
7569
8075
  // src/telemetry/index.ts
7570
- init_span_manager();
7571
8076
  async function createSpanManager(config) {
7572
8077
  if (!config?.enabled) {
7573
8078
  return new NoopSpanManager();
@@ -8595,7 +9100,9 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
8595
9100
  workflowCalls.set(event.workflow, (workflowCalls.get(event.workflow) ?? 0) + 1);
8596
9101
  }
8597
9102
  if (capturedTraces) {
8598
- if (event.type === "token" || event.type === "partial_object") return;
9103
+ if (event.type === "token" || event.type === "partial_object" || event.type === "string_delta") {
9104
+ return;
9105
+ }
8599
9106
  if (event.type === "agent_call_start" && event.data) {
8600
9107
  const d = event.data;
8601
9108
  if ("messages" in d) {
@@ -8919,6 +9426,49 @@ async function registerConditions(conditions) {
8919
9426
  }
8920
9427
  }
8921
9428
 
9429
+ // src/string-stream-from-events.ts
9430
+ async function* stringStreamFromEvents(source, opts) {
9431
+ const filterPath = opts?.path;
9432
+ const filterAskId = opts?.askId;
9433
+ if (filterPath !== void 0 && filterPath !== "" && !filterPath.startsWith("/")) {
9434
+ throw new Error(
9435
+ `stringStreamFromEvents({ path }) \u2014 path must start with '/' (RFC 6901 JSON Pointer); got ${JSON.stringify(filterPath)}. Examples: '/summary', '/sources/0/title'.`
9436
+ );
9437
+ }
9438
+ const acc = /* @__PURE__ */ new Map();
9439
+ for await (const event of source) {
9440
+ if (event.type === "string_delta") {
9441
+ const askId = event.askId ?? "";
9442
+ let paths = acc.get(askId);
9443
+ if (!paths) {
9444
+ paths = /* @__PURE__ */ new Map();
9445
+ acc.set(askId, paths);
9446
+ }
9447
+ const existing = paths.get(event.data.path);
9448
+ const accumulated = (existing?.text ?? "") + event.data.delta;
9449
+ paths.set(event.data.path, {
9450
+ agent: event.agent,
9451
+ text: accumulated,
9452
+ attempt: event.attempt
9453
+ });
9454
+ if (filterAskId !== void 0 && askId !== filterAskId) continue;
9455
+ if (filterPath !== void 0 && event.data.path !== filterPath) continue;
9456
+ yield {
9457
+ askId,
9458
+ agent: event.agent,
9459
+ path: event.data.path,
9460
+ delta: event.data.delta,
9461
+ accumulated,
9462
+ attempt: event.attempt
9463
+ };
9464
+ } else if (event.type === "pipeline" && event.status === "failed") {
9465
+ acc.delete(event.askId ?? "");
9466
+ } else if (event.type === "ask_end") {
9467
+ acc.delete(event.askId ?? "");
9468
+ }
9469
+ }
9470
+ }
9471
+
8922
9472
  // src/state/redis.ts
8923
9473
  var RedisStore = class _RedisStore {
8924
9474
  constructor(client) {
@@ -9324,6 +9874,7 @@ function cosineSimilarity2(a, b) {
9324
9874
  registerConditions,
9325
9875
  resolveRuntime,
9326
9876
  resolveThinkingOptions,
9877
+ stringStreamFromEvents,
9327
9878
  tool,
9328
9879
  workflow,
9329
9880
  zodToJsonSchema