@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.cjs +575 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +246 -2
- package/dist/index.d.ts +246 -2
- package/dist/index.js +576 -36
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
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
|
-
|
|
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
|
|
4400
|
+
const streamingObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof z.ZodObject;
|
|
3911
4401
|
const currentAttempt = schemaRetries + 1;
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
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 (
|
|
3921
|
-
|
|
3922
|
-
|
|
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
|
|
7408
|
-
return
|
|
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")
|
|
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
|