@tanstack/ai 0.46.0 → 0.47.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.
- package/dist/esm/activities/chat/index.d.ts +36 -11
- package/dist/esm/activities/chat/index.js +435 -76
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.d.ts +1 -0
- package/dist/esm/activities/chat/messages.js +12 -7
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.d.ts +7 -2
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +10 -3
- package/dist/esm/activities/chat/middleware/compose.js +55 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.d.ts +6 -3
- package/dist/esm/activities/chat/middleware/define.js.map +1 -1
- package/dist/esm/activities/chat/middleware/generic-interrupts.d.ts +13 -0
- package/dist/esm/activities/chat/middleware/generic-interrupts.js +8 -0
- package/dist/esm/activities/chat/middleware/generic-interrupts.js.map +1 -0
- package/dist/esm/activities/chat/middleware/index.d.ts +4 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +54 -3
- package/dist/esm/activities/chat/middleware/types.js +16 -0
- package/dist/esm/activities/chat/middleware/types.js.map +1 -0
- package/dist/esm/activities/chat/stream/processor.js +18 -5
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +6 -0
- package/dist/esm/adapter-internals.js +4 -1
- package/dist/esm/client.d.ts +4 -0
- package/dist/esm/client.js +3 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/generic-interrupt-continuation.d.ts +45 -0
- package/dist/esm/generic-interrupt-continuation.js +80 -0
- package/dist/esm/generic-interrupt-continuation.js.map +1 -0
- package/dist/esm/index.d.ts +6 -1
- package/dist/esm/index.js +4 -1
- package/dist/esm/interrupt-definition.d.ts +113 -0
- package/dist/esm/interrupt-definition.js +169 -0
- package/dist/esm/interrupt-definition.js.map +1 -0
- package/dist/esm/interrupt-resume.d.ts +3 -0
- package/dist/esm/interrupt-resume.js +77 -16
- package/dist/esm/interrupt-resume.js.map +1 -1
- package/dist/esm/interrupts.d.ts +12 -3
- package/dist/esm/interrupts.js.map +1 -1
- package/dist/esm/types.d.ts +11 -3
- package/dist/esm/utilities/chat-params.js +10 -1
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/package.json +1 -1
- package/skills/ai-core/media-generation/SKILL.md +4 -1
- package/skills/ai-core/middleware/SKILL.md +53 -44
- package/skills/ai-core/structured-outputs/SKILL.md +59 -55
- package/skills/ai-core/tool-calling/SKILL.md +54 -1
- package/src/activities/chat/index.ts +1024 -203
- package/src/activities/chat/messages.ts +11 -3
- package/src/activities/chat/middleware/builder.ts +29 -4
- package/src/activities/chat/middleware/compose.ts +95 -5
- package/src/activities/chat/middleware/define.ts +13 -3
- package/src/activities/chat/middleware/generic-interrupts.ts +26 -0
- package/src/activities/chat/middleware/index.ts +15 -0
- package/src/activities/chat/middleware/types.ts +127 -2
- package/src/activities/chat/stream/processor.ts +21 -0
- package/src/adapter-internals.ts +20 -0
- package/src/client.ts +20 -0
- package/src/generic-interrupt-continuation.ts +162 -0
- package/src/index.ts +34 -0
- package/src/interrupt-definition.ts +581 -0
- package/src/interrupt-resume.ts +156 -25
- package/src/interrupts.ts +13 -3
- package/src/types.ts +11 -3
- package/src/utilities/chat-params.ts +16 -3
|
@@ -14,10 +14,21 @@ import { EventType } from '../../types'
|
|
|
14
14
|
import {
|
|
15
15
|
INTERRUPT_BINDING_METADATA_KEY,
|
|
16
16
|
InterruptResumeValidationError,
|
|
17
|
+
readInterruptBinding,
|
|
17
18
|
readUnopenedInterruptBinding,
|
|
18
19
|
validateInterruptResumeBatch,
|
|
19
20
|
} from '../../interrupt-resume'
|
|
20
21
|
import { INTERRUPT_BINDING_VERSION } from '../../interrupts'
|
|
22
|
+
import {
|
|
23
|
+
INTERRUPT_PAYLOAD_METADATA_KEY,
|
|
24
|
+
createInterruptBinding,
|
|
25
|
+
rehydrateInterruptRequest,
|
|
26
|
+
} from '../../interrupt-definition'
|
|
27
|
+
import { readGenericInterruptContinuation } from '../../generic-interrupt-continuation'
|
|
28
|
+
import type {
|
|
29
|
+
GenericInterruptRequest,
|
|
30
|
+
InterruptDefinition,
|
|
31
|
+
} from '../../interrupt-definition'
|
|
21
32
|
import {
|
|
22
33
|
canonicalInterruptJson,
|
|
23
34
|
digestInterruptJson,
|
|
@@ -47,6 +58,7 @@ import {
|
|
|
47
58
|
convertMessagesToModelMessages,
|
|
48
59
|
generateMessageId,
|
|
49
60
|
modelMessageToUIMessage,
|
|
61
|
+
safeJsonStringify,
|
|
50
62
|
} from './messages'
|
|
51
63
|
import { MiddlewareRunner } from './middleware/compose'
|
|
52
64
|
import { getRunDetached } from './middleware/run-store'
|
|
@@ -89,6 +101,7 @@ import type {
|
|
|
89
101
|
SchemaInput,
|
|
90
102
|
StreamChunk,
|
|
91
103
|
StructuredOutputCompleteEvent,
|
|
104
|
+
StructuredOutputPart,
|
|
92
105
|
StructuredOutputStream,
|
|
93
106
|
TextMessageContentEvent,
|
|
94
107
|
TextOptions,
|
|
@@ -104,10 +117,13 @@ import type {
|
|
|
104
117
|
ChatMiddleware,
|
|
105
118
|
ChatMiddlewareConfig,
|
|
106
119
|
ChatMiddlewareContext,
|
|
120
|
+
ChatResumeGenericResolution,
|
|
107
121
|
ChatResumeToolState,
|
|
122
|
+
InterruptResolutionCollection,
|
|
108
123
|
SandboxFileHookEvent,
|
|
109
124
|
StructuredOutputMiddlewareConfig,
|
|
110
125
|
} from './middleware/types'
|
|
126
|
+
import { provideGenericInterruptDefinitionRegistry } from './middleware/generic-interrupts'
|
|
111
127
|
import type { CheckCoverage } from './middleware/builder'
|
|
112
128
|
import type { SystemPrompt } from '../../system-prompts'
|
|
113
129
|
import type { InternalLogger } from '../../logger/internal-logger'
|
|
@@ -204,74 +220,11 @@ function normalizePublicInterruptBinding(
|
|
|
204
220
|
value: unknown,
|
|
205
221
|
expectedInterruptId: string,
|
|
206
222
|
): InterruptBinding | undefined {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
)
|
|
213
|
-
if (
|
|
214
|
-
binding.interruptId !== expectedInterruptId ||
|
|
215
|
-
// A binding version we don't recognise belongs to another producer. Drop
|
|
216
|
-
// it rather than reading our fields out of it.
|
|
217
|
-
(binding.v !== undefined && binding.v !== INTERRUPT_BINDING_VERSION) ||
|
|
218
|
-
typeof binding.interruptedRunId !== 'string' ||
|
|
219
|
-
typeof binding.generation !== 'number' ||
|
|
220
|
-
!Number.isInteger(binding.generation) ||
|
|
221
|
-
binding.generation < 0 ||
|
|
222
|
-
typeof binding.responseSchemaHash !== 'string' ||
|
|
223
|
-
(binding.expiresAt !== undefined && typeof binding.expiresAt !== 'string')
|
|
224
|
-
) {
|
|
225
|
-
return undefined
|
|
226
|
-
}
|
|
227
|
-
const base = {
|
|
228
|
-
v: INTERRUPT_BINDING_VERSION,
|
|
229
|
-
interruptId: binding.interruptId,
|
|
230
|
-
interruptedRunId: binding.interruptedRunId,
|
|
231
|
-
generation: binding.generation,
|
|
232
|
-
responseSchemaHash: binding.responseSchemaHash,
|
|
233
|
-
...(typeof binding.expiresAt === 'string'
|
|
234
|
-
? { expiresAt: binding.expiresAt }
|
|
235
|
-
: {}),
|
|
236
|
-
}
|
|
237
|
-
if (binding.kind === 'generic') {
|
|
238
|
-
return { kind: binding.kind, ...base }
|
|
239
|
-
}
|
|
240
|
-
if (
|
|
241
|
-
typeof binding.toolName !== 'string' ||
|
|
242
|
-
typeof binding.toolCallId !== 'string'
|
|
243
|
-
) {
|
|
244
|
-
return undefined
|
|
245
|
-
}
|
|
246
|
-
if (
|
|
247
|
-
binding.kind === 'client-tool-execution' &&
|
|
248
|
-
typeof binding.outputSchemaHash === 'string'
|
|
249
|
-
) {
|
|
250
|
-
return {
|
|
251
|
-
kind: binding.kind,
|
|
252
|
-
...base,
|
|
253
|
-
toolName: binding.toolName,
|
|
254
|
-
toolCallId: binding.toolCallId,
|
|
255
|
-
outputSchemaHash: binding.outputSchemaHash,
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (
|
|
259
|
-
binding.kind === 'tool-approval' &&
|
|
260
|
-
Object.prototype.hasOwnProperty.call(binding, 'originalArgs') &&
|
|
261
|
-
typeof binding.inputSchemaHash === 'string' &&
|
|
262
|
-
typeof binding.approvalSchemaHash === 'string'
|
|
263
|
-
) {
|
|
264
|
-
return {
|
|
265
|
-
kind: binding.kind,
|
|
266
|
-
...base,
|
|
267
|
-
toolName: binding.toolName,
|
|
268
|
-
toolCallId: binding.toolCallId,
|
|
269
|
-
originalArgs: binding.originalArgs,
|
|
270
|
-
inputSchemaHash: binding.inputSchemaHash,
|
|
271
|
-
approvalSchemaHash: binding.approvalSchemaHash,
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return undefined
|
|
223
|
+
return readInterruptBinding({
|
|
224
|
+
id: expectedInterruptId,
|
|
225
|
+
reason: '',
|
|
226
|
+
metadata: { [INTERRUPT_BINDING_METADATA_KEY]: value },
|
|
227
|
+
})
|
|
275
228
|
}
|
|
276
229
|
|
|
277
230
|
// The leaf context-inference primitives (KnownContext, MergeContext,
|
|
@@ -310,33 +263,133 @@ type InferredContext<TTools, TMiddleware> = [
|
|
|
310
263
|
? unknown
|
|
311
264
|
: ContextFromInputs<TTools, TMiddleware>
|
|
312
265
|
|
|
313
|
-
type
|
|
314
|
-
|
|
266
|
+
type RegistryInterrupt<
|
|
267
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>,
|
|
268
|
+
> = [TInterrupts[number]] extends [never] ? never : TInterrupts[number]
|
|
269
|
+
|
|
270
|
+
type DuplicateInterruptDefinitionId<
|
|
271
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>,
|
|
272
|
+
TSeenIds extends string = never,
|
|
273
|
+
> = TInterrupts extends readonly [infer THead, ...infer TTail]
|
|
274
|
+
? THead extends InterruptDefinition<infer TId, any, any, any>
|
|
275
|
+
? string extends TId
|
|
276
|
+
? TTail extends ReadonlyArray<InterruptDefinition<any, any, any, any>>
|
|
277
|
+
? DuplicateInterruptDefinitionId<TTail, TSeenIds>
|
|
278
|
+
: never
|
|
279
|
+
: TId extends TSeenIds
|
|
280
|
+
? TId
|
|
281
|
+
: TTail extends ReadonlyArray<InterruptDefinition<any, any, any, any>>
|
|
282
|
+
? DuplicateInterruptDefinitionId<TTail, TSeenIds | TId>
|
|
283
|
+
: never
|
|
284
|
+
: never
|
|
285
|
+
: never
|
|
286
|
+
|
|
287
|
+
type CheckUniqueInterruptDefinitions<
|
|
288
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>,
|
|
289
|
+
> = [DuplicateInterruptDefinitionId<TInterrupts>] extends [never]
|
|
290
|
+
? unknown
|
|
291
|
+
: {
|
|
292
|
+
readonly '✖ Duplicate interrupt definition id in chat({ interrupts }).': never
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
type InlineChatContext<TTools, TContext> = MergeContext<
|
|
296
|
+
ContextFromArray<NonNullable<TTools>>,
|
|
297
|
+
TContext
|
|
298
|
+
>
|
|
299
|
+
|
|
300
|
+
type RegistryChatMiddleware<
|
|
301
|
+
TContext,
|
|
302
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>,
|
|
303
|
+
> = ChatMiddleware<TContext, RegistryInterrupt<TInterrupts>>
|
|
304
|
+
|
|
305
|
+
type MiddlewareInterruptDefinitions<TMiddleware> =
|
|
306
|
+
TMiddleware extends ReadonlyArray<infer TMiddlewareItem>
|
|
307
|
+
? TMiddlewareItem extends ChatMiddleware<any, infer TDefinitions>
|
|
308
|
+
? TDefinitions
|
|
309
|
+
: never
|
|
310
|
+
: never
|
|
311
|
+
|
|
312
|
+
type IsAny<TValue> = 0 extends 1 & TValue ? true : false
|
|
313
|
+
|
|
314
|
+
type CheckInterruptRegistry<
|
|
315
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>,
|
|
316
|
+
TMiddleware,
|
|
317
|
+
> =
|
|
318
|
+
IsAny<MiddlewareInterruptDefinitions<TMiddleware>> extends true
|
|
319
|
+
? unknown
|
|
320
|
+
: [MiddlewareInterruptDefinitions<TMiddleware>] extends [never]
|
|
321
|
+
? unknown
|
|
322
|
+
: [
|
|
323
|
+
Exclude<
|
|
324
|
+
MiddlewareInterruptDefinitions<TMiddleware>,
|
|
325
|
+
RegistryInterrupt<TInterrupts>
|
|
326
|
+
>,
|
|
327
|
+
] extends [never]
|
|
328
|
+
? unknown
|
|
329
|
+
: {
|
|
330
|
+
readonly '✖ Middleware emits an interrupt definition that is not registered in chat({ interrupts }).': never
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
type RuntimeContextOption<TTools, TMiddleware, TContext> = [
|
|
334
|
+
MergeContext<ContextFromInputs<TTools, TMiddleware>, TContext>,
|
|
315
335
|
] extends [never]
|
|
316
|
-
? { context?:
|
|
317
|
-
: undefined extends
|
|
318
|
-
|
|
319
|
-
|
|
336
|
+
? { context?: TContext }
|
|
337
|
+
: undefined extends MergeContext<
|
|
338
|
+
ContextFromInputs<TTools, TMiddleware>,
|
|
339
|
+
TContext
|
|
340
|
+
>
|
|
341
|
+
? {
|
|
342
|
+
context?: MergeContext<ContextFromInputs<TTools, TMiddleware>, TContext>
|
|
343
|
+
}
|
|
344
|
+
: {
|
|
345
|
+
context: MergeContext<ContextFromInputs<TTools, TMiddleware>, TContext>
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
type ExactMiddlewareOption<
|
|
349
|
+
TTools,
|
|
350
|
+
TContext,
|
|
351
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>,
|
|
352
|
+
TMiddleware extends Array<unknown> | undefined,
|
|
353
|
+
> = [TMiddleware] extends [undefined]
|
|
354
|
+
? Array<
|
|
355
|
+
RegistryChatMiddleware<
|
|
356
|
+
InlineChatContext<TTools, TContext>,
|
|
357
|
+
NoInfer<TInterrupts>
|
|
358
|
+
>
|
|
359
|
+
>
|
|
360
|
+
: TMiddleware &
|
|
361
|
+
(TMiddleware extends Array<
|
|
362
|
+
RegistryChatMiddleware<
|
|
363
|
+
InlineChatContext<TTools, NoInfer<TContext>>,
|
|
364
|
+
NoInfer<TInterrupts>
|
|
365
|
+
>
|
|
366
|
+
>
|
|
367
|
+
? Array<
|
|
368
|
+
RegistryChatMiddleware<
|
|
369
|
+
InlineChatContext<TTools, TContext>,
|
|
370
|
+
NoInfer<TInterrupts>
|
|
371
|
+
>
|
|
372
|
+
>
|
|
373
|
+
: CheckInterruptRegistry<TInterrupts, TMiddleware>) &
|
|
374
|
+
CheckCoverage<Extract<TMiddleware, ReadonlyArray<AnyChatMiddleware>>>
|
|
320
375
|
|
|
321
376
|
type TextActivityOptionsWithContext<
|
|
322
377
|
TAdapter extends AnyTextAdapter,
|
|
323
378
|
TSchema extends SchemaInput | undefined,
|
|
324
379
|
TStream extends boolean,
|
|
325
380
|
TTools extends TextActivityOptions<TAdapter, TSchema, TStream, any>['tools'],
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
any
|
|
331
|
-
>['middleware'],
|
|
381
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> =
|
|
382
|
+
[],
|
|
383
|
+
TContext = unknown,
|
|
384
|
+
TMiddleware extends Array<unknown> | undefined = undefined,
|
|
332
385
|
> = Omit<
|
|
333
386
|
TextActivityOptions<TAdapter, TSchema, TStream, any>,
|
|
334
|
-
'tools' | 'middleware' | 'context'
|
|
387
|
+
'tools' | 'middleware' | 'context' | 'interrupts'
|
|
335
388
|
> & {
|
|
336
389
|
tools?: TTools
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
} &
|
|
390
|
+
interrupts?: TInterrupts & CheckUniqueInterruptDefinitions<TInterrupts>
|
|
391
|
+
middleware?: ExactMiddlewareOption<TTools, TContext, TInterrupts, TMiddleware>
|
|
392
|
+
} & RuntimeContextOption<TTools, TMiddleware, TContext>
|
|
340
393
|
|
|
341
394
|
// ===========================
|
|
342
395
|
// Activity Options Type
|
|
@@ -494,6 +547,11 @@ export interface TextActivityOptions<
|
|
|
494
547
|
* ```
|
|
495
548
|
*/
|
|
496
549
|
middleware?: Array<ChatMiddleware<TContext>>
|
|
550
|
+
/**
|
|
551
|
+
* First-party generic interrupt definitions for this chat call.
|
|
552
|
+
* Register the same definitions on the client to type payloads and answers.
|
|
553
|
+
*/
|
|
554
|
+
interrupts?: ReadonlyArray<InterruptDefinition<any, any, any, any>>
|
|
497
555
|
/**
|
|
498
556
|
* Runtime context value passed to middleware hooks and server tools.
|
|
499
557
|
*/
|
|
@@ -534,28 +592,21 @@ export function createChatOptions<
|
|
|
534
592
|
TStream,
|
|
535
593
|
any
|
|
536
594
|
>['tools'] = TextActivityOptions<TAdapter, TSchema, TStream, any>['tools'],
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
>['middleware'] = TextActivityOptions<
|
|
543
|
-
TAdapter,
|
|
544
|
-
TSchema,
|
|
545
|
-
TStream,
|
|
546
|
-
any
|
|
547
|
-
>['middleware'],
|
|
595
|
+
const TInterrupts extends ReadonlyArray<
|
|
596
|
+
InterruptDefinition<any, any, any, any>
|
|
597
|
+
> = [],
|
|
598
|
+
TContext = unknown,
|
|
599
|
+
const TMiddleware extends Array<unknown> | undefined = undefined,
|
|
548
600
|
>(
|
|
549
601
|
options: TextActivityOptionsWithContext<
|
|
550
602
|
TAdapter,
|
|
551
603
|
TSchema,
|
|
552
604
|
TStream,
|
|
553
605
|
TTools,
|
|
606
|
+
TInterrupts,
|
|
607
|
+
TContext,
|
|
554
608
|
TMiddleware
|
|
555
609
|
>,
|
|
556
|
-
// Preserve the concrete `tools` tuple on the returned options (so a later
|
|
557
|
-
// `chat({ ...opts })` still narrows tool-call events to the tool names)
|
|
558
|
-
// while threading the inferred runtime context like the bare options type.
|
|
559
610
|
): Omit<
|
|
560
611
|
TextActivityOptions<
|
|
561
612
|
TAdapter,
|
|
@@ -563,8 +614,12 @@ export function createChatOptions<
|
|
|
563
614
|
TStream,
|
|
564
615
|
InferredContext<TTools, TMiddleware>
|
|
565
616
|
>,
|
|
566
|
-
'tools'
|
|
567
|
-
> & {
|
|
617
|
+
'tools' | 'middleware' | 'interrupts'
|
|
618
|
+
> & {
|
|
619
|
+
tools?: TTools
|
|
620
|
+
interrupts?: TInterrupts
|
|
621
|
+
middleware?: ExactMiddlewareOption<TTools, TContext, TInterrupts, TMiddleware>
|
|
622
|
+
} {
|
|
568
623
|
return options
|
|
569
624
|
}
|
|
570
625
|
|
|
@@ -628,7 +683,7 @@ interface TextEngineConfig<
|
|
|
628
683
|
adapter: TAdapter
|
|
629
684
|
systemPrompts?: Array<SystemPrompt>
|
|
630
685
|
params: TParams
|
|
631
|
-
middleware?: Array<
|
|
686
|
+
middleware?: Array<AnyChatMiddleware>
|
|
632
687
|
context?: TContext
|
|
633
688
|
/**
|
|
634
689
|
* If set, after the agent loop finishes the engine runs a
|
|
@@ -712,12 +767,18 @@ class TextEngine<
|
|
|
712
767
|
>,
|
|
713
768
|
> {
|
|
714
769
|
private readonly adapter: TAdapter
|
|
770
|
+
private readonly interruptDefinitions: ReadonlyMap<
|
|
771
|
+
string,
|
|
772
|
+
InterruptDefinition<any, any, any, any>
|
|
773
|
+
>
|
|
715
774
|
private params: TParams
|
|
716
775
|
private systemPrompts: Array<SystemPrompt>
|
|
717
776
|
private tools: Array<AnyRuntimeTool>
|
|
718
777
|
private readonly loopStrategy: AgentLoopStrategy
|
|
719
778
|
private toolCallManager: ToolCallManager<ReadonlyArray<AnyTool>, TContext>
|
|
720
779
|
private readonly lazyToolManager: LazyToolManager
|
|
780
|
+
/** A public interruption terminal must always have this run's start event. */
|
|
781
|
+
private hasPublicRunStarted = false
|
|
721
782
|
private readonly initialMessageCount: number
|
|
722
783
|
private readonly requestId: string
|
|
723
784
|
private readonly streamId: string
|
|
@@ -749,6 +810,9 @@ class TextEngine<
|
|
|
749
810
|
private finishedEvent: RunFinishedEvent | null = null
|
|
750
811
|
private readonly streamedToolErrorResults = new Map<string, ToolResult>()
|
|
751
812
|
private deferredToolCallRunFinishedChunks: Array<StreamChunk> = []
|
|
813
|
+
/** The model terminal is held until afterModel can choose an interrupt. */
|
|
814
|
+
private deferredModelRunFinishedChunks: Array<StreamChunk> = []
|
|
815
|
+
|
|
752
816
|
private earlyTermination = false
|
|
753
817
|
private toolPhase: ToolPhaseResult = 'continue'
|
|
754
818
|
private cyclePhase: CyclePhase = 'processText'
|
|
@@ -759,6 +823,14 @@ class TextEngine<
|
|
|
759
823
|
private readonly resumeClientToolResults = new Map<string, any>()
|
|
760
824
|
private readonly resumeDeniedToolResults = new Map<string, unknown>()
|
|
761
825
|
private readonly resumeCancelledToolCallIds = new Set<string>()
|
|
826
|
+
private readonly resumeGenericInterrupts = new Map<
|
|
827
|
+
string,
|
|
828
|
+
ChatResumeGenericResolution
|
|
829
|
+
>()
|
|
830
|
+
private readonly resumeGenericInterruptRequests = new Map<
|
|
831
|
+
string,
|
|
832
|
+
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
833
|
+
>()
|
|
762
834
|
|
|
763
835
|
// AG-UI protocol IDs
|
|
764
836
|
private readonly threadId: string
|
|
@@ -766,7 +838,10 @@ class TextEngine<
|
|
|
766
838
|
private readonly parentRunIdOverride?: string
|
|
767
839
|
|
|
768
840
|
// Middleware support
|
|
769
|
-
private readonly middlewareRunner: MiddlewareRunner<
|
|
841
|
+
private readonly middlewareRunner: MiddlewareRunner<
|
|
842
|
+
TContext,
|
|
843
|
+
InterruptDefinition<any, any, any, any>
|
|
844
|
+
>
|
|
770
845
|
private readonly middlewareCtx: ChatMiddlewareContext<TContext>
|
|
771
846
|
private readonly sandboxFileQueue: Array<StreamChunk> = []
|
|
772
847
|
private readonly deferredPromises: Array<Promise<unknown>> = []
|
|
@@ -788,8 +863,13 @@ class TextEngine<
|
|
|
788
863
|
private readonly logger: InternalLogger
|
|
789
864
|
|
|
790
865
|
// Structured-output finalization state (populated by runStructuredFinalization)
|
|
791
|
-
private structuredOutputResult: {
|
|
792
|
-
|
|
866
|
+
private structuredOutputResult: {
|
|
867
|
+
data: unknown
|
|
868
|
+
rawText: string
|
|
869
|
+
reasoning?: string
|
|
870
|
+
} | null = null
|
|
871
|
+
private structuredOutputMessageId: string | null = null
|
|
872
|
+
private structuredOutputMessageCreatedAt: Date | null = null
|
|
793
873
|
// Native combined mode: tracks whether we've already emitted the synthetic
|
|
794
874
|
// `structured-output.start` event before the schema-constrained final-turn
|
|
795
875
|
// text begins streaming. The event must precede the first
|
|
@@ -826,6 +906,15 @@ class TextEngine<
|
|
|
826
906
|
) {
|
|
827
907
|
this.logger = logger
|
|
828
908
|
this.adapter = config.adapter
|
|
909
|
+
this.interruptDefinitions = new Map(
|
|
910
|
+
(
|
|
911
|
+
(
|
|
912
|
+
config.params as TParams & {
|
|
913
|
+
interrupts?: ReadonlyArray<InterruptDefinition<any, any, any, any>>
|
|
914
|
+
}
|
|
915
|
+
).interrupts ?? []
|
|
916
|
+
).map((definition) => [definition.id, definition]),
|
|
917
|
+
)
|
|
829
918
|
this.finalStructuredOutput = config.finalStructuredOutput
|
|
830
919
|
this.params = config.params
|
|
831
920
|
this.systemPrompts = config.params.systemPrompts || []
|
|
@@ -878,7 +967,9 @@ class TextEngine<
|
|
|
878
967
|
// handleStreamChunk processes raw chunks BEFORE middleware, so internal
|
|
879
968
|
// state management sees extended fields (finishReason, delta, toolCallName, etc.).
|
|
880
969
|
// The strip middleware ensures the yielded public stream is AG-UI spec-compliant.
|
|
881
|
-
const allMiddleware: Array<
|
|
970
|
+
const allMiddleware: Array<
|
|
971
|
+
ChatMiddleware<TContext, InterruptDefinition<any, any, any, any>>
|
|
972
|
+
> = [
|
|
882
973
|
devtoolsMiddleware(),
|
|
883
974
|
...(config.middleware || []),
|
|
884
975
|
stripToSpecMiddleware(),
|
|
@@ -957,6 +1048,10 @@ class TextEngine<
|
|
|
957
1048
|
},
|
|
958
1049
|
})
|
|
959
1050
|
|
|
1051
|
+
provideGenericInterruptDefinitionRegistry(this.middlewareCtx, {
|
|
1052
|
+
definitions: this.interruptDefinitions,
|
|
1053
|
+
})
|
|
1054
|
+
|
|
960
1055
|
// Provide the internal SandboxRuntime capability so harness adapters and
|
|
961
1056
|
// sandbox middleware can emit file events. The sink logs, fans the event
|
|
962
1057
|
// out through the middleware `onFile*` hooks (fire-and-forget), and queues
|
|
@@ -1048,10 +1143,25 @@ class TextEngine<
|
|
|
1048
1143
|
)
|
|
1049
1144
|
this.applyMiddlewareConfig(transformedConfig)
|
|
1050
1145
|
await this.applyEphemeralInterruptResume(transformedConfig)
|
|
1146
|
+
await this.applyDurableGenericInterruptResolution()
|
|
1051
1147
|
|
|
1052
1148
|
// Run onStart (devtools middleware emits text:request:started and initial messages here)
|
|
1053
1149
|
await this.middlewareRunner.runOnStart(this.middlewareCtx)
|
|
1054
1150
|
|
|
1151
|
+
if (this.earlyTermination) {
|
|
1152
|
+
yield* this.emitSuccessfulEarlyTermination()
|
|
1153
|
+
if (!this.terminalHookCalled) {
|
|
1154
|
+
this.terminalHookCalled = true
|
|
1155
|
+
await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
|
|
1156
|
+
finishReason: this.lastFinishReason,
|
|
1157
|
+
duration: Date.now() - this.streamStartTime,
|
|
1158
|
+
content: this.accumulatedContent,
|
|
1159
|
+
usage: this.finishedEvent?.usage,
|
|
1160
|
+
})
|
|
1161
|
+
}
|
|
1162
|
+
return
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1055
1165
|
const pendingPhase = yield* this.checkForPendingToolCalls()
|
|
1056
1166
|
if (pendingPhase === 'wait') {
|
|
1057
1167
|
return
|
|
@@ -1095,7 +1205,35 @@ class TextEngine<
|
|
|
1095
1205
|
)
|
|
1096
1206
|
this.applyMiddlewareConfig(iterTransformedConfig)
|
|
1097
1207
|
|
|
1208
|
+
if (
|
|
1209
|
+
yield* this.emitBoundaryInterrupts(
|
|
1210
|
+
'beforeModel',
|
|
1211
|
+
this.createSyntheticFinishedEvent(),
|
|
1212
|
+
)
|
|
1213
|
+
) {
|
|
1214
|
+
this.setToolPhase('wait')
|
|
1215
|
+
return
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1098
1218
|
yield* this.streamModelResponse()
|
|
1219
|
+
|
|
1220
|
+
if (
|
|
1221
|
+
yield* this.emitBoundaryInterrupts(
|
|
1222
|
+
'afterModel',
|
|
1223
|
+
this.finishedEvent ?? this.createSyntheticFinishedEvent(),
|
|
1224
|
+
)
|
|
1225
|
+
) {
|
|
1226
|
+
this.setToolPhase('wait')
|
|
1227
|
+
return
|
|
1228
|
+
}
|
|
1229
|
+
if (this.shouldExecuteToolPhase()) {
|
|
1230
|
+
this.deferredToolCallRunFinishedChunks.push(
|
|
1231
|
+
...this.deferredModelRunFinishedChunks,
|
|
1232
|
+
)
|
|
1233
|
+
this.deferredModelRunFinishedChunks = []
|
|
1234
|
+
} else {
|
|
1235
|
+
yield* this.flushDeferredModelRunFinishedChunks()
|
|
1236
|
+
}
|
|
1099
1237
|
} else {
|
|
1100
1238
|
yield* this.processToolCalls()
|
|
1101
1239
|
}
|
|
@@ -1159,7 +1297,7 @@ class TextEngine<
|
|
|
1159
1297
|
duration: Date.now() - this.streamStartTime,
|
|
1160
1298
|
})
|
|
1161
1299
|
} else {
|
|
1162
|
-
this.
|
|
1300
|
+
this.addTerminalAssistantMessages()
|
|
1163
1301
|
this.terminalHookCalled = true
|
|
1164
1302
|
await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
|
|
1165
1303
|
finishReason: this.lastFinishReason,
|
|
@@ -1403,6 +1541,7 @@ class TextEngine<
|
|
|
1403
1541
|
typeof startValue.messageId === 'string'
|
|
1404
1542
|
) {
|
|
1405
1543
|
this.combinedStructuredMessageId = startValue.messageId
|
|
1544
|
+
this.captureStructuredOutputMessageIdentity(startValue.messageId)
|
|
1406
1545
|
}
|
|
1407
1546
|
}
|
|
1408
1547
|
|
|
@@ -1420,6 +1559,11 @@ class TextEngine<
|
|
|
1420
1559
|
this.structuredOutputResult = { data: object, rawText: parsed.raw }
|
|
1421
1560
|
this.combinedCompleteEmitted = true
|
|
1422
1561
|
const value = chunk.value
|
|
1562
|
+
const completeMessageId = readCustomEventMessageId(value)
|
|
1563
|
+
if (completeMessageId) {
|
|
1564
|
+
this.combinedStructuredMessageId = completeMessageId
|
|
1565
|
+
this.captureStructuredOutputMessageIdentity(completeMessageId)
|
|
1566
|
+
}
|
|
1423
1567
|
if (object !== parsed.object && value && typeof value === 'object') {
|
|
1424
1568
|
outboundChunk = { ...chunk, value: { ...value, object } }
|
|
1425
1569
|
}
|
|
@@ -1482,10 +1626,17 @@ class TextEngine<
|
|
|
1482
1626
|
) {
|
|
1483
1627
|
continue
|
|
1484
1628
|
}
|
|
1629
|
+
if (outputChunk.type === EventType.RUN_FINISHED) {
|
|
1630
|
+
this.deferredModelRunFinishedChunks.push(outputChunk)
|
|
1631
|
+
continue
|
|
1632
|
+
}
|
|
1485
1633
|
if (this.shouldDeferToolCallRunFinished(outputChunk)) {
|
|
1486
1634
|
this.deferredToolCallRunFinishedChunks.push(outputChunk)
|
|
1487
1635
|
continue
|
|
1488
1636
|
}
|
|
1637
|
+
if (outputChunk.type === EventType.RUN_STARTED) {
|
|
1638
|
+
this.hasPublicRunStarted = true
|
|
1639
|
+
}
|
|
1489
1640
|
this.logger.output(`type=${outputChunk.type}`, { chunk: outputChunk })
|
|
1490
1641
|
yield outputChunk
|
|
1491
1642
|
this.middlewareCtx.chunkIndex++
|
|
@@ -1586,6 +1737,11 @@ class TextEngine<
|
|
|
1586
1737
|
}
|
|
1587
1738
|
}
|
|
1588
1739
|
|
|
1740
|
+
private captureStructuredOutputMessageIdentity(messageId: string): void {
|
|
1741
|
+
this.structuredOutputMessageId = messageId
|
|
1742
|
+
this.structuredOutputMessageCreatedAt ??= new Date()
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1589
1745
|
private handleToolCallStartEvent(chunk: ToolCallStartEvent): void {
|
|
1590
1746
|
if (
|
|
1591
1747
|
typeof chunk.parentMessageId === 'string' &&
|
|
@@ -1763,6 +1919,18 @@ class TextEngine<
|
|
|
1763
1919
|
return 'continue'
|
|
1764
1920
|
}
|
|
1765
1921
|
|
|
1922
|
+
this.middlewareCtx.phase = 'beforeTools'
|
|
1923
|
+
if (
|
|
1924
|
+
yield* this.emitBoundaryInterrupts(
|
|
1925
|
+
'beforeTools',
|
|
1926
|
+
finishEvent,
|
|
1927
|
+
executablePendingCalls,
|
|
1928
|
+
)
|
|
1929
|
+
) {
|
|
1930
|
+
this.setToolPhase('wait')
|
|
1931
|
+
return 'wait'
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1766
1934
|
const { approvals, clientToolResults } = this.collectClientState()
|
|
1767
1935
|
|
|
1768
1936
|
const generator = executeToolCalls(
|
|
@@ -1930,6 +2098,17 @@ class TextEngine<
|
|
|
1930
2098
|
}
|
|
1931
2099
|
this.middlewareCtx.phase = 'beforeTools'
|
|
1932
2100
|
|
|
2101
|
+
if (
|
|
2102
|
+
yield* this.emitBoundaryInterrupts(
|
|
2103
|
+
'beforeTools',
|
|
2104
|
+
finishEvent,
|
|
2105
|
+
executableToolCalls,
|
|
2106
|
+
)
|
|
2107
|
+
) {
|
|
2108
|
+
this.setToolPhase('wait')
|
|
2109
|
+
return
|
|
2110
|
+
}
|
|
2111
|
+
|
|
1933
2112
|
const { approvals, clientToolResults } = this.collectClientState()
|
|
1934
2113
|
|
|
1935
2114
|
const generator = executeToolCalls(
|
|
@@ -1997,15 +2176,36 @@ class TextEngine<
|
|
|
1997
2176
|
needsClientExecution: executionResult.needsClientExecution,
|
|
1998
2177
|
})
|
|
1999
2178
|
|
|
2179
|
+
const afterToolBoundaryChunks = this.buildToolResultChunks(
|
|
2180
|
+
allResults,
|
|
2181
|
+
finishEvent,
|
|
2182
|
+
)
|
|
2183
|
+
const afterToolRequests =
|
|
2184
|
+
await this.middlewareRunner.runOnInterruptBoundary(
|
|
2185
|
+
this.middlewareCtx as ChatMiddlewareContext<TContext> & {
|
|
2186
|
+
phase: 'afterTools'
|
|
2187
|
+
},
|
|
2188
|
+
)
|
|
2189
|
+
if (afterToolRequests.length > 0) {
|
|
2190
|
+
for (const chunk of afterToolBoundaryChunks) {
|
|
2191
|
+
yield* this.pipeThroughMiddleware(chunk)
|
|
2192
|
+
}
|
|
2193
|
+
yield* this.emitBoundaryInterrupts(
|
|
2194
|
+
'afterTools',
|
|
2195
|
+
finishEvent,
|
|
2196
|
+
toolCalls,
|
|
2197
|
+
afterToolRequests,
|
|
2198
|
+
)
|
|
2199
|
+
this.setToolPhase('wait')
|
|
2200
|
+
return
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2000
2203
|
if (
|
|
2001
2204
|
executionResult.needsApproval.length > 0 ||
|
|
2002
2205
|
executionResult.needsClientExecution.length > 0
|
|
2003
2206
|
) {
|
|
2004
2207
|
if (allResults.length > 0) {
|
|
2005
|
-
for (const chunk of
|
|
2006
|
-
allResults,
|
|
2007
|
-
finishEvent,
|
|
2008
|
-
)) {
|
|
2208
|
+
for (const chunk of afterToolBoundaryChunks) {
|
|
2009
2209
|
yield* this.pipeThroughMiddleware(chunk)
|
|
2010
2210
|
}
|
|
2011
2211
|
}
|
|
@@ -2021,7 +2221,7 @@ class TextEngine<
|
|
|
2021
2221
|
|
|
2022
2222
|
yield* this.flushDeferredToolCallRunFinishedChunks()
|
|
2023
2223
|
|
|
2024
|
-
const toolResultChunks =
|
|
2224
|
+
const toolResultChunks = afterToolBoundaryChunks
|
|
2025
2225
|
|
|
2026
2226
|
for (const chunk of toolResultChunks) {
|
|
2027
2227
|
yield* this.pipeThroughMiddleware(chunk)
|
|
@@ -2061,6 +2261,48 @@ class TextEngine<
|
|
|
2061
2261
|
this.deferredToolCallRunFinishedChunks = []
|
|
2062
2262
|
}
|
|
2063
2263
|
|
|
2264
|
+
private *flushDeferredModelRunFinishedChunks(): Generator<StreamChunk> {
|
|
2265
|
+
for (const chunk of this.deferredModelRunFinishedChunks) {
|
|
2266
|
+
this.logger.output(`type=${chunk.type}`, { chunk })
|
|
2267
|
+
yield chunk
|
|
2268
|
+
this.middlewareCtx.chunkIndex++
|
|
2269
|
+
}
|
|
2270
|
+
this.deferredModelRunFinishedChunks = []
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
private async *emitSyntheticRunStarted(
|
|
2274
|
+
finishEvent: RunFinishedEvent,
|
|
2275
|
+
): AsyncGenerator<StreamChunk, void, void> {
|
|
2276
|
+
if (this.hasPublicRunStarted) return
|
|
2277
|
+
yield* this.pipeThroughMiddleware({
|
|
2278
|
+
type: EventType.RUN_STARTED,
|
|
2279
|
+
runId: finishEvent.runId,
|
|
2280
|
+
threadId: finishEvent.threadId,
|
|
2281
|
+
model: finishEvent.model,
|
|
2282
|
+
timestamp: Date.now(),
|
|
2283
|
+
})
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
private async *emitSuccessfulEarlyTermination(): AsyncGenerator<
|
|
2287
|
+
StreamChunk,
|
|
2288
|
+
void,
|
|
2289
|
+
void
|
|
2290
|
+
> {
|
|
2291
|
+
// `stop` is a finished run, not another tool cycle. `tool_calls` here
|
|
2292
|
+
// makes the client auto-send after afterTools, so reject looks stuck.
|
|
2293
|
+
this.lastFinishReason = 'stop'
|
|
2294
|
+
const finishEvent = {
|
|
2295
|
+
...this.createSyntheticFinishedEvent(),
|
|
2296
|
+
finishReason: 'stop' as const,
|
|
2297
|
+
}
|
|
2298
|
+
yield* this.emitSyntheticRunStarted(finishEvent)
|
|
2299
|
+
yield* this.pipeThroughMiddleware({
|
|
2300
|
+
...finishEvent,
|
|
2301
|
+
timestamp: Date.now(),
|
|
2302
|
+
outcome: { type: 'success' },
|
|
2303
|
+
})
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2064
2306
|
private discardDeferredToolCallRunFinishedChunks(): void {
|
|
2065
2307
|
this.deferredToolCallRunFinishedChunks = []
|
|
2066
2308
|
}
|
|
@@ -2092,27 +2334,104 @@ class TextEngine<
|
|
|
2092
2334
|
this.middlewareCtx.messages = this.messages
|
|
2093
2335
|
}
|
|
2094
2336
|
|
|
2095
|
-
private
|
|
2337
|
+
private addTerminalAssistantMessages(): void {
|
|
2096
2338
|
this.finalizeCurrentThinkingStep()
|
|
2097
|
-
if (this.accumulatedThinking.length === 0) return
|
|
2098
2339
|
|
|
2099
|
-
const
|
|
2100
|
-
const
|
|
2340
|
+
const structuredResult = this.structuredOutputResult
|
|
2341
|
+
const raw = structuredResult
|
|
2342
|
+
? structuredResult.rawText || safeJsonStringify(structuredResult.data)
|
|
2343
|
+
: ''
|
|
2344
|
+
const structuredOutput: StructuredOutputPart | undefined = structuredResult
|
|
2345
|
+
? {
|
|
2346
|
+
type: 'structured-output',
|
|
2347
|
+
status: 'complete',
|
|
2348
|
+
data: structuredResult.data,
|
|
2349
|
+
partial: structuredResult.data,
|
|
2350
|
+
raw,
|
|
2351
|
+
...(structuredResult.reasoning !== undefined
|
|
2352
|
+
? { reasoning: structuredResult.reasoning }
|
|
2353
|
+
: {}),
|
|
2354
|
+
}
|
|
2355
|
+
: undefined
|
|
2356
|
+
const nativeCombined = this.finalStructuredOutput?.nativeCombined === true
|
|
2357
|
+
const eventSourced = this.finalStructuredOutput?.source === 'event'
|
|
2358
|
+
const structuredId =
|
|
2359
|
+
this.structuredOutputMessageId ??
|
|
2360
|
+
this.combinedStructuredMessageId ??
|
|
2361
|
+
this.currentMessageId ??
|
|
2362
|
+
this.createId('msg')
|
|
2363
|
+
// Codex, OpenCode, ACP, and grok-build reuse the last text messageId
|
|
2364
|
+
// on structured-output.complete. Split only when the event uses a
|
|
2365
|
+
// different id (Claude Code). Same-id output stays on one message.
|
|
2366
|
+
const splitStructuredMessage =
|
|
2367
|
+
Boolean(structuredOutput) &&
|
|
2368
|
+
(!nativeCombined || eventSourced) &&
|
|
2369
|
+
this.currentMessageId != null &&
|
|
2370
|
+
structuredId !== this.currentMessageId
|
|
2371
|
+
const messages = [...this.middlewareCtx.messages]
|
|
2372
|
+
const existingStructuredIndex = messages.findIndex(
|
|
2373
|
+
(message) => message.role === 'assistant' && message.id === structuredId,
|
|
2374
|
+
)
|
|
2375
|
+
const currentTurnAlreadyRecorded = messages.some(
|
|
2101
2376
|
(message) =>
|
|
2102
2377
|
message.role === 'assistant' && message.id === this.currentMessageId,
|
|
2103
2378
|
)
|
|
2104
|
-
|
|
2379
|
+
const thinking =
|
|
2380
|
+
this.accumulatedThinking.length > 0 ? this.accumulatedThinking : undefined
|
|
2381
|
+
const startedLength = messages.length
|
|
2382
|
+
|
|
2383
|
+
if (structuredOutput && existingStructuredIndex >= 0) {
|
|
2384
|
+
const existing = messages[existingStructuredIndex]
|
|
2385
|
+
if (existing) {
|
|
2386
|
+
messages[existingStructuredIndex] = {
|
|
2387
|
+
...existing,
|
|
2388
|
+
content: raw || existing.content,
|
|
2389
|
+
structuredOutput,
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
} else if (structuredOutput && !splitStructuredMessage) {
|
|
2393
|
+
if (!currentTurnAlreadyRecorded) {
|
|
2394
|
+
messages.push({
|
|
2395
|
+
role: 'assistant',
|
|
2396
|
+
content: this.accumulatedContent || raw || null,
|
|
2397
|
+
id: structuredId,
|
|
2398
|
+
createdAt:
|
|
2399
|
+
this.currentMessageCreatedAt ??
|
|
2400
|
+
this.structuredOutputMessageCreatedAt ??
|
|
2401
|
+
new Date(),
|
|
2402
|
+
structuredOutput,
|
|
2403
|
+
...(thinking ? { thinking } : {}),
|
|
2404
|
+
})
|
|
2405
|
+
}
|
|
2406
|
+
} else {
|
|
2407
|
+
if (
|
|
2408
|
+
!currentTurnAlreadyRecorded &&
|
|
2409
|
+
(this.accumulatedContent !== '' || thinking)
|
|
2410
|
+
) {
|
|
2411
|
+
messages.push({
|
|
2412
|
+
role: 'assistant',
|
|
2413
|
+
content: this.accumulatedContent || null,
|
|
2414
|
+
id: this.currentMessageId ?? this.createId('msg'),
|
|
2415
|
+
createdAt: this.currentMessageCreatedAt ?? new Date(),
|
|
2416
|
+
...(thinking ? { thinking } : {}),
|
|
2417
|
+
})
|
|
2418
|
+
}
|
|
2419
|
+
if (structuredOutput) {
|
|
2420
|
+
messages.push({
|
|
2421
|
+
role: 'assistant',
|
|
2422
|
+
content: raw || null,
|
|
2423
|
+
id: structuredId,
|
|
2424
|
+
createdAt: this.structuredOutputMessageCreatedAt ?? new Date(),
|
|
2425
|
+
structuredOutput,
|
|
2426
|
+
})
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2105
2429
|
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
id: this.currentMessageId ?? undefined,
|
|
2112
|
-
createdAt: this.currentMessageCreatedAt ?? undefined,
|
|
2113
|
-
thinking: this.accumulatedThinking,
|
|
2114
|
-
},
|
|
2115
|
-
]
|
|
2430
|
+
if (messages.length === startedLength && existingStructuredIndex < 0) {
|
|
2431
|
+
return
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
this.messages = messages
|
|
2116
2435
|
this.middlewareCtx.messages = this.messages
|
|
2117
2436
|
}
|
|
2118
2437
|
|
|
@@ -2206,9 +2525,17 @@ class TextEngine<
|
|
|
2206
2525
|
return { approvals, clientToolResults }
|
|
2207
2526
|
}
|
|
2208
2527
|
|
|
2528
|
+
private genericInterruptId(): string {
|
|
2529
|
+
return this.createId('interrupt')
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2209
2532
|
private buildActionableInterrupts(
|
|
2210
2533
|
approvals: Array<ApprovalRequest>,
|
|
2211
2534
|
clientRequests: Array<ClientToolRequest>,
|
|
2535
|
+
genericRequests: ReadonlyArray<
|
|
2536
|
+
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2537
|
+
> = [],
|
|
2538
|
+
genericInterruptIds: ReadonlyArray<string> = [],
|
|
2212
2539
|
): Array<Interrupt> {
|
|
2213
2540
|
const interrupts: Array<Interrupt> = []
|
|
2214
2541
|
|
|
@@ -2278,6 +2605,64 @@ class TextEngine<
|
|
|
2278
2605
|
})
|
|
2279
2606
|
}
|
|
2280
2607
|
|
|
2608
|
+
for (const [index, request] of genericRequests.entries()) {
|
|
2609
|
+
const batchIndex = interrupts.length
|
|
2610
|
+
const id = genericInterruptIds[index]
|
|
2611
|
+
if (!id) throw new Error('Generic interrupt id is unavailable.')
|
|
2612
|
+
const preEmission = createInterruptBinding(request, { batchIndex })
|
|
2613
|
+
interrupts.push({
|
|
2614
|
+
id,
|
|
2615
|
+
reason: request.reason,
|
|
2616
|
+
message: request.message,
|
|
2617
|
+
...(preEmission.descriptor.responseSchemaCanonicalJson !== undefined
|
|
2618
|
+
? {
|
|
2619
|
+
responseSchema: JSON.parse(
|
|
2620
|
+
preEmission.descriptor.responseSchemaCanonicalJson,
|
|
2621
|
+
),
|
|
2622
|
+
}
|
|
2623
|
+
: {}),
|
|
2624
|
+
...(request.expiresAt !== undefined
|
|
2625
|
+
? { expiresAt: request.expiresAt }
|
|
2626
|
+
: {}),
|
|
2627
|
+
metadata: {
|
|
2628
|
+
[interruptBindingMetadataKey]: {
|
|
2629
|
+
v: INTERRUPT_BINDING_VERSION,
|
|
2630
|
+
kind: 'generic',
|
|
2631
|
+
interruptId: id,
|
|
2632
|
+
definitionId: preEmission.descriptor.definitionId,
|
|
2633
|
+
key: preEmission.descriptor.key,
|
|
2634
|
+
batchIndex,
|
|
2635
|
+
...(request.expiresAt !== undefined
|
|
2636
|
+
? { expiresAt: request.expiresAt }
|
|
2637
|
+
: {}),
|
|
2638
|
+
...(preEmission.descriptor.payloadSchemaHash
|
|
2639
|
+
? {
|
|
2640
|
+
payloadSchemaHash: preEmission.descriptor.payloadSchemaHash,
|
|
2641
|
+
}
|
|
2642
|
+
: {}),
|
|
2643
|
+
...(preEmission.descriptor.responseSchemaHash !== undefined
|
|
2644
|
+
? {
|
|
2645
|
+
responseSchemaHash: preEmission.descriptor.responseSchemaHash,
|
|
2646
|
+
}
|
|
2647
|
+
: {}),
|
|
2648
|
+
},
|
|
2649
|
+
...(preEmission.payload !== undefined
|
|
2650
|
+
? { [INTERRUPT_PAYLOAD_METADATA_KEY]: preEmission.payload }
|
|
2651
|
+
: {}),
|
|
2652
|
+
},
|
|
2653
|
+
})
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
const ids = new Set<string>()
|
|
2657
|
+
for (const interrupt of interrupts) {
|
|
2658
|
+
if (ids.has(interrupt.id)) {
|
|
2659
|
+
throw new Error(
|
|
2660
|
+
`Duplicate interrupt id in final batch: ${interrupt.id}`,
|
|
2661
|
+
)
|
|
2662
|
+
}
|
|
2663
|
+
ids.add(interrupt.id)
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2281
2666
|
return interrupts
|
|
2282
2667
|
}
|
|
2283
2668
|
|
|
@@ -2285,13 +2670,22 @@ class TextEngine<
|
|
|
2285
2670
|
finishEvent: RunFinishedEvent,
|
|
2286
2671
|
approvals: Array<ApprovalRequest>,
|
|
2287
2672
|
clientRequests: Array<ClientToolRequest>,
|
|
2673
|
+
genericRequests: ReadonlyArray<
|
|
2674
|
+
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2675
|
+
> = [],
|
|
2676
|
+
genericInterruptIds?: ReadonlyArray<string>,
|
|
2288
2677
|
): StreamChunk {
|
|
2289
2678
|
return {
|
|
2290
2679
|
...finishEvent,
|
|
2291
2680
|
timestamp: Date.now(),
|
|
2292
2681
|
outcome: {
|
|
2293
2682
|
type: 'interrupt',
|
|
2294
|
-
interrupts: this.buildActionableInterrupts(
|
|
2683
|
+
interrupts: this.buildActionableInterrupts(
|
|
2684
|
+
approvals,
|
|
2685
|
+
clientRequests,
|
|
2686
|
+
genericRequests,
|
|
2687
|
+
genericInterruptIds,
|
|
2688
|
+
),
|
|
2295
2689
|
},
|
|
2296
2690
|
}
|
|
2297
2691
|
}
|
|
@@ -2309,7 +2703,8 @@ class TextEngine<
|
|
|
2309
2703
|
message.id ||
|
|
2310
2704
|
`snapshot_${this.runIdOverride ?? this.requestId}_${index}`
|
|
2311
2705
|
const parts =
|
|
2312
|
-
message.role === 'assistant' &&
|
|
2706
|
+
message.role === 'assistant' &&
|
|
2707
|
+
(message.thinking?.length || message.structuredOutput)
|
|
2313
2708
|
? modelMessageToUIMessage(message, id).parts
|
|
2314
2709
|
: undefined
|
|
2315
2710
|
return {
|
|
@@ -2440,9 +2835,22 @@ class TextEngine<
|
|
|
2440
2835
|
finishEvent: RunFinishedEvent,
|
|
2441
2836
|
approvals: Array<ApprovalRequest>,
|
|
2442
2837
|
clientRequests: Array<ClientToolRequest>,
|
|
2838
|
+
genericRequests: ReadonlyArray<
|
|
2839
|
+
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2840
|
+
> = [],
|
|
2443
2841
|
): AsyncGenerator<StreamChunk, boolean, void> {
|
|
2842
|
+
yield* this.emitSyntheticRunStarted(finishEvent)
|
|
2843
|
+
const genericInterruptIds = genericRequests.map(() =>
|
|
2844
|
+
this.genericInterruptId(),
|
|
2845
|
+
)
|
|
2444
2846
|
const terminal = this.completeEphemeralInterruptBindings(
|
|
2445
|
-
this.buildInterruptFinishedChunk(
|
|
2847
|
+
this.buildInterruptFinishedChunk(
|
|
2848
|
+
finishEvent,
|
|
2849
|
+
approvals,
|
|
2850
|
+
clientRequests,
|
|
2851
|
+
genericRequests,
|
|
2852
|
+
genericInterruptIds,
|
|
2853
|
+
),
|
|
2446
2854
|
)
|
|
2447
2855
|
let terminalOutputs: Array<StreamChunk>
|
|
2448
2856
|
try {
|
|
@@ -2471,6 +2879,103 @@ class TextEngine<
|
|
|
2471
2879
|
return true
|
|
2472
2880
|
}
|
|
2473
2881
|
|
|
2882
|
+
private async *emitBoundaryInterrupts(
|
|
2883
|
+
phase: 'beforeModel' | 'afterModel' | 'beforeTools' | 'afterTools',
|
|
2884
|
+
finishEvent: RunFinishedEvent,
|
|
2885
|
+
toolCalls: ReadonlyArray<ToolCall> = [],
|
|
2886
|
+
requests?: ReadonlyArray<
|
|
2887
|
+
GenericInterruptRequest<InterruptDefinition<any, any, any, any>>
|
|
2888
|
+
>,
|
|
2889
|
+
): AsyncGenerator<StreamChunk, boolean, void> {
|
|
2890
|
+
this.middlewareCtx.phase = phase
|
|
2891
|
+
const boundaryRequests =
|
|
2892
|
+
requests ??
|
|
2893
|
+
(await this.middlewareRunner.runOnInterruptBoundary(
|
|
2894
|
+
this.middlewareCtx as ChatMiddlewareContext<TContext> & {
|
|
2895
|
+
phase: typeof phase
|
|
2896
|
+
},
|
|
2897
|
+
))
|
|
2898
|
+
if (boundaryRequests.length === 0) return false
|
|
2899
|
+
for (const request of boundaryRequests) {
|
|
2900
|
+
if (
|
|
2901
|
+
this.interruptDefinitions.get(request.definition.id) !==
|
|
2902
|
+
request.definition
|
|
2903
|
+
) {
|
|
2904
|
+
throw new Error(
|
|
2905
|
+
`Generic interrupt definition ${request.definition.id} is not registered on this chat.`,
|
|
2906
|
+
)
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
if (phase === 'afterModel') {
|
|
2910
|
+
if (this.toolCallManager.hasToolCalls()) {
|
|
2911
|
+
this.addAssistantToolCallMessage(this.toolCallManager.getToolCalls())
|
|
2912
|
+
} else {
|
|
2913
|
+
this.addAssistantTextMessageForInterrupt()
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
const actionable = this.getBoundaryActionableToolRequests(toolCalls)
|
|
2917
|
+
yield* this.emitActionableInterruptBoundary(
|
|
2918
|
+
finishEvent,
|
|
2919
|
+
actionable.approvals,
|
|
2920
|
+
actionable.clientRequests,
|
|
2921
|
+
boundaryRequests,
|
|
2922
|
+
)
|
|
2923
|
+
return true
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
private addAssistantTextMessageForInterrupt(): void {
|
|
2927
|
+
if (this.accumulatedContent.length === 0) return
|
|
2928
|
+
this.messages = [
|
|
2929
|
+
...this.messages,
|
|
2930
|
+
{ role: 'assistant', content: this.accumulatedContent },
|
|
2931
|
+
]
|
|
2932
|
+
this.middlewareCtx.messages = this.messages
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
private getBoundaryActionableToolRequests(
|
|
2936
|
+
toolCalls: ReadonlyArray<ToolCall>,
|
|
2937
|
+
): {
|
|
2938
|
+
approvals: Array<ApprovalRequest>
|
|
2939
|
+
clientRequests: Array<ClientToolRequest>
|
|
2940
|
+
} {
|
|
2941
|
+
const { approvals, clientToolResults } = this.collectClientState()
|
|
2942
|
+
const approvalRequests: Array<ApprovalRequest> = []
|
|
2943
|
+
const clientRequests: Array<ClientToolRequest> = []
|
|
2944
|
+
for (const toolCall of toolCalls) {
|
|
2945
|
+
const tool = this.resolveExecutableTools([toolCall]).find(
|
|
2946
|
+
(candidate) => candidate.name === toolCall.function.name,
|
|
2947
|
+
) as RuntimeToolWithApproval | undefined
|
|
2948
|
+
if (!tool) continue
|
|
2949
|
+
let input: unknown = {}
|
|
2950
|
+
try {
|
|
2951
|
+
const parsed = JSON.parse(toolCall.function.arguments.trim() || '{}')
|
|
2952
|
+
input = parsed && typeof parsed === 'object' ? parsed : {}
|
|
2953
|
+
} catch {
|
|
2954
|
+
input = {}
|
|
2955
|
+
}
|
|
2956
|
+
const approvalId = `approval_${toolCall.id}`
|
|
2957
|
+
if (tool.needsApproval && !approvals.has(approvalId)) {
|
|
2958
|
+
approvalRequests.push({
|
|
2959
|
+
toolCallId: toolCall.id,
|
|
2960
|
+
toolName: toolCall.function.name,
|
|
2961
|
+
input,
|
|
2962
|
+
approvalId,
|
|
2963
|
+
})
|
|
2964
|
+
} else if (
|
|
2965
|
+
!tool.execute &&
|
|
2966
|
+
!clientToolResults.has(toolCall.id) &&
|
|
2967
|
+
!this.resumeCancelledToolCallIds.has(toolCall.id)
|
|
2968
|
+
) {
|
|
2969
|
+
clientRequests.push({
|
|
2970
|
+
toolCallId: toolCall.id,
|
|
2971
|
+
toolName: toolCall.function.name,
|
|
2972
|
+
input,
|
|
2973
|
+
})
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
return { approvals: approvalRequests, clientRequests }
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2474
2979
|
private completeEphemeralInterruptBindings(chunk: StreamChunk): StreamChunk {
|
|
2475
2980
|
if (
|
|
2476
2981
|
chunk.type !== EventType.RUN_FINISHED ||
|
|
@@ -2718,7 +3223,7 @@ class TextEngine<
|
|
|
2718
3223
|
private createSyntheticFinishedEvent(): RunFinishedEvent {
|
|
2719
3224
|
return {
|
|
2720
3225
|
type: 'RUN_FINISHED',
|
|
2721
|
-
runId: this.
|
|
3226
|
+
runId: this.runIdOverride ?? this.requestId,
|
|
2722
3227
|
threadId: this.threadId,
|
|
2723
3228
|
model: this.params.model,
|
|
2724
3229
|
timestamp: Date.now(),
|
|
@@ -2997,6 +3502,7 @@ class TextEngine<
|
|
|
2997
3502
|
const buildSynthesizedStart = (timestamp = Date.now()): StreamChunk => {
|
|
2998
3503
|
const idForStart = structuredMessageId ?? generateMessageId()
|
|
2999
3504
|
structuredMessageId = idForStart
|
|
3505
|
+
this.captureStructuredOutputMessageIdentity(idForStart)
|
|
3000
3506
|
return {
|
|
3001
3507
|
type: EventType.CUSTOM,
|
|
3002
3508
|
name: 'structured-output.start',
|
|
@@ -3037,7 +3543,10 @@ class TextEngine<
|
|
|
3037
3543
|
// synthesized start (when needed) uses the SAME id the deltas carry
|
|
3038
3544
|
if (!structuredMessageId) {
|
|
3039
3545
|
const extracted = extractMessageId(chunk)
|
|
3040
|
-
if (extracted)
|
|
3546
|
+
if (extracted) {
|
|
3547
|
+
structuredMessageId = extracted
|
|
3548
|
+
this.captureStructuredOutputMessageIdentity(extracted)
|
|
3549
|
+
}
|
|
3041
3550
|
}
|
|
3042
3551
|
|
|
3043
3552
|
// Synthesis only matters for the streaming client path — the agentic
|
|
@@ -3104,7 +3613,13 @@ class TextEngine<
|
|
|
3104
3613
|
const object = this.finalStructuredOutput.normalize
|
|
3105
3614
|
? this.finalStructuredOutput.normalize(parsed.object)
|
|
3106
3615
|
: parsed.object
|
|
3107
|
-
this.structuredOutputResult = {
|
|
3616
|
+
this.structuredOutputResult = {
|
|
3617
|
+
data: object,
|
|
3618
|
+
rawText: parsed.raw,
|
|
3619
|
+
...(parsed.reasoning !== undefined
|
|
3620
|
+
? { reasoning: parsed.reasoning }
|
|
3621
|
+
: {}),
|
|
3622
|
+
}
|
|
3108
3623
|
// Rewrite the outbound event so the yielded chunk carries the
|
|
3109
3624
|
// normalized object (the original `chunk.value` still holds the
|
|
3110
3625
|
// widened one). Preserve every other field — `raw`, `reasoning` —
|
|
@@ -3555,7 +4070,15 @@ class TextEngine<
|
|
|
3555
4070
|
}
|
|
3556
4071
|
}
|
|
3557
4072
|
|
|
3558
|
-
const
|
|
4073
|
+
const genericPending = this.getGenericContinuationPending(interruptedRunId)
|
|
4074
|
+
const pending: Array<{
|
|
4075
|
+
interruptId: string
|
|
4076
|
+
payload: unknown
|
|
4077
|
+
binding: InterruptBinding
|
|
4078
|
+
genericRequest?: GenericInterruptRequest<
|
|
4079
|
+
InterruptDefinition<any, any, any, any>
|
|
4080
|
+
>
|
|
4081
|
+
}> = this.buildActionableInterrupts(
|
|
3559
4082
|
approvalRequests,
|
|
3560
4083
|
clientRequests,
|
|
3561
4084
|
).flatMap((descriptor) => {
|
|
@@ -3574,6 +4097,7 @@ class TextEngine<
|
|
|
3574
4097
|
]
|
|
3575
4098
|
: []
|
|
3576
4099
|
})
|
|
4100
|
+
pending.push(...genericPending)
|
|
3577
4101
|
const validated = await validateInterruptResumeBatch({
|
|
3578
4102
|
threadId: this.threadId,
|
|
3579
4103
|
interruptedRunId,
|
|
@@ -3601,6 +4125,185 @@ class TextEngine<
|
|
|
3601
4125
|
...validated.resumeToolState,
|
|
3602
4126
|
approvals,
|
|
3603
4127
|
})
|
|
4128
|
+
|
|
4129
|
+
const genericResolutions = validated.resumeToolState.genericInterrupts
|
|
4130
|
+
if (genericPending.length > 0 && genericResolutions) {
|
|
4131
|
+
const resolutions = genericPending
|
|
4132
|
+
.sort((left, right) => {
|
|
4133
|
+
const leftIndex =
|
|
4134
|
+
left.binding.kind === 'generic' ? (left.binding.batchIndex ?? 0) : 0
|
|
4135
|
+
const rightIndex =
|
|
4136
|
+
right.binding.kind === 'generic'
|
|
4137
|
+
? (right.binding.batchIndex ?? 0)
|
|
4138
|
+
: 0
|
|
4139
|
+
return leftIndex - rightIndex
|
|
4140
|
+
})
|
|
4141
|
+
.flatMap((record) => {
|
|
4142
|
+
const resolution = genericResolutions.get(record.interruptId)
|
|
4143
|
+
if (!resolution || !record.genericRequest) return []
|
|
4144
|
+
return [
|
|
4145
|
+
resolution.status === 'resolved'
|
|
4146
|
+
? {
|
|
4147
|
+
request: record.genericRequest,
|
|
4148
|
+
status: 'resolved' as const,
|
|
4149
|
+
response: resolution.payload,
|
|
4150
|
+
}
|
|
4151
|
+
: {
|
|
4152
|
+
request: record.genericRequest,
|
|
4153
|
+
status: 'cancelled' as const,
|
|
4154
|
+
},
|
|
4155
|
+
]
|
|
4156
|
+
})
|
|
4157
|
+
const collection: InterruptResolutionCollection = {
|
|
4158
|
+
for: (definition) =>
|
|
4159
|
+
resolutions.filter(
|
|
4160
|
+
(resolution) => resolution.request.definition === definition,
|
|
4161
|
+
) as never,
|
|
4162
|
+
all: (
|
|
4163
|
+
...definitions: Array<InterruptDefinition<any, any, any, any>>
|
|
4164
|
+
) =>
|
|
4165
|
+
definitions.length === 0
|
|
4166
|
+
? resolutions
|
|
4167
|
+
: resolutions.filter((resolution) =>
|
|
4168
|
+
definitions.includes(resolution.request.definition),
|
|
4169
|
+
),
|
|
4170
|
+
}
|
|
4171
|
+
const policy = await this.middlewareRunner.runOnInterruptResolution(
|
|
4172
|
+
this.middlewareCtx,
|
|
4173
|
+
collection,
|
|
4174
|
+
)
|
|
4175
|
+
if (policy.toolResume === 'stop') {
|
|
4176
|
+
this.earlyTermination = true
|
|
4177
|
+
} else if (policy.toolResume === 'cancel') {
|
|
4178
|
+
for (const request of pendingToolCalls) {
|
|
4179
|
+
this.resumeCancelledToolCallIds.add(request.id)
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
|
|
4185
|
+
private getGenericContinuationPending(interruptedRunId: string): Array<{
|
|
4186
|
+
interruptId: string
|
|
4187
|
+
payload: unknown
|
|
4188
|
+
binding: InterruptBinding
|
|
4189
|
+
genericRequest: GenericInterruptRequest<
|
|
4190
|
+
InterruptDefinition<any, any, any, any>
|
|
4191
|
+
>
|
|
4192
|
+
}> {
|
|
4193
|
+
const fail = (message: string): never => {
|
|
4194
|
+
throw new InterruptResumeValidationError([
|
|
4195
|
+
{
|
|
4196
|
+
scope: 'batch',
|
|
4197
|
+
threadId: this.threadId,
|
|
4198
|
+
interruptedRunId,
|
|
4199
|
+
generation: 0,
|
|
4200
|
+
interruptIds: [],
|
|
4201
|
+
code: 'stale',
|
|
4202
|
+
message,
|
|
4203
|
+
source: 'server',
|
|
4204
|
+
retryable: false,
|
|
4205
|
+
},
|
|
4206
|
+
])
|
|
4207
|
+
}
|
|
4208
|
+
const pending: Array<{
|
|
4209
|
+
interruptId: string
|
|
4210
|
+
payload: unknown
|
|
4211
|
+
binding: InterruptBinding
|
|
4212
|
+
genericRequest: GenericInterruptRequest<
|
|
4213
|
+
InterruptDefinition<any, any, any, any>
|
|
4214
|
+
>
|
|
4215
|
+
}> = []
|
|
4216
|
+
const ids = new Set<string>()
|
|
4217
|
+
const batchIndexes = new Set<number>()
|
|
4218
|
+
for (const resumeItem of this.params.resume ?? []) {
|
|
4219
|
+
const parsed = readGenericInterruptContinuation(resumeItem.metadata)
|
|
4220
|
+
if (parsed.status === 'absent') continue
|
|
4221
|
+
if (parsed.status === 'invalid') {
|
|
4222
|
+
return fail(parsed.message)
|
|
4223
|
+
}
|
|
4224
|
+
const entry = parsed.value
|
|
4225
|
+
const id = resumeItem.interruptId
|
|
4226
|
+
const definition = this.interruptDefinitions.get(entry.definitionId)
|
|
4227
|
+
if (!definition) {
|
|
4228
|
+
return fail(
|
|
4229
|
+
`Generic interrupt definition ${entry.definitionId} is unavailable.`,
|
|
4230
|
+
)
|
|
4231
|
+
}
|
|
4232
|
+
if (ids.has(id) || batchIndexes.has(entry.batchIndex)) {
|
|
4233
|
+
return fail(
|
|
4234
|
+
'Generic interrupt continuation contains duplicate entries.',
|
|
4235
|
+
)
|
|
4236
|
+
}
|
|
4237
|
+
ids.add(id)
|
|
4238
|
+
batchIndexes.add(entry.batchIndex)
|
|
4239
|
+
let request: GenericInterruptRequest<
|
|
4240
|
+
InterruptDefinition<any, any, any, any>
|
|
4241
|
+
>
|
|
4242
|
+
try {
|
|
4243
|
+
request = rehydrateInterruptRequest(definition, {
|
|
4244
|
+
key: entry.key,
|
|
4245
|
+
reason: entry.reason,
|
|
4246
|
+
message: entry.message,
|
|
4247
|
+
...(typeof entry.expiresAt === 'string'
|
|
4248
|
+
? { expiresAt: entry.expiresAt }
|
|
4249
|
+
: {}),
|
|
4250
|
+
...(Object.prototype.hasOwnProperty.call(entry, 'payload')
|
|
4251
|
+
? { payload: entry.payload }
|
|
4252
|
+
: {}),
|
|
4253
|
+
})
|
|
4254
|
+
} catch (error) {
|
|
4255
|
+
return fail(
|
|
4256
|
+
`Generic interrupt continuation ${id} is invalid: ${
|
|
4257
|
+
error instanceof Error ? error.message : String(error)
|
|
4258
|
+
}`,
|
|
4259
|
+
)
|
|
4260
|
+
}
|
|
4261
|
+
const emitted = createInterruptBinding(request, {
|
|
4262
|
+
batchIndex: entry.batchIndex,
|
|
4263
|
+
})
|
|
4264
|
+
if (
|
|
4265
|
+
entry.responseSchemaHash !== emitted.descriptor.responseSchemaHash ||
|
|
4266
|
+
entry.payloadSchemaHash !== emitted.descriptor.payloadSchemaHash
|
|
4267
|
+
) {
|
|
4268
|
+
return fail(
|
|
4269
|
+
`Generic interrupt continuation ${id} does not match its definition.`,
|
|
4270
|
+
)
|
|
4271
|
+
}
|
|
4272
|
+
pending.push({
|
|
4273
|
+
interruptId: id,
|
|
4274
|
+
payload: {
|
|
4275
|
+
id,
|
|
4276
|
+
...(emitted.descriptor.responseSchemaCanonicalJson !== undefined
|
|
4277
|
+
? {
|
|
4278
|
+
responseSchema: JSON.parse(
|
|
4279
|
+
emitted.descriptor.responseSchemaCanonicalJson,
|
|
4280
|
+
),
|
|
4281
|
+
}
|
|
4282
|
+
: {}),
|
|
4283
|
+
},
|
|
4284
|
+
binding: {
|
|
4285
|
+
v: INTERRUPT_BINDING_VERSION,
|
|
4286
|
+
kind: 'generic',
|
|
4287
|
+
interruptId: id,
|
|
4288
|
+
interruptedRunId,
|
|
4289
|
+
generation: 0,
|
|
4290
|
+
definitionId: entry.definitionId,
|
|
4291
|
+
key: entry.key,
|
|
4292
|
+
batchIndex: entry.batchIndex,
|
|
4293
|
+
...(typeof entry.expiresAt === 'string'
|
|
4294
|
+
? { expiresAt: entry.expiresAt }
|
|
4295
|
+
: {}),
|
|
4296
|
+
...(emitted.descriptor.payloadSchemaHash
|
|
4297
|
+
? { payloadSchemaHash: emitted.descriptor.payloadSchemaHash }
|
|
4298
|
+
: {}),
|
|
4299
|
+
...(entry.responseSchemaHash !== undefined
|
|
4300
|
+
? { responseSchemaHash: entry.responseSchemaHash }
|
|
4301
|
+
: {}),
|
|
4302
|
+
},
|
|
4303
|
+
genericRequest: request,
|
|
4304
|
+
})
|
|
4305
|
+
}
|
|
4306
|
+
return pending
|
|
3604
4307
|
}
|
|
3605
4308
|
|
|
3606
4309
|
private applyResumeToolState(state: ChatResumeToolState | undefined): void {
|
|
@@ -3624,6 +4327,58 @@ class TextEngine<
|
|
|
3624
4327
|
this.resumeCancelledToolCallIds.add(toolCallId)
|
|
3625
4328
|
}
|
|
3626
4329
|
}
|
|
4330
|
+
if (state?.genericInterrupts) {
|
|
4331
|
+
for (const [interruptId, resolution] of state.genericInterrupts) {
|
|
4332
|
+
this.resumeGenericInterrupts.set(interruptId, resolution)
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
if (state?.genericInterruptRequests) {
|
|
4336
|
+
for (const [interruptId, request] of state.genericInterruptRequests) {
|
|
4337
|
+
this.resumeGenericInterruptRequests.set(interruptId, request)
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
private async applyDurableGenericInterruptResolution(): Promise<void> {
|
|
4343
|
+
if (this.resumeGenericInterruptRequests.size === 0) return
|
|
4344
|
+
const resolutions = [
|
|
4345
|
+
...this.resumeGenericInterruptRequests.entries(),
|
|
4346
|
+
].flatMap(([interruptId, request]) => {
|
|
4347
|
+
const resolution = this.resumeGenericInterrupts.get(interruptId)
|
|
4348
|
+
if (!resolution) return []
|
|
4349
|
+
return [
|
|
4350
|
+
resolution.status === 'resolved'
|
|
4351
|
+
? {
|
|
4352
|
+
request,
|
|
4353
|
+
status: 'resolved' as const,
|
|
4354
|
+
response: resolution.payload,
|
|
4355
|
+
}
|
|
4356
|
+
: { request, status: 'cancelled' as const },
|
|
4357
|
+
]
|
|
4358
|
+
})
|
|
4359
|
+
const collection: InterruptResolutionCollection = {
|
|
4360
|
+
for: (definition) =>
|
|
4361
|
+
resolutions.filter(
|
|
4362
|
+
(resolution) => resolution.request.definition === definition,
|
|
4363
|
+
) as never,
|
|
4364
|
+
all: (...definitions: Array<InterruptDefinition<any, any, any, any>>) =>
|
|
4365
|
+
definitions.length === 0
|
|
4366
|
+
? resolutions
|
|
4367
|
+
: resolutions.filter((resolution) =>
|
|
4368
|
+
definitions.includes(resolution.request.definition),
|
|
4369
|
+
),
|
|
4370
|
+
}
|
|
4371
|
+
const policy = await this.middlewareRunner.runOnInterruptResolution(
|
|
4372
|
+
this.middlewareCtx,
|
|
4373
|
+
collection,
|
|
4374
|
+
)
|
|
4375
|
+
if (policy.toolResume === 'stop') {
|
|
4376
|
+
this.earlyTermination = true
|
|
4377
|
+
} else if (policy.toolResume === 'cancel') {
|
|
4378
|
+
for (const toolCall of this.getPendingToolCallsFromMessages()) {
|
|
4379
|
+
this.resumeCancelledToolCallIds.add(toolCall.id)
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
3627
4382
|
}
|
|
3628
4383
|
|
|
3629
4384
|
private applyMiddlewareConfig(config: ChatMiddlewareConfig): void {
|
|
@@ -3662,6 +4417,9 @@ class TextEngine<
|
|
|
3662
4417
|
chunk,
|
|
3663
4418
|
)
|
|
3664
4419
|
for (const outputChunk of outputChunks) {
|
|
4420
|
+
if (outputChunk.type === EventType.RUN_STARTED) {
|
|
4421
|
+
this.hasPublicRunStarted = true
|
|
4422
|
+
}
|
|
3665
4423
|
yield outputChunk
|
|
3666
4424
|
this.middlewareCtx.chunkIndex++
|
|
3667
4425
|
}
|
|
@@ -3802,67 +4560,136 @@ export function chat<
|
|
|
3802
4560
|
TStream,
|
|
3803
4561
|
any
|
|
3804
4562
|
>['tools'] = TextActivityOptions<TAdapter, TSchema, TStream, any>['tools'],
|
|
3805
|
-
const
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
>['middleware'] = TextActivityOptions<
|
|
3811
|
-
TAdapter,
|
|
3812
|
-
TSchema,
|
|
3813
|
-
TStream,
|
|
3814
|
-
any
|
|
3815
|
-
>['middleware'],
|
|
4563
|
+
const TInterrupts extends ReadonlyArray<
|
|
4564
|
+
InterruptDefinition<any, any, any, any>
|
|
4565
|
+
> = [],
|
|
4566
|
+
TContext = unknown,
|
|
4567
|
+
const TMiddleware extends Array<unknown> | undefined = undefined,
|
|
3816
4568
|
>(
|
|
3817
4569
|
options: TextActivityOptionsWithContext<
|
|
3818
4570
|
TAdapter,
|
|
3819
4571
|
TSchema,
|
|
3820
4572
|
TStream,
|
|
3821
4573
|
TTools,
|
|
4574
|
+
TInterrupts,
|
|
4575
|
+
TContext,
|
|
3822
4576
|
TMiddleware
|
|
3823
4577
|
>,
|
|
3824
4578
|
): TextActivityResult<TSchema, TStream, TTools> {
|
|
3825
|
-
|
|
4579
|
+
validateInterruptDefinitions(options.interrupts)
|
|
4580
|
+
validateCapabilities(
|
|
4581
|
+
readRuntimeMiddleware(options.middleware) ?? [],
|
|
4582
|
+
options.adapter,
|
|
4583
|
+
)
|
|
3826
4584
|
if (options.tools) {
|
|
3827
4585
|
assertUniqueToolNames(options.tools)
|
|
3828
4586
|
}
|
|
3829
4587
|
|
|
3830
4588
|
const { outputSchema, stream } = options
|
|
3831
4589
|
|
|
3832
|
-
// outputSchema + stream:true is the only branch that streams structured
|
|
3833
|
-
// output. Without an explicit `stream: true`, schema-bearing calls run the
|
|
3834
|
-
// agent loop and resolve to a typed Promise<InferSchemaType<TSchema>>.
|
|
3835
4590
|
if (outputSchema && stream === true) {
|
|
3836
|
-
return runStreamingStructuredOutput(
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
4591
|
+
return runStreamingStructuredOutput(
|
|
4592
|
+
toRuntimeTextActivityOptions(options, {
|
|
4593
|
+
outputSchema,
|
|
4594
|
+
stream: true,
|
|
4595
|
+
}),
|
|
4596
|
+
) as TextActivityResult<TSchema, TStream, TTools>
|
|
3841
4597
|
}
|
|
3842
4598
|
|
|
3843
|
-
// If outputSchema is provided, run agentic structured output (Promise<T>)
|
|
3844
4599
|
if (outputSchema) {
|
|
3845
|
-
return runAgenticStructuredOutput(
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
4600
|
+
return runAgenticStructuredOutput(
|
|
4601
|
+
toRuntimeTextActivityOptions(options, {
|
|
4602
|
+
outputSchema,
|
|
4603
|
+
stream: false,
|
|
4604
|
+
}),
|
|
4605
|
+
) as TextActivityResult<TSchema, TStream, TTools>
|
|
3849
4606
|
}
|
|
3850
4607
|
|
|
3851
|
-
// If stream is explicitly false, run non-streaming text
|
|
3852
4608
|
if (stream === false) {
|
|
3853
|
-
return runNonStreamingText(
|
|
3854
|
-
|
|
4609
|
+
return runNonStreamingText(
|
|
4610
|
+
toRuntimeTextActivityOptions(options, {
|
|
4611
|
+
outputSchema: undefined,
|
|
4612
|
+
stream: false,
|
|
4613
|
+
}),
|
|
4614
|
+
) as TextActivityResult<TSchema, TStream, TTools>
|
|
4615
|
+
}
|
|
4616
|
+
|
|
4617
|
+
return runStreamingText(
|
|
4618
|
+
toRuntimeTextActivityOptions(options, {
|
|
3855
4619
|
outputSchema: undefined,
|
|
3856
|
-
stream,
|
|
3857
|
-
})
|
|
4620
|
+
stream: true,
|
|
4621
|
+
}),
|
|
4622
|
+
) as TextActivityResult<TSchema, TStream, TTools>
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4625
|
+
type RuntimeTextActivityOptions<
|
|
4626
|
+
TAdapter extends AnyTextAdapter,
|
|
4627
|
+
TSchema extends SchemaInput | undefined,
|
|
4628
|
+
TStream extends boolean,
|
|
4629
|
+
> = Omit<TextActivityOptions<TAdapter, TSchema, TStream, any>, 'middleware'> & {
|
|
4630
|
+
middleware?: Array<AnyChatMiddleware>
|
|
4631
|
+
}
|
|
4632
|
+
|
|
4633
|
+
function readRuntimeMiddleware(
|
|
4634
|
+
middleware: unknown,
|
|
4635
|
+
): Array<AnyChatMiddleware> | undefined {
|
|
4636
|
+
if (middleware === undefined) return undefined
|
|
4637
|
+
if (!Array.isArray(middleware)) {
|
|
4638
|
+
throw new TypeError('Chat middleware must be an array.')
|
|
3858
4639
|
}
|
|
4640
|
+
return middleware
|
|
4641
|
+
}
|
|
3859
4642
|
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
4643
|
+
function toRuntimeTextActivityOptions<
|
|
4644
|
+
TAdapter extends AnyTextAdapter,
|
|
4645
|
+
TInputSchema extends SchemaInput | undefined,
|
|
4646
|
+
TInputStream extends boolean,
|
|
4647
|
+
TOutputSchema extends SchemaInput | undefined,
|
|
4648
|
+
TOutputStream extends boolean,
|
|
4649
|
+
TTools extends TextActivityOptions<
|
|
4650
|
+
TAdapter,
|
|
4651
|
+
TInputSchema,
|
|
4652
|
+
TInputStream,
|
|
4653
|
+
any
|
|
4654
|
+
>['tools'],
|
|
4655
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>,
|
|
4656
|
+
TContext,
|
|
4657
|
+
TMiddleware extends Array<unknown> | undefined,
|
|
4658
|
+
>(
|
|
4659
|
+
options: TextActivityOptionsWithContext<
|
|
4660
|
+
TAdapter,
|
|
4661
|
+
TInputSchema,
|
|
4662
|
+
TInputStream,
|
|
4663
|
+
TTools,
|
|
4664
|
+
TInterrupts,
|
|
4665
|
+
TContext,
|
|
4666
|
+
TMiddleware
|
|
4667
|
+
>,
|
|
4668
|
+
overrides: { outputSchema: TOutputSchema; stream: TOutputStream },
|
|
4669
|
+
): RuntimeTextActivityOptions<TAdapter, TOutputSchema, TOutputStream> {
|
|
4670
|
+
const { middleware, ...rest } = options
|
|
4671
|
+
return {
|
|
4672
|
+
...rest,
|
|
4673
|
+
...overrides,
|
|
4674
|
+
...(middleware === undefined
|
|
4675
|
+
? {}
|
|
4676
|
+
: { middleware: readRuntimeMiddleware(middleware) }),
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
|
|
4680
|
+
function validateInterruptDefinitions(
|
|
4681
|
+
definitions:
|
|
4682
|
+
| ReadonlyArray<InterruptDefinition<any, any, any, any>>
|
|
4683
|
+
| undefined,
|
|
4684
|
+
): void {
|
|
4685
|
+
if (!definitions) return
|
|
4686
|
+
const seen = new Set<string>()
|
|
4687
|
+
for (const definition of definitions) {
|
|
4688
|
+
if (seen.has(definition.id)) {
|
|
4689
|
+
throw new Error(`Duplicate interrupt definition id: ${definition.id}`)
|
|
4690
|
+
}
|
|
4691
|
+
seen.add(definition.id)
|
|
4692
|
+
}
|
|
3866
4693
|
}
|
|
3867
4694
|
|
|
3868
4695
|
/**
|
|
@@ -3914,8 +4741,8 @@ function publishDeliverySeams(
|
|
|
3914
4741
|
* returns, so the identity has to be minted out here and the engine reached back
|
|
3915
4742
|
* through `engineRef`, which the body fills as soon as its engine exists.
|
|
3916
4743
|
*/
|
|
3917
|
-
function runStreamingText
|
|
3918
|
-
options:
|
|
4744
|
+
function runStreamingText(
|
|
4745
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, undefined, boolean>,
|
|
3919
4746
|
): AsyncIterable<StreamChunk> {
|
|
3920
4747
|
const engineRef: DeliveryEngineRef = {}
|
|
3921
4748
|
const stream = streamTextChunks(options, engineRef)
|
|
@@ -3923,8 +4750,8 @@ function runStreamingText<TContext = unknown>(
|
|
|
3923
4750
|
return stream
|
|
3924
4751
|
}
|
|
3925
4752
|
|
|
3926
|
-
async function* streamTextChunks
|
|
3927
|
-
options:
|
|
4753
|
+
async function* streamTextChunks(
|
|
4754
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, undefined, boolean>,
|
|
3928
4755
|
engineRef: DeliveryEngineRef,
|
|
3929
4756
|
): AsyncIterable<StreamChunk> {
|
|
3930
4757
|
const { adapter, middleware, context, debug, mcp, ...textOptions } = options
|
|
@@ -3943,7 +4770,7 @@ async function* streamTextChunks<TContext = unknown>(
|
|
|
3943
4770
|
params: { ...textOptions, model, logger } as TextOptions<
|
|
3944
4771
|
Record<string, any>,
|
|
3945
4772
|
Record<string, any>,
|
|
3946
|
-
|
|
4773
|
+
any
|
|
3947
4774
|
>,
|
|
3948
4775
|
middleware,
|
|
3949
4776
|
context,
|
|
@@ -3965,19 +4792,13 @@ async function* streamTextChunks<TContext = unknown>(
|
|
|
3965
4792
|
* Run non-streaming text - collects all content and returns as a string.
|
|
3966
4793
|
* Runs the full agentic loop (if tools are provided) but returns collected text.
|
|
3967
4794
|
*/
|
|
3968
|
-
function runNonStreamingText
|
|
3969
|
-
options:
|
|
4795
|
+
function runNonStreamingText(
|
|
4796
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, undefined, false>,
|
|
3970
4797
|
): Promise<string> {
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
AnyTextAdapter,
|
|
3976
|
-
undefined,
|
|
3977
|
-
true,
|
|
3978
|
-
TContext
|
|
3979
|
-
>,
|
|
3980
|
-
)
|
|
4798
|
+
const stream = runStreamingText({
|
|
4799
|
+
...options,
|
|
4800
|
+
stream: true,
|
|
4801
|
+
})
|
|
3981
4802
|
|
|
3982
4803
|
return streamToText(stream)
|
|
3983
4804
|
}
|
|
@@ -3988,11 +4809,8 @@ function runNonStreamingText<TContext = unknown>(
|
|
|
3988
4809
|
* 2. Once complete, call adapter.structuredOutput with the conversation context
|
|
3989
4810
|
* 3. Validate and return the structured result
|
|
3990
4811
|
*/
|
|
3991
|
-
async function runAgenticStructuredOutput<
|
|
3992
|
-
|
|
3993
|
-
TContext = unknown,
|
|
3994
|
-
>(
|
|
3995
|
-
options: TextActivityOptions<AnyTextAdapter, TSchema, boolean, TContext>,
|
|
4812
|
+
async function runAgenticStructuredOutput<TSchema extends SchemaInput>(
|
|
4813
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, TSchema, boolean>,
|
|
3996
4814
|
): Promise<InferSchemaType<TSchema>> {
|
|
3997
4815
|
const {
|
|
3998
4816
|
adapter,
|
|
@@ -4063,7 +4881,7 @@ async function runAgenticStructuredOutput<
|
|
|
4063
4881
|
params: { ...textOptions, model, logger } as TextOptions<
|
|
4064
4882
|
Record<string, unknown>,
|
|
4065
4883
|
Record<string, unknown>,
|
|
4066
|
-
|
|
4884
|
+
any
|
|
4067
4885
|
>,
|
|
4068
4886
|
middleware,
|
|
4069
4887
|
context,
|
|
@@ -4126,6 +4944,15 @@ async function runAgenticStructuredOutput<
|
|
|
4126
4944
|
* Uses an `unknown`-input runtime check rather than `as` casts so the engine
|
|
4127
4945
|
* stays cast-free in its hot path.
|
|
4128
4946
|
*/
|
|
4947
|
+
function readCustomEventMessageId(value: unknown): string | undefined {
|
|
4948
|
+
if (typeof value !== 'object' || value === null) return undefined
|
|
4949
|
+
if (!('messageId' in value)) return undefined
|
|
4950
|
+
const messageId = value.messageId
|
|
4951
|
+
return typeof messageId === 'string' && messageId !== ''
|
|
4952
|
+
? messageId
|
|
4953
|
+
: undefined
|
|
4954
|
+
}
|
|
4955
|
+
|
|
4129
4956
|
function readStructuredOutputCompleteValue(
|
|
4130
4957
|
value: unknown,
|
|
4131
4958
|
): { object: unknown; raw: string; reasoning?: string } | null {
|
|
@@ -4271,11 +5098,8 @@ async function* fallbackStructuredOutputStream(
|
|
|
4271
5098
|
* synchronously at call time rather than as a yielded RUN_ERROR mid-stream —
|
|
4272
5099
|
* those are programmer errors, not runtime conditions.
|
|
4273
5100
|
*/
|
|
4274
|
-
function runStreamingStructuredOutput<
|
|
4275
|
-
|
|
4276
|
-
TContext = unknown,
|
|
4277
|
-
>(
|
|
4278
|
-
options: TextActivityOptions<AnyTextAdapter, TSchema, true, TContext>,
|
|
5101
|
+
function runStreamingStructuredOutput<TSchema extends SchemaInput>(
|
|
5102
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, TSchema, true>,
|
|
4279
5103
|
): StructuredOutputStream<InferSchemaType<TSchema>> {
|
|
4280
5104
|
const { outputSchema } = options
|
|
4281
5105
|
|
|
@@ -4336,11 +5160,8 @@ type StructuredOutputStreamInternal<T> = AsyncIterable<
|
|
|
4336
5160
|
StreamChunk | StructuredOutputCompleteEvent<T>
|
|
4337
5161
|
>
|
|
4338
5162
|
|
|
4339
|
-
async function* runStreamingStructuredOutputImpl<
|
|
4340
|
-
|
|
4341
|
-
TContext = unknown,
|
|
4342
|
-
>(
|
|
4343
|
-
options: TextActivityOptions<AnyTextAdapter, TSchema, true, TContext>,
|
|
5163
|
+
async function* runStreamingStructuredOutputImpl<TSchema extends SchemaInput>(
|
|
5164
|
+
options: RuntimeTextActivityOptions<AnyTextAdapter, TSchema, true>,
|
|
4344
5165
|
jsonSchema: NonNullable<ReturnType<typeof convertSchemaToJsonSchema>>,
|
|
4345
5166
|
normalize: (data: unknown) => unknown,
|
|
4346
5167
|
engineRef: DeliveryEngineRef,
|
|
@@ -4383,7 +5204,7 @@ async function* runStreamingStructuredOutputImpl<
|
|
|
4383
5204
|
params: { ...textOptions, model, logger } as TextOptions<
|
|
4384
5205
|
Record<string, unknown>,
|
|
4385
5206
|
Record<string, unknown>,
|
|
4386
|
-
|
|
5207
|
+
any
|
|
4387
5208
|
>,
|
|
4388
5209
|
middleware,
|
|
4389
5210
|
context,
|