langfx.js 0.1.0-alpha.5 → 0.1.0-alpha.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Language as functions, in TypeScript. Build agents in browsers, workers, and apps without a Python agent backend.
4
4
 
5
- **Status: alpha (`0.1.0-alpha.5`).** OpenAI, Anthropic, and Gemini support text/image inputs, client tools, and streaming tool events through fixture-tested transports. The runtime includes prompt composition, structured JSON streaming, bounded agents, invocation traces/logs/progress, retries, and local response caching. Gemini has passed the live smoke suite; live OpenAI/Anthropic behavior and provider browser CORS remain unverified. APIs may change during alpha. See [implementation status](docs/IMPLEMENTATION_STATUS.md) and the [opt-in live smoke runner](docs/LIVE_TESTING.md).
5
+ **Status: alpha (`0.1.0-alpha.6`).** OpenAI, Anthropic, and Gemini support text/image inputs, client tools, and streaming tool events through fixture-tested transports. The runtime includes prompt composition, structured JSON streaming, bounded agents, invocation traces/logs/progress, retries, and local response caching. Gemini has passed the live smoke suite; live OpenAI/Anthropic behavior and provider browser CORS remain unverified. APIs may change during alpha. See [implementation status](docs/IMPLEMENTATION_STATUS.md) and the [opt-in live smoke runner](docs/LIVE_TESTING.md).
6
6
 
