@xlaunch/llm 0.2.0-beta.1

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.
@@ -0,0 +1,26 @@
1
+ /* Generated by @xlaunch/typert-generator from the Host FaceModel — do not edit. */
2
+ import type {
3
+ RemoteResult,
4
+ TypertRemoteContribution,
5
+ } from '@xlaunch/typert-protocol'
6
+ import type { LlmConfigurableProvider, LlmDiscoveredModel, LlmModelDiscoveryRequest, LlmProviderInfo } from '@xlaunch/llm/types'
7
+
8
+ declare module '@xlaunch/typert-protocol' {
9
+ interface TypertRemoteNamespace$6c6c6d {
10
+ discoverModels: (settingsNs: string, request: LlmModelDiscoveryRequest, signal?: AbortSignal) => Promise<RemoteResult<LlmDiscoveredModel[]>>
11
+ listConfigurableProviders: () => Promise<RemoteResult<LlmConfigurableProvider[]>>
12
+ listProviders: () => Promise<RemoteResult<LlmProviderInfo[]>>
13
+ }
14
+ interface TypertRemoteMap {
15
+ 'llm/discoverModels': (settingsNs: string, request: LlmModelDiscoveryRequest, signal?: AbortSignal) => Promise<RemoteResult<LlmDiscoveredModel[]>>
16
+ 'llm/listConfigurableProviders': () => Promise<RemoteResult<LlmConfigurableProvider[]>>
17
+ 'llm/listProviders': () => Promise<RemoteResult<LlmProviderInfo[]>>
18
+ }
19
+ interface TypertRemoteNamespaceMap {
20
+ 'llm': TypertRemoteNamespace$6c6c6d
21
+ }
22
+ }
23
+
24
+ export declare const TYPERT_REMOTE: TypertRemoteContribution
25
+ export default TYPERT_REMOTE
26
+ //# sourceMappingURL=typert.remote-client.d.ts.map
@@ -0,0 +1,102 @@
1
+ /* Generated by @xlaunch/typert-generator from the Host FaceModel — do not edit. */
2
+ import { z } from 'zod'
3
+
4
+ const _xlaunch_llm_llm_discoverModels_parameter_0$schema = z.string()
5
+ const _xlaunch_llm_llm_discoverModels_parameter_1$schema = z.object({
6
+ 'provider': z.string().optional(),
7
+ 'baseURL': z.string().optional(),
8
+ 'api': z.string().optional(),
9
+ 'apiKey': z.string().optional(),
10
+ })
11
+ const _xlaunch_llm_llm_discoverModels_result$schema = z.array(z.object({
12
+ 'id': z.string(),
13
+ 'name': z.string().optional(),
14
+ 'contextWindow': z.number().optional(),
15
+ 'maxTokens': z.number().optional(),
16
+ }))
17
+ const _xlaunch_llm_llm_listConfigurableProviders_result$schema = z.array(z.object({
18
+ 'provider': z.string(),
19
+ 'displayName': z.string(),
20
+ 'settingsNs': z.string(),
21
+ 'settingsPath': z.array(z.string()),
22
+ 'declared': z.boolean().optional(),
23
+ }))
24
+ const _xlaunch_llm_llm_listProviders_result$schema = z.array(z.object({
25
+ 'id': z.string(),
26
+ 'name': z.string(),
27
+ }))
28
+
29
+ export const TYPERT_REMOTE = {
30
+ package: '@xlaunch/llm',
31
+ descriptors: [
32
+ {
33
+ id: '@xlaunch/llm#llm/discoverModels',
34
+ service: 'llm',
35
+ namespace: 'llm',
36
+ method: 'discoverModels',
37
+ implementation: 'remoteDiscoverModels',
38
+ invocation: { kind: 'direct' },
39
+ parameters: [
40
+ {
41
+ name: 'settingsNs',
42
+ wire: 'settingsNs',
43
+ source: 'json',
44
+ codec: {
45
+ mode: 'strict',
46
+ typeSymbol: '@xlaunch/llm#llm/discoverModels:settingsNs',
47
+ schema: _xlaunch_llm_llm_discoverModels_parameter_0$schema,
48
+ },
49
+ },
50
+ {
51
+ name: 'request',
52
+ wire: 'request',
53
+ source: 'json',
54
+ codec: {
55
+ mode: 'strict',
56
+ typeSymbol: '@xlaunch/llm/types#LlmModelDiscoveryRequest',
57
+ schema: _xlaunch_llm_llm_discoverModels_parameter_1$schema,
58
+ },
59
+ },
60
+ ],
61
+ cancellation: { parameter: 'signal' },
62
+ result: {
63
+ mode: 'strict',
64
+ typeSymbol: '@xlaunch/llm#llm/discoverModels:result',
65
+ schema: _xlaunch_llm_llm_discoverModels_result$schema,
66
+ },
67
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":625,"column":9},
68
+ },
69
+ {
70
+ id: '@xlaunch/llm#llm/listConfigurableProviders',
71
+ service: 'llm',
72
+ namespace: 'llm',
73
+ method: 'listConfigurableProviders',
74
+ invocation: { kind: 'direct' },
75
+ parameters: [
76
+ ],
77
+ result: {
78
+ mode: 'strict',
79
+ typeSymbol: '@xlaunch/llm#llm/listConfigurableProviders:result',
80
+ schema: _xlaunch_llm_llm_listConfigurableProviders_result$schema,
81
+ },
82
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":538,"column":3},
83
+ },
84
+ {
85
+ id: '@xlaunch/llm#llm/listProviders',
86
+ service: 'llm',
87
+ namespace: 'llm',
88
+ method: 'listProviders',
89
+ invocation: { kind: 'direct' },
90
+ parameters: [
91
+ ],
92
+ result: {
93
+ mode: 'strict',
94
+ typeSymbol: '@xlaunch/llm#llm/listProviders:result',
95
+ schema: _xlaunch_llm_llm_listProviders_result$schema,
96
+ },
97
+ sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":466,"column":3},
98
+ },
99
+ ],
100
+ }
101
+
102
+ export default TYPERT_REMOTE
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Normalization for values thrown by a final LLM adapter boundary.
3
+ *
4
+ * @module @xlaunch/llm/adapter-failure
5
+ */
6
+ import type { LlmFailure } from './types.ts';
7
+ /**
8
+ * Detach serializable provider facts from a value thrown by an adapter.
9
+ * @param value - arbitrary value thrown during adapter dispatch or iteration.
10
+ * @returns immutable provider-neutral facts suitable for a terminal finish chunk.
11
+ * @internal
12
+ */
13
+ export declare function normalizeLlmFailure(value: unknown): LlmFailure;
14
+ //# sourceMappingURL=adapter-failure.d.ts.map
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Normalization for values thrown by a final LLM adapter boundary.
3
+ *
4
+ * @module @xlaunch/llm/adapter-failure
5
+ */
6
+ import { HarnessError } from "./error.js";
7
+ /**
8
+ * Detach serializable provider facts from a value thrown by an adapter.
9
+ * @param value - arbitrary value thrown during adapter dispatch or iteration.
10
+ * @returns immutable provider-neutral facts suitable for a terminal finish chunk.
11
+ * @internal
12
+ */
13
+ export function normalizeLlmFailure(value) {
14
+ const error = value instanceof Error
15
+ ? value
16
+ : new HarnessError(thrownMessage(value), 'UNKNOWN', { cause: value });
17
+ // Cross-package copies preserve own data but not class identity. Trust the
18
+ // carried facts only when both own properties agree after validation.
19
+ const carried = ownFailureSnapshot(error);
20
+ if (carried !== undefined && carried.code === ownErrorCode(error))
21
+ return carried;
22
+ return Object.freeze({
23
+ message: errorMessage(error),
24
+ code: harnessErrorCode(error),
25
+ });
26
+ }
27
+ /** Render a non-Error throw without letting hostile coercion escape normalization. */
28
+ function thrownMessage(value) {
29
+ try {
30
+ const message = String(value);
31
+ return message.length > 0 ? message : 'LLM adapter failed';
32
+ }
33
+ catch (_hostileThrownValue) {
34
+ return 'LLM adapter failed';
35
+ }
36
+ }
37
+ /** Read a foreign error's own data-backed `code` without invoking accessors. */
38
+ function ownErrorCode(error) {
39
+ try {
40
+ const descriptor = Object.getOwnPropertyDescriptor(error, 'code');
41
+ return descriptor !== undefined && 'value' in descriptor ? descriptor.value : undefined;
42
+ }
43
+ catch (_sdkPropertyTrap) {
44
+ return undefined;
45
+ }
46
+ }
47
+ /** Snapshot an own data property without invoking an SDK-defined accessor. */
48
+ function ownFailureSnapshot(error) {
49
+ try {
50
+ const descriptor = Object.getOwnPropertyDescriptor(error, 'failure');
51
+ return descriptor !== undefined && 'value' in descriptor
52
+ ? failureSnapshot(descriptor.value)
53
+ : undefined;
54
+ }
55
+ catch (_sdkPropertyTrap) {
56
+ return undefined;
57
+ }
58
+ }
59
+ /** Validate and detach an arbitrary serializable failure payload. */
60
+ function failureSnapshot(value) {
61
+ if (typeof value !== 'object' || value === null)
62
+ return undefined;
63
+ try {
64
+ const candidate = value;
65
+ const message = candidate.message;
66
+ const code = candidate.code;
67
+ const status = candidate.status;
68
+ const providerRetryAfterMs = candidate.providerRetryAfterMs;
69
+ const requestId = candidate.requestId;
70
+ if (typeof message !== 'string' || message.length === 0
71
+ || typeof code !== 'string' || code.length === 0
72
+ || (status !== undefined && (!Number.isInteger(status) || status < 100 || status > 599))
73
+ || (providerRetryAfterMs !== undefined
74
+ && (!Number.isFinite(providerRetryAfterMs) || providerRetryAfterMs <= 0))
75
+ || (requestId !== undefined && (typeof requestId !== 'string' || requestId.length === 0)))
76
+ return undefined;
77
+ return Object.freeze({
78
+ message,
79
+ code,
80
+ ...status === undefined ? {} : { status },
81
+ ...providerRetryAfterMs === undefined ? {} : { providerRetryAfterMs },
82
+ ...requestId === undefined ? {} : { requestId },
83
+ });
84
+ }
85
+ catch (_sdkFailureGetter) {
86
+ return undefined;
87
+ }
88
+ }
89
+ /** Read an SDK error message without letting an accessor replace the primary failure. */
90
+ function errorMessage(error) {
91
+ try {
92
+ const message = error.message;
93
+ if (typeof message === 'string' && message.length > 0)
94
+ return message;
95
+ }
96
+ catch (_sdkMessageGetter) {
97
+ // The fallback below preserves a serializable failure beside the original Error.
98
+ }
99
+ return 'LLM adapter failed';
100
+ }
101
+ /** Trust only Harness-owned codes; third-party SDK codes are not our taxonomy. */
102
+ function harnessErrorCode(error) {
103
+ return error instanceof HarnessError ? error.code : 'UNKNOWN';
104
+ }
105
+ //# sourceMappingURL=adapter-failure.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The one definition of a well-formed provider API key, shared by every
3
+ * adapter that puts one in an HTTP header.
4
+ * @module @xlaunch/llm/api-key
5
+ */
6
+ /** Why a supplied API key cannot be used. */
7
+ export type ApiKeyRejection = 'empty' | 'illegalCharacters';
8
+ /** The verdict on one supplied API key. */
9
+ export type ApiKeyCheck = {
10
+ readonly ok: true;
11
+ readonly value: string;
12
+ } | {
13
+ readonly ok: false;
14
+ readonly reason: ApiKeyRejection;
15
+ };
16
+ /**
17
+ * Judge one *supplied* API key, trimming surrounding whitespace first.
18
+ *
19
+ * Trimming is silent because a padded key has one unambiguous reading; every
20
+ * other defect is reported. Absence is a configuration state this function
21
+ * never sees — a profile naming no credential authenticates through the
22
+ * provider's own ambient discovery or OAuth — so callers decide whether a
23
+ * value was supplied before asking.
24
+ * @param raw - the key exactly as configured, stored, or typed.
25
+ * @returns the trimmed key, or why it cannot be used.
26
+ */
27
+ export declare function normalizeApiKey(raw: string): ApiKeyCheck;
28
+ //# sourceMappingURL=api-key.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The one definition of a well-formed provider API key, shared by every
3
+ * adapter that puts one in an HTTP header.
4
+ * @module @xlaunch/llm/api-key
5
+ */
6
+ /**
7
+ * Characters an HTTP header value carries verbatim and every known provider
8
+ * key uses: printable ASCII, space excluded. A key outside this set cannot
9
+ * reach any provider — `fetch` refuses to build the header — so this is a
10
+ * transport invariant rather than one provider's policy. Latin-1 is excluded
11
+ * deliberately: a header could carry it, but no provider issues it, and
12
+ * admitting it trades a local explained refusal for an opaque 401.
13
+ */
14
+ const LEGAL_API_KEY = /^[\x21-\x7E]+$/;
15
+ /**
16
+ * Judge one *supplied* API key, trimming surrounding whitespace first.
17
+ *
18
+ * Trimming is silent because a padded key has one unambiguous reading; every
19
+ * other defect is reported. Absence is a configuration state this function
20
+ * never sees — a profile naming no credential authenticates through the
21
+ * provider's own ambient discovery or OAuth — so callers decide whether a
22
+ * value was supplied before asking.
23
+ * @param raw - the key exactly as configured, stored, or typed.
24
+ * @returns the trimmed key, or why it cannot be used.
25
+ */
26
+ export function normalizeApiKey(raw) {
27
+ const value = raw.trim();
28
+ if (value.length === 0)
29
+ return { ok: false, reason: 'empty' };
30
+ if (!LEGAL_API_KEY.test(value))
31
+ return { ok: false, reason: 'illegalCharacters' };
32
+ return { ok: true, value };
33
+ }
34
+ //# sourceMappingURL=api-key.js.map
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Incremental chunk-to-message assembler. This is the single canonical assembly
3
+ * algorithm used by the agent loop to build an assistant message from a chunk
4
+ * stream while logging the raw chunks for replay fidelity.
5
+ *
6
+ * @module @xlaunch/llm/assembler
7
+ */
8
+ import type { Message, MessageSource } from './message.ts';
9
+ import type { ContentBlock, FinishReason, ReplayEnvelope, StreamChunk, TokenUsage } from './types.ts';
10
+ /**
11
+ * Incrementally assembles raw {@link StreamChunk}s into complete
12
+ * {@link ContentBlock}s and a final assistant {@link Message}.
13
+ *
14
+ * The agent loop feeds it while logging raw chunks for replay fidelity, then
15
+ * reads `blocks()` / `message()` / `usage` / `finish` once the stream ends,
16
+ * or `interruptedBlocks()` when cancellation cut the stream short.
17
+ *
18
+ * Tolerant of delta-only protocols (no block-start/end); deltas arriving for
19
+ * an index already closed by `block-end` are ignored (malformed stream) so a
20
+ * misbehaving adapter cannot grow memory or corrupt a completed block.
21
+ */
22
+ export declare class BlockAssembler {
23
+ private partials;
24
+ private order;
25
+ private _usage;
26
+ private _finish;
27
+ private _replayState;
28
+ /**
29
+ * Feed one chunk into the assembly state.
30
+ * @param chunk - the next raw chunk, in stream order.
31
+ */
32
+ push(chunk: StreamChunk): void;
33
+ private ensure;
34
+ private assemble;
35
+ /** Invariant accessor: every index in `order` has a partial. */
36
+ private mustGet;
37
+ /**
38
+ * The one shared keep/drop decision over all seen blocks: max-token
39
+ * truncation drops tool calls that cannot be executed safely. Emitted blocks
40
+ * and replay metadata both derive from this result, so they cannot disagree.
41
+ */
42
+ private assembled;
43
+ /**
44
+ * Assemble all blocks seen so far, in stream order.
45
+ * @returns one block per seen index, except that max-token truncation drops
46
+ * tool calls that cannot be executed safely; an open block assembles from
47
+ * its accumulated deltas (an unknown block type never closed by `block-end` throws).
48
+ */
49
+ blocks(): ContentBlock[];
50
+ /**
51
+ * Assemble the prefix an interrupted stream can safely finalize: closed and
52
+ * open text/reasoning blocks with non-whitespace content, in stream order.
53
+ * Tool calls are omitted because interruption precedes dispatch; retaining
54
+ * one would require a fabricated result. Open unknown blocks are also omitted.
55
+ * @returns the kept blocks; empty when nothing streamed before the interruption.
56
+ */
57
+ interruptedBlocks(): ContentBlock[];
58
+ /** Usage from the `usage` chunk; undefined until one arrives. */
59
+ get usage(): TokenUsage | undefined;
60
+ /** Finish reason from the `finish` chunk; `{kind: 'stop'}` when the stream ended without one. */
61
+ get finish(): FinishReason;
62
+ /**
63
+ * Replay metadata from the terminal finish chunk, if any, with per-block
64
+ * entries pruned in step with {@link blocks}. Undefined when the envelope's
65
+ * entries do not align with the emitted blocks.
66
+ */
67
+ get replayState(): ReplayEnvelope | undefined;
68
+ /**
69
+ * The assembled assistant message.
70
+ * @param source - producer attribution for the assembled message.
71
+ * @returns a frozen assistant-role message over `blocks()` (same open-block assembly rules).
72
+ */
73
+ message(source?: MessageSource): Message;
74
+ }
75
+ //# sourceMappingURL=assembler.d.ts.map
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Incremental chunk-to-message assembler. This is the single canonical assembly
3
+ * algorithm used by the agent loop to build an assistant message from a chunk
4
+ * stream while logging the raw chunks for replay fidelity.
5
+ *
6
+ * @module @xlaunch/llm/assembler
7
+ */
8
+ import { brandString } from '@xlaunch/brand';
9
+ import { assertNever } from '@xlaunch/util-values';
10
+ import { createMessage } from "./message.js";
11
+ /**
12
+ * Incrementally assembles raw {@link StreamChunk}s into complete
13
+ * {@link ContentBlock}s and a final assistant {@link Message}.
14
+ *
15
+ * The agent loop feeds it while logging raw chunks for replay fidelity, then
16
+ * reads `blocks()` / `message()` / `usage` / `finish` once the stream ends,
17
+ * or `interruptedBlocks()` when cancellation cut the stream short.
18
+ *
19
+ * Tolerant of delta-only protocols (no block-start/end); deltas arriving for
20
+ * an index already closed by `block-end` are ignored (malformed stream) so a
21
+ * misbehaving adapter cannot grow memory or corrupt a completed block.
22
+ */
23
+ export class BlockAssembler {
24
+ partials = new Map();
25
+ order = [];
26
+ _usage;
27
+ _finish;
28
+ _replayState;
29
+ /**
30
+ * Feed one chunk into the assembly state.
31
+ * @param chunk - the next raw chunk, in stream order.
32
+ */
33
+ push(chunk) {
34
+ switch (chunk.type) {
35
+ case 'block-start': {
36
+ if (!this.partials.has(chunk.index)) {
37
+ this.order.push(chunk.index);
38
+ this.partials.set(chunk.index, {
39
+ blockType: chunk.blockType,
40
+ text: '',
41
+ toolCallArguments: '',
42
+ });
43
+ }
44
+ return;
45
+ }
46
+ case 'text-delta':
47
+ case 'reasoning-delta': {
48
+ const partial = this.ensure(chunk.index, chunk.type === 'text-delta' ? 'text' : 'reasoning');
49
+ if (partial.block)
50
+ return; // closed by block-end; ignore stragglers
51
+ partial.text += chunk.text;
52
+ return;
53
+ }
54
+ case 'tool-call-delta': {
55
+ const partial = this.ensure(chunk.index, 'tool-call');
56
+ if (partial.block)
57
+ return; // closed by block-end; ignore stragglers
58
+ partial.toolCallId = chunk.id;
59
+ if (chunk.name)
60
+ partial.toolCallName = chunk.name;
61
+ partial.toolCallArguments += chunk.argumentsDelta;
62
+ return;
63
+ }
64
+ case 'block-end': {
65
+ const partial = this.ensure(chunk.index, chunk.block.type);
66
+ // First close wins; ignoring re-close stragglers keeps streamed output
67
+ // and the final assembled block in agreement.
68
+ if (partial.block)
69
+ return;
70
+ partial.block = chunk.block;
71
+ return;
72
+ }
73
+ case 'usage': {
74
+ this._usage = chunk.usage;
75
+ return;
76
+ }
77
+ case 'finish': {
78
+ this._finish = chunk.reason;
79
+ this._replayState = chunk.replayState;
80
+ return;
81
+ }
82
+ default: return assertNever(chunk, 'BlockAssembler.push');
83
+ }
84
+ }
85
+ ensure(index, blockType) {
86
+ let partial = this.partials.get(index);
87
+ if (!partial) {
88
+ partial = { blockType, text: '', toolCallArguments: '' };
89
+ this.partials.set(index, partial);
90
+ this.order.push(index);
91
+ }
92
+ return partial;
93
+ }
94
+ assemble(partial, index) {
95
+ if (partial.block)
96
+ return partial.block;
97
+ switch (partial.blockType) {
98
+ case 'text': return { type: 'text', text: partial.text };
99
+ case 'reasoning': return { type: 'reasoning', text: partial.text };
100
+ case 'tool-call': return {
101
+ type: 'tool-call',
102
+ id: partial.toolCallId ?? brandString(`call-${index}`),
103
+ name: partial.toolCallName ?? '',
104
+ arguments: partial.toolCallArguments,
105
+ };
106
+ default: throw new Error(`cannot assemble incomplete block of type "${partial.blockType}"`);
107
+ }
108
+ }
109
+ /** Invariant accessor: every index in `order` has a partial. */
110
+ mustGet(index) {
111
+ const partial = this.partials.get(index);
112
+ if (!partial)
113
+ throw new Error(`BlockAssembler invariant violated: no partial for index ${index}`);
114
+ return partial;
115
+ }
116
+ /**
117
+ * The one shared keep/drop decision over all seen blocks: max-token
118
+ * truncation drops tool calls that cannot be executed safely. Emitted blocks
119
+ * and replay metadata both derive from this result, so they cannot disagree.
120
+ */
121
+ assembled() {
122
+ const all = this.order.map(index => this.assemble(this.mustGet(index), index));
123
+ const kept = this.finish.kind === 'max-tokens'
124
+ ? all.map(block => block.type !== 'tool-call')
125
+ : undefined;
126
+ const blocks = kept === undefined ? all : all.filter((_, position) => kept[position]);
127
+ const envelope = this._replayState;
128
+ if (envelope?.blocks === undefined)
129
+ return { blocks, replay: envelope };
130
+ if (envelope.blocks.length !== all.length)
131
+ return { blocks, replay: undefined };
132
+ return {
133
+ blocks,
134
+ replay: kept === undefined || blocks.length === all.length
135
+ ? envelope
136
+ : { response: envelope.response, blocks: envelope.blocks.filter((_, position) => kept[position]) },
137
+ };
138
+ }
139
+ /**
140
+ * Assemble all blocks seen so far, in stream order.
141
+ * @returns one block per seen index, except that max-token truncation drops
142
+ * tool calls that cannot be executed safely; an open block assembles from
143
+ * its accumulated deltas (an unknown block type never closed by `block-end` throws).
144
+ */
145
+ blocks() {
146
+ return this.assembled().blocks;
147
+ }
148
+ /**
149
+ * Assemble the prefix an interrupted stream can safely finalize: closed and
150
+ * open text/reasoning blocks with non-whitespace content, in stream order.
151
+ * Tool calls are omitted because interruption precedes dispatch; retaining
152
+ * one would require a fabricated result. Open unknown blocks are also omitted.
153
+ * @returns the kept blocks; empty when nothing streamed before the interruption.
154
+ */
155
+ interruptedBlocks() {
156
+ return this.order
157
+ .map((index) => {
158
+ const partial = this.mustGet(index);
159
+ const type = partial.block?.type ?? partial.blockType;
160
+ if (type !== 'text' && type !== 'reasoning')
161
+ return undefined;
162
+ return this.assemble(partial, index);
163
+ })
164
+ .filter((block) => (block?.type === 'text' || block?.type === 'reasoning') && block.text.trim() !== '');
165
+ }
166
+ /** Usage from the `usage` chunk; undefined until one arrives. */
167
+ get usage() {
168
+ return this._usage;
169
+ }
170
+ /** Finish reason from the `finish` chunk; `{kind: 'stop'}` when the stream ended without one. */
171
+ get finish() {
172
+ return this._finish ?? { kind: 'stop' };
173
+ }
174
+ /**
175
+ * Replay metadata from the terminal finish chunk, if any, with per-block
176
+ * entries pruned in step with {@link blocks}. Undefined when the envelope's
177
+ * entries do not align with the emitted blocks.
178
+ */
179
+ get replayState() {
180
+ return this.assembled().replay;
181
+ }
182
+ /**
183
+ * The assembled assistant message.
184
+ * @param source - producer attribution for the assembled message.
185
+ * @returns a frozen assistant-role message over `blocks()` (same open-block assembly rules).
186
+ */
187
+ message(source = { kind: 'plugin', plugin: 'xlaunch-llm/assembler' }) {
188
+ return createMessage({ role: 'assistant', content: this.blocks(), source });
189
+ }
190
+ }
191
+ //# sourceMappingURL=assembler.js.map