@axlsdk/axl 0.17.4 → 0.17.5

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.js CHANGED
@@ -2337,6 +2337,314 @@ var PartialParser = class {
2337
2337
  }
2338
2338
  };
2339
2339
 
2340
+ // src/streaming-walker.ts
2341
+ var MAX_DEPTH2 = 256;
2342
+ function encodeSegment(s) {
2343
+ if (typeof s === "number") return String(s);
2344
+ return s.replace(/~/g, "~0").replace(/\//g, "~1");
2345
+ }
2346
+ function computePath(stack) {
2347
+ let path = "";
2348
+ for (const frame of stack) {
2349
+ if (frame.type === "object") {
2350
+ path += "/" + encodeSegment(frame.currentKey ?? "");
2351
+ } else {
2352
+ path += "/" + encodeSegment(frame.currentIndex);
2353
+ }
2354
+ }
2355
+ return path;
2356
+ }
2357
+ var StreamingWalker = class {
2358
+ constructor(callbacks) {
2359
+ this.callbacks = callbacks;
2360
+ }
2361
+ state = "pre-json";
2362
+ stack = [];
2363
+ // String parsing state — survives across chunks so escapes can split.
2364
+ escaped = false;
2365
+ /** When > 0, we're collecting `\\uXXXX` hex digits. 4→0 as digits arrive. */
2366
+ unicodePending = 0;
2367
+ /** Accumulator for `\\uXXXX` digits. Cleared when the escape resolves. */
2368
+ unicodeBuf = "";
2369
+ // Per-string accumulators.
2370
+ keyBuf = "";
2371
+ deltaBuf = "";
2372
+ /** Cached JSON Pointer to the value currently being read. Recomputed
2373
+ * on entry to `read-value-string` (the only state that needs it). */
2374
+ currentPath = "";
2375
+ /** Set true when a structural boundary lands. Caller reads via
2376
+ * `consumeBoundary()` after each chunk to decide on partial_object emit. */
2377
+ boundaryPending = false;
2378
+ /** Walk `text` char-by-char, advancing state and accumulating deltas.
2379
+ * Calls `onStringDelta` at end-of-chunk for any pending value text. */
2380
+ processChunk(text) {
2381
+ for (let i = 0; i < text.length; i++) {
2382
+ if (this.state === "done") return;
2383
+ this.processChar(text[i]);
2384
+ }
2385
+ if (this.deltaBuf.length > 0 && this.state === "read-value-string") {
2386
+ this.callbacks.onStringDelta?.(this.currentPath, this.deltaBuf);
2387
+ this.deltaBuf = "";
2388
+ }
2389
+ }
2390
+ /** Read-and-clear the structural-boundary flag. Caller checks this
2391
+ * after `processChunk` to decide whether to emit `partial_object`. */
2392
+ consumeBoundary() {
2393
+ const v = this.boundaryPending;
2394
+ this.boundaryPending = false;
2395
+ return v;
2396
+ }
2397
+ /** True when the walker has decided it's done with this stream
2398
+ * (root closed or fatal malformed input). Caller may use this to
2399
+ * short-circuit further work. */
2400
+ get isDone() {
2401
+ return this.state === "done";
2402
+ }
2403
+ // ── Per-char dispatch ───────────────────────────────────────────────────
2404
+ processChar(ch) {
2405
+ switch (this.state) {
2406
+ case "pre-json":
2407
+ if (ch === "{") {
2408
+ if (!this.pushFrame({ type: "object" })) return;
2409
+ this.state = "expect-key-or-close";
2410
+ }
2411
+ return;
2412
+ case "expect-key-or-close":
2413
+ if (isWhitespace(ch)) return;
2414
+ if (ch === "}") {
2415
+ this.popFrame();
2416
+ return;
2417
+ }
2418
+ if (ch === '"') {
2419
+ this.keyBuf = "";
2420
+ this.escaped = false;
2421
+ this.unicodePending = 0;
2422
+ this.unicodeBuf = "";
2423
+ this.state = "read-key";
2424
+ return;
2425
+ }
2426
+ this.state = "done";
2427
+ return;
2428
+ case "read-key":
2429
+ this.processStringChar(
2430
+ ch,
2431
+ /* isValue */
2432
+ false
2433
+ );
2434
+ return;
2435
+ case "expect-colon":
2436
+ if (isWhitespace(ch)) return;
2437
+ if (ch === ":") {
2438
+ this.state = "expect-value";
2439
+ return;
2440
+ }
2441
+ this.state = "done";
2442
+ return;
2443
+ case "expect-value":
2444
+ if (isWhitespace(ch)) return;
2445
+ if (ch === "{") {
2446
+ if (!this.pushFrame({ type: "object" })) return;
2447
+ this.state = "expect-key-or-close";
2448
+ return;
2449
+ }
2450
+ if (ch === "[") {
2451
+ if (!this.pushFrame({ type: "array", currentIndex: 0 })) return;
2452
+ this.state = "expect-value";
2453
+ return;
2454
+ }
2455
+ if (ch === "]") {
2456
+ this.popFrame();
2457
+ return;
2458
+ }
2459
+ if (ch === '"') {
2460
+ this.currentPath = computePath(this.stack);
2461
+ this.deltaBuf = "";
2462
+ this.escaped = false;
2463
+ this.unicodePending = 0;
2464
+ this.unicodeBuf = "";
2465
+ this.state = "read-value-string";
2466
+ return;
2467
+ }
2468
+ if (isValueStartChar(ch)) {
2469
+ this.state = "read-value-other";
2470
+ return;
2471
+ }
2472
+ this.state = "done";
2473
+ return;
2474
+ case "read-value-string":
2475
+ this.processStringChar(
2476
+ ch,
2477
+ /* isValue */
2478
+ true
2479
+ );
2480
+ return;
2481
+ case "read-value-other":
2482
+ if (ch === "," || ch === "}" || ch === "]" || isWhitespace(ch)) {
2483
+ this.state = "expect-comma-close";
2484
+ this.processChar(ch);
2485
+ return;
2486
+ }
2487
+ return;
2488
+ case "expect-comma-close":
2489
+ if (isWhitespace(ch)) return;
2490
+ if (ch === ",") {
2491
+ this.boundaryPending = true;
2492
+ const top = this.stack[this.stack.length - 1];
2493
+ if (!top) {
2494
+ this.state = "done";
2495
+ return;
2496
+ }
2497
+ if (top.type === "object") {
2498
+ top.currentKey = void 0;
2499
+ this.state = "expect-key-or-close";
2500
+ } else {
2501
+ top.currentIndex += 1;
2502
+ this.state = "expect-value";
2503
+ }
2504
+ return;
2505
+ }
2506
+ if (ch === "}" || ch === "]") {
2507
+ this.popFrame();
2508
+ return;
2509
+ }
2510
+ this.state = "done";
2511
+ return;
2512
+ case "done":
2513
+ return;
2514
+ }
2515
+ }
2516
+ // ── String state machine (shared between key and value reads) ───────────
2517
+ /**
2518
+ * One char inside a string. Translates JSON escapes, accumulates
2519
+ * `\\uXXXX` digits across chunks, and routes the resolved char to
2520
+ * either `keyBuf` (when reading a key) or `deltaBuf` (when reading a
2521
+ * value).
2522
+ */
2523
+ processStringChar(ch, isValue) {
2524
+ if (this.unicodePending > 0) {
2525
+ if (isHex(ch)) {
2526
+ this.unicodeBuf += ch;
2527
+ this.unicodePending -= 1;
2528
+ if (this.unicodePending === 0) {
2529
+ const code = parseInt(this.unicodeBuf, 16);
2530
+ this.unicodeBuf = "";
2531
+ this.appendChar(String.fromCharCode(code), isValue);
2532
+ }
2533
+ } else {
2534
+ this.state = "done";
2535
+ }
2536
+ return;
2537
+ }
2538
+ if (this.escaped) {
2539
+ this.escaped = false;
2540
+ switch (ch) {
2541
+ case '"':
2542
+ this.appendChar('"', isValue);
2543
+ return;
2544
+ case "\\":
2545
+ this.appendChar("\\", isValue);
2546
+ return;
2547
+ case "/":
2548
+ this.appendChar("/", isValue);
2549
+ return;
2550
+ case "b":
2551
+ this.appendChar("\b", isValue);
2552
+ return;
2553
+ case "f":
2554
+ this.appendChar("\f", isValue);
2555
+ return;
2556
+ case "n":
2557
+ this.appendChar("\n", isValue);
2558
+ return;
2559
+ case "r":
2560
+ this.appendChar("\r", isValue);
2561
+ return;
2562
+ case "t":
2563
+ this.appendChar(" ", isValue);
2564
+ return;
2565
+ case "u":
2566
+ this.unicodePending = 4;
2567
+ this.unicodeBuf = "";
2568
+ return;
2569
+ default:
2570
+ this.state = "done";
2571
+ return;
2572
+ }
2573
+ }
2574
+ if (ch === "\\") {
2575
+ this.escaped = true;
2576
+ return;
2577
+ }
2578
+ if (ch === '"') {
2579
+ if (isValue) {
2580
+ if (this.deltaBuf.length > 0) {
2581
+ this.callbacks.onStringDelta?.(this.currentPath, this.deltaBuf);
2582
+ this.deltaBuf = "";
2583
+ }
2584
+ this.state = "expect-comma-close";
2585
+ } else {
2586
+ const top = this.stack[this.stack.length - 1];
2587
+ if (!top || top.type !== "object") {
2588
+ this.state = "done";
2589
+ return;
2590
+ }
2591
+ top.currentKey = this.keyBuf;
2592
+ this.keyBuf = "";
2593
+ this.state = "expect-colon";
2594
+ }
2595
+ return;
2596
+ }
2597
+ this.appendChar(ch, isValue);
2598
+ }
2599
+ appendChar(c, isValue) {
2600
+ if (isValue) {
2601
+ this.deltaBuf += c;
2602
+ } else {
2603
+ this.keyBuf += c;
2604
+ }
2605
+ }
2606
+ // ── Frame stack ─────────────────────────────────────────────────────────
2607
+ /** Push a frame onto the stack. Returns false if the stack is at the
2608
+ * depth cap — in which case the walker is also moved to `done` so the
2609
+ * caller's subsequent state assignment must be guarded. */
2610
+ pushFrame(frame) {
2611
+ if (this.stack.length >= MAX_DEPTH2) {
2612
+ this.state = "done";
2613
+ return false;
2614
+ }
2615
+ this.stack.push(frame);
2616
+ return true;
2617
+ }
2618
+ /** Pop a frame on `}` or `]`, fire boundary, transition to next state.
2619
+ * Defensive on empty stack — the state machine never invokes this
2620
+ * without an enclosing container today (callers gate on state, and
2621
+ * state transitions out of `pre-json` / `done` only after pushing),
2622
+ * but a future state-machine bug could land us here with nothing to
2623
+ * pop. Fail closed (move to `done`, no spurious boundary fire). */
2624
+ popFrame() {
2625
+ if (this.stack.length === 0) {
2626
+ this.state = "done";
2627
+ return;
2628
+ }
2629
+ this.stack.pop();
2630
+ this.boundaryPending = true;
2631
+ if (this.stack.length === 0) {
2632
+ this.state = "done";
2633
+ } else {
2634
+ this.state = "expect-comma-close";
2635
+ }
2636
+ }
2637
+ };
2638
+ function isWhitespace(ch) {
2639
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r";
2640
+ }
2641
+ function isHex(ch) {
2642
+ return ch >= "0" && ch <= "9" || ch >= "a" && ch <= "f" || ch >= "A" && ch <= "F";
2643
+ }
2644
+ function isValueStartChar(ch) {
2645
+ return ch === "-" || ch >= "0" && ch <= "9" || ch === "t" || ch === "f" || ch === "n";
2646
+ }
2647
+
2340
2648
  // src/event-utils.ts
2341
2649
  var COST_BEARING_LEAF_TYPES = [
2342
2650
  "agent_call_end",
@@ -2393,6 +2701,12 @@ var AXL_EVENT_TYPES = [
2393
2701
  "pipeline",
2394
2702
  // Progressive structured output — added in PR 2; reserved here.
2395
2703
  "partial_object",
2704
+ // Character-level deltas inside string values of progressive structured
2705
+ // output. Same gating as `partial_object` (schema set, no tools, root is
2706
+ // ZodObject); fires per text-delta chunk when the walker is inside a
2707
+ // string value at a known JSON Pointer path. Designed for chat-style
2708
+ // typewriter rendering of long string fields.
2709
+ "string_delta",
2396
2710
  // Verification
2397
2711
  "verify",
2398
2712
  // Observability
@@ -2490,6 +2804,27 @@ var AxlEventBus = class {
2490
2804
  * number of distinct askIds that emitted at least one partial.
2491
2805
  */
2492
2806
  latestPartialByAsk = /* @__PURE__ */ new Map();
2807
+ /**
2808
+ * Per-ask, per-path running accumulator for `string_delta` events,
2809
+ * updated on every `_push(string_delta)` regardless of consumer state.
2810
+ * Used to seed late `stringStream` subscribers with the current text-
2811
+ * so-far per (askId, path) pair, so a UI binding to `/summary` mid-
2812
+ * stream renders the field's current state on first event instead of
2813
+ * waiting for the next chunk.
2814
+ *
2815
+ * Cleared per-askId on `pipeline(failed)` (discarded attempt — old
2816
+ * deltas must not leak into the next attempt's view) and on `ask_end`
2817
+ * (frees memory; stream-event accumulators don't outlive the ask).
2818
+ *
2819
+ * Memory bound: O(active asks × distinct paths × total chars per
2820
+ * field). For typical chat workflows (1 ask, 1-3 streaming string
2821
+ * fields) this is a few KB. The clear-on-`ask_end` cap keeps a
2822
+ * long-running runtime from accumulating per-ask entries forever
2823
+ * (the way `latestPartialByAsk` does — partial snapshots are tiny by
2824
+ * comparison so that map's growth is acceptable; string accumulators
2825
+ * can be 4 KB+ each, hence the more aggressive cleanup here).
2826
+ */
2827
+ stringStreamByAsk = /* @__PURE__ */ new Map();
2493
2828
  /**
2494
2829
  * Hook fired when an iterator obtained from `[Symbol.asyncIterator]`
2495
2830
  * is `await using`-disposed. Set via the second constructor argument.
@@ -2793,6 +3128,141 @@ var AxlEventBus = class {
2793
3128
  }
2794
3129
  };
2795
3130
  }
3131
+ /**
3132
+ * Listener-based view over `string_delta` events with optional
3133
+ * filtering by `path` and/or `askId`. Yields `StringStreamEvent`s
3134
+ * carrying `delta` (the new chars) and `accumulated` (the full text-
3135
+ * so-far for that ask + path).
3136
+ *
3137
+ * Designed for chat-style typewriter rendering of long string fields:
3138
+ * a React component binding to `path: '/summary'` sets its text to
3139
+ * `event.accumulated` on every yield, getting smooth char-by-char
3140
+ * progression with no parser of its own.
3141
+ *
3142
+ * Late-subscriber seeding: at subscribe time, the bus's per-ask
3143
+ * accumulator is walked and one synthetic `StringStreamEvent` is
3144
+ * yielded per matching (askId, path) entry, with `delta ===
3145
+ * accumulated === <full text-so-far>`. After that, live deltas flow
3146
+ * normally. A consumer who attaches mid-ask sees the field's current
3147
+ * state on first iteration, then incremental updates.
3148
+ *
3149
+ * Like `partialObjects`, this view is listener-based — it does NOT
3150
+ * race the main async iterator (`for await (const e of bus)`).
3151
+ * Running both concurrently each receives every matching event.
3152
+ *
3153
+ * Schema-retry awareness: on `pipeline(status: 'failed')`, the
3154
+ * accumulator and any pending events for the affected ask are
3155
+ * dropped, so a consumer iterating during attempt-N never observes
3156
+ * stale text after attempt-N+1 begins. Mirrors `partialObjects`'
3157
+ * per-attempt reset.
3158
+ *
3159
+ * Termination: yields any pending events first (live + seeded), then
3160
+ * `done: true` once the bus is finished via `_finish()`.
3161
+ */
3162
+ stringStream(opts) {
3163
+ const self = this;
3164
+ const filterPath = opts?.path;
3165
+ const filterAskId = opts?.askId;
3166
+ if (filterPath !== void 0 && filterPath !== "" && !filterPath.startsWith("/")) {
3167
+ throw new Error(
3168
+ `AxlEventBus.stringStream({ path }) \u2014 path must start with '/' (RFC 6901 JSON Pointer); got ${JSON.stringify(filterPath)}. Examples: '/summary', '/sources/0/title'.`
3169
+ );
3170
+ }
3171
+ return {
3172
+ [Symbol.asyncIterator]() {
3173
+ const pending = [];
3174
+ let resolveWaiter = null;
3175
+ let finished = self.finished;
3176
+ let unsubscribed = false;
3177
+ const wakeWaiter = () => {
3178
+ const r = resolveWaiter;
3179
+ resolveWaiter = null;
3180
+ r?.();
3181
+ };
3182
+ const matchesFilter = (askId, path) => {
3183
+ if (filterAskId !== void 0 && askId !== filterAskId) return false;
3184
+ if (filterPath !== void 0 && path !== filterPath) return false;
3185
+ return true;
3186
+ };
3187
+ for (const [askId, paths] of self.stringStreamByAsk) {
3188
+ for (const [path, acc] of paths) {
3189
+ if (!matchesFilter(askId, path)) continue;
3190
+ pending.push({
3191
+ askId,
3192
+ agent: acc.agent,
3193
+ path,
3194
+ delta: acc.text,
3195
+ accumulated: acc.text,
3196
+ attempt: acc.attempt
3197
+ });
3198
+ }
3199
+ }
3200
+ const handler = (event) => {
3201
+ if (event.type !== "string_delta") return;
3202
+ const askId = event.askId ?? "";
3203
+ if (!matchesFilter(askId, event.data.path)) return;
3204
+ const paths = self.stringStreamByAsk.get(askId);
3205
+ const acc = paths?.get(event.data.path);
3206
+ const accumulated = acc?.text ?? event.data.delta;
3207
+ pending.push({
3208
+ askId,
3209
+ agent: event.agent,
3210
+ path: event.data.path,
3211
+ delta: event.data.delta,
3212
+ accumulated,
3213
+ attempt: event.attempt
3214
+ });
3215
+ wakeWaiter();
3216
+ };
3217
+ self.bus.on("string_delta", handler);
3218
+ const pipelineHandler = (event) => {
3219
+ if (event.type !== "pipeline") return;
3220
+ if (event.status !== "failed") return;
3221
+ const askId = event.askId ?? "";
3222
+ for (let i = pending.length - 1; i >= 0; i--) {
3223
+ if (pending[i].askId === askId) pending.splice(i, 1);
3224
+ }
3225
+ };
3226
+ self.bus.on("pipeline", pipelineHandler);
3227
+ const unsubscribe = () => {
3228
+ if (unsubscribed) return;
3229
+ unsubscribed = true;
3230
+ self.bus.off("string_delta", handler);
3231
+ self.bus.off("pipeline", pipelineHandler);
3232
+ unsubscribeFinish();
3233
+ };
3234
+ const unsubscribeFinish = self._onFinish(() => {
3235
+ finished = true;
3236
+ self.bus.off("string_delta", handler);
3237
+ self.bus.off("pipeline", pipelineHandler);
3238
+ wakeWaiter();
3239
+ });
3240
+ return {
3241
+ async next() {
3242
+ while (true) {
3243
+ if (pending.length > 0) {
3244
+ return { value: pending.shift(), done: false };
3245
+ }
3246
+ if (finished) {
3247
+ unsubscribe();
3248
+ return { value: void 0, done: true };
3249
+ }
3250
+ await new Promise((res) => {
3251
+ resolveWaiter = res;
3252
+ });
3253
+ }
3254
+ },
3255
+ async return() {
3256
+ unsubscribe();
3257
+ return { value: void 0, done: true };
3258
+ },
3259
+ [Symbol.asyncIterator]() {
3260
+ return this;
3261
+ }
3262
+ };
3263
+ }
3264
+ };
3265
+ }
2796
3266
  /** Iterator over structural lifecycle events only — skips per-token chatter
2797
3267
  * and progressive `partial_object` emissions. Useful for waterfall UIs
2798
3268
  * and any consumer that wants the "what happened" timeline. */
@@ -2829,14 +3299,6 @@ var AxlEventBus = class {
2829
3299
  */
2830
3300
  _push(event) {
2831
3301
  if (this.finished) return;
2832
- try {
2833
- this.bus.emit(event.type, event);
2834
- } catch (err) {
2835
- console.error(
2836
- `[axl] AxlEventBus listener for "${event.type}" threw; ignoring:`,
2837
- err instanceof Error ? err.message : String(err)
2838
- );
2839
- }
2840
3302
  if (event.type === "partial_object" && event.data?.object !== void 0) {
2841
3303
  const askId = event.askId ?? "";
2842
3304
  this.latestPartialByAsk.set(askId, {
@@ -2845,8 +3307,33 @@ var AxlEventBus = class {
2845
3307
  object: event.data.object,
2846
3308
  attempt: event.attempt
2847
3309
  });
3310
+ } else if (event.type === "string_delta") {
3311
+ const askId = event.askId ?? "";
3312
+ let paths = this.stringStreamByAsk.get(askId);
3313
+ if (!paths) {
3314
+ paths = /* @__PURE__ */ new Map();
3315
+ this.stringStreamByAsk.set(askId, paths);
3316
+ }
3317
+ const existing = paths.get(event.data.path);
3318
+ paths.set(event.data.path, {
3319
+ agent: event.agent,
3320
+ text: (existing?.text ?? "") + event.data.delta,
3321
+ attempt: event.attempt
3322
+ });
2848
3323
  } else if (event.type === "pipeline" && event.status === "failed") {
2849
- this.latestPartialByAsk.delete(event.askId ?? "");
3324
+ const askId = event.askId ?? "";
3325
+ this.latestPartialByAsk.delete(askId);
3326
+ this.stringStreamByAsk.delete(askId);
3327
+ } else if (event.type === "ask_end") {
3328
+ this.stringStreamByAsk.delete(event.askId ?? "");
3329
+ }
3330
+ try {
3331
+ this.bus.emit(event.type, event);
3332
+ } catch (err) {
3333
+ console.error(
3334
+ `[axl] AxlEventBus listener for "${event.type}" threw; ignoring:`,
3335
+ err instanceof Error ? err.message : String(err)
3336
+ );
2850
3337
  }
2851
3338
  const waiter = this.waiters.shift();
2852
3339
  if (waiter) {
@@ -3064,6 +3551,9 @@ var REDACTION_RULES = {
3064
3551
  handoff_return: passthrough,
3065
3552
  pipeline: (e) => e.status === "failed" ? { ...e, reason: REDACTED } : e,
3066
3553
  partial_object: (e) => ({ ...e, data: { ...e.data, object: REDACTED } }),
3554
+ // `path` is structural (schema-shape, no PII); `delta` is user content.
3555
+ // Scrub only the delta — preserving path keeps "which fields stream" telemetry.
3556
+ string_delta: (e) => ({ ...e, data: { ...e.data, delta: REDACTED } }),
3067
3557
  verify: (e) => e.data.lastError !== void 0 ? { ...e, data: { ...e.data, lastError: REDACTED } } : e,
3068
3558
  log: (e) => {
3069
3559
  if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
@@ -3907,36 +4397,26 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
3907
4397
  const toolCallBuffers = /* @__PURE__ */ new Map();
3908
4398
  let streamProviderMetadata;
3909
4399
  let thinkingContent = "";
3910
- const partialObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof z.ZodObject;
4400
+ const streamingObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof z.ZodObject;
3911
4401
  const currentAttempt = schemaRetries + 1;
3912
- let inString = false;
3913
- let escaped = false;
3914
- let boundaryPending = false;
4402
+ const walker = streamingObjectEnabled ? new StreamingWalker({
4403
+ onStringDelta: (path, delta) => {
4404
+ this.emitEvent({
4405
+ type: "string_delta",
4406
+ agent: agent2._name,
4407
+ attempt: currentAttempt,
4408
+ data: { path, delta }
4409
+ });
4410
+ }
4411
+ }) : void 0;
3915
4412
  for await (const chunk of provider.stream(currentMessages, chatOptions)) {
3916
4413
  if (chunk.type === "text_delta") {
3917
4414
  content2 += chunk.content;
3918
4415
  this.emitEvent({ type: "token", data: chunk.content });
3919
4416
  this.onToken?.(chunk.content, callbackMeta);
3920
- if (partialObjectEnabled) {
3921
- for (const ch of chunk.content) {
3922
- if (escaped) {
3923
- escaped = false;
3924
- continue;
3925
- }
3926
- if (ch === "\\") {
3927
- escaped = true;
3928
- continue;
3929
- }
3930
- if (ch === '"') {
3931
- inString = !inString;
3932
- continue;
3933
- }
3934
- if (!inString && (ch === "," || ch === "}" || ch === "]")) {
3935
- boundaryPending = true;
3936
- }
3937
- }
3938
- if (boundaryPending) {
3939
- boundaryPending = false;
4417
+ if (walker) {
4418
+ walker.processChunk(chunk.content);
4419
+ if (walker.consumeBoundary()) {
3940
4420
  let parsed;
3941
4421
  try {
3942
4422
  parsed = parsePartialJson(extractJson(content2));
@@ -6745,6 +7225,20 @@ var AxlStream = class extends Readable {
6745
7225
  get partialObjects() {
6746
7226
  return this.events.partialObjects;
6747
7227
  }
7228
+ /** Listener-based view over `string_delta` events, with optional filter
7229
+ * on `path` and/or `askId`. Yields `StringStreamEvent`s carrying the new
7230
+ * `delta` and full text-so-far `accumulated`. Designed for chat-style
7231
+ * typewriter rendering of long string fields — bind a UI component to
7232
+ * one path and set its text to `event.accumulated` per yield.
7233
+ *
7234
+ * Late subscribers receive a synthetic event seeded from the bus's
7235
+ * per-ask accumulator (so the field's current state renders on the
7236
+ * first iteration, not after the next char arrives). Doesn't race the
7237
+ * main async iterator. See `AxlEventBus.stringStream` for full
7238
+ * semantics. */
7239
+ stringStream(opts) {
7240
+ return this.events.stringStream(opts);
7241
+ }
6748
7242
  pipe(destination, options) {
6749
7243
  const shouldEnd = options?.end !== false;
6750
7244
  this.events.on("token", (event) => {
@@ -7404,8 +7898,8 @@ async function createSpanManager(config) {
7404
7898
  if (!config?.enabled) {
7405
7899
  return new NoopSpanManager();
7406
7900
  }
7407
- const { OTelSpanManager: OTelSpanManager2 } = await import("./span-manager-3IKXXUTZ.js");
7408
- return OTelSpanManager2.create(config);
7901
+ const { OTelSpanManager } = await import("./span-manager-3IKXXUTZ.js");
7902
+ return OTelSpanManager.create(config);
7409
7903
  }
7410
7904
 
7411
7905
  // src/runtime.ts
@@ -8427,7 +8921,9 @@ var AxlRuntime = class extends EventEmitter2 {
8427
8921
  workflowCalls.set(event.workflow, (workflowCalls.get(event.workflow) ?? 0) + 1);
8428
8922
  }
8429
8923
  if (capturedTraces) {
8430
- if (event.type === "token" || event.type === "partial_object") return;
8924
+ if (event.type === "token" || event.type === "partial_object" || event.type === "string_delta") {
8925
+ return;
8926
+ }
8431
8927
  if (event.type === "agent_call_start" && event.data) {
8432
8928
  const d = event.data;
8433
8929
  if ("messages" in d) {
@@ -8751,6 +9247,49 @@ async function registerConditions(conditions) {
8751
9247
  }
8752
9248
  }
8753
9249
 
9250
+ // src/string-stream-from-events.ts
9251
+ async function* stringStreamFromEvents(source, opts) {
9252
+ const filterPath = opts?.path;
9253
+ const filterAskId = opts?.askId;
9254
+ if (filterPath !== void 0 && filterPath !== "" && !filterPath.startsWith("/")) {
9255
+ throw new Error(
9256
+ `stringStreamFromEvents({ path }) \u2014 path must start with '/' (RFC 6901 JSON Pointer); got ${JSON.stringify(filterPath)}. Examples: '/summary', '/sources/0/title'.`
9257
+ );
9258
+ }
9259
+ const acc = /* @__PURE__ */ new Map();
9260
+ for await (const event of source) {
9261
+ if (event.type === "string_delta") {
9262
+ const askId = event.askId ?? "";
9263
+ let paths = acc.get(askId);
9264
+ if (!paths) {
9265
+ paths = /* @__PURE__ */ new Map();
9266
+ acc.set(askId, paths);
9267
+ }
9268
+ const existing = paths.get(event.data.path);
9269
+ const accumulated = (existing?.text ?? "") + event.data.delta;
9270
+ paths.set(event.data.path, {
9271
+ agent: event.agent,
9272
+ text: accumulated,
9273
+ attempt: event.attempt
9274
+ });
9275
+ if (filterAskId !== void 0 && askId !== filterAskId) continue;
9276
+ if (filterPath !== void 0 && event.data.path !== filterPath) continue;
9277
+ yield {
9278
+ askId,
9279
+ agent: event.agent,
9280
+ path: event.data.path,
9281
+ delta: event.data.delta,
9282
+ accumulated,
9283
+ attempt: event.attempt
9284
+ };
9285
+ } else if (event.type === "pipeline" && event.status === "failed") {
9286
+ acc.delete(event.askId ?? "");
9287
+ } else if (event.type === "ask_end") {
9288
+ acc.delete(event.askId ?? "");
9289
+ }
9290
+ }
9291
+ }
9292
+
8754
9293
  // src/state/redis.ts
8755
9294
  var RedisStore = class _RedisStore {
8756
9295
  constructor(client) {
@@ -9155,6 +9694,7 @@ export {
9155
9694
  registerConditions,
9156
9695
  resolveRuntime,
9157
9696
  resolveThinkingOptions,
9697
+ stringStreamFromEvents,
9158
9698
  tool,
9159
9699
  workflow,
9160
9700
  zodToJsonSchema