7
7
  The proposal is based on [`free-solo/langfx`](https://github.com/free-solo/langfx) at commit `2a1ea4e8dcd7075bf745ad707f901ece8546f47d`, examined on September 16, 2026.
8
8
 
package/dist/index.d.ts CHANGED
@@ -9,7 +9,8 @@ export * from './agentic.js';
9
9
  export * from './tool-call.js';
10
10
  export * from './tools.js';
11
11
  export * as llms from './llms/index.js';
12
- export type { FieldPath, StructuredPreviewEvent, StructuredStreamEvent } from './structured-stream.js';
12
+ export { collectStructured } from './structured-stream.js';
13
+ export type { CollectStructuredOptions, StructuredCompletion, StructuredProgressEvent, FieldPath, StructuredPreviewEvent, StructuredStreamEvent } from './structured-stream.js';
13
14
  export { Cache, InMemoryCache } from './cache.js';
14
15
  export type { InMemoryCacheOptions } from './cache.js';
15
16
  export type { RetryOptions } from './retry.js';
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export * from './agentic.js';
9
9
  export * from './tool-call.js';
10
10
  export * from './tools.js';
11
11
  export * as llms from './llms/index.js';
12
+ export { collectStructured } from './structured-stream.js';
12
13
  export { Cache, InMemoryCache } from './cache.js';
13
14
  export { Mapping, LfQuery, MappingExample, MappingError } from './mapping.js';
14
15
  export * as typed from './typed.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,OAAO,KAAK,IAAI,MAAM,iBAAiB,CAAC;AAExC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGlD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG9E,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,OAAO,KAAK,IAAI,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGlD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG9E,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC"}
@@ -45,3 +45,21 @@ export type StructuredPreviewEvent = {
45
45
  readonly path: FieldPath;
46
46
  readonly value: unknown;
47
47
  };
48
+ export type StructuredCompletion<T> = Extract<StructuredStreamEvent<T>, {
49
+ type: 'generationComplete';
50
+ }>;
51
+ export type StructuredProgressEvent<T = unknown> = Exclude<StructuredStreamEvent<T>, {
52
+ type: 'generationComplete';
53
+ } | {
54
+ error: unknown;
55
+ }>;
56
+ export interface CollectStructuredOptions<T = unknown> {
57
+ /** Awaited in order. Preview values are unvalidated and must not execute actions. */
58
+ readonly onProgress?: (event: StructuredProgressEvent<T>) => void | Promise<void>;
59
+ }
60
+ /** Consume a structured stream, resolving only after its successful terminal and cleanup.
61
+ * Does not own sessions or history. Query/session signals control cancellation;
62
+ * progress callbacks must settle independently. If consumption and closing both
63
+ * fail, an AggregateError preserves the primary error as its cause and first entry.
64
+ */
65
+ export declare function collectStructured<T>(stream: AsyncIterable<StructuredStreamEvent<T>>, options?: CollectStructuredOptions<T>): Promise<StructuredCompletion<T>>;
@@ -1,2 +1,43 @@
1
- export {};
1
+ /** Consume a structured stream, resolving only after its successful terminal and cleanup.
2
+ * Does not own sessions or history. Query/session signals control cancellation;
3
+ * progress callbacks must settle independently. If consumption and closing both
4
+ * fail, an AggregateError preserves the primary error as its cause and first entry.
5
+ */
6
+ export async function collectStructured(stream, options = {}) {
7
+ const iterator = stream[Symbol.asyncIterator]();
8
+ let completion;
9
+ let exhausted = false;
10
+ try {
11
+ while (true) {
12
+ const next = await iterator.next();
13
+ if (next.done) {
14
+ exhausted = true;
15
+ break;
16
+ }
17
+ if (completion !== undefined)
18
+ throw new Error('Structured stream emitted an event after its terminal event.');
19
+ const event = next.value;
20
+ if (event.type === 'generationComplete')
21
+ completion = event;
22
+ else if ('error' in event)
23
+ throw event.error;
24
+ else
25
+ await options.onProgress?.(event);
26
+ }
27
+ if (completion === undefined)
28
+ throw new Error('Structured stream ended without a terminal event.');
29
+ return completion;
30
+ }
31
+ catch (error) {
32
+ if (!exhausted && iterator.return) {
33
+ try {
34
+ await iterator.return();
35
+ }
36
+ catch (cleanupError) {
37
+ throw new AggregateError([error, cleanupError], 'Structured stream consumption and cleanup failed.', { cause: error });
38
+ }
39
+ }
40
+ throw error;
41
+ }
42
+ }
2
43
  //# sourceMappingURL=structured-stream.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"structured-stream.js","sourceRoot":"","sources":["../src/structured-stream.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"structured-stream.js","sourceRoot":"","sources":["../src/structured-stream.ts"],"names":[],"mappings":"AAyBA;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAA+C,EAC/C,OAAO,GAAgC,EAAE;IAEzC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;IAChD,IAAI,UAA+C,CAAC;IACpD,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBAAC,SAAS,GAAG,IAAI,CAAC;gBAAC,MAAM;YAAC,CAAC;YAC3C,IAAI,UAAU,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;YAC9G,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;YACzB,IAAI,KAAK,CAAC,IAAI,KAAK,oBAAoB;gBAAE,UAAU,GAAG,KAAK,CAAC;iBACvD,IAAI,OAAO,IAAI,KAAK;gBAAE,MAAM,KAAK,CAAC,KAAK,CAAC;;gBACxC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC;QACzC,CAAC;QACD,IAAI,UAAU,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACnG,OAAO,UAAU,CAAC;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,SAAS,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YAClC,IAAI,CAAC;gBAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC;YAAC,CAAC;YAChC,OAAO,YAAY,EAAE,CAAC;gBACpB,MAAM,IAAI,cAAc,CAAC,CAAC,KAAK,EAAE,YAAY,CAAC,EAAE,mDAAmD,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACzH,CAAC;QACH,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -7,3 +7,31 @@ The dashboard found a concrete missing runtime capability: structured streaming
7
7
  Keep schema validity separate from application validity. The dashboard must validate actual CSV columns, filter values, numeric metrics and group limits. The board must validate task identity and legal edits. Neither can be inferred from a generic approval component.
8
8
 
9
9
  Do not extract a general approval controller yet. First align and test cancellation during application, observer failures, changes while awaiting approval, and whether draft actions are replayed or committed directly. A useful future helper would own pending/applying/applied/discarded state and version checks through explicit callbacks; it should not own data stores, authorization, transactions, persistence, or undo. Extract it only when it removes duplicate behavior rather than hiding these differences.
10
+
11
+
12
+ ## Evaluation before abstraction
13
+
14
+ The dashboard now has a [task evaluation runner](../examples/dashboard-agent/README.md#evaluate-task-reliability) against its installed npm dependency. It separates model-quality checks (creation, follow-up, clarification) from deterministic lifecycle checks (invalid output, cancellation, stale approval, discard). Successful proposals also check approval and undo. This provides evidence for future shared helpers without changing the application lifecycle yet. Deterministic success establishes harness and application behavior; live task reliability must be measured separately.
15
+
16
+ For an end-to-end walkthrough of the implemented flow, see [the dashboard integration guide](DASHBOARD_INTEGRATION.md).
17
+
18
+ ## Integration audit: recommended next API
19
+
20
+ Audit of plain board, React board and dashboard after the dashboard evaluation merge:
21
+
22
+ | Concern | Plain board | React board | Dashboard | Recommendation |
23
+ | --- | --- | --- | --- | --- |
24
+ | Planning | `Session.query` | Structured stream to plan | Structured stream to completed message | Propose a small stream collector for the latter two |
25
+ | Progress | Status callback | Bounded text + projected query/action traces | Bounded text | Keep presentation and projection local |
26
+ | Session lifecycle | App creates/disposes | App creates/disposes and subscribes | App creates/disposes | Keep explicit ownership; existing Session handles stream cleanup |
27
+ | History | Request + current snapshot | Request + current snapshot | Completed valid turns + current snapshot | No common conversation controller justified yet |
28
+ | Domain validation | Preview task edits and reject no-op | Same board rules | Check columns, filters, groups and aggregates | Keep application-owned |
29
+ | Approval | Async action replay into draft, then commit | Same with inspection callbacks | Synchronous snapshot commit | Do not unify transaction semantics |
30
+ | Proposal state | pending/applying/applied/discarded | Same | Pending boolean; discard is idempotent | Document differences before sharing lifecycle code |
31
+ | Undo | Store-owned | Store-owned | Store-owned | No framework helper needed |
32
+
33
+ The concrete proposal is [`collectStructured(stream, { onProgress })`](STREAM_COLLECTION_PROPOSAL.md), returning the completed event. It removes duplicated terminal/error handling while retaining the actual assistant message for apps that need it. Before/after examples and a validation/release plan are included. The collector is now implemented in repository source; it has not yet been published or adopted by the pinned consumers.
34
+
35
+ Observer policy also needs to remain explicit. Session telemetry subscribers isolate errors, while direct UI callbacks can throw into application control flow. In the React board, direct `publish()` calls occur during error reporting and after commit/disposal, so a presentation failure can obscure the original error or report failure after a successful commit. A stream collector does not solve that boundary. A separate follow-up should establish and test the app's presentation-error policy before attempting any shared approval controller.
36
+
37
+ Decision: pursue the bounded stream-consumption helper first. Defer shared approval, automatic history retention, session wrappers and framework-owned transactions. The evidence currently supports a small convenience API, not a general agent application controller.
@@ -0,0 +1,51 @@
1
+ # Integrate streaming decisions into an application
2
+
3
+ The [dashboard example](../examples/dashboard-agent/README.md) uses the published `langfx.js` package to turn a request into a proposed application change. This guide follows its implementation; it complements the [board action guide](APP_ACTIONS.md), whose approval step replays actions asynchronously.
4
+
5
+ ## Declare model values and bind application dependencies
6
+
7
+ In [actions.ts](../examples/dashboard-agent/actions.ts), `BarChart`, `LineChart`, and `Metric` are `lf.typed.Class` subclasses. Their union is the element type of `SetDashboard.charts`. `Clarification` is an alternative to proposing an edit.
8
+
9
+ The model generates fields such as chart title, metric, grouping and filters. The application creates a private draft and binds it with `t.schemaFrom(SetDashboard, draft)`. That dependency is not an argument the model can supply. `SetDashboard.call()` writes only to this draft; generating the class instance does not invoke it.
10
+
11
+ ## Prepare authoritative context
12
+
13
+ `Conversation` starts with system instructions and a dataset summary. Every user turn includes the current dashboard snapshot. Earlier proposals remain in history, but may have been discarded or undone, so the new snapshot is explicitly authoritative.
14
+
15
+ The app decides which data to send. Here, full rows stay local, while column names, numeric ranges, bounded category samples, dashboard settings and conversation are sent to the provider. Summaries can still contain sensitive information. The local credential gateway is a development convenience, not a production authentication service.
16
+
17
+ ## Stream previews; retain only completed messages
18
+
19
+ The central API pattern is:
20
+
21
+ ```ts
22
+ let response: lf.Message<SetDashboard | Clarification> | undefined;
23
+ for await (const event of session.queryStream(input, schema, { maxChars: 32_768 })) {
24
+ if (event.type === 'textDelta') showPartialText(event.delta);
25
+ else if (event.type === 'generationComplete') response = event.message;
26
+ else if ('error' in event) throw event.error;
27
+ }
28
+ if (!response) throw new Error('Planning ended without a complete response.');
29
+ ```
30
+
31
+ This excerpt assumes the example's classes, session, schema and message history are already constructed. `showPartialText` is an application callback; partial output is display-only. Do not apply changes from an incomplete preview. The completion message contains the validated instance and provider continuation metadata; preserve it instead of rebuilding an assistant message from text.
32
+
33
+ The example bounds output size, uses a 60-second session deadline, passes an `AbortSignal`, and disposes the session in `finally`. The UI disables approval and upload while planning. It clears superseded proposals, reports errors, and returns controls to an idle state after cleanup.
34
+
35
+ ## Validate application rules before offering approval
36
+
37
+ Schema validation checks the shape of the generated value. The application must still check whether a referenced metric is an actual numeric CSV column, whether filter values exist, and whether grouping/aggregation limits hold.
38
+
39
+ After completion, check cancellation and whether the store revision changed during planning. Handle a valid clarification without creating an executable proposal. Otherwise invoke the action against its private draft, validate that draft, and expose a copied preview. Failed or cancelled turns do not enter conversation history. Completed, valid proposals do enter history even if the user subsequently discards them.
40
+
41
+ ## Commit after approval and recheck the revision
42
+
43
+ `Proposal.approve()` commits the validated draft only if its captured revision still matches the store. A successful commit consumes the proposal, preventing repeat approval. Discard consumes it without a mutation. Undo restores the previous dashboard and advances the revision, invalidating older proposals.
44
+
45
+ These are application guarantees supplied by `Proposal` and `DashboardStore`, not generic `lf.Action` guarantees. A real application's authorization, transactional storage and external side effects require their own checks. The dashboard can synchronously commit a complete snapshot; the board's asynchronous action replay has different cancellation and failure behavior. Keep that distinction explicit before extracting a shared controller.
46
+
47
+ ## Follow up and evaluate
48
+
49
+ On the next request, append the completed response to the explicit message history and include a fresh state snapshot. The example limits successful rounds rather than automatically compacting history; see [history management](HISTORY.md) for application-prepared histories.
50
+
51
+ The [evaluation runner](../examples/dashboard-agent/README.md#evaluate-task-reliability) checks exact chart semantics and independently calculated totals, along with cancellation, invalid output, stale approval, discard and undo. Use its deterministic mode for regression checks and opt into live runs for model task quality. A small passing suite is evidence for these specific tasks, not a cross-domain benchmark.
package/docs/RELEASING.md CHANGED
@@ -1,6 +1,24 @@
1
+ # Structured stream collector alpha: 0.1.0-alpha.6
2
+
3
+ Release target: `langfx.js@0.1.0-alpha.6` under the `alpha` dist-tag. Publication is pending registry verification. Keep `latest` on `0.1.0-alpha.0`.
4
+
5
+ Adds `collectStructured(stream, { onProgress })` and its completion/progress/options types. The helper awaits sequential progress callbacks and normal stream exhaustion, preserving the completed event and message identity. Terminal errors reject; combined consumption and cleanup failures retain both errors in an `AggregateError`. It does not own sessions, history, application validation or approval.
6
+
7
+ The collector passed independent review and the full quality gate before release preparation. Trial migrations against an unpublished packed candidate passed dashboard build/8 tests and React board build/16 tests. Published consumer migration and browser verification will follow publication. No new live-provider validation is claimed for this release.
8
+
9
+ ---
10
+
1
11
  # Conversation streaming alpha: 0.1.0-alpha.5
2
12
 
3
- Release target: `langfx.js@0.1.0-alpha.5` under the `alpha` dist-tag. Keep `latest` on `0.1.0-alpha.0`; publication is complete only after registry integrity verification.
13
+ Published and registry-verified on 2026-09-26: `langfx.js@0.1.0-alpha.5` under the `alpha` dist-tag. `latest` remains `0.1.0-alpha.0`. Release source commit: `76cdd0b` (tag `v0.1.0-alpha.5`).
14
+
15
+ Registry integrity matches the validated archive:
16
+
17
+ ```text
18
+ sha512-sCuSTLIo9XUaLNJVCBTPwhKEqmRRtHGKwBKbpTvEculVkakJGNSOs+Lco9IcHBmco43iTQ6vVLKlBxJQopzmdg==
19
+ ```
20
+
21
+ The versioned source passed all 250 tests and full type/browser/parity/packed-package checks. Archive inspection and publication dry-run passed before publishing the exact archive.
4
22
 
5
23
  ## Changes since alpha.4
6
24
 
package/docs/STREAMING.md CHANGED
@@ -102,3 +102,23 @@ Ambiguous syntax delays some previews until a separator resolves it: `(value)` i
102
102
  ## Differential Python evidence
103
103
 
104
104
  [The Python streaming comparison](PYTHON_STREAMING_PARITY.md) captures 12 fixed responses in whole-response and character chunks. `npm run check:streaming-parity` checks the normalized event traces and documented differences offline, and is included in `npm run check`. This compares semantic event order and values after explicit adaptations; it does not establish identical chunk timing or typed partial-object behavior.
105
+
106
+
107
+ ## Collect a structured stream (next release)
108
+
109
+ `collectStructured` is available in repository source; alpha.5 does not yet export it.
110
+
111
+ ```ts
112
+ const completion = await lf.collectStructured(
113
+ session.queryStream(history, Answer, { maxChars: 32_768 }),
114
+ { onProgress(event) {
115
+ if (event.type === 'textDelta') showPartialText(event.delta);
116
+ } },
117
+ );
118
+ // After application validation, retain the actual message for a follow-up:
119
+ history.push(completion.message);
120
+ ```
121
+
122
+ This excerpt assumes an existing session, history, schema and UI callback. Progress values are unvalidated. The helper awaits progress callbacks and stream exhaustion, rejects terminal errors or missing/duplicate terminals, and returns the original completion event with result/message identity preserved. It does not invoke actions, manage history, dispose sessions or impose its own deadline. Callbacks must settle independently; query/session signals control cancellation.
123
+
124
+ Callback errors close consumption. If cleanup also fails, an `AggregateError` preserves the primary error as `cause` and `errors[0]`, followed by the cleanup error. This differs from isolated Session telemetry subscriptions. See [the design and migration notes](STREAM_COLLECTION_PROPOSAL.md).
@@ -0,0 +1,127 @@
1
+ # Collect a structured stream into its completed response
2
+
3
+ Status: implemented in repository source, not yet published. The before/after examples describe consumer migrations for the next release; installed alpha.5 does not export this helper.
4
+
5
+ ## Finding
6
+
7
+ The React board and dashboard both consume a structured stream, update a text preview, retain a successful terminal value, throw error events, and reject a stream that ends without a result. This is a small reusable boundary. The plain board uses `Session.query` and does not need a streaming helper.
8
+
9
+ The rest of their integration code has materially different responsibilities. Keep domain validation, application revision checks, history retention, approval, undo, UI state, and session ownership outside this helper.
10
+
11
+ ## API
12
+
13
+ ```ts
14
+ export type StructuredCompletion<T> = Extract<
15
+ StructuredStreamEvent<T>, { type: 'generationComplete' }
16
+ >;
17
+ export type StructuredProgressEvent<T> = Exclude<
18
+ StructuredStreamEvent<T>,
19
+ { type: 'generationComplete' } | { error: unknown }
20
+ >;
21
+
22
+ export function collectStructured<T>(
23
+ stream: AsyncIterable<StructuredStreamEvent<T>>,
24
+ options?: {
25
+ onProgress?: (event: StructuredProgressEvent<T>) => void | Promise<void>;
26
+ },
27
+ ): Promise<StructuredCompletion<T>>;
28
+ ```
29
+
30
+ Accept an existing iterable so this works with both `lf.queryStream` and `Session.queryStream` without duplicating model/schema/options overloads. Return the complete success event to retain `result`, `message`, usage and finish reason without rebuilding or cloning values. `completion.result` must keep its identity with `completion.message.result`.
31
+
32
+ The helper does not add a second query method or introduce a conversation class merely to consume a stream.
33
+
34
+ ## Semantics
35
+
36
+ - Consume progress sequentially. Await `onProgress` before pulling the next event, providing explicit backpressure.
37
+ - Progress includes raw text and unvalidated structural previews. Never pass a terminal success or failure to the progress callback. Completion is returned; failures reject.
38
+ - On `parsingFailed`, `generationFailed`, or `cancelled`, reject with the original error value. Propagate thrown iterator errors as well.
39
+ - Save a successful terminal event and resolve only after normal stream exhaustion. Reject a stream that ends without a terminal event, emits multiple terminals, or emits anything after a terminal. This avoids returning success before iterator cleanup finishes.
40
+ - On a callback failure, stop consumption and close the iterator. If closing also fails, reject with an `AggregateError` whose `cause` and first `errors` entry are the original failure, and whose second entry is the cleanup failure. No thrown value is mutated.
41
+ - Preserve iterator cleanup errors when there is no earlier failure. Never return a successful completion after cleanup fails.
42
+ - Do not buffer text, field values or events. Retain only the completion and callback state; display truncation stays with the application.
43
+ - Do not create or dispose a session, append history, invoke actions, or change a store.
44
+ - Cancellation remains owned by the supplied query stream/session. The collector adds no new timeout or signal. An awaited callback must settle; a stalled arbitrary callback cannot be forcibly cancelled by this helper. Keep UI callbacks short and synchronous where possible.
45
+
46
+ These callbacks are part of consumption and their errors reject the collection. This deliberately differs from `Session.subscribe`, whose telemetry observers isolate failures in `observerErrors` and do not apply backpressure. A completed query trace may still precede an application callback/validation failure; do not rewrite it to claim the provider failed.
47
+
48
+ ## Dashboard before and after
49
+
50
+ Current outline in [dashboard actions](../examples/dashboard-agent/actions.ts):
51
+
52
+ ```ts
53
+ let response: lf.Message<SetDashboard | Clarification> | undefined;
54
+ for await (const event of session.queryStream(input, schema, { maxChars: 32_768 })) {
55
+ if (event.type === 'textDelta') onText(event.delta);
56
+ else if (event.type === 'generationComplete') response = event.message;
57
+ else if ('error' in event) throw event.error;
58
+ }
59
+ if (!response) throw new Error('Planning ended without a complete response.');
60
+ ```
61
+
62
+ Proposed replacement:
63
+
64
+ ```ts
65
+ const { message: response } = await lf.collectStructured(
66
+ session.queryStream(input, schema, { maxChars: 32_768 }),
67
+ { onProgress: event => {
68
+ if (event.type === 'textDelta') onText(event.delta);
69
+ } },
70
+ );
71
+ ```
72
+
73
+ Keep the subsequent abort/revision checks, clarification validation, private-draft invocation, domain validation and history append exactly where the application controls them. In particular, a structurally valid but domain-invalid response must not enter dashboard history. Keep `session.dispose()` in the surrounding `finally`.
74
+
75
+ ## React board before and after
76
+
77
+ Current outline in [React board actions](../examples/react-board/src/actions.ts):
78
+
79
+ ```ts
80
+ let plan: InstanceType<typeof Plan> | Clarification | undefined;
81
+ for await (const event of session.queryStream(input, t.union(Plan, Clarification), { maxChars: 32_768 })) {
82
+ if (event.type === 'textDelta') {
83
+ inspection = {
84
+ ...inspection,
85
+ characters: inspection.characters + event.delta.length,
86
+ text: (inspection.text + event.delta).slice(0, 12_000),
87
+ };
88
+ publish();
89
+ } else if (event.type === 'generationComplete') plan = event.result;
90
+ else if ('error' in event) throw event.error;
91
+ }
92
+ if (!plan) throw new Error('The stream ended without a validated plan.');
93
+ ```
94
+
95
+ Proposed replacement:
96
+
97
+ ```ts
98
+ const { result: plan } = await lf.collectStructured(
99
+ session.queryStream(input, t.union(Plan, Clarification), { maxChars: 32_768 }),
100
+ { onProgress: event => {
101
+ if (event.type !== 'textDelta') return;
102
+ inspection = {
103
+ ...inspection,
104
+ characters: inspection.characters + event.delta.length,
105
+ text: (inspection.text + event.delta).slice(0, 12_000),
106
+ };
107
+ publish();
108
+ } },
109
+ );
110
+ ```
111
+
112
+ The inspection subscription, bounded display, cancellation checks, revision validation and preview construction still belong to the board. The collector does not add history to an app that currently has none.
113
+
114
+ ## Adoption and release sequence
115
+
116
+ 1. Prototype the core collector with focused contract tests; no new lifecycle abstraction.
117
+ 2. Test exact completion/message identity; text and field callbacks; all error terminals; missing/duplicate terminal; post-terminal events; provider/cleanup failure; callback failure and iterator closing; async callback ordering; cancellation through a real Session stream. Include falsey valid results such as `false` and `0`.
118
+ 3. Compile type examples for inferred class unions, callback narrowing and completion metadata. Run existing streaming/session/browser checks.
119
+ 4. Compare actual before/after diffs in both apps. Remove or revise the helper if it merely moves an equally complicated loop into configuration.
120
+ 5. Test consumer migrations against a packed candidate in isolated checkouts. Keep committed consumers on published versions; do not switch them to source aliases or nonexistent npm versions. React currently pins alpha.4 and dashboard alpha.5; adopting the collector requires a new release.
121
+ 6. After publication, migrate both consumers, retain their lifecycle/history regression tests, and verify browser streaming/cancellation. Plain board keeps `Session.query`.
122
+
123
+ Do not implement the proposal as a cross-example import: the examples intentionally exercise package consumers. The published consumers remain unchanged until a release containing the collector is available.
124
+
125
+ ## Implementation validation
126
+
127
+ The repository implementation passed focused collector tests and full `npm run check` (including type contracts, browser and packed-package checks). Isolated copies of the dashboard and React board were migrated using the replacements above and installed a locally packed candidate: dashboard build and all 8 tests passed; React build and all 16 tests passed. These were local archive tests, not tests of a new published version. The archive retained the working-tree alpha.5 version number and was not published. No live-provider or browser smoke run was repeated for these migrations. Committed consumer source and dependency pins are unchanged pending a release.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langfx.js",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.6",
4
4
  "description": "Language as functions for TypeScript applications",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -78,7 +78,8 @@
78
78
  "example:history": "npm run build && node examples/history.mjs",
79
79
  "check:prefix-cache": "npm run build && node scripts/check-prefix-cache.mjs",
80
80
  "check:explicit-prefix-cache": "npm run build && node scripts/check-explicit-prefix-cache.mjs",
81
- "example:dashboard": "node examples/dashboard-agent/serve.mjs"
81
+ "example:dashboard": "npm --prefix examples/dashboard-agent run dev",
82
+ "evaluate:dashboard": "npm --prefix examples/dashboard-agent run evaluate --"
82
83
  },
83
84
  "devDependencies": {
84
85
  "@modelcontextprotocol/client": "2.0.0",